在 Linux sandbox 中运行 OpenCode ↗,根据提示编辑 GitHub 仓库,然后将其更改作为 diff 读取。OpenCode 通过 AI Gateway 调用模型。你的 Worker 会为这些请求添加 gateway token,且 sandbox 只能访问你的 gateway 和 github.com。
- 来自 构建编程 Agent runner 的
sandbox-coding-agent项目。本页会替换镜像、模型,以及 Durable Object 中与 Agent 相关的部分。 - AI Gateway 中的 Anthropic 凭据,可以是 Unified Billing 额度,或作为 provider key 存储的 Anthropic API key。
-
替换
Dockerfile。只有高亮行与教程中的Dockerfile不同:Dockerfiledockerfile FROM node:24-trixie-slim RUN apt-get update \ && apt-get install -y --no-install-recommends bash ca-certificates git ripgrep \ && rm -rf /var/lib/apt/lists/* RUN npm install --global --ignore-scripts [email protected] \ && ln -s /usr/local/lib/node_modules/opencode-linux-x64-baseline/bin/opencode /usr/local/bin/opencode COPY --from=docker.io/cloudflare/sandbox:1.0.1 /usr/local/bin/sandbox-shim /usr/local/bin/sandbox-shim WORKDIR /workspace CMD ["sleep", "infinity"]镜像会直接安装 baseline x64 OpenCode 二进制文件。baseline 二进制文件可在每台 x64 CPU 上运行。
opencode-ai软件包会为构建镜像的机器 CPU 选择二进制文件。在其他架构上构建的amd64镜像会在模拟器下运行,此时软件包可能选错二进制文件。OpenCode 使用镜像中的
rg搜索文件。没有ripgrep时,它会尝试从 GitHub 下载rg。镜像会固定 OpenCode 版本,因为 OpenCode 的模型列表和事件格式可能在版本之间变化。 -
在
wrangler.jsonc中,将MODEL设为 OpenCode 用于 AI Gateway 的模型 ID:{ "vars": { "AI_GATEWAY_ACCOUNT_ID": "<ACCOUNT_ID>", "AI_GATEWAY_ID": "default", "MODEL": "cloudflare-ai-gateway/anthropic/claude-sonnet-5", }, }[vars] AI_GATEWAY_ACCOUNT_ID = "<ACCOUNT_ID>" AI_GATEWAY_ID = "default" MODEL = "cloudflare-ai-gateway/anthropic/claude-sonnet-5"OpenCode 用于 AI Gateway 的模型 ID 形式为
cloudflare-ai-gateway/<PROVIDER>/<MODEL>。OpenCode 使用固定版本内置的模型列表。要打印该列表,请使用相同的 OpenCode 版本运行opencode models cloudflare-ai-gateway。 -
在
src/sandbox.ts中,将ResultEvent常量替换为承载回复和错误的 OpenCode 事件 schema:src/sandbox.tsts const OpencodeEvent = z.union([ z.object({ type: z.literal("text"), part: z.object({ text: z.string() }) }), z.object({ type: z.literal("error"), error: z.object({ name: z.string(), data: z.object({ message: z.string().optional() }).optional(), }), }), ]); -
替换
agentCommand()和agentEnv()方法:src/sandbox.tsts export class AgentSandbox extends DurableObject<Env> { // ... private agentCommand(prompt: string): string[] { return [ "opencode", // Run one task without an interactive terminal "run", // Write one JSON event per line to the events file "--format", "json", "--auto", // Select the AI Gateway model from `MODEL` "--model", this.env.MODEL, "--", prompt, ]; } private agentEnv(): Record<string, string> { return { CLOUDFLARE_API_TOKEN: "provided-by-worker", CLOUDFLARE_ACCOUNT_ID: this.env.AI_GATEWAY_ACCOUNT_ID, CLOUDFLARE_GATEWAY_ID: this.env.AI_GATEWAY_ID, OPENCODE_DISABLE_MODELS_FETCH: "1", OPENCODE_DISABLE_AUTOUPDATE: "1", OPENCODE_DISABLE_DEFAULT_PLUGINS: "1", OPENCODE_DISABLE_LSP_DOWNLOAD: "1", OPENCODE_DISABLE_SHARE: "1", }; } }--auto会批准 OpenCode 配置未拒绝的每个工具调用。没有它时,OpenCode 会在没有终端的运行中拒绝权限请求。container 会限制 OpenCode 命令可以访问的范围。OpenCode 内置的
cloudflare-ai-gatewayprovider 会从环境读取账户和 gateway ID。对于 Anthropic 模型,它会将请求发送到 gateway 的通用端点:不带后缀的 gateway 路径。runner 中的Outboundentrypoint 允许该路径及其子路径。CLOUDFLARE_API_TOKEN是占位符,因为没有 token 时 OpenCode 不会启动。Outboundentrypoint 会用 gateway token 替换 token header。OPENCODE_DISABLE_*变量会让 OpenCode 使用二进制内置的模型列表,并关闭其更新检查、默认插件、语言服务器下载和分享功能。OpenCode 仍会在后台尝试从 npm 安装@opencode-ai/plugin。该请求会从Outboundentrypoint 收到403响应,OpenCode 会继续运行。 -
替换
outcome()方法:src/sandbox.tsts export class AgentSandbox extends DurableObject<Env> { // ... private async outcome(exitCode: number): Promise<TaskStatus> { let text = ""; let error: string | undefined; const events = await this.files.readFile(eventsPath); for await (const line of readLines(events.body)) { const parsed = OpencodeEvent.safeParse(parseJson(line)); if (!parsed.success) { continue; } if (parsed.data.type === "text") { text = parsed.data.part.text; } else { error = parsed.data.error.data?.message ?? parsed.data.error.name; } } if (exitCode === 0) { return { state: "succeeded", result: text }; } if (error === undefined) { const stderr = (await this.readOptionalText(stderrPath)) ?? ""; return { state: "failed", error: `opencode exited with ${exitCode}: ${stderr.slice(-2000)}`, }; } return { state: "failed", error }; } }出现任何模型或 session 错误时,OpenCode 都会以非零退出码退出,因此由退出码决定结果。成功的任务会返回最后一个
text事件。失败的任务会返回最后一个error事件中的消息,例如 gateway 对拒绝的 token 或缺少 Unified Billing 额度返回的消息。 -
部署你的 Worker:
npx wrangler deployyarn wrangler deploypnpm wrangler deploy教程中的
AI_GATEWAY_TOKENsecret 会继续保留。正在运行的 container 会保留启动时使用的镜像,因此请为 OpenCode 使用新的 sandbox 名称。
-
将仓库克隆到名为
opencode-1的 sandbox:curl "$WORKER_URL/sandboxes/opencode-1/repository" \ --json '{"url": "https://github.com/octocat/Hello-World"}' -
启动任务:
curl "$WORKER_URL/sandboxes/opencode-1/task" \ --data "Add a file NOTES.md with a one-sentence summary of this repository. Do not commit." -
检查任务,直到其状态为
succeeded或failed:curl "$WORKER_URL/sandboxes/opencode-1/task"succeeded任务会包含 OpenCode 的最后一条文本回复。 -
读取更改:
curl "$WORKER_URL/sandboxes/opencode-1/diff"diff 会显示新的
NOTES.md文件。
- 构建编程 Agent runner:runner 如何启动 sandbox、运行任务并读取 diff。
- OpenCode 示例 ↗:可单独部署、用于运行 OpenCode 的 Worker。
- OpenCode 文档 ↗