> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inputapi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# WebSocket 连接与运行环境

> 配置服务端客户端、连接限制与共享传输模块。

本组示例使用 Node.js 22.18+ 或 24 LTS 与固定版本 `ws@8.22.0`，从文档目录运行。客户端在终端或应用服务器执行；浏览器原生 WebSocket 的认证方式不同，不能直接复制长期 API Key 到网页。

## 选择协议

| 能力 | 指南 | API 说明 |
| - | - | - |
| 同一连接内串行多轮文本 | [Responses WebSocket](/zh/guides/responses-websocket.md) | [事件与握手](/zh/api-reference/responses-websocket.md) |
| Realtime 文字与音频 | [Realtime](/zh/guides/realtime.md) | [会话与事件](/zh/api-reference/realtime.md) |

SDK 的 HTTP SSE 使用 `data:` / `event:` 行；WebSocket 每条应用消息就是 JSON，不包含这些前缀，也不以 `[DONE]` 判断完成。网络分帧、UTF-8 重组、ping/pong 由 `ws` 处理。

## 环境设置

```sh theme={null}
npm ci
export API_BASE_URL="https://gateway.example.com/v1"
export API_KEY="YOUR_API_KEY"
export API_MODEL="YOUR_MODEL_ID"
export API_WS_TIMEOUT_MS="60000"
```

`API_BASE_URL` 包含 `/v1` 和可选部署前缀。共享模块将 HTTPS 改为 WSS、HTTP 改为 WS，再追加端点；不接受凭据、query 或 fragment 混入基础地址。Realtime 的模型由代码放入 URL query；Responses 的模型在 create 事件内。生产地址使用 HTTPS/WSS。

这是服务端 Bearer 认证示例。HTTP 101 只表示升级成功；渠道选择、会话配置或后续生成仍可能失败。不要自动跟随重定向；确认正确 API 地址后重试。协议、权限与代理设置见[生产接入](/zh/guides/production.md)。

## 共用传输模块

`examples/websocket-client.mjs` 被两个示例导入。复制示例到其他项目时，同时复制此模块，并安装 `ws@8.22.0`。这不是可单独执行的调用脚本。

```javascript theme={null}
import WebSocket from 'ws';

// Shared by both server-side examples. No reconnect or automatic retry.
export async function openWebSocket(endpoint, query = {}) {
  const key = process.env.API_KEY;
  const model = process.env.API_MODEL;
  const timeout = Number(process.env.API_WS_TIMEOUT_MS ?? 60000);
  if (!key?.trim() || !model?.trim() || !Number.isInteger(timeout) || timeout < 100 || timeout > 120000) {
    throw new Error('Set API_KEY, API_MODEL, and a 100..120000 ms timeout.');
  }
  let url;
  try { url = new URL(process.env.API_BASE_URL); }
  catch { throw new Error('Set a valid HTTP(S) API_BASE_URL including /v1.'); }
  if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
    throw new Error('API_BASE_URL must use HTTP(S) without credentials, query, or fragment.');
  }
  url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
  url.pathname = `${url.pathname.replace(/\/+$/, '')}/${endpoint}`;
  for (const [name, value] of Object.entries(query)) url.searchParams.set(name, value);
  const ws = new WebSocket(url, {
    headers: { Authorization: `Bearer ${key}` },
    followRedirects: false, perMessageDeflate: false,
    handshakeTimeout: timeout, maxPayload: 1024 * 1024,
  });
  const queue = [];
  let queuedBytes = 0;
  let waiting;
  let fault;
  let closed = false;
  let rejectOpen;
  const opened = new Promise((resolve, reject) => {
    rejectOpen = reject;
    ws.once('open', resolve);
  });
  function fail(message) {
    if (fault || closed) return;
    fault = new Error(message);
    rejectOpen(fault);
    waiting?.reject(fault);
    waiting = undefined;
    ws.terminate();
  }
  const deadline = setTimeout(() => fail('WebSocket workflow deadline exceeded.'), timeout);
  ws.on('unexpected-response', (_request, response) => {
    response.resume();
    fail(`WebSocket handshake failed (HTTP ${response.statusCode}).`);
  });
  ws.on('error', () => fail('WebSocket transport failed.'));
  ws.on('close', () => fail('WebSocket closed before workflow completion.'));
  ws.on('message', (data, isBinary) => {
    if (closed || fault) return;
    if (isBinary) { fail('Expected a JSON text message, not a binary frame.'); return; }
    let event;
    try { event = JSON.parse(data.toString()); }
    catch { fail('Invalid JSON event.'); return; }
    if (!event || typeof event !== 'object' || Array.isArray(event) || typeof event.type !== 'string') {
      fail('Invalid event envelope.'); return;
    }
    if (event.type === 'error') { fail('Server returned a WebSocket error event.'); return; }
    if (waiting) {
      const pending = waiting;
      waiting = undefined;
      pending.resolve(event);
      return;
    }
    queuedBytes += data.length;
    if (queue.length >= 256 || queuedBytes > 4 * 1024 * 1024) {
      fail('WebSocket receive queue limit exceeded.'); return;
    }
    queue.push({ event, bytes: data.length });
  });
  try { await opened; }
  catch (error) { clearTimeout(deadline); throw error; }
  return {
    model,
    async send(event) {
      if (fault) throw fault;
      if (closed || ws.readyState !== WebSocket.OPEN) throw new Error('WebSocket is not open.');
      await new Promise((resolve, reject) => ws.send(JSON.stringify(event), (error) => {
        if (error) reject(new Error('WebSocket send failed.'));
        else resolve();
      }));
    },
    async receive() {
      if (fault) throw fault;
      if (closed) throw new Error('WebSocket is closed.');
      if (queue.length) {
        const next = queue.shift();
        queuedBytes -= next.bytes;
        return next.event;
      }
      if (waiting) throw new Error('Only one event reader is allowed.');
      return await new Promise((resolve, reject) => { waiting = { resolve, reject }; });
    },
    close() {
      closed = true;
      clearTimeout(deadline);
      waiting?.reject(new Error('WebSocket reader closed.'));
      waiting = undefined;
      ws.close(1000);
      // Bound shutdown even if the peer never acknowledges the close frame.
      const cleanup = setTimeout(() => ws.terminate(), 1000);
      cleanup.unref();
      ws.once('close', () => clearTimeout(cleanup));
    },
  };
}
```

## 示例边界

总截止时间包含握手、配置、发送与等待两轮或一轮结果，默认 60 秒，可设 100–120000 毫秒。单条入站消息限制 1 MiB，排队最多 256 条 / 4 MiB；这些是示例的资源保护值，不是网关或供应商限额。

非法 JSON、二进制帧、error 事件、提前关闭和超时均终止示例。日志只输出固定诊断，不打印远端完整错误或密钥；必要时结合网关日志排查。示例不自动重试、重连、发送取消或执行工具。连接关闭不是上游任务已取消的保证。

HTTP(S) 代理环境变量不会自动配置 `ws` 的代理连接；企业代理需在应用中显式接入经过验证的 Agent。此示例直接连接指定网关地址，TLS 验证保持启用。

[Read in English](/en/guides/websocket.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.