165、【Agent】【OpenCode】TuiThreadCmd(代理 Fetch 实现)

【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除

标题

165、【Agent】【OpenCode】TuiThreadCmd(代理 Fetch 实现)

背景

上篇 blog

【Agent】【OpenCode】TuiThreadCmd(工厂设计对比)

分析了 RPC 泛型推导是"造数据/造类型 ",而 createWorkerFetch 是"造函数/造行为 ",它们都用了工厂模式,但解决的问题和类型系统的玩法完全不同,并对比了它们的核心差异,最后总结两者都是工厂,但 RPC 泛型工厂的核心价值是 "类型推导 "------让 TS 知道有什么方法;createWorkerFetch 的核心价值是 "接口兼容 "------让代码感觉不到底层被换了 ;接着对比了 C 语言对这种设计模式的支持,C 语言能实现"工厂返回函数 "这个运行时行为,但完全无法实现 typeof fetch 这种编译时契约,并分析了 C 语言做不到的部分,TS 的 createWorkerFetch 之所以优雅,是因为箭头函数自动把 client 关进闭包,返回的函数可以直接当 fetch 用,createWorkerFetch 的设计精髓是返回的东西和原生 fetch 签名一模一样,C 因为没有闭包,要么破坏签名(加 ctx 参数),要么引入全局状态。两条路都违背了"透明替换"的设计目标,下面继续分析

OpenCode

上篇 blog 提到了 C 语言必须精确匹配 typedef,无鸭子类型 ,"鸭子类型"这个名字来自一句著名的俗语:

"如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。"

(If it walks like a duck and quacks like a duck, then it must be a duck.)

在编程中,它的核心含义是:只关心对象"能做什么"(有什么方法/属性),不关心对象"是什么"(属于哪个类/接口)


🦆 用 fetch 例子来理解

