通过 requestId 订阅异步运行产生的 SSE 事件流,支持断线重连;事件带序号,保证不丢失、不重复。以下为完整事件手册。
一、SSE 协议约定
响应为标准 Server-Sent Events(Content-Type: text/event-stream),每条消息包含三个字段:
| SSE 字段 | 说明 |
|---|
event | 事件类型名,即下文事件目录中的枚举名(如 TEXT_DELTA、TOOL_START) |
id | 事件序号,从 0 开始递增,是断线重连的凭据;HEARTBEAT 事件不携带 id |
data | 事件体 JSON,结构见下 |
data 的统一结构(AgentEvent):
{
"eventType": "TEXT_DELTA",
"data": { "type": "text", "text": "你好" },
"timestamp": "2026-07-24T10:00:00",
"step": 1,
"totalTokens": 1234
}
| 字段 | 类型 | 说明 |
|---|
eventType | string | 事件类型,与 SSE event 字段一致 |
data | object | 事件数据体,以 data.type 字段判别具体结构(见第三节) |
timestamp | string | 事件产生时间 |
step | integer | Agent 执行步骤号(部分事件为空) |
totalTokens | integer | 截至当前累计消耗的 token 数(部分事件为空) |
回答结束时,MESSAGE_COMPLETED、MESSAGE_FAILED 的 data.usage 可携带回答用量快照: promptTokens、completionTokens、 cachedTokens、reasoningTokens、totalTokens 为当前助手消息累计 Token,不含压缩。 缓存命中率由 cachedTokens / promptTokens 计算,输入为 0 时不计算。 旧事件或无法取得统计时 usage 可为空。顶层 step 与 totalTokens 保持兼容。
二、事件目录(共 19 种)
会话事件
| 事件 | data.type | 触发时机 | 客户端处理建议 |
|---|
SESSION_CREATED | session | 任务开始执行时的首个事件 | 记录 sessionId、userMessageId、assistantMessageId,用于后续消息定位与重试 |
文本事件(最终回答正文)
| 事件 | data.type | 触发时机 | 客户端处理建议 |
|---|
TEXT_START | text | 一段正文开始,text 为初始文本;synthetic=true 表示系统合成文本 | 新建一个正文气泡 |
TEXT_DELTA | text | 正文增量,text 为增量片段 | 按到达顺序追加拼接 |
TEXT_END | text | 一段正文结束(data 无有效字段) | 结束当前正文气泡 |
推理事件(思考过程,非正文)
| 事件 | data.type | 触发时机 | 客户端处理建议 |
|---|
REASONING_START | reasoning | 一段推理开始 | 新建"思考中"折叠区 |
REASONING_DELTA | reasoning | 推理增量,text 为增量片段 | 追加拼接,与正文分开展示 |
REASONING_END | reasoning | 一段推理结束 | 折叠或收起思考区 |
工具事件
| 事件 | data.type | 触发时机 | 客户端处理建议 |
|---|
TOOL_CALL_GENERATING | tool | 模型正在生成工具调用参数(逐帧,均早于 TOOL_START)。首帧携带 toolCallId/toolName,后续帧 arguments 为增量片段 | 首帧即可展示"正在调用 XXX 工具",按 toolCallId 拼接 arguments 实现参数逐字流式展示 |
TOOL_START | tool | 参数生成完毕、工具开始执行,携带完整 arguments | 作为同 toolCallId 的 TOOL_CALL_GENERATING 的隐含结束信号,用完整参数覆盖拼接结果 |
TOOL_RUNNING | tool | 工具执行中,title 为可展示的进度标题 | 更新工具卡片进度文案 |
TOOL_COMPLETED | tool | 工具执行成功,output 为执行结果 | 渲染执行结果,结束工具卡片 |
TOOL_FAILED | tool | 工具执行失败,errorMessage 为失败原因 | 展示失败态;智能体通常会自行调整策略继续执行,无需人工干预 |
TOOL_EVENT | tool_side | 工具执行过程中的侧信道结构化信息(如授权缺失、流式数据) | 按 category 分发处理,未识别的 category 忽略 |
权限确认事件
| 事件 | data.type | 触发时机 | 客户端处理建议 |
|---|
PERMISSION_ASK | permission_ask | 工具触发权限 ASK 规则,等待用户决策;随后会收到 MESSAGE_SUSPENDED(suspendReason.type=PERMISSION_ASK) | 弹出授权确认 UI,用户决策后调用 /api/agent/run/resumeTask(传 approved) |
PERMISSION_ASK_RESOLVED | permission_ask_resolved | 预留事件:当前版本内核不主动发射,确认结果以 resumeTask 响应及恢复后的事件流为准 | 预留兼容处理即可 |
消息生命周期事件(终态,三选一收尾)
| 事件 | data.type | 触发时机 | 客户端处理建议 |
|---|
MESSAGE_COMPLETED | message | 运行结束(正常完成或达到轮数上限) | 关闭 SSE 连接,结束会话轮次 |
MESSAGE_SUSPENDED | message | 任务挂起(权限确认、手动挂起、异步工具等待、节点停机),suspendReason 携带原因 | 关闭连接,按 suspendReason.type 展示对应 UI;恢复后用相同 requestId 重新连接本接口 |
MESSAGE_FAILED | message | 任务失败,携带 errorCode/recoverable/networkError | 关闭连接;recoverable=true 时可引导用户调用 /api/agent/run/retryTask 从失败点续跑 |
附件事件
| 事件 | data.type | 触发时机 | 客户端处理建议 |
|---|
ATTACHMENT_CHANGED | attachment | 智能体将产出文件转存为会话附件(新建或更新,按文件粒度逐个发射) | 实时刷新附件列表,凭 virtualPath 调用 /api/agent/attachment/download 下载,无需等消息结束后轮询 |
ATTACHMENT_SENT | attachment | 附件被智能体主动发送给用户(如外发渠道推送) | 复用附件渲染逻辑,展示"已发送"提示 |
补充输入事件
| 事件 | data.type | 触发时机 | 客户端处理建议 |
|---|
SUPPLEMENTARY_INPUT_CONSUMED | supplementary_input | 运行中提交的补充输入(/addSupplementaryInput)在步骤边界被注入上下文 | 将对应的补充输入气泡标记为"已送达" |
心跳事件
| 事件 | data.type | 触发时机 | 客户端处理建议 |
|---|
HEARTBEAT | - | 连接保活定时发送,不携带 id | 直接忽略,不参与断点序号记录 |
运行期状态事件
| 事件 | data.type | 触发时机 | 客户端处理建议 |
|---|
AGENT_STATUS | agent_status | 运行期瞬时信号(限流重试、上下文压缩等),状态码见第四节 | 渲染为 toast 或行内瞬时提示,不要拼入正文;只发"进入"信号,下一个正文/工具事件到达即隐含恢复 |
三、数据体字段说明(按 data.type)
session:sessionId(会话ID)、userMessageId(本轮用户消息ID)、assistantMessageId(本轮助手消息ID)。
text:text(文本内容,START 为初始文本、DELTA 为增量片段)、synthetic(是否系统合成文本,仅 TEXT_START 有值)。
reasoning:text(推理内容,语义同 text)。
tool:toolCallId(工具调用唯一标识,同一次调用的全部事件共享)、toolName(工具名)、title(进度标题,仅 TOOL_RUNNING)、arguments(调用参数 JSON:GENERATING 为增量片段、START 为完整参数)、output(执行结果,仅 TOOL_COMPLETED)、errorMessage(失败原因,仅 TOOL_FAILED)。
tool_side:toolCallId、toolName、category(事件分类,已知值:auth_required 用户授权缺失、tool_stream_data 工具流式数据;后续可能扩展)、payload(结构化数据,随 category 变化)、userMessage(用户友好提示,可选)。
permission_ask:askId(确认请求唯一标识,resumeTask 时需要)、principalId(身份主体ID)、toolName、toolCallId、arguments(待执行的工具参数 JSON)、ruleDescription(命中的权限规则描述)、rulePattern(规则匹配模式)。
permission_ask_resolved(预留):askId、approved(是否批准)、reason(结果原因)、resolveType(预留字段)。
message:messageId(助手消息ID);MESSAGE_COMPLETED 可含 finishReason(STOP 正常完成、MAX_STEPS 达到本次运行轮数上限)和 finishMessage(结束原因文本)。旧事件可能缺少这两个字段;客户端应兼容未来原因值。达到上限的提示也通过既有 synthetic TEXT_START/TEXT_END 展示并保存,完成事件中的 finishMessage 不应重复追加到正文;仅 MESSAGE_FAILED 有值:errorMessage(错误信息)、errorCode(错误码,见第五节)、recoverable(是否可从失败点重试续跑)、networkError(是否网络类瞬态错误);仅 MESSAGE_SUSPENDED 有值:suspendReason(挂起原因对象,suspendReason.type 可选值:PERMISSION_ASK 权限确认等待、MANUAL_SUSPEND 外部主动挂起、SHUTDOWN_INTERRUPT 系统停机中断、ASYNC_TOOL_PENDING 异步工具等待完成)。
attachment:attachmentId(附件ID)、sessionId、messageId(可为空)、fileName(文件名)、fileExtension(扩展名如 pdf、xlsx)、contentType(MIME 类型)、fileSize(字节数)、virtualPath(虚拟文件路径,用于构造下载URL)、source(来源:USER_UPLOAD 用户上传 / AGENT_GENERATED 智能体生成)、changeType(变更类型:CREATED 新建 / UPDATED 内容覆盖更新)。
supplementary_input:inputId(补充输入ID)、content(文本内容)、userMessageId(注入后生成的用户消息ID)、assistantMessageId、attachmentIds(随补充输入注入的附件ID列表)。
agent_status:code(状态码,见第四节)、level(严重等级:INFO/WARN/ERROR)、message(用户可读文案,可直接展示)、attributes(结构化扩展属性,随 code 变化)。
四、AGENT_STATUS 状态码
| 状态码 | level | 含义 | attributes 结构 |
|---|
IMPORTING_ATTACHMENTS | INFO | 正在将 URL 附件导入为平台附件(仅进度提示,导入失败经由后续错误事件或任务状态返回) | count(导入文件数) |
LLM_RATE_LIMITED | WARN | 模型限流,正在重试 | attempt(当前重试次数)、maxRetries(最大重试次数)、backoffMs(本次退避毫秒数) |
LLM_NETWORK_RETRYING | WARN | 模型网络抖动,正在重连 | 同上:attempt、maxRetries、backoffMs |
LLM_AUTO_RECOVERING | WARN | 网络类失败已被拦截,正在从失败点自动恢复 | attempt(当前恢复尝试次数)、maxAttempts(最大尝试次数) |
LLM_EMPTY_RESPONSE_RETRYING | WARN | 模型返回空回复,正在自动重试 | attempt、maxRetries |
CONTEXT_COMPACTING | INFO | 正在压缩历史上下文以释放 token 空间(同步调用摘要模型,耗时较久) | scope(压缩范围:in-loop 循环内 / turn-level 轮次级);scope=in-loop 时另有 estInput(预估输入token数)、maxInput(最大输入token上限) |
TOOL_CONSECUTIVE_FAILURES | WARN | 模型连续多个步骤的工具调用全部失败,已注入引导提醒 | failedSteps(连续失败步骤数)、maxFailedSteps(触发阈值) |
处理约定:该事件不写入消息历史、不回灌模型上下文,任务结束后查询消息历史不可见;UI 不应持久化,建议渲染为瞬时提示。服务端未来可能新增状态码,客户端遇到未识别的 code 必须静默忽略(可兜底展示 message 文案)。
五、终态与错误处理
每次运行的事件流必然以 MESSAGE_COMPLETED、MESSAGE_SUSPENDED、MESSAGE_FAILED 三者之一收尾,随后 SSE 连接正常关闭;收到终态事件后客户端应主动断开,不要再重连。
MESSAGE_FAILED 的 errorCode 可选值:
| errorCode | 含义 | recoverable |
|---|
LLM_ERROR | 模型调用失败(网络超时、限流等瞬态错误) | true,可调用 retryTask 从失败点续跑 |
LLM_INVALID_REQUEST | 模型拒绝了请求内容(参数错误、内容审核等),原样重试必然再次失败 | false |
TOOL_FAILED | 工具执行失败 | false |
TASK_TIMEOUT | 任务执行超时 | false |
SYSTEM_CRASH | 节点宕机等系统异常中断 | false |
USER_CANCELLED | 用户主动取消(terminateTask) | false |
USER_NO_RESPONSE | 挂起等待用户响应超时(兜底回收) | false |
RUN_SNAPSHOT_INVALID | 运行请求快照无效 | false |
INTERNAL_ERROR | 内部未分类错误 | false |
MESSAGE_SUSPENDED 后的恢复方式:调用 /api/agent/run/resumeTask(PERMISSION_ASK 场景需携带 approved 决策),恢复成功后使用相同 requestId 重新连接本接口(携带 lastEventId)继续消费。
控制台兜底错误事件:当流初始化失败(如 requestId 不存在或无权访问)时,本接口会发送一条 event: error 的 SSE 消息,其 data 为空、错误信息位于 SSE 注释行(: 开头的行),随后连接关闭。客户端收到 error 事件应终止重连并提示用户。
六、断线重连
- 消费过程中持续记录最近一条带 id 事件的 id 值(HEARTBEAT 无 id,跳过);
- 连接断开且未收到终态事件时,携带
lastEventId=<最近id> 重新调用本接口; - 服务端从 id+1 位置续推历史事件并无缝衔接实时事件,事件按序号连续、不丢不重;
- 若不传 lastEventId,则从事件流起点(id=0)完整重放。
七、典型事件时序示例
一次包含推理、工具调用与附件产出的完整运行,事件顺序如下:
SESSION_CREATED
REASONING_START → REASONING_DELTA × N → REASONING_END
TOOL_CALL_GENERATING × N → TOOL_START → TOOL_RUNNING → TOOL_COMPLETED
ATTACHMENT_CHANGED
TEXT_START → TEXT_DELTA × N → TEXT_END
MESSAGE_COMPLETED
(期间可能穿插 HEARTBEAT、AGENT_STATUS、SUPPLEMENTARY_INPUT_CONSUMED,位置不固定)
关键事件的完整 SSE 报文示例:
id: 0
event: SESSION_CREATED
data: {"eventType":"SESSION_CREATED","data":{"type":"session","sessionId":9001,"userMessageId":10001,"assistantMessageId":10002},"timestamp":"2026-07-24T10:00:00"}
id: 5
event: TOOL_START
data: {"eventType":"TOOL_START","data":{"type":"tool","toolCallId":"call_abc123","toolName":"web_search","arguments":"{\"query\":\"沪深300走势\"}"},"timestamp":"2026-07-24T10:00:03","step":1,"totalTokens":856}
id: 12
event: TEXT_DELTA
data: {"eventType":"TEXT_DELTA","data":{"type":"text","text":"根据检索结果,"},"timestamp":"2026-07-24T10:00:06","step":2,"totalTokens":1420}
id: 20
event: MESSAGE_COMPLETED
data: {"eventType":"MESSAGE_COMPLETED","data":{"type":"message","messageId":10002,"finishReason":"STOP"},"timestamp":"2026-07-24T10:00:09","step":2,"totalTokens":2310}
八、客户端展示工具(virtualTools)的渲染指令消费
若在 /async 请求中声明了 virtualTools,模型对这些工具的调用不产生新的事件类型,完全复用上文的 TOOL_* 事件:
TOOL_CALL_GENERATING:按 toolCallId 拼接 arguments 增量,可用于展示"正在准备图表"之类的过渡态;TOOL_START:arguments 为完整的渲染参数 JSON,是开始渲染的时机;TOOL_COMPLETED:参数校验通过后,output 原样返回工具定义中的 mockResponse 字符串(包括空字符串);省略或传入 null 时返回默认回执 {"status":"success"}。预设内容是模拟结果,不代表客户端已完成渲染或外部业务实际执行。
消费要点:
- 按
toolName 与自己声明的工具名匹配,即可把渲染指令与平台内置工具的调用区分开;未匹配上的工具名按普通工具处理; - 服务端不执行调用方代码,渲染完全在客户端完成,渲染结果无需回传,也没有对应的回传接口;
- 渲染失败属于客户端内部问题,不影响运行主流程,事件流仍会照常推进到终态。
九、迁移进来的历史运行没有事件流
通过定时任务「迁移历史运行记录」接口登记的运行(triggerSource 为 imported)只是历史留痕, 没有真实的执行过程,因此不存在可订阅的事件流:用这类运行的 requestId 调用本接口会返回 409 冲突, 而不是返回一个空流。
需要展示这类历史对话时,请改用会话轨迹查询接口读取已落库的消息内容。原生运行的事件流行为不受影响。
十、兼容性约定
- 服务端事件类型、data.type、AGENT_STATUS 状态码均只增不删;
- 客户端遇到未识别的事件类型、data.type、状态码或新增字段时,必须静默忽略,不得报错中断消费;
- 字段可能为 null 或缺省,解析时需做好空值兼容。