WebSocket API 参考
WebSocket API 使用 Bridge 协议 infini.bridge.v1,把自有消息系统连接到 WebSocket 渠道账号。本文说明连接、鉴权、消息、流式回复、中断、心跳和错误处理。创建渠道账号、签发 Token 和验证接入的步骤参见通过 WebSocket 接入智能体。
连接和编码
客户端需要按照目标部署的地址、协议版本和 JSON 字段约定建立连接。
- 连接地址:使用目标部署为 WebSocket 渠道账号提供的地址,端点路径为
/ws/v1。 - 协议版本:
infini.bridge.v1。 - 帧类型:WebSocket 文本帧。
- 消息编码:Protocol Buffers JSON 字段约定。
- JSON 字段名:snake_case。
生产环境应使用目标部署提供的 wss:// 地址。不要根据控制台站点地址自行拼接 WebSocket 主机名。
所有业务帧使用同一种信封:外层 type 指定消息类型,并由同名字段承载数据。例如:
{
"type": "message",
"message": {
"meta": {
"msg_id": "3eeec74d-a4d4-4517-89ec-ef8aeb676666",
"user_id": "user-001"
},
"content": "帮我查询订单状态"
}
}支持的帧类型:
| 方向 | type | 数据字段 | 用途 |
|---|---|---|---|
| 客户端到平台 | auth | auth | 在连接建立后完成鉴权 |
| 客户端到平台 | message | message | 发送用户文本或斜杠指令 |
| 客户端到平台 | interrupt_response | interrupt_response | 响应确认或用户工具中断 |
| 双向 | PING | 无 | 请求业务层心跳响应 |
| 双向 | PONG | 无 | 响应业务层心跳 |
| 平台到客户端 | auth_ok | auth_ok | 确认帧内鉴权成功 |
| 平台到客户端 | message_reply | message_reply | 返回一条完整文本;/help 使用该帧返回 JSON 命令清单 |
| 平台到客户端 | message_stream_reply | message_stream_reply | 返回流式增量、工具事件或中断 |
| 平台到客户端 | error | error | 返回连接或运行错误 |
完成鉴权
wst_ Token 鉴权一个 WebSocket 渠道账号,不直接鉴权智能体模板或智能体实例。渠道账号的绑定模式和绑定对象决定消息进入哪个智能体。
使用 Subprotocol 鉴权
推荐在 HTTP Upgrade 请求中传递 Token:
Sec-WebSocket-Protocol: infini.bridge.v1, bearer.<base64url_token>base64url_token 是 wst_ Token 的无填充 Base64URL 编码。连接成功后,服务端只回显:
Sec-WebSocket-Protocol: infini.bridge.v1这种方式在连接建立前完成鉴权,不发送 auth 或接收 auth_ok 帧。
使用帧内鉴权
客户端无法设置自定义 WebSocket 子协议时,可以使用帧内鉴权。建立连接时不要请求 Sec-WebSocket-Protocol;连接建立后,在目标部署规定的鉴权超时内发送:
{
"type": "auth",
"auth": {
"token": "wst_xxxxxxxxxxxxxxxxxxxx"
}
}鉴权成功后收到:
{
"type": "auth_ok",
"auth_ok": {
"connection_id": "550e8400-e29b-41d4-a716-446655440000",
"server_time": "1784685600000000000"
}
}| 字段 | 类型 | 说明 |
|---|---|---|
connection_id | string | 当前连接的标识,可用于客户端日志关联 |
server_time | string | Unix 纳秒时间;按字符串或 BigInt 处理 |
每次重连都必须重新鉴权。不要在日志、错误消息或客户端页面中输出 wst_ 明文。
Subprotocol 鉴权和帧内鉴权是两种独立路径。已经通过 Subprotocol 完成鉴权时,不要再发送 auth 帧;使用帧内鉴权时,不要只请求 infini.bridge.v1 子协议。
设置消息元数据
message、message_reply、message_stream_reply 和 interrupt_response 使用 meta 关联用户、对话和回复。
| 字段 | 必填 | 说明 |
|---|---|---|
msg_id | 是 | 由客户端为每条输入生成的全局唯一 ID,推荐使用 UUID;平台不会用该字段阻止重复请求 |
user_id | message 和 interrupt_response 必填 | 调用方业务系统中的稳定用户标识,用于定位渠道用户、用户绑定、单聊会话和等待中断响应的运行 |
user_name | 否 | 用于显示的用户名称 |
chat_type | 否 | p2p 或 group;省略时按 p2p 处理 |
chat_id | 群聊必填 | 稳定的业务对话标识;单聊省略时使用 user_id 维持对话连续性 |
不要为一次重试重复使用 msg_id 并假设平台会自动去重。会产生业务副作用的工具仍应在业务系统中使用幂等键。
wst_ Token 鉴权的是 WebSocket 渠道账号;user_id 负责在该账号内标识实际发送消息的业务用户。user_id 不是平台生成的用户 ID,也不是凭证。请使用业务系统中不包含敏感信息的稳定标识,不要按消息随机生成,也不要用可能变化的 user_name 代替。发送消息时,客户端应按 msg_id 保存完整的原始 meta,直到收到对应的 message_reply 或 done: true,或者已经按照故障处理流程确认结束。
同一个 WebSocket 渠道账号可以有多个客户端连接。客户端应使用 msg_id、chat_id 和自身连接上下文筛选并关联回复,不要假设账号上的每条回复只会发送给发起请求的连接。
发送用户消息
{
"type": "message",
"message": {
"meta": {
"msg_id": "3eeec74d-a4d4-4517-89ec-ef8aeb676666",
"user_id": "user-001",
"user_name": "Alice",
"chat_type": "p2p",
"chat_id": "chat-001"
},
"content": "帮我查询订单状态"
}
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
message.meta | object | 是 | 消息元数据 |
message.content | string | 是 | 用户文本或斜杠指令 |
模板绑定需要按用户创建专属实例时,使用稳定的 user_id 发送 /init。实例绑定可以直接发送普通消息。
WebSocket 接入以文本消息为受支持的输入路径。需要上传文件并把文件作为智能体输入时,使用 Invoke API 的文件上传接口,除非目标部署另行提供 WebSocket 文件输入约定。
接收回复
接收完整文本
message_reply 返回一条完整文本,常用于斜杠指令等非流式回复:
{
"type": "message_reply",
"message_reply": {
"meta": {
"msg_id": "3eeec74d-a4d4-4517-89ec-ef8aeb676666",
"chat_type": "p2p",
"chat_id": "chat-001"
},
"content": "已创建智能体实例"
}
}/help 是一个需要单独处理的结构化回复。外层仍是 message_reply,但 message_reply.content 是 JSON 文本,而不是已经解析的 JSON 对象。解析该字符串后得到包含 items 和 total 的对象。下面是只有一个命令条目的简化示例:
{
"items": [
{
"name": "help",
"description": "查看所有可用命令,包含命令说明、使用示例、子命令与参数。",
"example": "/help - 查看所有可用命令",
"scope": "system"
}
],
"total": 1
}items 是命令描述数组,total 是命令数量。每个命令可以包含 name、description、example、scope、requires_init、args 和 sub_commands;Protocol Buffers JSON 会省略未设置的可选字段,因此客户端不应假定每个字段都存在。例如,JavaScript 客户端可以使用 JSON.parse(frame.message_reply.content) 后再渲染命令清单。系统命令的用途参见使用系统斜杠命令。
接收流式事件
message_stream_reply 返回智能体运行中的增量事件:
{
"type": "message_stream_reply",
"message_stream_reply": {
"meta": {
"msg_id": "3eeec74d-a4d4-4517-89ec-ef8aeb676666",
"chat_type": "p2p",
"chat_id": "chat-001"
},
"content": {
"content": "订单正在处理中"
}
}
}| 字段 | 类型 | 说明 |
|---|---|---|
thinking.content | string | 思考文本增量 |
content.content | string | 最终回答文本增量 |
tool_call | object | 工具调用;包含 id、name 和 JSON 字符串 arguments |
tool_call_result | object | 工具结果;包含 id、result 和 status |
interrupt | object | 中断;包含 type、id 和 JSON 字符串 data |
status | object | 尽力而为的进度事件;包含 seq、source、phase、JSON 字符串 details、timestamp 和可选 context_key |
done | boolean | true 表示本次智能体运行结束 |
status.seq 和 status.timestamp 在 Protocol Buffers 中是 int64,JSON 帧将它们编码为十进制字符串;timestamp 使用 Unix 毫秒。客户端应按字符串或 BigInt 处理,不要先转换为 JavaScript Number。例如:
{
"type": "message_stream_reply",
"message_stream_reply": {
"meta": {
"msg_id": "3eeec74d-a4d4-4517-89ec-ef8aeb676666",
"chat_type": "p2p",
"chat_id": "chat-001"
},
"status": {
"seq": "12",
"source": "loop_telemetry",
"phase": "start",
"details": "{\"iteration\":1,\"phase\":\"start\"}",
"timestamp": "1784685600123"
}
}
}该示例展示一种可能返回的循环进度。读取 details 时,请按字段名处理,不依赖字段顺序。
同一帧最多包含一个增量字段;非终止帧通常省略 done,终止帧中的 done 为 true。done 可以单独出现,也可以与最后一个文本增量同时出现。客户端应分别累积 thinking 和 content,按工具调用 ID 关联工具事件,并在收到 done: true 后结束本次运行。
status 用于进度展示,可以按 seq 排序和去重,也可以忽略。它可能缺失或重复,details 结构随 source 变化。请以 done: true 判断运行结束,以 Trace 进行审计,并由客户端另行保存重连恢复所需的状态。
响应中断
中断出现在 message_stream_reply.interrupt。客户端应保存该帧的 meta 和中断 ID,并通过 msg_id 找到发送原消息时保存的完整 meta。完成用户确认或业务工具执行后,在同一 WebSocket 连接发送:
{
"type": "interrupt_response",
"interrupt_response": {
"meta": {
"msg_id": "3eeec74d-a4d4-4517-89ec-ef8aeb676666",
"user_id": "user-001",
"chat_type": "p2p",
"chat_id": "chat-001"
},
"payload": {
"interrupt_id": "interrupt-001",
"action": "RESPOND",
"response_data": "{\"ticket_id\":\"INC-1024\",\"status\":\"处理中\"}"
}
}
}| 动作 | 用途 | response_data |
|---|---|---|
APPROVE | 批准确认型中断 | 通常为空 |
REJECT | 拒绝确认型中断 | 通常为空 |
RESPOND | 返回用户输入或用户工具包执行结果 | 使用中断所需的 JSON 字符串 |
ERROR | 表示调用方无法返回有效结果 | 提供可供智能体处理的错误数据 |
interrupt_response.meta 必须标识触发中断的原消息,并包含其稳定 user_id。在协议的正常行为中,服务端中断帧会返回这份消息上下文;服务端修复上线前的兼容处理见下面的说明。用户工具包的执行和参数校验参见定义用户工具包。
注意
服务端修复上线前,中断帧可能缺少 user_id
当前已确认一个 WebSocket HITL 缺陷:服务端返回的 message_stream_reply.interrupt.meta 可能没有原消息的 user_id。如果客户端直接原样返回这份不完整的 meta,平台可能无法定位原来等待响应的运行;WebSocket 连接仍会保持在线并交换 PING 和 PONG,但工具结果、最终回复和 done: true 不会继续返回,原 Trace 也不会出现决定后的执行事件。
请按 msg_id 查找当前客户端保存的原始消息 meta,并只从该记录补回缺少的 user_id。如果当前客户端没有对应的待处理消息,不要响应该中断;同一渠道账号上的其他连接可能会收到并处理它。客户端确认中断属于自己发出的消息,却已经丢失原记录,或者中断帧的消息上下文与原记录不一致时,请停止发送决定,保存原始帧并联系平台支持。不要根据 chat_id、user_name、当前界面用户或另一条待处理消息猜测 user_id。
下面的代码展示补偿逻辑;pendingMessages 表示客户端在发送消息时保存的 msg_id 到完整 meta 的映射:
const interruptMeta = frame.message_stream_reply.meta;
const originalMeta = pendingMessages.get(interruptMeta.msg_id);
if (!originalMeta) {
return; // 不是当前客户端保存的待处理消息
}
if (!originalMeta?.user_id) {
throw new Error("无法确定原消息的 user_id;停止发送中断决定");
}
if (
interruptMeta.user_id &&
interruptMeta.user_id !== originalMeta.user_id
) {
throw new Error("中断 user_id 与原消息不一致;停止发送中断决定");
}
if (
interruptMeta.chat_id &&
originalMeta.chat_id &&
interruptMeta.chat_id !== originalMeta.chat_id
) {
throw new Error("中断 chat_id 与原消息不一致;停止发送中断决定");
}
const responseMeta = {
...originalMeta,
...interruptMeta,
user_id: originalMeta.user_id,
};服务端修复后,相同逻辑仍可核对返回的 user_id 是否与原消息一致。
注意
收到 done: true 后再确认运行结束
发送 APPROVE 或 REJECT 后,请继续等待原 meta 对应的 done: true,再确认本次运行结束。决定已经发出或收到 tool_call 都不表示决定已经送达原运行,也不证明工具已经执行。
如果连接仍正常但没有收到 done: true,请把本次结果记为未知,保留原始消息和中断的 meta、中断 ID 与发生时间。在原 Trace 中检查决定后的执行事件,并在 MCP 服务或实际写入的业务系统中核对工具是否执行;没有新的独立“审批 Trace”不表示记录丢失。不要重复发送决定或原消息;没有幂等保证时,也不要重试有副作用的操作。生产接入前,分别使用单项、无副作用的调用验证批准和拒绝都能收到 done: true;任一测试未通过时,请联系平台支持并改用已经完成审批验收的入口。
如果一个待审批中断同时包含多个 tool_calls,请保存原始帧和 meta,停止有副作用的处理并联系平台支持。当前接口一次只提交一个决定,无法保证批内每项决定都被保留,也未规定断线后的处理方式;不要逐项响应或使用内部 EDIT 动作。
处理心跳和重连
Bridge 使用 JSON 文本帧实现业务层心跳,不使用 WebSocket 协议控制帧:
{"type":"PING"}收到 PING 后发送:
{"type":"PONG"}客户端也可以发送 PING 并等待 PONG。心跳间隔、允许丢失次数、鉴权超时和连接数限制由目标部署配置。
断线恢复时:
- 使用带抖动的指数退避建立新连接。
- 重新鉴权。
- 恢复客户端自己的消息关联状态。
- 只重试能够安全重复执行的业务请求。
处理错误
HTTP Upgrade 错误
连接建立前的错误使用 HTTP 状态和 JSON 响应:
{
"code": "unauthorized",
"message": "token invalid"
}| HTTP 状态 | code | 处理 |
|---|---|---|
| 400 | missing_protocol_version | 添加 infini.bridge.v1 子协议 |
| 400 | invalid_token_encoding | 使用无填充 Base64URL 重新编码 wst_ Token |
| 401 | unauthorized | 检查、轮换或重新启用 Token |
| 401 | account_mismatch | 使用绑定到目标 WebSocket 渠道账号的 Token |
| 502 或 503 | gateway_unavailable | 按退避策略重试,并检查目标部署状态 |
| 503 | pre_auth_pool_full | 延迟后重试帧内鉴权,或改用 Subprotocol 鉴权 |
WebSocket 错误帧
连接建立后的错误使用:
{
"type": "error",
"error": {
"code": "invalid_message",
"message": "message parse error"
}
}code | 连接影响 | 处理 |
|---|---|---|
duplicate_auth | 关闭 | 建立新连接并只鉴权一次 |
auth_required | 可能保持或关闭 | 完成鉴权;连接已关闭时重新连接 |
auth_error、unauthorized、account_mismatch | 关闭 | 修正 Token 或渠道账号后重新连接 |
auth_timeout | 关闭 | 在目标部署的时限内发送 auth,或使用 Subprotocol 鉴权 |
max_connections_exceeded | 关闭 | 复用或关闭闲置连接,再按退避策略重试 |
server_restart | 关闭 | 按退避策略重新连接和鉴权 |
invalid_message | 保持 | 修正 JSON 信封或字段后发送新请求 |
meta_user_id_required | 保持 | 添加稳定且非空的 meta.user_id |
agent_error | 保持 | 将当前运行标记为失败,并结合 Trace 排查智能体实例 |
连接还可能以标准 WebSocket Close Code 结束:1000 表示正常结束或心跳超时,1001 表示服务端关闭,1003 表示鉴权或帧类型不可接受,1008 表示策略限制。客户端应同时记录最后一个 error 帧和 Close Code,但不要记录 Token 明文。
鉴权和账号错误在配置修正前不应无界重试。连接暂时不可用、服务重启和网络中断可以按有上限的退避策略恢复。