---
title: "SDK 参考"
description: "完整的 @beeuni/sdk API — 客户端方法、模块、轮次和分发。"
---

此页面介绍完整的 `@beeuni/sdk` API 表面：每个客户端方法、无框架模块、回合辅助工具，以及软件包的发布方式。使用 [SDK](/docs/zh/sdk) 开始，并参阅 [Sessions](/docs/zh/sdk/sessions) 深入了解会话生命周期。

<a id="the-client"></a>

## 客户端

`createBeeuni(config)` 返回一个客户端。每个方法都是对平台 API 的类型化调用。`project(id)` 和 `session(pid, sid)` 句柄会绑定 ID，因此无需重复传入。

```ts
const beeuni = createBeeuni({ backendUrl, getToken });

beeuni.accounts; // account / team operations
beeuni.accountInvites; // invite accept/decline by token alone
beeuni.projects; // top-level project operations
beeuni.connectors; // Connector calls scoped by an agent-minted token
beeuni.connector(slug); // one connector in the token's scope → see p.connector(slug)
beeuni.project(id); // id-bound project handle
beeuni.session(pid, sid); // id-bound session handle → see Sessions
beeuni.github; // GitHub App install + repo linking (account-scoped)
beeuni.gitBackend; // the instance git backend ("Beeuni managed")
beeuni.billing; // credits, subscription, tier, transactions
beeuni.sandboxShares; // public share links for a sandbox port
beeuni.connectStatus; // easy-connect status
beeuni.marketplace; // public marketplace catalog
beeuni.validateToken; // pasted-API-key check
beeuni.config; // platform config in effect
beeuni.runtime(); // 运行环境 REST compatibility client
```

<a id="accounts--beeuniaccounts"></a>

### 账户 — `beeuni.accounts`

| 方法 | 说明 |
| --- | --- |
| `list()` · `get(accountId)` | 所属账户 · 单个账户 |
| `create({ name })` · `updateName(accountId, name)` | 创建 · 重命名账户 |
| `branding.get(accountId)` · `branding.update(accountId, { app_name })` · `branding.uploadAsset(accountId, kind, file)` · `branding.removeAsset(accountId, kind)` · `branding.reset(accountId)` | 组织品牌设置（Enterprise）：自定义徽标、图标、favicon 和产品名称；`kind` 为 `logo` · `icon` · `favicon`，或深色方案变体的 `logo_dark` · `icon_dark` · `favicon_dark` |
| `members(accountId)` · `invite(accountId, input)` | 列出成员 · 邀请成员 |
| `updateMemberRole(accountId, userId, role)` · `removeMember(accountId, userId)` | 分配账户角色 · 移除成员 |
| `invites(accountId)` | 待处理邀请 |
| `cancelInvite(accountId, inviteId)` · `resendInvite(accountId, inviteId)` | 取消 · 重新发送待处理邀请 |
| `leave(accountId)` | 离开账户 |

