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

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

标题

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

背景

上篇 blog

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

分析了鸭子类型的含义:只关心对象"能做什么"(有什么方法/属性),不关心对象"是什么"(属于哪个类/接口),接着对比了 fetch 例子在 TS 和 C 语言上实现的差异,并对比了结构化和名义化的区别,并解释了鸭子类型对 createWorkerFetch 很重要,因为它的设计目标是透明替换原生 fetch,接着分析了代理 fetch 的详细内容,其核心功能是将标准的 HTTP Fetch 请求,翻译并转发到自定义的 RPC 通道中执行,其本质上是一个协议适配器,让上层代码可以用熟悉的 fetch API 发请求,但底层实际走的是 RpcClient 定义的私有通信协议,并逐层拆解了其功能,下面继续分析

OpenCode

上篇 blog 提到了两个点,下面澄清下

RPC 通道通常不支持流式传输或二进制 Blob,需要将请求体序列化为可跨进程/网络传输的简单数据类型。

这里流式传输和二进制 Blob 两个概念是理解为什么代码里要写 await request.text() 的关键,下面详细分析下


  1. 什么是二进制 Blob?

Blob = Binary Large Object(二进制大对象

可以把它理解为 "一团原始的、未解释的字节数据"。

  • 文本字符串:是有编码规则的(UTF-8),人类可读,比如 "Hello World"
  • Blob:没有编码规则,就是一堆 0 和 1,比如一张 PNG 图片、一个 PDF 文件、一段音频、一个加密后的数据包

在 Fetch API 中,请求体可以是多种类型:

javascript 复制代码
// ✅ 这些都可以作为 fetch 的 body
fetch(url, { body: "hello" })              // 字符串
fetch(url, { body: new Blob([...]) })      // Blob
fetch(url, { body: new FormData() })       // 表单
fetch(url, { body: new ReadableStream() }) // 流
fetch(url, { body: arrayBuffer })          // 二进制缓冲

问题在于RPC 通道 (如 JSON-RPC、gRPC-Web、WebSocket JSON 模式)通常只能传输 JSON 可序列化的数据 。而 JSON 只支持字符串、数字、布尔、数组、对象这几种类型。

❌ Blob、FormData、ArrayBuffer、Stream 都无法直接放进 JSON

✅ 只有字符串可以

所以代码用 .text() 把一切先变成字符串,才能塞进 client.call() 的参数里。这也是为什么这个 fetch 代理不能传文件------图片被 .text() 强行当 UTF-8 解码后会变成乱码


代码里没有任何对二进制 Blob 的拦截、判断或报错逻辑,这也是这段代码最危险的地方:它不是"禁止传文件",而是 "假装能传,然后默默把数据毁掉"

🔍 为什么没有拦截?

因为 .text() 这个方法本身永远不会抛错。它的设计语义就是:"不管肚子里是什么字节,都强行按 UTF-8 解码成字符串"

javascript 复制代码
// 即使 body 是一张 PNG 图片的二进制数据
const body = await request.text()
// ✅ 不报错,不警告,不拦截
// ❌ 返回一个充满乱码的字符串(如 "PNG\r\n\x1a\n...")
// ❌ 原始二进制数据已被不可逆地损坏

💥 实际会发生什么?

假设用它上传一张图片:

  • request.text() 把图片二进制强行解码为 UTF-8 字符串 → 数据已损坏
  • 损坏的字符串通过 RPC 发到远端
  • 远端把这个乱码字符串当请求体发给目标服务器
  • 目标服务器收到一个既不是合法图片、也不是合法文本的畸形请求
  • 返回 400 Bad Request 或保存了一个损坏的文件

全程没有任何一行代码通知"不该这么做"。


🛡️ 如果要正确拦截,应该怎么写?

javascript 复制代码
const contentType = request.headers.get("content-type") ?? ""
const isBinary = !contentType.startsWith("text/") 
              && !contentType.includes("json") 
              && !contentType.includes("xml")

if (isBinary) {
  throw new TypeError(
    `WorkerFetch 不支持二进制请求体 (${contentType}),请使用原生 fetch`
  )
}

const body = request.body ? await request.text() : undefined

或者更彻底地检查 body 类型:

javascript 复制代码
if (init?.body instanceof Blob || init?.body instanceof ArrayBuffer) {
  throw new TypeError("WorkerFetch 不支持二进制 body")
}

📌 总结

没有拦截 ≠ 支持 。这段代码对二进制 Blob 的态度是 "来者不拒,照单全毁 "。这不是功能,是 bug。在生产环境中使用这个代理时,调用方必须自己确保只传文本/JSON 类型的 body,否则会得到一个静默的数据损坏,且极难排查


  1. 什么是流式传输?

流式传输 = 数据像水流一样"边产生边发送",而不是"攒够了再一次性发"

🚰 类比理解

非流式(当前代码的做法) 流式传输
类比 用桶接满水再端过去 接上水管直接流过去
内存占用 整个文件大小 只需一个小缓冲区
延迟 等全部读完才能开始处理 第一个字节到达就能开始处理
适合场景 小文本、API 请求 大文件上传/下载、视频直播、SSE

💻 代码层面的区别

javascript 复制代码
// ❌ 当前代理的做法:非流式
const body = await request.text()  
// ↑ 必须等整个 body 全部读入内存变成一个完整字符串
//   如果 body 是 500MB 的视频 → 内存直接爆掉

// ✅ 原生 fetch 的流式做法
const reader = request.body!.getReader()
while (true) {
  const { done, value } = await reader.read()
  if (done) break
  // value 是一小块数据(比如 64KB),处理完就释放
  // 内存始终只占一小块的大小
}

🔗 和 RPC 的关系

大多数 RPC 框架的请求模型是 "请求-响应" 模式:

javascript 复制代码
client.call("fetch", { body: ??? })
//                     ↑ 这里必须传一个完整的值
//                       不能传"一个正在流动的数据管道"

client.call() 的参数是一个普通对象,它需要被整体序列化后发出去,没法把一个"水龙头"塞进 JSON 里

要实现真正的流式 RPC,需要特殊的双向流协议 (如 gRPC streaming、WebSocket 二进制帧分片),而这远远超出了 client.call("fetch", {...}) 这种简单调用模型的能力范围


📌 回到原代码

javascript 复制代码
const body = request.body ? await request.text() : undefined

这一行做了两件有损转换:

  • Blob → String:丢弃了二进制安全(图片/文件会损坏)
  • Stream → String:丢弃了流式能力(大文件会 OOM)

这是为了适配"RPC 只能传 JSON 简单类型 "这个限制所做的必要妥协。如果场景只涉及 JSON API 请求(body 都是小文本),这个妥协完全没问题;但如果要传文件或大数据量,就需要升级 RPC 通道本身的设计了


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

【Agent】【OpenCode】TuiThreadCmd(EvenSource)

相关推荐
吨吨ai2 小时前
# ChatGPT Plus / Pro + Codex Debug 实战:如何让 AI 从日志、Trace、异常堆栈自动定位线上 Bug
人工智能·chatgpt·bug
fivebliss2 小时前
取算存——模型容量、能耗指标和架构梳理
人工智能·性能优化·gpu算力
xiwc2 小时前
Token 预算管理:长对话不崩溃的秘密
人工智能
用户847181054192 小时前
LangChain中间件教程及DeepAgents应用
javascript·agent
何时梦醒2 小时前
第一篇:项目概览 — 在浏览器里跑大模型,端侧 AI 的革命来了
前端·人工智能
何时梦醒2 小时前
第二篇:工程化搭建 — Vite + React + TypeScript + TailwindCSS 全解析
前端·人工智能
橘子星2 小时前
浏览器也能跑大模型:WebGPU + Transformers.js 本地运行 DeepSeek-R1
前端·人工智能
物联网软硬件开发-轨物科技2 小时前
【轨物方案】从五维感知到一键顺控:箱变智能化不是一个传感器能解决的事
人工智能·科技·其他·机器人·开源
Swift社区2 小时前
Python 开发环境怎么选?PyCharm、VS Code、Trae 谁更适合 AI 开发?
人工智能·python·pycharm