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)

相关推荐
echoVic1 小时前
会话选择器不是列表:Orca 如何守住切换边界
agent·ai编程
echoVic1 小时前
Agent 架构里最容易混淆的四种责任
agent·ai编程
骄阳如火1 小时前
论文撰写SKILLS实测三|PaperSpine:每个阶段都是带硬关卡的 gate,审计能直接 BLOCK 你
人工智能
badhope1 小时前
用RAG做了个智能客服,上线第一天就被用户骂了——我的7天实战复盘
人工智能·langchain
dunge20261 小时前
2026年8月更新:ChatGPT与Codex开发实践——从工具使用到AI工程工作流,开发者如何建立长期生产力体系(GPT-5.6技术分享)
人工智能·gpt·chatgpt
硅基流动1 小时前
OPC 南川:超级个体的疯狂探索|开发者说
人工智能
147API1 小时前
AI 图片编辑接口报 400 怎么排查?先读错误体,再查参数、图片与 mask
网络·人工智能
xiaoxiaoxiaolll1 小时前
AI-有限元融合的复合材料多尺度建模与性能
人工智能
zyplayer-doc2 小时前
同一份制度别复制到多个知识库:用zyplayer-doc引用文档解决重复维护
javascript·人工智能·智能手机·开源·ocr