> For the complete documentation index, see [llms.txt](https://docs.convai.com/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/error-handling.md).

# 处理错误和断开连接

捕获连接失败，使用你自己的重连逻辑应对会话中断，并读取 Convai 为每条消息返回的确认。

处理 Convai Web SDK 会话可能遇到的故障：连接始终无法建立、会话中断，以及 Convai 拒绝的消息。当你从原型过渡到需要自行恢复的应用时，请使用本页。

### 故障会在何处显现

| 故障                 | 它会在何处显现                                               |
| ------------------ | ----------------------------------------------------- |
| `connect()` 无法打开会话 | 这里使用的 `connect()` Promise 被拒绝，且 `error` 事件会携带相同的错误触发。 |
| 角色从未发出已就绪信号        | 这里使用的 `error` 事件会触发（仅 LiveKit 传输）。                    |
| 会话结束，无论是有意还是无意     | 这里使用的 `disconnect` 事件会携带原因码触发。                        |
| Convai 接受或拒绝你发送的消息 | 这里使用的 `serverResponse` 事件会触发。                         |
| 角色选择不回答            | 这里使用的 `llmNoResponse` 事件会触发。                          |
| 会话即将因不活动而超时        | 这里使用的 `idleWarning` 事件会触发。                            |

### 捕获连接故障

包裹 `connect()` 中的 `尝试`/`捕获`。SDK 会发出 `error` 并带着它重新抛出的相同值，因此请在一个地方处理，以避免重复上报。

```typescript
try {
  await client.connect();
} catch (err) {
  showError(err instanceof Error ? err.message : String(err));
}
```

`connect()` 在发送任何请求之前会抛出这些错误：

| 消息                                             | 原因                         |
| ---------------------------------------------- | -------------------------- |
| `未提供配置。请将 config 传入 connect() 或将其存储在客户端中`      | 没有将配置传递给构造函数或 `connect()`. |
| `apiKey 或 authToken 至少需要其一，且 characterId 是必需的` | 缺少凭据或角色 ID。                |

当 Convai 返回 `/connect` 错误状态时，错误消息就是响应正文中的 `message`, `error`，或 `详情` 字段。列出多个问题的验证错误会合并成一条消息，如 `字段：问题；字段：问题`。没有可读正文的响应会产生 `HTTP <status>` ，后跟正文文本。

这里使用的 `error` 事件负载是有类型的 `未知`，因为它携带的是任何失败内容。读取属性之前先缩窄类型：

```typescript
client.on('error', (err: unknown) => {
  console.error(err instanceof Error ? err.message : err);
});
```

在 LiveKit 传输中，SDK 会在连接后等待角色发出就绪信号。如果 45 秒内没有收到信号， `error` 触发 `在 45000 毫秒内未收到 bot-ready`。会话仍保持连接；断开并重新连接以恢复。

### 响应断开

这里使用的 `disconnect` 事件会接收一个 `DisconnectReason` 代码。当传输未提供原因时，SDK 会报告 `0`。 `disconnect()` 在 WebSocket 传输上的调用会报告 `1`。最后一个原因也会保存在 `client.state.disconnectReason`，它在 `null` 在连接期间。

`getDisconnectReasonMessage()` 将代码转换为文本。它和 `DisconnectReasonEnum` 都从 `@convai/web-sdk/core`, `@convai/web-sdk/react`，以及 `@convai/web-sdk/vanilla`.

| Code | `DisconnectReasonEnum` 键 | `getDisconnectReasonMessage()` 文本 |
| ---- | ------------------------ | --------------------------------- |
| `0`  | `UNKNOWN_REASON`         | `网络不可用或原因未知`                      |
| `1`  | `CLIENT_INITIATED`       | `用户已断开连接`                         |
| `2`  | `DUPLICATE_IDENTITY`     | `另一个具有相同身份的客户端已加入`                |
| `3`  | `SERVER_SHUTDOWN`        | `服务器正在关闭`                         |
| `4`  | `PARTICIPANT_REMOVED`    | `通过 RemoveParticipant API 移除`     |
| `5`  | `ROOM_DELETED`           | `通过 DeleteRoom API 结束房间`          |
| `6`  | `STATE_MISMATCH`         | `状态不匹配`                           |
| `7`  | `JOIN_FAILURE`           | `加入失败`                            |
| `8`  | `MIGRATION`              | `参与者已移动到其他房间`                     |
| `9`  | `SIGNAL_CLOSE`           | `信令连接已关闭`                         |

任何其他代码都会返回 `已断开连接`，以及 `null` 返回 `已连接`.

### 使用退避策略重新连接

SDK 不会自行重新连接：它为 LiveKit 房间配置了一个会拒绝所有重试的策略。请从你的 `disconnect` 处理程序中，在原因不是有意断开时重新连接。

`reconnect()` 会使用最后一次配置再次连接，包括 Convai 返回的角色会话 ID，从而恢复对话。它会抛出 `没有可用于重新连接的已存储配置` 如果 `connect()` 从未带配置运行。

```typescript
import { DisconnectReasonEnum, getDisconnectReasonMessage } from '@convai/web-sdk/core';

async function retryWithBackoff(fn: () => Promise<void>, attempts = 3, delayMs = 500) {
  for (let i = 1; i <= attempts; i++) {
    try {
      return await fn();
    } catch (err) {
      if (i === attempts) throw err;
      await new Promise((r) => setTimeout(r, delayMs * 2 ** (i - 1)));
    }
  }
}

const doNotRetry: number[] = [
  DisconnectReasonEnum.CLIENT_INITIATED,
  DisconnectReasonEnum.DUPLICATE_IDENTITY,
  DisconnectReasonEnum.JOIN_FAILURE,
];

client.on('disconnect', (reason: number) => {
  console.log(getDisconnectReasonMessage(reason));
  if (!doNotRetry.includes(reason)) {
    void retryWithBackoff(() => client.reconnect());
  }
});
```

{% hint style="warning" %}
`disconnect()` 也会触发 `disconnect` 与 `CLIENT_INITIATED`。请排除该代码，否则每次有意断开都会启动重新连接。
{% endhint %}

### 检查消息是否已被接受

大多数发送方法会返回 `void` ，并且在传输未就绪时不做任何事，而不会抛出异常。这包括 `sendUserTextMessage()`, `sendTriggerMessage()`, `sendInterruptMessage()`, `updateContext()`, `updateDynamicInfo()`, `updateTemplateKeys()`, `updateSceneMetadata()`, `toggleTts()`, `toggleStt()`，以及 `resetIdleTimer()`。仅在 `botReady` 已触发后再发送：

```typescript
function safeSend(text: string) {
  if (!client.state.isConnected || !client.isBotReady) return;
  client.sendUserTextMessage(text);
}
```

`sendActionResult()` 是个例外：当会话未就绪或结果无效时，它会抛出异常。参见 [处理角色动作](/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/actions.md).

Convai 会用 `serverResponse` 事件。 `event_type` 是你发送的消息类型， `status` 是 `成功`, `error`, `处理中`，或 `待处理`, `message` 是描述或 `null`，以及 `extras` 包含特定事件数据或 `null`.

```typescript
import type { ServerResponse } from '@convai/web-sdk/core';

client.on('serverResponse', (res: ServerResponse) => {
  if (res.status === 'error') {
    console.error(`${res.event_type} 失败：${res.message}`);
  }
  if (res.event_type === 'context-update' && res.extras) {
    const { token_count, max_tokens, remaining_tokens } = res.extras;
    console.log(`上下文令牌：${token_count}/${max_tokens}，剩余 ${remaining_tokens} 个`);
  }
});
```

### 处理静默轮次和空闲会话

`llmNoResponse` 当角色故意不回答某一轮时触发。触发时清除任何思考指示。

`idleWarning` 在 Convai 关闭不活跃会话之前触发。其负载为 `{ remainingSeconds }`，一个数字或 `null`。调用 `resetIdleTimer()` 依靠终端用户活动来保持会话开启。

```typescript
client.on('llmNoResponse', () => hideThinkingIndicator());

client.on('idleWarning', ({ remainingSeconds }: { remainingSeconds: number | null }) => {
  if (remainingSeconds !== null) {
    showBanner(`会话将在 ${remainingSeconds} 秒后关闭`);
  }
});

document.addEventListener('pointerdown', () => client.resetIdleTimer());
```

### 保护媒体控制

麦克风、摄像头和屏幕共享方法是异步的，当浏览器拒绝权限或设备不可用时会拒绝。捕获拒绝并显示一条消息：

```typescript
try {
  await client.audioControls.toggleAudio();
} catch (err) {
  showError('麦克风不可用。请检查浏览器权限。');
}
```

### 后续步骤

{% content-ref url="/pages/8c6e3631e7e7da5b3c9acf5a0019d7737dc79e19" %}
[Web SDK 事件参考](/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/event-reference.md)
{% endcontent-ref %}

{% content-ref url="/pages/f1fc7386520b3ebeeafa719a8d9efb554e1d7024" %}
[ConvaiClient 参考](/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/vanilla-typescript/convaiclient-core-api.md)
{% endcontent-ref %}

{% content-ref url="/pages/534e8cdc1d57114dcd7601d4455823f77a9ad2ef" %}
[使用认证令牌连接](/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/auth-tokens.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/error-handling.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
