在 Linux sandbox 中运行 Codex CLI ↗,根据提示编辑 GitHub 仓库,然后将其更改作为 diff 读取。Codex 通过 AI Gateway 调用 OpenAI 模型。你的 Worker 会为这些请求添加 gateway token,且 sandbox 只能访问你的 gateway 和 github.com。
- 来自 构建编程 Agent runner 的
sandbox-coding-agent项目。本页会替换镜像、模型,以及 Durable Object 中与 Agent 相关的部分。 - AI Gateway 中的 OpenAI 凭据,可以是 Unified Billing 额度,或作为 provider key 存储的 OpenAI 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 @openai/[email protected] COPY --from=docker.io/cloudflare/sandbox:1.0.1 /usr/local/bin/sandbox-shim /usr/local/bin/sandbox-shim WORKDIR /workspace CMD ["sleep", "infinity"]Codex npm 软件包将其原生二进制文件作为依赖提供,因此无需运行安装脚本即可安装。镜像会固定版本,因为 Codex 的标志和事件格式可能在版本之间变化。
-
在
wrangler.jsonc中,将MODEL设为你的 gateway 可以提供的 OpenAI 模型 ID:{ "vars": { "AI_GATEWAY_ACCOUNT_ID": "<ACCOUNT_ID>", "AI_GATEWAY_ID": "default", "MODEL": "gpt-6-sol", }, }[vars] AI_GATEWAY_ACCOUNT_ID = "<ACCOUNT_ID>" AI_GATEWAY_ID = "default" MODEL = "gpt-6-sol"Codex 以 OpenAI Responses 格式发送请求,因此它只适用于支持 Responses API 的 OpenAI 模型。
-
在
src/sandbox.ts中,将ResultEvent常量替换为 Codex 最终消息的路径及其失败事件的 schema:src/sandbox.tsts const lastMessagePath = `${taskDirectory}/last-message.txt`; const FailureEvent = z.union([ z.object({ type: z.literal("turn.failed"), error: z.object({ message: z.string() }), }), z.object({ type: z.literal("error"), message: z.string() }), ]); -
替换
agentCommand()和agentEnv()方法:src/sandbox.tsts export class AgentSandbox extends DurableObject<Env> { // ... private agentCommand(prompt: string): string[] { const baseUrl = `https://gateway.ai.cloudflare.com/v1/${this.env.AI_GATEWAY_ACCOUNT_ID}/${this.env.AI_GATEWAY_ID}/openai`; return [ "codex", // Run one task without an interactive terminal "exec", // Write one JSON event per line to the events file "--json", // Skip the session files that Codex would otherwise save "--ephemeral", "--dangerously-bypass-approvals-and-sandbox", // Write the final message to a file outside the repository "--output-last-message", lastMessagePath, "--model", this.env.MODEL, // Add a custom provider that sends requests to the OpenAI endpoint of your gateway "--config", 'model_provider="cloudflare-ai-gateway"', "--config", `model_providers.cloudflare-ai-gateway={ name = "Cloudflare AI Gateway", base_url = ${JSON.stringify(baseUrl)}, wire_api = "responses" }`, // Stop the analytics and update requests from Codex "--config", "analytics.enabled=false", "--config", "check_for_update_on_startup=false", // Stop Codex from syncing its plugin marketplace, which clones a // repository from `github.com` on every run "--config", "features.plugins=false", "--", prompt, ]; } private agentEnv(): Record<string, string> { return {}; } }--dangerously-bypass-approvals-and-sandbox会关闭 Codex 的批准提示和内置命令 sandbox。改由 container 限制 Codex 命令可以访问的范围。自定义 provider 不会发送 API key,由
Outboundentrypoint 添加 gateway token。Codex 会从
SSL_CERT_FILE读取用于拦截 HTTPS 的证书,runner 中的trustEnv会设置该变量。 -
替换
outcome()方法:src/sandbox.tsts export class AgentSandbox extends DurableObject<Env> { // ... private async outcome(exitCode: number): Promise<TaskStatus> { if (exitCode === 0) { return { state: "succeeded", result: (await this.readOptionalText(lastMessagePath)) ?? "", }; } let failure: string | undefined; const events = await this.files.readFile(eventsPath); for await (const line of readLines(events.body)) { const parsed = FailureEvent.safeParse(parseJson(line)); if (parsed.success) { failure = parsed.data.type === "error" ? parsed.data.message : parsed.data.error.message; } } if (failure === undefined) { const stderr = (await this.readOptionalText(stderrPath)) ?? ""; return { state: "failed", error: `codex exited with ${exitCode}: ${stderr.slice(-2000)}`, }; } return { state: "failed", error: failure }; } }当其 turn 失败时,Codex 会以非零退出码退出,因此由退出码决定结果。失败的任务会返回最后一个
turn.failed或error事件中的消息。Codex 会对失败的模型请求重试五次,任务大约 20 秒后失败。 -
部署你的 Worker:
npx wrangler deployyarn wrangler deploypnpm wrangler deploy教程中的
AI_GATEWAY_TOKENsecret 会继续保留。正在运行的 container 会保留启动时使用的镜像,因此请为 Codex 使用新的 sandbox 名称。
-
将仓库克隆到名为
codex-1的 sandbox:curl "$WORKER_URL/sandboxes/codex-1/repository" \ --json '{"url": "https://github.com/octocat/Hello-World"}' -
启动任务:
curl "$WORKER_URL/sandboxes/codex-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/codex-1/task"succeeded任务会包含 Codex 写入的最终消息。 -
读取更改:
curl "$WORKER_URL/sandboxes/codex-1/diff"diff 会显示新的
NOTES.md文件。
- 构建编程 Agent runner:runner 如何启动 sandbox、运行任务并读取 diff。
- Codex 示例 ↗:可单独部署、用于运行 Codex 的 Worker。
- AI Gateway 中的 OpenAI Codex:从你自己的终端通过 AI Gateway 路由 Codex。