添加新的 Harness 包
本指南涵盖为代理 harness 创建新的 @ai-sdk/harness-<name> 包。
在评估运行时之前,请阅读 Harness 需求 以了解可行性标准和需要记录的能力差距。在实现 harness 包时遵循这些需求。
Harness 可以是宿主导模式(host-driven),即运行时在宿主机进程中运行并使用沙箱远程操作,也可以是桥接模式(bridge-backed),即由于运行时需要本地访问沙箱文件系统或进程环境,一个小的桥接部分在沙箱内部运行。 当运行时支持时,首选宿主导模式。
第一方 vs 第三方 Harness
- 第三方包:任何运行时都可以发布外部 harness 包。
- 第一方
@ai-sdk/harness-<name>包:首先创建一个 issue 以讨论该运行时是否应归属于此仓库。
参考示例
请参阅 https://github.com/vercel/ai/pull/16255/changes 以获取添加新 harness 的完整示例。
Harness 架构
AI SDK 使用遵循适配器模式的分层 harness 架构:
- Harness 规范(
@ai-sdk/harness):定义诸如HarnessV1和HarnessV1Session等接口 - 工具函数(
@ai-sdk/harness/utils):实现 harness 的共享代码 - Harness 实现(
@ai-sdk/harness-<name>):具体的 harness 适配器 - Harness 代理(
@ai-sdk/harness/agent):高级别的面向用户的HarnessAgentAPI
分步指南
1. 创建包结构
创建 packages/harness-<name>,其基础结构如下:
packages/harness-<name>/
├── src/
│ ├── index.ts
│ ├── <name>-harness.ts
│ ├── <name>-harness.test.ts
│ └── <name>-auth.ts # 如果运行时需要身份验证解析
├── package.json
├── tsconfig.json
├── tsconfig.build.json
├── tsdown.config.ts
├── turbo.json
├── vitest.node.config.js
└── README.md
如果运行时必须在沙箱内部执行,还需添加桥接文件:
src/
├── <name>-bridge-protocol.ts
├── <name>-bridge-protocol.test.ts
└── bridge/
├── index.ts
├── package.json
└── pnpm-lock.yaml
添加仅包含包标题(# @ai-sdk/harness-<name>)的 CHANGELOG.md。每个包都必须有一个。
2. 配置 package.json
使用现有 harness 包作为脚本、导出、仓库元数据和发布设置的真实来源。
所需的包基本信息:
"name": "@ai-sdk/harness-<name>""type": "module""version": "0.0.0"(新包的起点)"license": "Apache-2.0""sideEffects": false- 通过
workspace:*依赖@ai-sdk/harness - 当使用沙箱/身份验证/模式工具时,通过
workspace:*依赖@ai-sdk/provider-utils - harness 所需的运行时 SDK/CLI 依赖
- 与现有 harness 包匹配的 dev 依赖
"engines": { "node": ">=22" }
对于桥接包,添加所需的桥接资产复制步骤,用于 src/bridge/ 下的文件。
桥接依赖规则(桥接模式 harness):
- 桥接的运行时依赖位于
src/bridge/package.json中(在引导时在沙箱内安装),而不是主 package.json。更改后,使用pnpm --dir packages/harness-<name>/src/bridge install --lockfile-only --ignore-workspace从仓库根目录重新生成src/bridge/pnpm-lock.yaml。 - 对于
src/bridge/中的每个第三方导入,将依赖项添加到src/bridge/package.json,以便在沙箱中安装。tsdown 会自动外部化主包声明的依赖、对等依赖和可选依赖;将未在该处声明的仅桥接依赖项添加到tsdown.config.ts的deps.neverBundle中。缺失的依赖项或必需的排除项可能仅在沙箱运行时显示为模块解析错误。 - 包括运行时延迟导入的包——例如,在运行时从模型 ID 解析的提供商 SDK(
@anthropic-ai/sdk、openai)——即使没有任何东西直接导入它们,也应包含在src/bridge/package.json中。除非桥接源代码导入这些包,否则它们不需要neverBundle条目。这些包仅当实际使用该提供商的模型时才会失败。 - 将共享依赖版本(传输、模式、工具、运行时 SDK)与其它 harness 包当前使用的版本匹配——从同级包复制,而不是自行选择固定版本。过时的固定版本会偏离安全补丁,并可能使共享桥接运行时不同步;在创建时检查当前版本。
3. 创建 TypeScript、构建和测试配置
复制最接近的现有 harness 包配置文件,并调整路径/包名称:
tsconfig.jsontsconfig.build.jsontsdown.config.tsturbo.jsonvitest.node.config.js
Harness 包目前仅使用 Node 测试,除非实现有特定原因添加其他运行时。
4. 实现 Harness 适配器
从 <name>-harness.ts 导出一个工厂函数,并从 src/index.ts 重新导出。
使用架构文档了解合约细节。在实现时,验证:
- 返回一个
specificationVersion: 'harness-v1'的HarnessV1; - 使用稳定的 kebab-case
harnessId; - 通过
builtinTools公开适配器原生内置工具; - 保持构造过程同步且无副作用;
- 使用
startOpts.sandboxSession和startOpts.sessionWorkDir;切勿创建单独的沙箱; - 从需要不受支持的运行时能力的方法中抛出
HarnessCapabilityUnsupportedError; - 不要硬编码默认模型,除非运行时技术上需要一个——某些底层 SDK 自身没有默认值。否则,仅当消费者配置了模型时才传递模型,并保持底层 SDK 的默认值不变;保持会话的
modelId与实际发送的内容一致(不要报告桥接静默覆盖的模型); - 处理
doPromptTurn/doContinueTurn可能接收到的tools和instructions:如果运行时无法接受自定义tools,则抛出HarnessCapabilityUnsupportedError以明确说明,而不是静默丢弃;如果没有原生的instructions输入,则将它们附加到第一条用户消息之前(Codex/Claude Code 的变通方法); - 在为
sandbox.run/sandbox.spawn构建 shell 命令时,引用插值路径(workDir、桥接状态目录等)——它们可能包含空格。
如果运行时需要沙箱内设置,则公开 getBootstrap()。
5. 实现运行时特定关注点
仅添加运行时需要的关注点:
- 身份验证解析——对于 AI Gateway 支持,使用中央
getAiGatewayAuthFromEnv()辅助函数,而不是直接读取环境变量。这可确保VERCEL_OIDC_TOKEN和AI_GATEWAY_API_KEY都被接受为 Gateway 凭据。当运行时按模型解析提供商时,从模型 ID 解析提供商并设置该提供商的环境变量;如果通过 Gateway 路由,请注意基础 URL 约定因提供商而异(例如,Anthropic 客户端会将/v1/messages附加到根基础 URL,而 OpenAI 客户端会附加到/v1基础 URL); - AI Gateway 客户端归因——遵循现有 harness 包中的约定:定义一个版本化的客户端应用值,例如
ai-sdk/harness-<name>/${VERSION},并将其用于 Gateway 请求。当底层运行时/SDK 同时支持时,配置User-Agent和x-client-app标头;这两个标头中至少需要一个。如果运行时无法直接设置任意标头,则使用运行时支持的等效项生成其中一个标头(例如,SDK 的客户端应用环境变量); - 自定义工具模式转换——如果你将宿主机工具的 JSON Schema 转换为运行时的工具格式,请递归转换(嵌套对象、数组
items、枚举、描述);仅进行平面顶级转换会静默丢弃模型的结构化指导。如果运行时接受,直接传递 JSON Schema 可以避免此问题; - 技能或发现文件实例化;
- 原生协议到 harness 流/控制转换;
- 生命周期状态模式;
- 桥接协议和诊断。
某些 harness 适配器的结构约定正通过 konsistent CLI 强制执行。
完成后运行 pnpm konsistent 进行检查。在继续之前修复所有标记的违规项。
6. 编写测试
为以下方面添加集中的 Node 测试:
- 工厂元数据和设置;
- 身份验证解析;
- 沙箱使用和路径放置;
- 宿主导远程操作或桥接协议行为;
- 提示/控制事件转换;
- 恢复会话与继续轮次行为;
- 不支持的能力错误;
- 技能实例化(如果支持)。
尽可能使用模拟的沙箱会话和桥接/运行时边界。单元测试中不需要实时提供商凭据。
getBootstrap() 读取已编译的桥接资产(例如 dist/bridge/index.mjs),当测试针对 src 运行时这些资产不存在,因此调用它的测试将遇到 ENOENT。模拟 node:fs/promises 的 readFile 以获取桥接资产路径(请参阅 Codex/OpenCode harness 测试以了解模式)。
7. 添加 README
保持 README 简短:
- 包目的;
- 设置命令;
- 最小
HarnessAgent用法; - 所需的沙箱能力,例如桥接模式运行时的端口;
- 重要的身份验证配置。
链接到主要 harness 文档以获取更广泛的概念。
8. 添加示例
为新 harness 添加相关示例。
- 在
examples/ai-functions/src/harness-agent下添加所需的 API/函数示例,在examples/harness-e2e-next下添加交互式示例。
9. 添加文档
在 content/providers/02-ai-sdk-harnesses/<下一个数字>-<名称>.mdx 中创建文档。
包括:
- 设置说明
- 所需的沙箱能力
- 身份验证配置
- Harness 特定的选项
- 使用示例
- 已知限制
当新的 harness 准备公开时,更新 content/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx 以列出它。
10. 更新引用并验证
从工作区根目录运行:
pnpm konsistent
pnpm update-references
pnpm --filter @ai-sdk/harness-<name> build
pnpm --filter @ai-sdk/harness-<name> test
pnpm type-check:full
使用 pnpm changeset 添加一个变更集。对于全新的 harness 包的首次发布,使用 major(而不是通常的 patch),以匹配其他 harness 包。
尽早在实时沙箱上运行相关的 harness 示例——不要仅依赖单元测试和 type-check。运行时 API 限制(例如,意外的配置选项拒绝、运行时发出的确切流事件名称、Gateway 基础 URL 格式)只有在桥接实际驱动运行时才会显现,而且在文档/示例构建在其之上之前发现这些限制要便宜得多。
清单
- 已在
packages/harness-<name>中创建包结构 -
package.json已配置正确的依赖项 - TypeScript 配置已设置(
tsconfig.json、tsconfig.build.json) - 构建配置(
tsdown.config.ts) - 测试配置(
vitest.node.config.js) - Harness 适配器实现完整
- 运行时放置已处理,无需创建隐藏沙箱
- 桥接资产已在构建时复制(如果使用桥接模式)
- 身份验证解析已实现(如果需要)
- AI Gateway 请求包含
User-Agent和/或x-client-app客户端归因 - Harness 基础设施、技能、桥接代码和密钥已排除在
sessionWorkDir之外 - 会话恢复和轮次继续已测试
- 单元测试已编写且通过
- README.md 已编写
- 已添加
CHANGELOG.md(包标题;konsistent要求) - 已添加变更集(首次发布为
major) - 示例已添加
- 文档已添加到
content/providers/02-ai-sdk-harnesses/ - Harness 适配器列表已更新(如果公开)
- 已针对实时沙箱验证(不仅限于单元测试 / type-check)
- 已运行
pnpm update-references - 包构建通过
- 包测试通过
- 类型检查通过(从根目录运行
pnpm type-check:full) - 相关示例成功运行
相关文档
- Harness 抽象架构
@ai-sdk/harnessREADME- 具有类似运行时放置的现有 Harness 包