OpenHands Agent Canvas:容器化 Agent Server 驱动 ACP 代理(Codex / Claude Code / Gemini CLI)实战指南
2026/9/14 23:19:37 网站建设 项目流程

OpenHands Agent Canvas:容器化 Agent Server 驱动 ACP 代理(Codex / Claude Code / Gemini CLI)实战指南

【免费下载链接】OpenHands🙌 OpenHands: AI-Driven Development项目地址: https://gitcode.com/GitHub_Trending/ope/OpenHands

本文基于仓库中的 examples/acp-docker 快速上手文档 展开,讲解如何在本地用 Docker Compose 拉起一个容器化的 OpenHands Agent Server,让 Agent Canvas 在没有任何宿主机登录态的情况下驱动 Codex、Claude Code、Gemini CLI 这三个 ACP 代理:读完你将掌握镜像版本固定(pin)机制、Canvas 指针对接方式、通过 UI 注入凭据的完整流程,以及各凭据字段背后的 LookupSecret 解析原理与常见陷阱。

1. 背景:为什么 ACP 代理需要“容器化 + UI 凭据”两条腿

Agent Canvas 可以用内置的 OpenHands 代理驱动会话,也可以通过Agent Client Protocol(ACP)驱动外部代理:Agent Server 不直接调 LLM,而是把代理自带的 CLI(如claude-agent-acpcodex-acpgemini-cli --acp)作为子进程 spawn 出来,在 stdio 上以 JSON-RPC 转发每一轮对话。完整原理见 docs/ACP_AGENTS.md。

本地后端跑在开发者自己的机器上时,代理 CLI 可以直接复用宿主机上已登录的订阅态(macOS Keychain、~/.codex/auth.json等)。但容器是全新环境,没有任何宿主登录态,所以凭据必须从你这里来:要么在 Canvas 的 onboarding “Set up credentials” 步骤里填写(推荐),要么通过.env烘焙进容器(适合非交互 / CI 场景)。examples/acp-docker/就是这个本地 Docker 路径的现成脚手架,与云部署路径互为对应。

examples/acp-docker/目录包含三个文件:

  • docker-compose.yml —— 服务编排;
  • .env.example —— 可选的凭据/镜像模板;
  • README.md —— 本文对应的快速上手说明。

2. 第一步:拉起容器化的 agent-server

2.1 零配置路径

cd examples/acp-docker docker compose up

这会在http://localhost:8010启动ghcr.io/openhands/agent-server:latest-python,并挂载一个持久化acp-data卷。该镜像预装了 ACP CLI wrapper(claude-agent-acp/codex-acp/gemini),SDK 会在容器内把 Canvas 默认下发的npx -y <pkg>命令改写到这些预装固定版本二进制上,因此 Canvas 侧无需改动任何启动命令。

2.2 compose 文件的逐项解读

docker-compose.yml 中的关键配置值得逐项理解:

