> 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/character-versioning.md).

# 连接到角色版本

连接到角色的草稿、最新发布版本或带标签的版本，并从客户端列出、比较、发布、提升和丢弃版本。

每个角色都有一个可编辑的草稿，一旦发布，就会拥有一组不可变的带标签版本，例如 `1.0` 并且 `1.1`。一个可移动的 `latest` 指针会指定运行时在客户端连接且未提供选择器时使用的发布版本。使用此页面测试草稿中的更改，在准备就绪时发布标签，并通过将 `latest` 指向较旧的标签来回滚。

{% hint style="warning" %}
角色版本控制处于测试阶段。显式选择器（`draft`, `latest` 或标签）可用于生产和预发环境。没有角色版本解析的部署，例如自托管运行时，会返回 `503 无法解析角色版本`。省略 `characterVersion` 则按以往方式工作。
{% endhint %}

### 先决条件

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

### 选择一个版本选择器

设置 `characterVersion` 在客户端配置中，以选择要运行的版本。

| `characterVersion`  | 运行于                                                   |
| ------------------- | ----------------------------------------------------- |
| *（省略）*              | 实际生效的最新发布版本。早于版本控制的角色保持原有行为。                          |
| `'draft'`           | 可编辑的草稿，用于测试未发布的更改。                                    |
| `'latest'`          | 明确指定的已推广最新发布版本。                                       |
| `'1.2'` / `'1.2.3'` | 一个不可变的带标签版本，格式为 `major.minor` 或 `major.minor.patch` 。 |

在传输层中，选择器会与角色 ID 组合为 `<uuid>-draft`, `<uuid>-latest` 或 `<uuid>-1.2`. `characterId` ；也可以直接携带该后缀一起传递；只有在两者一致时，才允许同时设置。连接之后， `client.characterId` 仍然返回裸 UUID， `client.characterVersion` 返回选择器，而 `client.characterReference` 返回组合后的形式。

### 连接到某个版本

`characterVersion` 是 `ConvaiConfig`的一部分，因此你可以把它传给 `useConvaiClient`, `new ConvaiClient()` 或 `connect()`.

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

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

export default function App() {
  const client = useConvaiClient({
    apiKey: 'YOUR_API_KEY',
    characterId: 'YOUR_CHARACTER_ID',
    characterVersion: 'draft', // 或 'latest'、'1.2'，或省略
  });

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

{% endcode %}

该钩子返回 `characterVersion`, `characterReference` 并且 `characterVersions` 以及其他客户端成员。
{% 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',
  characterVersion: 'latest',
});

await client.connect();

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

{% endcode %}
{% endtab %}
{% endtabs %}

更改版本意味着会开启新的会话。使用新的配置调用 `disconnect()`，然后 `connect()` 。 `reconnect()` 会复用已存储的配置，包括其 `characterVersion`.

未知标签会失败并返回 `404 未找到角色版本`.

### 管理版本

`client.characterVersions` 返回一个 `CharacterVersionManager` 用于已配置的角色。只要配置拥有一个 `apiKey` 和一个 `characterId`，它就可用，因此你可以先列出版本、选择一个，再连接到它。

默认情况下，管理器会调用 `https://api2.convai.com`。将 `characterApiUrl` 为 `https://api2-stg.convai.com` 设置为在预发环境中工作。

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

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

