chore(vendor): import pi 1.3.0

Imported as a linear vendor commit so forge rebases do not replay the upstream root tree.

git-subtree-dir: third_party/pi

git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
This commit is contained in:
2026-10-15 17:58:48 -04:00
parent 719370c8f7
commit 6081e3e325
1743 changed files with 205460 additions and 131705 deletions

View File

@@ -0,0 +1,31 @@
# pi-codemode on Cloudflare
This example has two Workers:
- **`sandbox/`** deploys `CodemodeSandboxDurableObject` as its own Worker, `pi-codemode-sandbox`.
Scripts run there, in a separate isolate.
- **`agent/`** has a `Session` Durable Object that owns the tools. It runs scripts through
`RemoteCodemodeSandbox`, which keeps the tools and results in `Session` while the sandbox Worker
runs the VM.
The [README of the package](../../README.md#usage-on-cloudflare-workers) explains why the
sandbox must be a separate Worker, and which limits apply.
## Try it locally
Install `@earendil-works/pi-codemode` and `wrangler` in each directory, then start both Workers in
one process:
```sh
npx wrangler dev -c agent/wrangler.jsonc -c sandbox/wrangler.jsonc
curl -X POST http://localhost:8787 --data 'const n = await tools.count({ add: 2 }); text("n=" + n); return n'
```
## Deploy
Deploy the sandbox first, because the agent's binding refers to it by name:
```sh
(cd sandbox && npx wrangler deploy)
(cd agent && npx wrangler deploy)
```

View File

@@ -0,0 +1,50 @@
// A Worker whose Durable Object runs codemode the way an agent session would: tools run here, in the
// session object, and scripts run in the separately deployed pi-codemode-sandbox Worker.
//
// curl -X POST http://localhost:8787 --data 'const n = await tools.count({ add: 2 }); text("n=" + n); return n'
import type { CodemodeSandboxDurableObject } from "@earendil-works/pi-codemode/cloudflare";
// The portable entry has no QuickJS module: the WebAssembly lives only in the sandbox Worker.
import { type CodemodeTool, RemoteCodemodeSandbox } from "@earendil-works/pi-codemode/portable";
import { DurableObject } from "cloudflare:workers";
interface Env {
SESSION: DurableObjectNamespace<Session>;
CODEMODE_SANDBOX: DurableObjectNamespace<CodemodeSandboxDurableObject>;
}
export class Session extends DurableObject<Env> {
private counter = 0;
async runCodemode(code: string) {
const tools: CodemodeTool[] = [
{
name: "count",
description: "Adds to a counter kept in the session and returns the new value",
execute: (args) => {
this.counter += (args as { add: number }).add;
return this.counter;
},
},
];
// Before: new CodemodeSandbox({ tools, timeoutMs: 60_000 })
const sandbox = new RemoteCodemodeSandbox({
tools,
timeoutMs: 60_000,
// One sandbox instance per session keeps a session's scripts together and apart from others.
remote: () => this.env.CODEMODE_SANDBOX.getByName(this.ctx.id.toString()),
});
try {
return await sandbox.execute(code);
} finally {
await sandbox.close();
}
}
}
export default {
async fetch(request, env) {
if (request.method !== "POST") return new Response("POST a script\n", { status: 405 });
const result = await env.SESSION.getByName("demo").runCodemode(await request.text());
return Response.json(result);
},
} satisfies ExportedHandler<Env>;

View File

@@ -0,0 +1,15 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "pi-codemode-agent-example",
"main": "src/index.ts",
"compatibility_date": "2026-04-22",
"durable_objects": {
"bindings": [
{ "name": "SESSION", "class_name": "Session" },
// The sandbox class lives in its own Worker. `script_name` is what puts scripts in a different
// isolate; binding a class exported from this Worker would run them on the session's thread.
{ "name": "CODEMODE_SANDBOX", "class_name": "CodemodeSandbox", "script_name": "pi-codemode-sandbox" }
]
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["Session"] }]
}

View File

@@ -0,0 +1,7 @@
// The whole sandbox Worker: one Durable Object class that runs codemode scripts for callers bound to
// it. Only Workers with a binding can reach the class; HTTP requests get a 404.
export { CodemodeSandboxDurableObject as CodemodeSandbox } from "@earendil-works/pi-codemode/cloudflare";
export default {
fetch: () => new Response("Not found", { status: 404 }),
};

View File

@@ -0,0 +1,13 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "pi-codemode-sandbox",
"main": "src/index.ts",
"compatibility_date": "2026-04-22",
// Reached only through Durable Object bindings, never over HTTP.
"workers_dev": false,
"preview_urls": false,
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["CodemodeSandbox"] }],
// Optional. The default is 30 seconds of CPU per request; one execution is one request. Keep it above
// the time the interrupt budget allows, so scripts fail with a timeout instead of resetting the object.
"limits": { "cpu_ms": 60000 }
}