配置取值说明
image${AGENT_SERVER_IMAGE:-ghcr.io/openhands/agent-server:latest-python}默认回落latest-python(始终不低于 Canvas 兼容下限);可被.env中的AGENT_SERVER_IMAGE覆盖为固定版本
container_nameoh-acp固定容器名,便于docker logs oh-acp排查
ports8010:8000宿主机 8010 映射到容器内 agent-server 的 8000 端口,Canvas 把VITE_BACKEND_BASE_URL指向 8010
environment.OH_EXTRA_PYTHON_PATH=/canvas-tools把仓库 tools/ 目录以只读卷挂到/canvas-tools并加入 Python 路径:新会话用client_tools定义canvas_ui_control,但旧版本持久化的会话仍需要导入旧的 Python 模块才能恢复CanvasUIAction/CanvasUIObservation事件
environment.OH_SECRET_KEY注释掉的可选项不设置时 ACP 会话也能工作(Canvas 把 ACP 凭据作为回环 LookupSecret 从 agent-server 自身的 secret store 解析,且不标记secrets_encrypted);设置它用于 (a) 跨容器重启持久化已保存的 secrets,(b) 启用非 ACP 的 OpenHands 会话所用的加密设置路径。可用python -c "import secrets;print(secrets.token_urlsafe(32))"生成
environment.ANTHROPIC_API_KEY可选透传ANTHROPIC_API_KEYCLAUDE_CODE_OAUTH_TOKENOPENAI_API_KEYGEMINI_API_KEYGOOGLE_CLOUD_PROJECTGOOGLE_CLOUD_LOCATIONGOOGLE_GENAI_USE_VERTEXAI均从你的 shell /.env透传,未设置的不会导出——仅供“烘焙凭据”场景
volumes.acp-dataacp-data:/workspace持久化两类状态:会话历史,以及 SDK 落盘的凭据文件(Codex 的auth.json位于CODEX_HOME下、Gemini 的 ADC/SA JSON)
volumes.tools../../tools:/canvas-tools:ro只读挂载,相对 compose 文件位置解析
restartunless-stopped守护式重启策略

镜像的 CORS 已允许localhost来源,所以浏览器可以直连容器,不需要任何额外代理配置。

2.3 可复现的固定镜像路径(推荐)

latest-python适合尝鲜,但团队协作用固定版本更稳妥。仓库把版本固定收敛到单一事实源 config/defaults.json:其中versions.agentServer当前为1.44.0,兼容下限compatibility.minimumAgentServer1.28.0,镜像仓库地址在images.agentServer

在仓库根目录执行:

npm run example:acp-docker:env # 写入 examples/acp-docker/.env cd examples/acp-docker && docker compose up

该 npm script 对应 scripts/gen-acp-docker-env.mjs,其工作方式是:

  1. 读取config/defaults.json,由computeAgentServerImage()拼出ghcr.io/openhands/agent-server:<versions.agentServer>-python这样的完整 tag;
  2. upsertEnvLine()AGENT_SERVER_IMAGE=...一行幂等地写入/替换进examples/acp-docker/.env——已有该行就原位替换,没有就追加,其余行(如你手填的凭据)原样保留。

这条链路有专门的漂移检测测试tests/scripts/acp-docker-env-sync.test.ts,它强制三条不变量:生成的 pin 必须等于versions.agentServer;无配置 compose 回落必须保持latest-python;固定 tag 不得低于compatibility.minimumAgentServer(否则会渲染 “Disconnected”)。

版本兼容性提示:如果你手里有一份早年手写的.env且包含旧的AGENT_SERVER_IMAGE,请重跑npm run example:acp-docker:env或删掉该覆盖项,避免示例一直钉在compatibility.minimumAgentServer之下。

想手工钉一个更新的发布版或 main 分支构建,也可以直接:

AGENT_SERVER_IMAGE=ghcr.io/openhands/agent-server:$(gh api repos/OpenHands/software-agent-sdk/commits/main --jq '.sha[0:7]')-python docker compose up

2.4 可选:把凭据烘焙进.env

如果不想在 Canvas 里填凭据(例如非交互 / CI 环境),先复制模板:

cd examples/acp-docker && cp .env.example .env

然后在 .env.example 中填入对应 provider 的环境变量;compose 文件只会透传“有值”的变量。注意:推荐路径仍然是 Canvas 内填写(凭据随 start 请求以 secrets 形式下发),.env烘焙是替代方案,且可能过不了 onboarding 登录探测——这一点见下文第 5 节的警告。

3. 第二步:让 Canvas 指向容器

回到仓库根目录:

cd ../.. # repo root VITE_BACKEND_BASE_URL=http://localhost:8010 npm run dev:frontend

由于镜像 CORS 允许localhost,浏览器会直接和容器通信。也可以在 Canvas 的后端选择器(backend selector)里把它添加为一个 backend,host 填http://localhost:8010,效果等价。

