From 7d255857d1783c42038b38c9e0137250527d3927 Mon Sep 17 00:00:00 2001 From: yuanhe Date: Fri, 2 Oct 2026 12:50:55 +0800 Subject: [PATCH 1/2] docs: add maintained Chinese documentation and localized website navigation --- AGENTS.md | 2 +- CONTRIBUTING.md | 2 +- contracts/agents-api/zh/admin-api.md | 239 ++++++++ contracts/agents-api/zh/core-errors.md | 110 ++++ contracts/agents-api/zh/core-metrics.md | 67 +++ .../zh/environment-executor-credentials.md | 100 ++++ contracts/agents-api/zh/environment-files.md | 116 ++++ contracts/agents-api/zh/environments.md | 391 +++++++++++++ contracts/agents-api/zh/execution-tools.md | 95 +++ .../agents-api/zh/harness-capabilities.md | 63 ++ contracts/agents-api/zh/harness-catalog.md | 12 + contracts/agents-api/zh/harness-onboarding.md | 266 +++++++++ contracts/agents-api/zh/index.md | 153 +++++ contracts/agents-api/zh/machine-api.md | 124 ++++ contracts/agents-api/zh/message-content.md | 74 +++ contracts/agents-api/zh/model-execution.md | 144 +++++ .../agents-api/zh/node-generation-protocol.md | 126 ++++ .../zh/runtime-observability-api.md | 288 +++++++++ .../agents-api/zh/runtime-observability.md | 132 +++++ contracts/agents-api/zh/sandbox-deployment.md | 231 ++++++++ .../agents-api/zh/session-diagnostics.md | 48 ++ contracts/agents-api/zh/sessions-events.md | 156 +++++ contracts/agents-api/zh/source-files.md | 114 ++++ contracts/agents-api/zh/subagents.md | 63 ++ contracts/agents-api/zh/vaults.md | 150 +++++ contracts/agents-api/zh/wire-semantics.md | 251 ++++++++ docs/zh/api/index.md | 21 + docs/zh/api/public-agent-api.md | 547 ++++++++++++++++++ docs/zh/architecture.md | 51 ++ docs/zh/concepts.md | 37 ++ docs/zh/configuration.md | 215 +++++++ docs/zh/development.md | 114 ++++ docs/zh/examples.md | 39 ++ docs/zh/getting-started/index.md | 34 ++ docs/zh/getting-started/install-options.md | 351 +++++++++++ docs/zh/getting-started/install.md | 100 ++++ docs/zh/getting-started/nodes.md | 203 +++++++ docs/zh/getting-started/operations.md | 209 +++++++ docs/zh/getting-started/quickstart.md | 116 ++++ docs/zh/getting-started/self-hosted.md | 167 ++++++ docs/zh/maintainers.md | 219 +++++++ docs/zh/runtime-bootstrap.md | 38 ++ docs/zh/runtime-protocol.md | 212 +++++++ docs/zh/sandbox-provider.md | 237 ++++++++ docs/zh/web/console-api-usage.md | 148 +++++ docs/zh/web/console-server.md | 129 +++++ docs/zh/web/index.md | 58 ++ scripts/generate-harness-catalog.py | 11 + scripts/name-allowlist.json | 5 + website/.vitepress/config.mts | 33 +- website/.vitepress/docs-nav.mts | 7 +- website/.vitepress/locales.mts | 34 ++ website/.vitepress/repository-links.mts | 10 +- .../theme/components/ComposeLab.vue | 4 +- .../.vitepress/theme/components/Landing.vue | 21 +- website/.vitepress/theme/docs.data.mts | 8 +- website/.vitepress/theme/landing-content.ts | 2 +- website/README.md | 8 +- website/package.json | 5 +- website/tests/dist.test.mjs | 19 + website/tests/translations.test.mjs | 52 ++ 61 files changed, 6947 insertions(+), 34 deletions(-) create mode 100644 contracts/agents-api/zh/admin-api.md create mode 100644 contracts/agents-api/zh/core-errors.md create mode 100644 contracts/agents-api/zh/core-metrics.md create mode 100644 contracts/agents-api/zh/environment-executor-credentials.md create mode 100644 contracts/agents-api/zh/environment-files.md create mode 100644 contracts/agents-api/zh/environments.md create mode 100644 contracts/agents-api/zh/execution-tools.md create mode 100644 contracts/agents-api/zh/harness-capabilities.md create mode 100644 contracts/agents-api/zh/harness-catalog.md create mode 100644 contracts/agents-api/zh/harness-onboarding.md create mode 100644 contracts/agents-api/zh/index.md create mode 100644 contracts/agents-api/zh/machine-api.md create mode 100644 contracts/agents-api/zh/message-content.md create mode 100644 contracts/agents-api/zh/model-execution.md create mode 100644 contracts/agents-api/zh/node-generation-protocol.md create mode 100644 contracts/agents-api/zh/runtime-observability-api.md create mode 100644 contracts/agents-api/zh/runtime-observability.md create mode 100644 contracts/agents-api/zh/sandbox-deployment.md create mode 100644 contracts/agents-api/zh/session-diagnostics.md create mode 100644 contracts/agents-api/zh/sessions-events.md create mode 100644 contracts/agents-api/zh/source-files.md create mode 100644 contracts/agents-api/zh/subagents.md create mode 100644 contracts/agents-api/zh/vaults.md create mode 100644 contracts/agents-api/zh/wire-semantics.md create mode 100644 docs/zh/api/index.md create mode 100644 docs/zh/api/public-agent-api.md create mode 100644 docs/zh/architecture.md create mode 100644 docs/zh/concepts.md create mode 100644 docs/zh/configuration.md create mode 100644 docs/zh/development.md create mode 100644 docs/zh/examples.md create mode 100644 docs/zh/getting-started/index.md create mode 100644 docs/zh/getting-started/install-options.md create mode 100644 docs/zh/getting-started/install.md create mode 100644 docs/zh/getting-started/nodes.md create mode 100644 docs/zh/getting-started/operations.md create mode 100644 docs/zh/getting-started/quickstart.md create mode 100644 docs/zh/getting-started/self-hosted.md create mode 100644 docs/zh/maintainers.md create mode 100644 docs/zh/runtime-bootstrap.md create mode 100644 docs/zh/runtime-protocol.md create mode 100644 docs/zh/sandbox-provider.md create mode 100644 docs/zh/web/console-api-usage.md create mode 100644 docs/zh/web/console-server.md create mode 100644 docs/zh/web/index.md create mode 100644 website/.vitepress/locales.mts create mode 100644 website/tests/translations.test.mjs diff --git a/AGENTS.md b/AGENTS.md index e40aec17..ed02a428 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -63,7 +63,7 @@ OpenAgentCore is pre-release. Replace superseded interfaces, execution paths and - Do not hard-wrap prose. Write each paragraph, list item and blockquote on one line; editors wrap it for display. - User-facing documentation uses Web's exact page and action names. - Application examples read the endpoint and key from `OPENAI_BASE_URL` and `OPENAI_API_KEY`. -- Write documentation and code comments in English. The root README also has a Chinese version; user-facing product copy may be bilingual. +- Maintain authored documentation under `docs/` and `contracts/agents-api/` in English and Simplified Chinese in the same change. English files keep their paths; Chinese translations mirror them under `docs/zh/` and `contracts/agents-api/zh/`. Preserve protocol identifiers, executable examples and English heading anchors. Each translation records its English `source` and SHA-256 `source_hash` in frontmatter; website checks reject missing or stale translations. Generated references remain owned by their generators and are excluded from manual translation. Keep code comments in English. The root README also has a Chinese version; user-facing product copy may be bilingual. - Markdown in `docs/`, component guides and `contracts/` is the authored source. Generated files, such as the OpenAPI documents and the [Harness catalog reference](contracts/agents-api/harness-catalog.md), are never edited by hand: change the source and regenerate. - Update the owning document in the same branch as the rule, workflow or generated contract it describes. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d2546942..12cb0421 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,7 @@ This guide owns how to work in the repository: documentation ownership, the repo | Runtime telemetry responses | [Runtime telemetry API](contracts/agents-api/runtime-observability-api.md) | | Runtime observation, sampling, retention and export | [Runtime observability](contracts/agents-api/runtime-observability.md) | | Distribution builds, Runtime image builds, CI and publication | [Maintainer guide](docs/maintainers.md) | -| Website: landing page, documentation site build and GitHub Pages publication | [Website guide](website/README.md) | +| Website: landing page, bilingual documentation maintenance, documentation site build and GitHub Pages publication | [Website guide](website/README.md) | | Self-hosted Runtime installation, recovery and local operation | [Self-hosted execution](docs/getting-started/self-hosted.md) | | Installer lifecycle, locking, generated state, managed HTTPS and downloads | [Installer design rules](deploy/install/README.md) | | Operator installation and alternatives | [Installation](docs/getting-started/install.md), [installation options](docs/getting-started/install-options.md) | diff --git a/contracts/agents-api/zh/admin-api.md b/contracts/agents-api/zh/admin-api.md new file mode 100644 index 00000000..071bb2c4 --- /dev/null +++ b/contracts/agents-api/zh/admin-api.md @@ -0,0 +1,239 @@ +--- +title: "Core 管理 API" +source: contracts/agents-api/admin-api.md +source_hash: 04fbf3485f88ba0395efb31fec57b2e40e9584db6c648f7e83ab7866e45bdfb2 +--- + +Core 管理 API(`/core/v1`)用于管理安装实例:Project 及其 API 密钥、Project 资源的读取和删除、执行器凭据、部署默认模型、沙箱部署及其节点、监控和审计。Web 的[控制台服务器](../../../docs/zh/web/console-server.md#forwarding-to-core)会为已登录的管理员调用它;运维人员则从 Core 主机上的脚本调用它([编写 Core API 脚本](../../../docs/zh/getting-started/operations.md#script-the-core-api))。生成的架构是 [core.openapi.yaml](../core.openapi.yaml),所有错误都使用 [Core 错误封装](core-errors.md)。 + +应用程序绝不调用 `/core/v1`。它没有任何用于创建或编辑 Agents、Sessions、模板、Files、Skills 或 Vaults,启动或取消工作,读取 Source File 内容或流式传输事件的操作;应用程序通过 [Agents API](../../../docs/zh/api/public-agent-api.md)完成这些操作。 + +## 身份验证 {#authentication} + +每个 `/core/v1` 请求,包括针对未知路径的请求,都必须发送 `Authorization: Bearer `。缺少该请求头时,Core 会返回 401 `invalid_admin_key` 和 `WWW-Authenticate: Bearer`;只有在身份验证通过后,未知路径才会返回 404 `not_found`。 + +- Core 将 bearer 凭据的 SHA-256 与启动时从 `generated/core-key-digests.json` 读取的 Core 密钥摘要进行比较;该摘要由 `oac apply` 根据 `secrets/core.key` 派生([Core 密钥](../../../docs/zh/getting-started/operations.md#core-key))。轮换 Core 密钥后,需要重启 Core 才会生效。未配置任何摘要时,Core 不提供 `/core/v1` 路由。 +- Core 密钥仅用于 `/core/v1` 身份验证。Project API 密钥和机器凭据在此处会收到 401,而 Core 密钥在 `/v1` 和 `/api/v1` 上会收到 401([API 命名空间和凭据](../../../docs/zh/api/index.md))。 +- `X-Core-Console-Actor` 是由调用方声明的标签。Core 会将其记录为审计 `actor_label`,但不会验证。控制台服务器发送 `console`;脚本通常不发送该值,因此会记录空标签。切勿将其用于授权或作为来源证明。 +- 路径中的 `{project_id}` 用于选择目标 Project,包括已归档的 Project;它不会授予任何权限。 + +## 路由 {#routes} + +路径均相对于 `/core/v1`。 + +| 路由 | 用途 | 契约 | +| --- | --- | --- | +| `installation` | 公共 URL、API 基础 URL、源代码提交、安装器的进程设置,以及绑定到公共 URL 的内容 | [安装信息](#installation-facts) | +| `projects`、`projects/{project_id}`、`projects/{project_id}/archive`、`projects/{project_id}/keys[/{key_id}]` | Project 及其 API 密钥 | [Project 与密钥](#projects-and-keys) | +| `projects/{project_id}/{agents,environment-templates,skills,files,vaults,sessions}/**` | 资源读取和删除、Session 历史及 Artifact | [资源读取和删除](#resource-reads-and-deletion) | +| `projects/{project_id}/sessions/{session_id}/archive` | 归档一个托管 Session | [Session 归档](#session-archive) | +| `projects/{project_id}/sessions/{session_id}/execution-configuration` | Session 创建时冻结的模型、Harness 和提供商选择 | [执行配置](#execution-configuration) | +| `projects/{project_id}/sessions/{session_id}/diagnostics`、`…/turns/{turn_id}/diagnostics` | 失败类别和 Item 接收时序 | [Session 诊断](session-diagnostics.md) | +| `projects/{project_id}/sessions/{session_id}/runtime-observation`、`sandbox/runtime-observations` | 当前 Runtime 观测值 | [Runtime 观测](runtime-observability-api.md)、[列表中的磁盘字段](#runtime-observations) | +| `projects/{project_id}/sessions/{session_id}/runtime-history` | 已存储的 Runtime 历史 | [Runtime 历史](runtime-observability-api.md#session-runtime-history) | +| `projects/{project_id}/environments/{environment_id}/installation` | `self_hosted` Environment 的安装命令 | [安装授权](environment-executor-credentials.md#installation-grant) | +| `projects/{project_id}/environments/{environment_id}/executor-credentials[/{key_id}]` | `self_hosted` Environment 的执行器凭据 | [执行器凭据](environment-executor-credentials.md#core-key-routes) | +| `projects/{project_id}/resource-owners`、`projects/{project_id}/write-operations` | 创建资源的 API 密钥以及每个密钥执行的写入 | [写入溯源](#write-provenance) | +| `harnesses`、`harnesses/{harness}/model-configuration` | 已启用的 Harness 及各 Harness 的部署默认模型 | [部署默认值](model-execution.md#deployment-defaults) | +| `sandbox/deployment`、`sandbox/deployment/reset`、`sandbox/providers/{provider}/discovery` | 沙箱提供商、资源和 Runtime、重置,以及提供商配置发现,例如 E2B 模板 | [沙箱部署](sandbox-deployment.md#routes) | +| `sandbox/enrollment-tokens`、`sandbox/nodes[/{node_id}[/allocations]]` | 节点注册令牌、节点及其分配和主机历史 | [节点指南](../../../docs/zh/getting-started/nodes.md)、[沙箱部署](sandbox-deployment.md)、[节点主机历史](runtime-observability-api.md#node-host-observations-and-history) | +| `summary` | 按 Project、Agent 或密钥统计的 Session 数量和使用情况 | [汇总](#summary) | +| `metrics` | Core 自身的进程、执行、数据库和作业指标 | [Core 指标](core-metrics.md) | +| `audit-log` | 管理员写入 | [审计日志](#audit-log) | + +## Project 与密钥 {#projects-and-keys} + +每个 Project 拥有一个执行租户;其密钥共享该租户的主体和资产([Project 拥有资产](../../../docs/zh/concepts.md#projects-own-assets))。Web 的 **Projects and keys** 页面使用这些路由。 + +| 操作 | 路由 | 结果 | +| --- | --- | --- | +| 列出 Project | `GET /projects` | `{data, has_more}` | +| 创建 Project | `POST /projects`,请求体为 `{name}` | 201 和 Project | +| 重命名 Project | `POST /projects/{project_id}`,请求体为 `{name}` | Project | +| 归档 Project | `POST /projects/{project_id}/archive` | Project | +| 列出密钥 | `GET /projects/{project_id}/keys` | `{data, has_more}` | +| 签发密钥 | `POST /projects/{project_id}/keys`,请求体为 `{name}` | 201、密钥元数据和明文 `key` | +| 吊销密钥 | `DELETE /projects/{project_id}/keys/{key_id}` | `{id, deleted: true}` | + +- Project 包含 `id`、`name`、`created_at`、可为 null 的 `archived_at` 和 `active_key_count`。密钥包含 `id`、`project_id`、`name`、`prefix`、`created_at` 和可为 null 的 `revoked_at`。ID 是服务器生成的 UUID。 +- Project 名称长度为 1–128 个字符,密钥名称长度为 1–80 个字符;名称仅作为标签,可以重复,控制字符会被拒绝。 +- 列表按 ID 排序,可使用 `order=asc|desc`(默认 `desc`)、`limit=1..100`(默认 20)和 `after`。 +- 只有签发响应会通过 `key` 包含密钥明文;Core 仅存储其摘要。密钥只应显示一次,且绝不能缓存。如果签发响应不确定,请列出密钥,吊销所有无法使用的密钥,然后再签发新密钥。 +- 归档操作会在一个事务中将 Project 标记为已归档、吊销其所有密钥并写入审计条目。在已归档的 Project 中签发密钥会返回 409 `project_archived`。不支持删除 Project、取消归档或重置密钥。 + +[`/v1` 身份验证规则](wire-semantics.md#authentication)定义了密钥查找、吊销可见性、作用域标头和身份验证错误。 + +## 资源读取和删除 {#resource-reads-and-deletion} + +路径均相对于 `/core/v1/projects/{project_id}`。每次读取都会返回与对应 `/v1` 操作相同的对象、分页和错误,每次删除也具有相同的前置条件。 + +| 资源 | 读取 | 删除 | +| --- | --- | --- | +| Agents | `/agents`、`/agents/{agent_id}` | `/agents/{agent_id}` | +| Environment Templates | `/environment-templates`、`/environment-templates/{environment_template_id}` | 单项路由 | +| Skills | `/skills`、`/skills/{skill_id}`、`/skills/{skill_id}/content`、`/skills/{skill_id}/versions`、`/skills/{skill_id}/versions/{version}` 及其 `/content` | Skill 和版本单项路由 | +| Files | `/files`、`/files/{file_id}` | 单项路由 | +| Vaults | `/vaults`、`/vaults/{vault_id}`、`/vaults/{vault_id}/credentials`、`/vaults/{vault_id}/credentials/{credential_id}` | Vault 和 Credential 单项路由 | +| Sessions | `/sessions`、`/sessions/{session_id}`,以及其下的 `/turns`、`/turns/{turn_id}`、`/items`、`/artifacts`、`/artifacts/{artifact_id}` 及其 `/content` | Session 和 Artifact 单项路由 | + +- 管理员删除绝不会取消工作:如果 `/v1` 因 Turn 或输入待处理而无法删除某个 Session,管理员删除也会返回相同的 409。 +- 删除 Credential 只会移除 Core 中的副本;不会撤销提供商端的授权。 +- 对 Skill 和 Artifact 内容、Runtime 观测值、Runtime 观测值列表以及 Runtime 历史发送 `HEAD` 会返回 405,因此绝不会采样提供商或查询遥测数据。 +- 管理员删除会显示在[审计日志](#audit-log)中,而不会显示在密钥写入历史中。 + +## Session 归档 {#session-archive} + +使用 `{"expected_generation": N}` 发送 `POST /projects/{project_id}/sessions/{session_id}/archive`,可在不重置部署的情况下释放一个由 Core 管理的 `openai_hosted` Session 的沙箱。N 是[沙箱部署](sandbox-deployment.md)的当前代次,是一个正整数。必须先在 Web 中配置该部署。 + +| 情况 | 结果 | +| --- | --- | +| 代次过期 | 409 `sandbox_generation_stale` | +| Environment 类型不是 `openai_hosted` | 400 | +| Session 不存在或属于另一个 Project | 404 | + +一个事务会将 Environment 标记为过期(已失败的 Environment 仍保持失败状态)、请求取消正在运行的工作、吊销 Runtime 的权限并写入审计条目。沙箱及其快照随后通过正常生命周期完成清理;如果资源是否已释放尚不确定,在提供商确认之前,该资源仍归 Core 所有。Session 不会被删除:其历史以及已持久化的 Files 和 Artifacts 仍可读取,未持久化的工作区内容会丢失,并且该 Session 无法恢复运行。 + +`POST` 和 `GET /projects/{project_id}/sessions/{session_id}/archive` 返回 `{session_id, environment_id, state}`。`GET` 是只读操作,不需要代次。`state` 表示资源的当前处置状态:`active`、`cleanup_pending` 或 `released`,无论该资源由什么操作释放。`released` 不表示活动的 Turn 已完成取消;请读取相应的 Turn。 + +如果 `POST` 响应不确定,请先对归档记录发送 `GET`,然后再进行写入。重复发送 `POST` 会产生相同效果,并为每个被接受的请求记录一个审计条目。要在更改部署前清除所有托管 Session,请使用[部署重置](sandbox-deployment.md#reset)。 + +## 执行配置 {#execution-configuration} + +`GET /projects/{project_id}/sessions/{session_id}/execution-configuration` 报告 Session 在创建时冻结的模型、Harness、原生参数和模型提供商。它仅读取已存储的配置:绝不会联系提供商、启动 Turn 或唤醒沙箱。响应带有 `Cache-Control: no-store`。 + +```json +{ + "object": "agent.session.execution_configuration", + "schema_version": 1, + "session_id": "013773a9-44b9-4f84-baca-b51c04a01201", + "model": {"value": "requested-model", "source": "session"}, + "harness": {"value": "codex", "source": "agent"}, + "harness_config": {"value": {"model_reasoning_effort": "high"}, "source": "agent"}, + "model_provider": { + "source": "agent", + "status": "available", + "configuration": {"protocol": "responses", "base_url": "https://model.example/v1", "api_key_configured": true} + } +} +``` + +每个 `source` 都是 `session`、`agent`、`deployment` 或 `unknown`,并且独立记录:Session 可以覆盖模型,同时保留其 Agent 的 Harness 和提供商。显式内联 Harness 的来源是 `session`;内联 `agent.x_agents_core: null` 会将 Harness 重置为 `deployment`,并保留继承的提供商;Session 提供者为 null 时会正常继承。没有任何原生参数适用时,`harness_config.value` 为 `{}`。[模型执行](model-execution.md)负责定义每个值的解析方式。 + +| `model_provider.status` | 含义 | `configuration` | +| --- | --- | --- | +| `available` | Core 记录了冻结提供商的安全视图,包括部署默认值(来源 `deployment`) | `protocol`、`base_url`、`api_key_configured`,以及在已设置时包含的 `context_window` 和 `max_output_tokens` | +| `redacted` | 记录了部署选择,但没有安全视图 | null | +| `unavailable` | Core 没有关于该提供商的可靠记录(来源 `unknown`)。执行仍可能已成功 | null | + +Core 会在创建 Session 的同一事务中写入此记录。之后的 Agent 编辑或删除、部署默认模型更改、重启以及使用同一密钥重试创建都不会改变该记录。没有该记录的 Session 会报告其已存储的模型和 Harness,来源均为 `unknown`;未存储任何值时对应字段为 null;提供商为 `unavailable`。Session 不存在和 Session 属于另一个 Project 时返回相同的 404。响应绝不会包含密钥、密文、机密引用、原生标头或查询参数。 + +## 安装信息 {#installation-facts} + +`GET /installation` 报告管理员调用和更改此安装实例所需的信息。即使尚未创建任何沙箱部署,该接口也会响应,并且不会调用任何提供商或模型。 + +| 字段 | 含义 | +| --- | --- | +| `object` | `core.installation` | +| `installation_id` | `state.json` 中的安装 ID([安装目录](../../../docs/zh/configuration.md#installation-directory));Core 在不使用沙箱管理器运行时为 null | +| `public_url` | `public_url` 设置([设置](../../../docs/zh/configuration.md#settings)):应用程序、节点、沙箱和自托管执行器使用的源地址。未设置时为 null | +| `api_base_url` | 在 `public_url` 后附加 `/v1`,即 Project API 密钥使用的 `OPENAI_BASE_URL`。当 `public_url` 为 null 时为 null | +| `local_only` | 当 `public_url` 指向回环主机时为 True,该主机只能由 Core 主机访问 | +| `source_commit` | Core 构建所依据的完整源代码提交;开发构建为 null | +| `configuration` | 安装器对 `config.json` 的快照;安装器未启动 Core 时为 null | +| `address_bindings` | 更改 `public_url` 所影响的内容,每次读取都会重新统计 | + +`configuration` 包含: + +- `path`:`config.json` 在主机上的绝对路径,默认值为 `~/.oac/core/config.json`; +- `apply_command`:应用更改的命令,默认值为 `~/.oac/core/oac apply`; +- `applied_at`:最近一次应用快照的时间; +- `settings`:每个设置对应一个条目,包含以点分隔的 `key`、已应用的 `value`、`default`、安装后是否可 `changeable`、是否 `sensitive`,以及会 `restarts` 的服务(`core`、`web`、`database`)。 + +敏感设置的 `value` 和 `default` 为 null,并改为包含一个布尔值 `configured`;只有敏感设置具有 `configured`。如果快照违反此规则、重复使用某个键或包含未知成员,Core 将拒绝启动。Core 仅报告该快照;[配置](../../../docs/zh/configuration.md)会说明每个设置。 + +| `address_bindings` 字段 | 含义 | +| --- | --- | +| `nodes` | 已注册且未移除的节点 | +| `nodes_on_other_address` | 使用了 `public_url` 以外地址注册的节点。它们不会获得新的沙箱;请移除后重新添加。最多为 `nodes` 个 | +| `hosted_sandboxes` | 保留的待处理托管沙箱;它们启动时使用当时有效的地址运行 | +| `self_hosted_executors` | 未吊销的执行器凭据;对应执行器安装时使用的是当时公布的 `remote_url` | + +## 写入溯源 {#write-provenance} + +Core 会记录是哪个 Project API 密钥完成了每次成功的公共写入;该记录与写入在同一事务中完成,如果记录失败,写入也会失败。读取、被拒绝的请求以及 Core 自身的维护操作(例如 OAuth 令牌刷新和清理)不会被记录。 + +- 写入在提交时才会被记录。Session 输入一经接纳即记录一次,包括为仍在准备中的 Environment 预留的输入;后续执行失败或响应丢失不会删除该记录。显式提交的空输入批次以及重复的创建或删除操作也会被记录,但不会改变所有权。 +- Environment 文件上传会在 Runtime 确认写入时被记录。Core 会在发送文件前保存密钥、请求和追踪信息,并且只记录已确认的上传。 +- 创建资源时也会记录其创建者。更新、重试和无效写入绝不会改变创建者。新 Skill 的第一个版本和新 Session 的 Environment 共享创建操作。Artifact 来自 Runtime,没有创建者;删除 Artifact 时会记录该操作。 +- 吊销密钥会阻止新的写入,但会保留其历史。删除资源会保留其创建者和操作历史。 +- 每条记录包含其 ID、时间、密钥元数据、操作、资源类型和 ID、父 ID、`request_id` 和 `trace_id`。`request_id` 由服务器按请求生成;共享的 `trace_id` 不是幂等密钥。记录绝不会包含请求或响应正文、机密、模型凭据、令牌、文件路径或文件内容。 + +| 公共写入 | `action` | `resource_type`(父级) | +| --- | --- | --- | +| Agent 创建、更新、删除 | `create`、`update`、`delete` | `agent` | +| Session 创建、更新、删除 | `create`、`update`、`delete` | `session` | +| Session 事件 | `send_events` | `session` | +| Artifact 删除 | `delete` | `artifact`(`session`) | +| Environment 文件上传 | `upload_file` | `environment`(`session`) | +| Environment Template 创建、更新、删除 | `create`、`update`、`delete` | `environment_template` | +| Skill 创建、默认版本更改、删除 | `create`、`update_default_version`、`delete` | `skill` | +| Skill 版本上传、删除 | `upload_version`、`delete` | `skill_version`(`skill`) | +| Source File 上传、删除 | `create`、`delete` | `file` | +| Vault 创建、删除 | `create`、`delete` | `vault` | +| Credential 创建、替换、删除(静态和 OAuth) | `create`、`update`、`delete` | `credential`(`vault`) | + +上述两条路由仅接受列出的参数;未知、重复或空参数会返回 400,Project 不存在则返回 404。 + +`GET /projects/{project_id}/resource-owners?resource_type=agent&resource_ids=id1,id2` 按请求顺序返回同一类型下 1–100 个资源的创建密钥。`resource_type` 可以是 `agent`、`session`、`environment`、`environment_template`、`skill`、`skill_version`、`file`、`vault`、`credential` 或 `artifact`。 + +```json +{"data":[ + {"resource_id":"id1","api_key":{"id":"key-uuid","name":"SDK","prefix":"pc_example","kind":"issued","revoked_at":null},"source":"api_key","admin_audit_id":null}, + {"resource_id":"id2","api_key":null,"source":null,"admin_audit_id":null} +]} +``` + +当 Core 没有创建记录时,`api_key` 和 `source` 为 null,这包括另一个 Project 中的资源。带有 `admin_audit_id` 的 `source: "admin_copy"` 表示该资源由审计日志中的 `copy` 条目记录;当前没有路由会写入此类记录。 + +`GET /projects/{project_id}/write-operations` 按 `(created_at, id)` 从新到旧列出写入记录。过滤条件包括:`key_id`、`resource_type`、`resource_id`、包含起始时间的 `created_after` 和不包含结束时间的 `created_before`(RFC 3339)。`limit` 为 1–100,默认值为 50。在过滤条件不变的情况下,将上一个 `next_cursor` 作为 `after` 传入。响应为 `{data, has_more, next_cursor}`;每个条目包含 `id`、`created_at`、`api_key`、`action`、`resource_type`、`resource_id`、`parent_id`(不存在时为空)、`request_id` 和 `trace_id`。 + +创建记录会永久保留,即使资源已删除也一样。其他记录会按照 [`core.write_audit_retention`](../../../docs/zh/configuration.md#settings) 保留,默认期限为 90 天;Core 每分钟最多删除 1,000 条过期记录,因此积压记录需要经过多轮处理才能清空。吊销密钥或删除资源绝不会删除记录。 + +## 汇总 {#summary} + +`GET /summary` 统计 Session 数量和使用情况。 + +| 参数 | 含义 | +| --- | --- | +| `project_id` | 一个 Project;`group_by=agent` 时必填 | +| `group_by` | `project`(默认)、`agent` 或 `key` | +| `created_after`、`created_before` | Session 创建时间的 RFC 3339 下限(含)和上限(不含) | +| `after`、`limit`、`order` | 对 Project 进行分页 | + +响应为 `{data, has_more, next_cursor}`。每一行包含 `project_id`、可为 null 的 `agent_id` 和 `key_id`、当前 `assets` 计数(Agent 分组和密钥分组为 null)、`sessions` 计数(`total`、`idle`、`in_progress`、`requires_action`、`failed`)、汇总 `usage`、`coverage`(`measured_sessions`、`total_sessions`、可为 null 的 `ratio`)以及可为 null 的 Unix `last_active_at`。 + +- 在密钥分组中,每个 Session 都计入创建它的密钥,即使之后由另一个密钥发送输入也是如此。没有记录创建者的 Session 会组成一个 `key_id` 为 null 的分组。 +- 公共用量为 null 的 Session 不会增加 token 数量,但仍会计入覆盖率分母。 +- 每个 Project 都在单个数据库快照中读取;一页数据并不是整个部署的单一快照。总计属于运营计数,不是计费记录。 + +## Runtime 观测 {#runtime-observations} + +[Runtime 遥测 API](runtime-observability-api.md)负责当前观测值、[仅列表可用的磁盘字段](runtime-observability-api.md#disk)、Session 历史以及节点主机观测值和历史。 + +## 审计日志 {#audit-log} + +`GET /audit-log` 按从新到旧的顺序列出管理员写入。过滤条件包括 `project_id`、`resource_type`、`resource_id`、`action`、包含起始时间的 `created_after` 和不包含结束时间的 `created_before`(RFC 3339)。`limit` 为 1–100,默认值为 50,并使用不透明的 `after` 游标。响应为 `{data, has_more, next_cursor}`。 + +每个条目包含 `id`、`created_at`、`admin_credential_id`(Core 密钥摘要的前 8 个十六进制字符)、`actor_label`、`action`、`project_id`、`resource_type`、`resource_id`、`result_ids`、`request_id` 和 `trace_id`。除 `copy` 条目外,`result_ids` 都是空数组。部署范围条目为 `project_id: null`,使用 `project_id` 过滤时会排除这些条目。 + +| `resource_type` | `action` | `resource_id` | +| --- | --- | --- | +| `project` | `create`、`rename`、`archive` | Project ID | +| `api_key` | `create`、`revoke` | 密钥 ID | +| `executor_credential` | `issue`、`rotate`、`revoke` | 密钥 ID | +| `session` | `archive` | Session ID | +| `agent`、`environment_template`、`skill`、`skill_version`、`file`、`vault`、`credential`、`session`、`artifact` | `delete` | 资源 ID | +| `deployment_model_provider`(部署范围) | `set`、`delete` | Harness | +| `sandbox_deployment`(部署范围) | `change`、`replace_credential`(提交了提供商凭据,即使提交的是同一个凭据)、`reset_start`、`reset_force`、`reset_deadline`、`reset_cancel`、`reset_complete` | 安装 ID | + +管理员写入及其审计条目会在一个事务中提交;如果条目写入失败,写入也会失败。重置在后台执行的归档操作及其 `reset_deadline` 和 `reset_complete` 条目会携带请求方的凭据、操作者标签、请求和追踪信息,并且每个归档操作都会保留其 Session 的 Project。取消重置不会撤销已经提交的归档。被拒绝的提供商验证和无效更新不会写入条目。条目绝不会包含凭据值、请求正文或提供商响应文本,并且会在其资源被删除和密钥被吊销后继续保留。 diff --git a/contracts/agents-api/zh/core-errors.md b/contracts/agents-api/zh/core-errors.md new file mode 100644 index 00000000..d90ac52b --- /dev/null +++ b/contracts/agents-api/zh/core-errors.md @@ -0,0 +1,110 @@ +--- +title: "Core 管理错误" +source: contracts/agents-api/core-errors.md +source_hash: d5c4450c0c74927115c79dca70d29372a7316d5d2591c7e18af688d08257a98c +--- + +`/core/v1` 上的错误使用此封装结构。`message` 是安全的英文文本;`code` 和 `param` 可以为 null。客户端依据稳定的 `code` 和可选的 `param` 进行处理,对未知代码显示 `message`,绝不解析消息,也绝不自动重试被拒绝的写操作。 + +```json +{"error":{"message":"A valid Core key is required as the bearer credential.","type":"invalid_request_error","code":"invalid_admin_key","param":null}} +``` + +`/v1` 和 `/api/v1` 上的错误仍使用各自的封装结构,且绝不包含 `details`。 + +## 可选详细信息 {#optional-details} + +存在时,`error.details` 是一个非空的扁平对象。其值可以是字符串、有限数值、null 或字符串数组(数组可以为空)。它仅包含 Core 自身的事实;绝不包含已提交的名称、URL 或密钥、回显的请求值、原生错误文本或提供商响应正文。每个包含详细信息的代码都在下表列出了其确切键名。 + +| 代码 | 详细信息 | +| --- | --- | +| `sandbox_generation_stale` | `current_generation` | +| `sandbox_in_use` | `allocations`、`pending` | +| `sandbox_reset_required` | `current_provider`、`requested_provider` | +| 操作验证代码 | 请参阅 [operation validation](#operation-validation) | + +在 TypeScript 客户端中,`AgentCoreError.details` 是可选的 `CoreErrorDetails`。Core 客户端仅接受上述值类型,会复制字符串数组,并忽略格式错误或为空的 `details`,且不会改变错误的 message、status、code、param 或 type。公开的 `OpenAIAgentsClient` 不读取 `details`。 + +## 控制台自身故障 {#console-generated-failures} + +Web 的控制台服务器在 `/core` 路径上发生自身故障时使用此封装结构([request boundary](../../../docs/zh/web/console-server.md#request-boundary))。它绝不暴露请求值或传输层异常,并原样透传 Core 的响应。 + +| HTTP 状态 | 代码 | 含义 | `type` | +| --- | --- | --- | --- | +| 401 | `console_sign_in_required` | 控制台会话缺失或已过期 | `invalid_request_error` | +| 403 | `console_origin_rejected` | Host、Origin 或 Fetch Metadata 检查失败 | `invalid_request_error` | +| 400 | `console_request_invalid` | 路径、方法或升级不安全 | `invalid_request_error` | +| 502 | `core_unreachable` | 无法连接 Core,或 Core 返回了重定向 | `server_error` | + +这些错误的 `param` 为 null,且没有 `details`。因此,Core 的 `401 invalid_admin_key` 仍可与缺少 console sign-in 区分开来。Console sign-in 路由保留其 `{"error":"…"}` 错误([sign-in](../../../docs/zh/web/console-server.md#sign-in))。 + +## 沙箱提供商验证 {#sandbox-provider-verification} + +当 `POST` 或 `PUT /core/v1/sandbox/deployment`([sandbox deployment](sandbox-deployment.md#reset))的提供商像 E2B 一样验证凭据或配置时,请求会因以下固定错误而失败。这些错误均不会返回提供商文本、模板名称、密钥或资源数量。 + +| HTTP | 代码 | 含义 | `param` | +| --- | --- | --- | --- | +| 400 | `sandbox_credential_invalid` | 提供商拒绝了候选凭据 | `credential` | +| 400 | `sandbox_configuration_invalid` | 候选配置(例如 E2B 模板构建)未同时满足就绪和不可变要求,或者与资源不匹配 | `configuration` | +| 409 | `sandbox_credential_ownership` | 候选凭据无法管理保留的部署;更换账户前必须重置 | `credential` | +| 503 | `sandbox_verification_unconfirmed` | 无法确认验证结果、回执结算结果或凭据隔离状态 | null | + +每次写入部署时,类型化客户端都会将上述代码及其他 `sandbox_*` 部署代码的消息替换为固定的本地文本。`details` 中仅保留 `current_generation`、`allocations`、`pending`、`min` 和 `max`,并且仅当 status、code 和 param 与上表或下方 `invalid_sandbox_configuration` 各行完全匹配时,才保留 `param`。`409 sandbox_configuration_error` 会转换为有关公开 URL 的固定指引,并将 `param` 设为 null,即使对于未提供密钥的 `PUT` 也是如此。其他任何错误都会转换为 `sandbox_configuration_unconfirmed` 且不会重新发送,因为拒绝响应可能会回显密钥。 + +## 操作验证 {#operation-validation} + +每个代码均返回 HTTP 400,并带有 `type: "invalid_request_error"`。如果模型提供商配置包缺失、格式错误或类型错误,系统会在检查任何字段之前返回 `invalid_model_provider`。JSON 正文解析保留其自身错误;其他格式错误的管理请求返回 `invalid_request`。 + +| 代码 | Param | 详细信息 | 含义 | +| --- | --- | --- | --- | +| `invalid_name` | `name` | `max_length`:Projects 和节点为 128,Project 键为 80 | 名称未通过相应资源的验证器 | +| `invalid_node_capacity` | `max_active` 或 `max_retained` | `min`:1,`max`:1000000 | 容量无效;保留容量还必须至少等于活动容量 | +| `invalid_model_provider` | null | 省略 | 必须提供完整的模型提供商配置包 | +| `model_provider_base_url_invalid` | `base_url` | 省略 | 必须使用 HTTPS,且不得包含凭据、查询或片段 | +| `model_provider_protocol_unsupported` | `protocol` | `harness` 和 `allowed_protocols`,来自该构建的适配器目录 | 协议未知,或所选 Harness 不支持该协议 | +| `model_provider_api_key_invalid` | `api_key` | `max_length`:16384 | 密钥为空、过长或包含禁止字符 | +| `model_provider_token_limits_invalid` | `context_window` 或 `max_output_tokens` | 省略 | 限制无效,或 Harness 要求的正数限制缺失 | +| `model_configuration_model_invalid` | `model` | 省略 | 部署默认配置的 model 不是非空模型标识符 | +| `harness_config_invalid` | `harness_config` | 省略 | 部署默认配置的原生参数不受支持或无效 | +| `invalid_sandbox_configuration` | `resources.cpus` | `min`:1,`max`:255 | CPU 数量超出支持范围 | +| `invalid_sandbox_configuration` | `resources.memory_mib` | `min`:512,`max`:1048576 | 内存超出支持范围 | +| `invalid_sandbox_configuration` | `resources.root_disk_mib` 或 `resources.environment_disk_mib` | `min`:microsandbox 为 1024;Docker 和 E2B 的 `min`:0,`max`:0 | 磁盘容量缺失或提供商不支持 | +| `invalid_sandbox_configuration` | `runtime` | 省略 | Runtime release 缺失、可变、无效或 E2B 不允许 | + +这些边界是验证常量,绝不是提交的值。节点名称按字节数限制;Project 名称和键名称按去除首尾空白后的 Unicode 字符数限制,且不得包含控制字符。系统仅按以下顺序报告第一个失败项:模型提供商 URL、协议、密钥、常规限制、Harness 协议,然后是 Harness 的必需限制;沙箱资源依次为 CPU、内存、磁盘,然后是 Runtime。`model_provider` 对象内的模型提供商字段错误仍以该对象的相应字段作为 `param`。未知的沙箱提供商返回一个不含这些字段的错误。 + +## 诊断失败类别 {#diagnostic-failure-categories} + +[Session and Turn diagnostics reads](session-diagnostics.md) 会在成功的 200 快照内返回以下类别,而不是以错误封装的形式返回。公开 `/v1` 的 Turn 错误保持不变。除非表格另有说明,否则 `params` 为 `{}`。 + +| 代码 | 存储原因或安全含义 | +| --- | --- | +| `harness_error` | `engine_failed`,且没有原生分类 | +| `authentication_error` | 原生提供商拒绝了身份验证 | +| `rate_limit_exceeded` | 原生速率限制分类 | +| `usage_limit_exceeded` | 原生计费或使用量限制分类 | +| `server_overloaded` | 原生过载分类 | +| `server_error` | 原生服务器故障分类 | +| `invalid_request` | 原生请求被拒绝 | +| `resource_not_found` | 未找到原生资源或模型 | +| `request_timeout` | 保留的中性超时类别;当前没有适配器生成该类别 | +| `context_length_exceeded` | 原生上下文限制分类 | +| `cyber_policy` | 原生网络安全策略拒绝 | +| `connection_failed` | 原生连接故障;params 包含 `http_status`,其值为 100–599 范围内的整数或 null | +| `model_provider_required` | 缺少已冻结的模型提供商 | +| `runtime_unavailable` | `execution_device_unavailable`、`execution_unavailable` | +| `runtime_disconnected` | `device_disconnected`、`event_stream_incomplete` | +| `runtime_preparation_failed` | `preparation_start_failed`、`preparation_interrupted` | +| `execution_interrupted` | Core 执行被中断 | +| `delivery_unconfirmed` | `delivery_unknown`、`input_outcome_unknown`、`cancel_unconfirmed`、`cancel_outcome_unavailable`、`function_result_unconfirmed` | +| `input_rejected` | `invalid_input`、`input_not_applied`、`message_input_unsupported`,以及确切的 steering 结果 `input_invalid_input`、`input_run_inactive`、`input_input_conflict`、`input_input_limit`、`input_unsupported`、`input_rejected`、`input_not_ready`、`input_busy` | +| `executor_protocol_error` | `invalid_executor_result`、`interaction_not_supported`、`execution_state_unavailable`、`execution_state_changed`、`function_call_invalid`、`function_result_invalid` | +| `core_storage_failed` | `event_persistence_failed`、`artifact_capture_failed` | +| `internal_error` | 结果未知或格式错误;不返回原始值 | +| `environment_connection_timeout` | 初始输入连接截止时间已过 | +| `environment_unavailable` | 初始输入所需环境不可用 | +| `environment_provisioning_failed` | 托管预置失败;params 包含来自已清理回执的可空 `step`、`index`、`exit_code` | + +数据库故障属于错误,绝不会产生空快照或健康快照。绝不会解析预置原因或原生消息以确定类别或参数。 + +原生类别仅适用于 outcome 中含有 `error_code: engine_failed` 的失败 Turn。Core 仅接受列出的 `engine_error_code` 值;如果该值未知、格式错误或缺失,则仍归为 `harness_error`。只有 `connection_failed` 使用 `engine_http_status`。绝不会根据嵌套元数据或提供商文本来划分失败类别。Core 存储、流不完整和取消故障具有更高优先级;已取消或已完成的 Turn 没有故障。[Native error classification](../../../docs/zh/runtime-protocol.md#native-failure-classification) 列出了各适配器会报告哪些类别。 diff --git a/contracts/agents-api/zh/core-metrics.md b/contracts/agents-api/zh/core-metrics.md new file mode 100644 index 00000000..7503b861 --- /dev/null +++ b/contracts/agents-api/zh/core-metrics.md @@ -0,0 +1,67 @@ +--- +title: "Core 运行指标" +source: contracts/agents-api/core-metrics.md +source_hash: 08ac6802f0da2eb5138dc7706a995754a69e7c864e034cf412f75dcc4950c679 +--- + +`GET /core/v1/metrics?range=1h|6h|24h|7d` 报告 Core 自身的健康状况:进程、执行队列与槽位、PostgreSQL 和后台任务。它要求 Core 密钥([Core 管理 API](admin-api.md))。 + +`range` 是唯一参数,最多发送一次,默认为 `1h`。空值、重复或不支持的值以及任何其他参数均返回 400 `invalid_request`。Core 无法读取指标时,路由返回 503 `core_metrics_unavailable`。仅部分测量失败时,响应仍为 `200`,`service.status` 为 `degraded`,每个缺失值为 null。响应不会包含数据库或原生错误文本、凭据、响应体、资源 ID 或租户标签。 + +## 时间与缺失数据 {#time-and-missing-data} + +响应中的 `range` 包含 UTC RFC 3339 格式的 `start` 和 `end`,以及 `resolution_seconds`。`end` 为最近的完整桶边界;区间为 `[start, end)`,因此不包含当前未完成的桶。 + +| 范围 | 桶大小 | 桶数 | +| --- | --- | --- | +| 1h | 60 秒 | 60 | +| 6h | 300 秒 | 72 | +| 24h | 900 秒 | 96 | +| 7d | 7200 秒 | 84 | + +- 执行槽位、已连接 daemon、连接池、Go 堆和 goroutine 在请求到达时读取。进程 CPU、RSS 与限制、队列数量和数据库大小来自 Core 每 30 秒采集的样本;超过 60 秒的样本不会作为当前值报告。 +- 每个序列桶报告其中观察到的最高值,而非每一个中间峰值。缺失观测和进程启动时不完整的首个桶为 null。 +- 样本和拒绝计数在内存中保留七天,另加两小时用于桶对齐。重启会丢失它们;Core 不回填。Turn 历史来自 PostgreSQL,重启后仍保留。 +- 如果区间开始早于该进程开始观察的时间,`execution.unavailable` 为 null;完整观察且没有拒绝的区间为零。 +- 空队列的数量为零,最老年龄为 null。没有已开始 Turn 或成功 ping 样本时,百分位数为 null,而非零延迟。周期 ping 的百分位数(p50、p95)采用线性插值;请求不会触发 ping。 + +## 字段 {#fields} + +响应包含 `object: "core.metrics"`、`range`、`service`、`execution`、`database`、`jobs` 和 `process`。所有数值和 `service.execution_owner` 均可为 null;每个 `series` 始终列出范围内的所有完整桶。 + +| 字段 | 含义 | +| --- | --- | +| `service.status` | 通常为 `running`;测量或任务失败、最新样本缺失或过期、执行所有权未知,或 Core 有执行槽位却未持有执行租约时为 `degraded`。沙箱重置由[部署](sandbox-deployment.md)报告,不在此处报告 | +| `service.revision` | 构建时注入的完整源代码提交;未注入时为 null | +| `service.started_at` | 进程初始化时间 | +| `service.execution_owner` | 此进程是否持有执行 worker 的数据库租约 | +| `execution.slots_in_use`, `execution.slots_total` | 执行 worker 的活动 Session 预留数量及容量:[`core.execution_concurrency`](../../../docs/zh/configuration.md#settings),默认为 4。Environment 输入、Turn 和文件工作共享槽位;不统计原生 Harness 子进程。没有 worker 时两者均为 0 | +| `execution.queued_turns`, `execution.in_progress_turns` | 处于相应状态的根 Turn,包括已删除 Session 的 Turn。不统计 Subagent Turn 和为准备中 Environment 预留的输入 | +| `execution.waiting_for_daemon` | Session 设备未连接的排队 Turn;无网关时为 null | +| `execution.oldest_queued_seconds` | 最早排队 Turn 自 `created_at` 起的年龄 | +| `execution.connected_daemons` | 连接到 Core 网关的 Runtime daemon 数量;无网关时为 null | +| `execution.queue_wait_ms` | 区间内开始的 Turn 的 `started_at - created_at` 的 p50 和 p95,使用 PostgreSQL `percentile_cont` | +| `execution.interrupted` | 错误代码为 `execution_interrupted` 且 `completed_at` 在区间内的失败 Turn | +| `execution.unavailable` | 使用错误代码 `execution_unavailable` 发送的 HTTP 响应,每个计一次。不统计其他 503 代码或流开始后的错误 | +| `execution.series` | 每桶:`queued`、`in_progress` 和 `queue_wait_p95_ms` | +| `database.ping_ms` | 周期连接池 ping 的 p50 和 p95,包括获取连接的时间 | +| `database.pool` | 连接池的 `in_use`、`idle` 和 `max` 连接数量 | +| `database.size_bytes` | `pg_database_size(current_database())`,不是主机磁盘使用量 | +| `database.series` | 每桶:`ping_p95_ms` 和 `pool_in_use` | +| `process.memory_bytes`, `process.goroutines` | Go `runtime.MemStats.Alloc`(已分配堆,不是 RSS)和 `runtime.NumGoroutine()` | +| `process.cpu_cores` | 进程用户态与系统态 CPU 时间增量除以采样经过时间(Linux `getrusage(RUSAGE_SELF)`),不包括子进程。首个区间为 null;计数器缺失或重置、区间非正或间隔超过 60 秒时重建基线 | +| `process.rss_bytes` | Linux `/proc/self/status` 的 `VmRSS`,单位为字节 | +| `process.cpu_limit_cores` | 进程 cgroup v2 的 `cpu.max` 配额除以周期;配额为 `max` 或 cgroup 无配额接口时为 `GOMAXPROCS` | +| `process.memory_limit_bytes` | 进程 cgroup 的 `memory.max`;为 `max` 时返回 null | +| `process.series` | 每桶:`cpu_cores` 和 `rss_bytes` | + +Core 解析自身的 cgroup,包括嵌套与子树挂载,并报告该 cgroup 的限制,而非主机或祖先的限制。无法读取或格式错误的值为 null。非 Linux 构建报告 null CPU、RSS 和内存限制,CPU 限制为 `GOMAXPROCS`。仅缺失进程测量不会使服务变为 `degraded`。 + +## 后台任务 {#background-jobs} + +`jobs` 列出 `scheduler`、`runtime_sampler`、`history_cleanup` 和 `audit_cleanup`。每项包含 `status`(首次运行前为 `unknown`,之后为 `ok`、`failing`,循环禁用或结束时为 `stopped`)、`last_run_at`(上次一轮结束时间)、`processed` 和 `failed`,后两者描述上次一轮。 + +- scheduler 的 `processed` 统计上次轮询选中的 Turn 和 Environment 工作。 +- Runtime sampler 的 `processed` 和 `failed` 统计上次扫描成功观察和未能观察的目标。 +- 清理任务的 `processed` 统计删除的行数。 +- scheduler 和清理任务失败时,`processed` 为 null,`failed` 为 1;`failed` 不估算丢失行数或失败 Turn 数量。 diff --git a/contracts/agents-api/zh/environment-executor-credentials.md b/contracts/agents-api/zh/environment-executor-credentials.md new file mode 100644 index 00000000..d78e8c42 --- /dev/null +++ b/contracts/agents-api/zh/environment-executor-credentials.md @@ -0,0 +1,100 @@ +--- +title: "Environment 执行器凭证" +source: contracts/agents-api/environment-executor-credentials.md +source_hash: 6c1db305481f9ab2a51bdd0c88243feaab48348b71f5f3ebbc8f00b17744f412 +--- + +执行器凭证允许 `oac-daemon` 为一个 `self_hosted` Environment 注册并连接。它只授权该 Environment 的私有 daemon 传输(`/api/v1/agent-daemon/*`),不授权 `/v1`、`/core/v1`、sandbox node 注册或 Project 资源。Project 的 principal 是其执行 principal。Core 只保存密钥摘要。 + +凭证有两个来源: + +- **安装授权。** `self_hosted` Session 返回安装命令。安装器使用命令中的短期 grant 领取一个凭证,不需要 Web 或 Core key。[自托管指南](../../../docs/zh/getting-started/self-hosted.md)介绍操作步骤。 +- **Core-key 路由。** 管理员通过 Web 或 `/core/v1` 签发、轮换和撤销凭证。 + +Core 不创建、停止或回收机器。断开连接、撤销凭证或删除 Session 都不能证明所有原生进程已停止;机器所有者负责停止并清理自己的计算资源。 + +## 安装授权 {#installation-grant} + +`self_hosted` Session 的创建、查询和更新响应包含 `x_agents_core.installation`,Session 列表不包含。Web 使用 Core key 通过 `GET /core/v1/projects/{project_id}/environments/{environment_id}/installation` 读取同一对象,原样显示命令。 + +| 字段 | 含义 | +| --- | --- | +| `status` | `available`;当 Core 没有匹配的原生安装器时为 `unavailable`,此时 `message` 说明原因 | +| `version` | 命令安装的 Core 构建版本 | +| `expires_at` | grant 到期的 Unix 时间,为响应生成后 30 分钟 | +| `commands.posix`, `commands.powershell` | Linux/macOS 和 Windows PowerShell 的安装命令 | + +grant 绑定 Environment、Session 创建者的 principal 和 Core 构建版本。在到期、Session 被删除、Project 被归档或 Core 运行另一构建版本时失效。重新读取 Session 会获得新 grant。Core 不存储 grant:每个响应重新签名,存储的事件从不包含它。将命令视为临时秘密:它能领取凭证,但不能执行工作或读取文件。 + +安装器生成密钥,在领取前将其私密保存到安装目录的 `daemon/executor-credential.json`。Core 将摘要保存在 key ID 等于 Environment ID 的记录中。响应丢失后可以安全重试,但必须提交同一个密钥。grant 不替换或恢复凭证。如果 Environment 已有不同、已轮换或已撤销的凭证,领取返回 409 `executor_credential_exists`。 + +安装器调用 Core 的以下机器路由: + +| 路由 | 授权 | 用途 | +| --- | --- | --- | +| `GET /api/v1/agent-daemon/install/{version}/bootstrap.sh`, `bootstrap.ps1` | 无 | 平台 bootstrap 脚本 | +| `GET /api/v1/agent-daemon/install/{version}/{os}-{arch}.sha256`, `{os}-{arch}.tar.gz` | 无 | 安装器校验和与归档;Core 提供本地副本,或以 307 重定向到目录中的版本化发布 URL | +| `POST /api/v1/agent-daemon/installation` | Grant | 固定绑定:`version`、`protocol_version`、`environment_id`、`remote_url`、`workspace_directory`、`harness` | +| `POST /api/v1/agent-daemon/installation/claim` | Grant | `{"executor_token":"SECRET"}`;204 | + +无效或过期的 grant 返回 401 `installation_authorization_invalid`。没有匹配安装器时,grant 路由返回 503 `installation_unavailable`。Core 用安装的 [`secrets/credential.key`](../../../docs/zh/configuration.md#installation-directory) 签名每个 grant;未配置 key 时,上述 Session 响应、Core-key 查询和 grant 路由返回 503 `credential_storage_unavailable`。格式错误的密钥返回 400。产物路由不携带凭证,grant 只发送给 Core,不发送给产物主机。 + +## Core-key 路由 {#core-key-routes} + +所有路由位于 `/core/v1/projects/{project_id}/environments/{environment_id}/executor-credentials`,要求 Core key。它们仅适用于该 Project 内 Session 仍存在(未删除)的 `self_hosted` Environment;其他 Project、Environment 类型、缺失 Environment 或已删除 Session 均返回 404。Project API key 无权调用。 + +| 操作 | 请求 | 结果 | +| --- | --- | --- | +| 列表 | `GET …/executor-credentials` | `data` 中的凭证元数据,以及必需的 `connection` 对象 | +| 签发或轮换 | `POST …/executor-credentials`,内容为 `{"key_id":"UUID","rotate":false}` | 201 凭证文件,只返回一次 | +| 撤销 | `DELETE …/executor-credentials/{key_id}` | 204 | + +列表只包含限定于此 Environment 的凭证元数据,按最早创建优先排序。有效凭证的 `revoked_at` 为 null。列表不包含密钥。 + +`key_id` 是请求前选定并保留的规范非零 UUID。`rotate` 可选,默认 false。201 响应使用 daemon 凭证文件格式: + +```json +{"key_id":"UUID","environment_id":"ENVIRONMENT_UUID","executor_token":"ONE_TIME_SECRET"} +``` + +响应使用 `Cache-Control: no-store`。直接保存到自己拥有、权限为 0600 的文件,不放入 shell 参数、日志、工作区或源码。 + +写入有两种 409 冲突。`executor_credential_exists`:签发时 `key_id` 已存在且没有 `rotate:true`,即使已撤销也一样。`project_archived`:Project 已归档,不能签发或轮换凭证;列表和撤销仍可用,因为必须始终能够撤销。 + +签发或轮换按以下顺序检查,返回第一个失败:请求体(400);目标 Environment(404);已归档 Project(409 `project_archived`);key 本身(未设置 `rotate` 时为 409 `executor_credential_exists`,轮换从未签发的 `key_id` 时为 404)。 + +轮换替换限定于该 Environment 的现有 key 密钥,保留 Environment,立即使旧密钥失效,并恢复已撤销的 key。撤销是幂等操作,每次返回 204,禁止后续注册和连接。 + +超时等结果不确定的情况下,不要自动重试。先列出凭证,再轮换同一 `key_id`(已签发但密钥丢失),或重新签发(尚未签发)。 + +签发、轮换和撤销在写入的同一事务中记录管理员审计项:`resource_type:"executor_credential"`,key ID 为 `resource_id`,action 为 `issue`、`rotate` 或 `revoke`。审计不包含密钥。 + +### 应急命令 {#break-glass-command} + +Core API 不可用时,`oac-core-environment-key` 直接在数据库中签发、轮换或撤销凭证。它读取 Core 数据库设置(`OAC_DATABASE_URL`,以及配置时的 `OAC_DATABASE_PASSWORD_FILE`),并需要 Project 的执行 principal:`--tenant`(`projects` 表中 Project 的 tenant UUID)、`--organization core`、`--project proj_`、`--subject-kind service_account`、`--subject-id project:` 和 `--key-id`。没有其他标志时签发新凭证,`--environment` 限定到一个 Environment。`--rotate` 替换现有凭证的密钥,包括已撤销凭证;`--revoke` 撤销而不输出密钥。轮换和撤销保留存储的限制,拒绝 `--environment`,两个标志互斥。签发和轮换只在标准输出打印一次凭证文件,应重定向到新建的 0600 文件。命令绕过 Core API,跳过 Project 归档检查且不写审计项,因此 Core 运行时应使用 Core-key 路由。没有 Environment 限制的凭证不能通过上述路由管理。 + +## 连接状态 {#connection-status} + +列表必需的 `connection` 对象包含 `status`(`never_enrolled`、`connected` 或 `disconnected`)、`bound_key_id`、`enrolled_at` 和 `last_seen_at`。注册前,三个绑定字段均为 null。注册后,绑定 key 和注册时间描述已有设备;`last_seen_at` 为 null 表示尚无经过认证的心跳。签发另一 key 不改变绑定。轮换或撤销可能使绑定断开,但历史仍可见。过期 Environment 仍按现有列表规则可读,但不能拥有当前执行器权限。 + +Connected 表示 Environment 已连接,其设备和执行器 key 仍有当前 Core 权限,且进程内 gateway 有一个用当前 key 认证的开放 peer。Core 观察 peer 后重新检查权限。旧 key 的存活 socket、设备时间戳或看似就绪的 Environment 均不充分;没有 gateway 时 Core 不返回 connected。这些事实是观测结果,不预留连接,也不保证原生执行或模型就绪。`last_seen_at` 可能滞后一个心跳间隔。 + +列表元数据和绑定事实使用同一个只读数据库快照。快照在实时权限检查前结束,因此已提交的轮换或撤销不会被快照隔离隐藏。已知权限丢失映射为 disconnected;观测和存储失败仍返回错误。设备 ID 和凭证摘要为内部数据,不序列化。公开 `/v1` Environment 形状不变。 + +### 私有连接确认 {#private-connection-confirmation} + +`GET /api/v1/agent-daemon/connection?environment_id=UUID` 使用执行器 bearer,直接发往 Core(反向代理将 `/api/v1` 路由到 Core,console 不提供该接口)。它属于私有 daemon 传输,不属于公开 Agents API。它只读取现有授权和绑定,不注册设备、启动执行或修改资源。no-store 响应只包含请求的 `environment_id` 和 `status`(`connected` 或 `disconnected`)。Connected 要求 Environment 观测、确切的 Session 和设备绑定、当前执行器权限,以及用相同凭证认证的存活 gateway socket。过期观测或携带已轮换 key 的 socket 不能确认连接。 + +无效、已撤销、其他范围或已删除 Session 的权限返回 401;已绑定 Environment 使用不同 key 返回 409。响应不暴露实际绑定或数据库诊断。 + +安装器从返回的 `remote_url` 推导此路由,不跟随重定向。启动 daemon 后,每秒轮询一次,最多 45 秒。401 或 409 立即失败。超时后打印 daemon 的 `connect.log` 路径,请求重新执行同一命令;daemon 继续重连,安装、凭证和历史保留。连接确认只证明认证成功,不证明模型访问、Harness 能力或执行完成。 + +## 已撤销或轮换的凭证 {#revoked-or-rotated-credential} + +Core 永久拒绝 daemon 时(注册 401/409、永久 WebSocket 拒绝,或 daemon 版本来自其他 Core 分发),daemon 只打印一次原因,停止发请求直到被停止;随后成功退出,避免按退出重启的 supervisor 循环。再次启动时只尝试一次注册,然后再次停驻。临时故障保持正常重连行为,不重放执行。 + +机器只能使用绑定的 `key_id` 重连,轮换新密钥后替换配置路径的凭证文件;[自托管指南](../../../docs/zh/getting-started/self-hosted.md#rotate-or-revoke)提供步骤。新 `key_id` 无法重新连接已绑定的 Environment:签发成功,但用它注册返回 409。轮换不重装 Harness、不改变工作区、不替换原生历史;不要为恢复凭证创建新 Session 历史。 + +## 模型服务 {#model-provider} + +`self_hosted` Session 携带自己的模型服务,部署默认值不适用。[模型执行](model-execution.md)负责交付规则。保存的 Agent 的服务 key 会交给 Project 中用该 Agent 创建的每个 `self_hosted` Session 的执行器,因此任何能在该 Project 创建 `self_hosted` Session 并运行执行器的人都能读取它。 diff --git a/contracts/agents-api/zh/environment-files.md b/contracts/agents-api/zh/environment-files.md new file mode 100644 index 00000000..1977f2b6 --- /dev/null +++ b/contracts/agents-api/zh/environment-files.md @@ -0,0 +1,116 @@ +--- +title: "Environment 文件与 Artifact" +source: contracts/agents-api/environment-files.md +source_hash: 1b58aa02aaccddb9675ef41ebfe2506a6fba0bb12139efb67e0da378d879aee7 +--- + +Session 工作区保存由 agent 及其工具修改的实时文件。`/agents/environments/{environment_id}/files` 列出一个工作区目录,并在其中创建文件。Turn 完成时,Core 将工作区 `outputs/` 目录中的文件复制为不可变 Artifact,通过 `/agents/sessions/{session_id}/artifacts` 读取。Artifact 的生命周期长于 Environment;工作区文件则不是。 + +路由遵循 [upstream.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream.json) 中固定版本 SDK:[Environment files 资源](https://github.com/openai/openai-python/blob/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/resources/beta/agents/environments/files.py)、[列表参数](https://github.com/openai/openai-python/blob/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/types/beta/agents/environments/file_list_params.py)、[EnvironmentFile](https://github.com/openai/openai-python/blob/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/types/beta/agents/environments/environment_file.py) 和 [TokenPage](https://github.com/openai/openai-python/blob/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/pagination.py)。要求 `OpenAI-Beta: agents=v1` 头。 + +## 文件适用范围 {#where-files-work} + +- Environment 文件适用于 `openai_hosted` 和 `self_hosted` Environment。`none` Environment 返回 503 `execution_unavailable`。 +- 公开路径从 `/workspace` 开始,代表 Environment 在机器上实际位置的工作区目录。 +- 首先在调用方 Project 查找 Environment;不存在或属于其他范围的 ID 返回 404。 +- 仍为 `pending` 的 `openai_hosted` Environment,两种操作均返回 400 `the hosted environment is still provisioning; wait until it is connected before accessing files`。此检查在请求验证之后、任何源 File 查询之前执行。其他状态继续处理,Runtime 不可达时返回 503。 +- 列表和写入不启动 Turn,也不向模型发送输入。 + +## 列出文件 {#list-files} + +`GET /agents/environments/{environment_id}/files` 列出单个目录直接包含的普通文件,不递归。目录、符号链接和其他条目被排除。 + +| 参数 | 规则 | +| --- | --- | +| `path` | `/workspace` 本身或其下规范形式的绝对目录;默认 `/workspace` | +| `limit` | 1–100;默认 20 | +| `order` | `desc`(默认)或 `asc`,按路径字节序 | +| `page` | 上一页的 `next` token,`path`、`order`、`limit` 必须相同 | + +响应为 `{"object": "page", "data": [...], "next": …, "has_more": …}`。最后一页的 `next` 为 null,只有设置 `next` 时 `has_more` 才为 true。每个文件包含 `environment_id`、`object: "agent.environment.file"`、绝对 `path` 和 `size_bytes`。 + +- `path` 不存在、指向普通文件或经过符号链接时返回空页。不跟随链接。 +- 每页重新读取目录,不提供快照。token 签发后目录普通文件(名称或大小)或请求参数改变时,token 被拒绝。名称和大小未变不证明内容未变。 +- 目录中任何类型条目合计超过 1,024 个时返回 503,不返回部分页。权限错误、工作区根缺失和传输失败也返回 503。 +- daemon 无本地工作区绑定时,改由 Claude Code 适配器回答读取:路径缺失返回 404,普通文件或符号链接返回 503。 + +查询错误的 type 和 code 均为 `invalid_request_error`,除另有说明外 `param` 为 null: + +| 情况 | 消息 | +| --- | --- | +| `path` 为相对路径、位于 `/workspace` 外、超过 4,096 字节、非 UTF-8,或包含反斜杠、NUL、CR、LF | `path must be an absolute directory inside /workspace` | +| `path` 非规范形式:末尾或重复 `/`、`.` 或 `..` | `path must identify a non-reserved directory inside /workspace` | +| token 格式错误、属于其他请求,或对应列表改变 | `Invalid file page token for this request` | +| `limit` 超出 1–100 | `limit must be between 1 and 100`;格式错误整数及无效 `order` 使用共享 [Beta 列表错误](wire-semantics.md#lists) | +| 重复 `path`、`limit`、`order` 或 `page` | 共享重复字段错误([列表规则](wire-semantics.md)) | + +未知查询键忽略。每个键显式空值均无效。`%GG` 或 `;` 分隔符等格式错误查询编码返回 400 `invalid_request`。 + +## 创建文件 {#create-a-file} + +`POST /agents/environments/{environment_id}/files` 接受任一形式: + +```json +{"type": "inline", "data": "", "path": "/workspace/data/input.csv"} +{"type": "file_id", "file_id": "file-…", "path": "/workspace/data/input.csv"} +``` + +空 inline `data` 有效,会创建空文件。返回 201 和四个 EnvironmentFile 字段。`file_id` 指定同一 Project 的 [File](source-files.md);Core 联系 Runtime 之前读取字节。 + +| 情况 | 结果 | +| --- | --- | +| 父目录不存在 | 创建为 mode 0700;文件为 mode 0600 | +| 父目录是指向工作区内目录的符号链接 | 跟随链接,在目标处创建文件 | +| 目标已存在为文件、符号链接或其他非目录条目 | 400 `environment.files paths must not traverse symlinks or overwrite existing files`。无变化 | +| 目标为已有目录 | 400 `file path conflicts with an existing environment file` | +| 父组件为普通文件,或符号链接指向工作区外 | 400 `invalid_request`,`Invalid resource identifier or request limits.` | +| `inline` 数据解码后超过 5 MiB | 在任何 Runtime 工作前返回 400 `environment.files[0].data exceeds the 5 MiB decoded limit`。恰好 5 MiB 接受 | +| `file_id` File 超过 50 MiB | 413 `request_too_large` | +| 请求体大于 50 MiB 的 Base64 形式加 16 KiB | 413 `request_too_large` | +| `path` 为相对路径、根本身、位于 `/workspace` 外、超过 4,096 字节、非 UTF-8,或包含反斜杠、NUL、CR、LF | 400 `environment.files[0].path must be an absolute POSIX path inside /workspace` | +| `path` 含空、`.` 或 `..` 组件 | 400 `environment.files[0].path cannot contain empty, . or .. path components` | +| 未知顶层字段 | 400 `Unknown parameter: ''.`,`param` 为字段名。名称超过 256 字节或含不可打印字符时为 `Unknown parameter.`,`param` 为 null。仅报告正文首个未知字段 | +| 必需字段缺失或 null、使用另一形式字段、未知 `type` 或无效 Base64 | 400 `invalid_request` | +| 不存在或属于其他范围的 `file_id` | 404 `not_found_error` | + +除表格明确指定代码外,400 错误的 type 和 code 为 `invalid_request_error`,`param` 为 null。已有目标不被替换:Runtime 写临时文件,通过硬链接发布,目标存在则失败。后续工具写入仍可修改所创建文件。 + +### 写入顺序与不确定结果 {#write-ordering-and-uncertain-outcomes} + +- 发送任何字节之前,Core 在 Session 锁下记录写入。输入待处理、Turn 运行或较早写入未结算时,新写入返回 409 `turn_conflict`。未结算写入也使 Session 新消息返回 409。 +- Runtime 在创建任何内容前根据摘要检查完整正文,因此不完整输入不创建内容。后续失败的写入可能留下新建空父目录。 +- 仅 Runtime 的确定回执将写入结算为已提交或已拒绝。被拒绝写入不改变内容并释放 Session。连接断开、请求超时或无回执时,返回 503,写入持续未结算,Core 重启后仍如此。Core 不重发,也无自动恢复,因此 Session 不再接受写入或消息。读取仍可用。 +- 源 File 字节读取后,删除该 File 不影响副本。 + +## Artifact {#artifacts} + +### 捕获 {#capture} + +`openai_hosted` 或 `self_hosted` Environment 的 Turn 完成时,Core 复制 `/workspace/outputs/` 下所有普通文件,在完成 Turn 的同一事务发布副本。Artifact 的 `path` 为文件绝对工作区路径,例如 `/workspace/outputs/report.md`。失败与取消的 Turn 不发布内容。没有 `outputs/` 目录时无内容可捕获。 + +- `outputs/` 下任何符号链接根据其类型跳过:不跟随、打开或解析,不成为 Artifact。其余文件仍被捕获。 +- 同一 Session 后续 Turn 仅在该路径没有剩余 Artifact,或字节(SHA-256)与该路径最新剩余 Artifact 不同时发布路径。最新按生产 Turn 顺序判断。未变路径保留已有 Artifact 与 ID。已发布 Artifact 不修改。 +- 以下情况捕获失败,Turn 以 `artifact_capture_failed` 失败(请求取消时以 `cancelled` 结束):`outputs` 不是目录(包括指向目录的符号链接)、条目是 FIFO、socket 或设备、复制过程文件大小或修改时间改变,或超限:4,096 个条目、目录深度 64、路径 4,096 字节、单文件 200 MiB、单 Turn 500 MiB。 + +### 读取与删除 Artifact {#read-and-delete-artifacts} + +| 操作 | 行为 | +| --- | --- | +| `GET /agents/sessions/{session_id}/artifacts` | 列出 Session 的 Artifact | +| `GET /agents/sessions/{session_id}/artifacts/{artifact_id}` | 返回 `id`、`object: "agent.session.artifact"`、`session_id`、`turn_id`、`environment_id`、`path`、`size_bytes` 和 `created_at`(发布时间,Unix 秒) | +| `GET /agents/sessions/{session_id}/artifacts/{artifact_id}/content` | 以 `application/octet-stream` 流式返回字节,以文件基本名作为附件文件名 | +| `DELETE /agents/sessions/{session_id}/artifacts/{artifact_id}` | 返回 `{"id": …, "object": "agent.session.artifact.deleted", "deleted": true}` | + +- 无论 Environment 是否仍存在(包括过期后),均可读取。 +- 删除 Artifact 不改变工作区文件。已进行的内容读取可以完成;后续读取返回 404。移除工作区文件不改变其 Artifact。 +- 删除 Session 会删除其 Artifact。 + +列表参数: + +| 参数 | 规则 | +| --- | --- | +| `order` | `desc`(默认)或 `asc`,按发布时间再按 ID | +| `after` | 此 Session 的 Artifact ID | +| `environment_id` | 仅返回该 Environment 产生的 Artifact。格式错误或未知 ID 返回空页;空值不筛选 | + +`limit` 和游标错误遵循共享[列表规则](wire-semantics.md#lists)。响应为 `{"object": "list", "data": [...], "first_id", "last_id", "has_more"}`;空页 ID 为 null。先查询 Session,因此无论查询参数如何,不存在或属于其他范围的 Session 均返回 404。 diff --git a/contracts/agents-api/zh/environments.md b/contracts/agents-api/zh/environments.md new file mode 100644 index 00000000..221d34c2 --- /dev/null +++ b/contracts/agents-api/zh/environments.md @@ -0,0 +1,391 @@ +--- +title: "环境与模板" +source: contracts/agents-api/environments.md +source_hash: a93a477f3de39b2206702995821282404c29ee997ef6b782180d4972bada43f7 +--- + +Environment 是 Session 的执行资源,包括 Harness 运行所在的机器、工作区以及已完成准备的能力。Session 通过其 `environment` 配置创建 Environment;不存在独立的 create 调用。Environment Template 是 Session 创建时解析的可复用准备配置。本契约涵盖这两类资源、两种放置方式、输入接纳、能力准备、Skills、Plugins 和 MCP 连接来源。 + +相关职责归属: + +- [Environment files](environment-files.md):实时工作区上的 Files API。 +- [Executor credentials](environment-executor-credentials.md):`self_hosted` 机器的注册、安装授权和连接状态。 +- [Sandbox deployment](sandbox-deployment.md):托管 `openai_hosted` Environment 的 Sandbox Provider(E2B、Docker 或 microsandbox)。 +- [Core–Runtime protocol](../../../docs/zh/runtime-protocol.md):`runtime_prepare` 传输及所有其他线上消息。 +- [Runtime and outer isolation](../../../docs/zh/concepts.md#runtime-and-outer-isolation):daemon 使用启动用户的权限运行工具;隔离由外层 Environment 提供。 + +## 资源与状态 {#resources-and-states} + +路径遵循 SDK 资源方法,并位于服务的 `/v1` 前缀之前。链接中的固定版本源码定义了字段和联合类型。 + +| 资源 | 操作 | 规则 | +| --- | --- | --- | +| [Environment](https://github.com/openai/openai-python/blob/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/resources/beta/agents/environments/environments.py) | `GET /agents/environments/{id}` | 通过 Session 配置创建。返回 `id`、`type`、`status`,以及 Session 创建时记录的 `files`、`plugins` 和 `skills`。 | +| [Template](https://github.com/openai/openai-python/blob/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/resources/beta/agents/environments/templates.py) | `POST`、`GET /agents/environments/templates`;`GET`、`POST`、`DELETE /agents/environments/templates/{id}` | 请参阅 [Templates](#templates)。 | +| [Files](https://github.com/openai/openai-python/blob/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/resources/beta/agents/environments/files.py) | `POST`、`GET /agents/environments/{id}/files` | 请参阅 [Environment files](environment-files.md)。 | + +读取 Environment 时,Core 会在调用方的 Project 范围内与其实时所属的 Session 联查,并返回持久化的连接状态。它不需要活跃的 Runtime,不会启动任何原生工作,也不会改变连接状态。对于两种放置方式,安装项数组都会列出由 API 管理的文件、Plugins 和 Skills:文件使用 `{id, type, path, file_id, size_bytes}` 且不含内容,Skills 使用 `{type, name, description, skill_id, version}`,Plugins 使用 `{type, name, description}`。在本地能力目录中发现的 Capabilities 不会列出。 + +[Session environment input](https://github.com/openai/openai-python/blob/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/types/beta/environment_param.py) 与 [output](https://github.com/openai/openai-python/blob/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/types/beta/environment.py) 的结构不同: + +- `none` 不选择任何 Environment。 +- `self_hosted` 输入要求提供 `workspace_directory`;可空的 `capability_directories` 默认为空列表。输出会增加 Environment ID 和仅输出字段 `remote_url`。输出中 `/workspace` 的默认值不会使输入字段变为可选。 +- `openai_hosted` 可以引用 Template,并提供 `capability_directories`、`network`、`packages`、`files`、`plugins`、`skills`、`env` 和 `setup_commands`。 + +| 投影 | 状态 | +| --- | --- | +| [Environment resource](https://github.com/openai/openai-python/blob/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/types/beta/agents/environment_info.py) | `pending`、`connected`、`disconnected`、`expired`、`failed` | +| [Session environment event](https://github.com/openai/openai-python/blob/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/types/beta/agent_session_environment_state.py) | `pending`、`ready`、`connected`、`disconnected`、`failed`,并带可为 null 的 error | + +这两套词汇彼此独立;绝不要将其中一套转换为另一套。Session environment 事件携带 Session 和 Environment 的身份标识以及可选的 Turn 身份标识,绝不携带配置、凭据、注册 ID 或修订号。Session 的 `required_actions` 可以包含 `{type: "environment_connection", environment_id}`,这与函数调用相互独立。Environment 连接、Session 状态和 Turn 状态相互独立。 + +Core 在 Session 创建事务中创建 Environment 记录;Session upsert 会选出重试的胜者,而重试绝不创建或修复 Environment。Environment 从所属 Session 派生其 Project 和不可变配置。其首个状态为 `pending`。Session 删除后,读取会隐藏 Environment,而 Core 仍保留该记录,用于结算和清理。Session 为 `none` 时没有 Environment。 + +托管的外层 Environment 必须排除权限范围更广的应用程序凭据和其他租户的机密。 + +## 放置方式 {#placements} + +两种放置方式运行相同的 Runtime:daemon、所选 Harness、原生工具和工作区共同在一台机器上运行。二者唯一的差异是由谁拥有该机器。 + +| 对象 | 责任 | +| --- | --- | +| Session 与 Environment | 持久所有权、配置、待处理交互和连接观察(Core) | +| Provider 分配 | 计算资源和文件系统的生命周期:`openai_hosted` 使用 Core 的 Sandbox Provider,`self_hosted` 使用应用程序 | +| 设备与 daemon 连接 | 经认证的 Runtime 身份和可替换的分派传输 | +| Harness 进程与原生会话 | 原生模型和工具循环、其执行状态及原生历史 | +| 注册 | `self_hosted` 机器的 Environment、设备和 executor key 的精确绑定 | + +### 托管(`openai_hosted`) {#hosted-openai-hosted} + +部署中配置的 Sandbox Provider(E2B、Docker 或 microsandbox,请参阅 [sandbox deployment](sandbox-deployment.md))承载 Environment。[Harness capabilities](harness-capabilities.md) 列出了可在其中运行的 Harnesses。 + +- Session 创建时,无论是否包含初始输入,都会在 Worker 配置计算资源之前提交 Session、Environment 和重试身份。若创建在 bootstrap 前中断,可恢复时不会重复执行 Provider 的 Create。 +- 置备无需调用方执行任何操作;在 Turn 启动之前,Session 会保持空闲。 +- 省略或传入 null 的 `network` 表示启用;`disabled` 和 `restricted` 会在 Session 创建前被拒绝([restricted network](#restricted-network))。 +- Core 重启会保留分配、工作区和原生身份,并且绝不重播结果不确定的工作。 +- 终止清理会先在一个事务中撤销权限并结算待处理输入,然后 Provider 才会回收计算资源。向处于终止状态的 Environment 提交新输入会被拒绝。 +- 删除 Session 会回收其 Environment;删除 Template 则不会。 + +### 自托管(`self_hosted`) {#self-hosted-self-hosted} + +应用程序拥有机器。它使用干净的绝对路径 `workspace_directory` 和可选的绝对本地 `capability_directories` 创建 Session。Core 会返回 Environment ID、`remote_url` 以及 `x_agents_core.installation` 中的一条安装命令;在该机器上运行此命令会安装 daemon 并为其注册([self-hosted guide](../../../docs/zh/getting-started/self-hosted.md)、[executor credentials](environment-executor-credentials.md))。 + +- `remote_url` 是根据 Core 的公共 URL 推导出的 daemon WebSocket URL,绝不根据请求头或 daemon 地址生成。它指定 Core 的私有 daemon 传输通道。 +- 注册会将精确的 Session、Environment、设备和 executor key 绑定在一起。它不会创建任何分配,也无法将 Session 迁移到另一台设备。 +- Session 的工作区必须等于 `/workspace` 别名,或等于 Runtime 绑定到的精确规范目录。指定某个路径并不会授予对它的访问权限。 +- Session 携带自己的模型提供方;部署默认值绝不适用([model execution](model-execution.md#saved-defaults-and-precedence))。 +- Session 读取、列表和事件会返回带有 Environment ID、工作区及能力目录的 `self_hosted` 输出,但绝不返回私有配置。`capability_directories` 列出调用方选择的内容;Runtime 的安装位置保持私有。 +- 计算资源、工作区和文件仍归应用程序所有。删除 Session 或撤销凭据会拒绝后续访问,但不会停止原生进程;机器所有者负责停止和清理。 +- 工作区和原生历史必须能在 daemon 重启后继续存在。丢失它们绝不授权进行静默替换或重播。 + +**应用管理的 E2B。** 应用程序可以在由其使用 E2B SDK 创建、续期和销毁的 E2B sandbox 中运行 Runtime,然后将该 Runtime 注册为 `self_hosted` Environment([E2B Runtime guide](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/deploy/e2b/README.md))。Core 不为其保留 E2B 分配,也绝不续期或终止它。 + +### 所有权规则 {#ownership-rules} + +- 将 Environment 身份、所有权、配置和生命周期保留在 Core 中,并使其与 Provider 计算资源、设备身份、daemon 套接字和原生会话相分离。将可变连接状态排除在不可变配置之外;替换后的所有者会使过期观察值失效。 +- 调用方、设备和 Environment 连接使用彼此不同的凭据。daemon gateway 会对已注册的 executor key 和精确设备进行认证。连接观察保留 generation 和 revision 栅栏。注册和连接都不表示已就绪。 +- 轮换、撤销、Session 删除和所有权丧失都会拒绝后续访问;但它们不保证原生效果会立即停止。 +- 原生历史保留在绑定的 Runtime 上。替换计算资源时必须保留或以可证明的方式恢复原生历史;绝不能静默移动已绑定的 Session 或重播未知工作。 +- 持久元数据读取不需要活跃的 Runtime。实时文件读取需要获得对精确工作区的授权视图和有界操作所有权。写入、替换或撤销工作区所有者的操作也适用变更栅栏。 +- 当 Session 已有已启动的 Turn,但没有记录原生 session ID 时,下一个 Turn 必须通过经验证的 Runtime 能力恢复已有历史。已提供的原生 ID 始终具有权威性。Codex 适配器仅通过原生列举和精确 ID 恢复,恢复 Session 私有原生主目录及预期工作目录中唯一且未归档的根项。历史缺失、不完整或存在歧义时会失败且不会创建新根项;记录的启动可能早于原生工作时也会失败。恢复绝不会重播被中断的输入。设备身份和 Environment 范围属于 `ExecutionDevice`;原生会话身份和先前 Turn 状态属于 `SessionExecutionBinding`。 +- Runtime 发现会为每个已安装的 Harness 执行一次 15 秒版本探测;缺少二进制文件会立即失败。版本结果仅表示可用性,而非 Environment 就绪状态。 + +## 输入接纳 {#input-admission} + +Session 输入通过 `POST /agents/sessions/{id}/events` 以每次 1–64 个事件的有序批次提交。对于带 Environment 的 Session,Core 会将尚不能启动的输入预留,直到 Environment 完成连接和准备。 + +### 初始输入 {#initial-input} + +创建操作接受字符串形式的初始文本,或由用户消息构成的有序数组。省略或传入 null 输入不会创建 Turn 或连接操作。 + +- 创建事务会将初始批次存储为预留;对于 `self_hosted`,还会记录连接操作。只有创建胜者会插入它们;重试绝不会再次插入。 +- `self_hosted` 创建会同时返回 Session 和 Environment 连接目标,即使机器仍处于离线状态,也不等待接纳。流式 `self_hosted` 创建会将已提交的 JSON 投影作为其 `created` 快照发送,其中已经显示 `requires_action` 和连接操作,随后发送已提交的 `requires_action` 事件。 +- 新鲜的创建流会在已接纳 Turn 结束时记录空闲状态,或预留不再处于 pending 时记录空闲状态后立即结束,也会在失败后结束。仅因连接而清除操作不会结束该流。无输入的创建会在发送其 `created` 快照后立即结束;使用相同 key 的流重试会立即结束且不发送任何事件。后续订阅者只能看到未来事件,并通过查询恢复历史。 +- 关闭流不会影响已提交的工作;Worker 仍会准备并接纳输入。 +- 如果初始预留过期,Session 会变为 `failed`,并带有安全错误且不包含任何操作,同时不会创建 Turn。Environment 本身不会变为 `failed`。 +- 创建重试会保留原始身份、截止时间和输入。对于记录了意图的 Saved-Agent,重试会在执行接纳或源解析之前进行恢复。 + +### 后续输入 {#later-input} + +| 批次 | 行为 | +| --- | --- | +| 消息,Turn 处于活动状态 | 通过有序接纳和原生传递追加到当前 Turn。不会创建新 Turn、准备或预留。 | +| 消息,Session 处于空闲状态 | 预留该输入;请求等待 Worker 进行准备、接纳和认领。 | +| 仅取消 | 通过常规持久取消路径接纳。空闲状态下的取消不会创建 Turn。预留处于 pending 时,新的取消会发生冲突;它绝不会取消 Turn 之前的输入。 | +| 仅函数结果 | 使用常规结果回执接纳到指定的 pending 调用,不涉及 Turn 或准备。不能绕过 pending 预留。函数结果绝不会成为 Environment 安装元数据。 | +| 混合类型 | 拒绝。 | + +Core 仅在批次已持久接纳后返回 202。对于取消,202 确认的是接纳,而非原生完成或进程退出。在 Session 锁内,重试会先恢复其原始预留或回执,然后由 Core 在活动 Turn 与新预留之间选择,并在后续工作中保持该目标。未加锁的读取或冲突后的重试绝不会选择不同路径。 + +等待请求使用有界的池化操作,位于事务和执行租约之外;它绝不会准备或启动原生工作。只有此路由会将其响应写入期限延长至六分钟,以涵盖五分钟的数据库期限及响应时间。断开连接会停止等待,但不会取消预留。错误如下: + +| 条件 | 响应 | +| --- | --- | +| 预留已过期 | 409 `environment_input_expired` | +| 预留已取消 | 409 `environment_input_cancelled` | +| 向失败的 `openai_hosted` Environment 提交新输入 | 409 `conflict_error`,"the hosted environment failed to provision" | +| 向失败或过期的 `self_hosted` Environment 提交新输入,或 Environment 失败时仍在等待的输入 | 409 `environment_unavailable` | +| 执行所有权丧失 | 503 `execution_unavailable` | +| Session 已删除 | 404 | + +### 预留 {#reservations} + +预留会在 Turn 接纳之前存储一个规范消息批次,截止时间依据数据库时钟设置为五分钟。 + +- 它要求 Session 带有 Environment,且没有活动工作。预留和直接输入共享 Session 锁和重试标识;pending 或 settled 的 key 不能通过直接接纳绕过其预留。 +- pending 预留会阻止新的直接批次,包括取消;较早成功的重试仍保持可读。 +- 提升操作会将原始输入、历史、预留结算和 Turn 认领(`queued` 到 `in_progress`)一并提交。它要求当前持有租约的执行写入方和保留的原生准备状态;准备期间不持有数据库锁。只有首次成功且非重放的回执授权在该准备状态上执行 Start。已接纳的重试会返回原始回执,而不重新获取执行权;读取或不确定的提交绝不复授权另一次 Start。 +- 提升之后、Start 之前发生崩溃时,会进入已认领 Turn 的协调流程(`execution_interrupted`),即使 Session 未绑定或已删除也是如此。 +- 输入处于 pending 时会拒绝删除 Session,且不会更改任何内容。认领之后删除 Session 会像处理任何活动 Turn 一样被拒绝。 +- 截止时间在取得 Session 锁后检查。过期和定向取消会保留其终止身份;终止预留的重试不能影响后续预留或 Turn。 +- 初始预留过期时会使 Session 失败;后续预留则会让 Session 回到空闲状态。失败事件会原子地捕获已结算的活动和 Usage。迟到的连接或创建重试不能重置或重播已过期的输入。 + +当 Runtime 返回 `failed` 结果、带有 `preparation_failed` 且没有 Run 时,会立即以 `runtime_preparation_failed` 结算待处理输入。当当前 `execution_prepare` 在没有 Run 且代码为 `invalid_configuration`、`unsupported_configuration` 或 `unsupported_preparation` 时被拒绝,也同样如此。在任何 Turn 存在之前,Core 会记录安全的 Session 失败并释放输入门控;修复本地原因后即可接受新输入。传输丢失、容量拒绝和未确认的清理在原始期限内保持可重试。Core 使用这些通用控制状态,绝不使用 Harness 特定的错误文本。 + +### 活动与必需操作 {#activity-and-required-actions} + +在 Turn 存在之前,Session 活动来自最近相关的预留和连接状态。离线 `self_hosted` Environment 上的待处理输入会请求 `environment_connection`;连接到达后会将其清除并设为 `idle`,同时 Worker 仍负责原生就绪性和接纳。没有待处理输入的离线 Environment 不会请求任何操作,而 `openai_hosted` 置备也绝不会请求连接。预留或连接变更会在同一事务中提交不可变的 Session 活动和使用量快照;较新的预留或活动 Turn 负责后续活动。已结算的非初始预留会将操作恢复为 `idle`,直至出现较新的工作。 + +### 过期与调度 {#expiry-and-scheduling} + +Worker 在每个 tick 中最多处理 32 个到期预留,处理顺序是在检查其租约之后、检查设备或执行槽位之前。该扫描使用带事务超时的租约 Store 连接,绝不使用池化写入方。部分截止时间索引和 `SKIP LOCKED` Session 锁使无关工作得以继续。截止点采用语句时间,而结算会在取得 Session 锁后重新检查数据库时钟。重启会在正常 tick 中继续处理过期;不存在单独的调度器。失败的 Turn 绝不能代替 Turn 之前的连接失败。 + +当已完成的托管分配处于暂停或恢复状态且有下一 Turn 输入处于 pending 时,Core 会向托管 Runtime 维护循环发出提示。初始输入、冷创建、运行中或已禁用的计算资源、终止回执、取消和函数结果、历史及文件操作均不会发送提示。提示采用尽力而为的方式合并且不会阻塞;它们在正常的五秒周期中最多增加一次扫描,绝不会绕过容量、繁忙的生命周期门控或所有权检查。持久化工作和正常 ticker 始终具有权威性。 + +## Runtime 能力准备 {#runtime-capability-preparation} + +准备过程会安装 Session 选择的内容:初始文件、工具配置、Skills、Plugins、Environment MCP、npm 和 Python 软件包以及设置命令。两种放置方式使用同一条 Runtime 路径。 + +### 所有权与生命周期 {#ownership-and-lifetimes} + +资源管理负责 Provider 放置、容量和分配,以及 Environment 的创建、续期和回收。经认证的 Runtime 连接承载初始化、能力准备和 executor 操作;它绝不分配或销毁计算资源。Provider 绝不运行 Core 初始化命令。 + +关闭 Executor、取消 Turn 或传输丢失都会保留已安装的快照、工作区和分配。回收是与活动工作协调的显式操作;断开的套接字不能证明原生效果已经停止。[Harness onboarding](harness-onboarding.md#executor-and-turn-lifetimes) 负责 Executor 和 Turn 的生命周期。 + +### 准备顺序 {#preparation-order} + +Core 会在 Session 创建时冻结资源版本、元数据和源选择。随后初始化按以下顺序运行,每一步都通过 `runtime_prepare` 使用同一个 daemon: + +1. 初始文件和工具配置; +2. Skill 和 Plugin bundle 导入; +3. npm 和 Python 软件包,然后按顺序运行设置命令; +4. 能力目录快照。 + +运行器仅使用中立的 Environment 和 Session 身份以及一个 Runtime 对端,不包含 Provider、部署或操作系统分支。Harness 差异保留在适配器中。 + +**可移植的准备输入。** `self_hosted` 输入仅携带 `workspace_directory` 和 `capability_directories`。无论 Session 采用哪种工作区放置方式,还可以发送 `x_agents_core.environment`,这是 Core 扩展,包含 `environment_template_id`、`files`、`env`、`packages`、`setup_commands`、`skills`、`plugins` 和 `capability_directories`。它使用与 `openai_hosted` 输入相同的解析器和继承规则。若同一字段同时在 `x_agents_core.environment` 和顶层 `environment` 中提供,则会被拒绝,包括显式 null。`self_hosted` Session 只能通过此扩展指定 Template,并且不能使用 network 不是 `enabled` 的 Template(400,param `x_agents_core.environment.environment_template_id`)。机器位置、规格大小和网络策略不是此扩展的字段。 + +```json +{ + "agent_id": "agent_example", + "environment": {"type": "self_hosted", "workspace_directory": "/home/user/project"}, + "x_agents_core": {"environment": {"environment_template_id": "env_template_example"}} +} +``` + +将 `environment` 改为 `{"type":"openai_hosted"}` 会复用相同的准备输入。资源解析、Project 授权、具体 Skill 版本、加密文件内容和机密工具变量都会在 Session 创建时冻结。重试和重连会复用这些快照;新 Session 会解析新版本。`self_hosted` Environment 从不需要分配记录。 + +**就绪性。** 传输 `connected` 是一种连接观察,而不代表就绪。执行和实时文件访问都要等待初始化;随后,原生准备所有者会在接纳 Turn 前验证已安装的快照和 Harness。文件读取保留自身的就绪性和授权,不要求能力或原生就绪。部署模型凭据绝不发送到应用程序所有的机器。 + +**传输。** 初始文件、configure、npm、Python 和设置操作、惰性的 Skill 和 Plugin 归档以及最终确定选择,都会作为带有类型的 `runtime_prepare` 操作传输,并携带规范的 Session 和 Environment 身份;[protocol](../../../docs/zh/runtime-protocol.md#preparation-and-execution-order) 负责分块和回执。文件及设置工作目录使用逻辑 `/workspace` 地址;Runtime 负责选择可执行文件和物理目标位置,Core 不提供任何可执行文件或主机平台字段。源选择接受可移植的 Unix 绝对路径、Windows 驱动器路径和 UNC 路径;Core 绝不会在自己的主机上解析这些路径,而 daemon 会应用其本地路径和访问检查。 + +### 已安装快照 {#installed-snapshot} + +两种来源都使用通用 Runtime 解析器,以及适用于 Linux、macOS 和 Windows 的 `installed.json` 清单。Runtime 操作者负责选择能力根目录([installer options](../../../docs/zh/getting-started/self-hosted.md#options-for-automation));Core 和传输请求无法选择。 + +- 清单会将 Session 和 Environment 绑定到有序的源选择摘要。准备所有者会在原生执行之前验证或创建该清单,即使选择为空也是如此。 +- 设置命令运行后,初始化器会将声明的、位于工作区内的能力目录快照到 Runtime 存储中。目录字节是在设置之后读取,而不是在 Session 创建时读取。 +- 文件系统锁可防止并发安装。私有完成记录仅保留操作者的安装根目录,因此已删除的快照绝不会被误认为首次准备,也不会再次被捕获。 +- 快照缺失、不完整、冲突或属于外部来源时,会在不删除数据、修复或重播的情况下失败。 +- 重连和替换 Executor 会加载已安装的内容,而不会重新读取源。对源的编辑只会影响新 Session。 +- 对快照的递归引用,以及会逃逸出快照的目录项,都会被拒绝。 +- 只读快照模式只是完整性提示,不能防止启动用户进行修改。 + +Executor 接纳仅验证已冻结的描述符。准备所有者会在调用原生工厂之前使能力就绪;适配器仅接收已解析的、由 Runtime 所有的 Skill 路径和 MCP 声明。复用的 Executor 会保留其原始配置。 + +### 系统依赖与 Runtime 目录 {#system-dependencies-and-runtime-directories} + +daemon 以启动它的账户身份运行,绝不使用 sudo 或提升权限。准备过程中只会安装用户目录中的依赖项。 + +- 系统依赖必须预先安装在托管镜像中,或由自托管机器的所有者安装。缺少可执行文件或库时,需要该依赖的操作会失败。 +- Templates 和内联配置都会拒绝 `packages.system`,包括 null 或空列表(400,param `packages.system`)。软件包响应仍会包含官方要求的 `system: []`。 +- npm 会安装到本地 prefix,Python/pip 会安装到 Runtime 软件包目录下的本地 target;Node/npm 和 Python/pip 必须已经安装。其依赖项可被每个工作目录中的原生工具看到。 +- 设置命令使用 Bash 运行;在 Windows 上必须使用 Git Bash,且不能由其他 shell 替代。默认工作目录为 `/workspace`。 +- 在 Windows 上,npm 安装以及名为 `npm` 或 `npx` 的 stdio MCP 命令(包括其 `.cmd` shim)会通过 Node 调用 npm 的 JavaScript 入口点运行,而不经过额外的 shell。 + +初始化目录和软件包目录默认分别是 Runtime 主目录(`OAC_RUNTIME_HOME`)下的 `initialization` 和 `packages`,也可通过 `OAC_RUNTIME_INITIALIZATION_DIRECTORY` 和 `OAC_RUNTIME_PACKAGE_DIRECTORY` 设置;打包的 Linux 镜像使用 `/environment/initialization` 和 `/environment/packages`。这些是资源路径,在 Core 中绝不是 Environment 源或操作系统开关。 + +每条命令都使用启动用户的权限和主机网络。进程所有权会等待退出及 I/O 结算完成。命令输出会被丢弃;确认失败时只保留一个有界整数退出状态。 + +### 显式本地工具环境 {#explicit-local-tool-environment} + +安装器的 `--tool-env-file`(`OAC_RUNTIME_TOOL_ENV_FILE`)提供 Runtime 操作者的基础工具变量。准备过程会将这些值复制到其私有初始化快照中,并由 Session 的 `env` 键覆盖。Runtime 绝不会重写源文件,也不会继承无关的环境凭据。设置、能力解析和 Harness 执行都会读取同一份已准备快照。即使操作者编辑了文件,重连仍会保留该快照;新 Session 会读取当前文件。Harness profile 可以引用由 Runtime 所有的文件,但不得持久保存其值的副本。配置的文件缺失或无效时,准备过程会失败。 + +### 初始化状态与失败 {#initialization-state-and-failure} + +Environment 的初始化状态为 `pending`、`running`、`complete` 或 `failed`,且独立于任何分配、身份验证和连接发布。 + +- Worker 的初始化调度器每次扫描 32 个 Environment,在末尾循环回绕,并依据执行并发度限制并发准备,且独立于 Provider 维护。 +- 缺少套接字不会消耗一次 pending 尝试。Harness 不可用时,会在安装前失败。每个操作都会重新检查当前权限和原始套接字;完成时还会重新检查精确绑定。 +- 每个文件传输、configure、Skill、Plugin、软件包和设置步骤都有两分钟的预算;整个初始化过程有 30 分钟。初始输入仍保留其五分钟接纳期限,因此大型安装应从空闲 Session 开始。 +- 正在运行且所有权丧失的初始化,包括跨 Core 重启丧失所有权,会作为未确认而失败;不会重播任何内容。已完成的 Environment 在重连或原生恢复时绝不会重新安装,因此用户后续更改会保留下来。 +- 失败对 Session 而言是终止状态,但不会销毁计算资源或文件。 + +失败时,一个事务会将 Environment 标记为失败,并记录 `agent.session.environment.failed`、一个 `error` 事件和一个 `agent.session.failed`。Session 读取会返回 `failed`、作为 `error` 的原因以及作为 `last_active_at` 的失败时间;实时流会在失败事件后结束。待处理输入以失败状态结算。原因只指明步骤及其退出状态: + +| 失败步骤 | 原因 | +| --- | --- | +| 设置命令 `i`,已确认退出 1–255 | `Failed to provision environment: script "setup_commands[i]" failed with exit code N` | +| Python 软件包,已确认退出 1–255 | `Failed to provision environment: script "Python package installation" failed with exit code N` | +| npm 软件包,已确认退出 1–255 | `Failed to provision environment: script "npm package installation" failed with exit code N` | +| 初始文件安装,确认失败 | `Failed to provision environment: initial file installation failed` | +| Skill 准备,确认失败 | `Failed to provision environment: Skill installation failed` | +| Runtime 上未安装 Harness | `Failed to prepare environment: the selected Harness is unavailable. Install the supported Harness version on the Runtime and create a new Session.` | +| 其他情况:超时、未知效果、回执缺失或格式错误、Plugin 安装、快照最终确定、bootstrap 拒绝、Core 重启 | `Failed to provision environment: initialization did not complete` | + +每个初始化操作都会返回有类型的 `rejected`、`failed` 或 `unknown` 结果,并且 daemon 会先确认进程退出和 I/O 结算完成。Core 使用固定标签和整数组成原因,因此命令、env 值、软件包名称、路径和进程输出绝不会进入原因、事件、日志或响应。失败步骤不会重试,后续步骤也不会运行。机密 env 和设置快照会与普通元数据分开加密。初始文件在所有平台上都使用原子替换写入器和工作区锚定路径;Files API 创建则保留其自己的不覆盖规则。 + +## Templates {#templates} + +Template 是由 Project 拥有、供 `openai_hosted` Session 和 `x_agents_core.environment` 使用的配置。它不包含运行中的工作区,也与 E2B templates 等 Provider 镜像无关。每个引用它的 Session 都会通过与内联配置相同的准备过程获得自己的 Environment。Template 解析、存储和解析过程绝不会选择 Harness 或 Provider,也不会依赖原生工具名称或 Harness 私有路径,因此新的 Harness 或 Provider 无需修改 Template。 + +```python +from openai import OpenAI + +client = OpenAI() # reads OPENAI_BASE_URL and OPENAI_API_KEY +template = client.beta.agents.environments.templates.create( + name="Python workspace", network={"access": "enabled"}, + env={"APP_MODE": "analysis"}, packages={"python": ["packaging==26.0"]}, + setup_commands=[{"command": "mkdir -p /workspace/outputs"}], +) +session = client.beta.agents.sessions.create( + agent={"model": "your-configured-model"}, + environment={"type": "openai_hosted", "environment_template_id": template.id}, + input="Create /workspace/outputs/report.txt containing the result of 6 * 7.", +) +``` + +### 操作 {#operations} + +- 每个操作都需要 Project API key 和 `OpenAI-Beta: agents=v1`。Template 操作无需执行部署,也不会分配计算资源。 +- `name` 可选且可为 null,会逐字保留,长度为 1–256 个 Unicode 字符。 +- `network.access` 为 `enabled`、`disabled` 或 `restricted`;省略或传入 null 表示启用。请参阅 [Restricted network](#restricted-network)。 +- 响应会携带安全元数据,绝不会包含 `env`、`setup_commands` 正文或内联文件数据。 +- 列表操作使用 `after`、`limit`(默认 20;0 按 1 处理,超过 100 的值按 100 处理)和 `order`(默认 `desc`),并按创建时间和 ID 排序。不存在和属于外部 Project 的 Template ID 及游标都会返回相同的 404。 +- 更新时,省略字段会保留原值,提供字段则会替换原值。Null 会清除 `name` 和每个列表,并将 `network` 重置为启用。 +- 封装或解封机密内容(文件、env、设置命令、Skills、Plugins)的写入操作和 Session 解析需要 Core 的 [credential key](../../../docs/zh/configuration.md#installation-directory);元数据读取则不需要。 +- Session 会在创建时于其 Project 内解析一次 `environment_template_id`,冻结有效配置,并且绝不将 Template ID 传递给 Provider 或 Runtime。更新或删除 Template 绝不会改变现有 Session。创建重试会在读取 Template 之前恢复已记录的调用方意图,即使 Template 已删除也是如此;意图发生变化时会产生冲突。 + +### 继承 {#inheritance} + +Session 会先应用 Template,然后应用自身字段。组合仅在加密 Session 快照写入前执行一次,并使用常规验证器重新验证结果。调用方意图(省略、null 或显式提供)会单独保留,用于重试策略。 + +| 字段 | Session 中省略或为 null | Session 中提供 | +| --- | --- | --- | +| `network` | 继承整个 Template 策略 | 必须收窄策略:enabled 可以变为 restricted 或 disabled;restricted 可以变为其主机的子集或 disabled;disabled 不能放宽 | +| `env` | 继承 Template 键 | 按键叠加;Session 值优先;`{}` 会保留所有 Template 键 | +| `setup_commands` | 继承该序列 | 替换该序列;`[]` 会清除 | +| `files` | 继承文件集 | 替换整个文件集;`[]` 会清除 | +| `packages` | 继承两个管理器 | 分别解析 `python` 和 `npm`:省略或为 null 的管理器会被继承,列表会替换原值,`[]` 会清除;`system` 会被拒绝 | +| `skills`、`plugins`、`capability_directories` | 继承列表 | 替换列表;`[]` 会清除 | + +作为比较,不带 Template 的内联 `openai_hosted` Session 会将省略或为 null 的 `network` 视为启用。 + +### 受限网络 {#restricted-network} + +`restricted` 要求在 `allowed_domains` 中提供 1–100 个精确 ASCII 主机名;子域名和重定向目标需要各自的条目。通配符、URL 或端口语法、IP 字面量、Unicode 和尾随点都会被拒绝。读取操作会返回提供的拼写、顺序和重复项;比较操作使用单独的小写去重副本。 + +Runtime 不会强制执行 `disabled` 或 `restricted`,也没有 Provider 代为强制执行。因此,Core 会在 Templates 中存储这些策略,但会在分配计算资源之前拒绝任何有效 network 不是 `enabled` 的 Session。初始化和工具使用主机现有的网络。 + +### Env 与设置命令 {#env-and-setup-commands} + +Agent 代码可以读取 env 值,但它们绝不会出现在公开元数据或初始化诊断中。名称必须匹配 `^[A-Za-z_][A-Za-z0-9_]*$`,且值不能包含 NUL。Core 保留 `PATH`、`OPENAI_API_KEY` 以及所有以 `OAC_` 或 `CODEX_` 开头的名称。文件和软件包会在设置命令之前安装;退出状态非零的设置命令会使初始化失败;效果未知时不会重试任何命令;已完成设置在重连时绝不会再次运行。 + +### 初始文件 {#initial-files} + +`files` 条目会将文件放置在 `/workspace` 内的绝对目标路径,来源可以是内联标准 Base64 `data`,也可以是 Project 所有的已上传 `file_id`。 + +| 限制 | 值 | +| --- | --- | +| 每个配置的文件数 | 50 | +| 内联文件 | 5 MiB | +| 所有内联内容 | 10 MiB | +| 引用文件 | 50 MiB | +| Session 或 Template 请求正文 | 16 MiB | + +路径必须规范、互不相同且位于逻辑工作区内部;Runtime 会将每次写入锚定到其绑定的工作区。这是 API 路径范围,不是对以同一用户身份运行的原生工具的限制。Template 元数据会将内联文件显示为 type、path 和 size,将引用显示为 type、path 和 `file_id`;无论内联文件还是引用,每个 Session 都会获得全新的文件 ID 和大小。文件数据不会出现在普通配置、响应、事件或命令参数中。Template 会保留引用;每个 Session 会授权并冻结自己的加密源字节,因此之后删除源文件无法改变这些字节。 + +### Skills {#skills} + +Templates 和内联配置都接受 Project 所有的 Skill 引用及内联 Skill ZIP。通过固定版本 SDK 上传目录,然后引用其默认版本: + +```python +skill = client.skills.create(files=[ + ("report/SKILL.md", b"---\nname: report\ndescription: Create the report.\n---\nFollow the report procedure.", "text/markdown"), +]) +template = client.beta.agents.environments.templates.create( + skills=[{"type": "skill_reference", "skill_id": skill.id}], +) +``` + +固定版本的 `/v1/skills` 资源、版本及内容路由使用 Project API key,且不使用 Agents beta 标头。ZIP 上传使用 `files`,目录上传则重复使用 `files[]`。固定版本 SDK 3.13.0 在多部分内容提取过程中会丢弃单文件 tuple,因此应使用原始 HTTP 上传单个 ZIP。一次上传最多包含 500 个常规文件,并且必须恰好包含一个 `SKILL.md`;压缩后为 5 MiB,展开后为 20 MiB。[File resource semantics](source-files.md#versions-and-metadata) 负责默认版本和删除规则。 + +省略或传入 null 版本的引用会在 Session 创建时选择默认版本,`"latest"` 会选择最新版本,正版本号字符串则选择该版本。Template 响应会保留未解析的选择器(默认版本为 `version: null`);Session 元数据会显示 `{type, skill_id, version, name, description}`,其中版本为具体值。Session 会在其创建事务中冻结所选版本的字节和元数据;之后默认版本发生变化、源被删除或 Template 被更新,都无法改变这些内容。 + +内联 Skill 携带 `name`、`description` 和 Base64 ZIP `source`(`media_type` 为 `application/zip`)。归档包含一个顶层文件夹,其中有 `SKILL.md` 和可选的支持文件;清单中的 name 和 description 必须与请求一致。Frontmatter 可以包含 `name`、`description`、`license`、`compatibility` 和字符串 `metadata`;原生 hooks、权限控制和 subagent 指令都会被拒绝。只允许常规文件:路径遍历、链接、重复目标位置、特殊文件和无效清单都会被拒绝。内容在安装期间保持惰性,并保留可执行位。内联元数据只显示 type、name 和 description。 + +### Plugins 与能力目录 {#plugins-and-capability-directories} + +Plugin 是带有 type、name 和 description 的内联 ZIP,其单个归档根目录包含 `.codex-plugin/plugin.json`。清单中的 `skills` 指定 Skill 目录;整个包布局都会保留。公开 Plugin 元数据只显示 type、name 和 description。 + +`openai_hosted` 和 Template 的 `capability_directories` 接受 `/workspace` 内的干净绝对路径,初始文件和设置命令可以填充这些路径。`self_hosted` 能力目录是机器上的绝对本地路径。通过目录发现的 Skills 绝不会显示为 `skills` 或 `plugins` 条目。目录缺失、Skill 名称重复、清单不受支持以及存在非常规文件时,初始化会失败。 + +| 归档与安装限制 | 值 | +| --- | --- | +| 每个归档 | 压缩后 5 MiB,展开后 20 MiB,1,000 个条目 | +| 内联 Skills | 总计 50 个,压缩后共 10 MiB,展开后共 50 MiB | +| Plugins | 总计 50 个,压缩后共 10 MiB,展开后共 50 MiB | +| 已安装快照 | 50 个 Skills、50 个 Plugins、50 MiB | + +## Skills、Plugins 与 Environment MCP {#skills-plugins-and-environment-mcp} + +Skills 及其不可变版本是 Project 资源,独立于 Session 和原生安装。 + +- 版本分配和指针变更会在所属 Skill 行上串行化。顶层 name 和 description 在同一事务中跟随默认版本;非默认上传不会更改它们。在并发上传和删除过程中,版本身份始终唯一。 +- Bundle 使用服务密码进行加密,并绑定到 Project、Skill 和版本。元数据读取绝不会加载或解密 Bundle。删除 Skill 会回收其版本,但不会影响已冻结的 Session。 +- 引用会在 Session 创建事务中、upsert 建立所有权之后解析。被引用的资源按稳定顺序锁定,版本、元数据和字节会一并冻结。公开引用元数据、未解析的 Template 意图和已解析的 Runtime Bundle 始终彼此独立;引用绝不会报告为内联内容。 + +内联和引用的 Skills 使用相同的机密快照与安装器。Runtime 会在设置和原生执行之前,将它们安装到能力根目录下的 `skills/`。适配器只注册所选的 Skill 根目录:Codex 将其注册为显式额外根目录,MiniMax Code 通过其原生目录进行注册,Claude 则为每个包使用一个受控封装,其中包含实际目录和不可变硬链接。自动原生 MCP 发现保持禁用。Codex 的嵌套 `SKILL.md` 发现、`agents/openai.yaml` 依赖项配置以及 Claude 内联 shell 预处理都会导致适配器准备失败。原生 Harness 配置绝不会整体透传。 + +### Plugin MCP {#plugin-mcp} + +Plugin 可以在 `.codex-plugin/plugin.json` 中通过 `mcpServers: "./.mcp.json"` 声明 MCP 服务器,也可以在省略路径时使用根目录下的 `.mcp.json`。该文件包含以服务器名称为键的 `mcpServers`。将 Plugin 根目录选为能力目录会激活其 MCP 声明;选择父目录则会发现 Skills,但不会激活嵌套 MCP 服务器。 + +共享解析器接受 HTTP `url`、`bearer_token_env_var` 和字面量 `http_headers`,以及 stdio `command`、`args`、选定的 `env_vars` 和包相对路径 `cwd`。不支持公开的 `env_http_headers`。Runtime 会重新解析已冻结的已安装包,并且只从已初始化的 env 中解析选定值;缺少值时会失败,而不会回退到模型或 daemon 变量。 + +stdio 服务器通过 daemon 的 stdio helper 启动;该 helper 会解析已安装的声明,并以 Harness 的权限启动命令。在 Unix 上,helper 会将自身替换为服务器;在 Windows 上,它会在所属进程树内部转发 stdio。已初始化的值会覆盖声明中的变量。进程组和 Windows Jobs 负责取消及后代进程清理,而不负责隔离。 + +[Harness capabilities](harness-capabilities.md#environment-preparation) 负责受支持的 Plugin 传输及每个 Harness 的限制。 + +Environment MCP 需要启用的网络。重复的服务器身份会被拒绝。Claude 会拒绝字面量标头,因为固定版本客户端会再次展开这些标头,并将自定义标头跨来源转发。MiniMax ACP HTTP 声明会保留在 Session 本地的原生内存中;令牌绝不会进入原生配置文件或进程参数。无法通过 Plugin 清单设置必需初始化和工具允许列表。 + +### 有效绑定 {#effective-bindings} + +Runtime 会在适配器投影之前,通过 `agent.ResolveMCPBindings` 解析公开 HTTP 声明和已安装 Plugin MCP。每个瞬时绑定都会保留其连接来源、传输、可为 null 的工具允许列表、必需标志、凭据权限以及已安装 stdio 身份。绑定绝不会持久化或记录;重复身份和不可用的选定凭据都会被拒绝。MiniMax 会读取其 Session 私有的原生 runtime 名称注册表,以获取精确的首帧身份,并针对两种传输交叉检查已完成的原生结果;适配器绝不会伪造延迟启动事件或猜测身份。 + +### 公开 MCP 连接来源 {#public-mcp-connection-origin} + +Agent 的 HTTP MCP 工具([declaration](execution-tools.md#http-mcp))具有 `connection_origin`。省略或传入 null 表示 `service`,在 `self_hosted` Session 中也是如此。该来源会从已保存配置经过 Session 快照一直保留到 Runtime 请求,后者要求精确的线上版本;`service` 请求绝不会变为 `environment` 连接。 + +| 来源 | 连接发起位置 | 允许的放置方式 | +| --- | --- | --- | +| `service` | Core 的服务端执行主机 | 仅 `none` | +| `environment` | Environment 的工作区 | `openai_hosted` 和启用网络的 `self_hosted` | + +Core 的 Harness profile 会声明 `MCPOrigins`;接纳和分派会将来源与放置方式以及 Runtime 公布的 HTTP、bearer 和必需初始化能力进行核对,Runtime 会在调用适配器之前再次验证来源。不存在按 Harness 名称或 Provider 分支选择不同路径的逻辑。 + +[Harness capabilities](harness-capabilities.md#tools) 负责每个 Harness 的来源支持和策略限制。 + +两种来源都支持匿名 HTTP 和 HTTPS bearer 凭据。所附加 Vault 的选择会冻结凭据身份,包括唯一的隐式 URL 匹配或匿名选择。只有该凭据经 Project 授权后,才会进入瞬时 Runtime 请求;绝不会搜索 Core 默认值或无关 Vault。解密失败或凭据缺失时,执行会失败且不会回退到匿名。公开 Environment MCP 保留 `project_vault` 权限,Plugin 凭据保留 `environment_configuration` 权限;两者都不会覆盖重复的服务器标签。Bearer 令牌绝不会进入持久化的原生配置或进程参数。 + +工具允许列表和必需初始化遵循 [HTTP MCP contract](execution-tools.md#http-mcp)。 diff --git a/contracts/agents-api/zh/execution-tools.md b/contracts/agents-api/zh/execution-tools.md new file mode 100644 index 00000000..aaab6a7c --- /dev/null +++ b/contracts/agents-api/zh/execution-tools.md @@ -0,0 +1,95 @@ +--- +title: "执行工具" +source: contracts/agents-api/execution-tools.md +source_hash: 5e0b9ab1938ccd0ee7b22b1482a365bd54285c8c09fc4cb1ca172e34ab122ce2 +--- + +Agent 在 `tools` 中声明应用函数、控制项和 MCP 服务器,并可在 `text.format` 中声明输出 schema。本契约说明 Core 如何验证声明、哪些内容跨越 Runtime 边界,以及调用方如何恢复待执行操作。[Harness 能力](harness-capabilities.md)列出各 Harness 在不同部署位置支持的操作。原生工作区工具和 Environment Plugin MCP 属于 [Environment](environments.md#skills-plugins-and-environment-mcp)。 + +## 准入 {#admission} + +- 已保存的 Agent 将所有固定版本工具声明保留为资源数据。保存不代表通过执行资格验证。 +- 创建 Session 时,执行解析器将保存的引用与内联声明解析为不可变 Session 快照,再根据 `services/core/internal/engine` 中所选 Harness 的配置检查组合。不支持的组合在任何写入之前返回 400 `unsupported_or_invalid_configuration`。重复 `web_search` 或 `tool_search`、非对象 schema 根等协议错误使用官方错误字段([验证](wire-semantics.md#configuration-validation))。 +- 分发之前,所选 Runtime 也必须声明该操作的能力。仅有能力声明不会启用操作。 +- 原生 Harness 运行模型与工具循环。Core 不添加第二个循环、输出修复、schema 强制转换或提示词包装,也不选择原生工具名称。 + +## 函数 {#functions} + +函数声明要求 `name`、`description` 和 `parameters` 中的 JSON Schema;`defer_loading` 默认为 false,不能为 null。名称必须非空白、唯一且最多 512 字节;每个 Session 最多有 64 个函数定义。保存的引用解析到 Session 快照,定义在原生准备和继续执行期间保持固定。 + +**结果。** 调用方提交 `agent.session.input.tool_result` 事件,包含 `turn_id`、`call_id`、`success`,以及可选且可空的 `error` 和 `output`。输出为字符串或有序文本与图像部分,受 Harness 支持范围约束。批次原子处理并保留字段存在性、原始内容和重试身份;公开结果 Item 与事件始终包含 `output` 和 `error`,未提交时为 null。 + +| 情况 | 响应 | +| --- | --- | +| 相同重试,包括 Turn 结束之后 | 接受 | +| 同一调用提交不同结果 | 409 `conflict_error` | +| Turn 取消后首次提交结果 | 409 `conflict_error` | +| 调用方 Session 内未知调用或其他 Turn 的调用 | 400 `invalid_request_error`;待执行操作不变 | +| Session 不存在或属于其他范围 | 404 | + +[Session 输入冲突](sessions-events.md#input-errors)记录准确消息。无效或不支持的内容不能消耗待处理调用。准入与应用结果分离:只有匹配的原生工具结果出现在实时根 Turn 中,适配器才确认结果([回执契约](message-content.md#function-results))。传输写入本身不确认任何事实,确认也不代表提供方已消费结果或外部副作用恰好发生一次。Core 不自动重放结果。 + +### 待执行操作与恢复 {#required-actions-and-recovery} + +待处理函数在 Session 读取和 Session SSE 中显示为待执行操作 `{arguments, call_id, name, turn_id, type: "function_call"}`,直到原生应用、取消或终态结算。离线且有待处理输入的 `self_hosted` Environment 显示 `{environment_id, type: "environment_connection"}`([Environment](environments.md#activity-and-required-actions))。 + +SSE 仅提供实时事件。重启或流丢失后,读取 Session 的 `required_actions`;历史中的 `function_call` Item 不证明调用仍待处理。对于待处理函数,使用返回的 Session、Turn 与调用身份。如果应用已执行函数,应提交保存的结果,避免再次执行外部副作用。对于 Environment 操作,用其登记信息连接准确的 Environment。注册或操作消失均不证明模型运行过;读取 Turn 及其 Item 查看结果。重新连接不重放事件或外部副作用。 + +执行丢失后,Worker 将先前已领取的工作标记失败,不进行重放;排队工作可以继续排队。结果在准入后、原生观察之前被取消时,仍在内部保存,但可能没有公开输出 Item 和 `item.added`。 + +## 结构化输出 {#structured-output} + +`text.format` 接受 `{type: "json_schema", schema: {...}}`,即 Agents API 形式:不包含 `name`、`strict` 或其他 Responses API 包装字段。schema 被保存,经 Agent 和 Session 解析继承,并冻结于 Session 快照。对所有 Harness,显式非对象根类型在保存和 Session 创建时均为协议错误。Claude 要求 schema 根显式为 `type: "object"`。Claude SDK 将 JSON 数字读为 binary64,因此 Session 准入拒绝数字在转换中会变化的 schema;已保存 Agent 保留原值。 + +Core 在 `ExecutionControls.OutputFormat` 中携带 schema,仅对使用该选项的请求要求配置通过结构化输出资格验证,并要求 Runtime 具有 `structured_output` 和消息观察能力。冻结的 schema 在输入之前送达准备阶段,适用于初次和恢复执行;Start 不能替换它。 + +Claude 适配器将 `outputFormat` 传给固定版本 SDK,并允许原生 `StructuredOutput` 终态工具;该工具属于内部,不是额外的调用方函数。匹配的实时根工具结果和已归属的成功 SDK 结果确认输出。适配器将原生 `result.result` 字符串原样发布为已完成的 `final_answer` 消息,使用原生 tool-use ID;父 assistant 文本保留自己的 ID。未验证重试和已取消候选不会成为答案,适配器不会把 `structured_output` 重新序列化为 JSON。流遵循官方消息顺序,将整段文本放入一个 `output_text.delta`。桥接层仅在报告 `structured_output` 时声明该操作,工作区 Runtime 还需要 `workspace_structured_output`。 + +## 延迟函数发现 {#deferred-function-discovery} + +`tool_search` 工具仅包含 `type`;仅适用于 Responses 的执行字段被拒绝。函数 `defer_loading` 标记延迟加载的定义。发现要求两者同时存在:没有延迟函数的 `tool_search`,或没有 `tool_search` 的延迟函数均被拒绝。已保存 Agent 的工具联合类型保留 `tool_search`;固定版本 Session 响应联合类型省略它,因此 Session 与 SSE 资源投影移除它,冻结配置仍保留它。固定版本 Item 联合类型没有 tool-search Item,Core 不自行创建。 + +Core 发送 `PromptRequestPayload.ToolSearch` 和每个 `FunctionTool.DeferLoading`,并要求配置通过资格验证、Runtime 具备 `tool_search` 能力。原生搜索与 schema 延迟加载属于适配器。Claude 适配器的 MCP 服务器将立即加载定义标记为 `anthropic/alwaysLoad:true`,延迟定义标记为 false,并启用原生 ToolSearch;函数配置仅允许所声明回调、ToolSearch 与选定的工作区工具。工作区 Runtime 从桥接层的 `workspace_tool_search` 功能推导 `tool_search`。原生 Harness 管理模型与提供方策略;已知冲突模式和 beta 设置在适配器中拒绝,不透明策略改变后,SDK 不提供可靠的输入前信号来确认延迟加载是否生效。 + +## Web 搜索与程序化工具调用 {#web-search-and-programmatic-tool-calling} + +```json +[ + {"type": "web_search", "mode": "disabled"}, + {"type": "programmatic_tool_calling", "enabled": false} +] +``` + +已保存 Agent 保留固定版本的所有 `web_search` 模式:省略或 null 保存为 `live`,`cached` 和 `live` 原样保存([保存模式](wire-semantics.md#saved-configuration))。搜索设置是资源数据:省略或 null 的 `context_size` 解析为 `medium`;省略域名和位置解析为 null;空域名列表保持为空;提供的位置(包括 `{}`)包含 `city`、`country`、`region` 和 `timezone`,省略字段为 null。 + +执行仅允许 `mode: "disabled"` 和 `enabled: false`。启用或省略模式的搜索、启用或省略 `enabled` 的程序化调用,在 Session 准入时被拒绝,除非 Session 替换了保存的工具。省略程序化配置会保留各 Harness 原生行为,这与官方默认启用行为不同。无关的原生实用工具不会被移除。 + +`DisableProgrammaticToolCalling` 在初次执行和冷继续执行中携带禁用意图,仅在存在时要求 Runtime 能力。搜索使用已有禁用控制项。 + +| Harness | 原生执行约束 | +| --- | --- | +| Codex | 禁用 code-mode 功能;在启动或恢复线程前检查原生受管要求,拒绝被强制启用的冲突功能 | +| Claude SDK | 保留受限内置工具清单,并据此验证原生初始化 | +| MiniMax Code | 保留受限原生工具配置、空文本执行工具清单和禁用的 Web 搜索 | + +## HTTP MCP {#http-mcp} + +```json +{ + "type": "mcp", + "server_label": "tickets", + "transport": {"type": "http", "server_url": "https://mcp.example.com/mcp"}, + "connection_origin": "service", + "allowed_tools": ["lookup_ticket"], + "required": false +} +``` + +- `server_label` 非空且在 Session 中唯一。仅接受 `http` 传输;`server_url` 为不带凭据、查询或片段的绝对 HTTP 或 HTTPS URL。非空 `headers` 和 `request_metadata` 被拒绝。 +- [公开 MCP 连接来源](environments.md#public-mcp-connection-origin)定义来源默认值、部署位置和凭据权限;[Harness 能力](harness-capabilities.md#tools)定义各 Harness 支持范围。 +- 省略或 null 的 `allowed_tools` 允许所有服务器工具;`[]` 不允许任何工具。 +- `required: true` 使原生线程创建和冷恢复等待服务器初始化;失败会停止执行,不替换保留历史。它要求 Runtime 的 `mcp_http_required` 能力。等待期间公开工作可被接受或排队。 +- Bearer 认证使用附加的静态或 OAuth Vault 凭据。[Vault 凭据](vaults.md)定义选择规则,[MCP 凭据权限](environments.md#public-mcp-connection-origin)定义冻结的 Runtime 绑定。认证执行要求 `mcp_http_bearer_auth`。 +- Runtime 必须声明 `mcp_http_tools`。原生 Harness 管理发现、调用和结果;公开 `mcp_call` Item 使用原始服务器与工具名称,保留观察到的原生结果。 + +Codex 在启动或恢复线程之前验证准确的有效 MCP 配置,排除未声明服务器,禁用原生 apps 和 plugins,拒绝原生保留标签和已存储原生 MCP 凭据。Claude 接受 ASCII 字母、数字、下划线和连字符组成的标签,但不允许 `functions`;工具名称还可包含点,并要求已连接服务器具有静态工具清单。匿名 Claude 请求发送空 Authorization 头以阻止原生 OAuth 注入。不支持原生 OAuth 登录。 diff --git a/contracts/agents-api/zh/harness-capabilities.md b/contracts/agents-api/zh/harness-capabilities.md new file mode 100644 index 00000000..b947daaf --- /dev/null +++ b/contracts/agents-api/zh/harness-capabilities.md @@ -0,0 +1,63 @@ +--- +title: "Harness 能力" +source: contracts/agents-api/harness-capabilities.md +source_hash: e1ffc7a260ceaec64ba377f7c0db28f2c371c9d664098b110b740cc110506506 +--- + +本页列出每个 Harness 在每种部署位置支持的能力。Core 根据 `services/core/internal/engine` 中 Harness 的引擎配置决定准入,运行 Session 的 Runtime 也必须声明操作。所链接契约定义各操作;[Harness 接入](harness-onboarding.md#qualify-the-adapter)说明资格验证方法。 + +| 状态 | 含义 | +| --- | --- | +| 已验证 | Core 允许准入,且该位置通过固定版本官方客户端的真实模型验收 | +| 已准入 | Core 通过相同 Runtime 路径允许准入,但该位置尚未运行真实模型验收 | +| 已拒绝 | Core 在执行前拒绝请求 | + +部署位置为 `none`(无 Environment)、托管(`openai_hosted`)和自托管(`self_hosted`)。托管验收在 Docker 节点运行;E2B、microsandbox 使用相同 Runtime 与适配器,托管位置的每个已验证单元格在这两者中视为已准入。自托管验收在 Linux 机器运行;[自托管指南](../../../docs/zh/getting-started/self-hosted.md#platforms)列出支持平台。操作通过验证仅代表该操作本身,不代表与其他选项的所有组合;配置拒绝的组合列于对应操作契约。 + +## 执行与输入 {#execution-and-input} + +| 操作 | Codex | Claude SDK | MiniMax Code | +| --- | --- | --- | --- | +| 文本 Turn、活动输入、取消、重启与继续 | 所有位置已验证 | 所有位置已验证 | 所有位置已验证 | +| [文件与 Artifact](environment-files.md) | 已验证:托管、自托管 | 已验证:托管、自托管 | 已验证:托管、自托管 | +| [仅空白消息文本](message-content.md) | 已准入;原样交付 | 已拒绝 | 已拒绝 | +| [内联 PNG、JPEG 消息图像](message-content.md) | 所有位置已验证 | 所有位置已验证 | 已拒绝 | +| 远程图像 URL | 已拒绝 | 已拒绝 | 已拒绝 | +| 显式 `reasoning`;非 `auto` 的 `service_tier` | 已拒绝 | 已拒绝 | 已拒绝 | +| 非 `medium` 的 `text.verbosity` | 已准入;由原生模型决定 | 已拒绝 | 已拒绝 | +| [公开 token 用量](sessions-events.md) | 测量计数器 | Null | Null | + +各 Harness 原生模型参数及提供方协议见[模型执行](model-execution.md)。 + +## 工具 {#tools} + +| 操作 | Codex | Claude SDK | MiniMax Code | +| --- | --- | --- | --- | +| 文本结果的[公开函数](execution-tools.md#functions) | 已验证:`none`、托管;已准入:自托管 | 所有位置已验证;仅对象根 schema | 已拒绝 | +| [含图像函数结果](message-content.md#function-results) | 已验证:`none`、托管;已准入:自托管 | 所有位置已验证;仅成功结果中的内联 PNG 或 JPEG | 已拒绝 | +| [结构化输出](execution-tools.md#structured-output) | 已拒绝 | 所有位置已验证 | 已拒绝 | +| [延迟函数发现](execution-tools.md#deferred-function-discovery) | 已拒绝 | 已验证:`none`、自托管;已准入:托管 | 已拒绝 | +| [禁用 Web 搜索与程序化工具调用](execution-tools.md#web-search-and-programmatic-tool-calling) | 已验证:`none`;已准入:托管、自托管 | 已验证:`none`;已准入:托管、自托管 | 已验证:`none`;已准入:托管、自托管 | +| 启用 Web 搜索或程序化工具调用 | 已拒绝 | 已拒绝 | 已拒绝 | +| [服务端来源 HTTP MCP](environments.md#public-mcp-connection-origin) | 已验证:`none`;其他位置拒绝 | 已验证:`none`;其他位置拒绝 | 已拒绝 | +| [Environment 来源 HTTP MCP](environments.md#public-mcp-connection-origin) | 已验证:托管、自托管 | 已验证:托管、自托管 | 已验证:托管、自托管;仅 `allowed_tools` null、`required` false | +| [HTTP MCP bearer 凭据](execution-tools.md#http-mcp) | 已验证 | 已验证 | 已验证 | +| 必需 MCP 初始化 | 已验证 | 已验证 | 已拒绝 | +| [Subagent](subagents.md) | 已验证:托管;已准入:`none`、自托管 | 已验证:托管;已准入:`none`、自托管 | 已验证:托管;已准入:`none`、自托管 | +| Subagent 与函数或 HTTP MCP 同时使用 | 已拒绝 | 已拒绝 | 已拒绝 | + +Claude 结构化输出要求单 Agent、medium verbosity,且无 Skill、Plugin、能力目录、MCP 或 `tool_search`。Claude 延迟发现要求单 Agent,除 `tool_search` 与禁用控制项外仅函数工具,无 Skill、Plugin 或能力目录,也无结构化输出。Claude 在工作区位置需要每种操作的打包桥接功能(`workspace_functions`、`workspace_structured_output`、`workspace_tool_search`、`workspace_mcp_http`)。 + +## Environment 准备 {#environment-preparation} + +这些操作要求工作区,因此仅适用于托管与自托管位置。 + +| 操作 | Codex | Claude SDK | MiniMax Code | +| --- | --- | --- | --- | +| [初始文件、设置命令、Skill 与 Plugin](environments.md#runtime-capability-preparation) | 已验证:托管、自托管 | 已验证:自托管;已准入:托管 | 已验证:自托管;已准入:托管 | +| 能力目录 | 已准入 | 已准入 | 已准入 | +| npm 与 Python 包 | 已准入 | 已准入 | 已准入 | +| `packages.system` | 已拒绝 | 已拒绝 | 已拒绝 | +| 网络 `disabled` 或 `restricted` | 已拒绝 | 已拒绝 | 已拒绝 | +| [stdio Plugin MCP](environments.md#plugin-mcp) | 已验证:托管、自托管 | 已验证:自托管;已准入:托管 | 已验证:自托管;已准入:托管 | +| HTTP Plugin MCP | 已准入,字面值头或 HTTPS bearer | 已准入,匿名或 HTTPS bearer | 已验证:自托管;已准入:托管;匿名或 HTTPS bearer | diff --git a/contracts/agents-api/zh/harness-catalog.md b/contracts/agents-api/zh/harness-catalog.md new file mode 100644 index 00000000..115f99bb --- /dev/null +++ b/contracts/agents-api/zh/harness-catalog.md @@ -0,0 +1,12 @@ +[//]: # (Generated by scripts/generate-harness-catalog.py; DO NOT EDIT.) +# 内置 Harness 注册 {#built-in-harness-registrations} + +注册列表的源文件是 [`catalog.json`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/harnessconfig/builtin/catalog.json)。修改后运行 `make generate-harness-catalog`;`make check-harness-catalog` 检查生成结果。 + +| 标识符 | 显示名称 | 模型配置声明 | Core 验收构造函数 | +| --- | --- | --- | --- | +| `claude_sdk` | Claude Code | `claudesdk.Configuration` | `claudeProfile` | +| `codex` | Codex | `codex.Configuration` | `codexProfile` | +| `mcode` | MiniMax Code | `mcode.Configuration` | `mcodeProfile` | + +配置声明位于 `internal/harnessconfig/`,验收构造函数位于 `services/core/internal/engine`。这些注册描述当前构建。部署启用的 Harness 由 `core.harnesses` [进程设置](../../../docs/zh/configuration.md#settings)决定;[Harness 能力](./harness-capabilities.md)列出各 Harness 的支持范围,连接的 Runtime 报告自身可用性。[Harness 接入](./harness-onboarding.md)介绍适配器和打包步骤。 diff --git a/contracts/agents-api/zh/harness-onboarding.md b/contracts/agents-api/zh/harness-onboarding.md new file mode 100644 index 00000000..aa984be2 --- /dev/null +++ b/contracts/agents-api/zh/harness-onboarding.md @@ -0,0 +1,266 @@ +--- +title: "将原生 Harness 添加到 OpenAgentCore" +source: contracts/agents-api/harness-onboarding.md +source_hash: 526356d82bd6b69e0c848b570d4944cd4a5872f8bb85eee0fb99124da0f1b8bd +--- + +**Harness** 是一种运行模型和工具循环的原生代理引擎(Codex、Claude Code、MiniMax Code)。**Harness 适配器**将 Runtime 的 Executor 和 Turn 契约转换到该引擎的 SDK 或协议。本文档定义 Runtime–Harness 协议:适配器接口及其生命周期义务、注册、Core 资格认定和验收。[Harness capabilities](harness-capabilities.md) 记录了当前每个 Harness 支持的功能。 + +从两个入口开始: + +- [`internal/harnessconfig/harness.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/harnessconfig/harness.go):共享模型配置契约(声明和准备)。 +- [`agent/harness.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/apps/daemon/internal/agent/harness.go):执行生命周期、扩展契约和注册方法。 + +## 所有权 {#ownership} + +```text +Core: Session, Turn, immutable configuration, durable events + | + common Runtime protocol + | +Runtime: Executor preparation, reuse, idle expiry, recovery + | + Harness adapter package + | + native SDK, process or connection +``` + +| 组件 | 职责 | 位置 | +| --- | --- | --- | +| Core | 公共 API、控制权、持久状态、调度和配置快照 | `services/core` | +| Runtime | 经身份验证的连接、共享能力准备以及通用 Executor 和 Turn 生命周期 | `apps/daemon/internal/dispatch` | +| Adapter | 原生配置、资源、API 调用、事件转换和限制 | `apps/daemon/internal/agent/` | +| Harness | 原生模型和工具循环以及历史记录 | 锁定版本的 SDK 或可执行文件 | +| 服务 profile | 对已认定合格的操作和放置位置进行纯验证 | `services/core/internal/engine` | +| 注册 | 适配器声明、已安装工厂和已验证能力 | `apps/daemon/internal/agent//declaration.go`;`apps/daemon/internal/cli/agent_discovery.go` 中的静态列表 | + +Environment 提供执行资源。受管 E2B、Docker 和 microsandbox 机器以及应用自有机器在预配和连接方式上有所不同;已连接的 Runtime 使用同一契约。daemon 运行于 Linux、macOS 和 Windows,受管 Provider 仅支持 Linux,并且每个适配器自行认定其支持的平台([self-hosted platforms](../../../docs/zh/getting-started/self-hosted.md#platforms))。只有在 Runtime 加载绑定的已安装快照后,原生工厂才会收到能力([capability preparation](environments.md#runtime-capability-preparation))。模型 Provider 提供模型通信设置,而不负责 Turn 调度或原生进程所有权。 + +## 步骤 {#steps} + +1. **锁定原生来源。** 记录上游包版本和源修订版本,并在适配器旁记录原生入口点。 +2. **实现适配器**,位置为 `apps/daemon/internal/agent/`:实现 `ExecutorFactory`、`Executor` 和 `Turn`([required interfaces](#required-adapter-interfaces)、[lifetimes](#executor-and-turn-lifetimes))。复用共享的进程、凭据、配置和本地工作区辅助函数。 +3. **在适配器中声明 kind**,并将其声明添加到 `apps/daemon/internal/cli/agent_discovery.go` 中 Runtime 的静态列表([register the adapter](#register-the-adapter))。 +4. **添加服务 profile 和一个目录条目**([add the engine to Core](#add-the-engine-to-core))。 +5. **打包原生先决条件。** 在 `services/core/deploy/` 下添加 Runtime 镜像,并可选添加 [native installer participation](#native-installer-participation)。 +6. **启用并选择引擎**,通过 `core.harnesses` 设置和 [Harness selection](model-execution.md#harness-selection) 完成。 +7. **认定其资格**([qualify the adapter](#qualify-the-adapter)),并将结果记录到 [Harness capabilities](harness-capabilities.md)。 + +实现强制的文本生命周期,并明确处理每一种扩展。逐个认定受支持扩展的资格;未认定资格的扩展返回 `agent.ErrUnsupportedOperation`,且不会产生原生副作用。原生取消可能要求退役而非复用:`Reusable=false` 会携带原因,调用方必须确认 `Executor.Close`。不要为了适配测试辅助函数而强制复用,也不要将适配器的原生限制复制到共享 Core 协议中。 + +## 架构规则 {#architecture-rules} + +- Codex、Claude Code 和未来的 Harness 地位平等。通用 Runtime 线协议以及 Executor 和 Turn 接口负责生命周期、输入回执、取消、恢复和资源访问;每个适配器保留其原生实现以及模型和工具循环。 +- 新引擎需要提供适配器、已认定合格的 profile、注册以及经过独立验证的部署。它不得在 API 处理程序、持久化、调度、调度器或 Environment Provider 中添加按引擎名称分支的实现,也不得为契约已经涵盖的能力添加处理程序、存储表、调度器、事件投影器或模型循环。 +- 将必需的生命周期声明、扩展接口和注册方法保留在 `agent/harness.go` 中。结果类型、错误和 Registry 存储可以保留在聚焦的文件中。 +- 使用现有的 `proto.SupportedAgentKind` 和 `AgentKindCapabilities` schema。不要添加第二套能力描述符或组合式可选接口。 +- 接入不要求功能完全一致。Harness 不必匹配彼此的可选功能,并且注册时不强制要求 MCP、函数、图像或详细程度控制。验证通用生命周期义务,并对每个声明的操作使用相同的公共断言。缺少声明或扩展实现会阻止接入;原生差异不会。 +- 服务 profile 目录是资格认定边界。未知 profile 以关闭方式失败,并且 Runtime 心跳无法授权新的公共功能。Schema 有效性、服务资格认定和可用 Runtime 是相互独立的检查。 +- 绝不能将已接受的参数等同于已实际应用的原生行为。 + +## 必需的适配器接口 {#required-adapter-interfaces} + +[`agent/harness.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/apps/daemon/internal/agent/harness.go) 是接口入口。必需的生命周期包括 `ExecutorFactory`、`Executor`、`Turn`(包括 `DurableSteerer`)和 `TurnSettlement`。必需方法必须履行其原生义务;返回 Unsupported 并不构成对取消、回执、结算或清理的实现。Turn 和工作区扩展接口应保持小而独立,但每个公共适配器都必须明确实现每一个接口。所有接口都使用中立协议类型。 + +例如,Codex 适配器保留其 app-server 和 thread,Claude 适配器保留一个流式 Query,MiniMax 适配器保留其 ACP 连接和原生 session。它们都公开相同的 Executor 和 Turn 契约。原生回调和资源保留在适配器内部;Runtime 负责准入、空闲过期和替换。取消通过 `agent.Session` 精确定位到目标 Turn,适配器则向 Runtime 提供原生完成证据。 + +| 接口或契约 | 必需处理 | 义务 | +| --- | --- | --- | +| `ExecutorFactory`、`Executor.StartTurn`、`Executor.Close` | 真实实现 | 在没有模型输入的情况下准备;保留失败或不确定资源的所有权;确认清理 | +| `Session`、`Turn`、`CancellationOutcome`、`AwaitSettlement` | 真实实现 | 取消精确的 Turn,保留已观察结果,并独立于取消请求确认结算 | +| `DurableSteerer` | 每个 Turn 上真实实现 | 区分完整写入与原生应用回执;保留重试身份 | +| `Steerer` | 明确实现或 Unsupported | 额外的非持久化活动 Turn 输入 | +| `FunctionResultSubmitter` | 明确实现或 Unsupported | 匹配原生调用和结果身份,并确认应用 | +| `PermissionResponder`、`UserChoiceResponder` | 明确实现或 Unsupported | 响应精确发出的身份;未知或已过期的交互与 Unsupported 保持区分 | +| `WorkspaceReader`、`WorkspaceDirectoryLister`、`WorkspaceWriter` | 在 Turn、Executor 和 Prepared 所有者上明确实现 | 使用授权工作区,确认访问,提交或关闭,或者返回该操作的 Unsupported 错误 | +| `Prepared`、`PreparedCancellation` | 对可执行准备进行真实实现 | 在 Start、取消和未使用清理之间保持资源和输出的所有权 | +| 中立消息、图像、MCP、结构化输出和 Subagent 观察 | 明确作出能力决策 | 保持每项操作的协议语义;在提交前拒绝不受支持的输入 | + +每个适配器的 `contracts.go` 都包含针对每个小型接口的单项编译时断言。不要嵌入会让未来接口看起来已经实现的默认实现。添加契约时,还必须在通用完整性检查中进行分类,并在每个公共适配器中添加明确断言;该检查遵循已编写的 Harness 目录。 + +对于设计层面的拒绝,请直接实现该方法: + +```go +func (s *Session) SubmitFunctionResult(context.Context, proto.FunctionResultPayload) error { + return fmt.Errorf("%w: native public function tools are not qualified", agent.ErrUnsupportedOperation) +} +``` + +原因必须是固定的安全字符串,绝不能是已提交内容、凭据或原始原生诊断信息。Unsupported 保证不会产生原生副作用,也不表示操作成功且为空。安装不可用、未知交互 ID、原生失败和不确定结果应保留各自的错误和所有权。nil `Turn` 仍表示没有提交任何输入,并且输出归调用方所有;绝不能将其用作 Unsupported 标记。 + +线协议请求不携带工作目录。Runtime 将 `local_environment.workspace_directory` 与其绑定进行核对,并通过 `LocalEnvironment.WorkspaceRoot` 向 Harness 提供其绑定的工作区目录;必须在该目录中运行原生 Harness。 + +工作区能力描述实际 Runtime 与资源所有者的组合。Codex 和 MiniMax 资源对象拒绝原生工作区访问,而通用的授权 `localworkspace` 所有者提供该访问;Claude 可以公开原生读取和列举访问,通用所有者提供写入。仅仅存在相应接口绝不会选择某个资源或宣称支持。 + +服务 profile 对公共组合进行资格认定,Runtime 宣称已安装的组合;二者都不能替代 schema 验证或 Project 授权。原生行为测试必须与声明一致。已宣称但返回 Unsupported 的操作属于契约违规,既不是成功,也不能作为重放的依据。 + +## Executor 和 Turn 生命周期 {#executor-and-turn-lifetimes} + +| 生命周期 | 所有者 | 结束条件 | +| --- | --- | --- | +| Environment 分配 | Sandbox Provider | 显式回收,并与 Runtime 执行协调 | +| Runtime 连接 | Runtime 传输层 | 断开连接或被较新的连接替换 | +| 已安装能力快照 | Runtime | 其 Environment 被回收;绝不因 Executor 关闭而结束 | +| Session Executor | Runtime | 空闲过期、关闭或确认失效时执行 `Executor.Close` | +| Turn | 由 Runtime 跟踪的适配器 `Turn` | `AwaitSettlement` 确认结算完成 | + +Session 在其已连接的 Runtime 中拥有一个可复用的 Executor;Turn 拥有一次输入执行、其输出流和其取消操作。`agent.ExecutorFactory` 在没有模型输入的情况下准备固定配置,而 `Executor.StartTurn` 创建新的 `agent.Turn`,不替换健康的原生资源。正常完成仅结算 Turn。`Executor.Close` 在空闲过期、Environment 关闭或确认失效时释放原生资源;它既不释放 Environment 分配,也不释放工作区。Core 不保留第二套 Executor 缓存。相同的生命周期适用于托管、自托管和 `none` 放置方式。 + +**绑定。** Runtime 将其 Executor 记录绑定到 Session、Environment、连接和不可变执行配置。恢复身份和先前 Turn 恢复标志是连续性断言,而不是配置更改。提供的原生身份必须与保留的所有者匹配;当需要现有历史时,恢复绝不能启动新的根。配置冲突属于错误,而不是热切换。连接丢失会让其所有者和句柄退役;旧计时器、输出和取消操作不能影响替代对象。 + +**每 Turn 状态。** 每个 Turn 都会获得全新的包装器、输出通道和回执状态。引导、函数、权限和用户选择接口均属于该 Turn。原生回调必须在异步工作开始前捕获来源 Turn,因此迟到事件绝不会被归到当前活动的 Turn 上。原生进程、query 或传输连接、固定能力配置和原生 session 身份均属于 Executor。不要重置已完成的 `sync.Once` 值,也不要复用旧 Turn 对象。 + +**开始。** `StartTurn` 返回 nil Turn,保证没有提交任何原生输入,也没有保留输出通道;随后由 Runtime 关闭该通道。一旦输入可能已经提交,即使同时返回错误,也必须返回非 nil Turn:该 Turn 拥有恰好一次的输出关闭权,并在结算前持续接受跟踪。未知输入绝不能重放。明确的 `executor_unavailable` Start 拒绝允许进行一次通用恢复尝试,但只能在此前 Executor 已关闭且未提交输入之后进行;Runtime 会重新检查同一物理对端和当前授权。 + +**取消和结算。** `Turn.Cancel` 仅以目标 Turn 为对象,不会关闭健康的 Executor。`AwaitSettlement` 同时适用于自然完成和取消。成功意味着输出已无法再写入,并且该 Turn 的原生事件、输入、函数、交互和子任务均已结算。原生完成或取消确认独立于资源退役:关闭传输层无法提供缺失的原生终态或操作回执。 + +- `Reusable=true` 还要确认原生所有者能够接受下一个 Turn。`Reusable=false` 要求提供原因,并在之后确认 Executor 已关闭。 +- 错误表示结算尚未确认,既不释放所有权,也不释放容量。调用方截止时间只会停止等待,不会停止受跟踪的清理。必须串行重试同一个清理目标;清理失败会阻止替换并保留其资源槽位。 +- `Executor.Close` 独立于 Turn 结果确认资源退役:不可变的 Turn 错误不得阻止在其工作和输出已经停止后关闭原生传输层。 +- 结算必须包含所属的后台工作,并在失败后保留精确的原生清理目标。原生终止由适配器负责;仅有批量清理确认并不能证明已达到静默状态。 +- 每个 `Session`(包括直接调用工厂的结果)都要声明 `CancellationOutcome`。`Turn` 和 `PreparedCancellation` 继承该声明。快照保留已观察到的原生身份、Usage 和输出,并在取消后仍可读取。缺失的证据保持未设置;空的 `DonePayload` 表示未观察到任何内容,而不是表示取消成功或不受支持。读取快照不会等待结算。 +- 直接调用的 `Session.Cancel` 请求取消;输出关闭表示拆卸开始。可执行准备中的 `PreparedCancellation.Cancel` 会等待本地清理和输出写入停止。Turn 结算仍需要 `AwaitSettlement` 和所需的任何 `Executor.Close`;取消请求成功或其快照都不能替代这些等待。 + +**Runtime 在 Turn 前后执行的工作。** 一个输出消费者会在原生 Start 之前启动,耗尽有界的 64 帧通道,并将终态观察保留到 Start 发布、Turn 结算和已准入操作回执完成为止。正常完成绝不调用 Cancel。输入、函数和交互准入会在结算前关闭;已准入的操作会持有其屏障,直至原生回执和出站确认完成。Runtime 会在等待该屏障之前向 Turn 发送取消,因为已写入的输入可能需要原生中断才能生成回执。Runtime 会汇合原生结算、所需的已确认 Executor 关闭、输出耗尽和所有已准入操作,然后应用确认或执行复用,之后才会转发 Done 或已应用的取消回执。Close 失败可以报告失败,同时保留同一 Run 和未完成操作以供重试;已关闭的调用方等待无法凭空生成已应用输入回执。Runtime 会在发布 Done 前提交原生连续性状态并释放旧 Run 的准入,因为接收方可能立即启动另一个 Turn;迟到的终态发送失败属于旧 Run,不能使已拥有 Executor 的后继对象失效。连接关闭负责传输丢失清理。结算等待时间为十秒,回执发送预算为五秒;超时不能证明已达到静默状态。 + +## 事件、输入和可选能力 {#events-inputs-and-optional-capabilities} + +使用 [`internal/agentdaemon/proto`](https://github.com/MiniMax-AI/OpenAgentCore/tree/main/internal/agentdaemon/proto) 处理中立请求、事件和回执。每个 Turn 只按顺序发出带有其 Run ID 的自身事件,并产生一个终态结果。原生 ID 和 usage 必须来自观察,绝不能虚构;缺失的度量值表示未知,而不是零。 + +初始输入和引导使用有序的 `proto.MessageInput`。必须保持用户消息顺序和内容顺序。仅支持文本的适配器通过 `TextOnly()` 拒绝图像,而不是丢弃图像;图像适配器在原生环境中转换每个部分,并且只有在其所有消息均已应用后才确认活动批次。成功传输写入与确认原生应用是不同的事件。用户选择答案使用发出的问题 ID 和值数组;共享的 `PromptForUserChoiceDecisionPayload.AnswersFor` 会在消费待处理交互之前验证身份,因此绝不能按标头或位置映射答案。只能恢复绑定到 Session 的精确历史;缺失、含糊或外部历史会在新的模型输入之前导致失败。设备身份不代表原生 session 所有权。 + +### 必需操作和扩展操作 {#required-and-extension-operations} + +公共文本路径要求持久化 Turn、已应用输入回执、有序观察、取消,以及执行已禁用的执行控制;`execution.Policy.engineCapabilities` 保存精确要求。没有原生工具的引擎可以保证这些工具不存在;具有工具的引擎在收到要求时必须实际禁用它们。接受某项配置不能证明其已得到执行。 + +MCP、公共函数、延迟函数发现、结构化输出、图像输入、详细程度控制和其他可选操作不必匹配另一个引擎。使用 Unsupported 拒绝未认定资格的组合并记录差距;绝不能宣称某项能力来绕过选择。 + +- 结构化输出:读取 `ExecutionControls.OutputFormat`,并通过 Message 契约发布已确认的原生输出([execution tools](execution-tools.md#structured-output))。公共资格认定与 Runtime 能力分别注册。 +- 图像:分别注册 Runtime 的 `MessageImages` 并认定 profile 的 `MessageImages` 资格([message input](message-content.md))。 +- 工作区放置方式还需要经过验证的准备过程、工作区读取和输出导出,以及使用共享 Files 辅助函数的专用 Runtime 绑定。只有在展示其生命周期行为后才能启用放置方式。 + +### MCP 来源和原生限制 {#mcp-origin-and-native-limits} + +在引擎 profile 的 `MCPOrigins` 中声明受支持的公共来源,并在 `MCPBearer` 中声明 bearer 支持。Runtime 宣称其实际 HTTP、bearer 和必需初始化能力。共享准入负责验证来源和放置位置;适配器验证负责保留原生标签、允许列表和初始化限制。 + +使用 `agent.ResolveMCPBindings` 处理公共声明和已安装声明,并保留来源、凭据权限、`null` 与空允许列表之间的区别以及必需启动过程。不要将令牌复制到原生 profile 中,也不要将服务请求重新解释为 Environment 请求。拒绝不受支持的原生策略,而不是将其丢弃。遵循 [MCP origin contract](environments.md#public-mcp-connection-origin),并对每个宣称的组合执行公共客户端、失败、取消和冷恢复资格认定。模型能力与 Harness 传输支持相互独立;绝不能从模型名称推断模型能力,也绝不能静默降低输入质量。 + +### Subagent 观察 {#subagent-observations} + +支持 Subagent 读取的 Harness 必须实现[中立观察契约](subagents.md#adapter-contract)。它通过经身份验证的 Run 报告经过验证的子项身份、生命周期影响以及所属的 Turn 和 Item 历史,并通过真实执行认定这些事实,且不使用额外路由、存储分支或 Harness 专用调度器。明确报告不受支持的原生事实;完成子任务并不等于关闭其 Subagent。原生后台工作的所有权必须保持到结算和取消完成为止。 + +## 注册适配器 {#register-the-adapter} + +注册是静态的,并且需要构建。从 `apps/daemon/internal/agent//declaration.go` 导出一个 `agent.Declaration`,然后将其添加到 [`cli/agent_discovery.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/apps/daemon/internal/cli/agent_discovery.go) 的 `harnessDeclarations` 中。声明包含 kind、完整能力描述符、共享模型 `Configuration` 和 `Discover` 函数。发现过程接收 profile 和诊断写入器,负责原生配置和可用性检查,并返回已安装的 `agent.Runtime` 及其描述符、session 工厂、准备工厂和 Executor 工厂。未配置适配器时返回 nil;已配置的前置条件失败时,返回不可用描述符和 session 工厂。将版本门控和工厂选择条件保留在适配器内部。 + +`Runtime.SessionCapabilityContext` 和 `Runtime.ExecutorCapabilityContext` 会为相应的执行工厂显式请求能力下载 URL 解析和限定范围的产品上传上下文。准备过程绝不会收到这些影响。支持产品工作区创作功能的适配器自行声明 `WorkspaceAuthoring`;通用注册不会授予该能力。 + +[`cli/agent_registration.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/apps/daemon/internal/cli/agent_registration.go) 遍历已发现的 Runtime,并调用 `agent/harness.go` 中的 `Registry.Register`。它验证发现过程是否保留了声明的 kind,并按以下顺序安装工厂: + +| 顺序 | 方法 | 注册内容 | +| --- | --- | --- | +| 1 | `RegisterKind(proto.SupportedAgentKind, harnessconfig.Configuration, agent.Factory)` | Kind、可用性、版本、`AgentKindCapabilities`、模型配置声明和直接调用工厂。它会重置其他注册项,因此必须首先调用。 | +| 2 | `RegisterExecutor(kind, agent.ExecutorFactory)` | 执行所用的 Executor 和 Turn 生命周期;据此派生 `Preparation` 能力 | +| 3 | `RegisterPreparation(kind, workspaceRead, agent.PreparationFactory)` | 可选:针对已认定合格的工作区操作的独立只读工作区准备 | + +直接调用的 `agent.Factory` 委托给同一个 Executor 实现。 + +每个 `proto.AgentKindCapabilities` 字段都必须显式设为 `proto.CapabilitySupported` 或 `proto.CapabilityUnsupported`,即使 Harness 不可用也是如此。`proto.CapabilityUnspecified` 无效:零值和省略字段绝不表示 Unsupported。安装探测可以使用 `proto.CapabilityFromBool` 设置单个字段;但不得填充未提及字段或未来字段。可用性通过 `SupportedAgentKind.Available` 单独表示。注册会在更改 registry 之前验证完整声明;线协议会为每个字段携带显式布尔值,因此省略字段和 null 字段均无效。添加新字段时,每个生产声明都必须作出决定。Runtime 使用者应调用 `IsSupported()`,并在原生操作前拒绝不受支持的请求;接口断言用于验证实现,绝不表示支持。每个声明都必须与针对该安装验证的行为一致;[Core–Runtime protocol](../../../docs/zh/runtime-protocol.md#capability-declarations) 负责声明的传输方式和冻结方式。 + +准入映射是显式的。`Steering` 控制非持久化 `Steerer` 输入。`DurableInputReceipts` 控制 `DurableSteerer` 输入,并且还要求 Turn 结算契约;二者互不隐含,而且 Core 的公共文本 profile 要求同时具备二者。`Permissions` 一起认定权限响应和用户选择响应的资格,并要求两条原生响应路径均存在。工作区声明描述授权资源所有者,包括通用 Runtime 工作区实现。Runtime 注册不会授予 Core 资格;服务 profile 才会授予。 + +可运行的仅测试示例 [`testdata/onboarding/main.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/apps/daemon/testdata/onboarding/main.go) 会注册一个仅支持文本的合成 Harness。它展示 Session 所有的 Executor、全新的 Turn、持久化引导、取消和历史绑定,并且绝不会发布。 + +## 将引擎添加到 Core {#add-the-engine-to-core} + +Core 会识别[内置 Harness 注册项](harness-catalog.md)。向 `internal/harnessconfig/builtin/catalog.json` 添加一个条目,其中包含: + +- 公共 `kind` 和显示 `label`; +- `internal/harnessconfig` 下的模型 `configuration` 包; +- `services/core/internal/engine` 下的 `profile` 构造函数。 + +实现 profile 构造函数,然后运行 `make generate-harness-catalog`。它会生成模型配置 registry、Core profile 目录、客户端标识符和显示名称以及注册参考;公共输入验证器读取生成的 registry。`make openapi` 从同一目录派生 Harness 枚举,因此不要在 DTO 标签或路由注解中添加手写枚举。`make check-harness-catalog` 会拒绝过时的投影。 + +每个 Runtime 声明都引用生成的目录所使用的同一个 `internal/harnessconfig/.Configuration()`,并负责其原生工厂、探测和已安装能力证据。目录不能声明某台机器的可用性,也不存在动态插件加载器。 + +profile 是纯逻辑:它使用现有的公共类型和协议类型,声明受支持的放置方式、公共配置、结果限制和必需的 Runtime 控制。Profile 回调不能查询业务数据、解密凭据或控制原生进程。共享调度检查能力组合,而不是引擎名称允许列表。 + +### 显式服务资格认定 {#explicit-service-qualification} + +`engine.Profile` 是服务的资格声明,独立于 Runtime 的 `AgentKindCapabilities`。其能力字段复用小型 `proto.CapabilitySupport` 值类型:每个字段都必须显式选择 `CapabilitySupported` 或 `CapabilityUnsupported`。`CapabilityUnspecified`(包括省略字段)会被拒绝。复用此值类型并不意味着 Runtime 的宣称可以授予服务授权。 + +`ConfigurationValidation`、`ToolsValidation` 和 `FunctionResultValidation` 分别选择以下两种策略之一: + +- `CommonValidationOnly`:通用 schema 和准入检查已足够。相应回调必须为 nil;不需要提供成功的占位回调。 +- `AdditionalValidation`:相应的 `ValidateConfiguration`、`ValidateTools` 或 `ValidateFunctionResult` 回调为必需项,并添加纯 Harness 限制。 + +省略或未知策略、缺少必需回调,或者将回调与仅通用策略搭配,均无效。准入遵循声明的策略,而不依据方法是否存在。保留现有错误优先级:配置限制最先执行;选择额外配置验证时,工具解码错误先于工具限制。对于仅通用的配置验证,额外工具限制仍保持其相对于解码错误的现有优先级。仅通用的函数结果验证不会添加原生结果限制。 + +`engine.NewCatalog` 会在发布不可变快照之前验证每个条目,并对无效静态注册触发 `engine.ErrInvalidDeclaration` panic。Kind 必须非空且前后不得包含空白字符。放置方式必须显式列出至少一个受支持的放置方式;MCP 来源必须是非 nil 列表(空列表表示不认定任何来源合格)。未知或重复选项、没有对应放置方式的来源,以及没有 MCP 来源的 bearer 支持均会被拒绝。错误应标识已编写的字段,但不回显声明值。未来 profile 字段必须由完整性验证器分类,并由每个 profile 显式决定;不存在生产用默认填充构造函数。 + +运行 `engine` 和 `execution` 测试以覆盖遗漏、策略、组合和错误优先级,并运行公共接入测试和存储测试以覆盖准入和 Runtime 调度。测试夹具使用 `engine/enginetest`,其穷尽式字面量在添加字段时也要求作出决定;它不是生产 profile。 + +`execution.Policy` 向 HTTP 准入、Worker 设备选择和最终调度提供不可变服务资格认定。自定义组合将同一个 Policy 提供给 `api.Dependencies.Policy` 和 Core 调度器的 `Policy`。零值使用内置 profile;显式空目录不授权任何内容。不存在可变全局注册。 + +## 原生模型配置 {#native-model-configuration} + +[`internal/harnessconfig/harness.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/harnessconfig/harness.go) 负责共享配置声明和纯准备契约。每个适配器在 `internal/harnessconfig/` 中提供一个 `Configuration`,供 Core 组合和 Runtime 的 `RegisterKind` 使用。直接调用工厂、准备路径和 Executor 路径都会在产生原生副作用之前通过该声明进行验证,而 Registry 包装器会将声明与工厂保留在一起。线协议对象是 `proto.HarnessConfig`。[Model execution](model-execution.md#native-model-parameters) 列出了每个 Harness 接受的字段。 + +提供的 `model` 必须是非空字符串,并且显式指定 `model_provider` 时必须提供它。原生所有权连接路径可以省略二者;显式 null 无效。显式为空的声明不接受任何 Provider 或非空原生参数,也不宣称支持 Provider。未知协议格式和重复协议声明会导致注册失败。 + +声明中的有序 `protocols` 列表是接受协议及默认协议(第一个条目)的唯一来源;它还为 Core 的配置支持描述符提供数据,Core 和 Runtime 通过它拒绝不受支持的组合。适配器通过原生配置直接连接;它们绝不引入模型 API 代理或协议转换器、第二套模型能力 registry,也不会从模型名称推断能力。Claude 的私有 bridge 接收编译后的原生选项,并且只执行结构检查,而不是声明规则的第二份副本。 + +## 认定适配器资格 {#qualify-the-adapter} + +开始前,记录操作集、预期结果、排除项和停止条件。当其声明的操作通过时,资格认定即结束;它不会扩展为匹配另一个 Harness 的功能列表。 + +1. **契约测试。** 在名为 `TestSharedTextLifecycle` 的测试中,使用适配器准备好的 Executor 和确定性的原生夹具调用 `agent/contracttest.TextLifecycle`;`claudesdk/executor_test.go` 是参考实现。它检查独立的 Turn 流、原生所有者和历史连续性、持久化写入与应用回执、过期取消,以及取消后的健康继续执行。`make check-runtime-contract` 会将它与共享线协议、gateway、传输层和调度器测试、声明完整性检查以及每个适配器的 `TestUnsupportedExtensionsHaveNoNativeEffects` 一起运行。适配器测试还覆盖两个普通 Turn 共享一个原生进程或连接和历史、取消后执行另一个 Turn、过期取消和迟到事件、原生退出、清理失败、输入写入与应用回执、未知结果,以及每 Turn 新鲜的 usage、函数、输入和子项观察状态。必须说明夹具是受控夹具还是真实 Provider。 +2. **共享集成。** `TestThirdHarnessPublicOnboarding` 让合成 Harness 通过公共 Session 和输入准入、Worker 设备选择、真实 WebSocket gateway、daemon Registry 和 Router、中立事件以及持久化终态投影运行。它在与 API 处理程序和调度器相同的 `execution.Policy` 中使用自定义不可变 `engine.Catalog`,并检查已应用输入回执、已保存原生身份、继续执行、取消、不受支持的可选请求以及缺少强制 Runtime 支持。该夹具没有工作区、MCP、公共函数、权限或用户选择处理器,其注册仅保留在测试本地。它证明的是集成路径,而不是原生执行。 +3. **真实验收。** 使用锁定的官方 Python SDK 和针对 Core 的原始 HTTP、真实 Provider API、原生 Harness 以及专用数据库。验证初始执行、热后续执行、取消以及带继续执行的重启;记录原生所有者身份以及相同条件下的冷启动和热运行时间。对于工作区放置方式,还要验证 Files 和 Artifacts、工作区身份、公开响应中未出现凭据,以及外部历史会被拒绝。`services/core/tests/official_hosted_functions_native.py` 保存共享函数断言:成功和错误、原生文件输出和公共 Artifact 字节、重启后的同历史继续执行、外部结果拒绝以及待处理调用取消。合成运行或失败运行绝不计入。下面的选择性测试会在 `services/core/tests` 中针对真实 daemon 和模型运行锁定 SDK 夹具;设置 `OAC_TEST_OFFICIAL_SDK_PYTHON`、`OAC_TEST_NATIVE_DAEMON_BIN`、`OAC_TEST_NATIVE_PROOF_DIR` 及其私有选项文件后,每项测试才会运行。选项文件是一个 JSON 对象,恰好包含 `model` 和 `model_provider`(即 `x_agents_core.model_provider` 的字段);测试会将其设置为部署默认模型 Provider,而夹具的 `environment: none` Session 会在创建时将其冻结。 +4. **回归。** 现有 Harness 必须继续正常工作。先运行定向测试,然后运行 `make check`;API 更改后运行 `make openapi`,查询更改后运行 `make sqlc-generate`。 +5. **审查。** 遵循 [blind review workflow](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/CONTRIBUTING.md#review)。 + +| 操作 | `services/core/internal/store` 中的测试 | 选项文件变量;测试采用 Harness 变量时也列出该变量 | +| --- | --- | --- | +| 模型 Provider 协议 | `TestNativeModelProtocolPublicExecution` | [Model execution](model-execution.md#acceptance) | +| MiniMax Code 文本 | `TestNativeMCodePublicExecution` | `OAC_TEST_MCODE_REAL_OPTIONS` | +| 消息图像 | `TestNativeMessageImagePublicExecution` | `OAC_TEST_MESSAGE_IMAGE_REAL_OPTIONS`、`OAC_TEST_MESSAGE_IMAGE_ENGINE` | +| 带图像的函数结果 | `TestNativeFunctionImagePublicExecution` | `OAC_TEST_FUNCTION_IMAGE_REAL_OPTIONS`、`OAC_TEST_FUNCTION_IMAGE_ENGINE` | +| 结构化输出 | `TestNativeStructuredOutputPublicExecution` | `OAC_TEST_STRUCTURED_OUTPUT_REAL_OPTIONS` | +| 延迟函数发现 | `TestNativeToolSearchPublicExecution` | `OAC_TEST_TOOL_SEARCH_REAL_OPTIONS` | +| 禁用 Web 搜索和程序化工具调用 | `TestNativeToolPolicyPublicExecution` | `OAC_TEST_TOOL_POLICY_REAL_OPTIONS`、`OAC_TEST_TOOL_POLICY_ENGINE` | + +Environment 验收使用 `services/core/tests/official_environment_{templates,setup,skills,plugins,plugin_mcp,composition,initial_files,network,skill_references}.py`。对于组合式准备,请更改 Skill 默认值和 Template,删除源文件,重试并重启;验证冻结字节、一次 setup 执行和 MCP 取消。`official_hosted_structured_native.py` 覆盖托管结构化输出。随每项验收结果记录精确源修订版本、原生版本和命令。 + +将 Provider 密钥保存在私有操作员文件中,绝不能放入提交或日志。相对于 `apps/daemon/internal/agent` 的现有定向测试如下: + +| 边界 | 测试 | +| --- | --- | +| Codex 复用、取消和未确认清理 | `codex/executor_test.go`、`terminal_cleanup_test.go`、`prepared_cancel_test.go` | +| Codex 输入回执和严格恢复 | `codex/function_write_receipt_test.go`、`function_receipt_test.go`、`resume_test.go`、`recovery_test.go` | +| Claude 输入所有权、取消和准备清理 | `claudesdk/executor_test.go`、`cancellation_test.go`、`preparation_test.go` | +| MiniMax 取消退役、Start 失败和清理重试 | `mcode/executor_test.go`、`executor_backpressure_test.go` | +| MiniMax 原生历史绑定 | `mcode/session_test.go` | +| 无原生副作用或伪造结果的明确拒绝 | 每个适配器的 `unsupported_test.go` | + +## 原生安装器参与 {#native-installer-participation} + +适配器可以从自身包中的 `installation.go` 提供 `agent.Installation`:已注册 kind、锁定版本、受支持平台、激活环境和有界就绪探测。在 `cli/native_harness.go` 中注册它,并将其锁定组件添加到原生分发构建器。此可选契约不会改变 Executor 和 Turn 语义。Runtime 负责校验和、复制、锁和增量安装;适配器负责原生布局和探测。必须在每个宣称的平台上验证安装和执行。原生内容缺失或不兼容时必须失败;绝不会在 Turn 期间自行安装。 + +## 原生进程所有权 {#native-process-ownership} + +daemon 的 `clirunner` 为 SDK 会启动原生子进程的适配器提供可选的 Unix 进程组所有权;不支持的主机会在启动前拒绝此模式。显式取消和父上下文取消共享 TERM 宽限期(默认为三秒)以及有界的 KILL 升级过程。当直接进程退出时,内部回收器也会清理进程组的剩余成员,即使某个后代进程仍保持 stdout 打开;在取消过程中,主进程退出后,存活的后代进程仍会保留剩余宽限时间。daemon 的 `stop` 命令最多等待十秒以确认关闭,这涵盖该宽限期以及之后的管道和所有者清理。 + +所属输出管道在主进程退出后仍可读取。消费者在调用 `Wait` 之前耗尽 stdout 和 stderr;`Wait` 会汇合缓存的进程结果并关闭读取器。`Done` 报告主进程回收和进程组清理信号;它不是原生执行回执,也不是历史已持久化的证据。SDK 适配器会结算每个 Turn,并在发布完成状态前耗尽其观察结果;Executor 关闭还会关闭 Query 并等待原生子进程。进程组用于生命周期监管,而不是隔离或遏制离开进程组的后代进程。 + +适配器以启动用户的权限无人值守运行原生工具:Codex 使用批准策略 `never` 和完全访问权限;Claude 通过适配器的工具回调,以原生 `default` 权限模式运行,并禁用 SDK sandbox;MiniMax 绕过权限并禁用 sandbox。不要添加权限 profile、bubblewrap 包装器或原生 sandbox 设置;每种 Environment 来源都只有一条执行路径。资源路径属于操作员配置,而不是权限边界。 + +网络准入遵循 [Restricted network](environments.md#restricted-network)。 + +## 原生参考 {#native-references} + +| Harness | 适配器 | 原生传输方式 | Runtime 指南 | +| --- | --- | --- | --- | +| Codex | [`agent/codex`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/apps/daemon/internal/agent/codex/executor.go) | app-server | [Codex Runtime](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/deploy/codex/README.md) | +| Claude Code | [`agent/claudesdk`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/apps/daemon/internal/agent/claudesdk/executor.go) | [TypeScript SDK bridge](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/packages/claude-sdk-adapter/README.md) | [Claude Runtime](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/deploy/claude/README.md) | +| MiniMax Code | [`agent/mcode`](https://github.com/MiniMax-AI/OpenAgentCore/tree/main/apps/daemon/internal/agent/mcode) | ACP 和原生工作区配套组件 | [MiniMax Code Runtime](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/deploy/mcode/README.md) | diff --git a/contracts/agents-api/zh/index.md b/contracts/agents-api/zh/index.md new file mode 100644 index 00000000..18fe4ca3 --- /dev/null +++ b/contracts/agents-api/zh/index.md @@ -0,0 +1,153 @@ +--- +title: "Agents API 覆盖台账" +source: contracts/agents-api/index.md +source_hash: a158d22b6c42b10baa3867b56611f281dc13fd98b676765bf4b36a7684d0795a +--- + +Core 旨在以下方固定版本为准支持完整的 OpenAI Agents API([public API rule](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/AGENTS.md#public-api))。本台账记录 Core 对各项资源实现了哪些内容、哪些契约保存其详细信息,并列出相对于 OpenAI 服务的所有已知差异和所有未解决缺口。[API namespaces and credentials](../../../docs/zh/api/index.md) 说明谁调用哪些 API;[Agents API guide](../../../docs/zh/api/public-agent-api.md) 介绍使用方法。 + +## 固定基线 {#pinned-baseline} + +| 文件 | 内容 | +| --- | --- | +| [upstream.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream.json) | 固定版本:提交 `d7c41ef` 时的 [openai-python](https://github.com/openai/openai-python/tree/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/resources/beta/agents) 3.13.0;资源位于 `beta/agents` 下;Beta 标头为 `agents=v1` | +| [upstream-routes.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream-routes.json)、[upstream-fields.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream-fields.json) | 58 组方法与路径及其官方字段:`beta/agents` 下的 42 个操作、5 个 Files 操作和 11 个 Skills 操作。`scripts/extract-agents-api-upstream.py` 从固定版本的 SDK 中提取这些内容;安装该 SDK 后运行此脚本 | +| [openapi.yaml](../openapi.yaml) | Core 的公共架构,由 `make openapi` 根据 `services/core/internal/api/` 中的路由注解以及 [`v1/`](https://github.com/MiniMax-AI/OpenAgentCore/tree/main/contracts/agents-api/v1) 中的传输类型生成 | + +契约测试确保 Core 符合固定版本:路由器和 `openapi.yaml` 提供的路由与固定版本完全一致(`services/core/internal/api/routing_test.go`、`v1/upstream_contract_test.go`),每个查询参数和字段都采用官方定义,而 Core 专有字段仅位于 Agents 和 Sessions 内部的 `x_agents_core` 中。Swagger 2.0 无法表达字符串或数组联合类型,因此 `openapi.yaml` 不对 Session 的 `input` 和函数结果的 `output` 施加约束;这些类型由固定版本的类型定义和 Core 的校验逻辑确定。晚于该固定版本的操作和字段需等待协议升级。 + +各项状态的证据必须来自固定版本的官方 SDK,以及针对运行中服务发出的原始 HTTP 请求,正如 [CONTRIBUTING](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/CONTRIBUTING.md#compatibility-evidence) 所要求。 + +## 按资源划分的覆盖情况 {#coverage-by-resource} + +**已实现**表示每个操作都支持固定版本中的数据形态;仍然存在的限制列于 [known gaps](#known-gaps)。**部分实现**则说明缺少哪些内容。 + +| 资源 | 操作 | 状态 | 契约 | +| --- | --- | --- | --- | +| Agents | create, retrieve, update, list, delete | 已实现。所有固定版本设置都会保存;Session 准入只会用到其中一个子集 | [Agents](wire-semantics.md#agents) | +| Sessions | create (JSON 或流式), retrieve, update, list, delete | 已实现。更新仅接受 `metadata`;删除要求 Session 处于空闲或失败状态 | [Sessions](wire-semantics.md#sessions)、[creation streaming](sessions-events.md#creation-streaming) | +| Session events | create, stream | 部分实现:支持包含文本和内嵌图像的消息、取消和函数结果;流仅支持实时模式 | [Sessions, events and history](sessions-events.md)、[message content](message-content.md) | +| Turns | retrieve, list | 已实现;Session Turn 路由仅承载根级 Turns | [Turns and Items](sessions-events.md#turns-and-items) | +| Items | list | 部分实现:支持消息、命令、MCP 调用、functions、web search、reasoning 和 Subagent 协调 Items;其他原生变体不会被投影 | [Turns and Items](sessions-events.md#turns-and-items) | +| Artifacts | retrieve, list, delete, content | 已实现 | [Environment files and Artifacts](environment-files.md) | +| Subagents | retrieve, list; Items; Turns retrieve and list; Turn Items | 部分实现:只读子级工作;不支持实时子级进度或可选原生操作 | [Subagents](subagents.md) | +| Environments | retrieve | 已实现 | [Environments](environments.md) | +| Environment files | create, list | 已实现;列表不会递归 | [Environment files and Artifacts](environment-files.md) | +| Environment Templates | create, retrieve, update, list, delete | 已实现;执行限制列于 [known gaps](#known-gaps) | [Environment Templates](environments.md#templates) | +| Vaults | create, retrieve, list, delete | 已实现;没有归档操作 | [Vaults and Credentials](vaults.md) | +| Vault Credentials | create, retrieve, update, list, delete | 已支持 `static_bearer` 和 `mcp_oauth` | [Vaults and Credentials](vaults.md) | +| Files | create, retrieve, list, delete, content | 已支持 `purpose=user_data`;内容下载会被拒绝 | [Files and Skills](source-files.md) | +| Skills and Skill versions | create, retrieve, update, list, delete, content | 已实现 | [Files and Skills](source-files.md) | + +各 Harness 在不同部署位置支持哪些操作,请参阅 [Harness capabilities](harness-capabilities.md)。[Core wire behavior](wire-semantics.md) 包含适用于各项资源的通用规则:请求、错误和列表。 + +Core 自身字段位于 `x_agents_core` 中([Core extensions](../../../docs/zh/api/public-agent-api.md#core-extensions-x_agents_core))。Core 管理 API(`/core/v1`)和机器 API(`/api/v1`)不属于 Agents API。 + +## 与 OpenAI 的差异 {#differences-from-openai} + +以下每项都是 Core 有意采用或原生提供的行为,而官方服务的行为有所不同。链接中的规则规定了确切行为。 + +**请求和错误**([Core wire behavior](wire-semantics.md)) + +- Core 不会发送 `OpenAI-Organization` 或 `OpenAI-Project` 响应标头。 +- 对事件流、内容下载和 Environment files 列表执行 `HEAD` 会返回 405。 +- JSON 数组请求体会被拒绝;官方服务会将 `[]` 读取为 `{}`。 +- 存储的字符串中含有 U+0000 时会返回 400;官方服务会存储该字符。 +- 未找到消息从不会指明具体资源;Core 会给完整的元数据键加引号,而官方消息会将其缩略。 +- UUID 标识符也可按其他拼写形式解析,例如大写形式或带花括号的形式。 +- 对于重复的查询键,Files 路由会保留本地 `unsupported_parameter` 代码。 + +**列表**([lists](wire-semantics.md#lists)) + +- 将已删除的 Agent 或 Session 用作游标时会返回 404;官方服务仍可从该游标继续分页。 +- 使用来自其他 Session 的 Turn 游标、与 Vault ID 相等的 Credential 游标,或不是 Skill ID 的 Skills 游标时,都会返回 404。 +- Vault 和 Credential 列表会按照固定版本 SDK 的描述钳制负数 `limit`;官方服务返回 400。 + +**Agents 和 Sessions**([Agents](wire-semantics.md#agents)、[Sessions](wire-semantics.md#sessions)) + +- 使用相同 `Idempotency-Key` 重复创建 Session 会返回原 Session;官方服务会创建一个新的 Session。 +- 未指定程序化工具调用时,会保留 Harness 的原生行为;官方默认值为启用。 +- 未指定推理强度时会保持为 null,而不是采用模型的默认值。 +- Session 的 `agent.tools` 会省略 `tool_search` 声明。 +- 在 events 202 之后立即删除 Session 会返回 409,因为 Core 会在同一事务中准入该 Turn;官方服务返回 200。 + +**输入、事件和历史**([Sessions, events and history](sessions-events.md)、[message content](message-content.md)) + +- 提交给 `none` Session 的输入会同步完成准入;Core 不会模拟官方异步准入窗口。 +- 用于恢复等待中 Turn 的函数结果会发出 `turn.in_progress`;取消正在等待函数结果的 Turn 会发出临时 `agent.session.in_progress`。 +- 在 Turn 中途接入流时,不会发送补发 Item 快照。 +- Items 列表会包含进行中和未完成的输出 Items,并保留失败函数结果中已提交的 `output`。 +- 错误消息会省略官方消息包含的 call 和 executor ID。 +- 与其他文本并存的空文本部分可被接受并存储。 +- 空输入会返回 400 `invalid_request`,附带通用消息和值为 null 的 param;官方响应为 `invalid_request_error`,param 为 `input`。 +- 一旦所有根级 Turn 均已进入终态,Session 用量即可用;官方读取会滞后数秒。 + +**Files、Skills、Environment files 和 Artifacts**([Files and Skills](source-files.md)、[Environment files and Artifacts](environment-files.md)) + +- 文件上传上限为 512 MiB;官方上限为 512 MB。Files 列表默认最多返回 10,000 个 Files,`purpose` 过滤值不是 `user_data` 时会返回空页面。 +- Skill 版本号绝不复用,并且针对某个 Skill 的上传和删除会串行执行。 +- Environment files 可用于 `self_hosted` Environments,而官方服务会拒绝。 +- 在已有常规文件上创建 Environment file 会返回 "must not traverse symlinks or overwrite existing files" 消息。若父级符号链接仍位于工作区内,则会跟随该链接;若父级逸出工作区或父级本身是常规文件,则会返回通用 400;官方服务会拒绝父级符号链接。 +- Artifact ID 均为 UUID。 + +**Vaults 和 Credentials**([Vaults and Credentials](vaults.md)) + +- Vault 和 Credential 的状态仅在内部保存,默认值为 `active`;由于没有归档操作,未带过滤器的列表会同时包含两种状态。 +- `vault_ids` 中包含未知或属于其他账户的 Vault 时会返回 404 "Resource not found.";官方消息会指出该 ID。 +- 更新时显式为 OAuth `access_token`、`refresh` 或 `token_endpoint_auth` 指定 `null`,会保留已存储的值。 +- 静态令牌要成功运行,必须是 RFC 6750 `b64token`;其他已存储令牌会在派发时失败。 +- Vault 元数据上限为 64 KiB,且没有键对或长度限制;名称去除首尾空白后为 1–256 字节。 + +## 已知缺口 {#known-gaps} + +**配置和工具** + +- 显式指定推理强度或摘要、使用 `auto` 之外的服务层级、启用 `web_search` 或启用程序化工具调用,这些设置都会被保存,但在 Session 准入时会被拒绝。 +- Harness 对工具、结构化输出、延迟发现、subagents 和 MCP 的支持因 Harness 和部署位置而异;请参阅 [Harness capabilities](harness-capabilities.md)。MiniMax Code 不提供公共 functions、没有服务源 MCP,也不支持图像输入。 +- 由模型推导出的推理默认值不会被解析确定。 + +**执行和历史** + +- 流不会发出 reasoning-summary 事件、Environment 的 `pending` 或 `ready` 事件,也不会覆盖固定版本中的所有临时 tool-output 变体。 +- 除 [Turns and Items](sessions-events.md#turns-and-items) 中列出的变体外,其他原生 Item 变体不会被投影,而且 Items 无法修改。 +- 如果取消导致函数结果无法应用,该结果将永远不会作为 Item 出现。 +- 固定版本的 Codex 可能会丢失在订阅其流之前发出的命令输出。 +- Claude Code 和 MiniMax Code 都不报告公共用量。 +- 对于原生副作用,Core 不提供崩溃安全或恰好一次保证;已认领的工作若不重放,会在重启后失败。 +- 图像必须是内嵌的 PNG 或 JPEG data URI;远程 URL、`file_id` 和 `detail` 会被拒绝。 + +**Environments 和 Templates** + +- Runtime 不会实施 `disabled` 或 `restricted` 网络,因此需要这些网络的 Session 会被拒绝([restricted network policy](environments.md#restricted-network))。 +- `packages.system` 会被拒绝;系统软件包必须预先安装。 + +**Files 和 Environment files** + +- Files 仅接受 `purpose=user_data`;不支持其他 `purpose` 值、`expires_after` 和 Uploads API。 +- 结果不确定的 Environment file 写入不会自动重试或恢复;它会阻止后续写入以及向 Session 发送消息。 + +**Vaults 和 Credentials** + +- 没有归档生命周期、存储密钥轮换或重新加密。 +- OAuth 刷新仅在派发时执行:提供商返回 401 时不会刷新,不会在 Turn 中途替换令牌,也不会撤回已发送给 Runtime 的令牌。 +- 发送到所选 Credential 已被删除的 Session 的输入会先通过准入,随后在派发时失败。 +- 创建 Credential 时不接受 `Idempotency-Key`。 + +**Sessions** + +- 删除 Session 不会从物理存储中清除已保存的历史记录。 +- 对于 `self_hosted`、hosted 和无输入创建的流生命周期,以及创建流重试机制,均由 Core 自行决定。 + +### 尚未与官方服务核实的内容 {#unverified-against-the-official-service} + +- 当一个请求存在多项故障时的错误顺序,以及错误处理、默认值和载荷限制的总体一致性。 +- Subagent 子级 Turns 和待处理的 Environment file 写入是否会阻止 Session 删除。 +- 官方服务在待处理输入错误与未知结果目标之间的先后顺序。 +- npm、initial-file 和 Skill 安装的失败原因没有官方样本。 +- Codex 对文本旁的空文本部分、失败函数结果中的图像或远程引用图像的行为;Claude 对混合消息中仅含空白或空文本块的行为。 +- 在 Core 返回 405 的路由上,官方服务的 `HEAD` 行为。 +- 精确主机名之外的 Environment Template 主机名形式,以及 `disabled` 与域结合使用的情况。 +- Files 发生变化时的 purpose 筛选和分页;官方 Skill 上传限制和错误时机。 +- Environment file 列表的默认值(limit 为 20、默认路径为工作区根目录、不递归、page-token 失效)、50 MiB 的 `file_id` 复制上限,以及创建时的检查顺序。 +- Artifact 对硬链接、特殊文件和链接形式的 `outputs` 目录的捕获,内容字节变化后的重新发布,以及内容标头和范围。 +- Vault 和 Credential 的错误与重试语义、并发写入下的分页、删除后的可见性、依据官方规范化处理进行精确 URL 匹配、OAuth 刷新的时机和错误,以及受限密钥范围。 diff --git a/contracts/agents-api/zh/machine-api.md b/contracts/agents-api/zh/machine-api.md new file mode 100644 index 00000000..ab808d81 --- /dev/null +++ b/contracts/agents-api/zh/machine-api.md @@ -0,0 +1,124 @@ +--- +title: "机器连接 API" +source: contracts/agents-api/machine-api.md +source_hash: e9a16774d2924db9dde18385be315d3a0317c616b029b419165e442fa34fb08d +--- + +机器通过 `/api/v1` 调用 Core:包括沙箱节点、Runtime daemon 和自托管安装器。各路由仅接受所列凭据,不接受 Core 密钥或 Project API 密钥;控制台登录也不授予此处权限。反向代理将 `/api/v1` 直接发送给 Core;Web 不提供这些路由。 + +## 路由 {#routes} + +| 路由 | 调用方 | 凭据 | 契约 | +| --- | --- | --- | --- | +| `GET sandbox-node/configuration` | 节点安装器与节点 | 登记 token,或节点凭据加 `X-OAC-Node-ID` | [读取节点配置](#read-the-node-configuration) | +| `POST sandbox-node/enroll` | 节点安装器 | 登记 token | [登记节点](#enroll-a-node) | +| `GET sandbox-node/identity?node_id=` | 节点 | 节点凭据 | [恢复节点身份](#recover-a-nodes-identity) | +| WebSocket `GET sandbox-node/connect?node_id=` | 节点 | 节点凭据 | [节点代际协议](node-generation-protocol.md) | +| `GET agent-daemon/install/{version}/…` | 自托管安装器 | 无 | [安装授权](environment-executor-credentials.md#installation-grant) | +| `POST agent-daemon/installation`, `POST agent-daemon/installation/claim` | 自托管安装器 | 安装授权 | [安装授权](environment-executor-credentials.md#installation-grant) | +| `POST agent-daemon/enroll` | 自托管 daemon | 执行器凭据 | [登记自托管 daemon](#enroll-a-self-hosted-daemon) | +| `GET agent-daemon/connection?environment_id=` | 自托管安装器 | 执行器凭据 | [私有连接确认](environment-executor-credentials.md#private-connection-confirmation) | +| `POST agent-daemon/bootstrap` | Runtime daemon | daemon 凭据 | [daemon 引导](#daemon-bootstrap) | +| `GET agent-daemon/device-status?device_id=` | Runtime daemon | daemon 凭据 | [设备状态](#device-status) | +| WebSocket `GET agent-daemon/ws?device_id=&version=` | Runtime daemon | daemon 凭据 | [Core–Runtime 协议](../../../docs/zh/runtime-protocol.md) | + +所有凭据通过 `Authorization: Bearer` 头传输,不放入 URL。 + +生成的 [`runtime.openapi.yaml`](../runtime.openapi.yaml) 仅描述 sandbox-node 配置、登记、身份路由和两个安装路由。两个 WebSocket 及 daemon 引导、设备状态、登记和连接路由在 API 路由器外提供,无生成 schema;本文及所链接契约是它们唯一的定义。 + +## 凭据 {#credentials} + +| 凭据 | 签发方 | 接受位置 | +| --- | --- | --- | +| 登记 token | `POST /core/v1/sandbox/enrollment-tokens`(Web **Add node**),带节点批准容量。使用一次;在响应 `expires_at` 过期 | 无节点 ID 的 `sandbox-node/configuration`、`sandbox-node/enroll` | +| 节点凭据 | 节点自身:生成 32 至 256 个无空白字符的密钥,在登记时注册 | 带 `X-OAC-Node-ID` 的 `sandbox-node/configuration`、`sandbox-node/identity`、`sandbox-node/connect` | +| 安装授权 | `self_hosted` Session 的 `x_agents_core.installation` 命令;短期有效 | `agent-daemon/installation` 及其 `claim` | +| 执行器凭据 | 安装领取,或 Core 密钥[执行器凭据路由](environment-executor-credentials.md) | `agent-daemon/enroll` 和 `agent-daemon/connection`;登记后也作为绑定设备的 daemon 凭据 | +| 托管沙箱 daemon 凭据 | Core 为每个受管分配签发,通过[引导文件](../../../docs/zh/runtime-bootstrap.md)交付 | `agent-daemon/bootstrap`、`device-status` 和 `ws` | +| 操作者设备配置 | 具有数据库访问权限的操作者运行 `oac-core-device` | `agent-daemon/bootstrap`、`device-status` 和 `ws` | + +Core 对存储的每个 token 和凭据仅保留 SHA-256 摘要;安装授权经签名但不存储。凭据不可互换:各自仅适用于自身路由。 + +### 操作者设备配置 {#operator-device-profile} + +`environment: none` Session 的引擎主机使用操作者直接在数据库创建的设备配置连接: + +```sh +umask 077 +mkdir -p ~/.oac/daemon/default +OAC_DATABASE_URL=... oac-core-device --tenant --name 'engine host' --url https://core.example > ~/.oac/daemon/default/auth.json +oac-daemon connect --profile default +``` + +`--tenant` 为 Project 执行租户 UUID,`--url` 为不带路径的 Core origin。命令打印配置一次:`server_url`(origin 加 `/api/v1`)、`runtime_id`(设备 ID)、`runner_credential` 和 `device_name`。使用新配置,不覆盖其他设备文件;私密复制到远程主机相同路径。`oac-core-device --tenant --revoke ` 撤销设备:立即拒绝新连接,已有连接在下一次心跳关闭。Worker 将每个 `none` Session 绑定到其租户内声明所需能力的已连接设备,重试和重启保留绑定;自托管 Session 不使用此路径。 + +## 节点路由 {#node-routes} + +### 读取节点配置 {#read-the-node-configuration} + +`GET /api/v1/sandbox-node/configuration` 返回用于节点安装和恢复的活动部署,不消耗登记 token。 + +- 新节点发送登记 token,不带 `X-OAC-Node-ID`。token 必须有效、未过期、未消费且由此安装签发。活动重置拒绝该读取。 +- 已注册节点发送节点凭据,并在 `X-OAC-Node-ID` 放其 UUID。无查询时读取当前目标。`?generation=N` 仅读取此节点仍可能需要的代际:当前目标、服务 pin,或其上未释放分配或 placement 持有的代际;其他代际被拒绝。重置期间仍可读取,以恢复已有归属资源。 + +响应包含 `installation_id`、`provider`、`core_url`(安装公开 URL)、`generation`、`specification`、`specification_digest`、`max_active` 和 `max_retained`。不包含管理员、Project 或 E2B 凭据,仅适用于节点型提供方。[沙箱部署契约](sandbox-deployment.md#canonical-node-specification)定义 specification 与摘要。 + +### 登记节点 {#enroll-a-node} + +`POST /api/v1/sandbox-node/enroll` 注册节点并消费 token。正文恰好包含以下字段: + +| 字段 | 值 | +| --- | --- | +| `node_id` | 节点选定的规范 UUID | +| `credential` | 节点密钥,32 至 256 个无空白字符 | +| `name` | 显示名称 | +| `provider` | 部署提供方 | +| `backend_fingerprint` | 节点后端命名空间摘要 | +| `deployment_generation`, `specification_digest` | 节点读取的配置 | +| `core_url` | 节点存储并连接的 Core origin | + +Core 在一个事务中检查 token 有效、部署已初始化且为节点型且未重置、代际与摘要匹配当前 specification、`core_url` 等于安装公开 URL、节点 ID 未使用。仅通过后按 token 批准容量注册节点并消费 token。201 响应为节点身份:`node_id`、`installation_id`、`provider`、`deployment_generation`、`specification_digest`、`max_active` 和 `max_retained`。节点不能提交容量;登记代际和摘要为不可变身份,后续代际使用独立配置。 + +### 恢复节点身份 {#recover-a-node-s-identity} + +`GET /api/v1/sandbox-node/identity?node_id=` 返回同一身份,加 Core 当前观察的 `connected` 和 `provider_ready`。 + +### 节点路由错误 {#node-route-errors} + +| HTTP | 代码 | 时机 | +| --- | --- | --- | +| 400 | `invalid_request_error`, `param: "core_url"` | 登记未提供 `core_url` | +| 400 | `invalid_request` | 正文、节点 ID 或代际格式错误,或提供方与部署不同 | +| 401 | `invalid_node_credential` | token 或节点凭据缺失、无效、过期、已消费或属于其他安装 | +| 409 | `sandbox_specification_mismatch` | 节点代际或摘要不匹配 | +| 409 | `sandbox_node_address_mismatch` | `core_url` 不是安装公开 URL;token 保持未使用 | +| 409 | `idempotency_conflict` | 节点 ID 已注册 | +| 409 | `sandbox_reset_in_progress` | 重置期间登记或新节点读取配置 | +| 503 | `runtime_node_unavailable` | 部署未初始化或存储不可用 | + +先检查凭据,再检查部署状态,因此被拒绝凭据(包括其他安装签发的)即使未初始化或使用 E2B 也返回 401。部署初始化前,配置读取与登记对其他方面有效的 token 返回 503,身份读取与节点连接返回 401。 + +## daemon 路由 {#daemon-routes} + +### daemon 引导 {#daemon-bootstrap} + +`POST /api/v1/agent-daemon/bootstrap` 携带 daemon 凭据及 `{"device_id": "…"}`,返回 `device_id`、`workspace_id`、`ws_url`(从 `OAC_PUBLIC_URL` 推导,不使用请求头)、`heartbeat_seconds` 和 `protocol_version`。daemon 随后按 [Core–Runtime 协议](../../../docs/zh/runtime-protocol.md#ownership-and-connection)连接 `ws_url`。 + +### 设备状态 {#device-status} + +`GET /api/v1/agent-daemon/device-status?device_id=` 携带 daemon 凭据,返回 `device_id`、`online` 和 `owner`:当前连接所有者的 `owner_pod_id`、`owner_url`、`generation`、`status` 和 `lease_expires_at`,或 null。 + +引导、设备状态和 WebSocket 路由共享错误体 `{"error": code, "detail": text}`:400 `missing_params`、`missing_device_id` 或 `bad_json`;401 `missing_bearer`、`unknown_device` 或 `bad_credential`;403 `wrong_runtime_type`;500 `internal`;WebSocket 的 `version` 不等于 Core 精确 Runtime 协议版本时返回 426 `incompatible_version`。 + +### 登记自托管 daemon {#enroll-a-self-hosted-daemon} + +`POST /api/v1/agent-daemon/enroll` 携带执行器凭据及精确正文 `{"environment_id": "…"}`(无查询),将一个专用设备绑定到 Environment 的 Session,返回 `device_id`、`session_id`、`environment_id` 和 `workspace_directory`。不返回其他凭据:执行器凭据成为该设备的 daemon 凭据。相同凭据重试返回相同绑定。成功响应包含 `Cache-Control: no-store`。 + +| HTTP | 时机 | +| --- | --- | +| 400 | 正文格式错误或存在任何查询 | +| 401 | 凭据无效、撤销、属于其他范围,Session 已删除,或 Environment 无当前执行器权限 | +| 409 | Environment 已绑定到不同密钥或设备 | +| 503 | 存储不可用 | + +登记不创建受管分配,也不授予 Session API 访问权限。daemon 在凭据旁保存绑定,拒绝其他 Environment 的原生历史。网关与 Worker 在每次连接和分发时重查凭据权限,因此轮换、撤销和删除 Session 终止后续使用。[自托管指南](../../../docs/zh/getting-started/self-hosted.md)提供操作步骤,[执行器凭据契约](environment-executor-credentials.md#revoked-or-rotated-credential)描述 daemon 如何处理永久拒绝。 diff --git a/contracts/agents-api/zh/message-content.md b/contracts/agents-api/zh/message-content.md new file mode 100644 index 00000000..6e330bb8 --- /dev/null +++ b/contracts/agents-api/zh/message-content.md @@ -0,0 +1,74 @@ +--- +title: "消息内容" +source: contracts/agents-api/message-content.md +source_hash: 085ade22b792243b8fea4fb798f1de3cbed872d27752afa4f30b93f251b829a8 +--- + +用户消息和函数结果共享同一内容模型:由 `input_text` 与 `input_image` 部分组成的有序列表。Core 按发送形式准确存储消息边界、部分顺序和图像引用,并在用户 Item 中原样返回。不下载、转码或修复媒体。Session 创建的 `input` 与 `events.create` 消息共享验证和准入;[Session、事件与历史](sessions-events.md#send-input)定义准入、请求限制和错误。 + +## 消息 {#messages} + +消息包含 `role: "user"`、可选 `type: "message"` 和非空 `content` 数组。事件 `input` 为消息数组;Session 创建的 `input` 也可为字符串,转换为一条文本消息。显式 null 或空消息 `type`、字符串 `content` 或字符串事件 `input` 均无效。 + +消息含图像或至少一个非空文本部分时有效。Core 不修剪文本。以下请求返回 400 `invalid_request`,不写入内容: + +- 空 `input` 字符串、`input` 数组或 `content` 数组; +- 文本部分全为空且没有图像的消息。 + +其他内容旁的空文本部分(例如 `["", "text"]`)被接受并按发送形式存储。 + +## 图像 {#images} + +`input_image` 部分通过内联 data URI 携带 `image_url`:`data:image/png;base64,…` 或 `data:image/jpeg;base64,…`。base64 必须为规范形式,解码图像必须匹配声明类型。Core 不接受远程 URL、`file_id` 或 `detail`。 + +| Harness | 消息图像 | +| --- | --- | +| Codex | 在 Harness 支持的所有位置接受:`none`、`openai_hosted`、`self_hosted` | +| Claude Code | 在 `none`、`openai_hosted`、`self_hosted` 接受 | +| MiniMax Code | 拒绝 | + +任何写入之前,准入检查 Harness;Harness 无法接受的图像返回 400。Runtime 也必须报告消息图像支持:Core 仅将输入含图像的 Session 绑定到此类 Runtime,交付给不支持的 Runtime 会失败。应使用接受图像的模型。 + +## 仅空白文本 {#whitespace-only-text} + +`" "` 或 `"\n\t"` 等仅含空白的文本为有效内容,原样存储和返回。Harness 能否运行由引擎配置声明: + +| Harness | 无图像且无非空白文本的消息 | +| --- | --- | +| Codex | 准入并原样交付 | +| Claude Code | 400 `unsupported_or_invalid_configuration` | +| MiniMax Code | 400 `unsupported_or_invalid_configuration` | + +拒绝适用于 Session 创建(含流式和 `self_hosted` 创建)及 `events.create`,发生于任何写入、预留或 Turn 之前,因此不会干扰运行 Turn。同一消息中非空白文本旁的空白对所有 Harness 均准入。空白集合为 Go `unicode.IsSpace` 与 ECMAScript `String.prototype.trim` 的并集,例如 U+0085、U+FEFF;Core 准入和 Claude 桥接层使用相同集合。 + +## 函数结果 {#function-results} + +`agent.session.input.tool_result` 事件包含 `success`、可选且可空的 `error` 字符串,以及可选且可空的 `output`:字符串或有序 `input_text` 和 `input_image` 部分数组。 + +- Core 按提交形式存储结果,包括 `output`、`error` 是否存在,并用其确定重试身份。公开 Item 始终包含两字段([Item 规则](sessions-events.md#turns-and-items))。 +- Runtime 接收一个有序内容列表:先是 `output` 部分,再将 `error` 文本作为最后文本部分。转换不改变存储结果。 +- 含图像结果要求 Runtime 报告函数结果图像支持;仅含图像结果检查该能力。Runtime 在准入后拒绝结果时,Turn 失败且没有确认应用,存储结果仍可读取。 + +| Harness | 函数结果 | +| --- | --- | +| Codex | 文本与有序文本/图像输出。Core 仅检查各部分格式正确,并将图像引用原样传给 Harness | +| Claude Code | 文本输出。仅成功结果可含内联 PNG 或 JPEG 图像;失败结果图像或远程引用在任何存储前返回 400,待处理调用保持开放。Harness 可在原生历史中调整图像尺寸或重新编码;公开 Item 保留提交字节 | +| MiniMax Code | 无公开函数 | + +### 应用回执 {#application-receipts} + +准入(202)不表示 Harness 使用了结果。适配器确认原生应用后,待处理调用清除: + +- **Claude Code** 使用实时根原生工具结果确认,要求匹配 Session、调用 ID、成功标志、准确文本、块数和顺序,且每个图像位置都有原生图像。重放、合成和 Subagent 记录不能确认。 +- **Codex** 使用实时根 dynamic-tool `item/completed` 观察确认,要求匹配线程、Turn、调用、函数名称、状态、成功标志和准确有序内容。仅写入 Harness 不确认。 + +两适配器等待回执最多 10 秒。超时或原生释放且未确认时,应用保持不确定。确认表示 Harness 记录了结果,不表示模型提供方消费了结果。Core 不自动重放结果;提交仍保留用于恢复读取。 + +## Runtime 边界 {#runtime-boundary} + +Core–Runtime wire 在初始输入、准备后启动和引导中将消息作为 `MessageInput` 携带,使用与函数结果相同的有序 `InputContent` 部分([Core–Runtime 协议](../../../docs/zh/runtime-protocol.md))。准入检查 Harness 声明配置;绑定和交付检查 Runtime 报告。适配器负责原生编码和应用回执。仅文本适配器拒绝图像部分,不丢弃它们。 + +- **Codex** 将批次展平为原生输入列表,在公开消息之间插入空行分隔。公开消息边界保留于 Core 存储,原生历史不保留。 +- **Claude Code** 发送原生图像块及每条原生用户消息的 UUID。一个公开输入仅在批次所有消息消费后视为已应用。单个原生 Turn 内桥接层最多接受 64 条用户消息(含开场提示);超过界限的引导批次在提交任何部分前被拒绝,并结束运行 Turn。daemon 要求桥接协议 3。 + +原生消息与函数结果图像检查列于[验证适配器资格](harness-onboarding.md#qualify-the-adapter)。 diff --git a/contracts/agents-api/zh/model-execution.md b/contracts/agents-api/zh/model-execution.md new file mode 100644 index 00000000..0b29d25a --- /dev/null +++ b/contracts/agents-api/zh/model-execution.md @@ -0,0 +1,144 @@ +--- +title: "模型执行" +source: contracts/agents-api/model-execution.md +source_hash: d657e6189ebc5e55ed5201dceaa304b157a6b6ca1f45dcbe9756ef866d63ed05 +--- + +每个 Session 都运行一个 Harness,并使用一个模型提供商。Core 通过三个固定版本上游协议未定义的 Core 扩展来选择它们:`x_agents_core.harness` 选择 Harness,`x_agents_core.model_provider` 提供端点和密钥,`x_agents_core.harness_config` 携带原生模型参数。Core 没有提供商目录、模型别名解析或产品权限模型;除 Session 和已保存 Agent 配置包外,唯一存储的配置包是每个 Harness 的一个 [deployment default](#deployment-defaults)。本文档定义 Harness—模型提供商协议:[`internal/modelprovider/config.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/modelprovider/config.go) 负责验证冻结的提供商连接,每个 Harness 则通过 [`internal/harnessconfig/harness.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/harnessconfig/harness.go) 声明其协议和原生参数。 + +## Harness 选择 {#harness-selection} + +```json +{"x_agents_core": {"harness": "claude_sdk"}} +``` + +已保存 Agent 在创建、更新和读取时接受 `x_agents_core.harness`,Session 则在内联的 `agent.x_agents_core` 中接受它。标识符来自 [Harness catalog](harness-catalog.md);未知标识符和未知嵌套字段均会被拒绝,空的内联 Session 扩展也会被拒绝。部署启用哪些 Harness 及其默认值由进程设置 `core.harnesses` 和 `core.default_harness` 决定([configuration](../../../docs/zh/configuration.md#settings))。 + +- 省略:Session 继承其已保存 Agent 的 Harness;内联 Agent 使用部署默认 Harness。 +- Session 的内联扩展显式为 null:重置为部署默认 Harness,同时保留继承的提供商配置包。对于已保存 Agent,null 扩展会清除其 Harness 和提供商。 +- 所选 Harness 必须已启用;Core 绝不会回退到其他 Harness。 +- 创建 Session 时,Core 在处理已保存 Agent 的覆盖值后解析该选择,验证 Harness 配置文件,并将结果存储为 Session 的引擎。当生效的 Agent 包含该扩展时,Session 读取结果会报告它;其他 Session 保持官方 Agent 结构。读取操作从不查询当前 Agent 或部署默认值。 +- 使用显式选择器重试创建时会保留调用方意图;在已有 Idempotency-Key 下更改选择器会产生冲突。 + +Session 的 `environment` 和 Environment Templates 用于选择准备流程,而不是 Harness 或提供商。该扩展仅在 `contracts/agents-api/v1` 中定义一次;校验器从目录派生,任何处理程序或 schema 都不会维护自己的名称列表。 + +## 已保存默认值与优先级 {#saved-defaults-and-precedence} + +已保存 Agent 是可编辑配置,而不是绑定的运行时。创建或更新时,应提供 `model`、可选的 `x_agents_core.harness` 以及可选但完整的 `x_agents_core.model_provider`。响应仅返回安全的提供商字段和只读输出标志 `api_key_configured`,绝不返回 `api_key`、密文或可复用的凭据引用。Agent JSON 仅存储安全视图;密钥配置包拥有自己加密后的数据库行,使用独立的加密用途绑定到 Project 和 Agent,并与 Agent 在同一事务中写入。仅编辑模型时不需要密钥。 + +创建 Session 时,Core 会先解析每个显式的模型或 Harness 覆盖值,再应用已保存默认值;未选择 Harness 时,应用部署默认值。已保存 Agent 必须指定模型。内联 `openai_hosted` 或 `none` Session 可以省略模型,以使用解析后 Harness 的部署模型;`self_hosted` 绝不会使用部署模型设置。Core 绝不会根据模型名称推断模型。 + +提供商配置的优先级依次为:完整的 Session 配置包、完整的已保存配置包,以及解析后 Harness 的部署默认值。Core 绝不会将替换后的端点与继承的密钥合并;仅覆盖模型时会复用整个继承的配置包。每个 Harness 仅通过其原生协议连接: + +| Harness | 支持的协议(默认项在前) | +| --- | --- | +| Codex | `responses` | +| Claude SDK | `anthropic` | +| MiniMax Code | `anthropic`、`responses`、`chat_completions` | + +MiniMax Code 要求上下文限制和输出限制均为正数。Core 会在写入 Session 前验证解析后的组合。Core 和 Runtime 读取 `internal/harnessconfig` 中相同的有序 `protocols` 声明。不存在模型 API 代理、直通网关或跨协议转换,Harness 内部也不例外。不受支持的已保存配置和 Session 快照一旦使用便会失败;它们绝不会在何处被重写、创建别名或迁移。 + +适用来源取决于接收密钥的计算资源由谁拥有: + +| Environment | Session 或已保存 Agent 配置包 | 部署默认值 | 未解析到配置包 | +| --- | --- | --- | --- | +| `openai_hosted` | 接受 | 应用 | 400 `model_provider_required` | +| `self_hosted` | 接受 | 从不应用 | 400 `model_provider_required` | +| `none` | 以 400 拒绝 | 已配置时应用 | 接受;模型由设备自身的环境提供 | + +部署默认值保存运营方的密钥,因此仅保留在运营方拥有的计算资源上:Core 管理的沙箱和运营方注册的 `none` 设备。`self_hosted` 执行器属于应用程序,由应用程序提供自己的配置包。托管 Runtime 和自托管 Runtime 均不携带自己的模型配置,因此其中的 Session 如果没有配置包,就会在发生任何写入之前被拒绝;错误参数为 `x_agents_core.model_provider`,并会返回说明应配置内容的消息。 + +| 操作 | 省略 | 显式 null | +| --- | --- | --- | +| Agent 更新 `x_agents_core` | 保留两个默认值 | 清除 Harness 和提供商,包括其密钥 | +| Agent 更新嵌套的 `model_provider` | 保留配置包 | 清除整个已保存配置包 | +| Agent 更新嵌套的 `harness` | 保留 Harness | 拒绝;请使用 null 扩展进行重置 | +| Session 顶层 `x_agents_core` | 继承提供商默认值 | 继承提供商默认值 | +| Session 嵌套的 `model_provider` | 继承提供商默认值 | 继承提供商默认值 | +| Session 内联的 `agent.x_agents_core` | 继承已保存的 Harness | 重置为部署 Harness | + +空的 Session 执行扩展无效。显式 null 提供商会请求继承;空的或不完整的提供商对象无效。已保存提供商配置中的未知字段、重复字段或只读输出字段均会被拒绝。没有 Harness 的已保存 Agent 可以保存有效配置包;其 Harness 兼容性会在 Session 准入时检查。仅更新提供商的 Agent 更新会保留已保存的 Harness,并在行锁保护下验证合并后的组合。Session 内联的 `agent.x_agents_core` 接受 `harness` 和 `harness_config`;提供商覆盖值必须放在请求顶层。 + +Core 从同一个数据库快照读取 Agent 配置和加密配置包;显式提供完整 Session 覆盖值时,无需解密已保存的配置包。Session 自身的加密快照会与 Session 及其 Environment 原子写入。现有 Session 绝不会再次查询 Agent:Agent 编辑、密钥替换、删除、暂停和重启均无法改变其模型、Harness 或提供商。加密密钥缺失或错误时会安全失败;重启前后应保持相同的 [credential key](../../../docs/zh/configuration.md#installation-directory)。不存在 Turn 级覆盖。 + +新的托管请求以及省略内联模型的请求,会在解析可变默认值之前记录调用方意图。其他内联请求,例如 `none`,继续遵循已解析请求的重试规则;该哈希不包含部署默认值,因此更改默认值不会改变其重试标识。匹配的创建重试会在再次解析 Agent 或提供商之前恢复已提交的 Session,并且不会进一步加入输入。流式传输不参与重试标识的计算。[TypeScript client](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/packages/agents-client/README.md#saved-agent-and-deployment-defaults) 展示了已保存 Agent 和部署默认值。 + +## Session 覆盖 {#session-override} + +```json +{ + "agent": {"model": "exact-provider-model", "x_agents_core": {"harness": "mcode"}}, + "environment": {"type": "openai_hosted"}, + "x_agents_core": { + "model_provider": { + "protocol": "anthropic", + "base_url": "https://provider.example/anthropic", + "api_key": "", + "context_window": 200000, + "max_output_tokens": 8000 + } + } +} +``` + +- `protocol` 指定上游 API(`anthropic`、`responses` 或 `chat_completions`),而不是引擎。所选 Harness 必须原生支持它。 +- `base_url` 使用 HTTPS 和有效主机名,且不得包含凭据、查询参数或片段。 +- `api_key` 不得为空,最长为 16 KiB,并且不得包含 NUL、CR 或 LF。 +- `context_window` 和 `max_output_tokens` 是可选的非负整数,输出限制不得大于上下文限制;对于 MiniMax Code,两者都必须为正数。请使用真实模型的限制。 +- `agent.model` 是准确的提供商模型 ID;只要提供该值,就始终会替换部署模型。 +- Session 的 `x_agents_core` 接受 `model_provider`、`harness_config` 和 `environment`([Environments](environments.md#preparation-order));任何其他成员,例如 `sandbox_node_id`,都会以 400 拒绝。托管节点放置自动完成。 + +不受支持的协议、Harness 或 Environment 组合会在创建 Session 前被拒绝。提供商可用性在执行期间检查,而不是通过探测检查。 + +解析后的提供商配置会在 Session 创建事务中被冻结并加密,使用自己的加密用途,并绑定到 Project 和 Session。创建重试会将其纳入请求哈希,因此使用相同 Idempotency-Key 时,更改密钥或端点会产生冲突;密钥进入任何存储哈希时,只会表现为由部署凭据密钥加键控的指纹。任何公开的 Session、Agent、Environment、事件或常规配置均不包含该密钥。顶层扩展仅可写入,无法更新。 + +在分派时,Core 会通过绑定到 Session 的 daemon 连接,将快照作为一个机密提供商配置包发送出去;适配器会原生应用该配置并直接连接提供商。快照缺失或无法解密时,Core 绝不会回退到其他凭据。对于 `self_hosted`,接收方 daemon 是为该 Session 自身 Environment 注册的执行器,并持有 Session 创建者主体的当前执行器凭据;凭据轮换或吊销会在继续分派前关闭套接字。执行器主机将该配置包存放在其原生 Harness home 中,与托管 Runtime 的做法相同。原生工具以启动账户的权限运行,并且可以读取该账户有权读取的内容;吊销凭据不会擦除已经交付的配置包。 + +## 原生模型参数 {#native-model-parameters} + +`harness_config` 保存所选 Harness 的原生模型参数。已保存 Agent 在 `x_agents_core` 中接受它,Session 则在内联的 `agent.x_agents_core` 和顶层 `x_agents_core` 中接受它;顶层值优先。 + +| Harness | 接受的字段 | 应用方式 | +| --- | --- | --- | +| Codex | `model_reasoning_effort`:`none`、`minimal`、`low`、`medium`、`high`、`xhigh` | App-server `-c model_reasoning_effort=...` 以及每个 Turn 的 `collaborationMode.settings.reasoning_effort` | +| Claude SDK | `effort`:`low`、`medium`、`high`、`xhigh`、`max`;`thinking`:SDK 的 `adaptive`、`enabled` 或 `disabled` 对象 | SDK `Options.effort` 和 `Options.thinking` | +| MiniMax Code | 仅空对象 | 仍必须提供提供商 token 限制 | + +对于 `adaptive` 和 `enabled`,Claude `thinking` 接受 `display`(`summarized` 或 `omitted`);只有 `enabled` 接受正整数 `budgetTokens`;拒绝 `maxThinkingTokens`。这些是原生设置,而不是通用推理词汇表;模型可用性和提供商支持均由 Harness 负责。 + +提供对象时会替换整个对象;`{}` 会清除它,null 则无效。Session 在选择模型或提供商但未提供原生参数时,会使用 `{}`,而不会继承另一个模型的参数。否则,使用已保存 Agent 提供的对象;使用部署模型的内联 Session 使用部署对象。更改 Harness 会清除继承的参数;对模型、提供商或 Harness 的 Agent 更新如果未提供 `harness_config`,也会清除继承的参数。不进行深度合并。仅设置原生参数的内联扩展会保留已保存的 Harness。该对象和部署默认值都不会启用公开的 `reasoning` 选项。 + +Core 会在写入 Session 前验证解析后的配置,并将其冻结在 Session 的 Agent 配置中;管理员的 execution-configuration 读取结果会显示其值和来源。原生参数不属于机密信息;提供商密钥保存在单独加密的配置包中。重新连接会使用冻结的配置,无法更改提供商、协议或参数;不兼容的冻结配置会导致失败。仅仅支持某种协议,并不能说明结构化输出、工具发现、Web 搜索、详细程度或图像输入是否可用;连接受支持也不能证明远程模型接受某个参数。 + +## 部署默认值 {#deployment-defaults} + +部署默认值是存储在 Core 中的一项运行时设置:每个 Harness 有一个完整的模型配置,可使用 Core 密钥通过 Web 或 `/core/v1` 进行管理。 + +| 方法和路由 | 结果 | +| --- | --- | +| `GET /core/v1/harnesses` | 此构建支持的每个 Harness,包含来自进程配置的 `enabled` 和 `default`、其 `model_configuration`(安全视图)或 null,以及 `model_configuration_support` | +| `GET /core/v1/harnesses/{harness}/model-configuration` | 安全视图;未设置时返回 404 | +| `PUT /core/v1/harnesses/{harness}/model-configuration` | 使用共享的提供商和原生参数校验器替换 `{model_provider, model, harness_config}` | +| `DELETE /core/v1/harnesses/{harness}/model-configuration` | 移除配置;幂等,返回 204 | + +PUT 要求提供 `model` 和完整的 `model_provider`;`harness_config` 默认为 `{}`。读取操作返回 `model`、`harness_config`、安全的 `model_provider` 视图(`protocol`、`base_url`、可选的 token 限制和 `api_key_configured`)、observations 和 `updated_at`,绝不返回密钥。该配置包使用自己的加密用途加密并绑定到 Harness。每次写入都会记录一条管理员审计条目(`resource_type: deployment_model_provider`,Harness 作为 `resource_id`,操作为 `set` 或 `delete`,`project_id` 为 null),且不包含密钥。加密密钥缺失或错误时会安全失败:写操作以及需要使用默认值的 Session 创建都会返回 503 `credential_storage_unavailable`。 + +创建 Session 时,Core 会解密解析后 Harness 的默认值,并像处理其他配置包一样将其冻结在 Session 的加密快照中,因此更改或移除默认值绝不会影响现有 Session。execution-configuration 读取结果会显示冻结的安全视图,其来源为 `deployment`。提供商快照缺失或无效时会安全失败,并且不会回退到其他模型或提供商。 + +`model_configuration_support` 派生自 Core 和 Runtime 共享的适配器声明:`protocols` 列出可选择的原生协议,并将默认项放在首位;`accepts_harness_config` 表示是否接受原生参数;`token_limits_required` 表示是否必须提供提供商 token 限制。它描述的是构建版本,而不是实时 Runtime 或远程模型。 + +### 部署默认值观测 {#deployment-default-observations} + +Harness 和默认模型读取结果包含可空的 `last_used_at`、`last_error_code` 和 `last_error_at`;即使配置包未发生变化,PUT 也会重置这三项。 + +- 只有当 Session 冻结的正是当前默认修订版时,已完成的根 Turn 才会记录使用情况。 +- 失败的根 Turn 仅记录原生提供商错误码 `authentication_error`、`connection_failed`、`rate_limit_exceeded`、`usage_limit_exceeded`、`server_overloaded`、`server_error`、`resource_not_found`、`request_timeout` 和 `invalid_request`。输入策略、Core 或 Runtime 导致的失败,以及 cancelled 和 waiting 结果均不计入。 +- 成功不会清除较早的错误;比较时间戳只是一种显示约定。 +- 无论错误码是什么,错误观测都会节流 30 秒,常规成功观测也会节流 30 秒;错误后的首次成功会立即记录恢复。对于未变化的修订版,这意味着数据库时间的任意 30 秒窗口内最多允许三次有效写入。 +- 每次观测的预算为 1 秒,包括获取连接池资源和行锁的时间,并且无法更改已提交的 Turn。崩溃、失败或节流都可能导致最新观测缺失。 + +这些时间是在终态提交后由 Core 尽力记录的接收时间,而不是提供商健康状态时间或远程完成时间。旧 Session、显式指定提供商的 Session 和更早的 Session 均无法更新替换后的默认配置。公开的 Session 和 Turn 字段以及重试标识不受影响。Core 没有就绪探测、自动刷新、观测历史,也不会读取凭据或原始错误。 + +## 验收 {#acceptance} + +`TestNativeModelProtocolPublicExecution`(`services/core/internal/store/model_protocol_native_test.go`)结合 `services/core/tests/official_model_protocol_native.py`,通过固定版本的官方客户端针对真实提供商 API 运行每个 Harness。当 `OAC_TEST_OFFICIAL_SDK_PYTHON`、`OAC_TEST_NATIVE_DAEMON_BIN`、`OAC_TEST_NATIVE_PROOF_DIR` 和 `OAC_TEST_MODEL_PROTOCOL_OPTIONS` 均已设置时运行;最后一项指定一个私有模型设置文件。绝不提交这些设置或打印其值。 diff --git a/contracts/agents-api/zh/node-generation-protocol.md b/contracts/agents-api/zh/node-generation-protocol.md new file mode 100644 index 00000000..e6e5de8f --- /dev/null +++ b/contracts/agents-api/zh/node-generation-protocol.md @@ -0,0 +1,126 @@ +--- +title: "沙箱节点协议" +source: contracts/agents-api/node-generation-protocol.md +source_hash: 1ee43dfcdd0eec0806ea3bc8a4c1227e10bd8ac5e69486505cb113a98e3f548a +--- + +沙箱节点在其主机上运行 Docker 或 microsandbox Provider,并通过一个 WebSocket 与 Core 相连。Core 通过该连接发送 Provider 操作;节点针对本地 Provider 执行这些操作,并报告就绪状态、主机测量值及其持有的部署代次。Core 始终是唯一的生命周期所有者:节点绝不重试变更操作或调度工作。帧和校验器位于 [`services/core/internal/sandbox/node`](https://github.com/MiniMax-AI/OpenAgentCore/tree/main/services/core/internal/sandbox/node)(`wire.go`、`generation_wire.go`);节点用于注册和读取配置的 HTTP 路由位于[机器连接 API](machine-api.md#node-routes)。 + +## 帧与版本 {#frames-and-version} + +每个帧都是一个 JSON 文本消息,其 `version` 等于 `node.ProtocolVersion`;两端都会拒绝任何其他版本,并且没有回退解码器。成员名必须精确且唯一:未知成员、大小写别名、重复项和意外的空值都会被拒绝。控制帧(`hello`、`welcome`、`heartbeat`、`heartbeat_ack`、`retention`、`retention_ack`)最多为 32 KiB;`request` 和 `response` 帧最多为 72 MiB。无效帧会关闭连接。 + +## 连接 {#connection} + +1. 节点在其存储的 Core 源地址上发起对 `/api/v1/sandbox-node/connect?node_id=` 的连接(对于 `https` 使用 `wss`),并以 Bearer 请求头发送节点凭据。Core 对被拒绝的凭据返回 401,节点将其视为永久性拒绝;其他任何失败(包括代理返回的 403)都会使用有界退避进行重试。当某个节点身份已有一个连接正在建立、存活或关闭时,Core 会以 409 拒绝第二个连接。 +2. 节点须在 15 秒内发送 `hello`,其中包含节点身份(`node_id`、`installation_id`、`provider`、`backend_fingerprint`、已登记的 `deployment_generation` 和 `specification_digest`、`max_active`、`max_retained`)、首次健康报告;如果节点能够准备并保留多个部署代次,还包含 `generation_management: true`。除非身份与已认证节点匹配,否则 Core 会关闭连接。 +3. Core 记录节点的存在状态,然后回复 `welcome`,其中包含新的 `connection_id` 和当前 `owner_epoch`;对于支持代次管理的节点,还包含 `deployment`:目标 `generation`、其 `specification_digest` 和可空的 `serving_generation`。节点保存更高的所有者 epoch,并拒绝更低的值。 +4. 节点每 10 秒发送一次 `heartbeat`,其中包含 `connection_id`、`owner_epoch` 和健康状态。对于每次心跳,Core 都会再次认证节点凭据并检查所有者 epoch,记录健康状态并回复 `heartbeat_ack`;对于支持代次管理的节点,回复中还包含 `deployment`。任一端连续 35 秒未收到任何帧时都会关闭连接。 + +只要节点在当前所有者 epoch 下保持连接,并且最近一次心跳距今不足 45 秒,Core 就会将该节点计为在线。心跳会确立 Provider 的就绪状态和最近的主机测量值,但绝不表示 Session 活动。 + +健康报告包含 `provider_ready`、可选的固定 `diagnostic`、`observed_at`、最多为 32 的 `active_operations`,以及 [Runtime 遥测 API](runtime-observability-api.md#node-host-observations-and-history) 报告的主机测量值。未启用代次管理的节点每次报告时都会探测其 Provider;未就绪的 Provider 会报告一个固定诊断代码,该代码根据类型化探测错误进行分类;探测文本和主机路径保留在节点上。Core 会将未知代码存储为 `provider_unavailable`。[节点指南](../../../docs/zh/getting-started/nodes.md#readiness-codes) 列出了这些代码及其原因。支持代次管理的节点则按下文所述按代次报告就绪状态。 + +## Provider 请求 {#provider-requests} + +Core 发送包含以下内容的 `request` 帧: + +| 字段 | 含义 | +| --- | --- | +| `id` | 每个请求使用新的 UUID | +| `sequence` | 在此连接上,每个请求恰好递增 1 | +| `connection_id`、`owner_epoch` | `welcome` 中的值 | +| `deployment_generation` | 分配所属的部署代次,与其计算代次相互独立 | +| `operation` | 下列操作之一 | +| `timeout_ms` | 剩余预算,范围为 1 到 120000 | +| `reference` | 精确的 `(tenant_id, environment_id, allocation_id)` | + +每个操作都携带自己的参数,并在成功时返回以下结果: + +| `operation` | Provider 方法 | 参数 | 成功结果 | +| --- | --- | --- | --- | +| `create` | `Create` | `bootstrap` | `info` | +| `info` | `GetInfo` | 无 | `info` | +| `renew` | `Renew` | 无 | `info` | +| `kill` | `Kill` | 无 | 无 | +| `command` | `RunCommand` | `command` | `command` | +| `observe` | `Observe` | `observation` | `sample` | +| `initial` | `Initial` | 无 | `compute` | +| `new_compute` | `NewCompute` | 大于零的计算 `generation` 和可选的 `snapshot` | `compute` | +| `compute` | `GetCompute` | `compute` | `state` | +| `kill_compute` | `KillCompute` | `compute` | 无 | +| `resume_compute` | `ResumeCompute` | `compute` | `state` | +| `command_compute` | `RunCommandCompute` | `compute` 和 `command` | `command` | +| `suspend` | `Suspend` | `suspend` | `state` | +| `resume` | `Resume` | `resume` | `state` | +| `delete_snapshot` | `DeleteSnapshot` | `snapshot` | 无 | + +只要 `connection_id`、`owner_epoch` 或 `sequence` 中任一值不匹配,请求就会关闭连接。格式错误的请求会得到 `invalid` 响应。未启用代次管理的节点仅接受其登记的 `deployment_generation`;支持代次管理的节点在对应代次的 Provider 上运行请求,无法运行时回复 `unconfirmed`。Core 仅向节点上已就绪的代次发送 `create` 和非 observe-only 的 `resume`,并且每条连接最多保留 32 个待处理请求。 + +预算采用相对计时:节点收到请求时以自己的时钟为基准锚定 `timeout_ms`,并在请求排队等待期间持续消耗该预算,因此各主机的时钟无需保持一致。Core 仍会限制自身等待时长。节点队列已满时会关闭连接。 + +`response` 帧包含 `id` 和 `connection_id`。成功响应携带操作表中指定的结果;对于 `kill`、`kill_compute` 或 `delete_snapshot`,响应不含结果字段。失败响应携带一个 `error_code`: + +| `error_code` | 含义 | +| --- | --- | +| `invalid`、`ownership`、`exists`、`not_found` | `ErrInvalid`、`ErrOwnership`、`ErrExists`、`ErrNotFound` | +| `command_unconfirmed` | `ErrCommandUnconfirmed` | +| `observation_unavailable`、`runtime_not_running` | 对应的观察结果 | +| `unsupported` | 该操作被声明为不支持;见下文 | +| `unconfirmed` 或任何其他值 | 结果未知 | + +失败响应不携带结果,唯一的例外是作为精确引用 `CreateSettled` 回执的 `info` 结果:即便已确认的原生 Create 在后续检查中失败,仍可证明该尝试已有确定结果。超时、响应丢失或断连属于不可用或不确定情况,绝不能证明资源不存在;发生这些情况后,Core 绝不重放变更操作,而是改为观察原始操作。[Sandbox Provider 指南](../../../docs/zh/sandbox-provider.md#operation-outcomes-and-retries) 定义了每种结果。 + +节点启动和代次加载会在接受工作前验证完整的 Provider 操作声明,Core 代理使用同一份已注册声明,因此不支持的操作会在节点解析或原生 I/O 之前被拒绝。操作清单由[操作契约](../../../docs/zh/sandbox-provider.md#explicit-operation-contracts)维护。`unsupported` 响应包含一个 `unsupported` 对象,其中有精确的方法 `operation` 和经作者编写且安全的 `reason`;代理会将两者与请求进行核对。证据缺失、格式错误或不匹配会得到 `unconfirmed` 结果,而绝不会证明变更操作被拒绝。`unsupported` 始终不同于观察不可用,也不同于计算或命令结果未知;它既不确定资源所有权,也不授权重放。 + +## 代次控制 {#generation-control} + +未启用代次管理的节点只服务其登记的代次,配置固定,并且不会收到准备或保留帧。支持代次管理的节点会准备 Core 在 `welcome` 和 `heartbeat_ack` 中通告的目标代次,并在此期间继续服务其持久化的服务代次;目标代次的准备独立于服务 Provider 的就绪状态。 + +节点的 `hello` 和心跳最多携带八条代次观察记录。每条记录指定一个正值的有符号 64 位代次编号、其小写 SHA-256 规范摘要、`ready`、`preparing` 或 `failed` 状态,以及可选的固定诊断信息。目标代次和服务代次的记录排在前面,其余记录公平轮换。八条记录限制的是单条消息,而不是节点可保留的代次数量。省略某条观察记录绝不会授权删除,也不会暗示不存在。 + +保留使用独立且有界的交换。`retention` 请求最多指定八个本地 `(generation, specification_digest)` 引用、一个 UUID、一个每次递增 1 的 `sequence`、当前 `connection_id` 和所有者 epoch。`retention_ack` 必须与完整的待处理请求匹配,包括条目顺序和身份信息,并为每个条目给出显式布尔值 `keep`。每条连接只能有一个交换处于待处理状态,断连会将其丢弃。任何未经请求、重放、过期、不完整或混合的确认都不会删除任何内容。保留流量从不使用 Provider 请求队列。 + +## 本地保留与辅助程序生命周期 {#local-retention-and-helper-lifetime} + +Core 的丢弃授权是回收的必要条件,但并非充分条件。排队和运行中的 Provider 调用、准备过程、本地目标代次以及服务固定引用都会保留引用;回收会再次检查这些引用,并拒绝在已取消的连接上执行。取消 Provider 调用方并不能证明其原生辅助程序已经停止;节点会将该辅助程序计为存活,直到其实际 `Wait` 返回。 + +每个代次都拥有一个永久私有租约文件 `state/node/generations/.lease`。在启动原生辅助程序之前,节点先在该文件上获取共享 flock,在持有该锁时验证持久化租约身份,并要求最终 Provider 配置已经发布且不存在 preparing、collecting 或 dropped 日志。辅助程序继承该描述符;节点仅在 `Wait` 返回后关闭自己的副本,且绝不显式解锁该共享打开文件描述,因此取消调用方或节点退出都不会释放仍在运行的辅助程序所持的引用。原生辅助程序在调用 SDK 前会设置描述符的 close-on-exec 标志,因此 VM 和守护进程后代不会继承它。 + +回收在检查引用、删除共享镜像或版本文件或发布 dropped 标记之前,先获取独占非阻塞 flock,并在这些变更期间持续持有该锁。租约文件属于稳定的节点状态,回收期间绝不删除或替换;符号链接、多重硬链接文件、外来所有权、不安全权限以及被替换的锁定路径都会被拒绝。在第一个辅助程序能够启动之前,安装器先独占创建租约并对其执行 fsync,随后以原子方式持久化私有 `.lease-identity` 记录,并对该记录及其目录执行 fsync。该记录绑定安装、代次、规范摘要、设备号和 inode。Python 回收器和 Go 辅助程序打开器在每次打开时都会验证同一记录,包括重启后;身份记录缺失时,两者都不会进行接管,也不会替换其 inode 或在回收后将其擦除。若安装在身份记录持久化前中断,则会拒绝重新接管,并保留该安装以供检查。身份记录被移除或租约被替换时,即使当前元数据一致,也会拒绝重新接管。 + +已丢弃代次绝不能再次准备或使用。辅助程序退出只能证明本地文件方面的状态,不能证明远程变更操作或结果不确定的 Provider 回执已释放;Core 对分配和放置的持久保留仍保持独立。 + +## 完全匹配的新安装 {#matched-fresh-installation} + +主机程序版本与 Core 选择的 Runtime 版本彼此独立。全新节点从控制台当前版本获取其可执行文件和私有准备器,并从经认证的配置中读取确切的 Runtime 源、镜像身份以及原生运行时和固件摘要。当该 Runtime 较旧时,控制台仍会提供其不可变的 `releases//` 清单、校验和以及允许列表中的制品:Runtime 辅助程序、固件、seccomp 配置文件和镜像字节都来自所选版本;即使下载期间控制台的当前版本发生变化,制品 URL 仍固定到已验证的清单。 + +所需的保留版本缺失时,安装会被拒绝,而不会替换为当前 Runtime;仅包含另一个 Runtime 的本地捆绑包也会导致拒绝。所有这些拒绝都发生在安装器写入节点身份、导入 Runtime、注册节点或启动服务之前。 + +已发布的控制台版本会保留其元数据和制品字节。再次发布时,会先验证所有元数据及每个已存在的已声明制品,然后可以原子地仅添加缺失且校验和匹配的已声明制品;任何冲突都会阻止全部添加,并且不会覆盖任何内容。 + +## 重启恢复 {#restart-recovery} + +Runtime 字节缺失时,绝不将固定的放置实例迁移到当前 Runtime。节点将原始代次和规范摘要保留为未就绪状态,并在恢复前通过 Core 的经认证配置路由请求该精确代次。缺少 seccomp 字节可能留下未就绪的 Provider 占位项;Provider 探测发现镜像或原生制品缺失时,会将修复排队,但不会通告就绪状态。 + +准备和修复串行执行。目标代次和服务代次优先,其余代次则以有界方式推进。每次尝试的截止时间为 30 分钟;失败后依次退避 1、2、5 和 10 分钟,此后最多退避 30 分钟。在连接提供部署信息之前,不准备任何内容。修复保留现有配置和路径,验证所选版本及每个现有同级文件的校验和,并且只下载缺失的不可变文件。字节冲突或保留规范不同都会拒绝修复;如果缺少 Provider 配置且没有精确的准备计划,也会拒绝,而不是重建该配置。 + +修复与回收使用相同的独占代次租约和安装锁,因此正在运行的辅助程序或回收器会保持所有权,修复稍后重试。字节恢复后,节点仍会运行 Provider 的就绪探测;文件存在和具备可执行能力永远不能证明就绪。 + +## 准备与回收记录 {#preparation-and-collection-records} + +新的准备过程会写入两条不同的记录。下载前,`.preparing` 保存安装、代次和规范身份、私有 Provider 路径以及 `import_started: false`;它是恢复和回收计划,而不是 Provider。导入器运行前,计划会记录 `import_started: true`。Python 保留发现流程和 Go 重启恢复流程都能识别仅有待处理状态的计划,但绝不会据此构建、探测或获取 Provider;恢复或回收仍需要当前连接上的授权。 + +只有准备成功才会发布最终 `.json` Provider 配置,该配置只写入一次。Docker 记录其解析器返回的不可变本地镜像 ID;对于同一规范,构建器的配置身份或清单身份均可有效。准备日志清除之前,发布结果必须已经持久化。两者之间发生中断时,会重新验证同一计划和最终身份;计划、规范或路径发生漂移时会拒绝。已取消或失败的导入仍会向 Core 的保留交换保持可见,但不会成为服务代次。 + +在删除任何原生层对象或版本文件之前,节点都会持久化一份私有回收日志,并将其绑定到安装、代次和规范摘要。重启后,未完成的日志仅是保留交换的候选项:它不能准备、探测、获取或通告该代次,必须重新获取一份与之关联的新 Core 丢弃授权后才能继续。在删除版本文件之前先持久化原生清理完成状态,因此重试可以完成部分删除的版本,而不会运行已经删除的辅助程序。完成持久化文件清理后,再写入与摘要绑定的 dropped 标记,以防重新接管;这些小型的配置和所有权日志会保留为本地身份记录。 + +对于仅属于该安装的 microsandbox 存储,成功且完整的原生镜像清单可以区分“不存在”和“CLI 失败”;查询失败、清单格式错误、原生层因正在使用而拒绝或所有权不明时,都会保留这些字节。共享 microsandbox 镜像和私有版本会一直保留到最后一个本地引用消失。Docker 镜像属于主机的共享守护进程:自动回收绝不删除或修剪这些镜像,只有主机管理员在确认主机上没有安装需要它们后才能删除。 + +全新安装还会另行记录其 Runtime 文件的已验证校验和,与主机程序分开保存。回收原始代次时,只删除未被任何保留配置引用的确切私有 Runtime 辅助程序、可执行文件、固件、seccomp 和导入缓存文件。第一次删除前会检查每个剩余文件;哈希未知、字节发生变化、存在链接或缺少所有权元数据时,都会拒绝清理。节点可执行文件、准备器、身份、基础 Provider 配置和清单会保留,因此重启后的节点仍可读取其登记身份并构建较新的保留 Provider。删除共享原生路径前,会先在所有保留配置之间对这些路径进行比较。 + +下载中断后,只会修复原始路径中缺失的字节。如果在任何导入尝试之前执行回收,准备日志会证明该代次没有已导入的原生镜像。如果某代次的原生可执行文件缺失,且导入可能已经开始,该代次仍会保留:文件缺失永远不能证明原生制品不存在,而空的原生清单也永远不能抹除回执或存储历史。 + +诊断代码编写于 `services/core/internal/sandbox/node_diagnostic.go`。共享的 `services/core/internal/sandbox/testdata/node-diagnostics.json` 测试夹具检查 Go 映射、OpenAPI 源注释和生成的枚举,以及 TypeScript 客户端声明。Web 使用客户端规范化器,并检查每个已声明代码的本地化消息。代码变更时要同步更新这些投影;未知代码会规范化为 `provider_unavailable`。 + +准备诊断使用固定的类型化原因。只有制品传输、校验和或版本来源验证失败才会报告 `runtime_download_failed`;私有准备器通过退出类别指示这一类失败,Core 和节点都不解析 stderr。Provider 故障、所有权故障、取消和未分类故障保留其类型化代码,或使用 `provider_unavailable`。协议中不会传输任何 Provider 原始文本。 diff --git a/contracts/agents-api/zh/runtime-observability-api.md b/contracts/agents-api/zh/runtime-observability-api.md new file mode 100644 index 00000000..d81203db --- /dev/null +++ b/contracts/agents-api/zh/runtime-observability-api.md @@ -0,0 +1,288 @@ +--- +title: "Runtime 遥测 API" +source: contracts/agents-api/runtime-observability-api.md +source_hash: d6111447def530b07c392d457cce0cd553f92dafb2e7cc7f481da47f816fcab2 +--- + +Core 通过 `/core/v1` 下的只读管理员路由报告托管 Runtime 和沙箱节点所使用的信息:当前 Runtime 观测值、单个 Session 的已存储 Runtime 历史记录,以及沙箱节点的主机观测值和历史记录。读取操作绝不创建、唤醒、续期或更改计算资源,也绝不向历史记录添加样本。[Runtime observability](runtime-observability.md) 定义了 Core 如何采集和保留这些值;[Console API usage](../../../docs/zh/web/console-api-usage.md) 列出了读取这些值的 Web 页面。 + +每个路由都要求以 Core 密钥作为 Bearer 凭证;缺少或无效的密钥会返回 401 `invalid_admin_key`。路径中的 Project ID 用于选择目标 Project,不承担身份验证作用。响应带有 `Cache-Control: no-store`,采用 Core 错误封装格式,且绝不包含提供方响应、原生标识符、路径或凭证。 + +## 当前 Runtime 观测值 {#current-runtime-observations} + +### 列出所有 Project 的观测值 {#list-observations-of-every-project} + +```http +GET /core/v1/sandbox/runtime-observations?after={session_id}&limit=20&order=desc +Authorization: Bearer +``` + +| 参数 | 规则 | +| --- | --- | +| `after` | 上一页末尾的观测 ID(一个 Session ID)。 | +| `limit` | 1 到 100,默认 20。 | +| `order` | 按 Session 创建时间采用 `asc` 或 `desc`,默认 `desc`。 | + +列表中,每个未删除 Project 下的每个 Session 都有一行,包括模式为 `none`、`self_hosted` 的 Session 以及已释放的托管 Session。每行都包含所属 `project_id` 和一个 `observation`:[`RuntimeObservation`](#runtimeobservation) 与 [`disk`](#disk)。分页采用 Session 列表的创建时间与 ID 键集。观测 ID 就是 Session ID,因此即使 Session 背后的 Runtime 发生变化,分页边界也不会移动。页面不是原子快照:每行都有自己的 `resolved_at`,并在进行了采样时拥有 `observed_at`。未知的查询键会被忽略。 + +```json +{ + "object": "list", + "data": [ + { + "project_id": "3f0c2a9e-2b7d-4d0f-9a51-1c8e4b6d7a20", + "observation": { + "id": "6c77d3a2-71d6-4ed5-884f-687aecda02a3", + "object": "agent.runtime_observation", + "session_id": "6c77d3a2-71d6-4ed5-884f-687aecda02a3", + "environment_id": "6c02fb71-5fa8-4298-93e8-57c6625a3fc2", + "mode": "openai_hosted", + "provider_type": "docker", + "instance": { + "kind": "managed_allocation", + "allocation_id": "d23ab94e-e40b-45bd-93a2-444f1f74642b", + "device_id": "2e434f4f-76aa-4e54-a707-4757036d90ef", + "connection_generation": null + }, + "lifecycle_state": "active", + "status": "observed", + "reason": null, + "allocation_created_at": 1789951200, + "resolved_at": 1789953021, + "observed_at": 1789953020, + "started_at": 1789951220, + "cpu": { + "usage_seconds_total": 482.75, + "capacity_cores": 2.0, + "usage_cores": null, + "utilization_ratio": null + }, + "memory": { + "usage_bytes": 805306368, + "limit_bytes": 2147483648 + }, + "disk": null + } + } + ], + "has_more": false, + "first_id": "6c77d3a2-71d6-4ed5-884f-687aecda02a3", + "last_id": "6c77d3a2-71d6-4ed5-884f-687aecda02a3" +} +``` + +### 获取一个 Session 的观测值 {#retrieve-one-session-s-observation} + +```http +GET /core/v1/projects/{project_id}/sessions/{session_id}/runtime-observation +Authorization: Bearer +``` + +该接口返回一个不含 `disk` 的 `RuntimeObservation`。它不接受任何查询参数。`environment:none` Session 会返回 200,且 status 为 `unsupported`。 + +### `RuntimeObservation` {#runtimeobservation} + +| 字段 | 类型 | 含义 | +| --- | --- | --- | +| `id` | string | Session ID;此资源及其列表游标的稳定标识。 | +| `object` | string | `agent.runtime_observation`。 | +| `session_id` | string | 对应的 Session。 | +| `environment_id` | string 或 null | 仅当 mode 为 `none` 时为 null。 | +| `mode` | enum | `none`、`self_hosted` 或 `openai_hosted`。 | +| `provider_type` | string 或 null | 来源类型,例如 `docker`、`microsandbox` 或 `e2b`;未读取任何提供方数据时为 null。未知值应视为新类型,而不是错误。 | +| `instance` | object | 当前计算资源标识;请参阅 [`RuntimeInstance`](#runtimeinstance)。 | +| `lifecycle_state` | enum 或 null | Core 自身对托管分配的生命周期视图;对于 `none` 和 `self_hosted` 为 null。请参阅下文。 | +| `status` | enum | `observed`、`unsupported` 或 `unavailable`。 | +| `reason` | enum 或 null | 该行没有样本的原因;请参阅 [Status and reason](#status-and-reason)。 | +| `allocation_created_at` | integer 或 null | 创建托管分配时的 Unix 秒数。 | +| `resolved_at` | integer | Core 解析此行时的 Unix 秒数。 | +| `observed_at` | integer 或 null | 提供方样本的 Unix 秒数;无样本时为 null。 | +| `started_at` | integer 或 null | 当前计算实例代次启动时的 Unix 秒数。 | +| `cpu` | object 或 null | 未观测到 CPU 值时为 null。 | +| `memory` | object 或 null | 未观测到内存值时为 null。 | + +`lifecycle_state` 来自 Core 的分配记录,绝不来自样本: + +| 值 | 分配 | +| --- | --- | +| `pending` | 尚未创建,或正在创建 | +| `active` | 正在运行 | +| `sleeping` | 已挂起 | +| `transitioning` | 正在静默处理、挂起、恢复或唤醒 | +| `stopped` | 等待清理,或已释放 | + +### `RuntimeInstance` {#runtimeinstance} + +| 字段 | 含义 | +| --- | --- | +| `kind` | `managed_allocation`、`self_hosted_connection` 或 `none`。 | +| `allocation_id` | 用于标识托管 Session 计算资源的托管分配;其他情况下为 null。 | +| `device_id` | 存在时,为绑定到托管分配的 Runtime 设备;其他情况下为 null。 | +| `connection_generation` | 始终为 null:Core 不观测自托管连接。 | + +### `cpu` {#cpu} + +所有字段均为有限非负数或 null。0 表示观测到的零;null 表示不可用。 + +| 字段 | 含义 | +| --- | --- | +| `usage_seconds_total` | 当前计算实例代次的累计 CPU 秒数(Docker、microsandbox)。 | +| `capacity_cores` | 已配置的 CPU 容量,大于零。 | +| `usage_cores` | 始终为 null。 | +| `utilization_ratio` | 提供方报告的 `capacity_cores` 占比(E2B),不进行钳制;对于报告累计 CPU 时间的提供方为 null。 | + +### `memory` {#memory} + +`usage_bytes` 和 `limit_bytes` 是 JSON 安全整数或 null。使用量为零表示观测到零;`limit_bytes` 至少为 1,未知或无限制的限额为 null。 + +### `disk` {#disk} + +只有列表行包含 `disk`:其值为 null,或为遵循 `memory` 规则的 `{usage_bytes, limit_bytes}`。当沙箱同时报告磁盘使用量和非零容量时,E2B 会填充此对象。Docker 和 microsandbox 返回 null。非 null 的 `disk` 只出现在状态为 `observed` 的行中。 + +### 状态和原因 {#status-and-reason} + +| 状态 | 原因 | 适用情况 | +| --- | --- | --- | +| `observed` | null | 提供方返回了样本。 | +| `unsupported` | `runtime_mode_not_observable` | `none` 和 `self_hosted` Session。 | +| `unavailable` | `allocation_pending` | 托管分配尚不存在或正在创建。 | +| `unavailable` | `runtime_not_running` | 分配正在清理或已释放,或者提供方报告 Runtime 不存在、已停止或已暂停。 | +| `unavailable` | `source_not_configured` | 该分配的提供方未配置任何观测源。 | +| `unavailable` | `sample_timeout` | 提供方读取超过其截止时间。 | +| `unavailable` | `sample_unavailable` | 提供方无法生成当前样本。 | + +所有权不匹配、格式错误的持久身份或无效的提供方证据会使请求失败,而不会转换为 `unavailable` 行。生成的 `core.openapi.yaml` 会记录每个字段的类型、可空性和枚举,但无法表达 status、mode 与字段之间哪些组合有效;上表和上述字段规则具有规范效力。 + +### 错误 {#errors} + +| HTTP | 代码 | 适用情况 | +| --- | --- | --- | +| 400 | `invalid_request_error` | 列表:重复提供 `after`、`limit` 或 `order`,或者 `limit` 或 `order` 无效。 | +| 400 | `unsupported_parameter` | 单项读取:提供任何查询参数。 | +| 404 | `not_found_error` | Project 不存在;Session 或列表游标不存在、格式错误或属于其他 Project。 | +| 500 | `internal_error` | 标识不一致或提供方证据无效。 | +| 503 | `execution_unavailable` | 列表读取超过其采集预算。 | + +### 客户端 {#client} + +`packages/agents-client` 公开了 `AdminClient.listRuntimeObservations({after, limit, order})` 和 `AdminClient.retrieveRuntimeObservation(projectId, sessionId, options)`。`src/types.ts` 中的 `RuntimeObservation` 是以 `status` 和 `mode` 为判别字段的联合类型;`AdminRuntimeObservation` 增加了 `disk`。客户端会检查每个字段、枚举、可空性规则、时间戳和数值,并拒绝未知字段。格式错误的观测值会触发 502 `invalid_runtime_observation` 错误,格式错误的页面会触发 `invalid_admin_response`;任意一行有误都会拒绝整个页面。 + +## Session Runtime 历史记录 {#session-runtime-history} + +```http +GET /core/v1/projects/{project_id}/sessions/{session_id}/runtime-history?start=1789951200&end=1789954800&max_points=120 +Authorization: Bearer +``` + +| 参数 | 规则 | +| --- | --- | +| `start` | 必填。包含端点的 Unix 秒值,必须大于或等于 0。 | +| `end` | 必填。排除端点的 Unix 秒值,必须晚于 `start`,最多比 `start` 晚 24 小时,并且最多只能比当前时间晚 1 秒。 | +| `max_points` | 可选。每个数组的桶数,2 到 1000;默认 120。 | + +每个参数最多只能出现一次。Core 会选择桶宽:将范围除以 `max_points`,向上取整为整数秒,并取 30 秒或采样间隔中的较长者作为最小值。桶从 `start` 开始;最后一个桶在 `end` 结束。 + +Core 先解析 Project,然后解析 Session 及其 Environment,之后才读取存储;分配和提供方标识都是结果,绝不能作为查询输入。只有 `openai_hosted` Session 存在历史记录。 + +```json +{ + "object": "agent.runtime_history", + "source": "durable", + "session_id": "6c77d3a2-71d6-4ed5-884f-687aecda02a3", + "requested_range": { "start": 1789951200, "end": 1789954800 }, + "resolution_seconds": 60, + "generated_at": 1789954801, + "coverage": { + "retained_start": 1789951200, + "first_sample_at": 1789951210, + "last_sample_at": 1789954750, + "sample_count": 118, + "expected_sample_count": 120, + "buckets": [] + }, + "series": [], + "token_usage": [] +} +``` + +`source` 始终为 `durable`。`resolution_seconds` 是桶宽,`generated_at` 是读取时间。只有至少包含一个样本的桶才会出现在 `coverage.buckets`、`series[].points` 和 `token_usage` 中;缺口仍然是缺口,绝不会变成零值。 + +### 覆盖范围 {#coverage} + +`coverage` 会对范围内 Session 的每个已存储样本进行计数,包括不属于任何分配的不可用样本。`retained_start` 是 `start` 与 `generated_at` 前七天两者中较晚的时间。`expected_sample_count` 是 `retained_start` 与 `end` 之间的采样间隔数,并向上取整。每个桶都包含 `start`、`end`、`first_observed_at`、`last_observed_at`、`observation_count`、`observed_count` 和 `unavailable_count`。 + +### 数据系列 {#series} + +每个托管分配都有一条以 `allocation_id` 为键的 series,因此,即使提供方在同一分配下暂停、恢复或替换计算资源,也只会保留一条 series。`environment_id` 和 `provider_type` 用于标识其来源。`started_at` 是该分配的计算资源在保留范围内最早的 start,以 JSON 安全的 `{seconds, nanoseconds}` 表示,其中纳秒值为 0 到 999,999,999;它不是各个桶的开始时间,而且计算运行时长只能来自当前观测值。 + +每个点都包含该桶的边界、该分配样本的覆盖范围计数,以及可空的 `cpu` 和 `memory` 对象;其 `contributor_count` 至少为 1: + +- 对于结束于该桶的所有区间,`cpu.utilization_ratio` 根据同一计算实例代次的连续累计 CPU 计数器得出:消耗的 CPU 秒数除以(经过时间乘以容量)。计算实例代次发生变化或计数器数值减小时,基线会重置。E2B 不报告累计 CPU 时间;其桶值是桶内采样比率的平均值。`cpu.capacity_cores` 是桶内最后一次容量值。 +- `memory.usage_bytes` 和 `memory.limit_bytes` 是桶内最后观测到的值。 + +历史记录中不保存磁盘数据。 + +### Token 用量 {#token-usage} + +`token_usage` 属于 Session,而不属于分配。每个点都保存其所在桶中最后采样到的累计 Session 已测量用量:`start`、`end`、`sampled_at`、`input_tokens` 和 `output_tokens`。已测量的 Session 用量是一项 Core 扩展功能,会对每个已记录的根 Turn 快照求和,其中包括活跃 Turn。它不同于 [public Session usage](sessions-events.md#usage);在根 Turn 运行期间,或根 Turn 结束后未进行测量时,后者的值为 null。这些计数器衡量的是模型 token 数,而不是价格或计费记录。 + +### 错误和界限 {#errors-and-bounds} + +| HTTP | 代码 | 适用情况 | +| --- | --- | --- | +| 400 | `unsupported_parameter` | 提供了 `start`、`end` 和 `max_points` 以外的参数,或同一参数提供了两次。 | +| 400 | `invalid_request` | 范围或 `max_points` 无效。 | +| 404 | `not_found_error` | Project 不存在,或 Session 不属于该 Project。 | +| 409 | `runtime_history_unsupported` | Session 不是 `openai_hosted`。 | +| 500 | `internal_error` | 已存储的标识不一致。 | +| 503 | `runtime_history_unavailable` | Core 未收集周期性历史(即在不使用 execution worker 的情况下运行),或读取失败、超时或产生了超出界限的结果。 | + +每个数组最多包含 `max_points` 个桶,响应最多包含 64 条 series,并且 coverage 和 series 中的点总计最多为 10,000 个。存储错误文本既不会返回,也不会记录到日志中。 + +### 客户端 {#client-1} + +`AdminClient.retrieveRuntimeHistory(projectId, sessionId, {start, end, maxPoints, signal})` 会在发送查询前验证查询。随后,它会检查精确字段、回显的范围和 Session、范围内的桶顺序、coverage 总计、分配标识、贡献者计数、token 用量顺序、可空性、数值和响应大小。任何违规都会使整个响应被拒绝,并返回 502 `invalid_admin_response` 错误。 + +## 节点主机观测值和历史记录 {#node-host-observations-and-history} + +```http +GET /core/v1/sandbox/nodes/{node_id}?range=1h +Authorization: Bearer +``` + +`range` 可以是 `1h`(默认值)、`6h` 或 `24h`。提供其他参数、重复或无效的 `range`,或提供格式错误的节点 ID,都会返回 400 `invalid_request`;节点不存在或已被移除会返回 404 `not_found_error`。响应是 [node list](sandbox-deployment.md) 中的节点对象,并附加 `host` 和 `history`: + +```json +{ + "host": { + "effective_cpu_cores": 4, + "cpu_utilization": 0.35, + "total_memory_bytes": 17179869184, + "available_memory_bytes": 8589934592, + "available_disk_bytes": 107374182400, + "observed_at": "2026-09-25T09:00:00Z" + }, + "history": { + "resolution_seconds": 60, + "points": [{ + "start": "2026-09-25T08:59:00Z", + "cpu_utilization_max": 0.4, + "memory_used_bytes_max": 8589934592, + "available_disk_bytes_min": 107374182400 + }] + } +} +``` + +`host` 是节点最近收到的心跳观测值;每个不可用值,包括未观测到的 `observed_at`,均为 null。离线节点会保留其最后值及原始时间,因此应根据节点的 `online` 和 `host.observed_at` 判断数据时效性。 + +- `cpu_utilization` 是主机聚合 `/proc/stat` 计数器在两次心跳之间的忙碌 tick 占比,范围为 0 到 1。空闲和 I/O 等待 tick 不属于忙碌状态,来宾时间不会被重复计算。连接的首个心跳、计数器重置或无法读取基线时,该值为 null。它衡量整个可见主机,而不是节点进程或其沙箱。 +- `effective_cpu_cores` 会同时考虑节点进程的 CPU 亲和性和 cgroup 限制;无法确定这些值时为 null。 +- `total_memory_bytes` 和 `available_memory_bytes` 分别对应 `MemTotal` 和 `MemAvailable`。 +- `available_disk_bytes` 是节点状态文件系统的可用空间,而不是沙箱配额。 + +节点会测量其命名空间能够看到的内容,因此应让节点运行在其所报告的宿主机上。 + +`history` 按完整的 UTC 桶覆盖指定范围:`1h` 使用 60 秒,`6h` 使用 300 秒,`24h` 使用 900 秒。范围内的每个桶都会存在,但正在进行中的桶除外。`cpu_utilization_max` 和 `memory_used_bytes_max` 是已记录观测值的最大值,其中已用内存按同一观测值中的总内存减去可用内存计算;`available_disk_bytes_min` 是最小值。对于没有记录值的桶,包括离线时段,每个指标均为 null。Core 绝不进行插值或回填。 + +`packages/agents-client` 中的 `SandboxAdminClient.retrieveNode(nodeId, range, options)` 会读取此路由并验证响应。 diff --git a/contracts/agents-api/zh/runtime-observability.md b/contracts/agents-api/zh/runtime-observability.md new file mode 100644 index 00000000..a6d894a4 --- /dev/null +++ b/contracts/agents-api/zh/runtime-observability.md @@ -0,0 +1,132 @@ +--- +title: "运行时可观测性" +source: contracts/agents-api/runtime-observability.md +source_hash: 5da1279a81a6dcb3a85661dbba942c8937f871ae351ed80550db65b2a59156db +--- + +这是面向贡献者的契约,规定 Core 如何观测 Runtime 并保留其历史。路由和响应字段见 [Runtime telemetry API](runtime-observability-api.md)。代码位于 `services/core/internal/runtimeobs`(解析、源、采样器和导出)、`internal/runtimehistory`(历史查询和 PostgreSQL 存储)以及 `internal/runtimeobs/otlpexporter`。 + +观测属于遥测数据。它们绝不会创建、续期、唤醒、恢复或停止计算,绝不会影响 Session 活动,也绝不会决定空闲时间、挂起、准入或执行结果。 + +## 身份与源选择 {#identity-and-source-selection} + +在读取 provider 之前,Core 会先把每次观测归属到持久化的 Core 身份: + +```text +managed: tenant_id -> session_id -> environment_id -> runtime_allocation_id +self-hosted: tenant_id -> session_id -> environment_id -> device_id + connection_generation +none: tenant_id -> session_id (no Session-owned Runtime instance) +``` + +解析器(`internal/runtimeobs/storeresolver`)从存储中读取 Session、其 Environment、当前分配以及 Session 的实测使用量。Session、守护进程连接、进程、容器和原生 Harness Session 是不同身份,彼此绝不能替代。 + +托管 Docker、microsandbox 和 E2B 分配均会被观测。`none` 和 `self_hosted` Session 为 `unsupported`;Core 绝不会将共享主机统计信息归属于 `environment:none` Session。 + +分配中持久化的 `provider_key` 会选择且仅选择一个已配置源;该源在返回数值前会验证分配标签或等效所有权数据。在读取任何 provider 之前,部分行的结果由分配状态决定:处于 `creating` 状态或尚无分配时得到 `allocation_pending`,处于 `cleanup_pending` 或 `released` 状态时得到 `runtime_not_running`,provider key 没有对应源时得到 `source_not_configured`。provider 读取超出截止时间时得到 `sample_timeout`,返回未运行结果时得到 `runtime_not_running`,返回不可用结果时得到 `sample_unavailable`。任何其他错误、所有权不匹配或无效采样都会使读取失败。 + +观测边界在 `services/core/internal/runtimeobs/source.go` 中声明。每个注册的 `SourceResolver` 都声明支持 `ResolveObservationSource`;注册过程会验证此声明,但不会加载配置或读取数据库。Core 每页只解析每个 provider key 一次,随后验证返回的 `Source`,并在该页上针对此 key 的每次读取中使用同一不可变源。未配置的解析器返回类型化的 `ErrUnavailable`,从而生成不含 provider 类型的 `sample_unavailable`。其他解析错误遵循上述 provider 读取错误规则。 + +源会声明支持的 `ObservationProviderType`,该类型返回其不可变遥测标识:一个小写字母,后跟最多 31 个小写字母、数字或下划线。空标识无效。在任何采样或导出之前,provider 注册和每个解析后的绑定都会验证标识及操作声明。重新配置会影响后续的源解析,但无法更改正在处理页面所选定的标识或 provider。Generation 路由器仍会通过每个分配记录的部署代次解析该分配。 + +源实现 `Observe`,并在 provider 操作中声明 `ObserveBatch`。声明支持 `ObserveBatch` 时,一次调用可读取该 provider 的最多 100 个目标;声明不支持时,Core 使用 `Observe` 读取每个目标。批量读取失败后绝不会逐个重试目标。[Sandbox Provider guide](../../../docs/zh/sandbox-provider.md) 说明了这些操作声明。 + +## 采样语义 {#sample-semantics} + +一个采样包含: + +- `observed_at`,即 provider 的观测时间;以及 `started_at`,即当前计算实例启动周期的开始时间; +- 累计 CPU 秒数,以及以核心数计的已配置 CPU 容量; +- provider 报告的 CPU 利用率比率,仅适用于不提供累计 CPU 时间的 provider(E2B); +- 当前内存使用量,以及以字节计的内存限制; +- 当前磁盘使用量和容量,以字节计,且仅在 provider 报告这些值时提供(E2B)。 + +每项测量都是可选的。存在的零值表示观测到零;缺失值表示不可用,绝不会显示或聚合为零。Core 会拒绝以下采样:`observed_at` 晚于 Core 自身时钟,`started_at` 晚于 `observed_at`,或任一值为负数、非有限值、容量或限制值为零,或超出 JSON 安全整数范围。 + +`lifecycle_state` 根据 Core 记录中的分配状态和计算阶段派生,绝不会从采样派生:没有分配或状态为 `creating` 时为 `pending`;状态为 `running` 且计算阶段为 `running` 或 `disabled` 时为 `active`;`suspended` 为 `sleeping`;`quiescing`、`suspending`、`restoring` 或 `waking` 为 `transitioning`;`cleanup_pending` 和 `released` 为 `stopped`。 + +## Provider 映射 {#provider-mapping} + +### Docker {#docker} + +对所属容器进行一次非流式 Inspect 和 Stats 读取。累计 CPU 时间来自 cgroup 计数器,内存使用量来自当前 cgroup 使用量,CPU 和内存容量来自容器配置的容量限制。容器的 `StartedAt` 是本次计算实例启动周期的开始时间,因此重启容器会重置计算运行时长。容器缺失或已停止时为 `runtime_not_running`。磁盘为 null。 + +### microsandbox {#microsandbox} + +处于挂起状态的分配为 `runtime_not_running`,且不会调用 helper。否则,Core 会针对分配中记录的确切计算实例向 helper 发送 `metrics` 请求。在分配锁保护下,helper 通过固定版本 SDK 检查 sandbox 身份,运行固定版本的 `msb metrics --format json` CLI 读取,再次检查身份;sandbox 必须处于 running 或 draining 状态。[microsandbox helper](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/tools/microsandbox-provider/README.md) 负责此请求。 + +累计 vCPU 时间、来宾内存使用量和有效内存限制均来自该次 CLI 采样。CPU 容量为部署中配置的 CPU 数量。`started_at` 等于采样自身的时间戳减去其毫秒级运行时长,绝不能从较晚读取的时钟值中减去已取整的运行时长;因此,恢复后的 generation 会重置计算运行时长,而分配时长会继续累计。不使用瞬时 CPU 百分比、主机内存、磁盘和网络值,磁盘为 null。对于关闭了挂起功能但未记录计算实例身份的分配,结果为 `sample_unavailable`;其他任何未记录计算实例身份的分配都会使读取失败;绝不会改用确定性 sandbox 名称。 + +### E2B {#e2b} + +一次 helper `observe` 请求会读取一页最多 100 个分配:读取私有回执中指定 sandbox 的 E2B 批量指标,并获取该 installation 中运行中 sandbox 的带标签列表,以确认每一个 sandbox。[E2B helper](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/tools/e2b-provider/README.md) 负责此请求。它绝不会连接、续期或更改 sandbox,也不会写入任何回执。 + +| E2B 值 | 采样字段 | +| --- | --- | +| `cpuUsedPct / 100` | CPU 利用率比率 | +| `cpuCount` | CPU 容量 | +| `memUsed`、`memTotal` | 内存使用量和限制 | +| `diskUsed`、`diskTotal` | 磁盘使用量和容量;仅当两者都存在且总量非零时保留 | + +E2B 不报告累计 CPU 时间,因此 CPU 秒数保持为 null。`observed_at` 为 E2B 的时间点;比 Core 时钟最多领先 30 秒的时间点按 Core 时间记录,领先幅度更大时则为 `sample_unavailable`。未出现在运行列表中的 sandbox 为 `runtime_not_running`。时间点缺失或格式错误、列表存在歧义以及 E2B API 失败(包括 key 被拒绝)均为 `sample_unavailable`;格式错误的时间点只影响其所在行。 + +## 读取预算 {#read-budgets} + +当前列表读取处理一页最多 100 个 Session(默认 20 个),provider 读取并发数最多为 8。每次 provider 读取的时限为 2 秒,批量读取至少为 5 秒,整个列表请求为 10 秒;超过这些时限时,列表返回 503。单 Session 读取的时限为 2 秒。一次请求内不会重试任何 provider 调用,Core 也不保留观测缓存。 + +## 时长 {#durations} + +以下时长回答不同问题,彼此保持独立: + +- 分配时长:`runtime_allocations.created_at` 到 `released_at`,或到当前时间; +- 计算运行时长:采样的 `started_at` 到 `observed_at`; +- 忙碌 Turn 时长:`turns.started_at` 到 `completed_at`,或到当前时间。 + +CPU 静默状态、心跳时龄、连接状态和保活时间都不是空闲时间。 + +## 保留的历史记录与可选导出 {#retained-history-and-optional-export} + +### 周期采样 {#periodic-sampling} + +周期采集仅随执行工作器运行而执行(Core 需使用 `OAC_PUBLIC_URL` 启动;见 [Core environment](../../../docs/zh/configuration.md#appendix-core-environment-without-the-installer)),并受该工作器的数据库租约保护。未运行该工作器的 Core 不存储历史记录,所有历史读取都返回 503;当前读取在两种情况下均可正常工作。 + +采样器在启动时扫描一次,此后每次扫描结束后再经过一个采样间隔再次扫描。一次扫描按 Session ID 顺序,对未删除、状态为 `openai_hosted` 且没有已释放分配的 Session 执行 keyset 扫描。它通过与当前读取相同的解析器和源,以 32 个 Session 为一页进行读取,并发数为 8,每个源时限为 2 秒。采样器在每页之前以及扫描期间每 100 ms 检查租约;失去所有权时取消进行中的读取;将每条记录交给导出之前再次检查租约。失败的行不会停止扫描;未完成的扫描会在下一个间隔重复。 + +每次观测,无论来自当前读取还是周期采集,都会标记采集源 `on_read` 或 `periodic`,并放入每个导出器的有界队列。队列已满时会丢弃记录;该记录将成为缺失采样,而绝不会成为零。PostgreSQL 历史存储和可选 OTLP 导出器使用彼此独立的队列,因此导出器故障不会延迟本地历史记录或执行。[`core.runtime_history` settings](../../../docs/zh/configuration.md#settings) 用于设置采样间隔、队列容量、超时和 OTLP 目标。 + +### 存储的历史记录 {#stored-history} + +PostgreSQL 存储仅保留周期性的 `openai_hosted` 记录,因此 API 读取无法虚增覆盖率。每条记录都会写入一行 `runtime_history_samples`,以租户、Session、Environment 和解析时间为键,包括分配信息、provider 类型、状态、观测时间与启动时间、CPU 秒数、容量和利用率比率、内存使用量和限制,以及实测令牌计数器。磁盘数据不存储。缺少 `started_at` 的采样仍计入覆盖范围,但会丢弃资源值,因为这些值无法关联到某次计算实例启动周期。 + +历史服务在查询前解析 Project、Session 和 Environment;查询始终携带该作用域和有界时间范围,但绝不携带 provider 身份。存储保留 7 天。单次读取最多覆盖 24 小时,最多读取 20,000 条原始采样,并从范围起点之前两个采样间隔处开始读取,以查找 CPU 基线;每个数组最多返回 1,000 个桶,最多返回 64 个序列,总点数最多 10,000 个。结果超出请求的作用域、时间范围或限制时,读取失败。API 的 [Series](runtime-observability-api.md#series) 部分说明了聚合方式。 + +`runtimehistory.Capabilities` 声明采集模式、间隔、7 天保留期、最小桶宽度(30 秒或采样间隔,取较长者)、24 小时范围和点数限制;历史路由仅在所有这些值有效且采集模式为 `periodic` 时响应,否则返回 503。 + +清理循环每分钟运行一次,即使没有活跃 Runtime 也会运行。每轮最多耗时 2 秒,按每表 256 行的批次删除过期 Runtime 行和节点主机行,每张表最多 16 批。读取绝不会返回超过保留期的行。 + +### 节点主机历史记录 {#node-host-history} + +每次扫描结束后,Core 会在 2 秒内且在检查租约后,将每个新鲜节点心跳复制到 `node_host_history_samples`,其中包含主机 CPU 利用率、已用内存和可用磁盘,并以节点和心跳自身时间为键,因此重复扫描不会增加数据。心跳新鲜的判定条件是:其节点未被移除、在当前所有者 epoch 下处于连接状态、在过去 45 秒内被观察到,并且所报告的主机时间位于过去 45 秒内且不在未来。同样适用 7 天清理期。节点主机历史属于遥测数据,绝不作为调度或容量的权威依据,读取也绝不会对其进行采样或回填。 + +### 令牌使用量 {#token-usage} + +每次观测都携带来自 `MeasuredSessionUsage` 的 Session 实测使用量,即所有已记录根 Turn 快照之和,其中包含活跃 Turn。这不是公开的 Session 使用量规则,并且 Core 绝不会从 provider 计数器、上下文占用量或成本推导令牌数。 + +### OTLP 导出 {#otlp-export} + +配置 OTLP 端点后,Core 会将每条记录,包括 `on_read` 和 `periodic`,都导出为 OTLP/HTTP 指标。资源具有 `service.name=oac-core` 和 `service.namespace=oac`。 + +| 指标 | 聚合方式 | 来源 | +| --- | --- | --- | +| `agents.runtime.cpu.usage` | 单调累积和,秒 | 累计 CPU 计数器 | +| `agents.runtime.cpu.capacity` | Gauge,核心数 | 已配置的 CPU 容量 | +| `agents.runtime.cpu.utilization` | Gauge,比率 | Provider 报告的 CPU 占比(E2B) | +| `agents.runtime.memory.usage` | Gauge,字节 | 内存使用量 | +| `agents.runtime.memory.limit` | Gauge,字节 | 内存限制 | +| `agents.session.tokens.input` | Gauge,令牌 | 实测 Session 输入令牌 | +| `agents.session.tokens.output` | Gauge,令牌 | 实测 Session 输出令牌 | +| `agents.runtime.sample` | 单调差值和 | 每个经验证的结果一条,包括 unavailable 和 unsupported | +| `agents.runtime.sample.duration` | Delta 直方图,秒 | Provider 读取时长;批量读取仅计一次 | + +仅当采样包含 `started_at` 时才导出 CPU 和内存数据点;缺少测量值不会产生数据点。属性包括 `agents.tenant.id`、`agents.session.id`、`agents.environment.id`、`agents.runtime.allocation.id`、`agents.runtime.mode`、`agents.runtime.provider.type`、`agents.runtime.status`、`agents.runtime.reason`、`agents.runtime.collection.source`,以及以纳秒为单位的 `agents.runtime.resolved_at_unix_nano`、`agents.runtime.observed_at_unix_nano` 和 `agents.runtime.compute.started_at_unix_nano`。这些纳秒时间使记录在后端以较低精度存储事件时间时仍可关联。Provider key、回执、原生标识符、原始错误、路径和凭据绝不会作为属性。 + +Web 仅通过 Core 读取历史记录;其图表不需要 Collector 或其他指标存储。 diff --git a/contracts/agents-api/zh/sandbox-deployment.md b/contracts/agents-api/zh/sandbox-deployment.md new file mode 100644 index 00000000..509fed4b --- /dev/null +++ b/contracts/agents-api/zh/sandbox-deployment.md @@ -0,0 +1,231 @@ +--- +title: "沙箱部署" +source: contracts/agents-api/sandbox-deployment.md +source_hash: e84ced73c64da30f33feba032d7e8e9bc8c33b3062604a47258102460615429b +--- + +沙箱部署为 Core 管理的 `openai_hosted` 执行选择 Sandbox Provider、每个沙箱的资源以及不可变的 Runtime 发行版。PostgreSQL 为每个安装维护一个当前有效选择;Web 和 Core API 写入同一配置。节点文件保存其已安装副本和特定于主机的路径,且不能覆盖其资源或 Runtime。该选择独立于 Harness;部署可以保持未配置状态,既无节点,也不接受托管准入。 + +本契约负责下列 Core API 路由及其语义。[节点指南](../../../docs/zh/getting-started/nodes.md)负责操作员工作流,[机器连接 API](machine-api.md#node-routes)负责节点调用的路由,[沙箱节点协议](node-generation-protocol.md)负责节点连接。 + +## 路由 {#routes} + +每个路由都需要 Core 密钥。[Web 的控制台服务器](../../../docs/zh/web/console-server.md)会在服务器端为已登录请求添加该密钥。 + +| 路由 | 效果 | +| --- | --- | +| `GET /core/v1/sandbox/deployment` | 读取安全的当前配置、推出、重置和资源计数 | +| `POST /core/v1/sandbox/deployment` | 选择初始提供商、资源和 Runtime | +| `PUT /core/v1/sandbox/deployment` | 在线更改同一提供商的目标 | +| `POST /core/v1/sandbox/deployment/reset` | 启动托管资源的持久清除或将其升级为持久清除 | +| `DELETE /core/v1/sandbox/deployment/reset?expected_generation=N` | 取消剩余清除 | +| `POST /core/v1/sandbox/providers/{provider}/discovery` | 使用临时凭据查询提供商配置目录 | +| `POST /core/v1/sandbox/enrollment-tokens` | 签发具有已批准容量的一次性节点注册令牌 | +| `GET /core/v1/sandbox/nodes` | 列出已注册节点 | +| `GET /core/v1/sandbox/nodes/{node_id}` | 读取一个节点及其[主机观测和历史](runtime-observability-api.md#node-host-observations-and-history) | +| `PATCH /core/v1/sandbox/nodes/{node_id}` | 更改节点的名称和容量 | +| `DELETE /core/v1/sandbox/nodes/{node_id}` | 移除节点 | +| `GET /core/v1/sandbox/nodes/{node_id}/allocations` | 列出节点尚未释放的分配 | +| `GET /core/v1/sandbox/runtime-observations` | 当前 Runtime 观测;请参阅 [Runtime 遥测 API](runtime-observability-api.md) | + +## 选择请求 {#selection-request} + +POST 和 PUT 接受相同的完整选择,并要求提供先前 GET 返回的 `expected_generation`。对于初始未配置部署,零是有效值;省略或 null 无效。检查过期代次早于检查重置、提供商、资源和选择项相同这一条件,即使请求体与先前请求完全相同也是如此。 + +| 字段 | 含义 | +| --- | --- | +| `expected_generation` | 必填的非负整数,来自 GET;绝不会自动刷新并重放 | +| `provider` | 必须是 `docker`、`microsandbox`、`e2b` 中恰好一个 | +| `resources` | 每个沙箱的限制,见下文;Docker 和 microsandbox 必填,E2B 可选 | +| `runtime` | 不可变的 [Runtime 发行版](#runtime-release);Docker 和 microsandbox 必填,E2B 必须省略 | +| `configuration` | 提供商的公开选择器。E2B:不可变的 `template` 构建以及可选且配套的 `api_url` 和 `domain`。Docker 和 microsandbox 仅接受 `{}` 或省略 | +| `credential` | 提供商的只写凭据。E2B:`{api_key}`,首次设置时必填,在 PUT 中省略以保留当前密钥;null 或空密钥无效。Docker 和 microsandbox 拒绝该字段 | + +请求中没有 Core 地址。Core 根据安装公开 URL(`config.json` 中的 `public_url`,Core 对应 `OAC_PUBLIC_URL`)派生部署的 `core_url`:这是节点和沙箱客户机访问 Core 时使用的源地址。包含 `core_url` 的请求会像包含任何其他未知成员一样被拒绝,并返回 400 `invalid_request`。E2B 客户机从 E2B 云访问 Core,因此当公开 URL 为回环地址时,E2B 选择会被拒绝,并返回 409 `sandbox_configuration_error`。Docker 和 microsandbox 选择接受回环公开 URL,但这仅适用于本地开发,因为客户机的回环地址无法访问其主机。更改公开 URL 属于安装变更:使用旧地址注册的节点不会收到新沙箱,必须移除后重新添加。 + +### 资源 {#resources} + +| 字段 | 可接受值 | +| --- | --- | +| `cpus` | 整数,1 到 255 | +| `memory_mib` | 整数,512 到 1048576 MiB | +| `root_disk_mib` | microsandbox:至少 1024 MiB;Docker 和 E2B:省略或为零 | +| `environment_disk_mib` | microsandbox:至少 1024 MiB;Docker 和 E2B:省略或为零 | + +这些限制描述每个沙箱。节点的 `max_active` 和 `max_retained` 是独立的预留限制,主机测量值绝不会允许超过其中任何一个。即使数值满足这些边界,原生提供商仍可能拒绝这些值。 + +Docker 应用 CPU 和内存限制,并检查运行中容器的限制和精确镜像;它没有硬性的根磁盘或工作区磁盘配额。E2B 的 CPU 和内存必须与精确的就绪模板构建一致,Core 会在保存前通过固定版本 SDK 进行验证;磁盘容量仍属于模板的一部分。两种提供商都不接受其无法强制执行的磁盘配额。E2B 选择可以省略 `resources`:此时 Core 将构建的 CPU 数量和内存存储为 `cpus` 和 `memory_mib`,并通过 `specification.resources` 返回,不带磁盘字段。重启时,Core 会加载已提交的 E2B 选择,而不会再次验证模板构建,因此 E2B 中断绝不会阻碍检查或清理;新选择仍需验证。 + +microsandbox 会配置 CPU、内存、托管根磁盘,以及位于 `/environment` 的独立自有磁盘。[microsandbox 辅助工具](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/tools/microsandbox-provider/README.md)说明了 restore 如何处理这些限制。 + +### Runtime 发行版 {#runtime-release} + +Docker 和 microsandbox 使用一个经过验证发行包中的每个字段: + +| 字段 | 标识 | +| --- | --- | +| `source_commit` | 由 40 个字符组成的小写提交 SHA | +| `image_id` | Docker 镜像配置 ID:`sha256:` 加 64 个小写十六进制字符 | +| `image_manifest_digest` | OCI 镜像清单摘要,格式相同 | +| `microsandbox_ref` | `oac-runtime@sha256:` 加 64 个小写十六进制字符 | +| `runtime_sha256` | 原生 microsandbox Runtime 二进制文件的 SHA-256 | +| `firmware_sha256` | 匹配固件的 SHA-256 | + +请从匹配的发行清单中复制这些标识。镜像配置 ID 和 OCI 清单摘要标识不同的对象,绝不能相互替代。节点安装程序会在注册前根据载荷验证已保存的发行版,并保留其导入的精确本地镜像标识。 + +### E2B 配置 {#e2b-configuration} + +E2B 使用 `template-id:build-uuid` 形式的 `configuration.template`;构建 UUID 必须是规范形式且非零,仅提供可变模板别名将被拒绝。API 密钥在 PostgreSQL 中加密,绝不会出现在响应、引导配置、命令参数或日志中。默认情况下,Core 使用 `https://api.e2b.app` 和 `e2b.app`。对于兼容服务,请同时设置 `configuration.api_url`(一个不含路径、端口、查询、片段或凭据的 HTTPS 源地址)和 `configuration.domain`(沙箱数据平面 DNS 后缀);API 主机必须等于该域名或其子域。Core 会在发送守护进程凭据或使用 envd 之前,拒绝数据平面域名位于所选后缀之外的沙箱。 + +### 配置发现 {#configuration-discovery} + +`POST /core/v1/sandbox/providers/{provider}/discovery` 恰好接受 `configuration`、`credential` 和 `query` 对象,总大小最多为 64 KiB,运行时间最多为 30 秒。未知成员和 null 对象会被拒绝。凭据仅用于该请求,绝不会被存储或返回;发现操作不会保存任何内容、不会更改部署、不会分配任何资源,也绝不会证明某项选择会被准入。Docker 和 microsandbox 会以 400 `sandbox_operation_unsupported` 拒绝该操作。 + +对于 E2B,发布 `{"configuration": {"api_url": "…", "domain": "…"}, "credential": {"api_key": "…"}, "query": {}}` 可列出模板;添加 `"query": {"template": "template-id"}` 可列出该模板的就绪构建;官方端点可能会省略两个端点字段。结果为 `{"templates": [{"id": "…", "names": ["…"]}]}` 和 `{"builds": [{"id": "build-uuid", "cpus": 2, "memory_mib": 2048}]}`,两者都可能为空。固定版本 SDK 辅助工具读取 `GET /v2/templates` 并返回最多 200 条结果;达到限制或提供商失败会返回 503 和通用消息。部署写入操作会单独验证所选构建。 + +## 安全响应 {#safe-response} + +GET 和成功的写入操作会返回 `installation_id`、`provider`、`core_url`(只读:安装公开 URL,即使配置前也存在)、`mode`、`generation`、`owner_epoch`、`reset`、`rollout`、`suspension`、`resources` 和 `credential_configured`。已配置的部署还会返回 `specification`、`specification_digest`、`configuration` 和 `metadata`:这是适配器对其选择器及其记录的观测结果所作的公开投影,绝不会包含原始存储值或机密。E2B 返回 `configuration.template`、`configuration.api_url`、`configuration.domain`,并在记录后返回 `metadata.template_build`。Docker 和 microsandbox 返回空的 `configuration` 和 `metadata` 对象以及 `credential_configured: false`;未配置的部署则不含这两个对象。 + +- `metadata.template_build` 为 `{status, resources: {cpus, memory_mib, root_disk_mib}}`:这是保存选择时 Core 通过固定版本 SDK 读取的构建。GET 绝不会调用 E2B,因此 E2B 中断期间该操作仍保持低成本。验证仅接受 CPU 数量和内存与所选值一致的 `ready` 构建;`root_disk_mib` 是构建的原生磁盘大小,Core 不会强制执行该值。未知值为 null,`metadata: {}` 表示未记录任何观测,在不提供凭据的情况下提交完全相同的 PUT 也不会刷新它。 +- `suspension` 对 microsandbox 而言为 `{idle_seconds, retention_seconds}`,microsandbox 是 Core 唯一会暂停的提供商(当前为 300 和 86400);Docker、E2B 和未配置的部署返回 null。 +- 请求中的 `resources` 和响应中的 `specification.resources` 是每个沙箱的限制。响应中的 `resources.allocations` 和 `resources.pending` 分别计算尚未释放的分配,以及尚未分配沙箱的待处理托管 Environment。 +- 未配置的部署具有空的 provider 且没有 specification。Docker 和 microsandbox 使用 `mode: nodes`;E2B 使用 `mode: direct`,且没有合成节点。 +- `generation` 标识已保存的选择。`owner_epoch` 用于对执行所有者和节点连接进行栅栏隔离;它不能替代 `expected_generation`。 +- `specification_digest` 是服务器对提供商、限制和 Runtime 发行版的标识;注册过程会原样回显该值。 + +`packages/agents-client` 中类型化的 `SandboxAdminClient` 会根据准确的这些结构检查部署、节点列表、节点详情和分配响应。未知成员、缺少成员或类型错误都会拒绝整个响应,并产生 502 `invalid_admin_response` 错误。节点的 `diagnostic` 可能不存在,也可能是一个代码,但绝不会为空;客户端会将未知代码读取为 `provider_unavailable`。 + +## 初始设置与同提供商更改 {#initial-setup-and-same-provider-changes} + +POST 会在持久保存候选配置之前对其进行验证,并且不会创建计算资源、Session 或模型请求。在当前代次上,完全相同的选择是无操作;使用旧代次则返回 409 `sandbox_generation_stale`,即使请求体相同也是如此。缺少前置条件或准备失败时,已提交的提供商保持不变。更改后端前必须先重置。POST 还会在重置完成后使用该重置的新代次进行初始化。 + +仅在没有重置进行时,PUT 才接受同一提供商。只发送一次观测到的代次,绝不自动重放结果不确定的写入操作。新分配使用新提交的 specification,现有分配则保留其不可变的部署代次。同提供商更改不会停用任何节点、令牌或所有者 epoch,也不会排空任何执行:Docker 和 microsandbox 节点在继续提供旧固定版本的同时独立准备新目标,而 E2B 更改会立即生效。 + +### E2B 密钥替换 {#e2b-key-replacement} + +在 PUT 中省略 `credential` 可保留当前密钥;不提供该字段的完全相同选择是无操作。提交密钥时,即使密钥相同,也始终会验证密钥并推进代次;null 或空密钥无效。仅更改密钥时使用相同的完整请求体(provider、现有 configuration、可选 resources、`expected_generation` 和新 `credential`),无需单独路由,也不会隐式重置。 + +初始设置要求所选模板出现在该密钥所属团队的模板列表中;仅可公开读取并不足够。在线更改之前,Core 会验证已提交密钥拥有当前模板,然后要求候选密钥拥有完全相同的模板,从而确立团队所有权。Core 还会使用候选密钥在其原始端点读取候选构建和每个保留构建,并确认带安装标签的沙箱列表中每个已结算的实时回执。 + +| 结果 | 含义 | +| --- | --- | +| `409 sandbox_reset_required` | 当前选择没有团队所有权锚点,或已提交密钥不再通过身份验证 | +| `409 sandbox_credential_ownership` | 候选密钥属于另一个团队;初始化另一个团队前必须先重置 | +| `400 sandbox_credential_invalid` | 候选密钥收到 401 或 403 | +| `400 sandbox_configuration_invalid` | 候选构建无效或与资源不匹配 | +| `503 sandbox_verification_unconfirmed` | 回执缺失或未结算,或读取未得到确认 | + +不会返回提供商文本或凭据。写入操作及其 `change` 或 `replace_credential` 审计条目共用一个事务。替换操作会短暂对提供商调用设置栅栏,即使调用方已取消,也会等待辅助进程实际退出,并在提交前再次验证;辅助进程退出并不能证明远程 Create 已结算。栅栏和读取均有时间边界,失败时会保留旧密钥和生命周期。成功响应后,所有保留代次管理都使用已提交的密钥;只有在清理完成后,才能在 E2B 中撤销旧密钥,绝不能提前撤销。模板和资源更改不会排空生命周期。 + +## 代次所有权与推出 {#generation-ownership-and-rollout} + +`runtime_deployment` 保存当前 specification。被取代的行仅保留不可变的 specification、构建和端点元数据,绝不保留另一个 E2B 密钥。E2B 分配在预留时绑定其代次;节点放置在 Session 准入时绑定,其分配会复制该代次,即使之后发生更新也是如此。检查、续期、命令和清理使用分配的原始 specification 和端点以及当前密钥;缺失代次绝不会回退到当前 specification。已释放代次的标识仍会保留。 + +当一个代次为当前代次、被尚未释放的分配或放置引用,或者被尚未移除的节点固定时,该代次会保留。节点的持久服务固定状态可跨离线时段和零资源状态保留,并与当前就绪状态相互独立。回收操作与更新和准入共享部署锁,每轮最多删除 32 个符合条件的代次行。只有确认释放后,重置才会停用固定状态并清除被取代的行。 + +每个部署响应都包含 `rollout`: + +```json +"rollout": {"state": "settled", "previous_generation_sandboxes": 3, "nodes": null} +``` + +`previous_generation_sandboxes` 使用与资源总数相同的快照,统计旧代次中尚未释放的分配以及没有分配的待处理放置,绝不会重复计算同一资源。E2B 和未配置的部署返回 null `nodes`。节点部署将 `nodes` 返回为计数 `{ready, preparing, failed, update_required, unknown}`,每个尚未移除的节点恰好归入一个类别: + +- 在当前连接上离线的节点为 `unknown`; +- 不具备代次管理且在较旧目标上注册的在线节点为 `update_required`; +- 否则,当前连接和所有者 epoch 上的精确目标观测会给出 `ready`、`preparing` 或 `failed`,缺少观测则给出 `unknown`。 + +节点在当前所有者 epoch 下保持连接,并且最近 45 秒内有心跳时,即为在线。目标推出与服务就绪状态相互独立:`unknown`、`preparing`、`failed` 或 `update_required` 不会移除已独立确认的较旧服务代次。`provider_ready` 要求节点在线,并且在该连接和 epoch 上观测到精确的服务代次;不具备代次管理的节点仅能使其注册时的代次满足此条件。固定状态本身绝不表示就绪。 + +每个节点会添加 `rollout: {state, ready_generation, diagnostic?}`,其中 `ready_generation` 是可为 null 的持久服务固定状态,`diagnostic` 是目标代次的固定代码;分配项会添加 `deployment_generation`。仅当 `rollout.state` 为 `preparing` 或 `reset` 非 null 时,才每五秒轮询一次;旧 Session 以及失败、需要更新或离线的节点本身均不会使轮询保持活动状态。 + +新的准入操作会先按在线状态、精确的服务代次就绪情况、地址和共享容量筛选节点,再优先选择最新的合格固定状态,因此最新的节点已满时不会掩盖仍有空闲资源的较旧节点。没有候选项时,准入操作不会创建临时 Session 或放置:在线节点若确实正在准备且有空闲容量,则返回 503 `sandbox_nodes_preparing`;节点集群全部已满或离线,则返回 `runtime_node_unavailable`。 + +## 重置 {#reset} + +要更改后端,请启动重置: + +```json +{"expected_generation": 7, "clear": "auto", "deadline_seconds": 3600} +``` + +`clear` 必填,可取 `auto` 或 `force`。`deadline_seconds` 适用于 `auto`,默认值为 3600,接受 300 到 86400;`force` 必须省略该字段。在关闭新的托管准入之前,Core 会持久保存绝对截止时间和请求方的审计来源信息;请求结束后以及跨重启时,执行所有者会继续推进清除。在 `auto` 模式下,Core 会归档空闲的托管 Session,包括排队或待处理工作以及已暂停沙箱,并等待正在执行或等待的 root Turn 和 Subagent Turn 以及待处理的文件写入完成,同时在 Session 锁下重新检查。到达持久保存的截止时间时,Core 会持久地将操作升级为 `force`。`force` 会对每个符合条件的托管 Session 使用常规归档取消和清理路径。绝不触碰自托管 Session。 + +以相同代次和模式再次启动时,会保留原始截止时间。`auto` 可以升级为 `force`,但不能反向升级。使用当前 `expected_generation` 发出 DELETE 会取消剩余工作并重新开放准入;它不会撤销归档、恢复已过期 Environment,也不会取消已经请求的清理。没有活动重置时,DELETE 是无操作;请求过期则返回 409。 + +未激活时 `reset` 为 null,否则为: + +```json +{"clear": "auto", "requested_at": "2026-09-27T12:00:00Z", + "deadline_at": "2026-09-27T13:00:00Z", "forced_at": null, + "remaining": {"busy": 2, "idle": 1, "cleanup": 3, "on_offline_nodes": 2, + "offline_nodes": [{"node_id": "node-uuid", "name": "worker", "resources": 2}]}} +``` + +一个数据库快照会先按 `cleanup`,再按 `busy` 或 `idle`,对每个尚未释放的分配和每个尚未分配沙箱的待处理托管 Environment 进行划分,因此 `busy + idle + cleanup == resources.allocations + resources.pending`。已删除或已过期且具有未释放回执的 Session 计入 cleanup。`offline_nodes` 是通过分配或活动放置持有资源的离线节点的完整列表,并按 ID 排序,其总和等于 `on_offline_nodes`;存在性依据当前所有者 epoch、连接状态以及 45 秒内的心跳判断,而不依据提供商就绪状态。直接 E2B 资源没有节点。离线资源会持续构成阻塞条件,直到清理确认其已释放。 + +当两个占用计数均达到零时,所有者会排空执行,并原子清除 provider、mode、specification、provider configuration、credential、metadata 和 provider policy,停用节点和未使用的注册令牌,递增代次与所有者 epoch,并记录 `reset_complete`。安装身份和历史会保留。Core 会立即发布未配置状态,即使没有 provider 也保留新代次,因此延迟加载无法恢复旧配置。使用 POST 和返回的代次即可重新配置,无需重启。 + +重置期间,新的托管准入会返回 503 `sandbox_reset_in_progress`,并且不会留下临时 Session 行;实时输入、回执重试、恢复和清理会继续进行。管理写入和新注册会返回 409 `sandbox_reset_in_progress`,而已注册节点仍可读取其配置,以便恢复并执行清理。每个 Session 的 [archive](admin-api.md#session-archive)仅需要当前代次,无论是否正在重置均可使用。它会保留历史以及已持久化的 Files 和 Artifacts,丢弃未持久化的工作区,并阻止 Session 恢复;轮询 archive GET 以获取实际释放状态。重置绝不会伪造释放回执。 + +强制归档会立即对凭据和新工作设置栅栏。当 Turn 的托管传递仍处于连接状态时,Core 只会为该传递保留原生的取消和终止回执路径,直到终止提交完成;从最初取消请求起最多持续 20 秒;在取消确认或终止提交仍待处理时,`done` 不会结束此时间限制。该排空过程绝不会授权重新连接、访问 workspace 或 MCP 或继续执行,显式撤销凭据会终止它。回执缺失或失败时会如实保留失败结果;所有者断开连接、过期或重启时,会回退到常规提供商清理。 + +一个变更操作门会串行化设置、PUT、重置、取消和最终处理。archive 会先锁定 Session,再锁定部署,最终处理绝不会颠倒此顺序。候选操作会绑定到重置请求时间,因此取消后以相同代次启动的新重置无法复用旧工作。绝不重放被拒绝或结果不确定的写入操作:读取当前状态并重新决定。 + +## 节点与分配 {#nodes-and-allocations} + +节点容量由管理员批准,独立于部署 specification。`POST /core/v1/sandbox/enrollment-tokens` 接受可选的 `max_active` 和 `max_retained`,默认分别为 2 和 8,并返回 `{token, expires_at, enrollment_id}`;`enrollment_id` 是该命令的公共句柄,绝不是凭据。microsandbox 会使用两个限制。Docker 绝不暂停,因此 Core 会在此处和 PATCH 中用 `max_active` 替换其 `max_retained`。E2B 没有节点,并返回 409 `sandbox_deployment_conflict`。Core 将批准值与令牌一同存储,并复制到其注册的节点;节点不能提交容量,其本地检查可以拒绝其无法运行的部署,但绝不会提高限制。[节点容量](../../../docs/zh/configuration.md#node-capacity)为操作员说明了这些限制。 + +`GET /core/v1/sandbox/nodes` 返回 `{data: [...]}`,其中每个节点包含 `id`、`name`、`provider`、`online`、`last_seen_at`、`created_at`、`max_active`、`max_retained`,计数项 `active`、`reserved`、`running`、`retained`、`snapshots` 和 `cleanup_pending`,以及 `provider_ready`、`diagnostic`、主机测量值 `cpu_count`、`available_memory_bytes` 和 `available_disk_bytes`、`rollout`、`enrollment_id` 和 `core_url`。`enrollment_id` 是注册该节点的命令的句柄;如果 Core 没有该句柄,则为 null。`core_url` 是注册时的安装公开 URL;`core_url` 与当前公开 URL 不同的节点不会收到新放置。已放置到该节点的工作会继续在那里完成,包括已放置但尚未获得分配的 Environment;只要旧地址仍能访问 Core,其保留沙箱仍可恢复。请移除该节点并重新添加。 + +不具备代次管理的节点会自行报告其提供商的就绪状态:`provider_ready`,未就绪时则报告一个固定的 `diagnostic` 代码。通过 Web 的命令添加的节点会管理代次,因此其就绪状态取决于服务代次,而目标代次的固定代码会出现在 `rollout.diagnostic` 中。节点会对首次失败的就绪检查进行分类,并且只发送代码;Core 会将任何其他值存储为 `provider_unavailable`,且绝不存储或返回探测文本或主机路径。代码包括 `docker_unavailable`、`docker_limits_unsupported`、`runtime_download_failed`、`runtime_image_unavailable`、`kvm_unavailable`、`microsandbox_artifacts_unavailable`、`capacity_insufficient` 和 `provider_unavailable`。`runtime_download_failed` 表示无法传输或验证精确的 Runtime 制品;它绝不会包含制品 URL、凭据或传输输出。[就绪代码](../../../docs/zh/getting-started/nodes.md#readiness-codes)给出了原因和操作员应采取的措施。Core 和节点必须来自同一发行包。 + +`PATCH /core/v1/sandbox/nodes/{node_id}` 接受 `{name, max_active, max_retained}`;降低限制不会停止任何正在运行的沙箱。当节点仍持有分配、快照、预留或待处理清理时,包括节点离线期间,`DELETE /core/v1/sandbox/nodes/{node_id}` 会拒绝操作并返回 409 `runtime_node_in_use`。移除操作不会删除任何计算资源,并且会停用该节点的身份;该主机只能作为新节点重新加入。不存在节点排空过程。 + +`GET /core/v1/sandbox/nodes/{node_id}/allocations` 列出节点尚未释放的分配。每个项的 `compute_phase_changed_at` 表示该分配进入当前 `compute_phase` 的时间;未知时为 null。对于已暂停的 microsandbox 分配,该时间加上 `suspension.retention_seconds` 可大致确定 Core 回收它的时间。 + +## 各沙箱提供商中的字段含义 {#what-each-field-means-per-sandbox-provider} + +某些字段在不同提供商中保持同一名称,但含义不同或不适用。部署字段来自 `GET /core/v1/sandbox/deployment`,节点和分配字段来自节点路由,Runtime 字段来自 [Runtime 遥测 API](runtime-observability-api.md);其中 `disk` 仅出现在观测列表中,历史则在 [Session Runtime 历史](runtime-observability-api.md#session-runtime-history)下说明。 + +| 字段 | E2B | Docker | microsandbox | +| --- | --- | --- | --- | +| 部署 `specification.resources` | `cpus` 和 `memory_mib`,必须等于就绪模板构建中的值;省略时取自该构建;无磁盘字段 | `cpus` 和 `memory_mib`;无磁盘配额 | `cpus`、`memory_mib`、`root_disk_mib` 和 `environment_disk_mib` | +| 部署 `specification.runtime` | 不存在;`configuration.template` 用于选择构建 | 完整的[发行版](#runtime-release);节点必须与 `image_id` 或 `image_manifest_digest` 匹配 | 完整的[发行版](#runtime-release);节点必须与 `microsandbox_ref`、`runtime_sha256` 和 `firmware_sha256` 匹配 | +| 部署 `metadata.template_build` | Core 保存选择时读取到的构建 | 不存在:`metadata` 为空 | 不存在:`metadata` 为空 | +| 部署 `suspension` | `null`;Core 不暂停 E2B 沙箱 | `null` | `{idle_seconds, retention_seconds}` | +| 部署 `resources.allocations`、`resources.pending` | Core 中尚未释放的 E2B 沙箱,以及正在等待沙箱的托管 Environment | 所有节点的总数 | 所有节点的总数 | +| 注册令牌 `max_active`、`max_retained` | 先执行 400 容量检查,然后返回 409 `sandbox_deployment_conflict`;E2B 没有节点 | `max_retained` 始终等于 `max_active` | 两个限制均适用 | +| 节点列表和详情 | 空列表;详情返回 404 | 已注册节点 | 已注册节点 | +| 节点 `retained`、`snapshots`、`max_retained` | 不适用 | Docker 绝不暂停:`retained` 等于 `active`,`snapshots` 为 0,`max_retained` 等于 `max_active` | 已暂停沙箱数为 `retained` 减去 `active` | +| 节点 `diagnostic` 代码 | 不适用 | `docker_unavailable`、`docker_limits_unsupported`、`runtime_download_failed`、`runtime_image_unavailable`、`capacity_insufficient` 或 `provider_unavailable` | `kvm_unavailable`、`microsandbox_artifacts_unavailable`、`runtime_download_failed`、`capacity_insufficient` 或 `provider_unavailable` | +| 节点 `host.available_disk_bytes` | 不适用 | 节点状态目录所在文件系统的可用空间,而不是容器的磁盘 | 节点状态目录所在文件系统的可用空间;沙箱磁盘有自己的配额 | +| 分配 `compute_phase`、`compute_phase_changed_at` | 不适用:没有节点分配 | 始终为 `disabled`,在释放前计为运行中;该时间为分配创建时间 | 包含 `suspended`;该时间加上 `suspension.retention_seconds` 可大致确定 Core 回收快照的时间 | +| Runtime 观测 `cpu`、`memory` | 来自 E2B 指标:`cpu.utilization_ratio` 和 `capacity_cores`、内存使用量和限制;无累计 CPU 时间 | 来自 Docker stats:`cpu.usage_seconds_total`、CPU 和内存限制、内存使用量 | 来自 VM:`cpu.usage_seconds_total`、CPU 和内存限制、内存使用量 | +| Runtime 观测 `disk` | E2B `diskUsed` 和 `diskTotal`;模板未报告时为 `null` | `null`:无磁盘配额 | `null` | +| Runtime 观测 `lifecycle_state: sleeping` | 从不 | 从不 | 暂停期间 | +| Runtime 历史 CPU | 每个时间桶中 E2B 所报告使用率比值的平均值 | 根据累计 CPU 时间派生 | 根据累计 CPU 时间派生 | + +## 错误 {#errors} + +| HTTP | 代码 | 场景 | +| --- | --- | --- | +| 400 | `invalid_request_error` 或 `invalid_request` | 请求格式错误 | +| 400 | `invalid_sandbox_configuration` | 配置验证产生的诊断错误 | +| 409 | `sandbox_generation_stale` | `expected_generation` 不是当前值;`current_generation` 提供当前代次 | +| 409 | `sandbox_reset_required` | 后端不同;详情会指明当前提供商和请求的提供商 | +| 409 | `sandbox_not_configured` | 更改未配置的部署 | +| 409 | `sandbox_reset_in_progress` | 重置期间执行管理写入或新注册 | +| 409 | `sandbox_deployment_conflict` | 更改无法应用于另一种状态 | +| 409 | `runtime_node_in_use` | 节点仍持有资源时移除节点 | +| 503 | `execution_unavailable` | 无法准备提供商 | +| 503 | `credential_storage_unavailable` | Core 没有凭据加密密钥 | + +存储和凭据故障始终作为错误处理:读取为空或读取失败绝不能证明清理完成。[机器连接 API](machine-api.md#node-route-errors)列出了节点路由的错误。 + +## 规范的节点规格 {#canonical-node-specification} + +`sandbox/deployment_contract.go`负责资源边界、提供商要求、发行版模式和规范字段顺序;`sandbox/deployment.go`在 Core 中应用这些规则。安装程序会使用 `deploy/install/node_spec.py` 中生成的声明,因此不存在第二套限制或模式。请在仓库根目录运行 `go run ./services/core/cmd/specification-contract -write` 重新生成;作为 `make check` 一部分的沙箱 Go 测试会拒绝过时的投影。 + +规范摘要是紧凑 UTF-8 JSON 的 SHA-256,其中 `provider` 位于首位,其次是 `resources`,然后在提供商需要时放置 `runtime`。资源和 Runtime 字段遵循契约的声明顺序;值为零的可选磁盘字段会被省略,必填字段则保持存在。发行版标识采用小写 ASCII,摘要绝不会受传入字段顺序或空白字符影响。`services/core/internal/sandbox/testdata/deployment-contract.json`保存共享验收用例、精确的规范字节和摘要,Go 与 Python 测试都会使用这些内容。 diff --git a/contracts/agents-api/zh/session-diagnostics.md b/contracts/agents-api/zh/session-diagnostics.md new file mode 100644 index 00000000..6a823c16 --- /dev/null +++ b/contracts/agents-api/zh/session-diagnostics.md @@ -0,0 +1,48 @@ +--- +title: "根 Session 与 Turn 诊断" +source: contracts/agents-api/session-diagnostics.md +source_hash: d84a678de2a21f66e2090afff19aea5cff5d3f11d5c32306ebd4387b015cb416 +--- + +这些只读路由要求 Core 密钥。Project ID 选择目标 Project,不用于认证。两者都返回 `Cache-Control: no-store`: + +- `GET /core/v1/projects/{project_id}/sessions/{session_id}/diagnostics` +- `GET /core/v1/projects/{project_id}/sessions/{session_id}/turns/{turn_id}/diagnostics` + +不存在、格式错误、属于其他范围或已删除的资源遵循 Session 与 Turn 的未找到规则。Subagent Turn 不是根 Turn,返回 404。读取不会联系执行器、配置 Environment、修复历史或改变执行。 + +## Session 快照 {#session-snapshot} + +对象为 `core.session_diagnostics`,包含 `session_id`、官方 Session `status` 和可空的 `failure`。每次 Session 或 Turn 读取使用一个可重复读数据库快照和公开状态投影,时间预算为五秒,调用方更早的截止时间会缩短该预算。读取后关闭事务,取消时也一样。除非投影为 `failed`,否则 `failure` 为 null。其字段为: + +| 字段 | 值 | +| --- | --- | +| `source` | `turn`、`environment` 或 `environment_input` | +| `turn_id` | 仅在 `source: turn` 时存在 | +| `code` | [诊断目录](core-errors.md#diagnostic-failure-categories)中的固定分类 | +| `params` | 包含固定安全值的对象;无参数的分类使用 `{}` | +| `failed_at` | RFC 3339 时间戳;时间未知时为 null | + +托管配置失败优先于输入活动,输入活动优先于最新根 Turn;公开 Session 响应使用相同优先级。不会包含私有结果文本、原生消息、提供方响应体、命令文本、路径或凭据。未知结果代码变为 `internal_error`;不使用前缀匹配或原因文本解析。 + +配置参数来自已确认的结构化回执,在结算 Environment 及其输入并记录事件的失败事务中持久化。`step` 为 `setup`、`python`、`npm`、`system`、`file`、`skill` 或 null。`index` 仅对 setup 为 JSON 安全的非负整数,其他情况为 null。`exit_code` 对脚本步骤为 1 至 255,其他情况为 null。未记录的详情保持 null。这些私有字段不会改变公开失败原因或 SSE。 + +## Turn 快照与 Item 回执时间 {#turn-snapshot-and-item-receipt-timing} + +对象为 `core.turn_diagnostics`,包含 `session_id`、`turn_id`、官方根 Turn `status`、可空的 `failure`、`items` 和 `items_truncated`。Turn 失败包含 `code`、`params` 和可空的 `failed_at`,不包含来源或 Turn ID。只有失败的 Turn 才有失败详情;已取消或已完成的 Turn 不会继承私有结果中的分类。 + +原生分类仅适用于结果为 `engine_failed` 的失败 Turn,来自[原生失败分类](../../../docs/zh/runtime-protocol.md#native-failure-classification)中定义的有限结果元数据。`connection_failed` 的 params 包含 `http_status`,值为 100 至 599 或 null;其他原生分类使用空 params。 + +`items` 最多包含 1000 个根 Item,与公开 Item 列表一样,按 `(created_at, position, id)` 升序排列。存储最多读取 1001 行以检测截断。每项包含 `item_id`、`started_at`、可空的 `completed_at` 和可空整数 `observed_duration_ms`。 + +- `started_at` 是 Core 首次持久化该 Item 输入或事件回执的时间。 +- `completed_at` 是首次终态输入或事件回执的时间。首次观察即为终态的 Item 在同一回执处结算,观察时长为零。 +- 根 Turn 终止时仍在进行的 Item,使用 Session 锁和终态投影之后采样的一次数据库 `clock_timestamp()` 结算,同一事务中的所有此类 Item 共享该值。既不使用事务开始时的 `now()`,也不使用原生 Turn 完成时间。 +- 重复终态投影保留首次结算。已存储但没有结算记录的终态 Item 保持 null;Core 不回填或估算。 +- 当两个回执时间均已知时,`observed_duration_ms` 为整数毫秒差,否则为 null。它不是原生执行时间:日志批处理(事件日志约每 100 ms 刷新)、传输、持久化和数据库时钟行为都会影响它。值不会被钳制。 + +成功 Turn 的公开完成时间可能来自原生执行器,不会因这些读取而改变。 + +## 客户端 {#client} + +`packages/agents-client` 中的 `AdminClient.retrieveSessionDiagnostics(projectId, sessionId, options)` 和 `AdminClient.retrieveTurnDiagnostics(projectId, sessionId, turnId, options)` 支持请求取消,并验证范围、分类、可空性和安全参数值。 diff --git a/contracts/agents-api/zh/sessions-events.md b/contracts/agents-api/zh/sessions-events.md new file mode 100644 index 00000000..51972745 --- /dev/null +++ b/contracts/agents-api/zh/sessions-events.md @@ -0,0 +1,156 @@ +--- +title: "会话、事件和历史" +source: contracts/agents-api/sessions-events.md +source_hash: d5d0928665f38105167e592f9561c3d3852a13fc11b976f15c1bc4cc2ca9cb14 +--- + +本契约涵盖会话(Session)内部发生的事情:发送输入、实时事件流,以及读取轮次(Turn)、条目(Item)和使用量的持久化历史。会话资源本身(创建配置、重试标识、更新、列出和删除)见 [Core 线协议行为](wire-semantics.md)。消息和函数结果内容见[消息内容](message-content.md)。[Agents API 指南](../../../docs/zh/api/public-agent-api.md)展示了使用 SDK 和 HTTP 的调用方式。 + +## 恢复模型 {#recovery-model} + +事件流仅提供实时事件;Turn 和 Item 会持久保存。需要获得每个结果的客户端: + +1. 在发送输入前打开 `GET /v1/agents/sessions/{session_id}/events`。 +2. 断开连接后,再次订阅并缓冲新事件。 +3. 读取会话、其 Turn 和 Item,按 ID 对 Item 去重,并在应用缓冲更新时保留已最终确定的 Item。 + +流只是一个观察器。关闭流绝不会取消已接纳的工作,而且流绝不会重放未由它发送过的事件。输入响应丢失后,使用相同的 `Idempotency-Key` 重试,然后读取会话、Turn 和 Item。 + +## 会话状态 {#session-status} + +会话的 `status` 和 `last_active_at` 由其最新的根 Turn 以及仍在等待 Environment 的输入推导得出: + +| `status` | 适用情况 | +| --- | --- | +| `idle` | 尚无 Turn,或最新 Turn 已完成或已取消。为仍在预配的托管 Environment 预留的输入也显示为 `idle` | +| `in_progress` | 最新 Turn 正在排队、运行或等待 | +| `requires_action` | 最新 Turn 正在等待函数结果且未请求取消,或输入正在等待 `self_hosted` 机器连接。`required_actions` 列出 `function_call` 或 `environment_connection` 条目 | +| `failed` | 最新 Turn 失败(`error` 为 "The execution could not complete.")、为 Environment 预留的输入失败、初始输入在接纳前到期,或 Environment 初始化失败(见 [Environment 初始化失败](#environment-initialization-failure)) | + +Turn 失败后会话仍可使用:新输入会启动一个新 Turn。后来预留的输入到期时,会话保持为 `idle`。Environment 初始化失败或过期会阻止新工作。 + +## 发送输入 {#send-input} + +`POST /v1/agents/sessions/{session_id}/events` 接收包含 1 到 64 个事件的有序数组:`agent.session.input.message`、`agent.session.input.cancel` 和 `agent.session.input.tool_result`。整个批次以原子方式接纳;批次存储后且在任何 harness 读取它之前,响应为无正文的 202。接纳绝不表示已在原生侧应用。 + +- **限制。** 请求正文最多为 1 MiB,单次请求存储的输入最多为 512 KiB。 +- **空值批次。** 将 `events` 显式设为 `null` 无效。 +- **空批次。** `{"events": []}` 会检查会话是否存在并返回 202。它不会创建 Turn、Item 或重试标识。 +- **重试。** 最长 128 字节的 `Idempotency-Key` 标识整个有序批次。相同密钥和批次会再次返回 202,而不会重复接纳任何内容;相同密钥搭配另一个批次会返回 409 `idempotency_conflict`。不带密钥的请求始终是新请求。 +- **消息。** 在空闲会话上,消息批次会启动一个排队的 Turn。Turn 运行时,消息会加入其中(引导);它们绝不会启动并行 Turn。每条消息都保留为独立的用户 Item,即使 harness 将多条消息作为同一个提示接收。 +- **取消。** 排队的 Turn 无需活动 Runtime 即可取消。正在运行的 Turn 只有在 Runtime 确认后才会取消;完成操作可能赢得该竞争。读取到 `cancelled` 时才表示该 Turn 已停止,而不是请求返回时。在没有待处理输入的情况下,对空闲会话执行的取消会被接受且不产生任何效果;而在输入预留待处理期间,取消会返回 409。 +- **函数结果。** `turn_id`、`call_id` 和 `success` 为必填项;`output` 和 `error` 为可选项且可为空([内容规则](message-content.md#function-results))。重复提交完全相同的结果会返回 202,不会再次应用或发出事件。harness 应用结果时才会出现结果 Item;如果取消操作导致结果无法应用,结果仍会存储,但不会产生 Item。 +- **排队。** 当一个已连接且支持该会话 harness 和配置的 Runtime 接入,并且 Core 的 [`core.execution_concurrency`](../../../docs/zh/configuration.md#settings) 工作槽位有一个空闲时,排队的 Turn 才会启动。会话始终绑定到首次运行它的 Runtime。 +- **执行可用性。** 不具备执行能力的服务会返回 503 `execution_unavailable`,失去执行所有权的 Worker 会返回 503。创建时未指定模型提供方的会话会拒绝新消息并返回 400 `model_provider_required`([模型执行](model-execution.md))。 + +### 包含 Environment 的会话 {#sessions-with-an-environment} + +在 `openai_hosted` 和 `self_hosted` 会话中,Turn 运行期间发送的消息会立即加入该 Turn。发送给空闲会话的消息会为 Environment 预留该批次:请求等待 Turn 开始,从预留时起最多等待五分钟。预留等待 `self_hosted` 机器连接时,会话显示为 `requires_action`,并带有 `environment_connection` 操作。在这些部署方式下,携带消息的批次只能包含消息。仅取消和仅结果的批次会立即被接纳,并且不会创建 Turn。 + +当 Turn 启动时,等待中的请求以 202 结束;超过截止时间时,以 409 `environment_input_expired` 结束;管理员归档会话或重置其部署并取消预留时,以 409 `environment_input_cancelled` 结束;Environment 失败或过期时,以 409 `environment_unavailable` 结束。断开等待中的请求不会取消预留,也不会重新开始其截止计时。 + +### 输入错误 {#input-errors} + +检查按以下顺序执行:请求验证、会话查找、重试查找、针对包含消息的批次的 Environment 文件写入门控、待处理输入门控,最后按批次顺序检查每个事件。被拒绝的批次不会写入任何内容,也不会改变任何待处理操作。所有 409 响应的 `type` 都是 `conflict_error`([错误封装](wire-semantics.md))。 + +| 情况 | 状态和代码 | 消息 | +| --- | --- | --- | +| 发往该会话的较早输入仍在等待接纳,例如正在预配的托管会话所预留的初始输入,或离线 `self_hosted` 会话所预留的初始输入 | 409 `conflict_error` | "Earlier input to this Session is still pending." | +| Turn 在当前状态下无法接受的输入,例如取消后提交的结果,或 Turn 已结束且未存储结果后提交的结果 | 409 `conflict_error` | "The Turn cannot accept this input in its current state." | +| 与该调用已存储结果不同的结果,无论其 Turn 结束之前还是之后 | 409 `conflict_error` | "The tool call already has a different result." | +| 相同 `Idempotency-Key` 搭配不同批次 | 409 `idempotency_conflict` | "This idempotency key was used with different input." | +| `call_id` 未标识该会话的任何函数调用的结果 | 400 `invalid_request_error`, param null | "Unknown pending tool call." | +| 为该会话的某个调用提交结果,但 `turn_id` 标识另一个 Turn、未知 Turn ID,或不是 Turn ID 的值 | 400 `invalid_request_error`, param null | "The tool call belongs to a different Turn." | +| 缺失、格式错误或来自其他作用域的会话 | 404 `not_found_error` | "Resource not found." | +| `openai_hosted` Environment 预配失败后提交新输入 | 409 `conflict_error` | "the hosted environment failed to provision" | +| `self_hosted` Environment 失败后提交新输入、Environment 失败时输入已在等待,或 Environment 已过期 | 409 `environment_unavailable` | "The environment is no longer available for new input." | +| 会话 harness 无法处理的消息,例如 Claude Code 上仅含空白的文本 | 400 `unsupported_or_invalid_configuration` | 见[仅含空白的文本](message-content.md#whitespace-only-text) | + +`turn_id` 为空或 `call_id` 为空白时,会返回通用的 400 `invalid_request`。错误消息绝不会重复调用方输入或内部标识符。 + +## 创建会话时的初始输入 {#initial-input-at-session-creation} + +`POST /v1/agents/sessions` 接受字符串形式的 `input`(一条文本消息)或用户消息数组,并执行与 events 端点相同的验证和接纳。 + +- `none` 必须提供初始输入(400 `invalid_request_error`,"conversation-only sessions currently require initial input");使用 `stream: true` 时,除 `self_hosted` 外的所有部署方式也必须提供初始输入(400,"streaming session creation requires initial input")。这些检查在创建重试查找之前执行。 +- 会话及其初始工作在一个事务中提交。对于 `none`,这包括首个 Turn 和输入 Item。对于 `openai_hosted`,输入会在 Environment 预配期间保留。对于 `self_hosted`,输入会保留并带有 `environment_connection` 操作,即使机器仍处于离线状态,创建操作也会返回。 +- 预留的初始输入与后续输入具有相同的五分钟截止时间。如果截止时间在 Turn 启动前过去,会话将显示为 `failed`,且不会创建 Turn。 +- 创建重试会返回原始会话,绝不会再次接纳其输入,即使之后已经产生新的 Turn([创建重试](wire-semantics.md))。 + +## 创建时流式传输 {#creation-streaming} + +创建会话时使用 `stream: true` 会返回 201 和事件流,而不是 JSON。 + +1. 第一个事件是 `agent.session.created`,其中包含提交后读取到的已提交会话,与 JSON 201 正文相同。对于带初始输入的 `none`,其状态已经为 `in_progress`;对于 `self_hosted`,其中已经显示 `environment_connection` 操作。 +2. 随后,流会从创建操作自身的位置开始,将创建过程中每个已提交事件恰好发送一次,因此即使执行速度很快,也不会漏掉最初的事件。 +3. 流会在首次记录到 `agent.session.idle` 后立即结束;该事件会在 Turn 结束时,或输入预留停止等待时(到期、取消或失败)记录。流也会在任何 `agent.session.failed` 之后结束,并且绝不发送后续事件。`requires_action`、函数结果、恢复的工作和 `self_hosted` 连接会使流保持打开。预留会使流保持打开,直到 Turn 进入最终状态或预留结束。 +4. 如果创建未接纳任何内容,流会在 `agent.session.created` 后立即结束。如果某个结束过程未记录任何事件,流会在已提交至该最终状态的事件之后结束;另一个客户端在该时刻之前提交的工作仍可能被发送。 + +在结束中的 Turn 捕获 Artifacts 期间预留的输入,可以启动创建流不会跟踪的后续 Turn。 + +使用相同 `Idempotency-Key` 和 `stream: true` 重试会返回 201,流中仅包含连接注释并立即结束:它不会接纳任何内容,也不会跟踪任何工作。要恢复丢失的会话 ID,请使用相同密钥和 `stream: false` 重复请求,然后读取会话、Turn 和 Item。断开连接只会停止观察器。后续 Turn 可通过 GET 流进行观察。 + +## 实时事件流 {#live-event-stream} + +`GET /v1/agents/sessions/{session_id}/events` 从最新已提交事件处开始,只发送此后提交的事件。`Last-Event-ID` 会被忽略。事件会在各自事务提交后发布。 + +- **生命周期。** 流会跨 Turn 保持打开,并在 Turn 失败后继续存在。流会在会话被删除时,或在 [Environment 初始化失败](#environment-initialization-failure)产生终止 `agent.session.failed` 后结束;在该失败之后打开的流会保持打开。每 15 秒发送一次 keepalive 注释。 +- **缓冲区。** Core 为每个会话最多保留 256 个事件和 64 MiB 事件,必要时还会额外容纳一个更大的事件。读取方落后于缓冲区时,会收到一个 `error` 事件,其 type 为 `server_error`、code 为 `stream_interrupted`,随后流会关闭。套接字写入阻塞五秒也会关闭流。执行过程绝不会等待读取方。 +- **密钥重新检查。** 打开的流在空闲期间以及发送输出前,每秒最多重新检查一次原始 Project 密钥。撤销密钥或归档 Project 会关闭流,身份验证失败也会关闭流。重新检查使用正常的五秒身份验证超时,并且等待期间不会发送任何会话数据。已经发送的字节无法收回。 +- **仅根工作。** 子 Turn 和子 Item 不发布会话事件;`agent.session.subagent.*` 事件和根协调 Item 会发布。请通过 [Subagent 资源](subagents.md)读取子工作。 + +### 事件规则 {#event-rules} + +- 会话事件携带 `event_id`、`type`,以及该次状态转换时的 `session` 快照。Turn 事件携带 `session_id` 和 `turn_id`。不存在 Turn `waiting` 事件。 +- 新 Turn 会在一个事务中依次发布 `agent.session.turn.created`、用户条目的 `item.added`、`agent.session.in_progress`,然后发布 `agent.session.turn.in_progress`。当一个批次包含多条消息时,后续消息对应的条目事件会排在会话活动之后。 +- Turn 终止事件(`completed`、`failed`、`cancelled`)携带顶层的 `usage`,其值复制自当时的 Turn 快照;未知时为 null。其他事件省略此字段。 +- `item.added` 和 `item.done` 始终携带 `output_index`;输入 Item 的该值为 null。函数结果只发出 `item.added`;`item.done` 用于代理输出。 +- 助手消息遵循以下单一顺序:先发送 `content` 为空的进行中 `item.added`,再发送文本为空的 `content_part.added`,然后发送 `output_text.delta` 事件、`output_text.done`、`content_part.done` 和 `item.done`。首次观察时已完整的消息(例如结构化输出)会在一个 `output_text.delta` 中逐字节发送全部文本。完整文本会替换此前累积的增量。 +- Codex 命令输出以 `agent.output.command_execution_output.delta` 流式传输,并携带命令的 Item ID 和输出索引。原生输出配额和文本转换规则适用,因此这些增量并非逐字节捕获的原始结果;完整 Item 才是权威结果。 +- 已取消或失败的 Turn 会将其未完成的 Item 标记为 `incomplete`,并保留其部分内容。 +- 函数调用会一直保留在 `required_actions` 中,直到 harness 应用其结果,或取消操作或 Turn 结束将其移除。重复通知不会发出新状态。 + +## 轮次与条目 {#turns-and-items} + +Turn 和 Item 列表接受 `after`、`limit` 和 `order`([列表规则](wire-semantics.md))。游标是同一会话内的 ID。 + +**Turn。** 会话 Turn 路由只包含根 Turn,按创建时间、再按 ID 排序;在该路由中使用子 Turn ID 会返回 404。失败的 Turn 包含 `error: {code: "internal_error", message: "The execution could not complete."}`,绝不包含引擎原始诊断信息。管理员可通过[会话诊断](session-diagnostics.md)读取失败类别。 + +**Item。** Item 按首次观察时间、再按其在会话中的位置、最后按 ID 排序。更新和重试绝不会移动 Item,也不会更改其 `output_index`;`output_index` 是 Item 在该 Turn 输出 Item 中从零开始的位置,输入 Item 没有此值。读取操作使用已存储的历史索引,绝不会根据原生日志重新构建该索引。Item 列表包含仍在进行中或尚未完成的 Item。 + +| Item `type` | 内容 | +| --- | --- | +| `message` | 用户或助手内容片段。harness 报告阶段时,`phase` 为其阶段(`commentary`、`final_answer`),否则为 null | +| `command_execution` | 命令、报告的输出、退出代码、持续时间和工作目录 | +| `mcp_call` | 服务器和工具标识、参数、结构化结果或错误 | +| `function_call`、`function_call_output` | 关联的调用及其结果。结果始终携带 `output` 和 `error`;提交时省略这些字段则值为 null。存储的结果会保留提交时字段是否存在的信息。原生文件更改显示为 `apply_patch` 函数调用,更改内容作为参数,不会生成虚构结果 | +| `web_search_call` | 支持的操作字段(`search`、`open_page`、`find_in_page`、`other`) | +| `reasoning`、`agent_message` 和 Subagent 协调调用 | 见 [Subagents](subagents.md)。`agent_message` 没有状态;reasoning 状态可以缺失或为 null | + +工具失败不会导致其 Turn 失败。会话的 Project 可以读取工具输出,其中可能包含该工具自己的诊断文本。 + +## 使用量 {#usage} + +Turn 和会话的 `usage` 使用固定的 `TokenUsage` 字段:输入、缓存输入、输出、推理输出和令牌总数。`null` 表示未知,绝不表示零。 + +- **Turn 使用量**是 harness 为该 Turn 报告的最新完整快照。新快照会替换旧快照;重复快照绝不会累加。存储的快照在取消和 Worker 重启后仍会保留。 +- **会话使用量**是所有根 Turn 均已结束(完成、失败或取消)且使用量已知时,各根 Turn 使用量的总和。只要有根 Turn 正在排队、运行或等待,它就为 null;一旦某个根 Turn 在没有使用量的情况下结束,它就会持续保持 null。Subagent Turn 不计入其中。 +- **按 harness 划分。** Codex 会在 Turn 运行期间及其结束时报告测量快照;在任何使用量报告之前被中断的 Turn 会保持 null。Claude Code 和 MiniMax Code 不报告完整的公开明细,因此其使用量为 null。 + +使用量是对已报告测量值进行的尽力核算。它不是账单,Core 绝不会估算缺失的使用量。 + +## Environment 初始化失败 {#environment-initialization-failure} + +当 Environment 初始化失败时,无论是 `openai_hosted` 沙箱还是 `self_hosted` 机器,都会在一个事务中记录该失败和以下三个事件,顺序如下: + +| 事件 | 载荷 | +| --- | --- | +| `agent.session.environment.failed` | `error: {type: "environment_error", code: "environment_connection_failed", message: "The environment failed to connect."}` | +| `error` | `error: {type: "environment_error", code: "sandbox_error", message: , param: null}` | +| `agent.session.failed` | 该会话:`status: "failed"`,失败原因为 `error`,`required_actions: []`,失败时间为 `last_active_at` | + +会话读取和列表操作会返回同一个会话,正在等待 Environment 的输入也会在同一快照中结算为失败。GET 流和创建流会在 `agent.session.failed` 之后结束。新输入会返回 409([输入错误](#input-errors));该会话可以被删除。 + +[Environment 初始化契约](environments.md#initialization-state-and-failure)定义了固定的失败原因。 + +Core 自身的 `stream_interrupted` 错误事件携带 `type`、`code` 和 `message`,但不携带 `param`;与官方错误事件结构相同的 `error` 事件会携带 `param: null`。 diff --git a/contracts/agents-api/zh/source-files.md b/contracts/agents-api/zh/source-files.md new file mode 100644 index 00000000..659b4045 --- /dev/null +++ b/contracts/agents-api/zh/source-files.md @@ -0,0 +1,114 @@ +--- +title: "文件与 Skill" +source: contracts/agents-api/source-files.md +source_hash: 320216a4e7ac0d1e3e2455dfe622e0f91576ea1d0aa00d11911683b0e98387f1 +--- + +Files(`/v1/files`)和 Skills(`/v1/skills`)是具有独立生命周期的 Project 资源,不依赖 Session。File 保存上传字节,Environment 通过 ID 复制它们。Skill 保存不可变、版本化的包,由 Template 和 Session 引用。Project 的所有 API 密钥共享这些资源。 + +这些路由遵循 [upstream.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream.json) 中固定版本 SDK:[Files 资源](https://github.com/openai/openai-python/blob/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/resources/files.py)、[创建参数](https://github.com/openai/openai-python/blob/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/types/file_create_params.py)、[FileObject](https://github.com/openai/openai-python/blob/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/types/file_object.py) 和 [Skills 资源](https://github.com/openai/openai-python/tree/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/resources/skills)。要求 Project API 密钥,不要求 `OpenAI-Beta` 头。不存在 ID 与其他 Project 的 ID 返回相同的 404。 + +## 文件 {#files} + +| 操作 | 行为 | +| --- | --- | +| `POST /files` | Multipart 上传,包含一个 `file` 部分和 `purpose=user_data`,顺序不限。返回 200 和 File | +| `GET /files` | 列出 Project 的 File,不读取字节 | +| `GET /files/{file_id}` | 返回 File | +| `GET /files/{file_id}/content` | 400 `Not allowed to download files of purpose: user_data`,code 与 param 为 null。先检查 ID,因此 File 不存在时返回 404 | +| `DELETE /files/{file_id}` | 删除 File 及其字节;返回 `{"id": …, "object": "file", "deleted": true}` | + +将 File ID 传给 [Environment 文件](environment-files.md#create-a-file),或 Template、Session 的初始 `files`([Environment](environments.md))来使用文件。复制在内部读取字节;公开下载仍被拒绝。 + +### 上传 {#upload} + +- 仅接受 `purpose=user_data`。不支持其他用途、`expires_after` 或 Uploads API。 +- 文件可为空,最大 512 MiB;整个 multipart 正文可额外增加 64 KiB。更大上传返回 413 `request_too_large`。传输须在五分钟内完成。 +- 部分缺失、重复或未知,存在 `Content-Encoding` 或 `Content-Transfer-Encoding` 头,或文件名为空、超过 1,024 字节、非 UTF-8、包含 NUL 时返回 400。完整请求通过验证之前 Core 不存储任何内容。 +- Core 不对上传去重。响应丢失后,先列出 File 再次上传。 + +### File 对象 {#file-object} + +| 字段 | 值 | +| --- | --- | +| `id`, `object` | File ID;`file` | +| `bytes` | 字节大小 | +| `created_at` | Unix 秒 | +| `filename` | 上传名称。仅作为元数据,不成为文件系统路径 | +| `purpose` | `user_data` | +| `status` | `processed`,表示字节已存储。Core 不解析、索引或扫描 | +| `expires_at`, `status_details` | `null` | + +### 列出文件 {#list-files} + +| 参数 | 规则 | +| --- | --- | +| `order` | `desc`(默认)或 `asc`,按创建时间再按 ID 排序 | +| `after` | 此 Project 可见的 File ID | +| `purpose` | `user_data`、`assistants`、`batch`、`fine-tune`、`vision`、`evals`、`assistants_output`、`batch_output`、`fine-tune-results` 之一。其他值(包括不同大小写)在解析游标前返回 400,`param: "purpose"`。非 `user_data` 值返回空页。空值表示不筛选 | + +响应为 `{"object": "list", "data": [...], "first_id", "last_id", "has_more"}`;空页的 ID 为 null。`limit`、查询解析及其错误遵循共享[列表规则](wire-semantics.md#lists)。 + +### 错误 {#errors} + +读取、内容和删除时,不存在或属于其他范围的 File 返回 404,type 为 `invalid_request_error`,code 为 null,`param: "id"`。 + +### 存储与删除 {#storage-and-deletion} + +Core 在自身数据库中以 PostgreSQL 大对象存储 File 字节。上传和删除分别在单一事务中提交,失败不会留下部分字节或元数据。[备份](../../../docs/zh/getting-started/operations.md#back-up)数据库时包含大对象;删除 File 不会将其从预写日志或旧备份移除。 + +存在 File 行时,源文件 schema 拒绝降级。先通过 API 删除 File,以清理大对象。 + +复制到工作区时读取 File 一致快照,可在 File 删除后完成;后续查询失败。删除 File 不改变工作区副本。 + +## Skill {#skills} + +| 操作 | 行为 | +| --- | --- | +| `POST /skills` | 上传新 Skill。首版同时为默认和最新版本 | +| `POST /skills/{skill_id}/versions` | 上传新版本。表单 `default` 为 `true` 时设为默认;`false` 或省略时默认不变 | +| `GET /skills`, `GET /skills/{skill_id}` | Skill 元数据,不解密任何包 | +| `POST /skills/{skill_id}` | `{"default_version": ""}` 修改默认版本 | +| `DELETE /skills/{skill_id}` | 删除 Skill 与所有版本 | +| `GET /skills/{skill_id}/content` | 默认版本 ZIP | +| `GET /skills/{skill_id}/versions`, `GET /skills/{skill_id}/versions/{version}` | 版本元数据。列表按版本号排序,`after` 是版本 ID(`skillver_…`),不是数字 | +| `GET /skills/{skill_id}/versions/{version}/content` | 指定版本 ZIP | +| `DELETE /skills/{skill_id}/versions/{version}` | 见[删除版本](#delete-a-version) | + +`limit`、游标和查询错误遵循共享[列表规则](wire-semantics.md#lists)。 + +### 上传包 {#upload-a-bundle} + +将一个 ZIP 作为 `files` 部分发送,或将目录作为重复的 `files[]` 部分发送,文件名使用 `report/SKILL.md` 等相对路径。SDK 3.13.0 在 `files` 为单文件而非列表时不发送部分,因此上传单个 ZIP 应使用普通 HTTP: + +```sh +curl "$OPENAI_BASE_URL/skills" -H "Authorization: Bearer $OPENAI_API_KEY" -F files=@report.zip +``` + +包包含一个顶层文件夹,其中包含 `SKILL.md` 和支持文件: + +- `SKILL.md` 使用 UTF-8,最大 256 KiB,以 YAML frontmatter 开始。`name` 必填:小写字母与数字,可用单个 `-` 或 `_` 分隔,最多 64 字符。`description` 必填且非空。`license`、`compatibility` 和字符串值 `metadata` 映射可选;其他键被拒绝。 +- 条目为普通文件或目录,使用规范相对路径。链接、特殊文件、绝对路径、`..` 组件和重复项被拒绝。 +- 限制:压缩后 5 MiB,展开后 20 MiB,500 个文件,以及含目录在内的 1,000 个 ZIP 条目。 + +Core 将每个版本的包加密并绑定到 Project、Skill 与版本。ZIP 上传保留可执行位;目录上传以 mode 0644 存储文件。 + +### 版本与元数据 {#versions-and-metadata} + +- 版本号从 1 开始,每次上传加一,即使最新版本删除也不复用。Template 和 Session 选择器按数字指定版本,复用数字可能使保存的选择器指向不同字节。 +- Skill 的 `name` 和 `description` 来自默认版本。通过 `POST /skills/{skill_id}` 或 `default=true` 上传修改默认版本时,指针和两个字段一起更新;`id` 和 `created_at` 不变。 +- `latest_version` 是剩余版本中的最大版本号。 +- 同一 Skill 的上传与删除串行执行,删除不会移除已确认上传的版本。 + +Template 和 Session 如何选择版本(默认、`latest` 或数字)并冻结字节,见 [Environment](environments.md#skills-plugins-and-environment-mcp)。 + +### 删除版本 {#delete-a-version} + +| 版本 | 结果 | +| --- | --- | +| 默认且唯一版本 | 200 `{"id": "skillver_…", "object": "skill.version.deleted", "deleted": true, "version": "1"}`。Skill 在同一事务中删除 | +| 默认版本且其他版本存在 | 400,type 为 `invalid_request_error`,code 为 `invalid_value`,`param: "version"`,`Cannot delete the default skill version.` | +| 其他版本 | 200,响应体相同。若为最新版本,`latest_version` 回退到剩余最大版本号 | +| 不存在或属于其他范围的 Skill 或版本 | 404 | + +删除 Skill 或版本不改变已安装它的 Session;Template 保留所存储引用。 diff --git a/contracts/agents-api/zh/subagents.md b/contracts/agents-api/zh/subagents.md new file mode 100644 index 00000000..5c6128be --- /dev/null +++ b/contracts/agents-api/zh/subagents.md @@ -0,0 +1,63 @@ +--- +title: "子智能体" +source: contracts/agents-api/subagents.md +source_hash: 8fed8b73a8403d2ccf80354b2a981f11240eba3c10d5a0257206a39d048b92a7 +--- + +启用 `multi_agent.enabled` 后,Harness 可以启动原生子智能体。Core 通过锁定版本的 SDK(见 [`upstream.json`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream.json))中的六项 Subagent 读取操作公开它们,并根据适配器观察结果记录它们。当 `multi_agent.enabled=false` 时,Runtime 会移除原生子智能体工具。[Harness capabilities](harness-capabilities.md) 列出各 Harness 在哪些组合中支持子智能体。 + +## 公开读取 {#public-reads} + +所有路径都位于 `/v1/agents/sessions/{session_id}` 下,并且需要与普通 Session 读取相同的 Project 身份验证和 `OpenAI-Beta: agents=v1`。 + +| 路径 | 结果 | +| --- | --- | +| `/subagents` | 直接子智能体、嵌套子智能体和已关闭的子智能体 | +| `/subagents/{subagent_id}` | 单个子智能体 | +| `/subagents/{subagent_id}/items` | 仅该子智能体自身的 Items | +| `/subagents/{subagent_id}/turns` | 仅该子智能体自身的 Turns | +| `/subagents/{subagent_id}/turns/{turn_id}` | 一个所属 Turn | +| `/subagents/{subagent_id}/turns/{turn_id}/items` | 该 Turn 中仅属于该子智能体的 Items | + +列表使用 `after`、`limit`(默认 20)和 `order`(默认 `desc`),并返回 `object: "list"`、`data`、`first_id`、`last_id`(空页面时为 null)和 `has_more`。对于 1–100 范围之外的 `limit`,Subagent 和 Subagent Turn 列表会使用 `limit must be between 1 and 100` 拒绝请求;与 Session Items 一样,两个 Item 列表会将 0 视为 1,并将更大的值视为 100。游标必须属于所请求的 Project、Session、子智能体以及可选的 Turn。 + +## 子智能体可见性 {#subagent-visibility} + +子级工作仅出现在子智能体路由上。 + +- Session 的 `turns.list` 和 `turns.retrieve` 仅返回根级 Turns。将子级 Turn ID 作为路径参数或列表游标使用时,会返回与不存在的 Turn 相同的 404;Core 的 404 消息与官方消息不同。 +- Session 事件流和创建流仅传输根级工作:子级 Turns 及其 Items 不会发布任何 `agent.session.turn.*` 或 Item 内容事件。`agent.session.subagent.*` 事件和根级协调 Items 仍会保留。创建流仍会在根级收敛到 idle 状态时结束。 +- 子级 Turn 的 `agent_id` 是 Session 的 Agent ID(即直接子级的 `parent_agent_id`,也是 `create_subagent_call` 的 `agent_id`);`subagent_id` 标识该子级。嵌套子级遵循相同规则。根级 Turns 带有 `subagent_id: null`。 +- Session Items 始终归根级所有;继承的原生父级对话记录不属于子级工作。Session 用量仅汇总根级 Turns。 + +Active 状态包含 idle 状态。成功关闭会记录原生时间;成功重新打开会保留身份和 `opened_at`、清除 `closed_at`,并且只发出一次 `active`。恢复处于 active 状态的子智能体不会执行任何操作。Turn 完成、中断和进程释放绝不会关闭子智能体。未知的 token 度量值保持为 null。 + +## 适配器契约 {#adapter-contract} + +适配器通过 `internal/agentdaemon/proto/subagents.go`,利用现有的已认证 Run 和执行日志报告子智能体事实。不存在单独的传输机制、调度器或模型与工具循环。 + +| 事实 | 适配器义务 | +| --- | --- | +| 身份 | 证明原生 ID、初始父级和创建时间;先发布父级 | +| 生命周期影响 | 证明成功的关闭或重新打开及其实际发生时间;在读取历史记录时保持该影响的标识稳定 | +| 子级 Turn | 提供由原生端拥有的 ID、状态和来源时间戳,并且仅在已知时提供 Usage;如果缺少原生取消时间戳,则必须提供持久化的已确认操作回执 | +| 子级 Item | 使用中立词汇提供有序且完整的消息或工具快照 | +| 协调 | 转换原生操作以及操作者和接收者身份,但不得在 Core 中放入原生工具名称 | + +Core 根据已授权的 Session 绑定关系分配合公开 ID 和所有权。身份、生命周期、子级历史和实时事件投影在 Session 锁和执行租约下原子提交。重复观察会保留其 ID,且不会重复生成生命周期事件;检测到冲突效果时操作失败。对不存在子级的失败协调请求会保留其不透明的请求目标,并且不会创建子智能体。 + +子级 Turns 由原生写入方负责,因此它们与 Core 的工作队列分开存储,绝不会成为另一项排队执行。Session Turn 读取直接查询根级 Turns。公开 GET 读取持久化资源;它们绝不会启动原生进程或重放执行。 + +适配器会在子级工作收敛前冻结根级输出,在有限子级工作完成期间保持原生所有者和读取器存活,并在子级终态 Turn 快照和 Run 完成之前交付子级 Items。取消操作使用相同所有者,并在释放前确保子级写入已经完成。失败的观察或不确定的原生操作结果绝不会转化为成功的空历史;父级输出、任务完成、观察时间或空列表均不能替代缺失的事实。 + +## 原生配置 {#native-profiles} + +[Harness capabilities](harness-capabilities.md#tools) 列出被拒绝的工具组合。 + +**Codex.** 适配器会启用原生 `multi_agent` 特性,将嵌套深度设为 64,并把并发限制映射到 `agents.max_threads`。它会禁用原生钩子、插件、代码模式和 `multi_agent_v2`;如果原生钩子列表非空,或托管配置要求强制启用冲突特性,则拒绝启动。关闭和重新打开的事实来自直接工具输出,并与同一次调用的持久化完成记录相关联,因此需要原生持久化回执。原生 Turn 时间具有秒级精度。根级 Turn 完成后,子级文件工作可以在同一所有者下完成。超过调用方截止时间后,取消操作仍会在同一所有者下继续;后续调用可以确认已经收敛,而无需重复执行原生中断。 + +**Claude SDK.** 锁定版本 SDK 的原生 Agent 和 SendMessage 调用会运行唯一的子级类型 `oac_worker`;该类型继承模型,并可使用工作区中的 Bash、Agent 和 SendMessage;bridge 的 `subagent_resources` 特性控制是否启用它。子级使用原生 Bash,权限继承自父级的启动用户。私有子级记录用于确定父子关系、首次自身输入时间及后续自身 Turns;继承的父级上下文会被排除。查询所有者会在启动前接纳子级,并将子级历史保留至工作收敛。已确认的取消操作会写入不可变的操作回执,因为原生中止可能不会留下终态记录。不存在关闭操作:已完成或已取消的子级保持 active 状态。向运行中的子级发送消息、使用后台工作、采用其他子级配置以及按次覆盖模型都会被拒绝。[Claude SDK adapter](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/packages/claude-sdk-adapter/README.md#subagents) 记录了详细信息。 + +**MiniMax Code.** 原生 ACP 委派(`task`、`task_append`、`task_stop`)会创建子级。Session 私有 SQLite 记录提供子级身份、已接受的输入、终态时间和自身消息。原生 task-create 事务会在启动前强制执行并发限制,并且原生准备过程必须在任何模型输入前确认该限制和受限工具配置。不存在关闭或重新打开操作,原生工作器也不会委派嵌套工作。 + +Claude 和 MiniMax 在子级工作收敛时发布经验证的子级历史,而不是持续发布子级进度。向子级传播根级完成状态、完整的子级增量排序以及跨根级 Turns 的无界后台工作均尚未得到支持。 diff --git a/contracts/agents-api/zh/vaults.md b/contracts/agents-api/zh/vaults.md new file mode 100644 index 00000000..0f5dd927 --- /dev/null +++ b/contracts/agents-api/zh/vaults.md @@ -0,0 +1,150 @@ +--- +title: "Vault 与 Credential" +source: contracts/agents-api/vaults.md +source_hash: 9e56ee84c782d1f9c0f5506f960a275d9afa3e554634131bf09a74e736135926 +--- + +Vault 是 Project 所有的 Credential 容器。Credential 保存一个 HTTPS MCP server 的秘密:`static_bearer` token 或 `mcp_oauth` grant。Session 在 `vault_ids` 中关联 Vault;Core 在创建 Session 时为每个 HTTP MCP server 选择一个 Credential,只在分派工作时将解密 token 交给 Runtime。秘密只能写入:任何读取都不返回 token、refresh token、client secret 或密文。 + +应用负责 OAuth 授权与同意、服务方撤销以及审批策略。Core 不提供授权重定向、回调、撤销或公开刷新端点;创建或替换 Credential 不联系 MCP server 或 OAuth provider。 + +## 存储并使用凭证 {#store-and-use-a-credential} + +```python +vault = client.beta.agents.vaults.create(name="internal") +credential = client.beta.agents.vaults.credentials.create( + vault.id, + name="Internal MCP", + auth={ + "type": "static_bearer", + "mcp_server_url": "https://mcp.example.com/endpoint", + "token": token_from_private_configuration, + }, +) +session = client.beta.agents.sessions.create( + environment={"type": "none"}, + vault_ids=[vault.id], + input="List the open incidents.", + agent={ + "model": "your-model-id", + "tools": [{ + "type": "mcp", + "server_label": "internal", + "transport": {"type": "http", "server_url": "https://mcp.example.com/endpoint"}, + }], + }, +) +``` + +MCP tool 可以通过 `credential_id` 指定 Credential;未指定时,Core 选择已关联 Credential 中 `mcp_server_url` 等于 tool 的 `server_url` 的凭证([选择规则](#credential-selection-in-a-session))。能够连接该 server 的 Harness 和 placement 取决于 tool 的 `connection_origin`([MCP 连接来源](environments.md#public-mcp-connection-origin))。 + +## 路由 {#routes} + +所有路由位于 `/v1`,使用 Project API key,要求 `OpenAI-Beta: agents=v1`。Project 的 key 共享其 Vault;其他 Project 的 Vault 或 Credential 与不存在的资源一样返回 404。 + +| 操作 | 路由 | 结果 | +| --- | --- | --- | +| 创建 Vault | `POST /vaults` | 201 Vault | +| 查询 Vault | `GET /vaults/{vault_id}` | Vault | +| 列出 Vault | `GET /vaults` | Vault 列表 | +| 删除 Vault | `DELETE /vaults/{vault_id}` | `{id, object: "vault.deleted", deleted: true}` | +| 创建 Credential | `POST /vaults/{vault_id}/credentials` | 201 Credential | +| 查询 Credential | `GET /vaults/{vault_id}/credentials/{credential_id}` | Credential | +| 列出 Credential | `GET /vaults/{vault_id}/credentials` | Credential 列表 | +| 替换秘密 | `POST /vaults/{vault_id}/credentials/{credential_id}` | Credential | +| 删除 Credential | `DELETE /vaults/{vault_id}/credentials/{credential_id}` | `{id, object: "vault.credential.deleted", deleted: true}` | + +格式错误、缺失或其他 Project 的 ID 返回 404 `not_found_error`;通过非所属 Vault 访问 Credential 也一样。两个列表按创建时间、再按 ID 排序,按 `status`(`active`、`archived` 或二者)筛选;[列表规则](wire-semantics.md#lists)说明分页和筛选细节。Core 私有保存 status,默认 `active`,没有归档 Vault 或 Credential 的操作。Credential 的 status 独立于 Vault。 + +## Vault {#vaults} + +| 字段 | 规则 | +| --- | --- | +| `name` | 可选;省略保持 `null`,显式 `null` 被拒绝。字符串去除首尾空白后必须为 1–256 UTF-8 字节 | +| `metadata` | 省略或 `null` 转为 `{}`。值必须为字符串;其他类型返回 400 `invalid_request_error`,param 为 `metadata.`。编码后对象上限 64 KiB,无键值对数量或长度限制 | + +Vault 读取包含 `id`、`object: "vault"`、`created_at`、`name` 和 `metadata`。没有更新路由。 + +删除 Vault 在一个事务内移除 Vault 及所有 Credential,无论它们的 status。无需 storage key,不向服务方发送请求。对 Session 的影响见[删除](#deletion)。 + +## Credential {#credentials} + +创建需要 `name`(去除首尾空白后 1–256 UTF-8 字节)和 `auth` 对象,`type` 为 `static_bearer` 或 `mcp_oauth`。`mcp_server_url` 必须是无 userinfo、无 fragment 的绝对 HTTPS URL;Core 逐字节保留,包括 query,不执行 DNS 或 HTTP 请求。 + +Credential 读取包含 `id`、`vault_id`、`name`、`object: "vault.credential"`、`created_at`、`updated_at` 和 `auth`。`auth` 包含 `type`、`mcp_server_url`;OAuth Credential 还包含 `expires_at` 和 `refresh`,后者包含 `client_id`、`token_endpoint`、`token_endpoint_auth.type`、`resource` 和 `scope`。读取和列表无需 storage key。 + +### 静态 bearer {#static-bearer} + +`auth` 为 `{type: "static_bearer", mcp_server_url, token}`。`token` 必需且非空。Core 将它作为不透明字节保存,不去除空白。执行时 token 必须符合 RFC 6750 `b64token`;Session 选择了含空白等其他字符的存储 token 时,分派失败。 + +通过 `POST /vaults/{vault_id}/credentials/{credential_id}` 替换 token,请求必须正好为 `{"auth": {"type": "static_bearer", "token": "…"}}`。token 缺失、null、空值或任何其他字段均被拒绝。仅 token 和 `updated_at` 改变;ID、Vault、name、type、`mcp_server_url`、`created_at` 和所有 Session 绑定保留。替换失败保留旧 token。 + +### OAuth {#oauth} + +`auth` 为 `{type: "mcp_oauth", mcp_server_url, access_token, expires_at, refresh}`: + +| 字段 | 规则 | +| --- | --- | +| `access_token` | 必需,非空 | +| `expires_at` | 可选、可为 null 的 RFC 3339 时间戳。创建时允许已过期值 | +| `refresh` | 可选、可为 null;`client_id`、`refresh_token`、HTTPS `token_endpoint` 和 `token_endpoint_auth` 必需;`resource` 和 `scope` 为可选、可为 null 的字符串 | +| `refresh.token_endpoint_auth` | `{type: "none"}`,不含 `client_secret` 成员;或 `client_secret_basic` / `client_secret_post`,含只写 `client_secret` | + +使用同一更新路由、`auth.type: "mcp_oauth"` 替换 grant 材料。patch 必须至少修改 `access_token`、`expires_at`、`refresh.refresh_token`、`refresh.token_endpoint_auth.client_secret` 或 `refresh.scope` 之一;空 `access_token` 被拒绝。ID、name、type、`mcp_server_url`、`client_id`、`token_endpoint`、`resource` 和端点认证方法不变;创建时没有 `refresh` 的 Credential 不能添加该块。提交 `token_endpoint_auth` 时必须指定已存储的方法,且为 `client_secret_basic` 或 `client_secret_post`。 + +| 更新字段 | 省略 | `null` | +| --- | --- | --- | +| `access_token` | 保留 | 保留 | +| `expires_at` | 保留;发送新 `access_token` 时清空 | 清空 | +| `refresh` | 保留 | 保留 | +| `refresh.refresh_token` | 保留 | 保留 | +| `refresh.scope` | 保留 | 停止发送 scope | +| `refresh.token_endpoint_auth` | 保留 | 保留 | +| `refresh.token_endpoint_auth.client_secret` | 保留 | 保留 | + +更新时改变 Credential 的 `auth.type` 返回 400。 + +## Session 中的凭证选择 {#credential-selection-in-a-session} + +创建 Session 时,`vault_ids` 列出 Session 可以使用凭证的 Vault。省略、`null` 和 `[]` 均不关联任何 Vault;条目为 `null` 无效;每个 Vault 必须属于 Project,否则创建返回 404 `not_found_error`。Agent 上保存 `credential_id` 不提供授权;只有 Session 的关联提供授权。 + +创建 Session 时,Core 为每个 HTTP MCP tool 从已关联 Vault 中选择 Credential: + +- 指定 `credential_id` 时,该 Credential 必须在已关联 Vault 内,且 `mcp_server_url` 等于 tool 的 `server_url`。 +- 未指定时(省略或 `null`),选择唯一一个 `mcp_server_url` 等于 `server_url` 的静态或 OAuth Credential。没有匹配时匿名连接;多个匹配时创建失败,不偏好任何类型。 + +选择发生在内联 Agent 校验和 input 要求之后、任何写入之前。创建被拒绝时不写入任何内容。失败的 `param` 为 null: + +| 情况 | 响应 | +| --- | --- | +| `credential_id` 没有关联 Vault | 400 `invalid_request_error`: `MCP credential_id requires an attached vault` | +| `credential_id` 缺失、格式错误、属于其他 Project 或未关联 Vault | 400 `invalid_request_error`: `MCP credential_id was not found in an attached vault` | +| Credential 在已关联 Vault 内,但 URL 不同 | 400 `invalid_request_error`: `MCP credential_id does not match server_url ` | +| 未指定 `credential_id` 时匹配多个 Credential | 409 `conflict_error`: `multiple attached vault credentials match MCP server_url ; specify credential_id` | +| `vault_ids` 中的 Vault 未知或属于其他 Project | 404 `not_found_error` | + +`` 和 `` 仅在请求值不超过 256 字节且为可打印 UTF-8 时原样返回,否则消息省略它们。缺失、其他范围、未关联和格式错误的情况,对同一 ID 给出逐字节相同响应,因此引用不泄露调用方未关联 Vault 的信息。URL 不匹配消息中的 URL 来自 tool。 + +Session 固定其关联和每次选择,包括匿名选择。后续 Vault 变化不会重新选择:相同的创建重试返回原 Session 和选择。Session 查询、列表和事件快照在 tool 的 `credential_id` 中显示隐式选择的 Credential ID,即使该 Credential 已删除;匿名 tool 显示 `null`,显式 ID 按提交值读取。存储请求保留调用方值,因此重试比较原请求。 + +每次分派时,Core 重新检查 Project、已关联 Vault、选中 Credential、type 和确切 URL,然后只在 Runtime 执行请求中解密 token。Session、配置和历史的读取从不包含 token。选中 Credential 的 server 只能运行在声明 `mcp_http_bearer_auth` 的 Runtime 上。Credential 缺失、解密失败或刷新失败会使工作失败;Core 不退回其他 Credential 或匿名连接。 + +## OAuth 刷新 {#oauth-refresh} + +分派时,OAuth Credential 的 `expires_at` 已过期,则在 Core 将工作发送给 Runtime 前刷新。`expires_at` 为 null 的 grant 原样使用,不主动刷新。已过期且没有 `refresh` 的 grant 使工作失败。 + +刷新只执行一次 `refresh_token` 交换,使用存储的端点认证方法、`scope` 和 `resource`;不跟随重定向、不探测方法、失败后不重试。服务方必须返回 `Bearer` token,其到期时间(若有)必须在未来。Core 先提交新 access token、新到期时间(或无到期时间)和轮换后的 refresh token,再使用 token;省略 refresh token 时保留旧值。PostgreSQL 行锁使刷新与手动替换、删除串行,避免过期刷新恢复已删除或替换的 grant。交换或提交失败时不返回任何内容,也不重试。服务方轮换 grant 后本地提交失败时,应用必须重新授权。服务方错误响应体不返回也不写日志;Harness 只收到 access token,不收到 refresh token 或 client secret。 + +Token endpoint 必须为 HTTPS。Core 解析主机,拒绝回环、私有、链路本地和共享(`100.64.0.0/10`)地址,连接已检查地址以防第二次 DNS 响应改变目标;TLS 仍验证主机名。它忽略环境中的 HTTP proxy。对于私有 issuer,管理员在 [`core.oauth_trusted_origins`](../../../docs/zh/configuration.md#settings) 中列出确切 HTTPS origin;这只允许该主机与端口使用私有地址,不允许 HTTP、重定向或无效证书。issuer 的 CA 必须在 Core 信任库中。 + +## 存储密钥 {#storage-key} + +Core 使用安装的 [`secrets/credential.key`](../../../docs/zh/configuration.md#installation-directory),以 AES-256-GCM 加密每个 token、refresh token 和 client secret,绑定 Project、Vault、Credential、auth type 和 `mcp_server_url`。错误密钥、修改的行或移动到其他绑定的行均无法解密。名称是不参与绑定的元数据。key 和明文 token 存在于可信服务内存中;加密保护存储的秘密,不保护已被攻破的服务主机。 + +未配置 key 时,Credential 创建和替换在写入前返回 503 `credential_storage_unavailable`;读取、列表、删除和 Vault 操作仍可用。key 文件不可读或格式错误会使 Core 启动失败。丢失或替换 key 使全部已存储秘密无法使用;Core 只支持一个 key,不支持轮换或重新加密。 + +## 删除 {#deletion} + +删除 Credential 或其 Vault 会移除凭证行,但不会擦除 PostgreSQL 页面、WAL、备份或原生历史中的秘密。此后查询、更新和重复删除返回 404;列表不再包含它;新 Session 不能选择它;不能在已删除 Vault 中创建新 Credential(缺失 storage key 时仍先返回 503)。 + +现有 Session 保留固定的关联和选择,历史仍可读。下一次需要已删除 Credential 的分派失败;Core 不选择其他 Credential,也不匿名连接。删除不取消运行中的工作、不收回已发送 Runtime 的 token,也不在服务方撤销 grant;需要时自行取消 Session 并撤销 grant。已撤销或无效 OAuth grant 同样失败,直到被替换。 diff --git a/contracts/agents-api/zh/wire-semantics.md b/contracts/agents-api/zh/wire-semantics.md new file mode 100644 index 00000000..e3d0c022 --- /dev/null +++ b/contracts/agents-api/zh/wire-semantics.md @@ -0,0 +1,251 @@ +--- +title: "Core 协议行为" +source: contracts/agents-api/wire-semantics.md +source_hash: 5524db9b90aaf51026746316d6305da396033f50e88ed50792a8f52cf5aac9db +--- + +已锁定版本的 OpenAI Python SDK([upstream.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream.json))定义了 `/v1` 路由、字段和类型。本页面说明这些类型未作规定之处 Core 的行为,例如状态码、错误字段、默认值和列表边界,以及 Core 与官方服务存在差异的地方。[coverage ledger](index.md) 列出了这些差异和尚存缺口;[Sessions, events and history](sessions-events.md)、[message content](message-content.md)、[Vaults](vaults.md)、[source Files and Skills](source-files.md) 和 [Environment files and Artifacts](environment-files.md) 分别负责各自资源的规则。 + +下文所称“Beta 路由”是 `/v1/agents` 和 `/v1/vaults` 下的路由。“Files and Skills”是 `/v1/files` 和 `/v1/skills` 下的路由,这些路由会忽略 `OpenAI-Beta`。 + +## 请求 {#requests} + +### 路径和方法 {#paths-and-methods} + +| 情况 | Core 行为 | +| --- | --- | +| 为空、`.` 或 `..` 的路径段 | 在规范路径上提供服务,绝不重定向。路径段按 Go ServeMux 语义解析,并保留尾斜杠,因此 `/v1/agents/x/../` 会命中带尾斜杠的 404。 | +| 百分号编码的未保留字符(`A–Z`、`a–z`、`0–9`、`-`、`.`、`_`、`~`) | 在路由前解码,包括 `%2E` 点路径段。其他转义形式(如 `%2F`、`%5C` 和双重编码)保持编码状态,绝不会分隔路径段。每种拼写形式都按其规范路径进入对应路由并接受身份验证。 | +| 对 `GET` 路由使用 `HEAD` | 在执行相同的 Beta 和身份验证检查后运行 `GET` 路由,并返回其响应头但不返回正文。 | +| 对事件流、File 内容、Skill 内容、Skill 版本内容、Artifact 内容或 Environment 文件列表使用 `HEAD` | 返回 405,因此 `HEAD` 绝不会保持流打开或读取内容。 | +| 不支持的方法,包括 `FOO` 等未知方法 | 返回 405、代码 `unsupported_operation`、消息 "This API method is not supported.",并返回一个 `Allow` 头,按 `GET,HEAD,POST,DELETE` 的顺序列出该路由支持的方法。 | +| `/v1` 下的未知子路由,包括带尾斜杠的情况 | 在 Beta 和身份验证检查后返回 404、代码 `unsupported_operation`。 | +| `OPTIONS` 和 CORS | 不处理 CORS。 | + +创建 Agent、Vault、Credential、Environment Template、Environment 文件或 Session 均返回 201,流式 Session 创建也是如此。对 Agent 或 Environment Template 发送空更新正文会推进 `updated_at`,但不会更改其他内容;时间戳精确到秒。 + +### 标头 {#headers} + +Beta 路由要求 `OpenAI-Beta` 标头中恰好有一个值,且该值必须等于 `agents=v1`。如果该标头缺失、值不同或值重复出现,则返回 400,`type` 和 `code` 均为 `invalid_beta`,消息为 "To access the Agents API, set the 'OpenAI-Beta' header to 'agents=v1'."。此检查在身份验证之前执行。`agents=v0` 会被拒绝。 + +每个 Agents API 响应(包括错误响应和事件流)都包含: + +| 标头 | 值 | +| --- | --- | +| `X-Request-Id` | 一个新生成的 `req_`,后跟 32 个小写十六进制字符。Core 也会将其作为 `request_id` 写入日志。不会回显调用方提供的值。 | +| `OpenAI-Version` | `2020-10-01` | +| `OpenAI-Processing-Ms` | 写入标头时的处理耗时 | +| `X-Content-Type-Options` | `nosniff` | +| `Cache-Control` | JSON 响应上为 `no-store` | + +### 身份验证 {#authentication} + +`/v1` 仅接受 Project API 密钥,形式为 `Authorization: Bearer `。同一 Project 的所有密钥都视为同一调用方:它们共享该 Project 的资源和 Session 创建重试。Core 在每次请求时都在数据库中解析密钥及其 Project,不缓存凭据,超时时间为五秒。撤销密钥或归档其 Project 会在下一次请求时生效。Project 的所有密钥都使用主体 `service_account/project:`。[Projects and keys](admin-api.md#projects-and-keys) 介绍了密钥管理。 + +可选的 `OpenAI-Organization` 和 `OpenAI-Project` 标头在发送时必须各出现一次,并分别等于 `core` 和 `proj_`;任何其他值都会导致密钥被拒绝。 + +| 失败情况 | 响应 | +| --- | --- | +| 未提供密钥、使用其他方案、`Authorization` 标头为空或重复、密钥未知或已撤销、密钥属于已归档的 Project、使用 Core 密钥,或作用域标头不匹配 | 401,`type` 为 `invalid_request_error`,消息为 "A valid Agents API bearer key is required.",`WWW-Authenticate: Bearer`。在 Beta 路由上,`code` 为 null。在 Files and Skills 上,当发送且被拒绝的 Bearer 凭据恰好只有一个时,`code` 为 `invalid_api_key`;其他情况下为 null。 | +| 密钥查找失败,例如数据库不可用 | 503,`type` 为 `server_error`,`code` 为 `authentication_unavailable` | + +### 请求正文 {#request-bodies} + +每个 `/v1` JSON 路由在路由解码、验证或查找之前都会经过一个正文门禁:Agent 创建和更新、Vault 创建、Credential 创建和更新、Environment Template 创建和更新、Environment 文件创建、Session 创建和更新,以及 Session 事件。DELETE 路由、multipart Files 和 Skills 上传以及 Skill 更新使用各自的读取器。除下述 413 大小错误外,门禁错误均返回 400,`type` 和 `code` 为 `invalid_request_error`,param 为 null。 + +| 顺序 | 情况 | 响应 | +| --- | --- | --- | +| 1 | `Content-Type` 缺失、不是 JSON 或格式错误,包括无正文的 `POST`。`application/json` 和 `application/*+json` 不区分大小写,并允许附带参数 | "expected request with Content-Type: application/json" | +| 2 | 正文超过路由限制:Session 创建和 Environment Templates 为 16 MiB,文件创建采用 [Environment file](environment-files.md) 的限制,其他路由为 1 MiB | 413,代码 `request_too_large` | +| 3 | 无效 UTF-8 | "Invalid body: encountered a unicode decode error when parsing this JSON value. Please check the value to ensure it is valid unicode." | +| 4 | JSON 格式错误、尾随数据、两个值、字节顺序标记、仅含空白字符的正文,或形成孤立 UTF-16 代理项的转义 | "Invalid body: failed to parse JSON value. Please check the value to ensure it is valid JSON. (Common errors include trailing commas, missing closing brackets, missing quotation marks, etc.)" | +| 5 | 一个对象内任意深度出现重复键 | `"Invalid body: duplicate JSON key '' at ''. Duplicate JSON keys are not supported."` 路径使用 `.` 连接对象键并省略数组索引,例如 `metadata.k` 或 `tools.type`。键在反转义后按大小写敏感方式比较。报告文档顺序中首次出现的重复键。 | +| 6 | 根节点不是对象,包括数组 | `"Invalid type: expected an object, but got instead."` | +| — | 空正文或 `null` | 视为 `{}` | + +成员名称必须完全匹配。`Metadata` 等大小写变体或嵌套的 `Role` 属于未知成员,并且会在任何写入之前收到该路由的未知成员错误。 + +受 `echotext.Allowed` 保护的错误(例如未知成员、枚举、schema 根和游标错误)仅当调用方值不超过 256 字节的可打印 UTF-8 时才会原样返回。无法返回的未知成员会收到通用消息和 null param。Metadata 错误使用自身的验证规则,并且可以返回更长的键。 + +### 资源标识符 {#resource-identifiers} + +格式错误的路径标识符会收到与该路由上格式正确但不存在的标识符完全相同的响应,即使正文或查询也无效也是如此。缺失、格式错误和外部资源之间无法区分。UUID 标识符采用 Go UUID 解析器接受的其他拼写形式时也能解析,例如大写字母、花括号或 `urn:uuid:` 形式。 + +## 错误 {#errors} + +### 错误类型 {#error-types} + +| 状态 | `type` | `code` | +| --- | --- | --- | +| 缺失或无效的 `OpenAI-Beta` 导致 400 | `invalid_beta` | `invalid_beta` | +| Beta 路由上的资源缺失、格式错误或属于外部租户,导致 404 | `not_found_error` | `not_found_error`,消息 "Resource not found." | +| File 或 Skill 缺失,导致 404 | `invalid_request_error` | null | +| 401 | `invalid_request_error` | 见 [Authentication](#authentication) | +| 409 | `conflict_error` | 官方服务报告的冲突使用 `conflict_error`;仅 Core 存在的冲突保留自己的代码,例如 `idempotency_conflict` | +| 5xx | `server_error` | Core 的代码 | + +其他 400 响应使用 `invalid_request_error` 类型。存在官方等价项的验证失败使用 `invalid_request_error` 代码以及观察到的 param 和消息;其他请求错误保留 Core 的本地代码,例如 `invalid_request` 或 `unsupported_or_invalid_configuration`。 + +### 验证错误 {#validation-errors} + +| 情况 | 响应 | +| --- | --- | +| Agent 或 Session 创建或更新时,metadata 对超过 16 个、键超过 64 个字符或值超过 512 个字符 | Param 为 `metadata` 或 `metadata.`,并使用带有实际数量或长度的官方消息。先检查键值对,再检查键和值,键按排序顺序检查。 | +| Agent 或 Session 创建或更新时,或 Vault 创建时,metadata 值不是字符串 | Param 为 `metadata.`,消息为 `"Invalid type for 'metadata.': expected a string, but got instead."`。按文档顺序报告的第一个此类值会被优先报告。 | +| Agent `name` 超过 128 个字符 | Param 为 `name`。允许使用空名称和未去除首尾空白的名称。 | +| metadata 键或值中包含 U+0000 | Param 为 `metadata.` | +| 其他任何存储字符串或查询筛选条件中包含 U+0000 或无效 UTF-8 | Param 为 null,消息为 "Request text contains characters this service cannot store or compare, such as U+0000 or invalid UTF-8."。不会写入任何内容。PostgreSQL 无法存储 U+0000,而官方服务允许存储。 | +| Environment Template 或 Session 内联网络配置被拒绝:通配符、端口、方案、IPv6、空主机、没有域名的 `restricted` 策略、超过 100 个域名,或域名使用其他访问模式 | Param 为 null,并使用 Core 的消息 | +| Session 更新正文为空 | "At least one update field is required" | + +## 列表 {#lists} + +列表返回 `object: "list"`、`data`、`has_more`、`first_id` 和 `last_id`;空页面的 first ID 和 last ID 均为 null。`order` 默认为 `desc`。[Environment files](environment-files.md) 使用自己的 `page` token 分页,不在此处涵盖。 + +### 查询参数 {#query-parameters} + +| 情况 | Beta 列表 | Files | Skills 和 Skill 版本 | +| --- | --- | --- | --- | +| 未知键,包括 `tenant_id` | 忽略,在列表和单资源路由上均如此 | 忽略 | 忽略 | +| 重复的支持键,包括标量 `status` | 400 `invalid_request_error`,param 为 null,"Failed to deserialize query string: duplicate field ``" | 400 `unsupported_parameter` | 400 `duplicate_parameter`,param 为 ``,使用官方消息 | +| `order` 不是 `asc` 或 `desc`,包括显式空值 `order=` | 400 `invalid_request_error`,param 为 null,"Failed to deserialize query string: order: unknown variant ``, expected `asc` or `desc`" | 400,code 为 null,"order must be asc or desc." | 400 `invalid_value`,param 为 `order`,`"Invalid value: ''. Supported values are: 'asc' and 'desc'."` | + +已锁定版本的 Python SDK 会丢弃空查询值,因此 `list(order="")` 不会发送 `order`,而是使用默认值。`after` 会去除首尾空白。检查按以下顺序执行:重复键、`limit`、`order`;Vault 和 Credential 的 `status` 最先检查。这些检查都在任何资源查找之前执行。 + +Vault 和 Credential 列表接受标量 `status`、`status[]` 条目或两者,并按其并集进行筛选。默认会列出两个状态。其他值会返回 400 `invalid_request_error`,param 为 null,消息为 "Failed to deserialize query string: status: data did not match any variant of untagged enum VaultStatusFilterParam"。 + +### 页面大小 {#page-size} + +| 列表 | 默认值 | 接受范围 | 其他值 | +| --- | --- | --- | --- | +| Agents、Sessions、Items、Environment Templates、Subagent Items、Subagent Turn Items | 20 | 1–100 | 0 变为 1;大于 100 的值变为 100 | +| Vaults、Credentials | 20 | 1–100 | 0、负数和更大的整数,包括溢出值,都会限制在 1–100 范围内 | +| Turns、Subagents、Subagent Turns、Artifacts | 20 | 1–100 | 400 `invalid_request_error`,"limit must be between 1 and 100" | +| Skills、Skill 版本 | 20 | 0–100 | 0 返回空页面,其 `has_more` 表示游标后是否还有资源。负数:400 `integer_below_min_value`,param 为 `limit`。大于 100:400 `integer_above_max_value`,param 为 `limit` | +| Files | 10000 | 1–10000 | 400,code 为 null,"limit must be between 1 and 10000." | + +`limit` 不是十进制整数时(包括空值),Beta 列表返回 400 `invalid_request_error` 和 "Failed to deserialize query string: limit: invalid digit found in string";在 Vault 和 Credential 列表之外,超出有符号 64 位范围的值会返回 "Failed to deserialize query string: limit: number too large to fit in target type"。编码为 `%2B` 的前导 `+` 会被接受;前导 `-` 在 Beta 列表上会返回无效数字错误,但 Vaults 和 Credentials 除外。Skills 返回 `invalid_request` 和 "limit must be an integer between 0 and 100.";Files 返回 `invalid_request` 以及 Files 范围消息。 + +### 游标 {#cursors} + +`after` 指定同一列表中的某个资源,并且该资源必须位于已经解析出的父资源和租户内。首先解析父资源:父资源缺失或属于外部租户时,会先返回其 404,然后才读取游标。无法解析的游标,无论是随机值、格式错误、类型不同、父资源不同、已删除还是属于其他租户,均返回: + +| 列表 | 响应 | +| --- | --- | +| Agents、Sessions、Turns、Environment Templates、Vaults、Credentials | 404,`type` 和 `code` 均为 `not_found_error`,"Resource not found." | +| Session Items、Subagent Items、Subagent Turn Items | 400 `invalid_request_error`,param 为 null,"Invalid session item ID in `after`" | +| Subagents、Subagent Turns | 400 `invalid_request_error`,param 为 null,"Invalid resource ID in `after`" | +| Session Artifacts | 400 `invalid_request_error`,param 为 null,"after is not a valid artifact ID" | +| Skill 版本 | 不以 `skillver` 开头的值:400 `invalid_value`,param 为 `after`,`"Invalid 'after': ''. Expected an ID that begins with 'skillver'."`。其他 Skill 的版本:字段相同,消息为 "Skill version cursor does not match this skill."。格式错误的 `skillver` 后缀,或版本缺失、已删除、属于外部租户:404,code 和 param 均为 null | +| Skills | 404,code 和 param 均为 null | +| Files | 404,param 为 `after` | + +## Agent {#agents} + +### 已保存的配置 {#saved-configuration} + +Agent 创建要求提供 `model`。对于省略的字段,Core 会保存并返回以下值: + +| 字段 | 保存值 | +| --- | --- | +| `name`、`instructions` | null | +| `metadata` | `{}` | +| `tools` | `[]` | +| `text` | `{"format": {"type": "text"}, "verbosity": "medium"}` | +| `reasoning` | 按发送内容保存;省略 effort 时保持未设置,而不是采用模型默认值。Agent 和 Session 响应始终包含 `reasoning.effort` 和 `reasoning.summary`,未设置时为 null | +| `service_tier` | `auto` | +| `multi_agent` | 禁用。启用但未提供 `max_concurrent_subagents` 时为 6 | +| Function `defer_loading` | `false` | +| `programmatic_tool_calling.enabled` | `true` | +| `web_search` | 保存每个已锁定模式;请参阅 [tool policy](execution-tools.md#web-search-and-programmatic-tool-calling) | +| HTTP MCP 传输方式 | 以 `headers: {}` 保存;拒绝非空标头。Origin 和 allowlist 默认值见 [public MCP connection origin](environments.md#public-mcp-connection-origin) | + +保存某个值并不会使其可执行。Session 创建允许的配置集合更小;请参阅 [Session admission](#session-admission)。 + +### 配置验证 {#configuration-validation} + +Agent 创建和更新正文以及 Session 创建中的内联 `agent`,会在其解析器和 Harness 准入之前,根据已锁定的 `tools`、`text`、`reasoning`、`service_tier`、`multi_agent`、`model`、`name` 和 `instructions` 形状进行检查。失败时返回 400,`type` 和 `code` 均为 `invalid_request_error`: + +| 情况 | Param | 消息 | +| --- | --- | --- | +| 缺少必需成员 | JSON 路径,例如 `tools[0].parameters`;在 Session 创建中为 `agent.tools[0].parameters` | `Missing required parameter: ''.` | +| 未知成员,包括大小写变体和未锁定的 `tool_choice` | JSON 路径 | `Unknown parameter: ''.` | +| JSON 类型错误 | JSON 路径 | `Invalid type for '': expected , but got instead.` | +| 不支持的枚举值 | JSON 路径 | `Invalid value: ''. Supported values are: ...`,后跟已锁定的值 | +| 整数低于最小值 | JSON 路径 | `Invalid '': integer below minimum value. Expected a value >= 1, but got instead.` | +| 重复的函数名称、多个 `web_search`、多个 `tool_search` | null | `duplicate function tool name: `、`duplicate web_search tool`、`duplicate tool_search tool` | +| Function `parameters` 的字符串根 `type` 不是 `object` | null | `Invalid schema for function '': schema must be a JSON Schema of 'type: "object"', got 'type: ""'.` | +| `text.format` JSON schema 的字符串根 `type` 不是 `object` | null | `agent.text.format.schema must have top-level type "object"; got ""`,Agent 请求上也会返回此消息 | + +在同一个对象内,Core 会先报告联合类型的 `type`,然后报告未知成员,再按文档顺序报告成员值,最后报告缺失成员;先检查 tools,再检查 `text`,并在重复项和 schema 根检查之前检查整个对象。没有字符串根 `type` 的 schema 不会被检查。Function 和 output schema、MCP `transport`、`request_metadata`、`metadata` 和 `x_agents_core` 使用各自的解析器。更新正文和 Session 内联 Agent 会在查找 Agent 之前进行验证,因此属于当前租户、属于外部租户、缺失和格式错误的 Agent ID 会得到相同的响应。 + +即使无法执行,Core 也会保存已锁定形状允许的值:任意长度的函数名称、已启用的 programmatic tool calling、reasoning effort `max` 和 service tier `flex`。 + +### Session 准入 {#session-admission} + +Session 的有效配置还必须通过执行准入,无论配置来自保存还是内联方式。准入会先报告上表中的协议错误,包括已保存 Agent 中的重复工具和 schema 根,然后报告以下错误;在任何写入之前,这些错误均为 400 `unsupported_or_invalid_configuration`: + +| 配置 | 消息 | +| --- | --- | +| 显式 `reasoning.effort` 或 `reasoning.summary` | "Explicit reasoning execution options are not supported by this service yet." | +| `service_tier` 不是 `auto` | "Execution currently supports service_tier=auto only." | +| 已启用的 `web_search` 或省略模式的 `web_search`、已启用的 `programmatic_tool_calling` | 见 [tool policy](execution-tools.md#web-search-and-programmatic-tool-calling) | +| 超过 64 个函数,或函数名称为空或超过 512 字节 | "This service supports at most 64 function tools." 或 "Function names must be nonempty, unique and at most 512 bytes." | +| 两个 `programmatic_tool_calling` 声明、两个使用同一标签的 MCP 服务器 | "Execution requires distinct tool controls."、"Execution requires distinct MCP server labels." | + +每 Session 的 `tools` 替换可以允许一个其已保存 tools 原本会被拒绝的 Session。每种工具和 Harness 的支持情况见 [execution and tools](execution-tools.md)。 + +省略、null 和显式 `medium` text verbosity 会产生相同的 Session 配置。对于原生目录未声明 verbosity 支持的模型,Codex 适配器会丢弃 `medium` 设置并使用模型默认值,同时拒绝 `low` 或 `high`。 + +### 更新、删除和列表 {#update-delete-and-list} + +| 操作 | Core 行为 | +| --- | --- | +| `POST /agents/{agent_id}` | 仅替换提供的字段。嵌套对象会替换整个字段;null `name` 或 `instructions` 会将其清除;null 或 `{}` metadata 会清除所有键值对,而对象会替换这些键值对。现有 Session 保留其快照。 | +| `DELETE /agents/{agent_id}` | 返回 `{id, object: "agent.deleted", deleted: true}`。由该 Agent 创建的 Session、其历史及其创建重试均不受影响。重复删除或删除不存在的 Agent 会返回 404,指定该 Agent 的新 Session 也会返回 404。 | +| `GET /agents` | 先按创建时间、再按 ID 排序分页。 | + +## Session {#sessions} + +### 配置快照 {#configuration-snapshot} + +Session 创建会将 Agent 的有效配置复制到不可变快照中。使用 `agent_id` 时,已保存的 Agent 只会读取一次;内联 `agent` 中的字段会整体替换已保存字段,包括数组,而 null `tools` 会清空列表。省略的字段会继承保存值;如果内联 `x_agents_core` 省略 `harness`,则保留已保存的 harness。已保存的 Agent metadata 永远不会成为 Session metadata。后续更新或删除 Agent 只会影响新的 Session。 + +`stream` 默认为 false。`stream` 和 `agent_id` 不能为 null。省略或 null 的 `metadata` 为 `{}`。 + +创建过程会在查找创建重试之前,验证正文和 metadata 类型、请求字段及初始输入,以及放置和流式输入要求。对于新工作,Core 会解析 Template、已保存的 Agent 和模型配置,绑定 Vault Credential,然后验证所选 Harness 和执行配置,再执行写入。依赖项查找失败时会重新检查重试标识,以确保已经提交的创建仍可恢复。 + +### 创建重试 {#creation-retries} + +发送一个 1–128 字节且不只包含空白字符的 `Idempotency-Key`;更长或仅包含空白字符的键会返回 400 `invalid_request`。空标头视为未提供键。没有键时,每个请求都会创建一个新的 Session。官方服务即使收到相同的键,也会为每个请求创建新的 Session;Core 则返回原有 Session。 + +| 情况 | 响应 | +| --- | --- | +| 键、请求和 Project 均相同 | 返回 201以及 Session 的当前状态。不会再次接纳任何输入。`stream=true` 的重试返回 201,不含任何事件并关闭连接。 | +| 键相同但请求不同 | 409 `idempotency_conflict` | +| 删除 Session 后使用相同键 | 409 `idempotency_conflict` | + +键的作用域限定为 Project;该 Project 的任何键,包括轮换后签发的键,都可以用于重试。如果请求包含缺少 `model` 的内联 Agent,或者指定了已保存的 Agent、Template、初始文件或准备配置、`vault_ids` 或凭据引用、`x_agents_core`,或者 `openai_hosted` 环境,则会在读取上述任何来源之前按发送内容进行比较:即使 Agent、Template、Credential 或 deployment 默认值已更改或删除,匹配的重试仍会返回原有 Session。其他请求则按解析后的配置进行比较。模型提供商密钥仅以指纹形式参与比较。 + +### 更新和列表 {#update-and-list} + +`POST /agents/sessions/{session_id}` 仅接受必需的 `metadata`:null 或 `{}` 会将其清除,对象会替换所有键值对。执行状态和创建重试标识保持不变。 + +`GET /agents/sessions` 接受 `agent_id`,它会匹配 Session 不可变的根 Agent ID,包括内联 Agent ID,以及此后已更新或删除的 Agent。筛选在分页之前应用;空 `agent_id` 是一个筛选条件,而不是省略该筛选条件。 + +### 删除 {#delete} + +`DELETE /agents/sessions/{session_id}` 会删除处于空闲或失败状态、没有排队中、运行中或等待中的根 Turn,且没有待处理输入预留的 Session。 + +| 情况 | 响应 | +| --- | --- | +| 可删除 | 200 `{id, object: "agent.session.deleted", deleted: true}`。此后,对该 Session 的读取、更新、输入、Turn 和 Items 均返回 404,其打开的事件流也会结束。Core 会释放该 Session 由 Core 管理的沙箱;`self_hosted` 机器及其文件保持不变。 | +| 根 Turn 正在排队、进行中或等待必需操作,或者输入正在等待准入、自托管连接或托管预配 | 409,`type` 和 `code` 均为 `conflict_error`,param 为 null,"session must be durably idle or failed without required actions before deletion"。不会发生任何更改。 | +| 调用方自己的 Session,但已被删除 | 返回 200,并提供相同的确认信息 | +| 缺失、格式错误或属于外部租户 | 404 | + +Subagent 子 Turn 和待处理的 Environment 文件写入不会阻止删除。要删除正在运行的工作,请发送 `agent.session.input.cancel`,等待 Session 变为空闲状态,然后执行删除。等待 Environment 的输入无法取消;当输入开始、其五分钟期限届满或 Environment 失败时,Session 即可删除。Core 会在为其输入返回 202 的同一事务中接纳 Turn,因此紧接在该 202 响应之后执行删除会返回 409。 + +### 响应字段 {#response-fields} + +Session 的 `agent.tools` 会省略 `tool_search` 声明,因为已锁定的 Session 工具联合类型不包含这些声明;冻结配置仍会保留它们。在 `self_hosted` Session 上,`environment.remote_url` 是 Core 守护进程的 WebSocket URL,即公共 URL 下的 `/api/v1/agent-daemon/ws`,只有 OpenAgentCore 的 Runtime 守护进程会与其通信;请求无法设置此值。其他 Session 字段遵循已锁定的类型;`x_agents_core` 的说明见 [Agents API guide](../../../docs/zh/api/public-agent-api.md#core-extensions-x_agents_core)。 diff --git a/docs/zh/api/index.md b/docs/zh/api/index.md new file mode 100644 index 00000000..67d7a40c --- /dev/null +++ b/docs/zh/api/index.md @@ -0,0 +1,21 @@ +--- +title: "API 命名空间和凭据" +source: docs/api/index.md +source_hash: 278dc9cde80bdc89641b98c7c8dd386c9405b386cd2ad82e7addaf73f60a6126 +--- + +Core 提供三个命名空间。每个命名空间都有一种调用方及其独立凭据,凭据只能在其所属命名空间中使用。 + +| 命名空间 | 调用方 | 凭据 | 内容 | 所有者 | +| --- | --- | --- | --- | --- | +| `/v1` | 应用程序:业务系统和官方 OpenAI SDK | Project API key | 固定版本官方 Agents API 中全部且仅有的 58 个方法和路径对,列于 [upstream-routes.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream-routes.json)。仅属于 Core 的字段位于 `x_agents_core` 中:`harness`、`model_provider`、`harness_config`、`environment`,以及只读的 Session `installation` | [Agents API 指南](public-agent-api.md) | +| `/core/v1` | Web 的控制台服务器和操作员脚本 | [Core key](../getting-started/operations.md#core-key) | 安装信息、Project 和密钥、资源读取和删除、Session 归档、执行器凭据、默认模型、指标、审计、沙箱部署和节点 | [Core 管理 API](../../../contracts/agents-api/zh/admin-api.md) | +| `/api/v1` | 节点、Runtime 守护进程、自托管执行器及其安装程序 | 机器凭据:节点注册令牌和节点凭据、安装授权、执行器凭据和守护进程凭据。每种凭据只能用于其各自的路由 | `/api/v1/sandbox-node/*` 和 `/api/v1/agent-daemon/*` 下的机器初始化与连接(包括 WebSockets),以及公共原生安装程序下载 | [机器连接 API](../../../contracts/agents-api/zh/machine-api.md) | + +在其他命名空间中使用凭据会返回 401:在 `/core/v1` 或 `/api/v1` 上使用 Project API key,或者在 `/v1` 或 `/api/v1` 上使用 Core key。有关 Project 和密钥的行为,请参阅 [Project 自有资产](../concepts.md#projects-own-assets)。 + +**路由。** 反向代理将 `/v1` 和 `/api/v1` 发送到 Core,将其他所有请求发送到 Web([代理设置](../getting-started/install-options.md#https-and-the-reverse-proxy))。浏览器只能通过 Web 的控制台服务器访问 `/core/v1`;该服务器会在登录后添加 Core key,并对 `/v1` 和 `/api/v1` 返回 404([控制台服务器](../web/console-server.md))。操作员脚本通过 Core 的回环端口调用 `/core/v1`([编写 Core API 脚本](../getting-started/operations.md#script-the-core-api))。 + +## 机器连接 API {#machine-connection-api} + +节点、Runtime 守护进程和自托管安装程序使用各自的凭据调用 `/api/v1`。[机器连接 API](../../../contracts/agents-api/zh/machine-api.md) 列出了每个路由、调用方和凭据。 diff --git a/docs/zh/api/public-agent-api.md b/docs/zh/api/public-agent-api.md new file mode 100644 index 00000000..16f9fd1d --- /dev/null +++ b/docs/zh/api/public-agent-api.md @@ -0,0 +1,547 @@ +--- +title: "Agents API 指南" +source: docs/api/public-agent-api.md +source_hash: 4f830cdc1d1a73646d466b7de49496b72701b2f23f4842a384116cb75a91e5e5 +--- + +Core 在 `/v1` 提供 [OpenAI Agents API](https://platform.openai.com/docs/api-reference)。可以使用官方 OpenAI SDK 或普通 HTTP。本指南针对每项常见操作同时展示这两种方式,并说明 Core 与 OpenAI 存在差异的地方。 + +初次使用此 API?请先运行[快速入门](../getting-started/quickstart.md)。 + +## 准备开始 {#before-you-start} + +**基础 URL 和密钥。** 管理员会为你提供 API 基础 URL,例如 `https://core.example/v1`,以及 Project API 密钥。 + +```sh +export OPENAI_BASE_URL=https://core.example/v1 +read -rs OPENAI_API_KEY && export OPENAI_API_KEY +``` + +**SDK。** 使用固定版本: + +```sh +pip install openai==3.13.0 +``` + +```python +from openai import OpenAI + +client = OpenAI() # reads OPENAI_BASE_URL and OPENAI_API_KEY +``` + +**HTTP。** 每个请求都需要 Bearer 密钥。`/agents` 和 `/vaults` 下的路由还需要 `OpenAI-Beta: agents=v1`;`/files` 和 `/skills` 则不需要。SDK 会同时设置这两者。下面的 HTTP 示例使用以下 shell 辅助函数: + +```sh +oac() { # oac PATH [curl options]: call /agents or /vaults with the required headers + curl -sS "$OPENAI_BASE_URL$1" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "OpenAI-Beta: agents=v1" \ + -H "Content-Type: application/json" "${@:2}" +} +oac /agents +``` + +在共享主机上,`-H @<(printf 'Authorization: Bearer %s\n' "$OPENAI_API_KEY")` 可以避免密钥出现在进程列表中。 + +## 资源概览 {#resources-at-a-glance} + +| 资源 | 路径 | 用途 | +| --- | --- | --- | +| [Agents](#agents) | `/agents` | 可复用配置:模型、指令、工具和 harness | +| [Sessions](#sessions) | `/agents/sessions` | 一次 Agent 对话及其独立的 Environment | +| [输入事件](#send-input) | `/agents/sessions/{id}/events`(POST) | 消息、取消和工具结果 | +| [事件流](#stream-events) | `/agents/sessions/{id}/events`(GET) | 实时的服务器发送事件 | +| [Turns 和 Items](#turns-and-items) | `/agents/sessions/{id}/turns`、`/items` | 持久化历史 | +| [Files](#files) | `/files`、`/agents/environments/{id}/files`、`/agents/sessions/{id}/artifacts` | 上传内容、工作区文件和输出 | +| [Skills](#skills) | `/skills` | 带版本的能力捆绑包 | +| [Environment Templates](#environment-templates) | `/agents/environments/templates` | 可复用的工作区设置 | +| [Vaults](#vaults) | `/vaults` | MCP 服务器的只写凭据 | +| Subagents | `/agents/sessions/{id}/subagents` | 只读的子任务;请参阅 [subagents](../../../contracts/agents-api/zh/subagents.md) | + +Core 的路由与固定版本 SDK 中的路由完全一致,完整列表见 [upstream-routes.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream-routes.json)。Core 不添加任何路由;其扩展内容位于 [`x_agents_core`](#core-extensions-x_agents_core)。 + +## 常见任务 {#common-tasks} + +| 目标 | 使用方式 | +| --- | --- | +| 继续对话或引导正在运行的 Turn | 向同一 Session [发送消息](#send-a-message)。如果需要使用不同配置或工作区,请创建新的 Session | +| 实时查看输出 | [流式传输事件](#stream-events) | +| 停止当前 Turn,或在响应丢失后恢复 | [取消](#cancel)、[幂等性](#idempotency) | +| 从 Agent 调用你自己的代码 | [函数工具](#function-tools) | +| 为 Agent 提供 Skills、软件包、文件和设置命令 | [Skills](#skills)、[Environment Templates](#environment-templates) | +| 连接需要凭据的 MCP 服务器 | [Vaults](#vaults) 和 [execution tools](../../../contracts/agents-api/zh/execution-tools.md) | +| 在自己的计算机上使用 Skill 或 Plugin 目录 | [本地能力目录](../getting-started/self-hosted.md#local-capability-directories) | +| 将文件放入工作区,或下载 Agent 生成的内容 | [Files](#files) | +| 读取对话和工具结果 | [Turns 和 Items](#turns-and-items) | +| 查明 Session 或 Turn 失败的原因 | [诊断故障](#diagnose-a-failure) | + +## 约定 {#conventions} + +### 分页 {#pagination} + +列表返回: + +```json +{"object": "list", "data": [...], "has_more": true, "first_id": "...", "last_id": "..."} +``` + +| 参数 | 含义 | +| --- | --- | +| `limit` | 1–100,默认值为 20 | +| `order` | `desc`(默认值)或 `asc` | +| `after` | 传入上一页的 `last_id` | + +SDK 会自动为你分页: + +```python +for session in client.beta.agents.sessions.list(limit=100): + print(session.id, session.status) +``` + +```sh +oac "/agents/sessions?limit=100&after=$LAST_ID" +``` + +有两个列表存在差异:`GET /files` 一次最多返回 10,000 个文件;工作区文件使用不透明的 `page` 令牌(请参阅[工作区文件](#workspace-files))。 + +### 幂等性 {#idempotency} + +创建 Session 或发送输入时,请发送 `Idempotency-Key`(最长 128 字节)。使用相同密钥和正文重试时,会返回原始结果,而不会重复执行工作。相同密钥搭配不同正文会失败,并返回 409 `idempotency_conflict`。 + +```python +import uuid + +key = str(uuid.uuid4()) # store it before sending, reuse it on retry +client.beta.agents.sessions.events.create(session_id, events=[...], idempotency_key=key) +client.beta.agents.sessions.create(environment=..., extra_headers={"Idempotency-Key": key}) +``` + +**响应丢失后,**请使用相同密钥重试,然后读取 Session、Turns 和 Items。绝不要在没有密钥的情况下重新发送。即使 Session 已删除,Core 仍会保留创建时使用的密钥。 + +有关比较规则及其与 OpenAI 的差异,请参阅[创建重试](../../../contracts/agents-api/zh/wire-semantics.md#creation-retries)。 + +### 错误 {#errors} + +```json +{"error": {"type": "invalid_request_error", "message": "...", "code": "model_provider_required", "param": "x_agents_core.model_provider"}} +``` + +| 状态 | 常见代码 | 含义 | +| --- | --- | --- | +| 400 | `invalid_request_error`、`invalid_beta`、`model_provider_required`、`unsupported_or_invalid_configuration` | 修正请求。`invalid_beta` 表示 `OpenAI-Beta` 请求头缺失或错误 | +| 401 | `invalid_api_key` 或无代码 | 密钥错误或缺失,或者使用了其他命名空间中的密钥 | +| 404 | Beta 路由返回 `not_found_error` | 对象不存在,或属于其他 Project。这两种情况看起来完全相同 | +| 405 | `unsupported_operation` | Core 不支持此操作 | +| 409 | `conflict_error`、`idempotency_conflict` | 状态冲突,例如删除正在繁忙运行的 Session | +| 413 | `request_too_large` | 正文过大 | +| 503 | `authentication_unavailable`、`execution_unavailable` | 暂时无法确定状态;重试前应先读取状态 | + +每个响应都包含 `X-Request-Id`;报告问题时请包含该值。 + +### Core 扩展:`x_agents_core` {#core-extensions-x-agents-core} + +Core 可以运行多种 harness,并允许接入你自己的模型访问方式。这些设置是对 OpenAI 数据结构仅有的扩展,位于 `x_agents_core` 内: + +| 字段 | 所在位置 | 值 | +| --- | --- | --- | +| `harness` | Agent,或 Session 的内联 `agent` | `codex`、`claude_sdk` 或 `mcode` | +| `model_provider` | Agent,或 Session 创建请求(顶层) | `protocol`、`base_url`、`api_key`;对于 `mcode`,还包括 `context_window` 和 `max_output_tokens`。协议必须是该 harness 的原生协议之一 | +| `harness_config` | Agent、内联 `agent`,或 Session 创建请求(顶层,优先) | harness 的原生模型参数,例如 Codex 的 `model_reasoning_effort` | +| `environment` | `openai_hosted` 或 `self_hosted` Session 创建请求(顶层) | 可移植的准备配置:`environment_template_id`、`files`、`env`、`packages`、`setup_commands`、`skills`、`plugins`、`capability_directories`。同一字段不能也出现在 `environment` 中;请参阅 [Environments](../../../contracts/agents-api/zh/environments.md#preparation-order) | +| `installation` | 只读,适用于 `self_hosted` Session | 在你的计算机上运行的短期安装命令;请参阅 [self-hosted execution](../getting-started/self-hosted.md) | + +任何其他成员都会导致 400 错误。`api_key` 为只写字段;读取时会返回 `api_key_configured`。`harness_config` 会替换整个对象;传入 `{}` 可将其清除。使用 SDK 时,请通过 `extra_body` 传入这些字段。 + +## 选择 harness 和模型 {#choose-a-harness-and-a-model} + +harness 是运行 Session 的 Agent 程序:Codex(`codex`)、Claude Code(`claude_sdk`)或 MiniMax Code(`mcode`)。请在 Agent 或内联 `agent` 上设置 `x_agents_core.harness`;如果未设置,则使用安装的默认 harness([`core.default_harness`](../configuration.md#settings),除非操作员另行更改,否则为 Codex)。 + +- **模型。** `model` 是提供商给出的准确模型 ID。`openai_hosted` 或 `none` Session 上的内联 Agent 可以省略此字段,以使用其 harness 的默认模型配置。保存的 Agent 始终必须指定模型。 +- **提供商。** harness 会使用其原生协议之一直接调用你的提供商;系统不会进行转换,不匹配时会在创建 Session 阶段拒绝请求。[Model execution](../../../contracts/agents-api/zh/model-execution.md#saved-defaults-and-precedence) 列出了每个 harness 的协议,以及各类 Environment 上 Session 使用的提供商。Session 会在创建时冻结其提供商。 +- **原生参数。** `harness_config` 承载 harness 自身的模型设置;请参阅[原生模型参数](../../../contracts/agents-api/zh/model-execution.md#native-model-parameters)。 + +并非每种 harness、部署位置和操作组合都受支持;[Harness capabilities](../../../contracts/agents-api/zh/harness-capabilities.md) 列出了受支持的组合。 + +## Agents(智能体) {#agents} + +Agent 是保存的配置。Session 启动时会复制该配置,因此编辑 Agent 只会影响新的 Session。 + +```python +agent = client.beta.agents.create( + model="your-model-id", + name="Reviewer", + instructions="Review code changes and report problems.", + extra_body={"x_agents_core": {"harness": "codex"}}, +) +``` + +```sh +oac "/agents" -d '{ + "model": "your-model-id", + "name": "Reviewer", + "instructions": "Review code changes and report problems.", + "x_agents_core": {"harness": "codex"} +}' +``` + +```json +{"id": "00000000-0000-4000-8000-000000000001", "object": "agent", "model": "your-model-id", "name": "Reviewer", + "instructions": "...", "tools": [], "metadata": {}, "created_at": 1790000000, + "x_agents_core": {"harness": "codex"}} +``` + +| 操作 | SDK | HTTP | +| --- | --- | --- | +| 创建 | `agents.create(...)` | `POST /agents` | +| 读取 | `agents.retrieve(id)` | `GET /agents/{id}` | +| 更新 | `agents.update(id, ...)` | `POST /agents/{id}` | +| 列出 | `agents.list()` | `GET /agents` | +| 删除 | `agents.delete(id)` | `DELETE /agents/{id}` | + +(全文中的 `agents` 均指 `client.beta.agents`。) + +- **更新**只会更改你发送的字段。`metadata` 会替换所有键值对;`null` 会清除 `name` 或 `instructions`。 +- **删除**不会影响现有 Session。 +- **工具**包括函数、MCP 服务器、`tool_search`(Claude)以及设置了 `mode: "disabled"` 的 `web_search`。支持情况取决于 harness 和 Environment;请参阅 [execution tools](../../../contracts/agents-api/zh/execution-tools.md)。 + +## Sessions(会话) {#sessions} + +Session 是一次使用固定配置并拥有独立 Environment 的对话。 + +### 创建 Session {#create-a-session} + +```python +session = client.beta.agents.sessions.create( + environment={"type": "openai_hosted"}, + agent_id=agent.id, + input="Review the files in /workspace and summarize the risks.", + metadata={"ticket": "T-123"}, +) +``` + +```sh +oac "/agents/sessions" -H "Idempotency-Key: $(uuidgen)" -d '{ + "environment": {"type": "openai_hosted"}, + "agent_id": "00000000-0000-4000-8000-000000000001", + "input": "Review the files in /workspace and summarize the risks.", + "metadata": {"ticket": "T-123"} +}' +``` + +返回 201 和 Session: + +```json +{"id": "00000000-0000-4000-8000-000000000002", "object": "agent.session", "status": "idle", + "agent": {"model": "...", ...}, "environment": {"id": "env_...", "type": "openai_hosted", ...}, + "metadata": {"ticket": "T-123"}, "required_actions": [], "vault_ids": [], + "created_at": 1790000000, "last_active_at": 1790000000} +``` + +| 字段 | 含义 | +| --- | --- | +| `environment` | 必填。Agent 的工作位置;请参阅下表 | +| `agent_id` 或 `agent` | 已保存的 Agent,或内联 Agent 对象(字段与 Agent 创建操作相同)。`openai_hosted` 或 `none` 上的内联 Agent 可以省略 `model`,以使用安装的默认模型 | +| `input` | 第一条消息:字符串或消息数组。在 `none` 上为必填;在 `self_hosted` 之外使用 `stream: true` 时也为必填([初始输入](../../../contracts/agents-api/zh/sessions-events.md#initial-input-at-session-creation)) | +| `metadata` | 你自己的字符串键值对 | +| `vault_ids` | MCP 服务器可使用其凭据的 [Vaults](#vaults) | +| `stream` | `true` 时返回[服务器发送事件](#stream-events),而不是 JSON | +| `x_agents_core.model_provider` | 如果未从 Agent 或默认值继承,则指定此 Session 的模型访问方式。在 `none` 上会被拒绝 | + +| `environment.type` | 运行位置 | 备注 | +| --- | --- | --- | +| `openai_hosted` | Core 在某个节点或 E2B 上创建的沙箱;容量由管理员提供 | 可选 `network`、`packages`、`files`、`skills`、`plugins`、`env`、`capability_directories`、`setup_commands`,也可指定模板 | +| `self_hosted` | 你自己的 Linux、macOS 或 Windows 计算机 | 要求提供绝对路径 `workspace_directory`。Skills、软件包、文件或模板应放在 `x_agents_core.environment` 中。响应会在 `x_agents_core.installation` 中携带安装命令;请参阅 [self-hosted execution](../getting-started/self-hosted.md)。Session 会自带自己的 `model_provider` | +| `none` | 由操作员注册的设备连接,无工作区 | `input` 为必填。模型来自安装的默认配置;如果未配置默认模型,则来自设备 | + +新建的 `openai_hosted` Session 在 Core 准备沙箱期间会读取到 `idle`;Environment 准备就绪后,其首个 Turn 才会启动。[Environment contract](../../../contracts/agents-api/zh/environments.md) 负责部署位置、过期和准备过程。 + +### Session 状态 {#session-status} + +读取 `status`、`error` 和 `required_actions`,以决定是发送输入、返回[函数结果](#function-tools)、连接计算机还是诊断故障。[Session status](../../../contracts/agents-api/zh/sessions-events.md#session-status) 定义了所有状态,以及哪些故障允许发送新输入。 + +### 更新、列出和删除 {#update-list-and-delete} + +```python +client.beta.agents.sessions.update(session.id, metadata={"ticket": "T-124"}) +for s in client.beta.agents.sessions.list(agent_id=agent.id): + print(s.id) +client.beta.agents.sessions.delete(session.id) +``` + +| 操作 | HTTP | 备注 | +| --- | --- | --- | +| 读取 | `GET /agents/sessions/{id}` | | +| 更新 | `POST /agents/sessions/{id}` | 仅支持 `metadata` | +| 列出 | `GET /agents/sessions?agent_id=...` | 可选按 Agent 筛选 | +| 删除 | `DELETE /agents/sessions/{id}` | 仅当状态为 `idle` 或 `failed` 且没有待处理项时可用;否则返回 409。请先取消 | + +## 发送输入 {#send-input} + +所有输入都通过同一个端点以事件列表形式发送。输入持久化受理后、交给原生逻辑处理前,接口会返回 202。在空闲的 `openai_hosted` 或 `self_hosted` Session 上,消息请求最多可以等待五分钟,等待其 Turn 启动;请求可能因超时、取消或 Environment 错误而以 409 结束。客户端超时设置应涵盖这段等待时间([Environment 输入](../../../contracts/agents-api/zh/sessions-events.md#sessions-with-an-environment))。 + +### 发送消息 {#send-a-message} + +```python +client.beta.agents.sessions.events.create( + session.id, + events=[{ + "type": "agent.session.input.message", + "input": [{"role": "user", "content": [{"type": "input_text", "text": "Now fix the first risk."}]}], + }], + idempotency_key=key, +) +``` + +```sh +oac "/agents/sessions/$SESSION_ID/events" -H "Idempotency-Key: $KEY" -d '{ + "events": [{"type": "agent.session.input.message", + "input": [{"role": "user", "content": [{"type": "input_text", "text": "Now fix the first risk."}]}]}] +}' +``` + +- **处于空闲状态时,**消息会启动一个新 Turn。**Turn 运行时,**消息会加入该 Turn(进行引导),而不会启动并行任务。 +- **内容**使用 `input_text`,也可以使用 `input_image` 并以内联 PNG 或 JPEG 数据 URI 提供。Codex 和 Claude Code 接受图像;MiniMax Code 会拒绝图像。整个请求限制为 1 MiB。 +- 完整规则请参阅[消息内容](../../../contracts/agents-api/zh/message-content.md)。 + +### 取消 {#cancel} + +```python +client.beta.agents.sessions.events.create(session.id, events=[{"type": "agent.session.input.cancel"}]) +``` + +```sh +oac "/agents/sessions/$SESSION_ID/events" -d '{"events": [{"type": "agent.session.input.cancel"}]}' +``` + +Turn 到达 `cancelled` 状态时才会被取消,而不是请求返回时立即取消。在空闲状态下发送取消时,如果没有待处理输入,则不会执行任何操作;如果 Environment 输入预留仍处于待处理状态,则返回 409。重启同一 installation 时,自托管计算机上的工作区和历史记录会保留;请参阅[操作 installation](../getting-started/self-hosted.md#operate-the-installation)。 + +## 流式传输事件 {#stream-events} + +`GET /agents/sessions/{id}/events` 是服务器发送事件流。它**仅提供实时数据**:断连期间发送的事件不会重放。请在发送输入前打开该流,并通过 [Turns 和 Items](#turns-and-items) 补齐缺失内容。 + +```python +with client.beta.agents.sessions.events.stream(session.id) as stream: + for event in stream: + if event.type == "agent.session.turn.output_text.delta": + print(event.delta, end="", flush=True) + elif event.type in {"agent.session.turn.completed", "agent.session.turn.failed", "agent.session.turn.cancelled"}: + break +``` + +```sh +oac "/agents/sessions/$SESSION_ID/events" -N +``` + +```text +event: agent.session.turn.output_text.delta +data: {"type": "agent.session.turn.output_text.delta", "item_id": "item_...", "delta": "Hello", ...} +``` + +| 事件 | 发生时机 | +| --- | --- | +| `agent.session.turn.created`、`.in_progress` | Turn 启动 | +| `agent.session.turn.item.added`、`.item.done` | Item(消息、工具调用等)开始或完成 | +| `agent.session.turn.output_text.delta`、`.done` | 逐段输出助手文本 | +| `agent.session.turn.completed`、`.failed`、`.cancelled` | Turn 结束。携带 `usage` | +| `agent.session.in_progress`、`.idle`、`.requires_action`、`.failed` | Session 状态发生变化 | +| `agent.session.subagent.*` | 子任务开始或结束 | +| `error` | 流级错误 | + +该流会在多个 Turn 之间保持打开。要流式传输单个 Turn 并自动处理[函数调用](#function-tools),SDK 的 `sessions.stream` 辅助函数可以同时完成这两项操作。 + +**流式传输创建过程本身:**在 `sessions.create` 中传入 `stream=True`。你会先收到 `agent.session.created`,流会在首次出现 `idle` 或 `failed` 时结束。 + +**重新连接:**重新订阅,然后读取 Items,并按 ID 丢弃已经拥有的 Item。当流所属的 Project 密钥被吊销或 Project 被归档时,打开的流会关闭。详细信息请参阅[恢复模型](../../../contracts/agents-api/zh/sessions-events.md#recovery-model)。 + +## Turns 和 Items(轮次和条目) {#turns-and-items} + +Turn 是由输入启动的一项工作。Items 是其中记录的内容:消息、推理、工具调用及其结果。两者都是持久化的;你可以读取它们来检查结果或在断连后恢复。 + +```python +turns = client.beta.agents.sessions.turns.list(session.id, order="desc") +latest = turns.data[0] +print(latest.status, latest.usage) +for item in client.beta.agents.sessions.items.list(session.id, order="asc"): + print(item.type) +``` + +```sh +oac "/agents/sessions/$SESSION_ID/turns?order=desc&limit=1" +oac "/agents/sessions/$SESSION_ID/items?order=asc" +``` + +| Turn `status` | 含义 | +| --- | --- | +| `queued`、`in_progress` | 尚未完成 | +| `waiting` | 正在等待函数结果 | +| `completed`、`failed`、`cancelled` | 已完成 | + +- **用量**(`input_tokens`、`output_tokens`、`total_tokens`、……)未知时为 null,绝不会为零。Turn 运行期间,Session 的用量会保持为 null;Claude Code 和 MiniMax Code 不报告用量([用量规则](../../../contracts/agents-api/zh/sessions-events.md#usage))。 +- Turn 列表只包含顶层 Turn。要读取 `/subagents` 下的子任务,请使用该路径。 + +## 函数工具 {#function-tools} + +在 Agent 上声明函数。当模型调用该函数时,Session 会进入 `requires_action` 状态,Turn 会进入 `waiting` 状态,直到你返回结果。 + +```python +agent = client.beta.agents.create( + model="your-model-id", + tools=[{ + "type": "function", + "name": "get_weather", + "description": "Current weather for a city", + "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}, + }], +) + +def get_weather(args): + return f"Sunny in {args['city']}" + +session = client.beta.agents.sessions.create( + environment={"type": "openai_hosted"}, agent_id=agent.id, +) + +with client.beta.agents.sessions.stream( + session.id, input="What's the weather in Paris?", tool_handlers={"get_weather": get_weather} +) as stream: + for event in stream: + pass # the helper submits each result and stops when the Turn ends +``` + +如果不使用该辅助函数,请从 `session.required_actions` 读取调用,然后发送: + +```sh +oac "/agents/sessions/$SESSION_ID/events" -d '{ + "events": [{"type": "agent.session.input.tool_result", + "turn_id": "turn_...", "call_id": "call_...", "success": true, "output": "Sunny in Paris"}] +}' +``` + +重新发送相同结果是安全的。为同一次调用发送不同结果,或在取消后发送结果,都会失败并返回 409。MiniMax Code 不支持公共函数。 + +## Files(文件) {#files} + +三种文件具有不同用途: + +| 类型 | 用途 | 路径 | +| --- | --- | --- | +| [源文件](#source-files) | 一次性上传字节,并通过 ID 引用 | `/files` | +| [工作区文件](#workspace-files) | 将文件放入正在运行的 Session 工作区,或列出其中的文件 | `/agents/environments/{id}/files` | +| [Artifacts(产物)](#artifacts) | 下载 Agent 生成的内容 | `/agents/sessions/{id}/artifacts` | + +### 源文件 {#source-files} + +```python +f = client.files.create(file=open("data.csv", "rb"), purpose="user_data") +``` + +```sh +curl "$OPENAI_BASE_URL/files" -H "Authorization: Bearer $OPENAI_API_KEY" \ + -F purpose=user_data -F file=@data.csv +``` + +仅接受 `purpose=user_data`,最大为 512 MiB。不需要 Beta 请求头。其内容无法再次下载;请在工作区文件或模板中使用其 ID。 + +### 工作区文件 {#workspace-files} + +将文件复制到 Session 的 Environment 工作区(`session.environment.id`): + +```python +env_id = session.environment.id +client.beta.agents.environments.files.create(env_id, type="file_id", file_id=f.id, path="/workspace/data.csv") +client.beta.agents.environments.files.create(env_id, type="inline", data="aGVsbG8K", path="/workspace/hello.txt") +page = client.beta.agents.environments.files.list(env_id, path="/workspace") +``` + +```sh +oac "/agents/environments/$ENV_ID/files" -d '{"type": "file_id", "file_id": "file-...", "path": "/workspace/data.csv"}' +``` + +- `inline` 数据使用 base64,解码后最大为 5 MiB;`file_id` 最大为 50 MiB。 +- 系统会创建父目录。已有文件绝不会被覆盖(400)。 +- 列表显示单个目录中的普通文件,不会递归列出内容。列表使用 `page` 令牌分页,并返回 `next`。 + +### Artifacts(产物) {#artifacts} + +Turn 完成时,Core 会捕获工作区 `outputs/` 目录下的普通文件。即使 Environment 消失,Artifacts 仍可读取。 + +```python +for a in client.beta.agents.sessions.artifacts.list(session.id): + data = client.beta.agents.sessions.artifacts.content(a.id, session_id=session.id) + data.write_to_file(a.path.rsplit("/", 1)[-1]) +``` + +```sh +oac "/agents/sessions/$SESSION_ID/artifacts" +oac "/agents/sessions/$SESSION_ID/artifacts/$ARTIFACT_ID/content" -o report.md +``` + +每个 Artifact 都有 `path`、`size_bytes`、`turn_id` 和 `environment_id`。删除 Artifact 不会删除工作区文件。 + +## Skills(技能) {#skills} + +Skill 是 Agent 可使用的、包含指令和文件的带版本捆绑包。可以上传目录或 ZIP;每次上传都会创建一个版本。 + +```sh +curl "$OPENAI_BASE_URL/skills" -H "Authorization: Bearer $OPENAI_API_KEY" -F files=@my-skill.zip +``` + +```python +skill = client.skills.create(files=[("my-skill/SKILL.md", open("my-skill/SKILL.md", "rb"))]) +client.skills.versions.create(skill.id, files=[...], default=True) +``` + +- 不需要 Beta 请求头。每个 Environment 最多可选择 50 个 Skills;每个归档压缩后最大为 5 MiB,展开后最大为 20 MiB。 +- SDK 3.13.0 会上传内容中丢弃唯一的 ZIP 文件;上传 ZIP 时请使用 HTTP。 +- 可通过 Session 的 `environment.skills` 或[模板](#environment-templates)为其附加 Skills。 + +详细信息请参阅 [Files and Skills](../../../contracts/agents-api/zh/source-files.md)。Session 会在准备期间一次性安装其 Skills、Plugins 和软件包;之后编辑源文件不会影响正在运行的 Session。准备错误会在任何工作开始前导致 Session 失败:请修复原因,而不是在新 Session 中重试。 + +## Environment Templates(环境模板) {#environment-templates} + +模板用于保存工作区设置以供复用。`openai_hosted` Session 在 `environment` 中引用模板;`self_hosted` Session 则在 `x_agents_core.environment` 中引用: + +```python +template = client.beta.agents.environments.templates.create( + name="python-data", + packages={"python": ["pandas"]}, + setup_commands=[{"command": "mkdir -p /workspace/outputs"}], +) +``` + +| 字段 | 含义 | +| --- | --- | +| `network` | `access` 可设为 `enabled`(默认值)、`disabled` 或 `restricted`;设为 `restricted` 时,可将网络限制为 `allowed_domains` 中 1–100 个精确主机。Session 只能进一步缩小范围。请参阅[受限网络策略](../../../contracts/agents-api/zh/environments.md#restricted-network)中的执行限制 | +| `packages` | 软件包设置;请参阅[软件包准入](../../../contracts/agents-api/zh/environments.md#preparation-order) | +| `setup_commands`、`env` | 在准备期间运行和设置。读取时从不返回 | +| `files`、`skills`、`plugins` | 初始内容。最多 50 个文件,内联数据总计最多 10 MiB | + +Session 启动时会冻结模板。详细信息请参阅 [Environment Templates](../../../contracts/agents-api/zh/environments.md#templates)。 + +## Vaults(凭据库) {#vaults} + +Vaults 保存 HTTP MCP 服务器的凭据:`static_bearer` 令牌,或支持可选刷新的 `mcp_oauth` 令牌。令牌为只写字段。 + +```python +vault = client.beta.agents.vaults.create(name="github") +client.beta.agents.vaults.credentials.create( + vault.id, name="github-token", + auth={"type": "static_bearer", "token": "ghp_...", "mcp_server_url": "https://api.githubcopilot.com/mcp/"}, +) +session = client.beta.agents.sessions.create(environment={"type": "none"}, input="...", vault_ids=[vault.id], agent_id=agent.id) +``` + +HTTP 路径为 `/vaults`,需要 Beta 请求头。Session 从其 `vault_ids` 中选择凭据,也可以通过 `credential_id` 指定具体凭据;无论采用哪种方式,MCP 服务器的 URL 都必须与所选凭据的 `mcp_server_url` 完全匹配。[Vaults contract](../../../contracts/agents-api/zh/vaults.md) 负责选择、错误、OAuth 刷新和删除。MCP 工具的 `connection_origin` 决定由 Core 端还是工作区连接服务器,而且每个 harness 支持的取值集合不同;请参阅 [MCP connection origin](../../../contracts/agents-api/zh/environments.md#public-mcp-connection-origin)。 + +## 诊断故障 {#diagnose-a-failure} + +1. 读取 Session 的 `status` 和 `error`,以及最新 Turn 的 `error`。失败的 Turn 只会报告通用的 `internal_error`。 +2. 检查 Environment 是否已连接,以及其 harness 是否可用。 +3. 在 [Harness capabilities](../../../contracts/agents-api/zh/harness-capabilities.md) 中检查 harness、模型和工具的组合。 +4. 向管理员索取 Session 的[诊断信息](../../../contracts/agents-api/zh/session-diagnostics.md),其中会指出故障类别;同时请查看[故障排除](../getting-started/operations.md#troubleshooting),了解服务日志、凭据和节点就绪状态。 + +401 通常表示使用了其他命名空间中的密钥;请参阅 [API 命名空间与凭据](index.md)。 + +## 与 OpenAI 的差异 {#differences-from-openai} + +Core 在某些行为上与 OpenAI 服务不同,例如 Session 创建的幂等性和特定 harness 的工具支持。[覆盖情况清单](../../../contracts/agents-api/zh/index.md#differences-from-openai) 列出了所有差异及各资源的状态;[公共 OpenAPI](../../../contracts/agents-api/openapi.yaml) 包含准确的模式定义。 diff --git a/docs/zh/architecture.md b/docs/zh/architecture.md new file mode 100644 index 00000000..8777d50e --- /dev/null +++ b/docs/zh/architecture.md @@ -0,0 +1,51 @@ +--- +title: "架构" +source: docs/architecture.md +source_hash: b8a00701caa83314115f28c3fa754291e8eaf734e88de8e0012e45d8b1893338 +--- + +OpenAgentCore 将编排、计算资源和原生执行分开。Core 负责 API 和持久状态。Sandbox Provider 管理计算资源。Runtime daemon 准备 Environment 并运行选定的 Harness;Harness 的原生 SDK 或协议负责模型与工具循环。 + +```mermaid +flowchart TB + App["Application / official SDK"] <-->|"Agents API /v1: HTTP and SSE"| Core + Web["Web administrator console"] <-->|"Core API /core/v1"| Core + Core["Core: authorization, configuration snapshots,
orchestration and durable state"] + Core --- DB[("PostgreSQL")] + Core -->|"Sandbox Provider protocol"| SP["Sandbox Provider: Docker / E2B / microsandbox"] + SP -.->|"Provision compute and bootstrap Runtime"| R + User["User-machine installer"] -.->|"Start Runtime"| R + Core <-->|"Core–Runtime protocol:
preparation, execution, events and receipts"| R + subgraph Env["Environment: managed sandbox or user-owned machine"] + R["Runtime daemon"] --> P["Workspace and capability preparation"] + P -->|"Harness protocol"| A["Harness adapter"] + A <-->|"Native SDK or protocol"| H["Native Harness: model and tool loop"] + H <--> F["Workspace, tools and artifacts"] + end + H <-->|"Model API"| Model["Model provider"] + H <-->|"MCP"| MCP["Local or remote MCP servers"] +``` + +虚线表示资源供应与安装。实线表示组件交互,包括进程内接口。daemon 主动向 Core 发起经过认证的 WebSocket 连接。[API 索引](api/index.md) 说明应用、运维和机器命名空间;[协议边界](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/AGENTS.md#protocols-at-every-boundary) 列出各协议的代码和所属文档。 + +## 组件职责 {#component-responsibilities} + +| 组件 | 职责 | 参考 | +| --- | --- | --- | +| Core | 认证调用方,解析并冻结配置,调度 Turn,处理取消和待处理交互,将资源与执行事实持久化到 PostgreSQL | [Core 服务](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/README.md) | +| Sandbox Provider | 创建、观察、续期和回收计算资源;提供 Runtime 启动输入 | [Sandbox Provider](sandbox-provider.md)、[Runtime 引导](runtime-bootstrap.md) | +| Sandbox node | 运行 Docker 或 microsandbox 主机,协调分配给它的 generation 与 allocation | [Sandbox node 协议](../../contracts/agents-api/zh/node-generation-protocol.md) | +| Runtime | 准备工作区和能力,管理 Session Executor,执行 Turn 并报告事件与回执 | [Core–Runtime 协议](runtime-protocol.md) | +| Harness adapter | 验证原生配置,调用上游 SDK 或协议,转换事件并确认原生清理完成 | [Harness 接入](../../contracts/agents-api/zh/harness-onboarding.md) | +| Model provider | 提供 Harness 选定的模型协议 | [模型执行](../../contracts/agents-api/zh/model-execution.md) | +| Web | 让管理员通过服务端 Core API 连接配置与观察安装实例 | [控制台服务端](web/console-server.md) | + +[仓库地图](development.md#repository-map) 标出这些组件的位置。[概念与所有权](concepts.md) 解释 Project 边界、管理员权限和工具隔离。 + +## Session 的完整流程 {#a-session-end-to-end} + +应用通过 Agents API 创建 Session。Core 解析其配置与执行位置。托管 Session 通过选定的 Sandbox Provider 获取计算资源;自托管 Session 等待用户运行安装命令。`environment: none` 的 Session 使用已连接的执行设备,不提供工作区。[应用指南](api/public-agent-api.md#create-a-session) 说明这些选项。 + +daemon 连接后,Core 检查可用 Harness 和请求的能力。Runtime 准备工作区 Environment 及其能力快照,然后准备或复用 Session Executor。每个 Turn 通过原生 Harness 运行。Core 持久化输出、工具交互和回执,供应用读取和接收事件。完成或取消使 Turn 结算;健康的 Executor 可以在同一 Environment 中执行下一个 Turn。 + +执行与计算资源拥有独立生命周期:关闭 Executor 后,其 allocation 和工作区保留到 Provider 回收为止。准备、连接和执行就绪具有不同状态。[Environment 契约](../../contracts/agents-api/zh/environments.md) 负责准备规则,[Core–Runtime 协议](runtime-protocol.md) 负责顺序、回执和故障处理。 diff --git a/docs/zh/concepts.md b/docs/zh/concepts.md new file mode 100644 index 00000000..c916e09a --- /dev/null +++ b/docs/zh/concepts.md @@ -0,0 +1,37 @@ +--- +title: "概念与所有权" +source: docs/concepts.md +source_hash: f15758cc223f104393f3eca822dbc585c8c41bb5c7bdbe787e8af4e7bf1a0d95 +--- + +Project 是 OpenAgentCore 的执行租户。应用使用其 API key;运维人员使用独立的 Core key 管理安装实例。[API 索引](api/index.md) 将每类调用方映射到对应命名空间和凭据。 + +## Project 拥有资产 {#projects-own-assets} + +Project 拥有自己的 Agent、Session、Environment、Skill、File 和 Vault。其命名 API key 共用相同的主体、权限和资源。Core 不提供产品用户、角色、成员关系或只读 key。名称是标签;稳定 ID 标识 Project 和 key。 + +Project 和 key 存储在 PostgreSQL 中。Core 仅在签发时返回一次 key 明文。轮换 key 时,在同一 Project 中签发新 key,再撤销旧 key。撤销保留资产、写入来源和已接受的工作。归档 Project 会撤销所有 key 并禁止签发新 key;管理员仍可检查和删除其资源。[管理契约](../../contracts/agents-api/zh/admin-api.md#projects-and-keys) 定义这些操作。 + +[Core key](getting-started/operations.md#core-key) 是独立的部署凭据。管理员需要使用应用 API 时,应签发 Project key 并使用该 Project 的权限。产品用户、工作区和业务权限属于应用。 + +## 资源隔离 {#resource-isolation} + +应用的读取、写入和引用限定在 key 所属的 Project 内。其他 Project 中的资源与不存在的资源无法区分。Core 不在 Project 之间共享或复制资产。节点、已配置的模型端点和启动设置属于部署基础设施。 + +## 管理员可以做什么 {#what-administrators-can-and-cannot-do} + +管理员管理 Project 和 key、沙箱部署、节点、executor 凭据及部署默认模型。他们可以检查资源、执行历史、运行计数与用量,并按删除规则删除资源。归档托管 Session 会请求取消和沙箱回收;参见 [Session 归档](../../contracts/agents-api/zh/admin-api.md#session-archive)。 + +创建或编辑应用资产、启动 Session 和提交输入都需要 Project API key。Core key 不提供应用身份,不能读取已保存的秘密。[管理 API](../../contracts/agents-api/zh/admin-api.md) 定义其操作;[控制台服务端](web/console-server.md) 负责 Web 登录与凭据处理。 + +## Runtime 与外层隔离 {#runtime-and-outer-isolation} + +Runtime daemon 在 Linux、macOS 和 Windows 上运行。原生平台行为由 Runtime 及其 Harness adapter 负责;托管 Sandbox Provider 运行 Linux 环境。 + +工具以启动 daemon 的账户权限运行。daemon 不增加文件系统、权限或网络隔离。应使用外层 Environment 提供隔离:托管 Docker、E2B 或 microsandbox 环境,或在自托管机器的 Runtime 外使用容器或虚拟机。认证、私有存储、锁和进程清理保护连接与生命周期,但以同一用户运行的工具可以访问 Runtime 数据。 + +## 秘密与审计 {#secrets-and-audit} + +凭据值、模型 key 和机密模板数据仅可写入:应用和管理员读取结果都不包含它们。对话文本、Skill 源码和 Artifact 内容是可读取的资源数据,管理员也可以读取。 + +公开写入记录执行写入的 API key。管理员写入记录独立的审计身份与目标 Project。Web 的 actor 标签仅用于显示;Core 授权依据是 Core key。审计失败会回滚写入。读取不进行审计,审计记录不包含请求体、秘密或文件内容。[写入来源](../../contracts/agents-api/zh/admin-api.md#write-provenance) 和[审计日志](../../contracts/agents-api/zh/admin-api.md#audit-log) 契约定义存储的记录。 diff --git a/docs/zh/configuration.md b/docs/zh/configuration.md new file mode 100644 index 00000000..a09451aa --- /dev/null +++ b/docs/zh/configuration.md @@ -0,0 +1,215 @@ +--- +title: "配置参考" +source: docs/configuration.md +source_hash: 95f8988acdb576c2ecfe80f9ae0d1cb50a08db66495d5b7e4201a1f750fcc0c3 +--- + +Core 安装的每项设置都恰好只有一个归属位置。共有两类: + +| 类型 | 示例 | 归属位置 | 修改方式 | 生效方式 | +| --- | --- | --- | --- | --- | +| [进程设置](#process-settings-configjson) | 公共 URL、端口、日志、Harness、执行并发度、审计保留期、OAuth 来源、数据库连接池、Runtime 历史记录导出 | 安装目录中的 `config.json`(默认 `~/.oac/core`) | 通过 Web 域设置或 `oac domain` 配置托管 HTTPS;否则编辑该文件,然后运行 `oac apply` | `oac apply` 会重启读取了这些已更改设置的服务 | +| [运行时设置](#runtime-settings-web) | 沙箱后端和大小、节点、项目和密钥、默认模型、执行器凭据 | Core 的 PostgreSQL 数据库 | 在 Web 中修改,或使用 Core 密钥调用 Core API(`/core/v1`) | 保存时无需重启 Core;节点会异步准备 Runtime 变更 | + +Web 的 **System** 页面显示该安装的地址、默认模型和沙箱配置,并在 **Startup settings** 下以只读方式显示进程设置、`config.json` 的路径以及 apply 命令。机密信息存放在 [`secrets/`](#installation-directory) 中,每项仅保存一份。`generated/` 中的文件派生自 `config.json`。没有任何配置文件定义项目或 API 密钥。 + +## 进程设置:config.json {#process-settings-config-json} + +安装程序会写入适用于该安装[模式](getting-started/install-options.md#modes)的每项设置,因此该文件会显示每个值。[安装选项](getting-started/install-options.md)中列出的安装程序标志仅用于为该文件提供初始值。要更改设置,请编辑该文件并应用更改: + +```sh +~/.oac/core/oac apply --dry-run # show the changed settings, files and restarts +~/.oac/core/oac apply +``` + +### oac apply 的工作方式 {#how-oac-apply-works} + +1. 它会验证 `config.json`,如果某个值无效,则不会进行任何更改。`mode`、`native_core` 和 `ingress` 在安装后固定;要更改它们,请安装到新目录。它还会检查更改后的 `host`、端口或托管入口 `public_url` 所新增的监听器([端口](getting-started/install-options.md#ports));如果 `host` 不是本机的地址,或其他程序占用了其中一个端口,它也不会进行任何更改;安装自身的监听器不计入。 +2. 它会把 Core、Web 和 Compose 读取的文件写入 `generated/`:`compose.json`、`core.env`、`core-key-digests.json`、`settings.json`,以及在使用时写入 `runtime-history.json`、托管的 `Caddyfile` 和原生 Core 单元。不要编辑这些文件。手工编辑某个生成文件后,`apply` 会中止,直到你将相应更改写入 `config.json` 并运行 `oac apply --discard-edits`;该命令会将编辑过的副本保留为 `generated/.edited-