export default function App() {
  const client = useConvaiClient({
    apiKey: 'YOUR_API_KEY',
    characterId: 'YOUR_CHARACTER_ID',
    characterVersion: 'draft',
    characterApiUrl: 'https://api2-stg.convai.com', // 可选，默认为生产环境
  });

  const release = async () => {
    const versions = client.characterVersions;
    if (!versions) return; // 未配置 apiKey
    const { has_unpublished_changes, latest_version } = await versions.list();
    if (has_unpublished_changes) {
      await versions.create('1.1', { makeLatest: true });
    }
    console.log('latest was', latest_version);
  };

  return (
    <>
      <button onClick={release}>将草稿发布为 1.1</button>
      <ConvaiWidget convaiClient={client} />
    </>
  );
}
```

{% endcode %}
{% 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 config = {
  apiKey: 'YOUR_API_KEY',
  characterId: 'YOUR_CHARACTER_ID',
};

const client = new ConvaiClient(config);
const versions = client.characterVersions;

if (versions) {
  // 先查看草稿更改了什么，然后再发布。
  const diff = await versions.diff('latest', 'draft', { view: 'semantic' });
  diff.changes.forEach((c) => console.log(`${c.change_type} ${c.label}`));
  const released = await versions.create('2.0', { makeLatest: true });

  // 连接到刚刚发布的标签。
  await client.connect({ ...config, characterVersion: released.version });
}

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

{% endcode %}
{% endtab %}
{% endtabs %}

每个方法都会按到达时返回 API 的 JSON，字段名使用 snake\_case。SDK 会先在本地检查选择器或标签；如果格式不正确，则会在发送请求前抛出错误。

#### 列出版本

`list()` 返回已发布的标签，位于 `items`中，以及 `latest` 和草稿的状态。

```ts
const { items, latest_version, draft_revision_id, has_unpublished_changes } = await versions.list();
```

`has_unpublished_changes` 是 `true` 当草稿与其分支来源的发布版本不同时时为真。 `draft_revision_id` 是 `null` 用于早于版本控制、且尚未拥有草稿的角色。

#### 比较版本

`diff(from, to)` 比较两个选择器。两侧都可以是 `'draft'`, `'latest'` 或标签。

```ts
const raw = await versions.diff('1.0', '1.1');                                 // JSON 路径
const semantic = await versions.diff('latest', 'draft', { view: 'semantic' }); // 带标签的更改
```

默认的原始视图会列出已更改的 JSON 路径。语义视图会将它们分组为带标签的更改，每个更改都有一个 `change_id` 你可以将其传给 `revert()`.

#### 发布草稿

`create(version)` 将当前草稿发布为不可变标签。该标签必须是 `major.minor` 或 `major.minor.patch`. `latest` 除非你传入 `makeLatest: true`.

```ts
await versions.create('1.1');                       // 仅创建标签，latest 不变
await versions.create('1.2', { makeLatest: true }); // 创建标签并将 latest 指向它
```

#### 推广或回滚

`promote(version)` 将 `latest` 指向一个已发布的标签。推广较旧的标签就是回滚；较新的标签仍可通过其标签连接。

```ts
await versions.promote('1.1'); // latest → 1.1
await versions.promote('1.0'); // 回滚：latest → 1.0
```

#### 丢弃或回退草稿更改

`discardDraft()` 将草稿重置为其已发布的父版本。 `revert()` 撤销语义差异中的一个更改。两者都需要你上次看到的草稿修订版。

```ts
const { draft_revision_id } = await versions.list();
if (draft_revision_id) {
  await versions.discardDraft(draft_revision_id);
}
```

```ts
const diff = await versions.diff('latest', 'draft', { view: 'semantic' });
const change = diff.changes.find((c) => c.can_revert);
if (change && diff.draft_revision_id) {
  await versions.revert({
    from: 'latest',
    changeId: change.change_id,
    expectedDraftRevisionId: diff.draft_revision_id,
  });
}
```

{% hint style="info" %}
`discardDraft()` 并且 `revert()` 如果自你读取该修订版后草稿发生了移动，则会拒绝执行，因此不会静默丢弃并发编辑。失败后，请再次调用 `list()` 以获取当前的 `draft_revision_id`.
{% endhint %}

#### 分叉一个已发布版本

`fork(version)` 会用已发布标签的内容替换草稿，因此你可以基于较旧版本开始新工作。将你上次看到的草稿修订版作为第二个参数传入，如果在此期间有人更改了草稿，则拒绝分叉。

```ts
const { draft_revision_id } = await versions.list();
await versions.fork('1.0', draft_revision_id ?? undefined);
```

#### 将标签标记为弃用

`deprecate(version)` 将某个标签标记为已弃用，而 `deprecate(version, false)` 会清除该标记。已弃用的标签仍然可以连接。

#### 为旧版角色创建草稿

`bootstrap()` 为早于版本控制的角色创建草稿。它是幂等的： `已创建` 是 `false` 当该角色已经有草稿时。

```ts
await versions.bootstrap();
```

### 无需客户端使用管理器

`CharacterVersionManager` 可以独立构造，使用 API 密钥或 Convai 个人访问令牌均可。可从 `@convai/web-sdk/core` 或 `@convai/web-sdk/vanilla`.

{% code title="versions.ts" %}

```ts
import { CharacterVersionManager } from '@convai/web-sdk/core';

const versions = new CharacterVersionManager({
  apiKey: 'YOUR_API_KEY',
  characterId: 'YOUR_CHARACTER_ID',
});

const { has_unpublished_changes } = await versions.list();
```

{% endcode %}

### 处理错误

非 2xx 响应会拒绝并返回 `CharacterApiError`。其 `status` 是 HTTP 状态码，而 `详情` 是解析后的响应正文。

```ts
import { CharacterApiError } from '@convai/web-sdk/core';