4. 第三步:在 UI 中完成凭据注入

在 onboarding 里选择 ACP provider,进入Set up credentials步骤。在容器化后端上这一步是必填的(没有宿主登录可回退)。需要粘贴的内容按 provider 如下:

Provider需要粘贴/填写的内容
Codex(订阅)CODEX_AUTH_JSON——~/.codex/auth.json的完整内容
Claude Code(订阅)CLAUDE_CODE_OAUTH_TOKEN—— 你的 Pro/Max OAuth token
Gemini CLI(Vertex)GOOGLE_APPLICATION_CREDENTIALS_JSON(SA / ADC JSON)+GOOGLE_CLOUD_PROJECT+GOOGLE_CLOUD_LOCATION+GOOGLE_GENAI_USE_VERTEXAI=true

每个 provider 也接受 API key 路径(OPENAI_API_KEY/ANTHROPIC_API_KEY/GEMINI_API_KEY)。

4.1 凭据字段的来源:getAcpProviderSecrets

上述字段清单不是文档里的口头约定,而是由 Canvas 源码 src/constants/acp-providers.ts 单一来源生成的:

  • ACP_RESERVED_CREDENTIALS定义了每个 provider 的“容器凭据”:Codex 的CODEX_AUTH_JSON(多行文本,提示粘贴~/.codex/auth.json)、Claude Code 的CLAUDE_CODE_OAUTH_TOKEN、Gemini 的GOOGLE_APPLICATION_CREDENTIALS_JSON(多行,提示粘贴~/.config/gcloud/application_default_credentials.json)加三个 GCP 标量字段;
  • getAcpProviderSecrets()按“容器订阅/Vertex 凭据 → API key → 可选 base URL”的顺序拼装字段列表,其中 API key / base URL 的变量名直接取自 SDK 注册表(经@openhands/typescript-client镜像),避免前端与 agent-server 环境变量漂移;
  • 字段name同时就是全局 secret 名和 agent-server 导出到 ACP 子进程的环境变量名——保持同名正是已保存密钥能真正到达 provider CLI 的关键。onboarding 表单的状态逻辑由 src/hooks/use-acp-credential-form.ts 复用(含已存 secret 查询、冲突检测与保存流程)。

4.2 从 UI 到子进程:LookupSecret 解析链

