使用 @ai-sdk/harness-acp 将 ACP v1 Agent 接入 AI SDK:桥接架构、配置映射与实战
【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai
导读
@ai-sdk/harness-acp是 AI SDK 的 HarnessV1 适配器,它让HarnessAgent能够直接驱动遵循 Agent Client Protocol 为主线,结合该包源码,完整讲解它的桥接运行架构、安装配置、createACP全部核心选项(模型映射、指令映射、权限模式映射、凭据代理等),并给出一个可复制的端到端示例。读完本文,你将能基于任意 NPM 安装的 ACP v1 实现,在自己的沙箱中快速搭建一个 AI Agent Harness。
架构总览:桥接进程、沙箱与 WebSocket
@ai-sdk/harness-acp是一个HarnessV1适配器,其底层是一个通过 NPM 安装的Agent Client Protocol v1 实现(下称"ACP 实现")。整体运行模型可以用一句话概括:适配器在沙箱内托管一个桥接进程,桥接进程在沙箱代理的回环端口上通过 WebSocket 与宿主机通信,而配置的 ACP 实现与桥接进程一同运行在沙箱内部。
具体链路(对应 acp-v1-harness.ts 中doStart的实现)为:
- 宿主侧的
HarnessAgent调用createACP生成的适配器,在沙箱会话中启动桥接脚本bridge.mjs; - 桥接进程监听沙箱暴露的 TCP 端口(
BRIDGE_WS_PORT),并携带一枚BRIDGE_CHANNEL_TOKEN令牌进行鉴权(见 acp-v1-harness.ts); - 宿主通过
SandboxChannel(底层为ws库的 WebSocket 客户端)连接到该端口,按照outboundMessageSchema/inboundMessageSchema(见 acp-v1-bridge-protocol.ts)收发桥接协议消息; - 桥接进程内部加载 ACP v1 实现,将宿主的请求翻译为 ACP JSON-RPC 方法(
session/new、session/start、session/set_mode、session/set_config_option等),并把实现的流式事件翻译回宿主。
因此有两条硬性约束(README 也明确指出):
- 桥接型 ACP harness 要求沙箱至少暴露一个端口(
ports: [4000]或显式配置port)。源码中resolveBridgePort会优先使用portOverride,否则取沙箱ports数组的第一个元素,两者都没有时直接抛出unsupported错误(见 acp-v1-harness.ts)。 - 使用不支持
getPortEndpoint的 basic 沙箱会话时,必须同时显式配置port与portEndpoint(见 acp-v1-harness.ts)。
从源码结构看,桥接层还承担了相当多的职责,包括:ACP 流事件捕获(acp-stream-capture.ts)、Agent stderr 监控(agent-stderr-monitor.ts)、宿主工具 MCP 中继(host-tool-relay.ts、host-tool-mcp.ts)、会话生命周期管理(session-lifecycle.ts)与流翻译(stream-translator.ts)等,这些都位于 bridge 目录 下。
安装与首次启动
安装共需三个包:适配器本身、harness 基座(提供HarnessAgent)以及沙箱实现:
npm i @ai-sdk/harness-acp @ai-sdk/harness @ai-sdk/sandbox-vercel从 package.json 可以看到,@ai-sdk/harness-acp运行时依赖@ai-sdk/harness、@ai-sdk/provider-utils与ws,并将zod作为 peer 依赖(支持^3.25.76 || ^4.1.8),同时要求 Node.js>=22。包内声明了两个workspace:*依赖,说明在 monorepo 环境下与 harness 基座共同演进。
首次会话启动时,桥接进程会在沙箱内部安装配置的 ACP 实现。安装方式由source字段决定(见 implementation.ts):
npm-simple:按包名(可选精确版本)安装,内部生成package.json并执行pnpm install --prod;npm-locked:直接使用你提供的packageJson与pnpmLockYaml(可选pnpmWorkspaceYaml),以--frozen-lockfile锁定安装,保证可复现;install-command:在沙箱内执行自定义安装命令,可执行文件路径固定为home/.local/bin/<executable>,并拥有独立的私有$HOME。
最小可用示例:连接 Codex ACP
README 给出了一个完整示例——把HarnessAgent连接到 Codex ACP,并使用 OpenAI 直连鉴权:
import { HarnessAgent } from '@ai-sdk/harness/agent'; import { createACP } from '@ai-sdk/harness-acp'; import { createCredentialRequestTransformation } from '@ai-sdk/harness/utils'; import { createVercelSandbox } from '@ai-sdk/sandbox-vercel'; const codexACP = createACP({ harnessId: 'acp-codex', source: { type: 'npm-simple', packageName: '@agentclientprotocol/codex-acp', packageVersion: '1.1.4', }, executable: 'codex-acp', modelMapping: { type: 'session-config-option', path: 'model', }, credentialEnv: ['CODEX_API_KEY', 'OPENAI_API_KEY'], credentialBrokering: ({ env, sandboxEnv }) => { const environmentVariableName = env.CODEX_API_KEY ? 'CODEX_API_KEY' : 'OPENAI_API_KEY'; const credential = env[environmentVariableName]; const sandboxCredential = sandboxEnv?.[environmentVariableName]; if (!credential || !sandboxCredential) return []; return [ createCredentialRequestTransformation({ matchUrl: 'https://api.openai.com/v1', matchHeaders: { Authorization: `Bearer ${sandboxCredential}`, }, transformHeaders: { Authorization: `Bearer ${credential}` }, }), ]; }, instructionMapping: { type: 'launch-env-json', variable: 'CODEX_CONFIG', path: ['developer_instructions'], }, permissionModeMapping: { 'allow-reads': null, 'allow-edits': null, 'allow-all': { type: 'session-mode', modeId: 'agent-full-access' }, }, authentication: { methodId: 'api-key', }, }); const agent = new HarnessAgent({ harness: codexACP, sandbox: createVercelSandbox({ runtime: 'node24', ports: [4000], }), }); const session = await agent.createSession(); try { const result = await agent.generate({ session, prompt: 'Inspect this project and summarize its purpose.', }); console.log(result.text); } finally { await session.destroy(); }运行前需在宿主机环境中设置CODEX_API_KEY或OPENAI_API_KEY(二者取其一即可,credentialBrokering回调会按优先级选择)。session.destroy()放在finally中,确保任何情况下会话资源都能被释放。
配置项深度解析
createACP的入参类型为ACPHarnessSettings(见 acp-harness.ts),除上文示例外,下面逐个拆解核心配置。
harnessId 与版本
harnessId(必填):稳定的 kebab-case 标识符(正则^[a-z0-9]+(?:-[a-z0-9]+)*$),用于区分不同 ACP 实现、会话目录与生命周期状态。源码在 acp-v1-harness.ts 定义并在启动前校验。version:默认'v1',目前仅支持'v1',传入其他值会抛出 "Unsupported ACP protocol version"(见 acp-harness.ts)。clientApp:可选,默认{ name: 'ai-sdk/harness-acp', version: VERSION },会在 ACP 客户端身份(client/register)中上报。
实现安装:source 与 executable
source(必填):npm-simple/npm-locked/install-command三种安装源,详见上文"首次启动"一节。注意npm-simple的packageVersion必须是精确语义化版本,且省略版本号时安装latestdist-tag,此时版本不参与实现身份哈希——即上游发布新版本不会使既有生命周期状态失效(见 acp-v1-settings.ts 与 implementation.ts)。executable(必填):裸命令名(不含路径),安装后从node_modules/.bin/(npm 源)或home/.local/bin/(install-command 源)解析。args:可选,启动 ACP 实现时追加的命令行参数。
模型映射:modelMapping(必填)
ACP 实现的模型选择操作各不相同,因此modelMapping是必填项。两种取值(类型定义见 acp-v1-settings.ts,发送逻辑见 bridge/model-mapping.ts):
| 类型 | 用途 | 底层 ACP 方法 |
|---|---|---|
session-config-option | 通过 ACP 配置选项指定模型,path为配置选项 ID | session/set_config_option(configId: path,value: model) |
session-model | 使用传统session/set_model方法,path为 JSON-RPC 请求属性名 | session/set_model |
例如 Codex 使用session-config-option+path: 'model';而 Grok Build 等实现沿用session/set_model,应改用session-model。另外,当HarnessAgent未配置模型时,适配器不会发送任何模型操作(见 model-mapping.ts 的if (model == null) return;)。
指令映射:instructionMapping
当 ACP 实现支持原生 system / developer prompt 时,用instructionMapping把HarnessAgent的 instructions 路由进去。三种映射方式(见 acp-v1-settings.ts 与 bridge/instruction-mapping.ts):
session-meta:把指令写入 ACP 会话请求_meta字段下的指定路径(path为相对_meta的属性路径数组);launch-env-json:实现启动前,把指令合并进某个 JSON 环境变量(variable)的指定路径(如示例中的CODEX_CONFIG→developer_instructions);filesystem:把指令写成一个 markdown 文件,存放在实现有效$HOME下的相对路径。
重要兼容性说明:不配置instructionMapping时,适配器保留向后兼容行为——把指令前置拼接到第一条用户 prompt上。另外session-meta与launch-env-json的路径会做安全校验,禁止空属性名以及__proto__、constructor、prototype等危险段(见 instruction-mapping.ts)。
权限模式映射:permissionModeMapping
把 HarnessAgent 的三种权限模式(allow-reads/allow-edits/allow-all)映射为 ACP 目标(见 acp-v1-settings.ts):
- 值为
null:表示该权限模式不受支持,调用时直接报错; { type: 'session-mode', modeId }:调用 ACPsession/set_mode;{ type: 'session-config-option', configId, value }:调用session/set_config_option(布尔值会附带type: 'boolean')。
Codex 特例:Codex ACP 只支持permissionMode: 'allow-all',因为它更受限的模式会启用 Codex 内部的沙箱(导致嵌套沙箱冲突)。示例中把allow-reads、allow-edits都置为null,仅allow-all映射到agent-full-access模式。若运行时请求了未映射的模式,适配器会抛出带提示的错误(见 bridge/permission-mode.ts)。
凭据与认证
authentication:ACP 认证方式声明,示例使用{ methodId: 'api-key' };类型定义见 acp-auth.ts(ACPClientApp、ACPAuthenticationMode)。credentialEnv与credentialBrokering:二者必须同时配置,否则createACP直接抛错("ACP credentialEnv and credentialBrokering must be configured together.",见 acp-harness.ts)。credentialEnv声明要进入沙箱凭据环境的宿主机环境变量;credentialBrokering是一个接收{ env, sandboxEnv, headers }并返回HarnessV1RequestTransformation[]的回调,用于把真实凭据"代理"给沙箱内发出的匹配请求。- 两种沙箱行为差异(README 明确强调):
- 支持"additive request transformations"(即实现
addRequestTransformations)的沙箱,只收到凭据占位符,真实值仅当发出的出站请求携带预期占位符时才被注入(对应createSandboxCredentialEnvironment+addRequestTransformations的流程,见 acp-v1-harness.ts); - 其他沙箱保留传统行为:直接把凭据值转发给 ACP 进程。
- 支持"additive request transformations"(即实现
credentialForwarding:在凭据进入沙箱进程前做自定义改写;authenticationFiles:在实现启动前于其私有 home 目录下落地认证文件(写入后统一chmod 600保护,见 acp-v1-harness.ts)。
其他可选能力
mcpServers/isMcpToolCall:向 ACP 实现提供 MCP 服务器定义,ai-sdk-harness-tools这一名字被保留给 HarnessAgent 工具,占用会报错(见 acp-harness.ts)。hostToolMcpTransport:harness 自有的、把宿主工具暴露给 ACP 实现的 MCP 服务器传输方式,默认stdio;某些只接受 HTTP/SSE MCP 服务器的实现需设为http(并要求实现声明agentCapabilities.mcpCapabilities.http)。askUserQuestions:把 ACP 实现的原生提问请求桥接为 HarnessAgent 的askUserQuestions内置工具(配置了它,工具集会自动注入该内置工具,见 acp-harness.ts)。outputSchemaMapping:把结构化输出的 JSON Schema 映射到 ACP 会话 prompt_meta下的指定路径(当前仅session-prompt-meta类型)。session.meta:附加到 ACP 会话请求_meta的静态元数据。startupTimeoutMs:桥接启动超时,默认120_000毫秒(见 acp-v1-harness.ts)。mintBridgeToken:自定义沙箱桥接鉴权令牌,默认随机生成 32 字节十六进制串。env/forwardEnv:运行环境字面量 / 按名转发的环境变量;三个来源(forwardEnv、credentialEnv、env)之间的键不允许重叠,否则校验失败(见 implementation.ts)。
Skills 与指令落盘机制
Skills 默认写入 ACP 实现有效$HOME下的.agents/skills目录(常量DEFAULT_ACP_SKILLS_DIRECTORY,见 acp-v1-skills.ts),由实现原生发现。需要换目录时,通过skillsDirectory指定其他相对路径,例如 Claude Code 系的实现可设为.claude/skills。源码同时做了严格的路径与命名校验:
- skill 名称必须为 kebab-case 小写 slug(
^[a-z0-9]+(?:-[a-z0-9]+)*$),不允许./../ 重复名称; - 附带文件路径必须是相对 POSIX 路径,禁止绝对路径、反斜杠与
..穿越,且SKILL.md保留给 skill 定义文件,不能被附带文件占用(见 acp-v1-skills.ts)。
此外,每个会话的桥接状态目录位于~/.ai-sdk/harness-acp/<harnessId>/<sessionId 的 sha256 哈希>(见resolveACPPrivateSessionDirectory),事件日志event-log.ndjson落在此目录,供崩溃恢复(disk-replay/lossy-rerun/cold-restore三种重放策略,见 acp-v1-harness.ts)使用。
生命周期与崩溃恢复
ACP 适配器实现了较完整的会话生命周期管理(源码在 acp-v1-lifecycle.ts 与 bridge/session-lifecycle.ts):
- 冷启动:
createSession()后首次generate()发起session/new+session/start; - 恢复(resume):通过
continueFrom/resumeFrom携带生命周期状态,优先尝试直接重连既有 WebSocket 桥(带lastSeenEventId); - 进程丢失恢复:桥接失败时,根据
event-log.ndjson是否完整可重放,选择disk-replay(基于磁盘事件日志回放)或lossy-rerun(带turnStartConfig与acpSessionId重新跑一轮);冷会话则走cold-restore(见 acp-v1-harness.ts)。
生命周期状态本身也经过 zod schema 校验(acpResumeStateSchema,见 acp-harness.ts),其中包含实现身份(implementationIdentity)、认证画像(authenticationProfile)、桥接坐标(bridge)与恢复/还原标记等字段。恢复前还会校验生命周期兼容性(validateACPLifecycleCompatibility),确保实现身份与认证画像匹配后才允许续跑。
常见问题与使用边界
- 必须暴露端口:
createVercelSandbox({ runtime: 'node24', ports: [4000] })中ports不能省略;basic 沙箱会话则必须同时给port和portEndpoint。 - 凭据配置成对出现:
credentialEnv与credentialBrokering要么都不配,要么一起配,否则启动即抛错。 - Codex 仅支持
allow-all:不要向 Codex 请求allow-reads/allow-edits,其受限模式会启用 Codex 内部沙箱。 modelMapping必填:且选型取决于具体实现(session-config-optionvssession-model);未配置模型时不会发送模型操作。- 环境变量键冲突:同一键不能同时出现在
forwardEnv、credentialEnv、env中的任意两处。 - 指令映射缺省行为:不配
instructionMapping时,指令会前置拼接到首个用户 prompt(向后兼容)。 - Node 版本:包要求 Node.js
>=22(见 package.json)。
小结
@ai-sdk/harness-acp通过"沙箱内桥接 + 回环 WebSocket + 协议翻译"的架构,把任意 ACP v1 实现无缝接入 AI SDK 的HarnessAgent,并借助modelMapping、instructionMapping、permissionModeMapping、credentialBrokering等声明式映射,弥合了不同 Agent 实现之间的协议差异。本文涉及的实现细节均可在 packages/harness-acp/src 下对应源码中验证:入口与配置类型见 acp-harness.ts,v1 运行逻辑见 acp-v1-harness.ts,桥接子模块见 bridge 目录,测试用例见各*.test.ts文件(如 acp-harness.test.ts、acp-v1-lifecycle.test.ts)。如需接入新的 ACP 实现,参照上文示例替换source、executable、modelMapping与permissionModeMapping即可。
【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考