在 Linux sandbox 中运行 Pi ↗ 编程 Agent,根据提示编辑 GitHub 仓库,然后将其更改作为 diff 读取。Pi 通过内置 provider 经 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 @earendil-works/[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"]镜像会固定版本,因为 Pi 的标志和事件格式可能在版本之间变化。
-
在
wrangler.jsonc中,将MODEL设为 Pi 中cloudflare-ai-gatewayprovider 的模型 ID:{ "vars": { "AI_GATEWAY_ACCOUNT_ID": "<ACCOUNT_ID>", "AI_GATEWAY_ID": "default", "MODEL": "claude-sonnet-5", }, }[vars] AI_GATEWAY_ACCOUNT_ID = "<ACCOUNT_ID>" AI_GATEWAY_ID = "default" MODEL = "claude-sonnet-5"请使用 Anthropic 等 provider 的模型。对于其目录中的大多数 Workers AI 模型,Pi 0.87.1 会请求与上下文窗口一样多的输出 token。Workers AI 会拒绝这些请求。
-
在
src/sandbox.ts中,将ResultEvent常量替换为报告结果的 Pi 事件 schema:src/sandbox.tsts const PiEvent = z.union([ z.object({ type: z.literal("agent_settled") }), z.object({ type: z.literal("message_end"), message: z.object({ role: z.literal("assistant"), content: z.array( z.object({ type: z.string(), text: z.string().optional() }), ), stopReason: z.string(), errorMessage: z.string().optional(), }), }), ]); type PiMessage = Extract< z.infer<typeof PiEvent>, { type: "message_end" } >["message"]; -
替换
agentCommand()和agentEnv()方法:src/sandbox.tsts export class AgentSandbox extends DurableObject<Env> { // ... private agentCommand(prompt: string): string[] { return [ "pi", // Run one task, write one JSON event per line to the events file, and exit "--mode", "json", // Keep the session in memory instead of saving it to disk "--no-session", // Select the built-in AI Gateway provider in Pi, which reads the account // and gateway IDs from the environment "--provider", "cloudflare-ai-gateway", // Select the model from `MODEL` "--model", this.env.MODEL, "--", prompt, ]; } private agentEnv(): Record<string, string> { return { CLOUDFLARE_API_KEY: "provided-by-worker", CLOUDFLARE_ACCOUNT_ID: this.env.AI_GATEWAY_ACCOUNT_ID, CLOUDFLARE_GATEWAY_ID: this.env.AI_GATEWAY_ID, PI_OFFLINE: "1", PI_SKIP_VERSION_CHECK: "1", PI_TELEMETRY: "0", }; } }Pi 运行工具时不会出现批准提示。container 会限制 Pi 命令可以访问的范围。
没有 API key 时 Pi 不会启动,因此
CLOUDFLARE_API_KEY是占位符。Pi 会在cf-aig-authorizationheader 中发送该 key,Outboundentrypoint 会用 gateway token 替换它。PI_OFFLINE、PI_SKIP_VERSION_CHECK和PI_TELEMETRY会关闭 Pi 中的模型目录刷新、版本检查和遥测。Pi 仍会从仓库加载
AGENTS.md和CLAUDE.md文件。没有--approve标志时,它会跳过仓库.pi目录中受保护的项目资源。对于你不信任的仓库,请不要加上--approve。 -
替换
outcome()方法:src/sandbox.tsts export class AgentSandbox extends DurableObject<Env> { // ... private async outcome(exitCode: number): Promise<TaskStatus> { if (exitCode !== 0) { const stderr = (await this.readOptionalText(stderrPath)) ?? ""; return { state: "failed", error: `pi exited with ${exitCode}: ${stderr.slice(-2000)}`, }; } let settled = false; let final: PiMessage | undefined; const events = await this.files.readFile(eventsPath); for await (const line of readLines(events.body)) { const parsed = PiEvent.safeParse(parseJson(line)); if (!parsed.success) { continue; } if (parsed.data.type === "agent_settled") { settled = true; } else { final = parsed.data.message; } } if (!settled || final === undefined) { return { state: "failed", error: "pi exited before the agent settled" }; } if (final.stopReason === "error" || final.stopReason === "aborted") { return { state: "failed", error: final.errorMessage ?? `pi stopped: ${final.stopReason}`, }; } const text = final.content.flatMap((block) => block.type === "text" ? [block.text ?? ""] : [], ); return { state: "succeeded", result: text.join("") }; } }即使模型请求失败,Pi 也会以成功状态退出,因此结果来自其事件。当 Pi 发送
agent_settled事件,且其最后一条 assistant 消息的stopReason不是error或aborted时,任务成功。否则,任务会以 Pi 的错误消息失败。 -
部署你的 Worker:
npx wrangler deployyarn wrangler deploypnpm wrangler deploy教程中的
AI_GATEWAY_TOKENsecret 会继续保留。正在运行的 container 会保留启动时使用的镜像,因此请为 Pi 使用新的 sandbox 名称。
-
将仓库克隆到名为
pi-1的 sandbox:curl "$WORKER_URL/sandboxes/pi-1/repository" \ --json '{"url": "https://github.com/octocat/Hello-World"}' -
启动任务:
curl "$WORKER_URL/sandboxes/pi-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/pi-1/task"succeeded任务会包含 Pi 最后一条回复的文本。 -
读取更改:
curl "$WORKER_URL/sandboxes/pi-1/diff"diff 会显示新的
NOTES.md文件。
- 构建编程 Agent runner:runner 如何启动 sandbox、运行任务并读取 diff。
- Pi 示例 ↗:可单独部署、用于运行 Pi 的 Worker。
- AI Gateway 中的 Pi:从你自己的终端通过 AI Gateway 路由 Pi。