每个凭据被存为 agent-server secret store 里的全局 secret(与 Settings → Secrets 里添加完全等价,可随时编辑/删除)。会话启动请求并不携带明文凭据,而是把每个字段引用为一个LookupSecret(ACP 与非 ACP 会话统一如此);agent-server 在 spawn 子进程时从自己的 store 回查取值。从源码注释与文档交叉印证看,ACP 场景下该回查特意运行在事件循环之外(对应 software-agent-sdk 的 #3510 修复),避免回环 HTTP 请求自死锁。

拿到值之后,SDK 的acp_file_secrets默认行为负责“落地”:

  • CODEX_AUTH_JSON被还原为CODEX_HOME下的auth.json,并把 Codex 指向它;
  • GOOGLE_APPLICATION_CREDENTIALS_JSON被写成一个文件,由GOOGLE_APPLICATION_CREDENTIALS指向,路由 Gemini 走 Vertex AI;
  • 其余值(CLAUDE_CODE_OAUTH_TOKEN、project/location、各 API key)直接作为环境变量导出给 CLI。

也就是说Canvas 只负责发送 secrets,文件物化完全由 SDK 完成——这也是为什么acp-data卷要把/workspace持久化:物化出来的凭据文件重启后仍在。

4.3 凭据冲突警告

表单内置了冲突检测(ACP_CREDENTIAL_CONFLICTS/getAcpCredentialConflicts,同样位于 src/constants/acp-providers.ts):不要与 Claude OAuth token 一起设置ANTHROPIC_BASE_URL。被继承的 LiteLLM base URL 会悄悄破坏 bearer 认证——Canvas 从不替你设置 base URL,但你自己保存过ANTHROPIC_BASE_URLsecret 会随每个 start 请求搭便车,所以表单会对这一对组合发出警告。同理,CLAUDE_CODE_OAUTH_TOKENANTHROPIC_API_KEY同时存在时 token 会静默压过 key,SDK 侧也会剥离冲突项(对应 SDK 的_ENV_CONFLICT_MAP,#3588)。

5. 三个必须知道的警告

(1)Gemini Vertex 的 ADC 必须新鲜。复制 ADC 前执行gcloud auth application-default login——过期 token 会返回invalid_rapt,这是凭据问题而非 Canvas bug。另外按 docs/ACP_AGENTS.md 的说明,Gemini 请选非 flash 模型:gemini-cli 0.45.x 会在生成时把任意*-flash模型 id 重新解析为“当前默认 flash”,导致固定gemini-2.5-flash实际跑了不存在的 flash 模型而 404,因此 Canvas 对 Gemini 预选gemini-2.5-pro(源码中的ACP_VERTEX_SAFE_MODEL常量,src/constants/acp-providers.ts)。若某轮 Gemini 报错Publisher Model … was not found,先检查所选模型是否碰巧是 flash id。

(2).env烘焙的凭据可能过不了 onboarding 门禁。登录探测检查的是 CLI 的登录状态(claude auth status/codex login status/ Gemini 的 OAuth 凭据文件),而不是容器环境变量——只有GEMINI_API_KEY烘焙进.env的容器通常仍会被探测为“未登录”,凭据步骤会卡住 “Next”。在 UI 里(重新)填写一次凭据即可继续;烘焙的环境变量对 agent 本身依然生效。

(3)同容器并发会话共享 HOME。同一 provider 的并发会话共用 HOME,可能在 CLI 的 auth/config/lock 文件上互相竞态。SDK 已支持按会话隔离数据目录(acp_isolate_data_dir,#3492),但当前发布的@openhands/typescript-client尚未在ACPAgentSettings上暴露该字段,Canvas 无法安全下发(跟踪于 agent-canvas#1019)。本地临时避免办法是错峰使用同一 provider 的多个会话。

6. 收尾与拆除

docker compose down # 保留 volume(会话 + 物化凭据) docker compose down -v # 同时删除 credentials/conversations

down不带-vacp-data卷保留,SDK 物化的凭据文件与会话历史在下次up后原样可用;带-v则是彻底清场,等价于一个全新的容器环境,onboarding 凭据步骤需要重新走一遍。

7. 小结:这个示例沉淀的工程实践

examples/acp-docker/的价值不只是“能跑”,它还示范了几条可复用的工程约定:

  1. 双轨版本策略:零配置走latest-python(永远满足兼容下限),可复现走config/defaults.jsonnpm run example:acp-docker:env生成的精确 tag,两条轨都由 acp-docker-env-sync 测试 锁住不漂移;
  2. 凭据不落镜像、不落 Canvas 前端:一律经 agent-server 的 secret store +LookupSecret在 spawn 时解析,文件物化交给 SDK 的acp_file_secrets
  3. 持久化边界清晰acp-data卷同时承载会话与物化凭据,/canvas-tools只读挂载只负责旧会话的可恢复性,职责互不干扰。

按 examples/acp-docker/README.md 的三步(docker compose upVITE_BACKEND_BASE_URL=http://localhost:8010 npm run dev:frontend→ UI 填凭据)即可在本地跑通完整的容器化 ACP 链路;更深入的 ACP 概念、认证优先级(订阅登录优先于 API key)与后续切换 agent/模型的说明,可继续阅读 docs/ACP_AGENTS.md。

【免费下载链接】OpenHands🙌 OpenHands: AI-Driven Development项目地址: https://gitcode.com/GitHub_Trending/ope/OpenHands

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询