try {
  await versions.promote('9.9');
} catch (err) {
  if (err instanceof CharacterApiError) {
    console.error(err.status, err.detail);
  }
}
```

| 错误                                             | 原因                                                        |
| ---------------------------------------------- | --------------------------------------------------------- |
| `503 无法解析角色版本`                                 | 运行时不会解析显式选择器，例如没有角色版本解析的自托管部署。将 `characterVersion` 在那里省略。 |
| `404 未找到角色版本`                                  | 该角色不存在此标签。                                                |
| `403` 来自 `resolve()`                           | `includeRuntimeSettings` 仅供受信任的服务保留。API 密钥不能请求它。          |
| `期望 "draft"、"latest" 或 major.minor[.patch] 标签` | 格式错误的选择器，在任何请求发出之前就被本地拒绝。                                 |

### API 参考

| 属性 / 方法                                               | 类型                                                                       | 描述                                                                                                   |
| ----------------------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| `ConvaiConfig.characterVersion`                       | `'draft' \| 'latest' \| string \| null`                                  | 要连接到的版本。 `major.minor[.patch]` 字符串会选择一个标签。若省略，则使用实际生效的 latest。                                       |
| `ConvaiConfig.characterId`                            | `string`                                                                 | 可以直接将选择器作为 `<uuid>-draft`携带。只有在两者一致时，才允许同时设置。                                                        |
| `ConvaiConfig.characterApiUrl`                        | `string`                                                                 | 的基础 URL。 `client.characterVersions`默认 `https://api2.convai.com`；预发环境是 `https://api2-stg.convai.com`. |
| `client.characterId`                                  | `string \| null`                                                         | 当前连接的裸角色 UUID。                                                                                       |
| `client.characterVersion`                             | `CharacterVersionSelector \| null`                                       | 生效中的选择器，或者 `null` 在未版本化时。                                                                            |
| `client.characterReference`                           | `string \| null`                                                         | 发送到运行时的组合形式 `<uuid>[-selector]` 。                                                                    |
| `client.characterVersions`                            | `CharacterVersionManager \| null`                                        | 已配置角色的版本管理器； `null` 没有 `apiKey`.                                                                     |
| `new CharacterVersionManager(options)`                | `{ characterId, apiKey?, personalAccessToken?, baseUrl?, workspaceId? }` | 独立构造。需要一种凭据。                                                                                         |
| `list()`                                              | `Promise<CharacterVersionList>`                                          | 已发布标签以及 `latest_*`, `draft_*` 并且 `has_unpublished_changes` 字段。                                       |
| `resolve(version?, { includeRuntimeSettings? })`      | `Promise<ResolvedCharacterVersion>`                                      | 运行时将为该引用运行的配置快照。                                                                                     |
| `diff(from, to, { view? })`                           | `Promise<CharacterVersionRawDiff \| CharacterVersionSemanticDiff>`       | 比较两个选择器。 `view: 'semantic'` 返回带标签、可回退的更改。                                                            |
| `create(version, { makeLatest? })`                    | `Promise<CharacterVersionInfo>`                                          | 将草稿发布为不可变标签。                                                                                         |
| `promote(version)`                                    | `Promise<CharacterVersionInfo>`                                          | 指向 `latest` 指向一个已发布的标签。                                                                              |
| `discardDraft(expectedDraftRevisionId)`               | `Promise<DiscardCharacterDraftResult>`                                   | 将草稿重置为其已发布的父版本。                                                                                      |
| `revert({ from, changeId, expectedDraftRevisionId })` | `Promise<RevertCharacterChangeResult>`                                   | 撤销草稿中的一个语义更改。                                                                                        |
| `fork(version, expectedDraftRevisionId?)`             | `Promise<ForkCharacterVersionResult>`                                    | 用已发布标签的内容替换草稿。                                                                                       |
| `deprecate(version, deprecated = true)`               | `Promise<CharacterVersionInfo>`                                          | 为标签添加或移除弃用标记。已弃用的标签仍然可以连接。                                                                           |
| `bootstrap()`                                         | `Promise<CharacterDraftBootstrapResult>`                                 | 为早于版本控制的角色创建草稿。                                                                                      |
| `CharacterApiError`                                   | `错误` 与 `status: number`, `detail: unknown`                               | 对于 Character API 的任何非 2xx 响应的拒绝。                                                                     |


---

# 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/character-versioning.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.
