# 用 WebSocket 订阅事件 (/docs/api/native/agent/streamEventsWebSocket)



会话事件除了用 [订阅事件](/docs/api/native/agent/streamEvents)（SSE）拉取，也可以在**同一个地址**上建立 WebSocket 接收：每个事件一帧，内容与 SSE 的事件数据完全相同。这是本平台在官方 SSE 之外额外提供的接入方式，适合在服务端长期挂着连接、把事件转发给自己系统的场景。

```text
wss://api.deepraxis.ai/agent/v1/sessions/{session_id}/events/stream
```

这条连接是**只读**的：它只推送事件，不接收任何消息。发消息、打断仍然用 [发送事件](/docs/api/native/agent/sendEvents)。

## 握手与认证 [#握手与认证]

握手请求带与 HTTP 接口相同的认证头：`Authorization: Bearer <API Key>` 或 `x-api-key: <API Key>`。

浏览器原生的 `WebSocket` 不能设置请求头，所以这是**服务端用法**。浏览器里请用 SSE，或者由你的服务端建立连接后再转给前端。

握手失败时连接不会建立，服务端直接返回 HTTP 错误（错误体格式见[错误](/docs/api/errors)）：

| 情况                      | 返回                                 |
| ----------------------- | ---------------------------------- |
| 没带凭证，或 Key 无效、已禁用       | `401`                              |
| 会话不存在，或不属于这个 Key 所在的工作区 | `404`                              |
| 同时进行的请求超过 Key 的并发上限     | `429`，见[限流](/docs/api/rate-limits) |

一条打开的 WebSocket 和一个进行中的 SSE 一样，会一直占用 Key 的一个并发名额，直到连接关闭。

## 帧 [#帧]

每一帧都是一个 JSON 文本。连接建立后，第一帧是就绪通知：

```json
{"type":"session.events.ready"}
```

收到它之后，会话里产生的每个事件以一帧送达，`event` 字段就是 SSE 里的事件数据：

```json
{"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` 帧有；结构与[事件列表](/docs/api/native/agent/listEvents)返回的单个事件相同 |

同一个事件可能送达不止一次。例如一条 `user.message` 会先以 `processed_at: null`（已接纳、待处理）出现，被处理后再以带时间的形式出现一次。请按 `event.id` 去重，以最后一次为准。

## 关闭码 [#关闭码]

| 关闭码    | 含义          | 你该做什么          |
| ------ | ----------- | -------------- |
| `1000` | 服务端正常结束订阅   | 需要继续接收就重新连接    |
| `1008` | 客户端向连接发送了消息 | 这条连接只读；不要发送任何帧 |
| `1011` | 服务端内部中断     | 重新连接，并用事件列表回填  |
| `1013` | 暂时超出限额（见下）  | 重新连接，并用事件列表回填  |

## 限额 [#限额]

* 一条连接累计最多推送约 **512 KB** 的事件，超出后以 `1013` 关闭。长会话请准备好重连。
* 服务端与会话之间的内部连接断开时会自动重试；多次重试仍失败，也以 `1013` 关闭。

## 断线与回填 [#断线与回填]

断开期间产生的事件**不会补发**。重新连接后，用[事件列表](/docs/api/native/agent/listEvents)按时间补齐：

```http
GET /agent/v1/sessions/{session_id}/events?order=asc&created_at[gt]=<你收到的最后一个事件的 processed_at>
```

回填的事件和新连接推送的事件可能重叠，按 `event.id` 去重即可。

## 示例 [#示例]

Bun（内置 `WebSocket` 支持 `headers`）：

```ts
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` 包）：

```ts
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` 包）：

```python
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"))
```
