Dual Output 事件与控制协议
处理握手、增量消息、审批竞争、输入排队和异常关闭。
This page has not been translated into English yet. The original Chinese version is shown below.
Dual Output 一行一个 JSON 对象,事件格式沿用无头 stream-json,且始终开启部分消息。消费者需同时理解流式增量和完成消息,避免把每种事件都当作新的独立回复。
先读会话握手
通道第一条是 system / session_start。用 session_id 关联会话,并读取 data.protocol_version、data.version、data.supported_events 做能力检测。旧版本可能没有 protocol_version,可把缺失视为 0,再决定是否接受较弱兼容。
协议版本 2 将文本 tool_result.content 限制为 JSON 字符串序列化后的 65,536 UTF-8 字节。超出时变成确定性的头尾预览。这是字段限制,不是每行 JSONL 的通用大小上限,也不代表输出中保留了完整原始工具正文。
事件分类
| 事件 | 消费方式 |
|---|---|
| stream_event | 处理 message_start、content_block_start/delta/stop、message_stop,更新正在输出的消息 |
| user / assistant | 完整消息;assistant 可能带 usage,user 也用于承载 tool_result |
| control_request | 需要工具审批,保留 request_id 与工具参数 |
| control_response | TUI 或外部通道做出的最终控制结果 |
| result | 轮次结果、错误和时长等汇总 |
| system / session_end | 正常会话关闭信号 |
不要只按 type:user 就渲染为用户刚输入的文字;其中也可能是工具结果内容块。控制请求的 request.subtype 为 can_use_tool,提供 tool_name、tool_use_id、input 等信息,供外部界面展示待批准操作。
提交与审批
输入通道接受两种记录:
{"type":"submit","text":"Explain the latest change"}{"type":"confirmation_response","request_id":"REQUEST_ID_FROM_EVENT","allowed":false}先用真实控制事件里的 request_id 替换占位值,再根据用户决定填写 allowed。不要根据模型文本或猜测出的 ID 自动批准。
submit 在 TUI 忙时进入队列,回到 idle 后重试提交。confirmation_response 则立即派发,不能被前面的普通提示挡住,否则正在等待批准的工具无法继续。
本地界面与远端界面竞争同一审批,先完成的决定生效。迟到响应不会再次执行工具;未知、已取消或已处理的 request_id 可收到 subtype:error 的 control_response。接入方应关闭过期审批,不要无限重发。
延迟与解析错误
输入使用 fs.watchFile,轮询间隔 500ms,因此从追加记录到被发现可能约半秒;模型响应和工具等待时间另计。输出随事件产生写出,没有同样的输入轮询间隔。
JSON 解析失败的行记录错误并跳过,不会停止整个 watcher。写入方应一次追加完整一行并以换行结束,读取方保留未完成行,避免把任意文件读取块当作完整 JSON。
正常结束与失联
只有看到 session_end 才把该流标记为正常关闭。进程退出、旁路停用或流断开时可能没有此事件;旁路异常并不意味着 TUI 已经停止,更不能据此重新提交上一次可能已执行的操作。
会话记录可用于观察和回放,但文本工具结果可能已截断。审计完整原始输出时还需原始工具记录;不要把 JSONL 旁路称为无损的全部执行证据。