`accounts.tokens` 会签发账户范围的 API 密钥（`beeuni_pat_...`）。完整令牌模型请参阅 [SDK 身份验证](/docs/zh/accounts#tokens)。

| 方法 | 说明 |
| --- | --- |
| `tokens.list(accountId?, options?)` | 列出 API 密钥——整个账户的密钥，或使用 `{ mine: true }` 仅列出你自己的密钥 |
| `tokens.create(input)` | 签发一个密钥——`{ accountId?, name, expiresAt?, projectId? }` |
| `tokens.revoke(tokenId, accountId?)` | 撤销一个密钥 |

`accounts.audit` 是企业重建日志。它将经过身份验证的 API 请求与语义会话、连接器、审批和计算机事件结合起来。

| 方法 | 说明 |
| --- | --- |
| `audit.log(accountId, filters?)` | 按项目、会话、执行者、来源、结果、请求、关联、资源、操作或时间列出事件 |
| `audit.export(accountId, filters?)` | 将相同的筛选事件流导出为 CSV 或 JSONL |
| `audit.webhooks.list/create/update/remove(...)` | 管理用于集中式事件流的已签名 SIEM webhook |

每个事件都包含 `project_id`、`session_id`、`actor_type`、`source`、`outcome`、`request_id`、`trace_id` 和 `correlation_id`（如果操作提供了这些信息）。API 不会在集中式事件中存储请求正文、提示词、机密、凭据或原始连接器参数。连接器事件可以包含有界的参数预览，其中会对凭据形式的字段和不透明数据进行编辑。

<a id="access-assignments"></a>

### 访问分配

Beeuni 只有一种授权记录：**分配**。它将一个主体（`user`、`group`、`service_account` 或 `pending`）绑定到一个角色和一个范围（`account` 或一个 `project`），也可以进一步限定到一个对象（`agent`、`skill`、`secret`、`app` 或 `trigger`），并可选择携带一个 `expires_at`。群组访问、按资源访问和自定义角色绑定都属于分配。模型详见 [账户与访问](/docs/zh/accounts#one-access-model)。

规范的 REST 表面如下：

| 方法 + 路径 | 功能 |
| --- | --- |
| `GET /v1/accounts/{accountId}/iam/assignments` | 列出分配，可按主体、范围、对象或角色筛选 |
| `POST /v1/accounts/{accountId}/iam/assignments` | 创建一个分配 |
| `DELETE /v1/accounts/{accountId}/iam/assignments/{assignmentId}` | 撤销一个分配 |
| `GET /v1/accounts/{accountId}/iam/permissions` | 以数据形式提供权限目录 |
| `GET /v1/accounts/{accountId}/iam/roles` · `…/roles/{roleId}/permissions` | 角色 · 单个角色的权限 |

SDK 将它们公开为 `listAssignments`、`createAssignment`、`revokeAssignment` 和 `listPermissions`。目录行包含 `action`、`scope_type`、`resource_type`、`delegable`、`description`、`area`、`level` 和 `implies`——请读取这些字段，而不是硬编码操作字符串。

分配自定义角色需要账户的 `rbac` 权限；没有该权限时，路由会返回带有 `code: "entitlement_required"` 的 `402`。

<a id="account-invites--beeuniaccountinvites"></a>

### 账户邀请 — `beeuni.accountInvites`

仅凭邀请令牌即可访问——受邀者可能尚未成为成员。

| 方法 | 说明 |
| --- | --- |
| `listMine()` | 列出发送到你邮箱的所有待处理、未过期邀请，并包含工作区及其授予的任何项目 |
| `describe(inviteId)` · `accept(inviteId)` · `decline(inviteId)` | 预览 · 接受 · 拒绝邀请 |

<a id="projects--beeuniprojects"></a>

### 项目 — `beeuni.projects`

| 方法 | 说明 |
| --- | --- |
| `list()` · `listForAccount(accountId)` | 你的项目 · 账户中的项目 |
| `get(id)` · `detail(id)` | 摘要 · 完整详情 |
| `create(input)` · `createRepo(input)` | 从现有 `repo_url` · 新建空 GitHub 仓库 |
| `provision(input)` | 在新的 Beeuni 管理仓库上创建项目，并使用入门模板初始化——`{ name, account_id?, seed_starter?, starter_template?, marketplace_items?, source_item_id?, idempotency_key? }` |
| `update(id, input)` · `archive(id)` | 更新设置 · 归档 |
| `llmCatalog(id)` · `modelPicker(id)` | 完整 · 紧凑模型目录，用于选择器 |
| `sandboxTemplates(id)` · `sandboxHealth(id)` | 沙盒构建模板 · 构建健康状况 |
| `sessions(id)` · `createSession(id, input?)` | 列出可见会话 · 创建会话 |

`provision` 会创建新项目；它不会启动现有项目的沙盒。请改为启动会话——参阅 [Sessions](/docs/zh/sdk/sessions)。

可以重试时发送 `idempotency_key`——例如重新加载、打开第二个标签页或重试超时请求。`provision` 每次调用都会创建一个全新的托管仓库，因此没有密钥时，这些调用都会创建实际的重复项目。对同一次逻辑创建的每次尝试都重复使用同一个密钥，后续重复请求会返回第一次尝试创建的项目（201、相同的 `project_id`、`push_token: null`）。密钥标识的是尝试，而不是负载——使用同一个密钥提交不同的 `name` 时，会返回第一个项目并忽略新值，因此每次不同的创建都应签发新密钥。使用相同的**名称**且不带密钥创建第二个项目仍然有效。

如果重复请求到达时第一次调用仍在配置中，它会返回带有 `code: 'provision_in_flight'` 的 `409`，而不是 `project_id`；第一次调用仍可能回滚。请使用相同密钥重试。

`project(id).sessions.list({ scope: 'project' })` 是面向具有项目管理权限的调用者的生命周期清单。它会增加可访问但不可用、预热中和软删除的会话，并附带所有权和运行时状态元数据。两种列表范围都会省略调用者无法打开的所有会话。

`sessions.list` 和 `sessions.listPage` 也接受层级和归因筛选条件。不提供这些条件时，列表保持扁平且不变。

| 选项 | 说明 |
| --- | --- |
| `parent: 'root'` | 仅顶层会话。每行都包含 `child_count`。 |
| `parent: '<sessionId>'` | 该会话的子会话，按最新优先排序。 |
| `startedBy: 'me' \| 'others' \| 'automated'` | 按相对于调用者的 `initiator` 筛选。 |
| `q` | 在调用者可以打开的每个会话中，对标题、启动器标签、触发器 slug 和会话 ID 前缀执行不区分大小写的搜索。使用 `parent: 'root'` 时，通过子会话匹配的根会话具有 `search_match: 'child'`。 |

每个会话行都有 `parent_session_id` 和 `initiator`（`{ type: 'member' | 'trigger' | 'channel' | 'api' | 'system', id, label }`）。`initiator` 标识启动整个运行的人员；子会话会复制其父会话的值。`sessionParentId(session)` 会读取 `parent_session_id`，然后读取旧版 `metadata.spawned_by_session`。游标仅对签发它的筛选条件集有效。

### GitHub — `beeuni.github`

账户范围的 GitHub App 安装和仓库链接，而非项目范围的操作。

| 方法 | 说明 |
| --- | --- |
| `getInstallation(accountId)` · `listInstallations(accountId)` | 此账户的安装 · 用户可访问的安装 |
| `saveInstallation(input)` · `deleteInstallation(accountId, installationId?)` | 记录 · 解除安装关联 |
| `listLinkableInstallations(input)` | 已授权的 GitHub 用户可关联的安装；每个条目包含 `linked_to_other_accounts`，表示持有该安装的其他 Beeuni 账户数量，绝不包含其名称 |
| `listRepositories(accountId, installationId?)` · `listRepositoryBranches(...)` | 安装可访问的仓库 · 分支和 GitHub 默认分支 |
| `linkRepository(input)` | 使用已授权的 GitHub 安装将仓库导入为项目。 |
| `replaceProjectRepository({ project_id, repo_url, expected_repo_url, github_token })` | 替换现有项目的 GitHub 仓库。使用具有 Contents 读写权限的 `github_token`，或使用 `installation_id` 加上临时的 `github_user_token` 来验证组织管理员权限，并通过 GitHub App 仅授权此仓库。`copy_shared_secrets_from_project_id` 加上 `copy_shared_secret_identifiers` 会在同一事务中复制选定的共享运行时密钥。过期的 `expected_repo_url` 返回 409。旧仓库仍保留在 GitHub 上。新会话使用替换后的仓库；现有会话保留其检出内容。 |

此列表仅包含真实的账户连接。“Beeuni managed”不在其中——它是下文的实例后端。

<a id="instance-git-backend--beeunigitbackend"></a>

### 实例 git 后端 — `beeuni.gitBackend`

整个部署共用一个所有者及其凭据，用于 Beeuni 托管的仓库。它绝不是账户连接，也绝不会出现在 `beeuni.github.listInstallations` 中。

| 方法 | 说明 |
| --- | --- |
| `get()` | `{ configured, kind: 'app' \| 'pat' \| null, owner }` — 任何已认证用户均可读取；它仅公开每个托管的 `repo_url` 已包含的所有者登录名 |


<a id="billing--beeunibilling"></a>

### 计费 — `beeuni.billing`

读取额度、订阅、套餐等级和交易历史。通过 Beeuni 结账和账单管理处理付款及额度购买。

| 方法 | 说明 |
| --- | --- |
| `accountState(accountId?)` · `accountStateMinimal(accountId?)` | 完整 · 最简计费状态 |
| `transactions(params?)` · `transactionsSummary(params?)` | 历史记录 · 汇总总额 |
| `creditBreakdown(accountId?)` · `usageHistory(params?)` | 按来源划分的额度余额 · 随时间变化的用量 |
| `sessionCosts.list(options?)` · `sessionCosts.get(sessionId, options?)` | 分页会话费用记录 · 单个会话的详细账目 |
| `tierConfigurations()` | 可用套餐等级 |
| `checkout.createSession(input)` · `checkout.confirmSession(sessionId, accountId?)` | **已弃用。** 以 `ENDPOINT_RETIRED` 拒绝。将在下一个主要版本中移除。 |
| `subscription.createPortalSession(...)` · `subscription.cancel(...)` · `subscription.reactivate(...)` | 打开客户门户 · 取消 · 重新启用 |
| `subscription.cancelScheduledChange(...)` · `prorationPreview(...)` | 取消 · 预览套餐变更 |
| `subscription.scheduleDowngrade(...)` | **已弃用。** 以 `ENDPOINT_RETIRED` 拒绝。请在客户门户中更改套餐。将在下一个主要版本中移除。 |
| `credits.purchase(input)` · `credits.autoTopupSettings(...)` · `credits.configureAutoTopup(...)` | 一次性购买 · 读取 · 配置自动充值 |

`sessionCosts.list()` 接受 `accountId`、`projectId`、`limit` 和 `offset`。
每行合并已结算的 LLM 费用和已计费的沙盒计算费用。
`sessionCosts.get()` 额外提供模型用量及区分 LLM/计算费用的账目。
列表包含无关联会话费用的对账总额。

<a id="sandbox-shares--beeunisandboxshares"></a>

### 沙盒共享 — `beeuni.sandboxShares`

针对一个对外开放的沙盒端口的公开共享链接。作用范围是沙盒，而非项目。

| 方法 | 说明 |
| --- | --- |
| `list(sandboxId)` | 有效共享链接 |
| `create(input)` | 创建一个 — `{ sandboxId, port, ttl?, label? }` |
| `revoke(sandboxId, token)` | 撤销一个 |

<a id="marketplace-catalog--beeunimarketplace"></a>

### 市场目录 — `beeuni.marketplace`

公开目录浏览，只读——不同于将条目安装到项目上的 `project(id).marketplace`。

| 方法 | 说明 |
| --- | --- |
| `items(options?)` · `item(id)` · `itemFile(id, path)` | 浏览 · 单个条目 · 条目中的文件 |
| `marketplaces()` · `featured()` | 所有 · 精选市场 |
| `sources.list()` · `sources.add(input)` · `sources.remove(id)` | 列出 · 添加 · 移除来源 |

<a id="notifications--beeuninotifications"></a>

### 通知 — `beeuni.notifications`

调用者在所有账户和项目中的通知。每位已登录用户都有一个收件箱、一条偏好设置记录，以及其浏览器的 Web Push 订阅。每个方法都需要个人凭据：服务账户会收到 `403`。

:::info[通知是一项功能标志]
Beeuni 仅为启用了 `notification_center` 标志的项目写入收件箱记录、发送 Web Push 和电子邮件。该标志默认关闭。`list()` 会排除标志已关闭的项目中的记录。标志关闭时，`session(pid, sid).watch()` 和 `.setWatch()` 会以 `{ error, code: "feature_disabled", feature: "notification_center" }` 回应 `403`；使用 `isFeatureDisabledError(error)` 据此进行分支处理。
偏好设置、Web Push 订阅和设备令牌按个人保存，无论标志是否开启都可用。标志关闭时，当轮次结束、失败、提出问题或请求权限时，会话创建者的手机仍会收到推送。参见
[功能标志](/docs/zh/feature-flags) 和 [通知](/docs/zh/work/notifications)。
:::

```ts
const page = await beeuni.notifications.list({ limit: 20 });
await beeuni.notifications.markRead({ ids: [page.notifications[0].id] });
await beeuni.notifications.updatePreferences({ kinds: { turn_done: { push: false } } });
```

| 方法 | 说明 |
| --- | --- |
| `list({ limit?, before? }, { signal? })` | 最新一页：`{ notifications, unread_count, next_before }`。`limit` 为 1 到 50（默认 20）。将 `next_before` 作为 `before` 传入以获取下一页。调用者不再可见的记录会被排除。失败时不会调用宿主错误处理程序。 |
| `markRead({ ids })` · `markRead({ all: true })` · `markRead({ sessionId })` | 将 1 到 100 条记录、所有记录或某个会话的所有记录标为已读。仅更改调用者的记录。返回 `{ updated, unread_count }`。 |
| `preferences()` · `updatePreferences({ kinds })` | 读取 · 更改每种类型的推送和电子邮件选项。返回 `{ kinds, email_available }`。补丁仅更改其中指定的类型和渠道。 |
| `webPushPublicKey()` | `{ public_key }`：用于 `pushManager.subscribe` 的 `applicationServerKey` |
| `registerWebPushSubscription({ endpoint, keys })` | 向此浏览器发送推送。需要浏览器登录：个人令牌会收到 `403`。未知推送服务上的端点会收到 `400`。 |
| `unregisterWebPushSubscription(endpoint, { signal? })` | 停止向此浏览器推送。返回 `{ deleted }`。 |
| `registerDeviceToken(input)` · `unregisterDeviceToken(token, { signal? })` | 注册 · 移除原生应用的 Expo 推送令牌 |

类型（`InboxNotificationKind`，列于 `INBOX_NOTIFICATION_KINDS`）：

| 类型 | 发送时机 | 推送 | 电子邮件（默认） |
| --- | --- | --- | --- |
| `turn_done` | 你发起或关注的轮次结束 | 开启 | 关闭 |
| `turn_error` | 该轮次因错误而结束 | 开启 | 开启，汇总发送 |
| `question` | 智能体提出问题 | 开启 | 开启，汇总发送 |
| `permission` | 智能体请求权限 | 开启 | 关闭 |
| `shared` | 有人与你共享了会话 | 开启 | 开启，汇总发送 |
| `automation_failed` | 你关注的自动化开始失败 | 开启 | 开启，立即发送 |
| `automation_recovered` | 该自动化恢复正常 | 开启 | 关闭 |

`DEFAULT_NOTIFICATION_PREFERENCES` 保存这些默认值。收件箱记录始终会写入；偏好设置仅决定推送和电子邮件。汇总邮件列出收到 15 分钟后仍未读的记录，每小时最多发送一封。当 `email_available` 为 `false` 时，隐藏电子邮件列。

每条记录的 `url` 会打开其主题。其 `?notification=<id>` 查询参数让页面将该记录标为已读。

会话句柄包含调用者对单个会话的关注状态：

| 方法 | 说明 |
| --- | --- |
| `session(pid, sid).watch()` | `{ watching }`：调用者是否会收到此会话的通知 |
| `session(pid, sid).setWatch(watching)` | `false` 为调用者静音此会话，`true` 重新关注它。向会话发送提示会关注该会话，但绝不会取消静音。 |
| `session(pid, sid).presence({ tab_id, active, alerts? }, { keepalive? })` | 此标签页的在线状态租约。`alerts: true` 表示标签页会自行显示通知：启用 `notification_center` 时，服务器仅在显示提醒的标签页正被使用时跳过手机和 Web Push 推送。关闭时，`alerts` 被忽略，任何在线标签页都会阻止手机推送。`keepalive` 允许请求在页面关闭后继续。默认值：报告离线时开启，报告在线时关闭。`useSession` 仅在带有 `presencePageExit` 时发送 `keepalive`。 |

<a id="the-project-handle--beeuniprojectid"></a>

### 项目句柄 — `beeuni.project(id)`

绑定项目 ID；所有子资源都依附于它。

```ts
const p = beeuni.project(projectId);
await p.detail();
await p.update({ name });
await p.llmCatalog();
```

| 方法 | 用途 |
| --- | --- |
| `get` · `detail` · `update` · `archive` | 读取 · 完整详情 · 更新 · 归档 |
| `llmCatalog` · `modelPicker` · `sandboxHealth` | 读取模型和沙箱构建 |
| `onboardingComplete` | 将项目入门引导标记为已完成 |
| `validateManifest(raw)` | 在服务端验证 `beeuni.yaml`（或旧版 `beeuni.toml`）清单 |
| `gitToken()` | 生成新的限定作用域的 git 推送令牌（自带仓库使用 `409`） |
| `setAgentScope(agentName, scope)` | 在清单中设置智能体允许使用的密钥和连接器——这是第二重绑定，不是角色 |

<a id="ptokens--project-scoped-api-keys"></a>

#### `p.tokens` — 项目作用域的 API 密钥

创建会话时自动生成，作为 `BEEUNI_TOKEN`；也可手动生成。

| 方法 | 用途 |
| --- | --- |
| `list()` | 项目 API 密钥 |
| `create(input?)` | 生成新密钥 |
| `revoke(tokenId)` | 撤销密钥 |

<a id="psetuplinks--agent-minted-setup-links"></a>

#### `p.setupLinks` — 智能体生成的设置链接

用户打开此链接即可输入密钥或连接应用，无需拥有完整的
项目访问权限。

| 方法 | 用途 |
| --- | --- |
| `requestSecret(input)` · `requestConnector(input)` | 用于收集密钥的链接 · 连接应用 |

<a id="papps--apps-releases-machines-volumes"></a>

#### `p.apps` — Apps（发布、机器、卷）

由项目功能 `apps` 控制。指南：[SDK → Apps](/docs/zh/sdk/apps)。

| 方法 | 用途 |
| --- | --- |
| `list()` · `get(appId)` · `create(input)` · `update(appId, input)` · `remove(appId)` | 资源清单和生命周期；`create` 接受 `uses`，`update` 还接受 `labels` |
| `deployments.*` · `artifacts.*` · `access.*` · `rollback` · `start` · `stop` | 部署、构件、访问策略、流量和运行时 |
| `machines.list/start/stop/restart/destroy` · `scale(appId, count)` · `exec(appId, input)` | 每个进程组的机器；`exec` 需要 `project.app.admin` |
| `volumes.list/create/update/destroy/snapshots/snapshot/restore/fork` | 持久卷、每日快照、恢复、分叉 |
| `token(appId)` | 有效期为 15 分钟的登录令牌；不带 `member_tokens` 的 `409 app_capability_unsupported` |

<a id="psecrets--project-secrets"></a>

#### `p.secrets` — 项目密钥

| 方法 | 用途 |
| --- | --- |
| `list()` · `upsert(input)` | 列出元数据 · 创建或更新只写值和交付策略 |
| `setStrategy(identifier, strategy, options?)` | 更改暴露方式及其主机列表 |
| `broker(identifier, request)` | 执行经会话授权、受策略约束的 HTTPS 请求 |
| `remove(identifier)` | 删除密钥 |
| `setPersonal(name, value)` · `removePersonal(name)` | 设置 · 移除用户级覆盖值 |
| `setGitCredential(input)` | 设置 git 身份验证凭据 |

使用消费方 `sandbox` 的 `runtime` 属于**环境**暴露——这是默认方式，
也是唯一会将明文值放入会话的策略。使用
消费方 `network` 的 `egress` 属于**出口强制执行**暴露：会话持有句柄，
Beeuni 在沙箱外替换为真实值，仅针对策略列出的确切 HTTPS
主机。出口强制执行暴露是实验性功能；它需要
`secrets_egress` 功能开关（Settings → Feature flags）。关闭此开关时，
带有出口策略的 `setStrategy(identifier, 'egress', …)` 和 `upsert(...)`
返回 `403` `feature_disabled`。所有 `broker` 消费方都完全不会出现在会话中。
参见
[密钥](/docs/zh/project/secrets)。`broker(...)` 方法需要
会话作用域的令牌和有效的会话句柄。

<a id="paccess--project-assignments-invites-requests"></a>

#### `p.access` — 项目分配、邀请、请求

这里的每个方法都读取或写入此项目作用域内的分配。项目
角色为 `manager` 和 `member`。

| 方法 | 用途 |
| --- | --- |
| `list()` · `invite(email, role)` | 拥有访问权限的主体 · 邀请用户 |
| `update(userId, role)` · `revoke(userId)` | 分配项目角色 · 撤销分配 |
| `pendingInvites()` · `requests()` | 未完成的邀请 · 待处理的访问请求 |
| `resendInvite(inviteId)` · `revokeInvite(inviteId)` · `approveRequest(id)` · `rejectRequest(id)` | 重新发送/撤销邀请 · 批准/拒绝请求 |
| `groupGrants()` · `attachGroupGrant(...)` · `updateGroupGrant(...)` · `detachGroupGrant(...)` | 相同的分配，但主体为 `group` |

`p.access.resourceGrants` 是**对象分配**视图：它将
主体的范围限定为项目中的一个对象，而不是整个项目。Beeuni
目前对智能体和技能强制执行对象分配。智能体默认不开放
——只有分配中指定了成员本人或其所属的某个
组，该成员才能访问它。对象分配本身不赋予任何权限，对
项目管理员和成员施加同样的限制。

| 方法 | 用途 |
| --- | --- |
| `resourceGrants.list()` | 此项目中的对象分配 |
| `resourceGrants.create(input)` | 将一个对象分配给用户或组 |
| `resourceGrants.remove(grantId)` | 撤销一项对象分配 |

<a id="pconnectorslug--one-connector"></a>

#### `p.connector(slug)` — 单个连接器

| 方法 | 用途 |
| --- | --- |
| `run(action, args, options?)` | 操作的输出；抛出 `ConnectorCallError` 或 `ConnectorApprovalPendingError`——参见[错误](/docs/zh/sdk/reference#pconnectorslug--one-connector) |
| `call(action, args, options?)` | 原始调用结果；`202` 以 `ok: false` 完成解析 |
| `describe()` · `describe(action)` | 带输入和输出模式的连接器 · 单个操作 |
| `accounts()` | 此调用者可用来运行它的账户，默认账户排在首位 |
| `paginate(action, args, { next, maxPages? })` | 遍历每页输出的异步迭代器；页数超过 `maxPages`（默认 100）时抛出带有 `nextArgs` 的 `ConnectorPageLimitError`——参见[分页](/docs/zh/sdk/reference#pconnectorslug--one-connector) |
| `uploadAttachment(content, input)` | 将 `connector` 设为此 slug 的 `p.connectors.uploadAttachment` |

`options`：`account`、`approvalContext`、`timeoutMs`（默认 90 000）、`signal`。使用来自 `beeuni connectors types` 的文件后，参数和输出都具有类型。

<a id="pconnectors--tool-connectors"></a>

#### `p.connectors` — 工具连接器

| 方法 | 用途 |
| --- | --- |
| `catalog()` · `tools()` | 可调用的 Connector 目录 · 扁平化的 `<connector>.<action>` 工具 |
| `search(query, options?)` · `describe(tool)` | 查找 · 检查单个可调用工具 |
| `call(tool, args?, options?)` | 通过服务端网关调用一个 `<connector>.<action>` 工具。`options.account` 指定以哪个账户运行——见下文。 |
| `accounts(slug)` | 此调用者可用来调用 `slug` 的账户，默认账户排在首位——即 `options.account` 的匹配对象 |
| `uploadAttachment(content, input)` | 为 `input.connector` 暂存字节；返回 `ref`（`{ $beeuni_attachment }`），供放入后续调用的参数中——参见[附加文件](/docs/zh/connect/connectors#attach-files) |
| `list()` · `config(connectorId)` | 已配置的连接器 · 单个连接器的配置 |
| `create(input)` | 添加连接器 |
| `auth.discover(input)` | 从 OpenAPI 规范、Postman 集合或端点预览身份验证 |
| `remove(connectorId)` · `sync()` | 删除连接器 · 重新同步连接器 |
| `setName(connectorId, name)` · `setSensitive(connectorId, sensitive)` | 重命名 · 标记为敏感（额外的审批控制） |
| `setAuthorizationStrategy(slug, strategy)` | @deprecated。仍会调用路由；该路由不执行任何操作，仅返回 `{ok:true, deprecated:true}`，不做任何更改。现在，每个连接上的 `owner_type` 就是完整的访问规则。 |
| `setCredentialMode(connectorId, mode)` · `setCredential(connectorId, input)` | 切换来源 · 设置凭据值 |
| `policies.get(connectorId)` · `policies.set(connectorId, policies)` | 读取 · 替换其工具策略 |
| `connections.list()` · `connections.reconcile(input)` | 列出 · 创建/更新已连接的账户 |
| `connections.updateCredential(connectionId, input)` | 轮换连接凭据 |
| `connections.revoke(connectionId)` · `connections.activate(connectionId)` | 禁用 · 恢复连接 |
| `connections.setDefault(connectionId)` | 将其设为所属所有者作用域的默认连接 |
| `connections.rename(connectionId, label)` | 仅更改标签；账户仍保持授权。每个连接都携带 `connected_as`，即其获得授权时所用的身份。 |

`p.connectors.discover` 用于浏览直接连接器目录。它是**实验性**功能，默认关闭——可在每个项目的 [Settings → Feature flags](/docs/zh/feature-flags) → "Connectors API Discover" 下启用。Easy Connect 仍是默认连接器市场。

| 方法 | 用途 |
| --- | --- |
| `discover.list(query?, cursor?)` · `discover.detail(id)` | 搜索 OpenAPI/MCP/GraphQL/CLI 条目 · 单个条目的详情 |

`p.connectors.pipedream` 是旧版托管 OAuth 兼容路径；`listApps`
仅返回 OAuth 应用。新的托管集成请使用 Easy Connect。API 密钥应用请直接连接。

| 方法 | 说明 |
| --- | --- |
| `pipedream.listApps(params?)` · `pipedream.connect(input)` · `pipedream.finalize(input)` | 浏览应用目录 · 开始连接流程 · 完成连接 |

连接器定义工具、提供商应用和策略。`connections`
存储其已连接的**账户**——一个连接器可以并列持有多个账户。
会话可以通过 `connector_bindings: { alias: { connection_id } }` 为每次调用固定一个账户，
也可以通过 `call(tool, args, { account })` 为单次调用指定一个账户。凭据始终加密存储，
并按请求解析。

`connections` 是唯一有效的授权门面。已废弃的
`authorizations` 和 `profiles` 名称不属于当前 SDK 接口。

<a id="accounts-and-the-call-time-gate"></a>

#### 账户与调用时检查

每个连接都有一个 `owner_type`：`project`（共享——连接器授予的任何人都可访问，
包括用户或服务账户）或 `member`（私有——只有其所有者可访问，且只能在私有会话中访问；服务账户永远无法访问）。共享账户可以进一步限制到群组和人员：
`connections.list()` 在 `shared_with` 中返回其授权范围，而
`connectionSharedWithEveryone(connection)` 会回答项目中的所有人是否都可以使用它。受限账户对其指定的人员遵循私有规则，而 `usable: false` 会标记一个账户，使调用者只能看到并管理它。使用 `createAssignment` / `revokeAssignment` 授予或撤销权限
（`object: { type: 'connection', id }`）——参见
[选择谁可以使用共享账户](/docs/zh/connect/connectors#choose-who-can-use-a-shared-account)。
`p.connectors.accounts(slug)` 列出调用者可以访问的所有账户，
默认账户排在首位：

```ts
const accounts = await beeuni.project(projectId).connectors.accounts('stripe-read');
// [{ connection_id, label, owner_type, is_default }, ...]

await beeuni.project(projectId).connectors.call(
  'stripe-read.get_balance',
  {},
  { account: 'My Stripe' }, // a connection id, a label, or the words `me` / `project`
);
```

未指定账户的调用会先解析为调用者自己的默认私有账户，然后解析为项目的默认共享账户。无法解析的指定账户会通过 `connector_not_connected` 被**拒绝**——绝不会静默替换，并且拒绝信息会列出 `requested_account` 和 `available_accounts`。每次成功调用都会返回其使用的账户：
`result.account = { connection_id, label, owner_type }`。

现在已不再有创建会话时的连接器检查。过去，当所需连接器没有可用连接时，会话会被 `409 CONNECTOR_CONNECTION_REQUIRED` 拒绝；该拒绝已取消（`require_connectors` 不再生效——参见
[`p.sessions`](#psessions--and-the-session-handle)）。检查已移至调用阶段：被拒绝的 `connector_not_connected` 会在可以为调用者生成授权链接时携带 `connect_url`，即托管授权链接——整个补救过程只需点击一次，并通过记录中的连接按钮提供。

<a id="ppolicies--project-policies"></a>

#### `p.policies` — 项目策略

| 方法 | 说明 |
| --- | --- |
| `list()` · `set(policies)` | 项目策略 · 替换整个策略集 |

<a id="ptriggers--cron-webhook-monitor-and-event-automations"></a>

#### `p.triggers` — cron、Webhook、监控和事件自动化

触发器会按计划、通过入站 Webhook、受监控的命令或连接器上的应用事件启动代理操作。参见
[触发器](/docs/zh/connect/triggers)，了解会话策略和负载模板化。

| 方法 | 说明 |
| --- | --- |
| `list()` | 所有触发器 |
| `eventTypes({ connector })` | 连接器可以触发的应用事件（`{ provider, app, event_types }`）；未知连接器返回 404；其提供商没有事件源时返回 409 `event_source_unavailable` |
| `create(input)` | 创建一个触发器 |
| `update(triggerId, input)` | 编辑触发器 |
| `remove(triggerId)` | 删除触发器 |
| `fire(triggerId)` | 立即运行 |
| `setActivation(paused)` | 暂停或恢复项目中的所有触发器 |

`create(input)` 接受 `{ name, type: 'cron' | 'webhook' | 'monitor' | 'event', prompt_template,
slug?, agent?, model?, enabled?, session_mode?, session_id?, cron?, run_at?,
timezone?, secret_env?, connector?, event?, event_source?, event_account?, event_config?, session_access? }`. `name` and `prompt_template`，其中 name 和 prompt_template 为必填项。
`cron`/`run_at` 互斥（`type: 'cron'`）；`secret_env`（Webhook HMAC 密钥）适用于 `type: 'webhook'`。

`event` 触发器需要 `connector`（连接器 slug）和 `event`（来自 `eventTypes` 的类型 ID）。`event_account` 为可选项：该连接器的一个共享账户的标签；`null` 或省略时使用连接器的默认共享账户。
`event_source` 为可选项：事件源适配器 ID，例如 `composio`。`null` 或省略时使用连接器的提供商。类型 ID 属于该适配器。`event_config` 的结构由该类型的 `config_schema` 决定。
`eventTypes` 和 `eventApps` 会将适配器报告为 `source`；`provider` 是已弃用的别名。
每个列出的触发器都包含 `event: { connector, account, connected_as, type, config, source, provider, app,
status: 'active' | 'needs_connection' | 'error' | 'pending', error,
last_event_at } | null`。提示模板会读取事件的 `{{ event.data.<field> }}`，以及 `event.id`、`event.type`、`event.app`、
`event.connector` 和 `event.occurred_at`。

`session_access` 控制谁可以打开触发器创建的会话。它是
`{ mode: 'private' | 'members' | 'project', memberIds: string[], groupIds:
string[] }` and defaults to `private`。此策略是账户本地的运行时状态，不会写入可移植的 `beeuni.yaml` 清单。仅更新
`session_access` 不会创建 Git 提交。固定的会话会保留自己的共享设置。项目管理员始终可以打开触发器创建的会话，包括使用 `private` 或选定成员访问权限的会话。

`session_access` 是基于每个资源的可见性设置，叠加在角色模型之上，而不是一种角色。它决定谁可以打开某个触发器的会话。它不会授予角色判定所拒绝的任何权限。参见
[账户与访问权限](/docs/zh/accounts#per-feature-access-settings)。

<a id="pmarketplace--pregistry--installed-items"></a>

#### `p.marketplace` / `p.registry` — 已安装项目

将目录项目的文件安装到项目的默认分支。
`registry.*` 是 `marketplace.*` 的完全相同别名。

| 方法 | 说明 |
| --- | --- |
| `marketplace.list()` · `marketplace.install(id)` | 已安装项目 · 安装目录项目 |
| `marketplace.updates()` · `marketplace.update(name)` · `marketplace.updateAll()` | 可用更新 · 更新一个 · 全部更新 |
| `marketplace.remove(name)` | 卸载项目 |

<a id="pfiles--repo-files-read"></a>

#### `p.files` — 仓库文件（读取）

只读访问项目的 Git 树。要在运行中的会话内读取和写入文件，请使用会话的文件操作——参见下方模块中的
[文件](#files)。

| 方法 | 说明 |
| --- | --- |
| `listDirectory({ ref?, path? })` | 一层文件夹：`{ entries: [{ path, type, size? }], truncated }`。用于构建目录树；每个文件夹最多完整返回 5,000 个条目。 |
| `list(options?)` · `read(path, ref?)` | 路径下的所有文件（递归），达到 1,000 个时截断（截断时响应会携带 `X-Beeuni-Truncated: 1`）· Git 引用处某个文件的内容 |
| `search(query, { content?, ref?, limit? })` | 在整个仓库中搜索文件名，或使用 `content: true` 搜索内容 |
| `archive(options?)` · `history(path)` | 下载 tarball · 某个文件的 Git 历史 |

<a id="pgit--history"></a>

#### `p.git` — 历史记录

| 方法 | 说明 |
| --- | --- |
| `commits()` | 提交日志 |
| `commit(sha)` · `commitDiff(sha)` | 某次提交 · 其差异 |
| `branches()` · `versionDiff(from, to)` | 分支 · 两个引用之间的差异 |

<a id="pchangerequests--lifecycle-and-merge"></a>

#### `p.changeRequests` — 生命周期与合并

变更请求（CR）用于将会话的工作合并到默认分支。

| 方法 | 说明 |
| --- | --- |
| `list()` · `get(crId)` | 未关闭的 CR · 某个 CR |
| `diff(crId)` · `mergePreview(crId)` | 其差异 · 预览合并结果 |
| `open(input)` · `merge(crId, input?)` | 打开 · 合并 CR |
| `close(crId, input?)` · `reopen(crId, input?)` | 不合并直接关闭 · 重新打开已关闭的 CR |
| `requestChanges(crId, input)` | 记录反馈，可选择将其传回发起会话 |

<a id="psessions--and-the-session-handle"></a>

#### `p.sessions` — 以及会话句柄

| 方法 | 用途 |
| --- | --- |
| `list()` | 项目的会话 |
| `create(input?)` | 创建会话 |
| `session(sid)` | 会话句柄（与 `beeuni.session(id, sid)` 相同） |

会话句柄是运行时的核心 — 参见
[会话](/docs/zh/sdk/sessions)。

`create(input)` 接受以连接器连接 slug 为键的 `connector_bindings`。
每个值指定一个 `connection_id`。它还接受 `secrets`，用于
缩小后端来源密钥的范围。`require_connectors` 已弃用且
不生效：仍接受该字段（不会因旧客户端发送该字段而拒绝请求），
但没有任何逻辑读取它 — 创建会话时，绝不会因某个连接器
没有可用账户而拒绝会话。参见[账户与调用时
门控](#accounts-and-the-call-time-gate)。

<a id="papprovals--the-connector-approval-inbox"></a>

#### `p.approvals` — 连接器审批收件箱

受连接器门控、等待决策的待处理操作 — 为
权限审批体验提供支持（`APPROVE` / `ASK` / `BLOCK`）。

| 方法 | 用途 |
| --- | --- |
| `list(options?)` · `sessionsNeedingInput(options?)` | 待处理审批 · 因等待决策而阻塞的会话 |
| `resolve(executionId, decision, scope?)` | 批准或拒绝一项 — `decision: 'approve' \| 'deny'`、`scope: 'once' \| 'session' \| 'session_all'` |

<a id="pgateway--llm-observability"></a>

#### `p.gateway` — LLM 可观测性

此项目模型流量的请求日志、成本/延迟汇总、预算和网关 API 密钥。

| 方法 | 用途 |
| --- | --- |
| `logs(opts?)` · `log(logId)` | 请求日志条目 · 单个日志条目 |
| `overview(days?)` · `series(days?)` · `breakdown(days?)` · `sessions(days?)` · `errors(days?)` | 时间窗口内的汇总、每会话成本和错误 |
| `budgets()` · `setBudget(input)` · `deleteBudget(budgetId)` | 读取 · 创建/编辑 · 移除预算 |
| `keys()` · `createKey(name)` · `revokeKey(keyId)` | 列出 · 生成 · 撤销网关 API 密钥 |
| `playground(prompt, models)` | 在最多 6 个模型上运行同一条提示词 |
| `routing.get()` · `routing.set(policy)` · `routing.reset()` | 读取 · 替换 · 继承路由策略 |
| `routing.preview(input)` | 解析路由，不调用模型 |

路由策略包含默认模型、视觉模型和有序的
回退链，每个模型最多尝试一次；`fallbackOn` 为
`transient` 或 `any-error`。

只有当前模型在返回任何输出之前失败，网关才会
转到下一个回退模型：

- `transient`：网络错误、超时、任何 `5xx`，或 `402`、`403`、`429`。
- `any-error`：所有失败，包括 `400`。

开始输出后的失败仍由该模型承担。使用你自己的
提供商密钥（BYOK）的请求绝不会回退到由 Beeuni 计费的模型。每个回退
模型都有各自的生成默认设置。

<a id="pchannels--slack--email--voice"></a>

#### `p.channels` — Slack / 电子邮件 / 语音

连接器接口，让智能体能够充当 Slack 应用、电子邮件地址，
或加入实时语音通话。

| 方法 | 用途 |
| --- | --- |
| `slack.installation()` · `slack.mode()` · `slack.manifest()` | 当前安装 · 模式 · 应用清单 |
| `slack.connect(input)` · `slack.disconnect()` | 连接 · 断开连接 |
| `slack.getFile(url)` · `slack.uploadFile(input)` | 通过服务器代理下载 · 上传文件 |
| `email.installation(connectorSlug?)` · `email.mode()` | 当前安装 · 模式 |
| `email.connect(input)` · `email.disconnect(...)` · `email.updatePolicy(input)` | 连接 · 断开连接 · 更新发送/回复策略 |
| `voice.setBotName(name)` | 在进行中的通话中重命名机器人 |

<a id="pmodeldefaults--default-model-preferences"></a>

#### `p.modelDefaults` — 默认模型偏好设置

账户、智能体和项目范围的模型默认设置，由网关解析。

| 方法 | 用途 |
| --- | --- |
| `get()` · `set(input)` · `clear(params)` | 读取 · 设置默认值 · 清除覆盖设置 |

<a id="psetdefaultagent--project-default-agent"></a>

#### `p.setDefaultAgent` — 项目默认智能体

`p.setDefaultAgent(agentName)` 检查智能体是否已声明并启用，
然后将其设为项目 `beeuni.yaml` 中的 `default_agent`。新会话
优先使用该智能体，除非用户选择其他智能体。

<a id="pupdatefeatureflag--feature-flags"></a>

#### `p.updateFeatureFlag` — 功能标志

`p.updateFeatureFlag(feature, enabled)` 为项目开启或关闭一个功能标志。
传入 `enabled: null` 可清除覆盖设置并回退到
平台默认值。它调用规范的 `PATCH /v1/projects/:id/features`。

`feature` 是 `FEATURE_FLAG_KEYS` 中的一个（从 `@beeuni/sdk` 导出，类型为
`FeatureFlagKey`）。调用者需要项目的 `project.settings.write`
权限；否则路由返回 `403`。

标志关闭时，所有受该标志门控的路由都以相同方式拒绝请求：HTTP `403`，
附带 `{ error, code: "feature_disabled", feature }`。使用 `isFeatureDisabledError(error)`
据此执行分支逻辑，使用 `featureDisabledKey(error)` 读取标志键 — 切勿
匹配消息文本。

`p.updateExperimentalFeature(feature, enabled)` 是**已弃用**的别名。它
仍调用已弃用的路由别名 `PATCH /v1/projects/:id/experimental`，
以便固定使用较旧已部署 API 的调用方继续正常运行。新代码中请使用
`p.updateFeatureFlag`。

<a id="psandbox--templates-and-snapshot-builds"></a>

#### `p.sandbox` — 模板与快照构建

项目句柄上 `sandboxHealth`/`sandboxTemplates` 之外的沙箱构建配置：
Dockerfile/镜像/预热池模板及其快照构建。

| 方法 | 用途 |
| --- | --- |
| `list()` · `snapshots()` | 此项目的沙箱 · 已构建的快照 |
| `rebuildSnapshot(slug?)` · `fixWithAgent()` | 重建快照 · 请求智能体修复失败的构建 |
| `createTemplate(input)` · `updateTemplate(...)` · `removeTemplate(...)` · `buildTemplate(...)` | 添加 · 编辑 · 删除 · 构建模板 |
| `setProvider(provider)` | 请求切换提供商。`null`（或平台默认提供商 / 已在使用的提供商）立即生效；切换到另一个已启用的提供商会启动持久化的准备→验证→激活转换 — 在构建和验证目标预热镜像期间，当前提供商继续提供服务，之后激活目标。返回值为带标签的联合类型：`kind:'project'`（立即生效）或 `kind:'preparation'`（轮询 `getProjectSandboxProviderTransition()`，直到 `activated`/`failed`） |

<a id="platform-admin-hooks-beeunisdkreact"></a>

### 平台管理员 hooks（`@beeuni/sdk/react`）

这些 hooks 为管理控制台提供服务。API 仅响应平台
管理员的这些请求。每次变更都会使 `['admin', 'accounts']` 查询失效。

| Hook | 功能 |
| --- | --- |
| `useAdminRole(userId, options?)` | 从 `GET /user-roles` → `{ isAdmin, role }` 读取调用者角色。`userId` 用作缓存键。缺少 `userId` 时禁用查询。在挂载和获得焦点时重新获取，因此角色被撤销后，最多一次导航便不再据此放行。 |
| `useAdminGrantCredits()` | 变更 `{ accountId, amount, description, isExpiring }`：增加钱包余额。`POST /admin/api/accounts/:id/credits`。涉及资金变动：请先在 UI 中确认。 |
| `useAdminDebitCredits()` | 变更 `{ accountId, amount, description }`：扣减钱包余额。`POST /admin/api/accounts/:id/credits/debit`。涉及资金变动：请先在 UI 中确认。 |
| `useAdminSetTier()` | 变更 `{ accountId, tier }`：设置套餐档位。 |
| `useAdminGrantTrial()` | 变更 `{ accountId, tierKey, seats, durationDays, creditGrant?, note? }`：授予或替换试用。在试用期结束前，账户按 `tierKey` 运行。`creditGrant` 还会增加钱包余额。 |
| `useAdminRevokeTrial()` | 变更 `{ accountId }`：立即结束当前试用。没有进行中的试用时返回 `400`。 |

<a id="escape-hatch"></a>

### 备用访问方式

`beeuni.runtime()` 返回当前活动沙箱的类型化 运行环境 REST 客户端。
在由 `createScopedBeeuni` 创建的客户端（`@beeuni/sdk/server`）上，它会**抛出异常**
— 在多租户服务器中，进程全局的“活动”运行时是另一个请求的沙箱，
会导致跨租户泄露。请改用会话范围的
`beeuni.session(pid, sid).runtime`（先调用 `ensureReady()`），它会
解析该会话自己的沙箱。

<a id="modules"></a>

## 模块

[客户端](#the-client) 门面和
React hooks 背后不依赖框架的模块。当你需要绕过门面执行单个操作、
使用纯辅助函数或仅适用于 Node 的隔离层时，可以使用这些模块。每个模块都标有
稳定性级别，便于你判断可以基于哪些模块进行构建。

| 层级 | 含义 |
| --- | --- |
| 标准入口 | 从根入口 `@beeuni/sdk` 导入。所有新代码都应使用此入口。 |
| 受支持 | 专用子路径（`@beeuni/sdk/react`、`@beeuni/sdk/server`）。提供一等支持，未弃用。 |
| 已弃用别名 | 仍可使用的旧子路径。它重新导出根入口已导出的代码。请改为从根入口导入。 |
| 内部 | 不受 semver 约束。不要在宿主代码中导入。 |

根入口是标准入口。下面所有不依赖框架的名称都可以直接从 `@beeuni/sdk` 导入：

```ts
import { files, getSessionHealth, getClient, authenticatedFetch, backendApi } from '@beeuni/sdk';
```

<a id="canonical-modules"></a>

### 标准模块

| 模块 | 功能 |
| --- | --- |
| 文件 | 工作区文件操作：列出、读取、搜索、写入 |
| 会话运行时 | 健康探测及预览/代理 URL 构建器 |
| 运行环境 客户端 | 类型化的 运行环境 REST 客户端及其完整类型接口 |
| 认证 | `authenticatedFetch` 和令牌访问器 |
| Projects REST | 门面封装的原始 REST 函数 |
| API 客户端 | `backendApi`，底层类型化 HTTP 客户端 |
| 轮次 | 将消息分组为轮次，以及费用和状态计算 — 参见[轮次](#turns) |
| 对话记录 | `formatTranscript`，客户端 Markdown 导出 |
| 输入编辑器列表 | 会话输入编辑器的智能体和模型列表：提供哪些选项、运行哪个选项 |

<a id="files"></a>

#### 文件

```ts
import { files } from '@beeuni/sdk';

const tree = await files.list('/workspace/src');
const { content } = await files.read('/workspace/README.md');
const hits = await files.findText('TODO');
await files.upload(file, '/workspace/uploads', file.name, {
  onProgress: ({ loadedBytes, totalBytes }) => {
    console.log(`${loadedBytes}/${totalBytes}`);
  },
});
```

超过 64 KiB 的上传内容会以经过验证的分块跨越沙箱边界。每个分块到达会话文件系统后，都会调用进度回调。

`uploadNativeFile({ uri, name, type? }, targetPath, baseUrl)` 通过一次请求从 React Native 上传设备文件。运行时从 `uri` 流式传输字节，因此这些字节不会进入 JS 堆。它使用与 `files.upload` 相同的认证、截止时间和 `401` 重放机制。不进行分块。

`fetchProjectArchive`、`fetchCostExportCsv` 和沙箱版本调用通过同一传输层发送。失败时会抛出带有 `status` 的 `ApiError`。

`files.write(path, blob)` 替换 `path` 处的文件；如果 `path` 是文件夹，则会失败。`files.create(path)` 创建空文件，且绝不覆盖：当 `path` 处已存在文件或文件夹时，它会抛出 `FileExistsError`（`status` `409`、`code` `'FILE_EXISTS'`）。

```ts
import { FileExistsError, files } from '@beeuni/sdk';

try {
  await files.create('/workspace/notes.md');
} catch (error) {
  if (error instanceof FileExistsError) console.log(`${error.path} already exists`);
  else throw error;
}
```

`files` 面向全局当前活动的沙箱。如果宿主同时运行多个会话，请改为在会话句柄上调用 `s.files`。它始终面向该会话自己的沙箱。参见[会话](/docs/zh/sdk/sessions)。

<a id="session-runtime-helpers"></a>

#### 会话运行时辅助函数

```ts
import { getSessionHealth, isRuntimeReady } from '@beeuni/sdk';

const result = await getSessionHealth();
if (result.ok && isRuntimeReady(result.health)) {
  // the sandbox daemon is ready
}
```

`getSessionHealth` 不会因非 2xx 状态而抛出异常。它返回 `{ status, ok, health, body }`，由你决定状态的含义。同一模块还导出 URL 辅助函数，用于将智能体的 `localhost` 输出改写为可访问的代理 URL：`detectLocalhostUrls`、`rewriteLocalhostUrl`、`proxyLocalhostUrl`、`parseLocalhostUrl` 和 `buildWebProxyUrl`。

<a id="opencode-client"></a>

#### 运行环境 客户端

```ts
import { getClient } from '@beeuni/sdk';

const client = getClient();
const { data } = await client.session.list({ limit: 100 });
```

`getClient()` 返回活动沙箱的类型化 运行环境 v2 兼容客户端，已注入认证信息。当宿主运行多个会话时，应优先使用限定于单个会话的同一客户端 `beeuni.session(pid, sid).runtime`，而不是全局的 `getClient()`。

<a id="auth-helpers"></a>

#### 认证辅助函数

```ts
import { authenticatedFetch, getAuthToken } from '@beeuni/sdk';

const res = await authenticatedFetch(`${runtimeUrl}/beeuni/health`);
const token = await getAuthToken();
```

令牌来自你传给 `createBeeuni` 的 `getToken` 函数。大多数应用代码不需要此模块 — 文件、会话和门面层已经为你处理认证。

<a id="api-client"></a>

#### API 客户端

```ts
import { backendApi } from '@beeuni/sdk';

const data = await backendApi.get('/some/endpoint');
await backendApi.post('/some/endpoint', { name: 'x' });
```

`backendApi` 是所有 REST 函数的基础类型化 HTTP 客户端。仅在端点尚无类型化封装时使用它。

<a id="composer-agent-and-model-lists"></a>

#### 输入编辑器的智能体和模型列表

使用与 Beeuni Web 应用相同的规则，构建会话输入编辑器的智能体选择器和模型选择器。每个函数都是纯函数：先通过客户端获取输入，再将其传入。

```ts
import {
  composerSelectableAgents,
  createModelVisibility,
  flattenModels,
  modelInDefaultView,
  pickerProviderList,
  projectConfigAgentsToRuntimeAgents,
  resolveComposerAgent,
  resolveComposerModel,
  resolveModelDefault,
} from '@beeuni/sdk';

// Agents: the roster, the picker list, and the agent the send carries.
const roster = projectConfigAgentsToRuntimeAgents(detail.config);
const pickerAgents = composerSelectableAgents(roster, { enableProjects: false });
const agent = resolveComposerAgent({
  agents: roster,
  boundAgent: session?.agent,
  defaultAgent: detail.config.default_agent,
  selectedAgent: pick,
});
if (agent.disabled) {
  // no accessible agent: refuse the send
}

// Models: gateway projects read /model-picker, native projects merge the
// catalog synthesis with the running sandbox's provider list.
const gatewayEnabled = detail.project.experimental?.llm_gateway === true;
const providers = pickerProviderList({
  gatewayEnabled,
  modelPicker,
  runtimeProviders,
  llmCatalogProviders,
  secretNames,
});
const models = flattenModels(providers, { providerMode: gatewayEnabled ? 'gateway' : 'native' });
const isVisible = createModelVisibility({ catalogModels: models });
const defaultView = models.filter(
  (m) => m.enabled !== false && modelInDefaultView(m, { search: '', isStoreVisible: isVisible, selected }),
);
const { model, explicit } = resolveComposerModel({
  models,
  picks: [sessionPick],
  serverDefault: resolveModelDefault(modelDefaults, agent.selected ?? undefined),
  providers,
});
```

当输入编辑器使用默认设置时，`resolveComposerModel` 返回 `explicit: undefined`。`model` 是用于显示和发送的键。每个扁平化模型的 `id` 是选择操作存储的引用；`modelRefToKey(ref, gatewayEnabled)` 将已存储的引用（会话固定项、频道绑定、触发器的 `model`）读回为键。

<a id="supported-subpaths"></a>

### 受支持的子路径

| 子路径 | 功能 |
| --- | --- |
| `@beeuni/sdk/react` | React hooks — 参见 [React hooks](/docs/zh/sdk/react) |
| `@beeuni/sdk/server` | 多租户后端的请求级配置 |
| `@beeuni/sdk/wire-message-id` | 仅包含 运行环境 传输消息 ID 时钟（单个模块，无导入）。根入口导出相同的名称 |

<a id="wire-message-ids"></a>

#### 传输消息 ID

提示的 `messageId` 是它在对话记录中的位置。运行环境 按 `msg_` + 48 位时钟（`Date.now() * 0x1000`，低 48 位）+ 14 个 base62 字符对消息排序，时钟每 2^36 毫秒（约 2.2 年）回绕一次。切勿手动编码。

```ts
import { mintWireMessageId } from '@beeuni/sdk';

// `after`: the ids already in this session's transcript.
const messageId = mintWireMessageId({ after: transcriptIds });
```

生成时先将时间设为两分钟前，再提升到 `after` 中最多超前一小时的最新 ID 之上一个时钟刻度。调用方若以时钟而非 ID 保存最新位置，则使用 `mintWireMessageIdAbove({ nowMs,
newestKnownTime })`, which returns `{ id, time }`. `newestWireIdClock(ids)` 查找该时钟。所有比较都在环上进行，因此回绕两侧的 ID 仍保持真实顺序。

48 位时钟约每 2.2 年回绕一次，因此切勿用 `>` 或 `<=` 比较两个时钟：刚回绕后生成的 ID 在数值上小于刚回绕前生成的 ID。请使用 `wireIdClockDelta(clock, reference)`，即环上的有符号距离（当 `clock` 在前时为正），并使用 `maxWireIdClock(clocks)` 找出多个值中最新的一个：

```ts
import { maxWireIdClock, wireIdClockDelta } from '@beeuni/sdk/wire-message-id';

const floor = maxWireIdClock([transcriptNewest, deliveredNewest]);
const stale = floor !== null && wireIdClockDelta(clientClock, floor) <= 0n;
```

<a id="server-side-isolation"></a>

#### 服务端隔离

`createBeeuni` 将其配置（包括令牌函数）存储在一个进程级变量中。这适用于浏览器标签页、CLI 或单租户服务器。但对于处理不同用户并发请求的 Node 服务器，这并不安全，因为最后一次 `createBeeuni` 调用会覆盖所有进行中请求的配置。`@beeuni/sdk/server` 通过按请求隔离解决此问题：

```ts
import { createScopedBeeuni } from '@beeuni/sdk/server';

export async function handler(req: Request) {
  const beeuni = createScopedBeeuni({ backendUrl, getToken: () => tokenFor(req) });
  return beeuni.projects.list();
}
```

`createScopedBeeuni` 和 `runWithBeeuni` 使用 Node 的 `AsyncLocalStorage` 按请求隔离配置。切勿在浏览器打包代码中导入 `@beeuni/sdk/server` — 它会静态引入 `node:async_hooks`。

限定作用域客户端的顶层 `runtime()` 会抛出异常（它会解析到另一个租户的沙箱）。在 `await s.ensureReady()` 之后，通过 `beeuni.session(pid, sid).runtime` 访问特定会话的运行时。

<a id="deprecated-aliases"></a>

### 已弃用的别名

约二十个旧子路径仍可使用：`/files`、`/turns`、`/session`、`/auth`、`/projects-client`、`/api-client`、`/config`、`/event-stream`、`/opencode-client`、`/platform-client` 等。每个子路径都重新导出根入口 `@beeuni/sdk` 已导出的代码。它们保持可用，以免现有的
import breaks, but new code should import from the root.

<a id="internal-modules"></a>

### 内部模块

`@beeuni/sdk/internal/*` 包含 apps/web 自身运行时在内部使用的 zustand 存储 — 会话同步状态、活动运行时跟踪和重连记录。此子路径明确不受 semver 约束。不要在宿主代码中导入它；[React hooks](/docs/zh/sdk/react) 已经提供了你需要的状态。

<a id="turns"></a>

## 轮次

将会话消息分组为轮次并对每个消息部分进行分类的普通函数。用它们构建自定义聊天渲染器，替代 `apps/web` 中的参考渲染器。不依赖 React 或 DOM — 每个导出项都是普通函数或类型，可从任何宿主安全调用。

```ts
import { classifyPart, classifyTurn, toolInfo, toolViewModel } from '@beeuni/sdk';
```

从根入口 `@beeuni/sdk` 导入所有函数。`@beeuni/sdk/turns` 子路径仍可使用，但它是已弃用的别名。新代码必须使用根入口 — 参见[分发](#distribution)。

<a id="classify-a-part"></a>

### 对部分进行分类

`classifyPart(part)` 将 Beeuni 对话记录格式（`beeuni.transcript.v1`、`BEEUNI_TRANSCRIPT_SCHEMA`）的 12 种片段类型之一规范化为
`ClassifiedPart`——一个以 `kind` 为键的联合类型。每个变体都已解析出渲染器所需的字段：工具状态、解析后的 JSON 输出、图像检测。

```ts
import { classifyPart, type ClassifiedPart } from '@beeuni/sdk';

for (const part of message.parts) {
  const classified: ClassifiedPart = classifyPart(part);
  switch (classified.kind) {
    case 'text':
      render(classified.text);
      break;
    case 'tool':
      render(classified.tool.title, classified.tool.status);
      break;
  }
}
```

| `kind` | 结构 |
| --- | --- |
| `text` | `{ id, text, synthetic }`——跳过 `synthetic` 片段；它们标记 shell 模式的合成提示 |
| `reasoning` | `{ id, text }` |
| `tool` | `{ id, tool: ToolView }` |
| `file` | `{ id, filename?, mime, url, isImage, isPdf }` |
| `subtask` | `{ id, description, agent, prompt, model? }` |
| `patch` | `{ id, hash, files, fileCount }` |
| `snapshot` | `{ id, snapshot }` |
| `agent` | `{ id, name }` |
| `retry` | `{ id, attempt, message, createdAt }` |
| `compaction` | `{ id, auto, overflow, tailStartId? }` |
| `step` | `{ id, phase: 'start' \| 'finish', snapshot?, reason?, cost?, tokens? }` |
| `unknown` | `{ raw }`——此 SDK 版本不认识的片段类型 |

无法识别的传输片段会在运行时降级为 `unknown`，而不是抛出异常。这让旧版客户端能够与新版服务器通信。

工具片段被分类为 `ToolView`：

```ts
interface ToolView {
  name: string;
  title: string;
  status: 'pending' | 'running' | 'done' | 'error';
  input?: Record<string, unknown>;
  output?: string;
  error?: string;
  outputParsed?: unknown; // JSON.parse(output) when it parses, capped at 256KB
  outputText?: string;    // the raw output text, always present
  kind?: ToolKind;        // toolKind(name): the action, whatever the harness calls the tool
  files?: ToolFile[];     // the files a file tool read, wrote or changed: path, patch, additions/deletions
  diff?: string;          // a unified diff of the change
  diagnostics?: Record<string, unknown[]>; // language-server diagnostics, keyed by file path
  answers?: string[][];   // the answers to a `question`, one list per question
  childSessionId?: string; // the child session a delegating call (`task`) runs
}
```

`toToolView(part)` 直接从工具片段构建它。读取这些字段，而不是执行框架自身的 `state.metadata`：这样，同一张卡片就能渲染 运行环境 的 `edit`（`filePath`、`filediff`）和 pi 的 `edit`（`path`、`edits`，以及统一的 `patch`）。

某些工具（`web_search`、`image_search`、连接器调用）即使 JSON 正文包含 `success: false`，也会报告 `state.status: 'completed'`。`classifyPart` 会检测这种情况，并同样将 `ToolView.status` 设为 `'error'`。

`classifyTurn(message)` 对一条助手消息的每个片段进行分类，并返回一个包含三个字段的 `ClassifiedTurn`：

- `parts`——分类后的各个片段
- `error`——来自 `message.info.error`（如果有）
- `isEmpty`——当该轮次没有错误，也没有包含可见内容的片段时为 true

<a id="tool-metadata"></a>

### 工具元数据

`toolKind(name)` 是统一的工具分类体系：它将任意执行框架的工具名称（pi 的 `write`、运行环境 的 `oc-apply_patch`、插件的 `pty_spawn`）映射为一个 `ToolKind`——`read`、`edit`、`write`、`bash`、`web_search`、`task`、`question`、……或 `other`。叙述类别、视图模型、`toolInfo` 类别和上下文工具组都由它派生。`inputPath(input)` 从 `filePath`、`file_path` 或 `path` 读取文件工具的路径。

```ts
toolKind('oc-apply_patch'); // 'apply_patch'
toolKind('pty_spawn');      // 'pty'
inputPath({ path: 'app.py' }); // 'app.py'
```

还有两个查询函数都用于描述工具。不要混淆它们。

- `toolInfo(name)`——不涉及图标，返回 `{ label, category }`。`classifyPart` 在内部使用它。`ToolCategory` 为 `'shell' | 'files' | 'search' |
  'edit' | 'web' | 'task' | 'other'`。
- `getToolInfo(name, input)`——包含图标信息，为参考工具卡片 UI 返回 `{ icon, title, subtitle }`。副标题来自工具的输入，例如文件路径或搜索查询。

```ts
toolInfo('bash'); // { label: 'Shell', category: 'shell' }
getToolInfo('write', { filePath: '/workspace/main.go' });
// { icon: 'file-pen', title: 'Write', subtitle: 'main.go /workspace' }
```

这两个函数都匹配工具名称前缀，因此 Beeuni 的插件工具系列（`agent_*`、`session_*`、`task_*`、`trigger_*`、`project_*`、`pty_*`）无需更新注册表即可解析。未知工具名称绝不会抛出异常——它会回退为 `humanizeToolName(name)`，类别为 `'other'`。

<a id="tool-view-models"></a>

### 工具视图模型

`toolViewModel(classifiedTool)` 将已分类的工具片段映射为专为某个工具系列构建的结构。UI 随后可以对其进行专门渲染，而不是作为通用 JSON 数据块显示。

```ts
const vm = toolViewModel(classifiedTool);
if (vm.kind === 'shell') {
  render(vm.command, vm.stdout, vm.exitCode);
}
```

| `kind` | 结构 | 工具 |
| --- | --- | --- |
| `web-search` | `{ query, results?, answer?, error? }` | `web_search`、`image_search` |
| `shell` | `{ command, stdout?, exitCode? }` | `bash` |
| `file-read` | `{ path, preview? }` | `read` |
| `file-write` | `{ path, preview? }` | `write` |
| `file-edit` | `{ path, diff?: DiffLine[] }` | `edit`、`morph_edit` |
| `search` | `{ pattern, matches?: SearchMatch[] }` | `grep`、`glob` |
| `task` | `{ description, agent? }` | `task` |
| `todo` | `{ items: TodoItem[] }` | `todowrite` |
| `question` | `{ questions: QuestionItem[], answers? }` | `question`、`ask` |
| `generic` | `{ label, inputPretty?, outputPretty? }` | 其余所有工具，始终可安全渲染 |

`DiffLine` 为 `{ type: 'added' | 'removed' | 'unchanged', text }`。字符串字段设有上限，避免单次大型工具输出破坏渲染：格式化 JSON 限为 4000 个字符，运行差异比较前限为 256KB。

<a id="group-messages-into-turns"></a>

### 将消息分组为轮次

一个轮次将一条用户消息与回答它的助手消息配对。它是聊天 UI 作为一次交互渲染的单位。

```ts
import { groupMessagesIntoTurns, collectTurnParts, type TurnLike } from '@beeuni/sdk';

const turns: TurnLike[] = groupMessagesIntoTurns(messages);
for (const turn of turns) {
  const parts = collectTurnParts(turn);
}
```

`groupMessagesIntoTurns` 将每条助手消息关联到其父级用户消息。缺少父级链接时，它会回退到消息顺序。它还会将孤立的助手消息——没有父级且出现在所有用户消息之前的消息——附加到第一个轮次，而不是最后一个。

相关辅助函数：

- `findLastTextPart(parts)`——该轮次的最终回复文本
- `turnHasSteps(parts)`——如果存在 `tool`、`compaction`、`snapshot` 或 `patch` 片段，则为 true
- `isShellMode(turn)` / `getShellModePart(turn)`——由一个合成提示驱动一次 `bash` 调用的轮次

<a id="type-guards"></a>

### 类型守卫

通过 `type` 收窄片段类型：

```ts
import { isTextPart, isToolPart, getPartText } from '@beeuni/sdk';

if (isTextPart(part)) {
  // part.type narrowed to 'text'
}
const text = getPartText(part); // works for 'text' and 'reasoning' parts
```

还提供：`isReasoningPart`、`isFilePart`、`isAgentPart`、`isCompactionPart`、`isSnapshotPart`、`isPatchPart`、`isStepPart`（`step-start` 和 `step-finish`）。

<a id="status-and-errors"></a>

### 状态和错误

```ts
import { getWorkingState, getTurnStatus, formatDuration } from '@beeuni/sdk';

const status = getTurnStatus(parts, childMessages); // "Running commands..."
formatDuration(4300); // "4s" — durations under 1s return ''
```

`getTurnStatus` 扫描一个轮次的片段，查找最后一条状态行。当最后一个片段是正在运行的 `task` 委派时，传入 `childMessages`，使状态显示子智能体的实际活动，而不是通用的“正在委派...”状态行。

```ts
import { getTurnError, getChildSessionError, unwrapError } from '@beeuni/sdk';

getTurnError(turn);                    // the first assistant error, unwrapped
getChildSessionError(childMessages);   // newest error in a sub-agent's messages
unwrapError(rawError);                 // normalizes double-JSON and mixed error shapes
```

<a id="cost-and-token-totals"></a>

### 费用和 token 总数

```ts
import { getTurnCost, getSessionCost, formatCost, formatTokens } from '@beeuni/sdk';

const info = getTurnCost(partsWithMessage, modelPricingLookup);
const sessionCost = getSessionCost(messages, modelPricingLookup);

formatCost(0.0032);  // "$0.003"
formatTokens(12345); // "12k"
```

这两个函数都从 `step-finish` 片段读取费用和 token 总数。当片段报告为零时，它们会使用 `ModelPricingLookup` 根据 token 数量估算费用。

ChatGPT/Codex 订阅消息计入 `$0.00`，包括运行时费用非零的历史消息。token 和上下文用量仍然可见。混合会话保留付费 API 消息的费用。OpenAI API 模型仍会计费，包括名称中含有 `codex` 的 API 模型。

<a id="child-sessions-and-pending-requests"></a>

### 子会话和待处理请求

项目会话的对话树（根对话及其生成的子智能体对话）来自其 `runtime_sessions` 列表。这些辅助函数读取该列表，优先使用当前名称，而不是 W4 之前的名称：

```ts
import { directSubsessions, projectSessionForRuntimeId, rootRuntimeSession, runtimeSessionsOf } from '@beeuni/sdk';

const root = rootRuntimeSession(projectSession);            // the root conversation id
const children = directSubsessions(projectSession);         // its subagent conversations
const owner = projectSessionForRuntimeId(projectSessions, childId); // the session a conversation belongs to
```

`task` 工具调用会委派给子会话。这些辅助函数将父轮次与该子会话自身的消息关联起来。

```ts
import { getChildSessionId, getChildSessionToolParts } from '@beeuni/sdk';

const childId = getChildSessionId(taskToolPart);
const steps = getChildSessionToolParts(childMessages);
```

将待处理的权限或问题请求与其工具调用匹配，并找出在请求处于活动状态时应隐藏哪些工具片段：

```ts
import { getPermissionForTool, getHiddenToolParts, isToolPartHidden } from '@beeuni/sdk';

const permission = getPermissionForTool(permissions, callID);
const hidden = getHiddenToolParts(activePermission, activeQuestion);
```

<a id="formatting-and-lists"></a>

### 格式化和列表

```ts
import { getFilename, getDirectory, relativizePath, stripAnsi } from '@beeuni/sdk';

getFilename('/workspace/src/main.go');               // "main.go"
getDirectory('/workspace/src/main.go');               // "/workspace/src"
relativizePath('/workspace/src/main.go', '/workspace'); // "src/main.go"
```

会话列表辅助函数操作相同的会话数据：

```ts
import { sortSessions, childMapByParent, allDescendantIds } from '@beeuni/sdk';

sessions.sort(sortSessions(Date.now())); // pins sessions updated in the last 60s
const childMap = childMapByParent(sessions);
const descendants = allDescendantIds(childMap, sessionId);
```

重试状态：`getRetryInfo(sessionStatus)` 返回 `{ attempt, message, next,
details? }` when `status.type === 'retry'`, with `message` 限为 60 个字符。`details` 会在存在结构化 LLM 网关封装时保留它：最终提供商、网关代码、请求 ID、建议、上游状态，以及按顺序排列的候选失败信息。每个候选失败信息包含 `attempt`、`provider`、`routeModel`、`resolvedModel`、`stage`、可选的 `status`、`code` 和 `message`。旧版纯文本重试返回 `details: undefined`。
`getRetryMessage(sessionStatus)` 返回解除封装后的完整消息。当 运行环境 仅保留该消息时，网关复合信息仍包含请求 ID，以及每个候选的提供商、解析后的模型、HTTP 状态、代码和有长度限制的消息。

<a id="structural-types"></a>

### 结构类型

分组和状态函数接受最小结构类型 —
`PartLike`、`MessageInfoLike`、`TurnLike`、`ToolStateLike`、
`SessionStatusLike` — 而非具体的会话记录类型。
只要符合必需字段，你自己的消息和片段结构就会原样传递。
`classifyPart` 和 `classifyTurn` 是例外。
它们以 Beeuni 的 `Part` 联合类型（`beeuni.transcript.v1`）为类型依据，因此其
穷尽性检查会在构建时发现新的片段类型。

<a id="distribution"></a>

## 分发

```sh
npm install @beeuni/sdk
```

此包以编译后的 ESM 形式发布，并提供完整的 TypeScript 类型声明。
`react` 和 `@tanstack/react-query` 是可选的对等依赖。如果
使用 `@beeuni/sdk/react`，请同时安装两者。

```ts
import { createBeeuni } from '@beeuni/sdk'; // framework-free core
import { useSession } from '@beeuni/sdk/react'; // optional React layer
import { createScopedBeeuni } from '@beeuni/sdk/server'; // Node and Bun servers
```

<a id="entry-points-and-stability"></a>

### 入口与稳定性

根入口 `@beeuni/sdk` 是标准入口。它导出完整的
无框架依赖接口，可在浏览器、Node 18+、Bun 和边缘
运行时中运行。

| 入口 | 级别 | 约定 |
| --- | --- | --- |
| `@beeuni/sdk` | 标准 | 无框架依赖。绝不导入 React 或 `node:*`。 |
| `@beeuni/sdk/react` | 受支持 | React hooks。唯一导入 React 的入口。 |
| `@beeuni/sdk/server` | 受支持 | 通过 `node:async_hooks` 为 Node 和 Bun 服务器提供按请求隔离的配置。切勿将其打包到浏览器中。 |
| 旧版子路径（`/projects-client`、`/turns`、`/files` 等） | 已弃用 | 仍可使用。每个子路径都从根入口重新导出。请改为从根入口导入。 |
| `@beeuni/sdk/internal/*` | 内部 | 仅供 Beeuni Web 应用使用。不是受支持的 API。 |

<a id="cdn-bundles"></a>

### CDN 包

此包还提供两个由 `tsup` 构建的浏览器包：一个 ESM 包
（`dist/beeuni.esm.min.js`）和一个定义了 `window.Beeuni` 的 IIFE 全局包（`dist/beeuni.global.js`）。

```html
<script src="https://unpkg.com/@beeuni/sdk/dist/beeuni.global.js"></script>
<script>
  const beeuni = Beeuni.createBeeuni({
    backendUrl: 'https://api.beeuni.ai/v1',
    getToken: async () => KEY,
  });
</script>
```

`window.Beeuni` 暴露的根入口与 npm 导入相同：
`Beeuni.createBeeuni`、`Beeuni.classifyTurn`、`Beeuni.ApiError`。

<a id="see-also"></a>

## 另请参阅

- [SDK](/docs/zh/sdk) — 安装并创建你的第一个会话。
- [会话](/docs/zh/sdk/sessions) — 生命周期、流式传输和错误处理。
- [React hooks](/docs/zh/sdk/react) — 基于这些模块构建的响应式层。


<a id="agent-repository-access"></a>

### 智能体仓库访问

智能体配置接受 `repository_access?: boolean`（默认值为 `true`）。
设置 `false`，即可在不访问项目仓库或仓库 API 的情况下运行新会话。
Git、密钥、连接器和工具权限仍独立管理。现有会话保留其已保存的策略。
`AgentConfigBlock.workspace` 已弃用。SDK 将旧版 `branch`/`runtime` 映射到布尔字段。
写入旧版 `read` 时，必须明确选择 `repository_access`；这不会启用只读仓库访问。


<a id="provider-and-model-access"></a>

## 提供商和模型访问

读取 `beeuni.projects.modelAccess(projectId)`。使用 `beeuni.projects.setModelAccess(projectId, { target: 'provider' | 'model', id, enabled })` 更改一个目标。

对于 Beeuni Managed Models，使用提供商 ID `beeuni`；对于 BYOK、Codex 和自定义提供商，使用目录中的提供商 ID。对于模型，使用完整的网关传输 ID。禁用提供商会涵盖未来的模型，并保留各模型的偏好设置和已保存的凭据。

禁用当前默认模型及其提供商之前，必须先指定替代项（`409 cannot_disable_default`）。直接向已禁用目标发送的网关请求会失败，并返回 `model_disabled` 或 `provider_disabled`。现有正在处理的上游请求会正常完成。当项目使用原生 运行环境 而非网关时，响应中的 `enforced` 标志为 false。

React 使用方使用 `@beeuni/sdk/react` 中的 `useModelAccess(projectId)`。其 `setEnabled(change)` 变更操作会刷新管理目录和会话选择器。
