Skip to main content

SSE 事件

平台 API 的流式端点返回 text/event-stream。这一页是公开事件词表的完整清单——不在这张表里的事件名,说明是内部事件原样透传,不构成契约,随时可能变。

传输格式

每个事件两行加一个空行:
三个约定:
  • data 是单行 JSON
  • 每个 payload 都带单调递增event_id(从 1 开始),可用于去重与断点判断
  • type 字段与 event: 行的值相同,所以只解析 data 也够用
流的结束标记是:
[DONE] 不是 JSON。直接 JSON.parse(line.slice(6)) 会抛 SyntaxError: Unexpected token D。这是接入时最常见的第一个坑——解析循环必须先判断这一行再解析。

事件全表

内部事件名到公开事件名是一张固定映射表。公开名是契约,内部名不是——内部改名不影响你,但公开名改动会走版本流程。

运行生命周期

run.in_progress 每轮都会发一次。一次运行里模型可能调工具、看结果、再推理,每一轮都是一个 turn——所以收到多个 run.in_progress 是正常的,不要当成重复请求。

输出内容

textreasoning 这两类事件的内容字段,在映射时content 改名成了 delta。这是刻意的(对齐 OpenAI Responses API),但意味着任何消费方都必须读 delta稳妥的写法是 event.delta ?? event.content——两个名字都吃,这样内部事件透传或跨版本时都不会哑掉。
拼接文本就是把所有 output.text.deltadelta 顺序拼起来。不要按 event_id 重排——事件本来就是顺序到达的,重排只会引入额外的失败模式。

工具调用

工具调用被拆成五个事件,前四个由内部 tool_call 事件按 status 字段分流:
出现未列出的 tool_call.<something> 是可能的:status 若是映射表以外的值,会按 tool_call.${status} 原样生成事件名。所以 UI 对 tool_call.* 前缀要有兜底渲染,不要写死四种状态的 switch 然后让 default 分支什么都不做。
tool_call.deniedtool_call.failed 是两回事:
  • failed = 工具跑了但出错(网络超时、参数不合法、目标不存在)
  • denied = 工具根本没跑,被权限门拦下
把两者合并成”工具出错”会让用户看到”重试”按钮却永远重试不成功。

审批与委托

approval.required阻塞事件:流会停在这里等你回应。不处理它,运行就一直挂着直到超时。

用量与配额

usage.trial_exhausted 必须单独处理。它不是错误——是”这个用户的试用额度用完了,接下来需要购买”。把它落进通用错误分支的后果是用户看到”当前操作被拒绝”这种毫无信息量的提示,然后以为账号出问题了。IM 渠道曾经就是这么翻译的,导致试用结束的用户全部误以为故障。正确做法是命中这个事件时给出购买入口。

消费示例

边界与失败态

无法识别的事件类型是原样透传而不是丢弃。这是刻意的:新增内部事件不会因为映射表没更新就在你这边消失。代价是你必须容忍未知事件名——所以 default 分支应该是”忽略”,不是”抛错”。

验证接入

最小验证:
应当看到:
  1. event: run.created 打头
  2. 若干 event: output.text.delta
  3. event: run.completed
  4. data: [DONE] 收尾
若第一个事件就是 run.failed,看载荷里的错误码,对照 错误码

相关页面

端点速查

哪些端点返回流

错误码

run.failed 载荷里的码怎么读
核对日期 2026-08-11。来源:services/core/src/lib/sse-event-mapper.tsEVENT_MAP 16 条 + TOOL_CALL_STATUS_MAP 4 条 = 20 个公开事件名,该文件是词表的唯一真相源)。