WebSocket 协议
1. WebSocket 简介
WebSocket 是一种在单个 TCP 连接上进行双向通信的协议,适合需要长连接、低延迟、服务端持续推送消息的场景。客户端先发起一个普通 HTTP 请求,服务端通过 Upgrade: websocket 完成协议升级;升级成功后,连接就不再按一次请求一次响应的 HTTP 模式工作,而是由客户端和服务端互相发送消息帧。
DolphinX 的 HTTP API 和 WebSocket API 使用同一组业务能力。区别在于:HTTP 通过 method + path + query + body 表达一次调用;WebSocket 连接建立后,业务调用通过 JSON 请求帧表达,其中 action 表示要调用的接口,payload 承载请求参数。
建议在以下场景使用 WebSocket:
- 需要流式对话、实时展示模型输出或上下文处理事件。
- 客户端需要复用同一个连接连续发送多次请求。
- 需要用 requestId / correlationId 在同一连接上匹配多路请求和响应。
如果只是偶发查询、管理类操作或不需要实时流式返回,HTTP 调用通常更简单。
2. 连接
WebSocket 端点路径与 HTTP 相同,通过协议升级连接:
ws://<host>:<port>/agent-bus/v1
wss://<host>:<port>/agent-bus/v1
客户端推荐在握手时声明 agent-bus subprotocol,并通过 Authorization 携带登录后获得的 token:
Sec-WebSocket-Protocol: agent-bus
Authorization: Bearer <token>
通过 Python 发起 WebSocket 请求的示例:
import asyncio
import json
import websockets
async def main():
token = "<token>"
url = "ws://localhost:8848/agent-bus/v1"
async with websockets.connect(
url,
additional_headers={"Authorization": f"Bearer {token}"},
subprotocols=["agent-bus"],
) as ws:
request = {
"type": "REQUEST",
"requestId": "req_001",
"action": "agent.accessible",
}
await ws.send(json.dumps(request, ensure_ascii=False))
response = await ws.recv()
print(response)
asyncio.run(main())
3. 消息格式
所有 WebSocket 消息都是 JSON。
请求帧
客户端调用接口时发送 REQUEST 帧。请求帧只识别下列顶层参数,其它参数一律忽略:
| 参数 | 必填 | 说明 |
|---|---|---|
| type | 是 | 固定为 "REQUEST"。 |
| action | 是 | WebSocket action 名,见下方 Action 对照表。 |
| requestId | 否,但是建议填写 | 客户端生成的请求 ID,服务端会原样回填到响应帧的 correlationId。 |
| agentId | 视 action 而定 | Agent ID。当对应 HTTP 路由含 {agentId} 或业务要求指定 Agent
时必填。 |
| sessionId | 视 action 而定 | 会话 ID。当对应 HTTP 路由含 {sessionId}
或业务要求指定会话时必填。 |
| payload | 视 action 而定 | JSON 对象,承载 HTTP 请求体、查询串,以及除 agentId / sessionId 以外的路径参数。 |
{
"type": "REQUEST",
"requestId": "req_001",
"action": "chat.completions",
"agentId": "agent_xxx",
"sessionId": "sess_001",
"payload": {
"message": "你好",
"stream": true,
"contextEvent": true
}
}
响应帧
普通非流式响应会返回一条 RESPONSE 帧。客户端可用 correlationId 匹配请求中的 requestId。
{
"type": "RESPONSE",
"msgId": "...",
"correlationId": "req_001",
"action": "chat.completions",
"agentId": "agent_xxx",
"sessionId": "sess_001",
"data": {}
}
流式帧
流式请求会返回多条消息。每条消息都是独立 JSON,不会包装成 HTTP SSE 的 event: / data: 文本格式。客户端应持续读取,直到收到 STREAM_END 或 STREAM_ERROR。
{"type":"STREAM_START","correlationId":"req_001","action":"chat.completions","data":{"requestId":"req_001","model":"<resolved-model>"}}
{"type":"STREAM_REASONING","correlationId":"req_001","action":"chat.completions","data":{"reasoning":"..."}}
{"type":"STREAM_CHUNK","correlationId":"req_001","action":"chat.completions","data":{"content":"..."}}
{"type":"STREAM_END","correlationId":"req_001","action":"chat.completions","data":{"metadata":{}}}
WebSocket 对话补全支持 contextEvent=true。开启后,上下文压缩等过程会以 EVENT 消息返回,并使用同一个 correlationId。
错误帧
{
"type": "ERROR",
"msgId": "...",
"correlationId": "req_001",
"action": "chat.completions",
"agentId": "agent_xxx",
"sessionId": "sess_001",
"error": {
"code": "BUS_SESSION_NOT_FOUND",
"message": "session not found"
},
"headers": {
"busErrorCode": "BUS_SESSION_NOT_FOUND",
"httpStatus": "404"
}
}
4. HTTP 请求转换为 WebSocket 请求
把 HTTP 请求转换成 WebSocket 请求时,只需要按下面步骤处理:
- 在 Action 对照表中找到 HTTP 接口对应的 action。
- 将 HTTP 路径里的
{agentId}/{sessionId}放到请求帧顶层的 agentId / sessionId。 - 将其它路径参数、查询参数和 JSON 请求体字段放到 payload。
- 生成一个 requestId,发送完整 JSON 请求帧。
- 读取服务端返回消息,用响应里的 correlationId 匹配请求的 requestId。
同一个字段不要在顶层和 payload 中同时出现且取值不同,否则服务端会拒绝请求。例如 agentId 已经放在顶层时,不要再在
payload.agentId 中传入另一个值。
示例 1:GET 请求,路径参数 + 查询参数
HTTP 请求:
GET /agent-bus/v1/session/sess_001/messages?startSeq=1&endSeq=20
Authorization: Bearer <token>
转换规则:
| HTTP 中的位置 | HTTP 值 | WebSocket 中的位置 |
|---|---|---|
| 路径 | sess_001 | 顶层 sessionId |
| 查询参数 | startSeq=1 | payload.startSeq |
| 查询参数 | endSeq=20 | payload.endSeq |
| 接口 | GET /session/{id}/messages | action: "session.getMessages" |
发送的 WebSocket 请求帧:
{
"type": "REQUEST",
"requestId": "req_42",
"action": "session.getMessages",
"sessionId": "sess_001",
"payload": {
"startSeq": 1,
"endSeq": 20
}
}}
示例 2:GET 请求,查询参数中的 agentId 放到顶层
HTTP 请求:
GET /agent-bus/v1/session/list?agentId=agent_xxx&limit=50&offset=0
Authorization: Bearer <token>
转换规则:
| HTTP 中的位置 | HTTP 值 | WebSocket 中的位置 |
|---|---|---|
| 查询参数 | agentId=agent_xxx | 顶层 agentId |
| 查询参数 | limit=50 | payload.limit |
| 查询参数 | offset=0 | payload.offset |
| 接口 | GET /session/list | action: "session.list" |
发送的 WebSocket 请求帧:
{
"type": "REQUEST",
"requestId": "req_43",
"action": "session.list",
"agentId": "agent_xxx",
"payload": {
"limit": 50,
"offset": 0
}
}
示例 3:POST 请求,路径参数 + JSON 请求体
HTTP 请求:
POST /agent-bus/v1/workspace/sess_001/replace
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "main.dos",
"hash": "xxh64:...",
"replacements": [
{
"old": "return 1",
"new": "return 2"
}
]
}
转换规则:
| HTTP 中的位置 | HTTP 值 | WebSocket 中的位置 |
|---|---|---|
| 路径 | sess_001 | 顶层 sessionId |
| 请求体 | name | payload.name |
| 请求体 | hash | payload.hash |
| 请求体 | replacements | payload.replacements |
| 接口 | POST /workspace/{sessionId}/replace | action: "workspace.replace" |
发送的 WebSocket 请求帧:
{
"type": "REQUEST",
"requestId": "req_44",
"action": "workspace.replace",
"sessionId": "sess_001",
"payload": {
"name": "main.dos",
"hash": "xxh64:...",
"replacements": [
{
"old": "return 1",
"new": "return 2"
}
]
}
}
5. 支持的 Action
WebSocket 支持下表列出的 action。
| Action | 对应 HTTP 接口 |
|---|---|
| agent.accessible | GET /agent/accessible |
| agent.get | GET /agent/{id} |
| agent.llm.selectable | GET /agent/{id}/llm/selectable |
| session.create | POST /session/create |
| session.get | GET /session/{id} |
| session.update | PUT /session/{id} |
| session.delete | DELETE /session/{id} |
| session.list | GET /session/list |
| session.getMessages | GET /session/{id}/messages |
| session.generateSummary | POST /session/{id}/summary |
| chat.completions | POST /chat/completions |
| skill.catalog | GET /skill/{agentId}/catalog |
| skill.getInstruction | GET /skill/{id}/instruction |
| skill.files.list | GET /skill/{id}/files/list |
| skill.files.get | GET /skill/{id}/files/get |
| skill.files.search | POST /skill/{id}/files/search |
| skill.files.batchGet | POST /skill/{id}/files/batchGet |
| workspace.list | GET /workspace/{sessionId}/files |
| workspace.read | GET /workspace/{sessionId}/file |
| workspace.write | POST /workspace/{sessionId}/file |
| workspace.delete | DELETE /workspace/{sessionId}/file |
| workspace.search | POST /workspace/{sessionId}/search |
| workspace.replace | POST /workspace/{sessionId}/replace |
| network.fetch | POST /network/fetch |
| mcp.tools.call | POST /mcp/tools/call |
| context.preview | POST /context/preview |
| llm.complete | POST /llm/complete |
