用 WebSocket 订阅事件
在事件流的同一地址上建立只读 WebSocket:握手认证、帧格式、关闭码、限额与断线回填。
会话事件除了用 订阅事件(SSE)拉取,也可以在同一个地址上建立 WebSocket 接收:每个事件一帧,内容与 SSE 的事件数据完全相同。这是本平台在官方 SSE 之外额外提供的接入方式,适合在服务端长期挂着连接、把事件转发给自己系统的场景。
wss://api.deepraxis.ai/agent/v1/sessions/{session_id}/events/stream这条连接是只读的:它只推送事件,不接收任何消息。发消息、打断仍然用 发送事件。
握手与认证
握手请求带与 HTTP 接口相同的认证头:Authorization: Bearer <API Key> 或 x-api-key: <API Key>。
浏览器原生的 WebSocket 不能设置请求头,所以这是服务端用法。浏览器里请用 SSE,或者由你的服务端建立连接后再转给前端。
握手失败时连接不会建立,服务端直接返回 HTTP 错误(错误体格式见错误):
| 情况 | 返回 |
|---|---|
| 没带凭证,或 Key 无效、已禁用 | 401 |
| 会话不存在,或不属于这个 Key 所在的工作区 | 404 |
| 同时进行的请求超过 Key 的并发上限 | 429,见限流 |
一条打开的 WebSocket 和一个进行中的 SSE 一样,会一直占用 Key 的一个并发名额,直到连接关闭。
帧
每一帧都是一个 JSON 文本。连接建立后,第一帧是就绪通知:
{"type":"session.events.ready"}收到它之后,会话里产生的每个事件以一帧送达,event 字段就是 SSE 里的事件数据:
{"type":"session.event","event":{"id":"d4dc3d96-…","type":"agent.message","processed_at":"2026-09-26T13:35:04.906Z","content":[{"type":"text","text":"好"}]}}| 字段 | 说明 |
|---|---|
type | session.events.ready(就绪,只出现一次)或 session.event(一个事件) |
event | 仅 session.event 帧有;结构与事件列表返回的单个事件相同 |
同一个事件可能送达不止一次。例如一条 user.message 会先以 processed_at: null(已接纳、待处理)出现,被处理后再以带时间的形式出现一次。请按 event.id 去重,以最后一次为准。
关闭码
| 关闭码 | 含义 | 你该做什么 |
|---|---|---|
1000 | 服务端正常结束订阅 | 需要继续接收就重新连接 |
1008 | 客户端向连接发送了消息 | 这条连接只读;不要发送任何帧 |
1011 | 服务端内部中断 | 重新连接,并用事件列表回填 |
1013 | 暂时超出限额(见下) | 重新连接,并用事件列表回填 |
限额
- 一条连接累计最多推送约 512 KB 的事件,超出后以
1013关闭。长会话请准备好重连。 - 服务端与会话之间的内部连接断开时会自动重试;多次重试仍失败,也以
1013关闭。
断线与回填
断开期间产生的事件不会补发。重新连接后,用事件列表按时间补齐:
GET /agent/v1/sessions/{session_id}/events?order=asc&created_at[gt]=<你收到的最后一个事件的 processed_at>回填的事件和新连接推送的事件可能重叠,按 event.id 去重即可。
示例
Bun(内置 WebSocket 支持 headers):
const ws = new WebSocket(
`wss://api.deepraxis.ai/agent/v1/sessions/${sessionId}/events/stream`,
{ headers: { Authorization: `Bearer ${process.env.PRAXIS_API_KEY}` } },
);
ws.addEventListener("message", (message) => {
const frame = JSON.parse(String(message.data));
if (frame.type === "session.events.ready") console.log("ready");
if (frame.type === "session.event") console.log(frame.event.type, frame.event);
});
ws.addEventListener("close", (close) => console.log("closed", close.code));Node(ws 包):
import WebSocket from "ws";
const ws = new WebSocket(
`wss://api.deepraxis.ai/agent/v1/sessions/${sessionId}/events/stream`,
{ headers: { Authorization: `Bearer ${process.env.PRAXIS_API_KEY}` } },
);
ws.on("message", (data) => {
const frame = JSON.parse(String(data));
if (frame.type === "session.event") console.log(frame.event.type, frame.event);
});
ws.on("close", (code) => console.log("closed", code));Python(websockets 包):
import asyncio, json, os
import websockets
async def main(session_id: str):
url = f"wss://api.deepraxis.ai/agent/v1/sessions/{session_id}/events/stream"
headers = {"Authorization": f"Bearer {os.environ['PRAXIS_API_KEY']}"}
async with websockets.connect(url, additional_headers=headers) as ws:
async for message in ws:
frame = json.loads(message)
if frame["type"] == "session.event":
print(frame["event"]["type"], frame["event"])
asyncio.run(main("sesn_123"))