跳转到内容
本站为社区维护的非官方中文翻译,与 Cloudflare, Inc. 无关联。本站不提供账户登录,请勿在任何非官方站点输入您的 Cloudflare 账户信息。官方文档:developers.cloudflare.com

在 sandbox 中运行 Codex

最后更新 查看 MarkdownAgent 设置

在 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。

将 Worker 切换到 Codex

  1. 替换 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 的标志和事件格式可能在版本之间变化。

  2. 在 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 模型。

  3. 在 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() }),
    ]);
  4. 替换 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,由 Outbound entrypoint 添加 gateway token。

    Codex 会从 SSL_CERT_FILE 读取用于拦截 HTTPS 的证书,runner 中的 trustEnv 会设置该变量。

  5. 替换 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 秒后失败。

  6. 部署你的 Worker:

    npx wrangler deploy

    教程中的 AI_GATEWAY_TOKEN secret 会继续保留。正在运行的 container 会保留启动时使用的镜像,因此请为 Codex 使用新的 sandbox 名称。

运行任务

  1. 将仓库克隆到名为 codex-1 的 sandbox:

    curl "$WORKER_URL/sandboxes/codex-1/repository" \
    	--json '{"url": "https://github.com/octocat/Hello-World"}'
  2. 启动任务:

    curl "$WORKER_URL/sandboxes/codex-1/task" \
    	--data "Add a file NOTES.md with a one-sentence summary of this repository. Do not commit."
  3. 检查任务,直到其状态为 succeeded 或 failed:

    curl "$WORKER_URL/sandboxes/codex-1/task"

    succeeded 任务会包含 Codex 写入的最终消息。

  4. 读取更改:

    curl "$WORKER_URL/sandboxes/codex-1/diff"

    diff 会显示新的 NOTES.md 文件。

相关资源

这篇文档对您有帮助吗?