TypeScript(有鸭子类型 = 结构化类型

javascript 复制代码
// 原生 fetch 的签名
type FetchFn = (input: string) => Promise<Response>

// 自己写的函数,只要参数和返回值形状一样,就能直接赋值
const myFetch = async (url: string) => new Response("ok")

const fn: FetchFn = myFetch  // ✅ 通过!没继承、没 implements,纯靠形状匹配

TS 编译器不看 myFetch 的"出身 ",只看它有没有 (string) => Promise<Response> 这个形状。这就是鸭子类型


C 语言(无鸭子类型 = 名义类型

c 复制代码
typedef Response* (*FetchFn)(const char* url);

Response* myFetch(const char* url) { return NULL; }

FetchFn fn = myFetch;  // ⚠️ 虽然签名一模一样,但必须精确匹配 typedef
                       // 如果参数用了 char* 而不是 const char*,直接报错

C 要求名义上的精确匹配哪怕两个函数行为完全相同,只要类型定义的名字或修饰符有一丝不同,就不兼容 。它不认"形状 ",只认"身份证"。


📊 一张表看懂区别

鸭子类型 (结构化) 非鸭子类型 (名义化)
判断依据 有没有对应的方法/属性 是否声明了同一个类/接口名
代表语言 TypeScript, Go, Python, Ruby C, Java, C#, Rust
类比 看能力发上岗证 看毕业证发上岗证
灵活性 高:不同来源的对象可互换 低:必须显式声明关系
安全性 运行时可能翻车(Python) 编译时安全(TS/Go) 编译期严格保证

💡 为什么这对 createWorkerFetch 很重要?

createWorkerFetch设计目标是透明替换原生 fetch。这意味着:

  • 有鸭子类型的语言 :返回的假 fetch 只要"长得像 "真 fetch,所有第三方库(axios、React Query 等)都会无条件接受它。不需要改任何第三方代码
  • 没有鸭子类型的语言 :必须让假 fetch 和真 fetch 共享同一个显式声明的类型/接口 。如果第三方库用的是自己的函数指针 typedef,就得写适配层 或者修改第三方源码

一句话总结

鸭子类型就是 "以貌取人 "------不管你是谁,只要长得符合这个岗位的要求,就可以上岗。C 语言是 "查户口"------能力再强,户口本上没写这个名字,就不认。


下面看这个代理 fetch 的详细内容

这个代理 fetch 的核心功能是:将标准的 HTTP Fetch 请求,翻译并转发到自定义的 RPC 通道中执行 ,其本质上是一个协议适配器 ,让上层代码可以用熟悉的 fetch API 发请求,但底层实际走的是 RpcClient 定义的私有通信协议,下面逐层拆解功能:


  1. 请求归一化
javascript 复制代码
const request = new Request(input, init)
  • 功能 :利用浏览器/Node.js 内置的 Request 构造函数,将各种输入格式 (字符串 URL、URL 对象、已有 Request 对象)统一解析为一个标准的 Request 实例
  • 目的 :避免手动处理 input 和 init 合并的复杂逻辑(如 headers 合并、method 默认值等),直接复用平台能力完成请求预处理

  1. 请求体序列化
javascript 复制代码
const body = request.body ? await request.text() : undefined
  • 功能 :读取请求体内容,将其转换为纯文本字符串;若无请求体则传 undefined。
  • 目的RPC 通道通常不支持流式传输或二进制 Blob需要将请求体序列化为可跨进程/网络传输的简单数据类型

⚠️ 代价:所有请求体被全量加载到内存,丧失流式上传能力(其实也不需要流式,RPC 作为进程间通信,直接通信就好了)。


  1. RPC 转发
javascript 复制代码
const result = await client.call("fetch", {
  url: request.url,
  method: request.method,
  headers: Object.fromEntries(request.headers.entries()),
  body,
})
  • 功能 :将标准 HTTP 请求的关键字段(URL、方法、Headers、Body)提取为普通 JSON 对象,通过 client.call 发送到远端服务。
  • 目的:完成从 HTTP 语义到 RPC 调用参数的映射。远端服务收到后会代为发起真实 HTTP 请求,并将结果返回。

关键点Object.fromEntries(request.headers.entries()) 将 Headers 对象转为普通键值对,因为 Headers 对象无法直接跨 RPC 序列化。


  1. 响应重构
javascript 复制代码
return new Response(result.body, {
  status: result.status,
  headers: result.headers,
})
  • 功能:将 RPC 返回的原始结果重新包装为标准 Response 对象。
  • 目的 :确保返回值与原生 fetch 完全一致,调用方可以正常使用 .json().text().status 等所有标准 API。

📌 整体数据流总结

bash 复制代码
调用方 fetch(url, options)
        ↓
[1] 签名兼容 → 接收标准参数
        ↓
[2] Request 归一化 → 统一请求对象
        ↓
[3] Body 序列化 → 文本字符串
        ↓
[4] RPC 转发 → client.call("fetch", {...})
        ↓
    (远端执行真实 HTTP 请求)
        ↓
[5] Response 重构 → 标准响应对象
        ↓
调用方拿到标准 Response

💡 一句话功能定义

HTTP-over-RPC 透明代理 :对外暴露标准 Fetch API,对内将请求序列化后通过 RPC 通道转发,再将远端响应反序列化回标准 Response 对象,实现"用 fetch 的语法,走 RPC 的通道"。


OK,本篇先到这里,如有疑问,欢迎评论区留言讨论,祝各位功力大涨,技术更上一层楼!!!更多内容见下篇 blog

【Agent】【OpenCode】TuiThreadCmd(流式传输&二进制Blob)

相关推荐
TaoMetrix2 小时前
TaoMetrix:AI 硬件创业公司如何做好原型制造
人工智能·制造
智圣新创012 小时前
面向多级组织协同场景 高校第二课堂一站式管理中枢落地全场景实操答疑
大数据·人工智能
cd_949217212 小时前
AI纹理和Substance Painter手绘纹理哪个更适合游戏资产制作?
人工智能·游戏·substance painter
sali-tec2 小时前
C# 基于OpenCv的视觉工作流-章106-差值追踪
图像处理·人工智能·opencv·算法·计算机视觉
Shockang2 小时前
用 AI 打造高品质 Web 应用
人工智能
l1258652 小时前
# LangGraph Memory机制深度解析:短期记忆与长期记忆的工程实践
前端·人工智能·python·langchain·bootstrap
手写码匠2 小时前
Dify 多 Agent 工具权限与安全沙箱实战:让智能体“有能力,但不越权“
人工智能·深度学习·算法·aigc
ZGIAI2 小时前
ZGI Skill Loop:给工具调用设边界
人工智能·架构
黎阳之光2 小时前
打破堆场感知黑盒:黎阳之光视频孪生,构建港口码头网格化透明管控新体系
大数据·人工智能·算法·安全·数字孪生
ZGIAI2 小时前
ZGI 工作区权限:用户为何看不到资源
人工智能·架构