> 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/raw-messages.md).

# 读取原始传输消息

使用带类型的 rawMessage 事件观察会话通过任意传输接收的每条消息，包括 MCP 工具生命周期帧和未知类型。

观察 Convai 会话接收到的消息，这些消息是从线上原始数据解码而来、且在 SDK 解释之前。使用 `rawMessage` 事件来记录流量、读取 MCP 工具生命周期帧，或处理 SDK 不会转换为自身事件的消息类型。它在 LiveKit、WebSocket 和 SSE 上的工作方式相同。

### 先决条件

* 已安装 Convai Web SDK，并且有一个可连接的客户端。参见 [Web SDK 快速入门](/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/quickstart.md).

### 订阅原始消息

调用 `client.on('rawMessage', listener)`. 它会返回一个取消订阅函数。React Hook 通过其 `在` 并且 `移除监听` 方法。

{% tabs %}
{% tab title="React" %}
{% code title="src/Chat.tsx" %}

```tsx
import { useEffect } from 'react';
import { useConvaiClient } from '@convai/web-sdk/react';
import { ConvaiWidget } from '@convai/web-widget/react';

const config = {
  apiKey: 'YOUR_API_KEY',
  characterId: 'YOUR_CHARACTER_ID',
  startWithAudioOn: false,
};

export default function Chat() {
  const client = useConvaiClient(config);

  useEffect(() => client.on('rawMessage', (event) => {
    console.log(event.transport, event.message);
    if (event.transport === 'sse') console.log(event.request.logicalTurnIds);
  }), [client.on]);

  return <ConvaiWidget convaiClient={client} />;
}
```

{% endcode %}

像上面一样，从 effect 中返回取消订阅函数，这样卸载和重新渲染就会移除监听器。
{% endtab %}

{% tab title="TypeScript" %}
{% code title="src/main.ts" %}

```typescript
import { ConvaiClient } from '@convai/web-sdk/core';
import { createConvaiWidget } from '@convai/web-widget/vanilla';

const client = new ConvaiClient({
  apiKey: 'YOUR_API_KEY',
  characterId: 'YOUR_CHARACTER_ID',
  startWithAudioOn: false,
});

const unsubscribe = client.on('rawMessage', (event) => {
  console.log(event.transport, event.message);
});

createConvaiWidget(document.body, { convaiClient: client });

// 当宿主视图被释放时：
// unsubscribe();
```

{% endcode %}

你也可以使用 `client.off('rawMessage', listener)`，传入相同的函数引用来移除监听器。
{% endtab %}
{% endtabs %}

订阅不会连接或发送任何内容。小部件，或者你自己对 `connect()`的调用，负责控制连接。监听器只覆盖入站消息；它不会发布事件，也不会观察出站流量。

### 读取事件负载

每个事件都携带 `message`, `byteLength` 并且 `transport`。剩余字段取决于传输方式，因此请先检查 `event.transport` ，TypeScript 会为你缩小类型范围。

| Transport     | `message`                                                   | 额外字段                                                |
| ------------- | ----------------------------------------------------------- | --------------------------------------------------- |
| `'livekit'`   | 解码后的 JSON 信封，包括嵌套的 `server-message` 数据。非 JSON 数据包不会被发出。     | `participant`：房间提供的发送者。它可能不存在。                      |
| `'websocket'` | 解码后的 JSON 信封，包括嵌套的 `server-message` 数据。非 JSON 数据包不会被发出。     | 无                                                   |
| `'sse'`       | `{ type: eventName, data: decodedData }`。非 JSON 的数据会保持为字符串。 | `request`：原始请求的 `logicalTurnIds` 并且 `messageTypes`. |

* `message` 是 `未知`。在使用之前验证其形状。
* `message` 以及 SSE 请求元数据与 SDK 自身的处理是分离的。请将它们视为只读。
* `byteLength` 是传入负载的大小。对于 SSE，它统计合并后的 data 字段，不包括事件分帧。
* 在 SSE 上，会包含 session、done、error 和 unknown 帧。SSE 注释和没有 data 的帧不会包含在内。
* SSE 在 SDK 将其批处理为聊天消息之前，会先报告单独的文本帧。

#### 检查消息来自哪里

在 LiveKit 上，使用 `event.participant` 与当前房间进行验证。它是原始的 participant 对象，不是副本。

在 SSE 上，使用 `event.request.logicalTurnIds` 将某个帧与触发它的用户轮次匹配起来。仅控制类请求可能会有一个空列表。当请求被中断时，其剩余帧不会被发出。

### 读取 MCP 工具生命周期帧

有关工具调用的消息，包括 MCP 工具，会像其他帧一样到达 `rawMessage` ，并伴随 SDK 不会解释的事件类型。读取 `type` 字段，位于 `message` 以挑出你需要的内容，例如 `llm-function-call-in-progress` 并且 `llm-function-call-stopped`，或一个 `server-message` 其 `data.type` 是 `mcp-tool-heartbeat`.

{% code title="src/tools.ts" %}

```typescript
client.on('rawMessage', ({ message }) => {
  const frame = message as { type?: string; data?: { type?: string; tool_call_id?: string } };
  if (frame.type === 'llm-function-call-in-progress') showToolSpinner(frame.data?.tool_call_id);
  if (frame.type === 'llm-function-call-stopped') hideToolSpinner(frame.data?.tool_call_id);
});
```

{% endcode %}

### 何时使用 rawMessage

使用 SDK 的常规事件，这些事件列在 [Web SDK 事件参考](/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/event-reference.md)中，来处理它们已经涵盖的任何内容。对 `rawMessage` 用于它们未涵盖的内容：调试和记录流量、工具和 MCP 生命周期帧、SDK 不会解释的服务器消息类型，以及 SSE 上按请求关联。

{% hint style="warning" %}
原始消息只是一个观察结果，并不能证明某事已成功。在 SSE 上，服务器可能会发送一个冲突的终结帧，而 SDK 随后会拒绝它。使用 `botTurnCompleted` 事件来知道某轮对话已完成。
{% endhint %}

你的监听器中抛出的异常不会阻止 SDK 正常处理该消息。

### API 参考

| 属性 / 方法                              | 类型                                                                       | 描述                                               |
| ------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------ |
| `client.on('rawMessage', listener)`  | `(listener: (event: RawMessageEvent) => void) => () => void`             | 订阅传入消息。返回一个取消订阅函数。                               |
| `client.off('rawMessage', listener)` | `void`                                                                   | 通过引用移除相同的监听器。                                    |
| `RawMessageEvent`                    | 联合类型                                                                     | 事件负载。通过 `transport`.                             |
| `event.message`                      | `未知`                                                                     | SDK 解释之前解码后的线上消息。使用前验证其形状。                       |
| `event.byteLength`                   | `数值`                                                                     | 传入负载大小。对于 SSE，是不含事件分帧的合并 data 字段。                |
| `event.transport`                    | `'livekit' \| 'websocket' \| 'sse'`                                      | 发送消息的传输方式。                                       |
| `event.participant`                  | `未知`                                                                     | 仅限 LiveKit。由房间提供的发送者；可能不存在。                      |
| `event.request`                      | `SSERequestContext`                                                      | 仅限 SSE。原始请求的 `logicalTurnIds` 并且 `messageTypes`. |
| `SSERequestContext`                  | `{ transport: 'sse'; logicalTurnIds: string[]; messageTypes: string[] }` | 生成该事件的 SSE 请求的标识。                                |


---

# 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/raw-messages.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.
