尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

168、【Agent】【OpenCode】TuiThreadCmd(SSE)

168、【Agent】【OpenCode】TuiThreadCmd(SSE) 【声明】本博客所有内容均为个人业余时间创作所述技术案例均来自公开开源项目如GithubApache基金会不涉及任何企业机密或未公开技术如有侵权请联系删除标题168、【Agent】【OpenCode】TuiThreadCmdSSE背景上篇 blog【Agent】【OpenCode】TuiThreadCmdEvenSource分析了 Headers 是一个特殊的浏览器/Node.js 内置对象它不能直接被 JSON 序列化所以必须先把它“拆”成普通的{ key: value }对象才能通过 RPC 发送因为 Headers 把数据存在了内部私有槽位里而不是挂在可枚举的属性上。JSON 序列化器只能看到对象的自有可枚举属性所以什么都读不到而.entries()是 Headers 提供的标准读取接口能正确访问到内部存储的数据再由Object.fromEntries()把迭代器里的每一对[key, value]重新组装成一个纯普通对象接着分析了基于 RPC 通道的“伪 EventSource”对象它和上一个createWorkerFetch是同一套设计思路对外暴露熟悉的 API对内走 RPC 通道。但这次适配的不是 HTTP 请求而是服务端推送事件下面继续分析OpenCode上篇 blog 提到了 SSE 的概念下面澄清下SSE Server-Sent Events服务器发送事件它是浏览器原生提供的一种 “服务端向客户端单向推送数据” 的标准协议。用最简单的类比理解通信方式类比方向普通 HTTP打电话问老师问题老师回答后挂断一问一答WebSocket和老师接通电话双方可以随时说话双向实时SSE老师打开广播频道只能听不能说话服务端 → 客户端 单向推送SSE 就是那个“广播频道”服务端持续往客户端推数据客户端只需要监听不需要反复发请求。浏览器里怎么用// 浏览器原生 API就这么简单constsourcenewEventSource(/api/events)// 此 EventSource 非 opencode EvenSource方法不一样source.onmessage(event){console.log(收到推送:,event.data)}source.onerror(err){console.log(连接出错浏览器会自动重连)}浏览器会自动建立一个 HTTP 长连接服务端返回的响应头是 Content-Type: text/event-stream然后数据就像水流一样持续推过来。SSE 的数据格式服务端推送的内容是纯文本格式非常简单data:{user:alice,action:login}data:{user:bob,action:upload}event:notificationdata:{title:新消息,body:你好}每条消息以data:开头空行表示一条消息结束可选的event:字段区分消息类型SSE vs WebSocketSSEWebSocket方向单向服务端→客户端双向协议普通 HTTP独立协议 (ws://)自动重连✅ 浏览器内置❌ 需自己实现数据格式纯文本文本或二进制复杂度极低较高适用场景通知推送、实时日志、AI 流式输出聊天室、游戏、协作编辑为什么 AI 对话都用 SSEChatGPT 等产品的“打字机效果”就是 SSE模型生成一个 token服务端就通过 SSE 推一个 token前端逐字渲染。不需要双向通信SSE 比 WebSocket 简单得多。回到代码functioncreateEventSource(client:RpcClient):EventSource{return{on:(handler)client.onEvent(event,handler),// ...}}这里做的事情就是把原本应该走 HTTP SSE 长连接的推送替换成了走 RPC 通道的事件订阅。原来浏览器new EventSource(url)→ HTTP 长连接 → 服务端推 SSE 文本流现在client.on(event, handler)→ RPC 通道WebSocket/IPC→ 服务端推结构化事件对象调用handler通知好处是不需要额外维护一个 SSE 端点所有通信都走同一个 RPC 通道代价是失去了浏览器原生的自动重连等能力需要自己在 RpcClient 里实现。另外这里的 “event” 就是一个硬编码的字符串字面量。这意味着所有 handler 都绑定在同一个通道上无论注册多少次on(handler)底层都是在执行client.on(event, handler)。所有的回调函数都被注册到了 RPC Client 内部名为 “event” 的这唯一一个事件监听器列表中。服务端推送时没有区分度当服务端通过 RPC 发送消息时它只能发类似这样的结构client.emit(event,payload)它无法直接指定“这条消息只给某个特定的 handler”。RPC Client 收到 “event” 消息后会广播式地触发所有注册在该通道上的 handler。那如何区分不同类型的事件既然通道只有一个事件的分类逻辑就下沉到了 payload 内部。通常 Event 类型会长这样interfaceEvent{type:workspaceChanged|fileUpdated|notificationdata:unknown}客户端消费方需要在自己的 handler 里做二次分发eventSource.on((event){if(event.typefileUpdated){handleFileUpdate(event.data)}elseif(event.typenotification){showNotification(event.data)}})为什么这么设计这是一种 “单通道 消息自描述” 的模式好处是RPC 接口极简服务端只需要暴露一个emit(event, ...)方法不用为每种事件定义单独的 RPC 消息名统一中间件可以在 “event” 这一个点上统一做日志、鉴权、序列化等处理与 SSE 语义对齐原生 SSE 本质上也是一个 HTTP 连接上的单一文本流所有事件都走同一个管道靠event: type字段区分类型。这个设计是对 SSE 模型的忠实映射⚠️潜在风险如果业务事件种类很多、频率很高所有 handler 都会被频繁触发然后各自过滤存在一定的性能开销。当事件量大到成为瓶颈时就需要拆分为多个命名通道如client.on(fileUpdated, ...)但在那之前单通道模式是更简单、更合理的选择OK本篇先到这里如有疑问欢迎评论区留言讨论祝各位功力大涨技术更上一层楼更多内容见下篇 blog【Agent】【OpenCode】TuiThreadCmdworkspaceworker路径
返回列表