167、【Agent】【OpenCode】TuiThreadCmd(EvenSource)

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

标题

167、【Agent】【OpenCode】TuiThreadCmd(EvenSource)

背景

上篇 blog

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

分析了流式传输和二进制 Blob 两个概念,这是理解为什么代码里要写 await request.text() 的关键,接着解释了什么是二进制 Blob,Blob = Binary Large Object(二进制大对象),可以把它理解为 "一团原始的、未解释的字节数据",RPC 通道通常只能传输 JSON 可序列化的数据。而 JSON 只支持字符串、数字、布尔、数组、对象这几种类型,所以 fetch 代理不能传文件,图片被 .text() 强行当 UTF-8 解码后会变成乱码,这也是这段代码最危险的地方:它不是"禁止传文件",而是 "假装能传,然后默默把数据毁掉"。接着解释了什么是流式传输,流式传输 = 数据像水流一样"边产生边发送",而不是"攒够了再一次性发",最后提到如果场景只涉及 JSON API 请求(body 都是小文本),这个妥协完全没问题;但如果要传文件或大数据量,就需要升级 RPC 通道本身的设计,下面继续分析

OpenCode

之前 blog 还有个点

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

这句话的意思是:Headers 是一个特殊的浏览器/Node.js 内置对象,它不能直接被 JSON 序列化,所以必须先把它"拆"成普通的 { key: value } 对象,才能通过 RPC 发送,下面详细分析下,分三步拆解:


  1. Headers 对象长什么样?
javascript 复制代码
const headers = new Headers({
  "Content-Type": "application/json",
  "Authorization": "Bearer xxx"
})

它不是普通对象 {},而是一个有内部状态的类实例。直接打印或序列化它:

javascript 复制代码
console.log(JSON.stringify(headers))        // "{}" ← 空的!字段全丢了
console.log(Object.keys(headers))           // []   ← 也是空的

因为 Headers 把数据存在了内部私有槽位里,而不是挂在可枚举的属性上。JSON 序列化器只能看到对象的自有可枚举属性,所以什么都读不到


  1. .entries() 做了什么?
javascript 复制代码
headers.entries()
// → Iterator [["content-type", "application/json"], ["authorization", "Bearer xxx"]]

它返回一个迭代器 ,逐个吐出 [key, value] 键值对数组。这是 Headers 提供的标准读取接口,能正确访问到内部存储的数据


  1. Object.fromEntries() 做了什么?
javascript 复制代码
Object.fromEntries(headers.entries())
// → { "content-type": "application/json", "authorization": "Bearer xxx" }

它把迭代器里的每一对 [key, value] 重新组装成一个纯普通对象。这个对象:

  • ✅ 没有特殊原型链
  • ✅ 所有属性都可枚举
  • JSON.stringify() 能完整序列化
  • ✅ 可以安全地作为 client.call() 的参数跨 RPC 传输

📌 整条链路一图看懂

bash 复制代码
Headers 实例(不可序列化)
       ↓ .entries()
Iterator<[key, value]>(仍不可序列化)
       ↓ Object.fromEntries()
Plain Object { key: value }(✅ 可 JSON 序列化)
       ↓ JSON.stringify / RPC 编码
发送到远端

一句话总结 :Headers 是个"带锁的保险箱 ",JSON 序列化器打不开它。.entries()钥匙 ,把内容取出来;Object.fromEntries() 是把取出来的东西装进一个"透明塑料袋 "(普通对象),这样 RPC 通道才能安全运输。


OK,下面继续分析

这个函数的功能是:创建一个基于 RPC 通道 的"伪 EventSource"对象,用来替代浏览器原生的 EventSource(SSE )。它和上一个 createWorkerFetch 是同一套设计思路:对外暴露熟悉的 API,对内走 RPC 通道。但这次适配的不是 HTTP 请求,而是服务端推送事件


🔍 下面逐行拆解

javascript 复制代码
function createEventSource(client: RpcClient): EventSource {
  return {
    // 1️⃣ 订阅事件:把 handler 注册到 RPC client 的 "event" 消息上
    on: (handler) => client.on<Event>("event", handler),

    // 2️⃣ 切换工作区:通过 RPC 调用通知服务端改变当前 workspace
    setWorkspace: (workspaceID) => {
      void client.call("setWorkspace", { workspaceID })
    },
  }
}

on(handler) --- 事件订阅

  • 原生 EventSource :通过 addEventListener("message", cb) 接收 SSE 推送
  • 这里 :改为 client.on("event", handler),从 RPC 通道监听服务端主动推过来的 "event" 消息

本质 :把 SSE 的"HTTP 长连接推送 "替换成了"RPC 通道的事件订阅"


setWorkspace(workspaceID) --- 控制指令

这不是标准 EventSource 的 API,是业务扩展,通过 RPC 单向调用告诉服务端:"现在要切换到这个 workspace,后续推送的事件都应该是这个 workspace 的"

void 表示不关心返回值,这是一个"发了就行"的通知型调用


📌 和原生 EventSource 的对比

原生 EventSource 这个代理 EventSource
传输通道 HTTP SSE 长连接 RPC 通道(WebSocket/IPC 等)
订阅方式 addEventListener() client.on("event", handler)
连接管理 浏览器自动重连 由 RpcClient 内部管理
业务扩展 ❌ 不支持 setWorkspace()
返回类型 真正的 EventSource 实例 只是形状相似的普通对象 { on, setWorkspace }

⚠️ 注意:这不是真正的 EventSource

返回类型标注为 EventSource,但实际上只是一个结构兼容的子集(Duck Typing)。它没有:

  • readyState、url、withCredentials 等属性
  • close()、dispatchEvent() 等方法
  • 浏览器的自动重连机制

代码能正常工作的前提是:消费方只用到了 on()setWorkspace() 这两个方法。如果某处代码调用了 eventSource.close(),运行时会直接报错


💡 一句话总结

它是一个 RPC 版的事件推送适配器 :用 client.on 替代 SSE 长连接来接收服务端推送,同时扩展了 setWorkspace 业务能力,让上层代码可以用类 EventSource 的方式消费 RPC 事件流


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

相关推荐
TunerT_TQ1 小时前
Valhalla 静态工程审阅 #022|百度PaddlePaddle 源码证据驱动评测【大厂开源基础设施特辑】
人工智能·百度·开源·paddlepaddle·#深度学习·#飞桨
DigitalOcean1 小时前
多款模型最高直降 50%:DigitalOcean 无服务器推理大降价
llm·agent
spter。1 小时前
AI 面试相同请求为什么会重复调用,如何解决
人工智能·面试·职场和发展
阿里云大数据AI技术1 小时前
基于阿里云EMR Serverless StarRocks AI Function和混合检索,构建智驾训练数据管理平台
人工智能
evans在进步1 小时前
Spring AI 从入门到实战:用 Java 实现大模型对话与 Tool Calling
java·人工智能·spring
智塑未来1 小时前
发那科又叫法兰克?译名误区拆解,数控自动化龙头全维度解析
大数据·人工智能·自动化
罗高1 小时前
Hermes Agent Token 机制深度解析
llm·agent
网易云信1 小时前
立即下载!帝王蟹(ClawHive)桌面客户端正式上线!
人工智能·agent
windliang1 小时前
Claude Code 源码分析(六):上下文的发现、注入与压缩
前端·javascript·人工智能