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)

相关推荐
xcLeigh12 小时前
FreeRTOS实战:用STM32+SEGGER打造工业级实时嵌入式系统
人工智能·stm32·单片机·嵌入式硬件·ai
考拉讲支付12 小时前
延迟分账 / 核销分账的技术实现方案:服务完成后再分钱,到底怎么设计?
大数据·运维·人工智能·微信小程序·小程序·系统架构
ADAI_Bowen13 小时前
写文章 - 知乎
大数据·运维·人工智能
虎鲸不是鱼13 小时前
AI 自动化逆向(三十四)自建指令缓存:把分钟级查询压到秒级
人工智能·自动化·大模型·逆向·ai逆向
正经教主13 小时前
【FDE系列】阶段2:Day 42(UP详细版):Dockerfile 实战 — 把你的应用打包成镜像
人工智能·docker·fde
MarkHD13 小时前
超分辨率与图像放大:从ESRGAN的纹理重建到UltimateSDUpscale的4K分块实战
人工智能·深度学习·计算机视觉
武子康13 小时前
H3-World 的动作没按时发生,先检查时间线变成了什么
人工智能·后端·agent
jimmyleeee13 小时前
Agent安全之五:当语言变成执行------深入理解Unexpected Code Execution (ASI05)
人工智能·安全
前进的李工13 小时前
Agent 学习笔记 02:Harness 与工作流怎么理解
笔记·学习·agent·工作流·harness
wflynn13 小时前
GitHub 今日推荐|rea:用 MCP 把反编译器和 AI agent 接到一起的逆向工具
typescript·开源·github·agent·agent-skills·binary-analysis