Appearance
Agent 事件怎么推给前端:SSE、WebSocket 与 Event Stream
前置:长任务 API 用普通 REST 还是 Streaming HTTP
核心问题:前端怎样持续看到 Agent 事件,同时在断线重连后不丢事件、不重复解释顺序?
先按“通信方向”选 Transport
如果前端主要订阅 /runs/{id}/events 接收 model/tool/status,而审批、取消等命令通过普通 POST 提交,通信主体是 server→client,SSE 通常更简单。实时语音、远程终端这类需要持续双向低延迟消息的场景,WebSocket 才更自然。
SSE
事件应有 type/id 并处理代理缓冲/重连。
SSE 是基于 HTTP 的服务端→客户端文本事件流,浏览器支持简单、自动重连,适合 Agent status/token stream。它是单向通道,不适合高频双向交互。
WebSocket
WebSocket 提供持久双向 channel,适合实时双向控制;需要额外 heartbeat、backpressure、reconnect/session 设计。
代价是连接状态、心跳、重连、扩容和代理配置更复杂。
Agent Event Stream
定义领域事件如 run.started/model.delta/tool.started/node.completed,而不是把 provider 原始 event 直接暴露给前端。
事件流应定义 typed event schema、sequence/id、run correlation 和 terminal event,避免前端解析任意日志字符串。事件是观察视图,不应成为唯一 durable state。
Resumable Event Stream:重连后从哪里继续
长 Run 的浏览器连接必然会断。真正需要恢复的不是 socket,而是事件消费位置。事件 envelope 至少应提供稳定 event_id 或单 Run 单调 sequence:
text
run_id = r-123
sequence = 184
type = tool.completed
occurred_at = ...
payload = ...客户端重连时携带最后确认位置,服务端从 durable event projection 补发后续事件;如果保留窗口已经过期,就先返回当前 Run snapshot,再从新的 cursor 继续。重复收到同一 event_id 时 UI 应幂等应用。
这里解决的是“观察流如何续上”。客户端断线是否要取消 Run,属于下一篇的 Runtime cancellation 语义。
SSE 和 WebSocket 怎么选
如果客户端主要是“服务端不断推 Run event”,SSE 通常更简单:天然单向、基于普通 HTTP、浏览器支持事件重连语义。若客户端和服务端需要长期双向低延迟消息,例如远程终端、实时 computer-use control,WebSocket 更自然。
不要让 transport 决定内部事件模型。内部可以统一成:
json
{
"run_id": "r-42",
"seq": 318,
"type": "tool.completed",
"timestamp": "...",
"payload": {"tool_call_id":"tc-9","status":"SUCCEEDED"}
}SSE 只是把它编码成 event stream,WebSocket 则把同一 envelope 放进 frame。这样以后切 transport、同时支持 Web 和 CLI,Runtime 都不需要知道连接细节。
还要区分可丢的“体验事件”和必须可恢复的“系统事件”:token delta 丢几个通常可以接受,但 approval.requested、run.failed、artifact ready 等关键事件应能从 durable event log 或当前 state 重建。
Failure Modes
单向事件流无理由使用 WebSocket
症状: 只需 server→client 推送却承担双向连接复杂度。
为什么危险: 运维、代理兼容和重连更复杂。
处理: 单向优先 SSE/streaming HTTP;持续双向低延迟才用 WebSocket。
如何验证
| Case | 断言 |
|---|---|
| 单向事件流无理由使用 WebSocket | 比较两种实现的断线重连/代理兼容/状态复杂度。 |
| 客户端从旧 cursor 重连 | 关键状态事件不丢失;重复 event_id 不造成 UI 状态重复推进。 |
自测
- 只需要服务端持续推送 Run event,客户端几乎不反向发送消息。SSE 与 WebSocket 哪个更简单,为什么?
- 断线重连后如何避免前端既漏事件又重复处理?event id/offset 应承担什么作用?