From 330a701cbaccfa5e797588f64180051ef7317b4f Mon Sep 17 00:00:00 2001 From: Ben Reitz Date: Wed, 4 Nov 2026 12:06:23 +0000 Subject: [PATCH] computer: Rename WorkerJavaScriptBackend to CodemodeBackend The backend that runs ECMAScript modules in a Dynamic Worker is the home of codemode modules and the `codemode:*` host modules, but its name described where it runs rather than what it is for. Rename it to `CodemodeBackend`, its options to `CodemodeBackendOptions`, and its import path to `@cloudflare/computer/backends/codemode`, with no alias for the old names. The source folder, the example, the docs and the tests follow. The backend's default id and its `type` change from `"worker-javascript"` to `"codemode"`, so the exec tool now offers it to the model as `"codemode"`. Runs are saved under the id of the backend that started them, and a backend only looks at its own, so a backend under the new id leaves runs saved under the old one alone: it doesn't resume them, list their waiting calls or find them by id. Passing `id: "worker-javascript"` keeps them reachable. The changeset describes both upgrade paths. The example's Worker and R2 bucket are renamed to `computer-codemode-example` and `computer-codemode-hello`. --- .changeset/codemode-backend.md | 13 ++ .changeset/exec-metadata.md | 2 +- .github/workflows/ci.yml | 6 +- README.md | 6 +- docs/05_runtime_interface.md | 10 +- docs/09_tool_interface.md | 8 +- docs/10_project_layout.md | 6 +- docs/12_worker_backend.md | 2 +- docs/16_code_execution.md | 20 +- docs/17_isolate_javascript.md | 38 +-- docs/18_runtime_migration.md | 8 +- docs/README.md | 2 +- examples/celld/README.md | 2 +- .../celld/src/celld-javascript-backend.ts | 2 +- .../.gitignore | 0 .../{worker-javascript => codemode}/README.md | 24 +- .../package.json | 8 +- .../seed/data/hello.txt | 0 .../src/index.ts | 8 +- .../src/skills/exec/SKILL.md | 0 .../tsconfig.json | 0 .../wrangler.jsonc | 10 +- examples/egress/README.md | 4 +- examples/egress/src/index.ts | 12 +- examples/rlm/README.md | 6 +- examples/rlm/worker/executor-agent.ts | 4 +- examples/rlm/worker/rlm-agent.ts | 4 +- package-lock.json | 30 +-- packages/computer/README.md | 33 +-- packages/computer/package.json | 6 +- .../codemode.test.ts} | 219 +++++++++--------- .../codemode.ts} | 48 ++-- .../durable-runtime.test.ts | 0 .../durable-runtime.ts | 0 .../frames.test.ts | 0 .../{worker-javascript => codemode}/frames.ts | 10 +- .../computer/src/backends/codemode/index.ts | 9 + .../journal.test.ts | 0 .../journal.ts | 2 +- .../module-graph.ts | 2 +- .../src/backends/worker-javascript/index.ts | 9 - packages/computer/src/client.test.ts | 14 +- packages/computer/src/modules/browser.test.ts | 2 +- .../computer/src/modules/container.test.ts | 4 +- packages/computer/src/modules/container.ts | 8 +- packages/computer/src/modules/git.ts | 4 +- .../src/runtime/bridge-codemode.test.ts | 2 +- packages/computer/src/runtime/runtime.test.ts | 6 +- packages/computer/src/runtime/types.ts | 2 +- .../computer/src/tools/ai-sdk/index.test.ts | 30 +-- packages/computer/src/tools/common/exec.ts | 2 +- .../src/tools/tanstack-ai/index.test.ts | 6 +- .../computer/tests/script-runner-worker.ts | 37 ++- packages/computer/tests/script-runner.test.ts | 6 +- packages/computer/tsdown.config.ts | 2 +- 55 files changed, 353 insertions(+), 345 deletions(-) create mode 100644 .changeset/codemode-backend.md rename examples/{worker-javascript => codemode}/.gitignore (100%) rename examples/{worker-javascript => codemode}/README.md (89%) rename examples/{worker-javascript => codemode}/package.json (59%) rename examples/{worker-javascript => codemode}/seed/data/hello.txt (100%) rename examples/{worker-javascript => codemode}/src/index.ts (95%) rename examples/{worker-javascript => codemode}/src/skills/exec/SKILL.md (100%) rename examples/{worker-javascript => codemode}/tsconfig.json (100%) rename examples/{worker-javascript => codemode}/wrangler.jsonc (83%) rename packages/computer/src/backends/{worker-javascript/worker-javascript.test.ts => codemode/codemode.test.ts} (93%) rename packages/computer/src/backends/{worker-javascript/worker-javascript.ts => codemode/codemode.ts} (98%) rename packages/computer/src/backends/{worker-javascript => codemode}/durable-runtime.test.ts (100%) rename packages/computer/src/backends/{worker-javascript => codemode}/durable-runtime.ts (100%) rename packages/computer/src/backends/{worker-javascript => codemode}/frames.test.ts (100%) rename packages/computer/src/backends/{worker-javascript => codemode}/frames.ts (86%) create mode 100644 packages/computer/src/backends/codemode/index.ts rename packages/computer/src/backends/{worker-javascript => codemode}/journal.test.ts (100%) rename packages/computer/src/backends/{worker-javascript => codemode}/journal.ts (99%) rename packages/computer/src/backends/{worker-javascript => codemode}/module-graph.ts (99%) delete mode 100644 packages/computer/src/backends/worker-javascript/index.ts diff --git a/.changeset/codemode-backend.md b/.changeset/codemode-backend.md new file mode 100644 index 000000000..12e73a25d --- /dev/null +++ b/.changeset/codemode-backend.md @@ -0,0 +1,13 @@ +--- +"@cloudflare/computer": minor +--- + +Rename `WorkerJavaScriptBackend` to `CodemodeBackend`, and change its default id from `"worker-javascript"` to `"codemode"`. There is no alias for the old names. To upgrade: + +1. Rename the import path and exports: + - `@cloudflare/computer/backends/worker-javascript` → `@cloudflare/computer/backends/codemode` + - `WorkerJavaScriptBackend` → `CodemodeBackend` + - `WorkerJavaScriptBackendOptions` → `CodemodeBackendOptions` +2. Decide what id the backend uses. A backend without `id` now registers as `"codemode"`, and the exec tool, which shows the model each backend's id, offers it as `"codemode"`. Its `type`, which traces record, is now `"codemode"` whatever the id. Runs are saved under the id of the backend that started them, and a backend only sees runs saved under its own id. A `"codemode"` backend doesn't fail on runs saved under `"worker-javascript"`; it ignores them. It doesn't resume or fail interrupted ones, `runtime.pending()` doesn't list their waiting calls, `runtime.getExec(id)` on it throws `ENOENT`, and the rows stay in storage. + - To keep runs waiting for a person, saved results and journals, and existing `backend: "worker-javascript"` and `exec: { backends: { "worker-javascript": ... } }` references working, pass `id: "worker-javascript"` to `new CodemodeBackend(...)`. The exec tool then keeps offering it to the model as `"worker-javascript"`. + - Otherwise, change `backend: "worker-javascript"` to `backend: "codemode"` and `exec.backends` keys from `"worker-javascript"` to `"codemode"`. Left unchanged, `backend: "worker-javascript"` throws `no backend with id "worker-javascript"` and `exec.backends` throws `unknown backend "worker-javascript"`. Runs saved before the upgrade, including any still running or waiting for a person when you deploy, are then out of reach: nothing resumes, lists or cleans them up unless a backend with `id: "worker-javascript"` comes back. diff --git a/.changeset/exec-metadata.md b/.changeset/exec-metadata.md index 7aea79184..fd7180030 100644 --- a/.changeset/exec-metadata.md +++ b/.changeset/exec-metadata.md @@ -2,4 +2,4 @@ "@cloudflare/computer": minor --- -`runtime.exec()` takes `metadata`, a record of strings for host modules that the run's code never sees. Host module calls read it as `context.execution.metadata`, `runtime.pending()` lists it with each waiting call, and a replayable run keeps it when it resumes. `WorkerJavaScriptBackend` caps it at 16 KiB of keys and values. +`runtime.exec()` takes `metadata`, a record of strings for host modules that the run's code never sees. Host module calls read it as `context.execution.metadata`, `runtime.pending()` lists it with each waiting call, and a replayable run keeps it when it resumes. `CodemodeBackend` caps it at 16 KiB of keys and values. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4fb951c8..1a1154f3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -140,6 +140,9 @@ jobs: - name: assets workspace: "@example/computer-assets" path: examples/assets + - name: codemode + workspace: "@example/computer-codemode" + path: examples/codemode - name: container workspace: "@example/computer-container" path: examples/container @@ -164,9 +167,6 @@ jobs: - name: tutorial workspace: "@example/computer-tutorial" path: examples/tutorial - - name: worker-javascript - workspace: "@example/computer-worker-javascript" - path: examples/worker-javascript - name: worker-shell workspace: "@example/computer-worker-shell" path: examples/worker-shell diff --git a/README.md b/README.md index 311124c96..c690a4e02 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ SQLite and exposes one pluggable execution surface through - **Isolate shell** runs [just-bash](https://github.com/vercel-labs/just-bash) in a Dynamic Worker. It reaches the authoritative Workspace over Workers RPC, so there is no second store or sync round trip. -- **Isolate JavaScript** runs an ECMAScript module in a fresh Dynamic +- **Codemode** runs an ECMAScript module in a fresh Dynamic Worker with structured input/results, durable relative imports, configured libraries, Workspace-backed `node:fs/promises`, and host modules such as `codemode:git`, `codemode:artifacts`, and `codemode:container`. @@ -62,14 +62,14 @@ public surface. Each is a Worker workspace with its own README. - [`examples/worker-shell`](examples/worker-shell) — same HTTP surface as the container example, but the shell runs [just-bash](https://github.com/vercel-labs/just-bash) in a Dynamic Worker loaded through `env.LOADER`. No container. -- [`examples/worker-javascript`](examples/worker-javascript) — mirrors +- [`examples/codemode`](examples/codemode) — mirrors `worker-shell`, but `exec` evaluates an ECMAScript module in a Dynamic Worker instead of running a shell command. - [`examples/vite-bundle`](examples/vite-bundle) — a Vite-built Worker that ships `src/skills` into every workspace with `computerBundle()` and `WorkerBundle`, including under `vite dev`. - [`examples/egress`](examples/egress) — sends one URL through the container, - Worker shell, and Worker JavaScript backends with matching `none`, `all`, or + Worker shell, and codemode backends with matching `none`, `all`, or custom egress policies. - [`examples/mcp`](examples/mcp) — a Computer MCP example: one Code Mode `code` tool backed by a durable workspace, a Worker shell, diff --git a/docs/05_runtime_interface.md b/docs/05_runtime_interface.md index f6bcfa02b..04c38d659 100644 --- a/docs/05_runtime_interface.md +++ b/docs/05_runtime_interface.md @@ -103,7 +103,7 @@ flowchart LR - The files are ordinary Workspace files. A container backend receives them on its next push like any other change. - If saving fails, for example under a read-only mount, the entry has `status: "not-saved"` and a `reason`; the result still holds the last lines. -Command backends leave `value` unset. `worker-javascript` uses `value` for the module's structured return value and reports a zero-entry completed sync. A command can complete while its post-command pull fails; in that case `sync.status` is `"pending"`, and the next `pull()` resumes the operation from its durable watermark without rerunning the command. +Command backends leave `value` unset. `codemode` uses `value` for the module's structured return value and reports a zero-entry completed sync. A command can complete while its post-command pull fails; in that case `sync.status` is `"pending"`, and the next `pull()` resumes the operation from its durable watermark without rerunning the command. ## Backend routing @@ -121,7 +121,7 @@ await workspace.runtime.exec( import fs from "node:fs/promises"; export default async () => fs.readFile("/workspace/package.json", "utf8"); `, - { backend: "worker-javascript" }, + { backend: "codemode" }, ); ``` @@ -132,14 +132,14 @@ Omitting `backend` selects the first configured backend. Backend selection is ro ```ts workspace.runtime.backends(); // [ -// { id: "worker-javascript", protocol: "module", callable: true, description: "..." }, +// { id: "codemode", protocol: "module", callable: true, description: "..." }, // { id: "container-shell", protocol: "command", callable: false, description: "..." }, // ] ``` - `protocol` says what `exec` source means there: a shell command (`"command"`) or module source (`"module"`). - `callable` says whether the backend takes structured `input` and returns a `value`. -- `description`, when present, is what the backend tells a model about itself. `WorkerJavaScriptBackend` lists its source language and every module code can import, and `WorkerShellBackend` and `ContainerBackend` describe what they can run. The `exec` tool builds its description from these. +- `description`, when present, is what the backend tells a model about itself. `CodemodeBackend` lists its source language and every module code can import, and `WorkerShellBackend` and `ContainerBackend` describe what they can run. The `exec` tool builds its description from these. The list is fixed when the Workspace is constructed. A client from `getWorkspace()` takes a copy of it when the client loads, so once awaited the answer is the same locally and over RPC. Before then, the client reports no backends. @@ -163,7 +163,7 @@ Module backends use host capability calls against the authoritative Workspace an `container-shell` provides computerd's retained process log, replay, signals, and disposal. Execution handles are scoped to the UUID of the container process that accepted them, so they cannot target a reused execution id after process replacement. The latest UUID owner for each execution ID is stored in Workspace SQLite, with a bounded in-memory LRU cache, so direct by-ID operations preserve the fence across Durable Object incarnation and cache eviction. -`worker-javascript` provides a Workspace-owned execution journal, retained result/events, host cancellation, and explicit disposal. Active Workers cannot be serialized across host restart; orphaned running records are reconciled to failed. +`codemode` provides a Workspace-owned execution journal, retained result/events, host cancellation, and explicit disposal. Active Workers cannot be serialized across host restart; orphaned running records are reconciled to failed. `worker-shell` intentionally preserves one-call, buffered-result behavior in this release. It does not retain executions for later reattachment or disposal. `timeoutMs` and a concurrent `killExec()` for a caller-supplied execution ID cooperatively abort just-bash at statement boundaries; by the time an ordinary `exec()` promise returns, the command has already settled. Use the Container or JavaScript isolate when detached execution and retained lifecycle are required. diff --git a/docs/09_tool_interface.md b/docs/09_tool_interface.md index e4179d88d..4b1374f94 100644 --- a/docs/09_tool_interface.md +++ b/docs/09_tool_interface.md @@ -69,11 +69,11 @@ Pass the returned AI SDK `ToolSet` to `generateText`, `streamText`, or an agent `exec: { backends }` lists the backends the model can use, keyed by backend id. Leave it out to use every backend. ```ts -createAITools({ workspace }); // every backend -createAITools({ workspace, exec: { backends: { "worker-javascript": {} } } }); // just this one +createAITools({ workspace }); // every backend +createAITools({ workspace, exec: { backends: { codemode: {} } } }); // just this one createAITools({ workspace, - exec: { backends: { "worker-javascript": { description: "Use for data work." } } }, // with your own text + exec: { backends: { codemode: { description: "Use for data work." } } }, // with your own text }); ``` @@ -361,7 +361,7 @@ The tool uses forced removal, so deleting a missing path succeeds. Set `recursiv `exec` calls `workspace.runtime.exec` on the chosen backend and streams bounded output. `createExecTool({ workspace, backends?, maxBytes?, maxLines?, metadata? })` takes the same `backends` as the `exec` option, plus output limits and [metadata](#run-metadata). -Each backend's entry in the tool description joins two parts: your text, if any, and what the backend says about itself (`backend.description`, read through `workspace.runtime.backends()`). `WorkerJavaScriptBackend` describes its source language and every module code can import, so the list stays in step with `modules`. `WorkerShellBackend` and `ContainerBackend` describe their command sets, network access, and startup cost. A backend that says nothing gets a one-line default, so add text for a custom backend. +Each backend's entry in the tool description joins two parts: your text, if any, and what the backend says about itself (`backend.description`, read through `workspace.runtime.backends()`). `CodemodeBackend` describes its source language and every module code can import, so the list stays in step with `modules`. `WorkerShellBackend` and `ContainerBackend` describe their command sets, network access, and startup cost. A backend that says nothing gets a one-line default, so add text for a custom backend. The tool offers only the arguments that can work: diff --git a/docs/10_project_layout.md b/docs/10_project_layout.md index 0929944b0..a4da05c3a 100644 --- a/docs/10_project_layout.md +++ b/docs/10_project_layout.md @@ -57,7 +57,7 @@ packages/computer/ │ ├── backends/ │ │ ├── container/ # Cloudflare Container + computerd backend │ │ ├── worker-shell/ # Dynamic Worker + just-bash shell backend -│ │ ├── worker-javascript/ # Dynamic Worker ECMAScript module backend +│ │ ├── codemode/ # Dynamic Worker ECMAScript module backend │ │ └── test.ts # In-process test backend │ ├── mounts/ # Mount registry, indexer, R2Bucket and WorkerBundle │ ├── vite/ # computerBundle Vite plugin (@cloudflare/computer/vite) @@ -74,7 +74,7 @@ packages/computer/ └── package.json ``` -The package builds ESM and declarations with tsdown. Public entrypoints include the root export, tools, Git, assets, artifacts, the Container, Worker shell, and Worker JavaScript backends, and Cloudflare observability. The injected service is the separate `computerd` package, and shared wire types live in `@cloudflare/computer-rpc`. +The package builds ESM and declarations with tsdown. Public entrypoints include the root export, tools, Git, assets, artifacts, the Container, Worker shell, and codemode backends, and Cloudflare observability. The injected service is the separate `computerd` package, and shared wire types live in `@cloudflare/computer-rpc`. ## `packages/dofs/` — `@cloudflare/dofs` @@ -213,7 +213,7 @@ Runnable examples live at the repo root, not inside any package: examples/ ├── container/ # computerd inside a Cloudflare Container ├── worker-shell/ # WorkerShellBackend (just-bash) example -├── worker-javascript/ # WorkerJavaScriptBackend example +├── codemode/ # CodemodeBackend example ├── think/ # @cloudflare/think chat integration ├── think-compare-runtimes/ # container vs worker runtime comparison UI ├── tutorial/ # step-by-step pandoc PDF agent diff --git a/docs/12_worker_backend.md b/docs/12_worker_backend.md index 4140edca9..2b12111c9 100644 --- a/docs/12_worker_backend.md +++ b/docs/12_worker_backend.md @@ -47,7 +47,7 @@ isolate that boots instantly, scales out cheaply, and has no container lifecycle. The shell is the just-bash interpreter; the supported command set is broad (`cat`, `grep`, `awk`, `sed`, `jq`, `sort`) but not the full Linux userland. JavaScript modules run through the -[`worker-javascript` backend](./17_isolate_javascript.md), not through just-bash's +[codemode backend](./17_isolate_javascript.md), not through just-bash's Node-only language commands. Filesystem operations forward into the same SQLite store as the container backend, so the storage shape, mount rules, and read-only enforcement are unchanged. diff --git a/docs/16_code_execution.md b/docs/16_code_execution.md index 34c316a22..58bbf19b1 100644 --- a/docs/16_code_execution.md +++ b/docs/16_code_execution.md @@ -17,7 +17,7 @@ The selected backend defines how it interprets `source`. | --- | --- | --- | | `container-shell` | shell command | Full Linux, native binaries, installed packages, processes | | `worker-shell` | just-bash command | Fast text tools and Workspace Git without a Container | -| `worker-javascript` | ECMAScript module | Isolated structured JavaScript with Workspace host modules | +| `codemode` | ECMAScript module | Isolated structured JavaScript with Workspace host modules | Applications may register additional command or module backends under their own IDs. Backend IDs are part of the execution contract: changing the backend may change the source language. @@ -26,19 +26,19 @@ Applications may register additional command or module backends under their own ```ts const handle = await workspace.runtime.exec(source, { id: "build-1", - backend: "worker-javascript", + backend: "codemode", }); handle.id; await handle.kill(); const resumed = await workspace.runtime.getExec("build-1", { - backend: "worker-javascript", + backend: "codemode", resume: "full", }); await workspace.runtime.disposeExec("build-1", { - backend: "worker-javascript", + backend: "codemode", }); ``` @@ -59,9 +59,9 @@ interface WorkspaceRuntimeResult { Command backends leave `value` unset. Module backends use it for their structured return value. -`container-shell` retains the existing computerd process lifecycle. `worker-javascript` keeps an execution journal in the Workspace database and retains events/results until `disposeExec`. Active isolate cancellation is host-driven by disposing the child Worker. An execution left running across a Workspace host restart is reconciled to failed because a live Worker capability cannot be serialized into SQLite. +`container-shell` retains the existing computerd process lifecycle. `codemode` keeps an execution journal in the Workspace database and retains events/results until `disposeExec`. Active isolate cancellation is host-driven by disposing the child Worker. An execution left running across a Workspace host restart is reconciled to failed because a live Worker capability cannot be serialized into SQLite. -`worker-shell` intentionally retains its existing behavior in this release: it buffers a just-bash call to completion, does not retain cross-request events, and cannot reattach by ID. Callers that require supervised process behavior should use `container-shell`; callers that require a managed isolate should use `worker-javascript`. +`worker-shell` intentionally retains its existing behavior in this release: it buffers a just-bash call to completion, does not retain cross-request events, and cannot reattach by ID. Callers that require supervised process behavior should use `container-shell`; callers that require a managed isolate should use `codemode`. ## Backend authority @@ -70,14 +70,14 @@ There is no general `workspace.scope()` abstraction. Backend construction fixes For different authority levels, configure distinct backend instances: ```ts -new WorkerJavaScriptBackend({ - id: "worker-javascript-readonly", +new CodemodeBackend({ + id: "codemode-readonly", loader: env.LOADER, access: "read", }); -new WorkerJavaScriptBackend({ - id: "worker-javascript", +new CodemodeBackend({ + id: "codemode", loader: env.LOADER, access: "read-write", }); diff --git a/docs/17_isolate_javascript.md b/docs/17_isolate_javascript.md index bc4aad937..0150ef245 100644 --- a/docs/17_isolate_javascript.md +++ b/docs/17_isolate_javascript.md @@ -1,20 +1,20 @@ # Isolate JavaScript runtime > [!NOTE] -> **Stable.** `WorkerJavaScriptBackend` and its host modules are +> **Stable.** `CodemodeBackend` and its host modules are > suitable for production use, except `codemode:container`, which runs on the > experimental container backend. See the [status table](../packages/computer/README.md#status). -`WorkerJavaScriptBackend` runs an ECMAScript module in a fresh Cloudflare Dynamic Worker: +`CodemodeBackend` runs an ECMAScript module in a fresh Cloudflare Dynamic Worker: ```ts import { Workspace } from "@cloudflare/computer"; -import { WorkerJavaScriptBackend } from "@cloudflare/computer/backends/worker-javascript"; +import { CodemodeBackend } from "@cloudflare/computer/backends/codemode"; const workspace = new Workspace({ storage: ctx.storage, backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: env.LOADER, root: "/workspace", access: "read-write", @@ -31,7 +31,7 @@ const workspace = new Workspace({ `root`, `/workspace` by default, confines every path isolate code touches. A new Workspace doesn't have that directory yet, so a read-write backend creates it the first time it runs. -Execute a module through the common runtime entry point: +The backend registers as `"codemode"` unless you pass `id`. Execute a module through the common runtime entry point: ```ts const handle = await workspace.runtime.exec( @@ -46,7 +46,7 @@ const handle = await workspace.runtime.exec( } `, { - backend: "worker-javascript", + backend: "codemode", input: { value: 21 }, encoding: "utf8", }, @@ -94,7 +94,7 @@ await workspace.fs.writeFile( await workspace.runtime.exec( `import task from "./task.js"; export default task;`, { - backend: "worker-javascript", + backend: "codemode", cwd: "/workspace", input: { value: 42 }, }, @@ -129,7 +129,7 @@ An execution runs until its code returns or its timeout passes, and `maxTimeoutM Raise the ones a long run needs along with the timeout: ```ts -new WorkerJavaScriptBackend({ +new CodemodeBackend({ loader: env.LOADER, maxTimeoutMs: 2 * 60 * 60_000, maxCapabilityCalls: 100_000, @@ -150,7 +150,7 @@ A deploy or a crash still ends a run. See [Interrupted executions](#interrupted- A deploy or a crash restarts the Durable Object and ends every run in flight. `onInterrupt` decides what happens to those runs. Set a default on the backend and override it per run: ```ts -const backend = new WorkerJavaScriptBackend({ loader: env.LOADER, onInterrupt: "replay" }); +const backend = new CodemodeBackend({ loader: env.LOADER, onInterrupt: "replay" }); await workspace.runtime.exec(source, { onInterrupt: "fail" }); ``` @@ -259,7 +259,7 @@ const handle = await workspace.runtime.exec( } `, { - backend: "worker-javascript", + backend: "codemode", env: { WHO: "demo" }, stdin: "hello", encoding: "utf8", @@ -286,7 +286,7 @@ import { createGitModule } from "@cloudflare/computer/modules/git"; import { createToolBindings } from "@cloudflare/computer/modules/tools"; import { forPiTools } from "@cloudflare/computer/tools/pi-ai"; -new WorkerJavaScriptBackend({ +new CodemodeBackend({ loader: env.LOADER, modules: { "tar-stream": TAR_STREAM_BUNDLE, @@ -408,7 +408,7 @@ The arguments come from caller code, so parse them before use. A function may re ```ts await workspace.runtime.exec(source, { - backend: "worker-javascript", + backend: "codemode", metadata: { conversation: conversationId }, }); @@ -556,7 +556,7 @@ export default async function () { } ``` -`publish(path, { expiresAfter?, filename?, disposition?, contentType? })` resolves `path` against the backend root like any other host call. `expiresAfter` is milliseconds or a duration such as `"30m"`; the default is one hour, `createAssetsModule({ defaultExpiresAfterMs })` changes it, and the assets client caps it at seven days. `disposition` is `"inline"` or `"attachment"`. If the Workspace has no assets client, the JavaScript backend fails to connect. Publishing uploads a file and hands out a public link, so `publish` refuses to run on a read-only backend, as the read-only tool set has no `publish` tool. +`publish(path, { expiresAfter?, filename?, disposition?, contentType? })` resolves `path` against the backend root like any other host call. `expiresAfter` is milliseconds or a duration such as `"30m"`; the default is one hour, `createAssetsModule({ defaultExpiresAfterMs })` changes it, and the assets client caps it at seven days. `disposition` is `"inline"` or `"attachment"`. If the Workspace has no assets client, the codemode backend fails to connect. Publishing uploads a file and hands out a public link, so `publish` refuses to run on a read-only backend, as the read-only tool set has no `publish` tool. ### `codemode:tools` @@ -584,7 +584,7 @@ import { validateToolArguments } from "@earendil-works/pi-ai"; import { createToolBindings } from "@cloudflare/computer/modules/tools"; import { forPiTools } from "@cloudflare/computer/tools/pi-ai"; -new WorkerJavaScriptBackend({ +new CodemodeBackend({ loader: env.LOADER, modules: { "codemode:tools": createToolBindings(forPiTools(() => agent.tools, { validate: validateToolArguments })), @@ -622,7 +622,7 @@ class Agent extends withWorkspaceContainer(class extends DurableObject {}) workspace = new Workspace({ storage: this.ctx.storage, backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: this.env.LOADER, access: "read-write", modules: { "codemode:container": createContainerModule() }, @@ -636,8 +636,8 @@ class Agent extends withWorkspaceContainer(class extends DurableObject {}) }); } -// Offer only the JavaScript backend; the container is reached through codemode:container. -const tools = createAITools({ workspace: this.workspace, exec: { backends: { "worker-javascript": {} } } }); +// Offer only the codemode backend; the container is reached through codemode:container. +const tools = createAITools({ workspace: this.workspace, exec: { backends: { codemode: {} } } }); ``` ```js @@ -649,7 +649,7 @@ export default async function () { } ``` -`exec(command, { cwd, env, stdin, timeoutMs })` runs through `workspace.runtime.exec` on the container backend: `ContainerBackend`, registered as `"container-shell"` unless you pass `backend`. If that backend is missing, or runs module source rather than shell commands, the JavaScript backend fails to connect. The container shares the Workspace's files: writes the module made before the call are pushed to the container, and the container's changes are pulled back before `exec` returns. A non-zero exit code comes back as a value, not as an error. +`exec(command, { cwd, env, stdin, timeoutMs })` runs through `workspace.runtime.exec` on the container backend: `ContainerBackend`, registered as `"container-shell"` unless you pass `backend`. If that backend is missing, or runs module source rather than shell commands, the codemode backend fails to connect. The container shares the Workspace's files: writes the module made before the call are pushed to the container, and the container's changes are pulled back before `exec` returns. A non-zero exit code comes back as a value, not as an error. The result also carries `sync`: `{ status, skipped, skippedCount, error? }`. `status` is `pending` when the container's file changes have not reached the Workspace yet, and `error` says why. `skipped` lists up to 100 paths the container wrote that the Workspace refused, such as files in a read-only mount, and `skippedCount` gives the full count. The sync is last-writer-wins: the container's changes replace files written in the Workspace while the command runs, without reporting them as skipped. Don't write files from the isolate that the running command also writes. @@ -659,7 +659,7 @@ A few limits follow from `exec` being a host call: - The command's timeout is capped at the time left before the host call deadline (`maxHostCallMs`, which defaults to `maxTimeoutMs`). Raise `defaultTimeoutMs`, `maxTimeoutMs`, and `maxHostCallMs` for slow installs and builds, and remember the container's first start. - Cancelling the execution kills the running command. -A container command can write to the Workspace, so `exec` refuses to run on a read-only backend. Whether it can reach the network follows `ContainerBackend`'s own `egress` setting, not the JavaScript backend's. +A container command can write to the Workspace, so `exec` refuses to run on a read-only backend. Whether it can reach the network follows `ContainerBackend`'s own `egress` setting, not the codemode backend's. Two options shape every command: diff --git a/docs/18_runtime_migration.md b/docs/18_runtime_migration.md index db68c8b12..dcc9cbf0a 100644 --- a/docs/18_runtime_migration.md +++ b/docs/18_runtime_migration.md @@ -10,7 +10,7 @@ This change is a breaking preview-API migration. Public execution now uses one r | `workspace.shell.get(id, options)` | `workspace.runtime.getExec(id, options)` | | `workspace.shell.kill(id, options)` | `workspace.runtime.killExec(id, options)` | | `workspace.shell.dispose(id, options)` | `workspace.runtime.disposeExec(id, options)` | -| `workspace.code` / script execution | `workspace.runtime.exec(source, { backend: "worker-javascript", input })` | +| `workspace.code` / script execution | `workspace.runtime.exec(source, { backend: "codemode", input })` | `CommandExecutor` exists internally to implement command backends. It is not a public Workspace property. @@ -18,14 +18,14 @@ This change is a breaking preview-API migration. Public execution now uses one r - Cloudflare Container: `container-shell` - just-bash Dynamic Worker: `worker-shell` -- ECMAScript Dynamic Worker: `worker-javascript` +- ECMAScript Dynamic Worker: `codemode` The first configured backend is the default for `runtime.exec()`. Pass `backend` explicitly at security boundaries. Routing is not authorization: trusted gateways must choose from a host-owned allowlist rather than accepting an arbitrary model-supplied backend ID. ## Source semantics -Command backends interpret the first argument as a shell command and reject structured `input`. `worker-javascript` interprets it as an ECMAScript module and supports structured JSON-compatible input/results, durable relative modules, `node:fs/promises`, and host-owned trusted modules. +Command backends interpret the first argument as a shell command and reject structured `input`. `codemode` interprets it as an ECMAScript module and supports structured JSON-compatible input/results, durable relative modules, `node:fs/promises`, and host-owned trusted modules. ## Lifecycle differences -Container command executions use the remote process journal and push/pull synchronization bracket. `worker-shell` uses the documented limited one-call Worker lifecycle. `worker-javascript` stores execution status and events in the Workspace database and supports replay, cancellation, disposal, and restart recovery. Completed filesystem and provider side effects are not rolled back when execution fails or is cancelled. +Container command executions use the remote process journal and push/pull synchronization bracket. `worker-shell` uses the documented limited one-call Worker lifecycle. `codemode` stores execution status and events in the Workspace database and supports replay, cancellation, disposal, and restart recovery. Completed filesystem and provider side effects are not rolled back when execution fails or is cancelled. diff --git a/docs/README.md b/docs/README.md index d6b2f5d46..0ad57acb7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -45,7 +45,7 @@ The package ships several entrypoints: | `@cloudflare/computer/backends/container` | `ContainerBackend` and `withWorkspaceContainer`, for a container the durable object schedules (`scheduling_policy: "durable_object"`). Same sync plumbing; the launch names the image and the instance size. | | `@cloudflare/computer/backends/container-legacy` | Deprecated; use `@cloudflare/computer/backends/container`. `LegacyContainerBackend` and `withLegacyWorkspaceContainer`, for a container the platform schedules and sizes from the containers block. | | `@cloudflare/computer/backends/worker-shell` | `WorkerShellBackend` and the bundled just-bash command runtime. | -| `@cloudflare/computer/backends/worker-javascript` | `WorkerJavaScriptBackend`, configured libraries, durable relative imports, `node:fs/promises`, and host modules. | +| `@cloudflare/computer/backends/codemode` | `CodemodeBackend`, configured libraries, durable relative imports, `node:fs/promises`, and host modules. | | `@cloudflare/computer/git` | Opt-in isomorphic-git glue for working with checkouts inside the workspace. Bundled lazily, with `pako` replaced by Workers `node:zlib`, and kept out of the default `@cloudflare/computer` graph. | | `@cloudflare/computer/artifacts` | `createArtifact`, an optionally session-scoped wrapper over the Cloudflare Artifacts Workers binding, plus its argv CLI. | | `@cloudflare/computer/modules/container` | `createContainerModule()` for `codemode:container`: run container commands from isolate JavaScript. | diff --git a/examples/celld/README.md b/examples/celld/README.md index 5d2d2220d..181bfba58 100644 --- a/examples/celld/README.md +++ b/examples/celld/README.md @@ -161,7 +161,7 @@ export default async function main(input, ctx) { `ctx.cwd` is metadata; the directory is not mounted in the Dynamic Worker. The backend does not yet provide the full Computer -`WorkerJavaScriptBackend` bridge: +`CodemodeBackend` bridge: - Capability stubs cannot cross into the loaded Worker, so `ctx.fs` methods throw and durable `node:fs/promises` is unavailable there. diff --git a/examples/celld/src/celld-javascript-backend.ts b/examples/celld/src/celld-javascript-backend.ts index 437c3fb3c..e667d482c 100644 --- a/examples/celld/src/celld-javascript-backend.ts +++ b/examples/celld/src/celld-javascript-backend.ts @@ -27,7 +27,7 @@ export interface CelldJavaScriptBackendOptions { * WorkerEntrypoint RPC, static sibling modules, plain JSON env, and no-egress * `globalOutbound: null`. That is enough for callable JavaScript execution. * - * The full Computer WorkerJavaScriptBackend also passes a host filesystem bridge + * The full Computer CodemodeBackend also passes a host filesystem bridge * into the loaded worker. celld v0.1.0 currently rejects that bridge with a * DataCloneError, so this backend deliberately exposes only the pieces that are * validated under celld today: structured input/output, captured console output, diff --git a/examples/worker-javascript/.gitignore b/examples/codemode/.gitignore similarity index 100% rename from examples/worker-javascript/.gitignore rename to examples/codemode/.gitignore diff --git a/examples/worker-javascript/README.md b/examples/codemode/README.md similarity index 89% rename from examples/worker-javascript/README.md rename to examples/codemode/README.md index 4334d2a17..6c66721cc 100644 --- a/examples/worker-javascript/README.md +++ b/examples/codemode/README.md @@ -1,4 +1,4 @@ -# worker-javascript example +# codemode example A Cloudflare Worker + Durable Object that runs a Workspace whose runtime evaluates **ECMAScript modules** in a **Dynamic Worker** @@ -13,7 +13,7 @@ module instead of a just-bash command. client ─► Worker /c//{file,exec} │ (DO RPC calls) ▼ - DO (ContainerExample) ──► Workspace ──► WorkerJavaScriptBackend + DO (ContainerExample) ──► Workspace ──► CodemodeBackend │ │ env.LOADER.load(...) ▼ @@ -26,13 +26,13 @@ client ─► Worker /c//{file,exec} (fs, git, artifacts) ``` -1. The DO constructs a `WorkerJavaScriptBackend` from - `@cloudflare/computer/backends/worker-javascript`, passing only +1. The DO constructs a `CodemodeBackend` from + `@cloudflare/computer/backends/codemode`, passing only the Loader binding. Unlike the shell backend there is no `WorkspaceServiceProxy` loopback: the backend is self-contained and reaches the host through the `WorkspaceRuntimeBridge` it builds internally. -2. Each `exec(source, { backend: "worker-javascript", input })` +2. Each `exec(source, { backend: "codemode", input })` builds a module graph from the source, mints a fresh Dynamic Worker through `env.LOADER.load(...)`, and calls the runner's `evaluate(input, bridge)`. The module's default export receives @@ -71,10 +71,10 @@ internet on its own. Identical to the worker-shell example. Seed once with: ```sh -npm run seed:r2:local --workspace @example/computer-worker-javascript +npm run seed:r2:local --workspace @example/computer-codemode # or after deploy -npm run seed:r2 --workspace @example/computer-worker-javascript +npm run seed:r2 --workspace @example/computer-codemode ``` ## Bundled skill @@ -104,13 +104,13 @@ POST /c//exec { source, input?, cwd?, env?, stdin? } ## Run it locally No Docker, no extra build step. The module runner ships inside -`@cloudflare/computer/backends/worker-javascript`; -`WorkerJavaScriptBackend` mints the Dynamic Worker through the +`@cloudflare/computer/backends/codemode`; +`CodemodeBackend` mints the Dynamic Worker through the Loader binding internally so the DO constructor stays a one-line backend invocation. ```sh -npm run dev --workspace @example/computer-worker-javascript +npm run dev --workspace @example/computer-codemode ``` Smoke test: @@ -145,14 +145,14 @@ environment is never exposed), and `stdin` is readable through ## Layout ``` -examples/worker-javascript/ +examples/codemode/ wrangler.jsonc Worker + DO + worker_loaders binding src/index.ts Worker handler + DO (ContainerExample) src/skills/ Files mounted read-only at /workspace/.agents/skills ``` Nothing else. The Dynamic Worker module runner ships from -`@cloudflare/computer/backends/worker-javascript`. +`@cloudflare/computer/backends/codemode`. ## Known limitations diff --git a/examples/worker-javascript/package.json b/examples/codemode/package.json similarity index 59% rename from examples/worker-javascript/package.json rename to examples/codemode/package.json index 3f77f2d02..f8ed906d6 100644 --- a/examples/worker-javascript/package.json +++ b/examples/codemode/package.json @@ -1,15 +1,15 @@ { - "name": "@example/computer-worker-javascript", + "name": "@example/computer-codemode", "version": "0.0.0", "private": true, "type": "module", - "description": "Example Worker + Durable Object that evaluates ECMAScript modules inside a Dynamic Worker via @cloudflare/computer's WorkerJavaScriptBackend.", + "description": "Example Worker + Durable Object that evaluates ECMAScript modules inside a Dynamic Worker via @cloudflare/computer's CodemodeBackend.", "scripts": { "dev": "wrangler dev", "deploy": "wrangler deploy", "typecheck": "tsc --noEmit", - "seed:r2": "wrangler r2 object put computer-worker-javascript-hello/hello.txt --file ./seed/data/hello.txt --remote", - "seed:r2:local": "wrangler r2 object put computer-worker-javascript-hello/hello.txt --file ./seed/data/hello.txt --local", + "seed:r2": "wrangler r2 object put computer-codemode-hello/hello.txt --file ./seed/data/hello.txt --remote", + "seed:r2:local": "wrangler r2 object put computer-codemode-hello/hello.txt --file ./seed/data/hello.txt --local", "build:types": "wrangler types" }, "dependencies": { diff --git a/examples/worker-javascript/seed/data/hello.txt b/examples/codemode/seed/data/hello.txt similarity index 100% rename from examples/worker-javascript/seed/data/hello.txt rename to examples/codemode/seed/data/hello.txt diff --git a/examples/worker-javascript/src/index.ts b/examples/codemode/src/index.ts similarity index 95% rename from examples/worker-javascript/src/index.ts rename to examples/codemode/src/index.ts index 6b5a95d65..50292f15d 100644 --- a/examples/worker-javascript/src/index.ts +++ b/examples/codemode/src/index.ts @@ -7,13 +7,13 @@ import { type WorkspaceRuntimeValue, withWorkspace, } from "@cloudflare/computer"; -import { WorkerJavaScriptBackend } from "@cloudflare/computer/backends/worker-javascript"; +import { CodemodeBackend } from "@cloudflare/computer/backends/codemode"; export class ContainerExample extends withWorkspace(class extends DurableObject {}, (self) => { const { ctx, env } = self as unknown as { ctx: DurableObjectState; env: Env }; return { storage: ctx.storage, - backends: [new WorkerJavaScriptBackend({ loader: env.LOADER })], + backends: [new CodemodeBackend({ loader: env.LOADER })], mounts: { "/workspace/r2": R2Bucket(env.Bucket), // Read-only copy of src/skills, shipped with the Worker through the @@ -61,7 +61,7 @@ export default { if (url.pathname === "/" || url.pathname === "") { return new Response( [ - "worker-javascript example", + "codemode example", "", ` PUT /c//file/workspace/ write file at ${MOUNT_ROOT}/`, ` GET /c//file/workspace/ read file at ${MOUNT_ROOT}/`, @@ -131,7 +131,7 @@ async function handleExec(request: Request, env: Env, name: string): Promise[0]); try { const handle = await ws.runtime.exec(body.source, { - backend: "worker-javascript", + backend: "codemode", cwd: body.cwd, input: body.input, env: body.env, diff --git a/examples/worker-javascript/src/skills/exec/SKILL.md b/examples/codemode/src/skills/exec/SKILL.md similarity index 100% rename from examples/worker-javascript/src/skills/exec/SKILL.md rename to examples/codemode/src/skills/exec/SKILL.md diff --git a/examples/worker-javascript/tsconfig.json b/examples/codemode/tsconfig.json similarity index 100% rename from examples/worker-javascript/tsconfig.json rename to examples/codemode/tsconfig.json diff --git a/examples/worker-javascript/wrangler.jsonc b/examples/codemode/wrangler.jsonc similarity index 83% rename from examples/worker-javascript/wrangler.jsonc rename to examples/codemode/wrangler.jsonc index 8d8000bcb..c60a1eee1 100644 --- a/examples/worker-javascript/wrangler.jsonc +++ b/examples/codemode/wrangler.jsonc @@ -5,10 +5,10 @@ // // Mirrors examples/worker-shell beat for beat — same DO class // name, same routes, same R2 mount. The only difference is the - // backend: WorkerJavaScriptBackend instead of WorkerShellBackend, + // backend: CodemodeBackend instead of WorkerShellBackend, // so exec evaluates JavaScript modules rather than shell commands. "$schema": "node_modules/wrangler/config-schema.json", - "name": "computer-worker-javascript-example", + "name": "computer-codemode-example", "main": "src/index.ts", "compatibility_date": "2026-05-26", "compatibility_flags": ["nodejs_compat"], @@ -20,7 +20,7 @@ "find_additional_modules": true, "rules": [{ "type": "Data", "globs": ["skills/**/*"], "fallthrough": true }], - // Worker Loader binding. The DO's WorkerJavaScriptBackend calls + // Worker Loader binding. The DO's CodemodeBackend calls // env.LOADER.load(...) to mint a Dynamic Worker that evaluates // each module. "worker_loaders": [ @@ -40,12 +40,12 @@ // R2 bucket mounted into every Workspace at /workspace/r2 via // R2Bucket(...). Seed it once with: - // npm run seed:r2 --workspace @example/computer-worker-javascript + // npm run seed:r2 --workspace @example/computer-codemode // which uploads ./seed/data/hello.txt to the bucket. "r2_buckets": [ { "binding": "Bucket", - "bucket_name": "computer-worker-javascript-hello" + "bucket_name": "computer-codemode-hello" } ], diff --git a/examples/egress/README.md b/examples/egress/README.md index b8c7a4329..3dbc7b6a2 100644 --- a/examples/egress/README.md +++ b/examples/egress/README.md @@ -12,7 +12,7 @@ This Worker sends one URL through all three Cloudflare Computer execution backen - the container shell runs `curl`; - the Worker shell runs its `curl` command; -- Worker JavaScript runs `fetch`. +- the codemode backend runs `fetch`. `EGRESS_MODE` applies the same egress policy to every backend. @@ -46,7 +46,7 @@ The endpoint returns the status code and MIME type from each backend, or an erro "url": "https://example.com/", "container": { "status": 200, "mimeType": "text/html" }, "worker-shell": { "status": 200, "mimeType": "text/html" }, - "worker-javascript": { "status": 200, "mimeType": "text/html" } + "codemode": { "status": 200, "mimeType": "text/html" } } ``` diff --git a/examples/egress/src/index.ts b/examples/egress/src/index.ts index 5b7085265..5a3c8132b 100644 --- a/examples/egress/src/index.ts +++ b/examples/egress/src/index.ts @@ -9,11 +9,11 @@ import { WorkspaceServiceProxy, withWorkspace, } from "@cloudflare/computer"; +import { CodemodeBackend } from "@cloudflare/computer/backends/codemode"; import { LegacyContainerBackend, withLegacyWorkspaceContainer, } from "@cloudflare/computer/backends/container-legacy"; -import { WorkerJavaScriptBackend } from "@cloudflare/computer/backends/worker-javascript"; import { WorkerShellBackend, type WorkerShellLoader, @@ -71,7 +71,7 @@ function workspaceOptions(self: InstanceType): Works commands: [curl], egress: self.egress, }), - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: env.LOADER as unknown as WorkspaceRuntimeLoader, egress: self.egress, }), @@ -131,7 +131,7 @@ export default { const stub = env.EgressExample.get(env.EgressExample.idFromName("default")); const workspace = await getWorkspace(stub as unknown as Parameters[0]); await workspace.fs.mkdir("/workspace", { recursive: true }); - const [container, workerShell, workerJavaScript] = await Promise.all([ + const [container, workerShell, codemode] = await Promise.all([ shellFetch(workspace, "container-shell", url), shellFetch(workspace, "worker-shell", url), javaScriptFetch(workspace, url), @@ -142,7 +142,7 @@ export default { url, container, "worker-shell": workerShell, - "worker-javascript": workerJavaScript, + codemode, }); }, } satisfies ExportedHandler; @@ -168,13 +168,13 @@ async function shellFetch( async function javaScriptFetch(workspace: WorkspaceClient, url: string): Promise { try { const handle = await workspace.runtime.exec(JAVASCRIPT_FETCH, { - backend: "worker-javascript", + backend: "codemode", input: url, encoding: "utf8", }); const result = await handle.result(); if (result.status !== "completed" || result.value === undefined) { - return { error: result.stderr.trim() || "Worker JavaScript fetch failed" }; + return { error: result.stderr.trim() || "Codemode fetch failed" }; } return result.value as FetchResult; } catch (error) { diff --git a/examples/rlm/README.md b/examples/rlm/README.md index 60865a6d0..3245bac40 100644 --- a/examples/rlm/README.md +++ b/examples/rlm/README.md @@ -45,7 +45,7 @@ This pattern is less useful when the context already fits comfortably in one pro 1. The host fetches an official Oolong task and writes its corpus and manifest to a Workspace. 2. The parent model receives the question, file paths, and the available Computer tool. It does not receive the complete corpus. -3. The parent writes a complete ECMAScript module for the Worker JavaScript backend. +3. The parent writes a complete ECMAScript module for the codemode backend. 4. That module reads record-aligned partitions from the Workspace. 5. The module calls `codemode:model` with a bounded batch of classification or extraction requests. 6. The module validates the returned JSON and reduces it to one answer with JavaScript. @@ -60,7 +60,7 @@ The host gives generated code a read-only Workspace and one trusted model module ```ts const modelCapability = createModelCapability(model, hooks); -const backend = new WorkerJavaScriptBackend({ +const backend = new CodemodeBackend({ id: "oolong-rlm-javascript", loader: env.LOADER, root: "/workspace", @@ -149,7 +149,7 @@ The reducer is exact relative to its inputs. Model classifications can still be The shortest path through the example is: -1. [`worker/rlm-agent.ts`](worker/rlm-agent.ts) wires together the model, Workspace, Worker JavaScript backend, executor tool, and `codemode:model`. +1. [`worker/rlm-agent.ts`](worker/rlm-agent.ts) wires together the model, Workspace, codemode backend, executor tool, and `codemode:model`. 2. [`worker/capability.ts`](worker/capability.ts) implements the bounded `batch(requests)` function behind `codemode:model`. 3. [`worker/structured-rlm.ts`](worker/structured-rlm.ts) describes the map result and JavaScript reduction for each task family. 4. [`worker/agent-common.ts`](worker/agent-common.ts) writes the same corpus into each Computer Workspace. diff --git a/examples/rlm/worker/executor-agent.ts b/examples/rlm/worker/executor-agent.ts index ac0a2aebf..0a58687f1 100644 --- a/examples/rlm/worker/executor-agent.ts +++ b/examples/rlm/worker/executor-agent.ts @@ -1,6 +1,6 @@ import { AIChatAgent, type OnChatMessageOptions } from "@cloudflare/ai-chat"; import { Workspace, type WorkspaceRuntimeLoader } from "@cloudflare/computer"; -import { WorkerJavaScriptBackend } from "@cloudflare/computer/backends/worker-javascript"; +import { CodemodeBackend } from "@cloudflare/computer/backends/codemode"; import type { Connection } from "agents"; import { isStepCount, streamText, type ToolSet } from "ai"; @@ -36,7 +36,7 @@ export class ExecutorAgent extends AIChatAgent { constructor(ctx: DurableObjectState, env: ModelEnv) { super(ctx, env); - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ id: EXECUTOR_BACKEND, loader: env.LOADER as unknown as WorkspaceRuntimeLoader, root: WORKSPACE_ROOT, diff --git a/examples/rlm/worker/rlm-agent.ts b/examples/rlm/worker/rlm-agent.ts index d0f626175..8b7c0c3d5 100644 --- a/examples/rlm/worker/rlm-agent.ts +++ b/examples/rlm/worker/rlm-agent.ts @@ -1,6 +1,6 @@ import { AIChatAgent, type OnChatMessageOptions } from "@cloudflare/ai-chat"; import { Workspace, type WorkspaceRuntimeLoader } from "@cloudflare/computer"; -import { WorkerJavaScriptBackend } from "@cloudflare/computer/backends/worker-javascript"; +import { CodemodeBackend } from "@cloudflare/computer/backends/codemode"; import type { Connection } from "agents"; import { isStepCount, streamText, type ToolSet } from "ai"; @@ -97,7 +97,7 @@ export class RlmAgent extends AIChatAgent { }); }, }); - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ id: RLM_BACKEND, loader: env.LOADER as unknown as WorkspaceRuntimeLoader, root: WORKSPACE_ROOT, diff --git a/package-lock.json b/package-lock.json index 3d130e583..ed63d58c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -240,6 +240,17 @@ "packages/*" ] }, + "examples/codemode": { + "name": "@example/computer-codemode", + "version": "0.0.0", + "dependencies": { + "@cloudflare/computer": "*" + }, + "devDependencies": { + "typescript": "^6.0.3", + "wrangler": "^4.148.0" + } + }, "examples/container": { "name": "@example/computer-container", "version": "0.0.0", @@ -1187,17 +1198,6 @@ "wrangler": "^4.148.0" } }, - "examples/worker-javascript": { - "name": "@example/computer-worker-javascript", - "version": "0.0.0", - "dependencies": { - "@cloudflare/computer": "*" - }, - "devDependencies": { - "typescript": "^6.0.3", - "wrangler": "^4.148.0" - } - }, "examples/worker-shell": { "name": "@example/computer-worker-shell", "version": "0.0.0", @@ -3658,6 +3658,10 @@ "resolved": "examples/celld", "link": true }, + "node_modules/@example/computer-codemode": { + "resolved": "examples/codemode", + "link": true + }, "node_modules/@example/computer-container": { "resolved": "examples/container", "link": true @@ -3690,10 +3694,6 @@ "resolved": "examples/vite-bundle", "link": true }, - "node_modules/@example/computer-worker-javascript": { - "resolved": "examples/worker-javascript", - "link": true - }, "node_modules/@example/computer-worker-shell": { "resolved": "examples/worker-shell", "link": true diff --git a/packages/computer/README.md b/packages/computer/README.md index 806ba2702..ba6ba68bd 100644 --- a/packages/computer/README.md +++ b/packages/computer/README.md @@ -22,7 +22,7 @@ removed in a future release; each one names its replacement. | --- | --- | --- | | `Workspace`, `withWorkspace`, `getWorkspace`, `workspace.fs`, `workspace.runtime`, and the stub types | `@cloudflare/computer` | Stable | | The SQLite filesystem layer (`@cloudflare/dofs`, bundled into this package), including `SQLiteWorkspaceProvider` | `@cloudflare/computer` | Stable | -| `WorkerJavaScriptBackend` | `@cloudflare/computer/backends/worker-javascript` | Stable | +| `CodemodeBackend` | `@cloudflare/computer/backends/codemode` | Stable | | Read-only mounts (`R2Bucket`, `WorkerBundle`) and the Vite plugin | `@cloudflare/computer`, `@cloudflare/computer/vite` | Stable | | Agent tools | `@cloudflare/computer/tools`, `@cloudflare/computer/tools/*` | Stable | | Host modules for isolate JavaScript: `codemode:git`, `codemode:artifacts`, `codemode:assets`, `codemode:tools` | `@cloudflare/computer/modules/git`, `modules/artifacts`, `modules/assets`, `modules/tools` | Stable | @@ -31,11 +31,11 @@ removed in a future release; each one names its replacement. | Observability hook and its Cloudflare adapter | `@cloudflare/computer`, `@cloudflare/computer/observe/cloudflare` | Stable | | `ContainerBackend` and `withWorkspaceContainer` | `@cloudflare/computer/backends/container` | Experimental | | `LegacyContainerBackend` and `withLegacyWorkspaceContainer` | `@cloudflare/computer/backends/container-legacy` | Deprecated: use `ContainerBackend` | -| Resuming interrupted isolate JavaScript: `onInterrupt: "restart"` and `"replay"`, `workspace.runtime.recover()`, `unsafeToRepeat()`, and waiting for answers with `ctx.interrupt()`, `pending()` and `respond()` | `@cloudflare/computer`, `@cloudflare/computer/backends/worker-javascript` | Experimental | +| Resuming interrupted isolate JavaScript: `onInterrupt: "restart"` and `"replay"`, `workspace.runtime.recover()`, `unsafeToRepeat()`, and waiting for answers with `ctx.interrupt()`, `pending()` and `respond()` | `@cloudflare/computer`, `@cloudflare/computer/backends/codemode` | Experimental | | `WorkerShellBackend` and the shell command groups | `@cloudflare/computer/backends/worker-shell`, `@cloudflare/computer/shell/*` | Experimental | | The `codemode:container` host module | `@cloudflare/computer/modules/container` | Experimental | | The `codemode:browser` host module | `@cloudflare/computer/modules/browser` | Experimental | -| Codemode modules: `createCodemodeModule()`, with objects and scoped context | `@cloudflare/computer`, `@cloudflare/computer/backends/worker-javascript` | Experimental | +| Codemode modules: `createCodemodeModule()`, with objects and scoped context | `@cloudflare/computer`, `@cloudflare/computer/backends/codemode` | Experimental | | `workspace.push()`, `workspace.pull()`, and the sync protocol between the durable object and `computerd` | `@cloudflare/computer` | Experimental | | The `computerd` daemon and its container image | `ghcr.io/cloudflare/computer-computerd-linux-x64` | Experimental | | The RPC wire protocol between the durable object and `computerd` (`@cloudflare/computer-rpc`) | Internal | Experimental | @@ -82,7 +82,7 @@ npm install @cloudflare/computer ``` Your Worker needs the `nodejs_compat` compatibility flag. The -worker-shell and worker-javascript backends also need a Worker Loader +worker-shell and codemode backends also need a Worker Loader binding, and the worker-shell backend needs the `experimental` flag. Each backend has its own binding requirements — see [Choosing a backend](#choosing-a-backend). @@ -138,8 +138,8 @@ export default { To run commands against those files, add an execution backend. The worker-shell backend needs no container and no Docker, so it's the quickest way to get `exec` working. It is experimental; for a stable -backend, use the worker-javascript backend, shown in -[`examples/worker-javascript`](../../examples/worker-javascript): +backend, use the codemode backend, shown in +[`examples/codemode`](../../examples/codemode): ```ts import { withWorkspace, getWorkspace } from "@cloudflare/computer"; @@ -279,7 +279,7 @@ for the options, types, and how refresh works. `workspace.runtime.exec(source, options)` is the single execution entry point. What `source` means depends on the backend: a shell command for the container and worker-shell backends, an ECMAScript module for the -worker-javascript backend. +codemode backend. ```ts using run = await ws.runtime.exec("ls -la /workspace", { encoding: "utf8" }); @@ -319,7 +319,7 @@ Alongside `exec`, the runtime exposes `getExec`, `killExec`, and | **Container** | Experimental | `@cloudflare/computer/backends/container` | Shell commands in full Linux userland (real binaries, `npm`, `node`, network) | A Cloudflare Container running `computerd`, scheduled by the durable object (`scheduling_policy: "durable_object"`) | | **Container (legacy)** | Deprecated | `@cloudflare/computer/backends/container-legacy` | The same | A Cloudflare Container the platform schedules and sizes from the `containers` block | | **Worker shell** | Experimental | `@cloudflare/computer/backends/worker-shell` | Shell commands via [just-bash](https://github.com/vercel-labs/just-bash) in a Dynamic Worker | A Worker Loader binding; `experimental` flag; the `just-bash` package | -| **Worker JavaScript** | Stable | `@cloudflare/computer/backends/worker-javascript` | ECMAScript modules in a fresh Dynamic Worker | A Worker Loader binding | +| **Codemode** | Stable | `@cloudflare/computer/backends/codemode` | ECMAScript modules in a fresh Dynamic Worker | A Worker Loader binding | - **Container** cold-starts more slowly but gives you a real Linux environment. The container owns its own SQLite-backed VFS and this @@ -335,13 +335,13 @@ Alongside `exec`, the runtime exposes `getExec`, `killExec`, and second store and no sync round trip. See [`docs/12_worker_backend.md`](../../docs/12_worker_backend.md) and [`examples/worker-shell`](../../examples/worker-shell). -- **Worker JavaScript** evaluates a module with structured +- **Codemode** evaluates a module with structured input/results, durable relative imports, configured libraries, Workspace-backed `node:fs/promises`, and host modules such as `codemode:git`, `codemode:artifacts`, and `codemode:container`. It runs after `runtime.exec()` returns; the run stays alive while its event stream is consumed. See [`docs/17_isolate_javascript.md`](../../docs/17_isolate_javascript.md) - and [`examples/worker-javascript`](../../examples/worker-javascript). + and [`examples/codemode`](../../examples/codemode). You can register several backends on one Workspace and route each call to a named one — see [Multiple backends](#multiple-backends). @@ -512,7 +512,7 @@ on a computerd instance. | `@cloudflare/computer/backends/container` | `ContainerBackend` and `withWorkspaceContainer`. Pulls in the computerd / capnweb sync plumbing. | | `@cloudflare/computer/backends/container-legacy` | Deprecated. `LegacyContainerBackend` and `withLegacyWorkspaceContainer`; use `@cloudflare/computer/backends/container` instead. | | `@cloudflare/computer/backends/worker-shell` | `WorkerShellBackend` and the bundled just-bash runtime. | -| `@cloudflare/computer/backends/worker-javascript` | `WorkerJavaScriptBackend`, configured libraries, durable imports, `node:fs/promises`, and host modules. | +| `@cloudflare/computer/backends/codemode` | `CodemodeBackend`, configured libraries, durable imports, `node:fs/promises`, and host modules. | | `@cloudflare/computer/modules/container` | `createContainerModule()` for `codemode:container`: run container commands from isolate JavaScript. | | `@cloudflare/computer/modules/git` | `createGitModule()` for `codemode:git`: confined Git from isolate JavaScript. | | `@cloudflare/computer/modules/artifacts` | `createArtifactsModule()` for `codemode:artifacts`: Artifacts and git tokens from isolate JavaScript. | @@ -541,9 +541,10 @@ with the in-container service live in the sibling package ### Multiple backends A Workspace can carry more than one backend, each registered under a -stable selector `id` (defaulting to `"worker-shell"`, -`"container-shell"`, or `"worker-javascript"`). `runtime.exec` uses the -first backend unless the caller names one: +stable selector `id`. `WorkerShellBackend` defaults to `"worker-shell"`, +`ContainerBackend` to `"container-shell"`, and `CodemodeBackend` to +`"codemode"`. `runtime.exec` uses the first backend unless the caller +names one: ```ts const ws = new Workspace({ @@ -564,7 +565,7 @@ backend, which is handy from an agent's `onStart`. Per-backend sync cursors are independent, so activity on one never disturbs the other. A backend that accepts a structured `input` and returns a structured -`value` declares `callable: true` (the worker-javascript backend does). +`value` declares `callable: true` (the codemode backend does). Passing `input` to a non-callable backend is a clear error rather than a silent drop. @@ -609,7 +610,7 @@ An adapter for the Cloudflare runtime lives at - [`examples/worker-shell`](../../examples/worker-shell) — the worker-shell backend behind a `write` / `read` / `exec` HTTP surface. No container. -- [`examples/worker-javascript`](../../examples/worker-javascript) — the +- [`examples/codemode`](../../examples/codemode) — the same shape, running ECMAScript modules instead of shell commands. - [`examples/container`](../../examples/container) — the container backend running `computerd`. diff --git a/packages/computer/package.json b/packages/computer/package.json index 0334e3d10..9dd3f3eae 100644 --- a/packages/computer/package.json +++ b/packages/computer/package.json @@ -83,9 +83,9 @@ "types": "./dist/backends/container/index.d.ts", "import": "./dist/backends/container/index.js" }, - "./backends/worker-javascript": { - "types": "./dist/backends/worker-javascript/index.d.ts", - "import": "./dist/backends/worker-javascript/index.js" + "./backends/codemode": { + "types": "./dist/backends/codemode/index.d.ts", + "import": "./dist/backends/codemode/index.js" }, "./backends/worker-shell": { "types": "./dist/backends/worker-shell/index.d.ts", diff --git a/packages/computer/src/backends/worker-javascript/worker-javascript.test.ts b/packages/computer/src/backends/codemode/codemode.test.ts similarity index 93% rename from packages/computer/src/backends/worker-javascript/worker-javascript.test.ts rename to packages/computer/src/backends/codemode/codemode.test.ts index 6afb7ee62..087b3e230 100644 --- a/packages/computer/src/backends/worker-javascript/worker-javascript.test.ts +++ b/packages/computer/src/backends/codemode/codemode.test.ts @@ -5,8 +5,8 @@ import { describe, expect, it, vi } from "vitest"; import { createCodemodeModule, type WorkspaceModuleScopeEnd } from "../../runtime/codemode-module"; import type { WorkspaceRuntimeLoader } from "../../runtime/types"; import { Workspace } from "../../workspace"; +import { runtimeCode, CodemodeBackend } from "./codemode"; import { ExecutionJournal, fingerprint, loadResume, type ResumeStart, saveResume } from "./journal"; -import { runtimeCode, WorkerJavaScriptBackend } from "./worker-javascript"; function throwingLoader(message: string) { return { @@ -45,7 +45,7 @@ async function evaluateResult( await host.attachOutput(readable); } -describe("WorkerJavaScriptBackend", () => { +describe("CodemodeBackend", () => { it("blocks ambient egress by default", async () => { const load = vi.fn(() => ({ getEntrypoint() { @@ -62,7 +62,7 @@ describe("WorkerJavaScriptBackend", () => { })); const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load } })], + backends: [new CodemodeBackend({ loader: { load } })], }); await workspace.fs.mkdir("/workspace", { recursive: true }); @@ -88,7 +88,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load }, egress: { mode: "direct" }, }), @@ -119,7 +119,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load }, egress: { mode: "http-gateway", gateway }, }), @@ -135,7 +135,7 @@ describe("WorkerJavaScriptBackend", () => { it("rejects globalOutbound together with egress", () => { expect( () => - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: throwingLoader("unused"), globalOutbound: null, egress: { mode: "none" }, @@ -146,14 +146,14 @@ describe("WorkerJavaScriptBackend", () => { it("validates timeout configuration", () => { expect( () => - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: throwingLoader("unused"), maxTimeoutMs: Number.NaN, }), ).toThrow(/positive finite/); expect( () => - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: throwingLoader("unused"), defaultTimeoutMs: -1, }), @@ -177,7 +177,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load }, maxCapabilityBytes: 256, }), @@ -199,7 +199,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load() { return { @@ -246,7 +246,7 @@ describe("WorkerJavaScriptBackend", () => { VALUES ('isolate-javascript', 'legacy', 'completed')`, ); const fs = new WorkspaceFilesystem(db); - const backend = new WorkerJavaScriptBackend({ loader: throwingLoader("unused") }); + const backend = new CodemodeBackend({ loader: throwingLoader("unused") }); await backend.connect({ db, fs, @@ -280,7 +280,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load }, maxInputBytes: 8, maxResultBytes: 8, @@ -313,7 +313,7 @@ describe("WorkerJavaScriptBackend", () => { await blocked; return readFile(...args); }) as typeof fs.readFile; - const backend = new WorkerJavaScriptBackend({ loader: throwingLoader("must not load") }); + const backend = new CodemodeBackend({ loader: throwingLoader("must not load") }); const handle = await backend.connect({ db, fs, @@ -341,7 +341,7 @@ describe("WorkerJavaScriptBackend", () => { const load = vi.fn(); const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load }, maxStdinBytes: 8 })], + backends: [new CodemodeBackend({ loader: { load }, maxStdinBytes: 8 })], }); await workspace.fs.mkdir("/workspace", { recursive: true }); await expect( @@ -354,7 +354,7 @@ describe("WorkerJavaScriptBackend", () => { const load = vi.fn(); const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load }, maxEnvBytes: 8 })], + backends: [new CodemodeBackend({ loader: { load }, maxEnvBytes: 8 })], }); await workspace.fs.mkdir("/workspace", { recursive: true }); await expect( @@ -367,7 +367,7 @@ describe("WorkerJavaScriptBackend", () => { const load = vi.fn(); const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load } })], + backends: [new CodemodeBackend({ loader: { load } })], }); await workspace.fs.mkdir("/workspace", { recursive: true }); await expect( @@ -382,7 +382,7 @@ describe("WorkerJavaScriptBackend", () => { const load = vi.fn(); const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load } })], + backends: [new CodemodeBackend({ loader: { load } })], }); await workspace.fs.mkdir("/workspace", { recursive: true }); await expect( @@ -395,7 +395,7 @@ describe("WorkerJavaScriptBackend", () => { const load = vi.fn(); const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load } })], + backends: [new CodemodeBackend({ loader: { load } })], }); await workspace.fs.mkdir("/workspace", { recursive: true }); await expect( @@ -415,7 +415,7 @@ describe("WorkerJavaScriptBackend", () => { const load = vi.fn(); const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load }, maxSourceBytes: 128 })], + backends: [new CodemodeBackend({ loader: { load }, maxSourceBytes: 128 })], }); await workspace.fs.mkdir("/workspace", { recursive: true }); const execution = await workspace.runtime.exec("export default 1", { encoding: "utf8" }); @@ -430,14 +430,14 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: throwingLoader("loader failed"), }), ], }); await workspace.fs.mkdir("/workspace", { recursive: true }); const handle = await workspace.runtime.exec("export default () => 1", { - backend: "worker-javascript", + backend: "codemode", id: "startup-failure", encoding: "utf8", }); @@ -447,7 +447,7 @@ describe("WorkerJavaScriptBackend", () => { stderr: expect.stringContaining("loader failed"), }); const replay = await workspace.runtime.getExec("startup-failure", { - backend: "worker-javascript", + backend: "codemode", encoding: "utf8", }); await expect(replay.result()).resolves.toMatchObject({ status: "failed", exitCode: 1 }); @@ -468,20 +468,20 @@ describe("WorkerJavaScriptBackend", () => { }; const first = new Workspace({ storage, - backends: [new WorkerJavaScriptBackend({ loader })], + backends: [new CodemodeBackend({ loader })], }); await first.fs.mkdir("/workspace", { recursive: true }); await first.runtime.exec("export default async () => new Promise(() => {})", { - backend: "worker-javascript", + backend: "codemode", id: "interrupted", }); const recreated = new Workspace({ storage, - backends: [new WorkerJavaScriptBackend({ loader })], + backends: [new CodemodeBackend({ loader })], }); const replay = await recreated.runtime.getExec("interrupted", { - backend: "worker-javascript", + backend: "codemode", encoding: "utf8", }); await expect(replay.result()).resolves.toMatchObject({ @@ -496,7 +496,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load() { return { @@ -536,7 +536,7 @@ describe("WorkerJavaScriptBackend", () => { await writeReleased; return originalWrite(...args); }) as typeof fs.writeFile; - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ loader: { load() { return { @@ -592,7 +592,7 @@ describe("WorkerJavaScriptBackend", () => { releaseExit = resolve; }); const encoder = new TextEncoder(); - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ loader: { load() { return { @@ -649,7 +649,7 @@ describe("WorkerJavaScriptBackend", () => { await fs.mkdir("/workspace", { recursive: true }); const encoder = new TextEncoder(); let streamController!: ReadableStreamDefaultController; - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ loader: { load() { return { @@ -712,7 +712,7 @@ describe("WorkerJavaScriptBackend", () => { const fs = new WorkspaceFilesystem(db); await fs.mkdir("/workspace", { recursive: true }); const encoder = new TextEncoder(); - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ loader: { load() { return { @@ -764,7 +764,7 @@ describe("WorkerJavaScriptBackend", () => { const fs = new WorkspaceFilesystem(db); await fs.mkdir("/workspace", { recursive: true }); let aborted = false; - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ maxHostCallMs: 5, modules: { "codemode:test": { @@ -828,7 +828,7 @@ describe("WorkerJavaScriptBackend", () => { await writeReleased; return originalWrite(...args); }) as typeof fs.writeFile; - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ loader: { load() { return { @@ -889,7 +889,7 @@ describe("WorkerJavaScriptBackend", () => { const evaluation = new Promise<{ result: number }>((resolve) => { finish = resolve; }); - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ loader: { load() { return { @@ -939,7 +939,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: throwingLoader("finished"), maxRetainedExecutions: 1, }), @@ -961,7 +961,7 @@ describe("WorkerJavaScriptBackend", () => { const load = vi.fn(); const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load } })], + backends: [new CodemodeBackend({ loader: { load } })], }); await workspace.fs.mkdir("/workspace", { recursive: true }); await expect(workspace.runtime.exec("export default 1", { cwd: "/outside" })).rejects.toThrow( @@ -978,7 +978,7 @@ describe("WorkerJavaScriptBackend", () => { initializeSchema(db, () => 0); const fs = new WorkspaceFilesystem(db); await fs.mkdir("/workspace", { recursive: true }); - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ maxExecutionSubscribers: 2, loader: { load() { @@ -1052,7 +1052,7 @@ describe("WorkerJavaScriptBackend", () => { ])("rejects %s at construction", (_label, modules, message) => { expect( () => - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: throwingLoader("must not load"), // SAFETY: Each case hands the constructor a shape the types may forbid, to check its runtime guard. modules: modules as never, @@ -1063,7 +1063,7 @@ describe("WorkerJavaScriptBackend", () => { it("rejects a factory whose functions are not allowed when it connects", async () => { const db = new Database(new SQLiteTestStorage()); initializeSchema(db, () => 0); - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ loader: throwingLoader("must not load"), modules: { "codemode:test": () => ({ "not-a-name": async () => null }) }, }); @@ -1079,7 +1079,7 @@ describe("WorkerJavaScriptBackend", () => { }); it("describes its modules for a model", () => { - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ loader: throwingLoader("must not load"), access: "read", modules: { @@ -1101,7 +1101,7 @@ describe("WorkerJavaScriptBackend", () => { }); it("lists the Node.js built-ins code can import", () => { - const backend = new WorkerJavaScriptBackend({ loader: throwingLoader("must not load") }); + const backend = new CodemodeBackend({ loader: throwingLoader("must not load") }); expect(backend.description).toContain( "- Node.js built-ins: `node:path`, `node:url`, `node:util`,", @@ -1110,7 +1110,7 @@ describe("WorkerJavaScriptBackend", () => { }); it("lists no Node.js built-ins without the nodejs_compat flag", () => { - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ loader: throwingLoader("must not load"), compatibilityFlags: [], }); @@ -1119,7 +1119,7 @@ describe("WorkerJavaScriptBackend", () => { }); it("tells a model to put the work in a default-exported function", () => { - const backend = new WorkerJavaScriptBackend({ loader: throwingLoader("must not load") }); + const backend = new CodemodeBackend({ loader: throwingLoader("must not load") }); expect(backend.description).toContain( "Put the work in `export default async function (input) { ... }` and call `node:fs` and the other modules below inside it, since the module's top level can't do I/O. To run a file you've already written, re-export it: `export { default } from \"./main.js\"`.", @@ -1131,7 +1131,7 @@ describe("WorkerJavaScriptBackend", () => { const factory = Object.defineProperty(() => ({ run: () => null }), "description", { get: () => `Tools: ${names.join(", ")}.`, }); - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ loader: throwingLoader("must not load"), modules: { "codemode:tools": factory }, }); @@ -1146,7 +1146,7 @@ describe("WorkerJavaScriptBackend", () => { initializeSchema(db, () => 0); const git = { marker: "git" }; let seen: unknown; - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ loader: throwingLoader("must not load"), modules: { "codemode:test": (host) => { @@ -1170,7 +1170,7 @@ describe("WorkerJavaScriptBackend", () => { initializeSchema(db, () => 0); const assets = { marker: "assets" }; const seen: unknown[] = []; - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ loader: throwingLoader("must not load"), modules: { "codemode:test": (host) => { @@ -1197,7 +1197,7 @@ describe("WorkerJavaScriptBackend", () => { const fs = new WorkspaceFilesystem(db); await fs.mkdir("/workspace", { recursive: true }); let response: unknown; - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ modules: { "codemode:test": { run: async () => null } }, loader: { load() { @@ -1236,7 +1236,7 @@ describe("WorkerJavaScriptBackend", () => { it("does not install codemode:git or codemode:artifacts unless they are configured", async () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: throwingLoader("must not load") })], + backends: [new CodemodeBackend({ loader: throwingLoader("must not load") })], }); await workspace.fs.mkdir("/workspace", { recursive: true }); for (const specifier of ["codemode:git", "codemode:artifacts"]) { @@ -1255,7 +1255,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load() { return { @@ -1302,7 +1302,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ ...options, loader: { load() { @@ -1381,7 +1381,7 @@ describe("WorkerJavaScriptBackend", () => { const setIntervalSpy = vi.spyOn(globalThis, "setInterval"); const clearIntervalSpy = vi.spyOn(globalThis, "clearInterval"); try { - const backend = new WorkerJavaScriptBackend({ + const backend = new CodemodeBackend({ maxTimeoutMs: 10_000, maxHostCallMs: 5_000, loader: { @@ -1542,8 +1542,7 @@ describe("WorkerJavaScriptBackend", () => { it("rejects a keepAliveIntervalMs that is negative, non-finite, or 15 minutes or more", () => { for (const keepAliveIntervalMs of [-1, Number.NaN, Number.POSITIVE_INFINITY, 900_000]) { expect( - () => - new WorkerJavaScriptBackend({ loader: throwingLoader("unused"), keepAliveIntervalMs }), + () => new CodemodeBackend({ loader: throwingLoader("unused"), keepAliveIntervalMs }), ).toThrow(/keepAliveIntervalMs/); } }); @@ -1570,7 +1569,7 @@ describe("WorkerJavaScriptBackend", () => { const load = recordingLoader(); const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load } })], + backends: [new CodemodeBackend({ loader: { load } })], }); await workspace.fs.mkdir("/workspace", { recursive: true }); await (await workspace.runtime.exec("export default null", { timeoutMs: 45_000 })).result(); @@ -1583,7 +1582,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load }, maxTimeoutMs: 60 * 60_000, maxCpuMs: 300_000, @@ -1599,9 +1598,9 @@ describe("WorkerJavaScriptBackend", () => { }); it("rejects a maxCpuMs that is not positive and finite", () => { - expect( - () => new WorkerJavaScriptBackend({ loader: throwingLoader("unused"), maxCpuMs: 0 }), - ).toThrow(/maxCpuMs/); + expect(() => new CodemodeBackend({ loader: throwingLoader("unused"), maxCpuMs: 0 })).toThrow( + /maxCpuMs/, + ); }); }); @@ -1609,7 +1608,7 @@ describe("WorkerJavaScriptBackend", () => { const load = vi.fn(); const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load }, root: "/" })], + backends: [new CodemodeBackend({ loader: { load }, root: "/" })], }); await workspace.fs.writeFile("/workspace-capabilities.js", "export const stolen = true"); await expect( @@ -1625,7 +1624,7 @@ describe("WorkerJavaScriptBackend", () => { (specifier) => { expect( () => - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: throwingLoader("must not load"), modules: { [specifier]: "export default 42" }, }), @@ -1650,7 +1649,7 @@ describe("WorkerJavaScriptBackend", () => { it("is created on the first run in a fresh Workspace", async () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader })], + backends: [new CodemodeBackend({ loader })], }); const execution = await workspace.runtime.exec("export default () => null;"); @@ -1662,7 +1661,7 @@ describe("WorkerJavaScriptBackend", () => { it("is left alone by a read-only backend", async () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader, access: "read" })], + backends: [new CodemodeBackend({ loader, access: "read" })], }); const execution = await workspace.runtime.exec("export default () => null;"); @@ -1701,7 +1700,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load }, modules: { large, "codemode:echo": { run: async () => null } }, }), @@ -1738,7 +1737,7 @@ describe("WorkerJavaScriptBackend", () => { const load = completingLoader(); const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load } })], + backends: [new CodemodeBackend({ loader: { load } })], }); const execution = await workspace.runtime.exec( @@ -1756,7 +1755,7 @@ describe("WorkerJavaScriptBackend", () => { const load = completingLoader(); const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load } })], + backends: [new CodemodeBackend({ loader: { load } })], }); const execution = await workspace.runtime.exec( @@ -1775,7 +1774,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load }, modules: { events: "export const mine = true;" }, }), @@ -1798,7 +1797,7 @@ describe("WorkerJavaScriptBackend", () => { async (specifier) => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load: completingLoader() } })], + backends: [new CodemodeBackend({ loader: { load: completingLoader() } })], }); await expect( @@ -1811,7 +1810,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load: completingLoader() }, compatibilityFlags: [], }), @@ -1828,7 +1827,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load }, modules: { "codemode:tools": () => ({}), "codemode:other": { run: async () => null } }, }), @@ -1854,7 +1853,7 @@ describe("WorkerJavaScriptBackend", () => { it("rejects a module with no default export before loading it", async () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: throwingLoader("must not load") })], + backends: [new CodemodeBackend({ loader: throwingLoader("must not load") })], }); await expect(workspace.runtime.exec(`const x = 1;`)).rejects.toThrow( @@ -1872,7 +1871,7 @@ describe("WorkerJavaScriptBackend", () => { ])("runs a module with %s", async (_label, source) => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load: completingLoader() } })], + backends: [new CodemodeBackend({ loader: { load: completingLoader() } })], }); await workspace.fs.mkdir("/workspace", { recursive: true }); await workspace.fs.writeFile("/workspace/main.js", "export default 1;"); @@ -1885,7 +1884,7 @@ describe("WorkerJavaScriptBackend", () => { const load = completingLoader(); const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load } })], + backends: [new CodemodeBackend({ loader: { load } })], }); await workspace.fs.mkdir("/workspace/shared", { recursive: true }); await workspace.fs.writeFile("/workspace/shared/util.js", "export const value = 1;"); @@ -1907,7 +1906,7 @@ describe("WorkerJavaScriptBackend", () => { const load = vi.fn(); const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load } })], + backends: [new CodemodeBackend({ loader: { load } })], }); await workspace.fs.mkdir("/outside", { recursive: true }); await workspace.fs.writeFile("/outside/secret.js", "export default 1;"); @@ -1924,7 +1923,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load }, root: "/", modules: { lib: "export default 1;" }, @@ -1960,7 +1959,7 @@ describe("WorkerJavaScriptBackend", () => { const load = vi.fn(); const workspace = new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: { load }, modules: { lib: imports } })], + backends: [new CodemodeBackend({ loader: { load }, modules: { lib: imports } })], }); await workspace.fs.mkdir("/workspace", { recursive: true }); @@ -1975,7 +1974,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load }, modules: { "codemode:facade": `import { run } from "codemode:echo"; import { open } from "codemode:browser"; export const echo = { run, open };`, @@ -2003,7 +2002,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load }, modules: { "codemode:a": `import { value } from "codemode:b"; export { value };`, @@ -2031,7 +2030,7 @@ describe("WorkerJavaScriptBackend", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load }, modules: { base: "export const value = 1;", @@ -2054,7 +2053,7 @@ describe("WorkerJavaScriptBackend", () => { }); }); -describe("WorkerJavaScriptBackend interrupted executions", () => { +describe("CodemodeBackend interrupted executions", () => { const hanging = { load() { return { @@ -2067,11 +2066,11 @@ describe("WorkerJavaScriptBackend interrupted executions", () => { function open( storage: SQLiteTestStorage, - options: Partial[0]> = {}, + options: Partial[0]> = {}, ) { return new Workspace({ storage, - backends: [new WorkerJavaScriptBackend({ loader: hanging, ...options })], + backends: [new CodemodeBackend({ loader: hanging, ...options })], }); } @@ -2083,7 +2082,7 @@ describe("WorkerJavaScriptBackend interrupted executions", () => { const workspace = open(storage); await workspace.fs.mkdir("/workspace", { recursive: true }); await workspace.runtime.exec("export default 1", { - backend: "worker-javascript", + backend: "codemode", id: "run", onInterrupt: "replay", ...(timeoutMs === undefined ? {} : { timeoutMs }), @@ -2095,10 +2094,10 @@ describe("WorkerJavaScriptBackend interrupted executions", () => { // Rewrite the run's saved start as a version without metadata wrote it. function forgetMetadata(storage: SQLiteTestStorage) { const db = new Database(storage); - const saved = loadResume(db, "worker-javascript", "run"); + const saved = loadResume(db, "codemode", "run"); if (saved === undefined) throw new Error("expected a saved run"); const { metadata: _metadata, ...start } = saved.start; - saveResume(db, "worker-javascript", "run", saved.policy, start as ResumeStart); + saveResume(db, "codemode", "run", saved.policy, start as ResumeStart); } // A loader whose runs call codemode:test.who once and then hang, so the run @@ -2136,7 +2135,7 @@ describe("WorkerJavaScriptBackend interrupted executions", () => { async function settle(workspace: Workspace) { const handle = await workspace.runtime.getExec("run", { - backend: "worker-javascript", + backend: "codemode", encoding: "utf8", }); return handle.result(); @@ -2148,7 +2147,7 @@ describe("WorkerJavaScriptBackend interrupted executions", () => { const workspace = new Workspace({ storage, backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ onInterrupt: "replay", loader: { load() { @@ -2192,7 +2191,7 @@ describe("WorkerJavaScriptBackend interrupted executions", () => { }); await workspace.fs.mkdir("/workspace", { recursive: true }); const handle = await workspace.runtime.exec("export default 1", { - backend: "worker-javascript", + backend: "codemode", }); await expect(handle.result()).resolves.toMatchObject({ status: "completed" }); expect(whileRunning).toEqual([[{ position: 0, events: "[-1]" }], [{ name: "stdout" }]]); @@ -2223,12 +2222,12 @@ describe("WorkerJavaScriptBackend interrupted executions", () => { const workspace = open(storage); await workspace.runtime.recover(); workspaces.push(workspace); - return loadResume(db, "worker-javascript", "run")?.attempt; + return loadResume(db, "codemode", "run")?.attempt; }; expect(await recover()).toBe(2); expect(await recover()).toBe(3); // The run journals a call, so its next interruption starts the count again. - const journal = new ExecutionJournal(db, "worker-javascript", "run"); + const journal = new ExecutionJournal(db, "codemode", "run"); const step = journal.begin("fs.readFile", ["/a"]); if (step.kind !== "live") throw new Error("expected a live call"); journal.commit(step.seq, { result: "a" }); @@ -2251,16 +2250,16 @@ describe("WorkerJavaScriptBackend interrupted executions", () => { await workspace.runtime.recover(); workspaces.push(workspace); } - expect(loadResume(db, "worker-javascript", "run")?.attempt).toBe(3); + expect(loadResume(db, "codemode", "run")?.attempt).toBe(3); // The run's next call waits for an answer. - const journal = new ExecutionJournal(db, "worker-javascript", "run"); + const journal = new ExecutionJournal(db, "codemode", "run"); const step = journal.begin("host/codemode:test.approve", []); if (step.kind !== "live") throw new Error("expected a live call"); journal.interrupt(step.seq, { kind: "approval" }); const answering = open(storage); workspaces.push(answering); await answering.runtime.respond("run", `run:${step.seq}`, true); - expect(loadResume(db, "worker-javascript", "run")?.attempt).toBe(1); + expect(loadResume(db, "codemode", "run")?.attempt).toBe(1); for (const workspace of workspaces) await workspace.close(); }); @@ -2298,7 +2297,7 @@ describe("WorkerJavaScriptBackend interrupted executions", () => { const db = new Database(storage); const first = await interrupt(storage, undefined, metadata); if (metadata === undefined) forgetMetadata(storage); - const journal = new ExecutionJournal(db, "worker-javascript", "run"); + const journal = new ExecutionJournal(db, "codemode", "run"); const step = journal.begin("host/codemode:test.approve", []); if (step.kind !== "live") throw new Error("expected a live call"); journal.interrupt(step.seq, { kind: "approval" }); @@ -2354,7 +2353,7 @@ describe("WorkerJavaScriptBackend interrupted executions", () => { const workspace = new Workspace({ storage, backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ onInterrupt: "replay", loader: { load() { @@ -2383,7 +2382,7 @@ describe("WorkerJavaScriptBackend interrupted executions", () => { }); await workspace.fs.mkdir("/workspace", { recursive: true }); const handle = await workspace.runtime.exec("export default 1", { - backend: "worker-javascript", + backend: "codemode", }); await expect(handle.result()).resolves.toMatchObject({ status: "completed", value: 1 }); expect(whileRunning).toEqual([ @@ -2406,7 +2405,7 @@ describe("WorkerJavaScriptBackend interrupted executions", () => { await first.fs.mkdir("/workspace", { recursive: true }); for (let index = 0; index < 6; index += 1) { await first.runtime.exec("export default 1", { - backend: "worker-javascript", + backend: "codemode", id: `run-${index}`, onInterrupt: "replay", }); @@ -2425,27 +2424,27 @@ describe("WorkerJavaScriptBackend interrupted executions", () => { await new Promise((resolve) => setTimeout(resolve, 20)); expect(launched).toBe(4); // Each run that settles lets a waiting one start. - await later.runtime.killExec("run-0", { backend: "worker-javascript" }); + await later.runtime.killExec("run-0", { backend: "codemode" }); await vi.waitFor(() => expect(launched).toBe(5)); await later.close(); await first.close(); }); it("rejects an unknown onInterrupt policy", async () => { - expect( - () => new WorkerJavaScriptBackend({ loader: hanging, onInterrupt: "maybe" as never }), - ).toThrow('onInterrupt must be "fail", "restart" or "replay".'); + expect(() => new CodemodeBackend({ loader: hanging, onInterrupt: "maybe" as never })).toThrow( + 'onInterrupt must be "fail", "restart" or "replay".', + ); const workspace = open(new SQLiteTestStorage()); await expect( workspace.runtime.exec("export default 1", { - backend: "worker-javascript", + backend: "codemode", onInterrupt: "maybe" as never, }), ).rejects.toThrow("onInterrupt must be"); await workspace.close(); }); }); -describe("WorkerJavaScriptBackend scoped contexts", () => { +describe("CodemodeBackend scoped contexts", () => { interface Script { // Host functions the run calls in turn, such as "host/codemode:test.run". calls?: string[]; @@ -2502,19 +2501,19 @@ describe("WorkerJavaScriptBackend scoped contexts", () => { function open( script: Script, - modules: ConstructorParameters[0]["modules"], - options: Partial[0]> = {}, + modules: ConstructorParameters[0]["modules"], + options: Partial[0]> = {}, ) { return new Workspace({ storage: new SQLiteTestStorage(), - backends: [new WorkerJavaScriptBackend({ loader: scripted(script), modules, ...options })], + backends: [new CodemodeBackend({ loader: scripted(script), modules, ...options })], }); } async function start(workspace: Workspace, options: { onInterrupt?: "replay" } = {}) { await workspace.fs.mkdir("/workspace", { recursive: true }); return workspace.runtime.exec("export default 1", { - backend: "worker-javascript", + backend: "codemode", id: "run", encoding: "utf8", ...options, @@ -2647,7 +2646,7 @@ describe("WorkerJavaScriptBackend scoped contexts", () => { const handle = await start(workspace); await starting.opened; - await workspace.runtime.killExec("run", { backend: "worker-javascript" }); + await workspace.runtime.killExec("run", { backend: "codemode" }); await expect(handle.result()).resolves.toMatchObject({ status: "cancelled" }); await workspace.close(); @@ -2693,7 +2692,7 @@ describe("WorkerJavaScriptBackend scoped contexts", () => { }); await cleaning.opened; - const killed = workspace.runtime.killExec("run", { backend: "worker-javascript" }); + const killed = workspace.runtime.killExec("run", { backend: "codemode" }); await idle(); expect(steps).toEqual(["cleanup starts"]); expect(exited).toBe(false); @@ -2722,7 +2721,7 @@ describe("WorkerJavaScriptBackend scoped contexts", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load: () => ({ getEntrypoint: () => ({ evaluate }) }) }, modules: { "codemode:test": { @@ -2740,7 +2739,7 @@ describe("WorkerJavaScriptBackend scoped contexts", () => { await interrupting.opened; await idle(); - await workspace.runtime.killExec("run", { backend: "worker-javascript" }); + await workspace.runtime.killExec("run", { backend: "codemode" }); await expect(handle.result()).resolves.toMatchObject({ status: expect.stringMatching(/^(completed|cancelled)$/), }); diff --git a/packages/computer/src/backends/worker-javascript/worker-javascript.ts b/packages/computer/src/backends/codemode/codemode.ts similarity index 98% rename from packages/computer/src/backends/worker-javascript/worker-javascript.ts rename to packages/computer/src/backends/codemode/codemode.ts index e4535466a..78aa145d6 100644 --- a/packages/computer/src/backends/worker-javascript/worker-javascript.ts +++ b/packages/computer/src/backends/codemode/codemode.ts @@ -64,7 +64,7 @@ import { prepareSourceModules, } from "./module-graph"; -export interface WorkerJavaScriptBackendOptions { +export interface CodemodeBackendOptions { loader: WorkspaceRuntimeLoader; id?: string; root?: string; @@ -202,9 +202,9 @@ export interface WorkerJavaScriptBackendOptions { globalOutbound?: Fetcher | null; } -type ResolvedWorkerJavaScriptBackendOptions = Required< +type ResolvedCodemodeBackendOptions = Required< Pick< - WorkerJavaScriptBackendOptions, + CodemodeBackendOptions, | "root" | "access" | "defaultTimeoutMs" @@ -232,7 +232,7 @@ type ResolvedWorkerJavaScriptBackendOptions = Required< | "compatibilityFlags" > > & - Omit & { + Omit & { egress: WorkspaceEgressPolicy; modules: ParsedModules; }; @@ -306,18 +306,18 @@ interface ExecutionRecord { instances?: Map>; } -export class WorkerJavaScriptBackend implements WorkspaceModuleBackend { +export class CodemodeBackend implements WorkspaceModuleBackend { readonly protocol = "module" as const; - readonly type = "worker-javascript"; + readonly type = "codemode"; readonly callable = true; readonly id: string; - readonly #options: ResolvedWorkerJavaScriptBackendOptions; + readonly #options: ResolvedCodemodeBackendOptions; readonly #header: string; - constructor(options: WorkerJavaScriptBackendOptions) { - this.id = options.id ?? "worker-javascript"; + constructor(options: CodemodeBackendOptions) { + this.id = options.id ?? "codemode"; if (options.egress !== undefined && options.globalOutbound !== undefined) { - throw new Error("WorkerJavaScriptBackend cannot use globalOutbound together with egress."); + throw new Error("CodemodeBackend cannot use globalOutbound together with egress."); } const maxTimeoutMs = options.maxTimeoutMs ?? 180_000; const defaultTimeoutMs = options.defaultTimeoutMs ?? Math.min(60_000, maxTimeoutMs); @@ -335,9 +335,7 @@ export class WorkerJavaScriptBackend implements WorkspaceModuleBackend { keepAliveIntervalMs < 0 || keepAliveIntervalMs >= 900_000 ) { - throw new Error( - "WorkerJavaScriptBackend keepAliveIntervalMs must be zero or less than 15 minutes.", - ); + throw new Error("CodemodeBackend keepAliveIntervalMs must be zero or less than 15 minutes."); } assertPositiveFinite(options.maxSourceBytes ?? 1024 * 1024, "maxSourceBytes"); assertPositiveFinite(options.maxInputBytes ?? 1024 * 1024, "maxInputBytes"); @@ -365,14 +363,14 @@ export class WorkerJavaScriptBackend implements WorkspaceModuleBackend { assertPositiveFinite(options.retentionMs ?? 60 * 60_000, "retentionMs"); assertPositiveInteger(options.maxRetainedExecutions ?? 100, "maxRetainedExecutions"); if ((options.maxCapabilityBytes ?? 1024 * 1024) < 256) { - throw new Error("WorkerJavaScriptBackend maxCapabilityBytes must be at least 256 bytes."); + throw new Error("CodemodeBackend maxCapabilityBytes must be at least 256 bytes."); } const compatibilityDate = options.compatibilityDate ?? "2026-05-23"; if (!/^\d{4}-\d{2}-\d{2}$/.test(compatibilityDate)) { - throw new Error("WorkerJavaScriptBackend compatibilityDate must use YYYY-MM-DD."); + throw new Error("CodemodeBackend compatibilityDate must use YYYY-MM-DD."); } if (defaultTimeoutMs > maxTimeoutMs) { - throw new Error("WorkerJavaScriptBackend defaultTimeoutMs cannot exceed maxTimeoutMs."); + throw new Error("CodemodeBackend defaultTimeoutMs cannot exceed maxTimeoutMs."); } const { globalOutbound, egress, ...backendOptions } = options; const resolvedEgress = @@ -432,12 +430,12 @@ export class WorkerJavaScriptBackend implements WorkspaceModuleBackend { } async connect(host: WorkspaceModuleBackendHost): Promise { - return new JavaScriptBackendHandle(this.#options, host); + return new CodemodeBackendHandle(this.#options, host); } } -class JavaScriptBackendHandle implements WorkspaceModuleBackendHandle { - readonly #options: ResolvedWorkerJavaScriptBackendOptions; +class CodemodeBackendHandle implements WorkspaceModuleBackendHandle { + readonly #options: ResolvedCodemodeBackendOptions; readonly #host: WorkspaceModuleBackendHost; readonly #hostModuleFunctions: ReadonlyMap; readonly #emptyHostModules: ReadonlySet; @@ -462,7 +460,7 @@ class JavaScriptBackendHandle implements WorkspaceModuleBackendHandle { // Zero when the timer is off or can't keep this object in memory. readonly #keepAliveIntervalMs: number; - constructor(options: ResolvedWorkerJavaScriptBackendOptions, host: WorkspaceModuleBackendHost) { + constructor(options: ResolvedCodemodeBackendOptions, host: WorkspaceModuleBackendHost) { this.#options = options; this.#keepAliveIntervalMs = hostKeepsPendingWork() ? options.keepAliveIntervalMs : 0; this.#host = host; @@ -973,7 +971,7 @@ class JavaScriptBackendHandle implements WorkspaceModuleBackendHandle { } async exec(input: ModuleExecutionInput): Promise { - if (this.#closed) throw runtimeError("ECLOSED", "Workspace JavaScript backend is closed"); + if (this.#closed) throw runtimeError("ECLOSED", "CodemodeBackend is closed"); const id = input.id ?? crypto.randomUUID(); assertExecutionId(id); this.#prune(); @@ -1028,7 +1026,7 @@ class JavaScriptBackendHandle implements WorkspaceModuleBackendHandle { maxCapabilityBytes: this.#options.maxCapabilityBytes, }); if (this.#closed) { - throw runtimeError("ECLOSED", "Workspace JavaScript backend closed during module loading"); + throw runtimeError("ECLOSED", "CodemodeBackend closed during module loading"); } const record: ExecutionRecord = { id, @@ -1316,7 +1314,7 @@ class JavaScriptBackendHandle implements WorkspaceModuleBackendHandle { } get #backendId(): string { - return this.#options.id ?? "worker-javascript"; + return this.#options.id ?? "codemode"; } async #cancel(record: ExecutionRecord, message: string): Promise { @@ -2261,13 +2259,13 @@ function assertStringRecord( function assertPositiveFinite(value: number, name: string) { if (!Number.isFinite(value) || value <= 0) { - throw new Error(`WorkerJavaScriptBackend ${name} must be a positive finite number.`); + throw new Error(`CodemodeBackend ${name} must be a positive finite number.`); } } function assertPositiveInteger(value: number, name: string) { if (!Number.isSafeInteger(value) || value <= 0) { - throw new Error(`WorkerJavaScriptBackend ${name} must be a positive integer.`); + throw new Error(`CodemodeBackend ${name} must be a positive integer.`); } } diff --git a/packages/computer/src/backends/worker-javascript/durable-runtime.test.ts b/packages/computer/src/backends/codemode/durable-runtime.test.ts similarity index 100% rename from packages/computer/src/backends/worker-javascript/durable-runtime.test.ts rename to packages/computer/src/backends/codemode/durable-runtime.test.ts diff --git a/packages/computer/src/backends/worker-javascript/durable-runtime.ts b/packages/computer/src/backends/codemode/durable-runtime.ts similarity index 100% rename from packages/computer/src/backends/worker-javascript/durable-runtime.ts rename to packages/computer/src/backends/codemode/durable-runtime.ts diff --git a/packages/computer/src/backends/worker-javascript/frames.test.ts b/packages/computer/src/backends/codemode/frames.test.ts similarity index 100% rename from packages/computer/src/backends/worker-javascript/frames.test.ts rename to packages/computer/src/backends/codemode/frames.test.ts diff --git a/packages/computer/src/backends/worker-javascript/frames.ts b/packages/computer/src/backends/codemode/frames.ts similarity index 86% rename from packages/computer/src/backends/worker-javascript/frames.ts rename to packages/computer/src/backends/codemode/frames.ts index 3209155c6..b31ce6b1b 100644 --- a/packages/computer/src/backends/worker-javascript/frames.ts +++ b/packages/computer/src/backends/codemode/frames.ts @@ -14,13 +14,13 @@ export function parseRuntimeFrame(line: string): RuntimeFrame { try { record = JSON.parse(line) as Record; } catch { - throw new Error("WorkerJavaScriptBackend received an invalid execution frame"); + throw new Error("CodemodeBackend received an invalid execution frame"); } const frame = parseFrameBody(record); const log = record.log as { from?: unknown; events?: unknown } | undefined; if (log === undefined) return frame; if (log === null || typeof log !== "object") { - throw new Error("WorkerJavaScriptBackend received a malformed event log"); + throw new Error("CodemodeBackend received a malformed event log"); } return { ...frame, log: { from: log.from, events: log.events } }; } @@ -29,13 +29,13 @@ function parseFrameBody(record: Record): RuntimeFrame { const name = record.name; if (name === "stdout" || name === "stderr") { if (typeof record.b64 !== "string") { - throw new Error("WorkerJavaScriptBackend received a malformed output frame"); + throw new Error("CodemodeBackend received a malformed output frame"); } return { name, value: decodeBase64(record.b64) }; } if (name === "exit") { if (!Number.isSafeInteger(record.code)) { - throw new Error("WorkerJavaScriptBackend received a malformed exit frame"); + throw new Error("CodemodeBackend received a malformed exit frame"); } if ("result" in record) { return { @@ -46,7 +46,7 @@ function parseFrameBody(record: Record): RuntimeFrame { } return { name, code: record.code as number }; } - throw new Error("WorkerJavaScriptBackend received an unknown execution frame"); + throw new Error("CodemodeBackend received an unknown execution frame"); } export function decodeRuntimeFrames( diff --git a/packages/computer/src/backends/codemode/index.ts b/packages/computer/src/backends/codemode/index.ts new file mode 100644 index 000000000..0e661d5b3 --- /dev/null +++ b/packages/computer/src/backends/codemode/index.ts @@ -0,0 +1,9 @@ +// Public surface of @cloudflare/computer/backends/codemode. +// +// CodemodeBackend runs an ECMAScript module in a fresh Dynamic +// Worker against the Workspace's files. +// +// Status: stable. + +export type { WorkspaceEgressPolicy } from "../../runtime/egress"; +export { CodemodeBackend, type CodemodeBackendOptions } from "./codemode"; diff --git a/packages/computer/src/backends/worker-javascript/journal.test.ts b/packages/computer/src/backends/codemode/journal.test.ts similarity index 100% rename from packages/computer/src/backends/worker-javascript/journal.test.ts rename to packages/computer/src/backends/codemode/journal.test.ts diff --git a/packages/computer/src/backends/worker-javascript/journal.ts b/packages/computer/src/backends/codemode/journal.ts similarity index 99% rename from packages/computer/src/backends/worker-javascript/journal.ts rename to packages/computer/src/backends/codemode/journal.ts index 6067c56d4..6f22b6ed8 100644 --- a/packages/computer/src/backends/worker-javascript/journal.ts +++ b/packages/computer/src/backends/codemode/journal.ts @@ -1,4 +1,4 @@ -// Durable execution state for the JavaScript backend. +// Durable execution state for the codemode backend. // // An execution started with `onInterrupt: "restart"` or `"replay"` saves // what it needs to start again: its module graph, input, context and diff --git a/packages/computer/src/backends/worker-javascript/module-graph.ts b/packages/computer/src/backends/codemode/module-graph.ts similarity index 99% rename from packages/computer/src/backends/worker-javascript/module-graph.ts rename to packages/computer/src/backends/codemode/module-graph.ts index e0776dca1..7cb47f6dd 100644 --- a/packages/computer/src/backends/worker-javascript/module-graph.ts +++ b/packages/computer/src/backends/codemode/module-graph.ts @@ -331,7 +331,7 @@ export async function buildModuleGraph(options: BuildModuleGraphOptions) { continue; } throw new Error( - `Module ${JSON.stringify(specifier)} is not configured for the worker-javascript backend.`, + `Module ${JSON.stringify(specifier)} is not configured for the codemode backend.`, ); } modules[name] = rewriteImports(source, edits); diff --git a/packages/computer/src/backends/worker-javascript/index.ts b/packages/computer/src/backends/worker-javascript/index.ts deleted file mode 100644 index 2d05ecc0f..000000000 --- a/packages/computer/src/backends/worker-javascript/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Public surface of @cloudflare/computer/backends/worker-javascript. -// -// WorkerJavaScriptBackend runs an ECMAScript module in a fresh Dynamic -// Worker against the Workspace's files. -// -// Status: stable. - -export type { WorkspaceEgressPolicy } from "../../runtime/egress"; -export { WorkerJavaScriptBackend, type WorkerJavaScriptBackendOptions } from "./worker-javascript"; diff --git a/packages/computer/src/client.test.ts b/packages/computer/src/client.test.ts index 98e08e79e..58704bc36 100644 --- a/packages/computer/src/client.test.ts +++ b/packages/computer/src/client.test.ts @@ -11,7 +11,7 @@ import { SQLiteTestStorage } from "@cloudflare/dofs/testing"; import { describe, expect, it } from "vitest"; import { z } from "zod"; -import { WorkerJavaScriptBackend } from "./backends/worker-javascript/worker-javascript"; +import { CodemodeBackend } from "./backends/codemode/codemode"; import { getWorkspace } from "./client"; import type { WorkspaceBackendInfo } from "./runtime/runtime"; import type { WorkspaceModuleBackend } from "./runtime/types"; @@ -469,13 +469,13 @@ function echoBackend(): WorkspaceModuleBackend { } describe("getWorkspace — backend information", () => { - // A Workspace with one callable JavaScript backend that describes its + // A Workspace with one callable codemode backend that describes its // modules. The loader is never reached; only construction runs. - function workspaceWithJavaScript() { + function workspaceWithCodemode() { return new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load: () => ({ getEntrypoint: () => ({}) }) }, modules: { "codemode:weather": { forecast: () => null } }, }), @@ -556,17 +556,17 @@ describe("getWorkspace — backend information", () => { ], ] as const) { it(`answers backend questions on a ${path} client`, async () => { - const client = await connect(workspaceWithJavaScript()); + const client = await connect(workspaceWithCodemode()); const [backend, ...others] = client.runtime.backends(); expect(others).toEqual([]); - expect(backend).toMatchObject({ id: "worker-javascript", callable: true }); + expect(backend).toMatchObject({ id: "codemode", callable: true }); expect(backend?.description).toContain("`codemode:weather`: exports"); }); it(`builds a callable exec tool from a ${path} client`, async () => { - const client = await connect(workspaceWithJavaScript()); + const client = await connect(workspaceWithCodemode()); const tools = createAITools({ workspace: client }); const exec = tools.exec as { description?: string; inputSchema?: unknown } | undefined; if (!(exec?.inputSchema instanceof z.ZodType)) diff --git a/packages/computer/src/modules/browser.test.ts b/packages/computer/src/modules/browser.test.ts index 3b3d2c211..9589f2d3e 100644 --- a/packages/computer/src/modules/browser.test.ts +++ b/packages/computer/src/modules/browser.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { parseModules } from "../backends/worker-javascript/module-graph"; +import { parseModules } from "../backends/codemode/module-graph"; import { type CodemodeModule, codemodeModuleDefinition, diff --git a/packages/computer/src/modules/container.test.ts b/packages/computer/src/modules/container.test.ts index 6c3bab619..b440b0a3f 100644 --- a/packages/computer/src/modules/container.test.ts +++ b/packages/computer/src/modules/container.test.ts @@ -39,7 +39,7 @@ function fakeRuntime(output: { backends: () => [ { id: "container-shell", protocol: "command" as const, callable: false }, { id: "linux", protocol: "command" as const, callable: true }, - { id: "worker-javascript", protocol: "module" as const, callable: true }, + { id: "codemode", protocol: "module" as const, callable: true }, ], async exec(command: string, options: ExecOptions) { const run: Run = { command, options, killed: false }; @@ -322,7 +322,7 @@ describe("createContainerModule", () => { it("refuses a backend that runs module source", () => { const { runtime } = fakeRuntime({}); - expect(() => build(runtime, { backend: "worker-javascript" })).toThrow( + expect(() => build(runtime, { backend: "codemode" })).toThrow( /runs module source, not shell commands/, ); }); diff --git a/packages/computer/src/modules/container.ts b/packages/computer/src/modules/container.ts index ff3daca0b..90b497396 100644 --- a/packages/computer/src/modules/container.ts +++ b/packages/computer/src/modules/container.ts @@ -1,8 +1,8 @@ // `codemode:container`: lets isolate JavaScript run shell commands in the // Workspace's container backend. // -// Installed on a WorkerJavaScriptBackend, it turns the container into a -// library the JavaScript backend calls, rather than a second backend +// Installed on a CodemodeBackend, it turns the container into a +// library the codemode backend calls, rather than a second backend // the model has to choose between: // // import { exec } from "codemode:container"; @@ -64,7 +64,7 @@ export interface ContainerModuleOptions { * * A container command can write to the Workspace, so `exec` refuses to * run on a read-only backend. Network access follows the container - * backend's own egress setting; the JavaScript backend's does not apply. + * backend's own egress setting; the codemode backend's does not apply. * * @param options - Which backend to use and how much output to return. * @returns The module to pass as `modules["codemode:container"]`. Its @@ -102,7 +102,7 @@ export function createContainerModule( prelude === undefined || prelude.trim() === "" ? command : `${prelude}\n${command}`; const create = (host: WorkspaceModuleHost): WorkspaceModuleFunctions => { - // The factory runs when the JavaScript backend connects, so a + // The factory runs when the codemode backend connects, so a // missing or wrong container backend fails there, before any code // runs, rather than on the first exec. const target = host.runtime.backends().find((info) => info.id === backend); diff --git a/packages/computer/src/modules/git.ts b/packages/computer/src/modules/git.ts index 7ffb80d01..b05d97b18 100644 --- a/packages/computer/src/modules/git.ts +++ b/packages/computer/src/modules/git.ts @@ -2,7 +2,7 @@ // // import { status, diff, log, clone, cli } from "codemode:git"; // -// Every path the isolate passes is confined to the JavaScript backend's +// Every path the isolate passes is confined to the codemode backend's // root. Commands that change the repository need a read-write backend, // and commands that reach the network are denied unless the module is // created with `allowNetwork: true`: they run from the host, so the @@ -24,7 +24,7 @@ export interface GitModuleOptions { /** * Allow `clone` and network `cli` commands such as `fetch` and `push`. * Defaults to `false`. These requests run from the host, so the - * JavaScript backend's egress settings do not apply to them. + * codemode backend's egress settings do not apply to them. */ readonly allowNetwork?: boolean; } diff --git a/packages/computer/src/runtime/bridge-codemode.test.ts b/packages/computer/src/runtime/bridge-codemode.test.ts index e8f32dfd9..ec1400c4e 100644 --- a/packages/computer/src/runtime/bridge-codemode.test.ts +++ b/packages/computer/src/runtime/bridge-codemode.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { decodeValue, encodeValue } from "../backends/worker-javascript/journal"; +import { decodeValue, encodeValue } from "../backends/codemode/journal"; import { type BridgeJournal, type BridgeResponse, WorkspaceRuntimeBridge } from "./bridge"; import type { WorkspaceRuntimeCapability } from "./capability"; diff --git a/packages/computer/src/runtime/runtime.test.ts b/packages/computer/src/runtime/runtime.test.ts index a4cb8aa2b..72cf80dc4 100644 --- a/packages/computer/src/runtime/runtime.test.ts +++ b/packages/computer/src/runtime/runtime.test.ts @@ -204,13 +204,13 @@ describe("WorkspaceRuntime callable gate", () => { it("accepts structured input for a callable module backend", async () => { const handle = moduleHandleStub(); const runtime = new WorkspaceRuntime({ - backends: new Map([["worker-javascript", { callable: true }]]), + backends: new Map([["codemode", { callable: true }]]), backendHandle: async () => handle, - resolveBackendId: () => "worker-javascript", + resolveBackendId: () => "codemode", }); await runtime.exec("export default (i) => i", { - backend: "worker-javascript", + backend: "codemode", input: { n: 1 }, }); diff --git a/packages/computer/src/runtime/types.ts b/packages/computer/src/runtime/types.ts index 576249116..56dbfd6ea 100644 --- a/packages/computer/src/runtime/types.ts +++ b/packages/computer/src/runtime/types.ts @@ -117,7 +117,7 @@ export interface WorkspaceModuleFactory { (host: WorkspaceModuleHost): WorkspaceModuleFunctions; /** * What the module does and how to call it, for a model. The - * JavaScript backend adds it to its own description, which the exec + * codemode backend adds it to its own description, which the exec * tool shows. Objects of functions are listed by their export names. */ readonly description?: string; diff --git a/packages/computer/src/tools/ai-sdk/index.test.ts b/packages/computer/src/tools/ai-sdk/index.test.ts index 9262d7a9d..c86185d8d 100644 --- a/packages/computer/src/tools/ai-sdk/index.test.ts +++ b/packages/computer/src/tools/ai-sdk/index.test.ts @@ -1,7 +1,7 @@ import { SQLiteTestStorage } from "@cloudflare/dofs/testing"; import { describe, expect, it } from "vitest"; import { z } from "zod"; -import { WorkerJavaScriptBackend } from "../../backends/worker-javascript/worker-javascript"; +import { CodemodeBackend } from "../../backends/codemode/codemode"; import { createGitModule } from "../../modules/git"; import type { WorkspaceRuntimeExecHandle, WorkspaceRuntimeResult } from "../../runtime/types"; import { Workspace } from "../../workspace"; @@ -1348,7 +1348,7 @@ describe("createAITools exec tool", () => { storage: new SQLiteTestStorage(), backends: [ streamingCommandBackend([]) as never, - new WorkerJavaScriptBackend({ loader: { load: () => ({ getEntrypoint: () => ({}) }) } }), + new CodemodeBackend({ loader: { load: () => ({ getEntrypoint: () => ({}) }) } }), ], }); const tools = createAITools({ workspace }); @@ -1357,7 +1357,7 @@ describe("createAITools exec tool", () => { required?: string[]; }; - expect(schema.properties.backend?.enum).toEqual(["shell", "worker-javascript"]); + expect(schema.properties.backend?.enum).toEqual(["shell", "codemode"]); expect(schema.required).toContain("backend"); expect(toolDescription(tools.exec)).not.toMatch(/default backend/i); expect(toolDescription(tools.exec)).toContain('- "shell": Runs shell commands.'); @@ -1369,13 +1369,13 @@ describe("createAITools exec tool", () => { storage: new SQLiteTestStorage(), backends: [ streamingCommandBackend([]) as never, - new WorkerJavaScriptBackend({ loader: { load: () => ({ getEntrypoint: () => ({}) }) } }), + new CodemodeBackend({ loader: { load: () => ({ getEntrypoint: () => ({}) }) } }), ], }); - const listed = createAITools({ workspace, exec: { backends: { "worker-javascript": {} } } }); + const listed = createAITools({ workspace, exec: { backends: { codemode: {} } } }); const mapped = createAITools({ workspace, - exec: { backends: { "worker-javascript": { description: "Use for data work." }, shell: {} } }, + exec: { backends: { codemode: { description: "Use for data work." }, shell: {} } }, }); expect(inputProperties(listed.exec)).not.toContain("backend"); @@ -1813,12 +1813,12 @@ describe("createAITools callable exec", () => { }); }); -describe("createAITools exec against a real JavaScript backend", () => { +describe("createAITools exec against a real CodemodeBackend", () => { it("lists the backend's modules in the tool description", () => { const workspace = new Workspace({ storage: new SQLiteTestStorage(), backends: [ - new WorkerJavaScriptBackend({ + new CodemodeBackend({ loader: { load: () => ({ getEntrypoint: () => ({}) }) }, modules: { "tar-stream": "export default {};", @@ -1830,7 +1830,7 @@ describe("createAITools exec against a real JavaScript backend", () => { }); const tools = createAITools({ workspace, - shell: { backends: { "worker-javascript": {} } }, + shell: { backends: { codemode: {} } }, }); const description = toolDescription(tools.exec); @@ -1856,7 +1856,7 @@ describe("createAITools exec with one backend", () => { return { result: async () => ({ exitCode: 0, stdout: "", stderr: "", value: 1 }) }; }, backends: () => [ - { id: "worker-javascript", callable }, + { id: "codemode", callable }, { id: "shell", callable: false }, { id: "container", callable: false }, ], @@ -1869,7 +1869,7 @@ describe("createAITools exec with one backend", () => { const { calls, workspace } = recordingWorkspace(true); const tools = createAITools({ workspace, - shell: { backends: { "worker-javascript": { description: "Isolated JavaScript." } } }, + shell: { backends: { codemode: { description: "Isolated JavaScript." } } }, }); expect(inputProperties(tools.exec)).toEqual(["command", "cwd", "env", "input"]); @@ -1880,7 +1880,7 @@ describe("createAITools exec with one backend", () => { expect(parsed).toEqual({ command: "export default () => 1" }); await executeTool(tools.exec, parsed); expect(calls).toEqual([ - { command: "export default () => 1", backend: "worker-javascript", input: undefined }, + { command: "export default () => 1", backend: "codemode", input: undefined }, ]); }); @@ -1896,7 +1896,7 @@ describe("createAITools exec with one backend", () => { const { workspace } = recordingWorkspace(true); const tools = createAITools({ workspace, - shell: { backends: { "worker-javascript": { description: "Isolated JavaScript." } } }, + shell: { backends: { codemode: { description: "Isolated JavaScript." } } }, }); expect(toolDescription(tools.exec)).not.toMatch(/backend/i); @@ -1919,9 +1919,9 @@ describe("createAITools exec with one backend", () => { const tools = createAITools({ workspace, shell: { - defaultBackend: "worker-javascript", + defaultBackend: "codemode", backends: { - "worker-javascript": { description: "Isolated JavaScript." }, + codemode: { description: "Isolated JavaScript." }, container: { description: "Full Linux." }, }, }, diff --git a/packages/computer/src/tools/common/exec.ts b/packages/computer/src/tools/common/exec.ts index 45673ab2c..7b1906b05 100644 --- a/packages/computer/src/tools/common/exec.ts +++ b/packages/computer/src/tools/common/exec.ts @@ -86,7 +86,7 @@ export interface ExecBackendOptions { /** * The backends the exec tool may run on, keyed by backend id: - * `{ "worker-javascript": { description: "Use for data work." } }`. + * `{ codemode: { description: "Use for data work." } }`. * Pass `{}` for a backend that needs nothing beyond its own * description. */ diff --git a/packages/computer/src/tools/tanstack-ai/index.test.ts b/packages/computer/src/tools/tanstack-ai/index.test.ts index c3f7eeed5..1458b3855 100644 --- a/packages/computer/src/tools/tanstack-ai/index.test.ts +++ b/packages/computer/src/tools/tanstack-ai/index.test.ts @@ -2,7 +2,7 @@ import { SQLiteTestStorage } from "@cloudflare/dofs/testing"; import { isContentPartArray } from "@tanstack/ai"; import { describe, expect, it } from "vitest"; import { z } from "zod"; -import { WorkerJavaScriptBackend } from "../../backends/worker-javascript/worker-javascript"; +import { CodemodeBackend } from "../../backends/codemode/codemode"; import { Workspace } from "../../workspace"; import { createTanStackTools } from "./index"; @@ -278,7 +278,7 @@ describe("createTanStackTools", () => { storage: new SQLiteTestStorage(), backends: [ streamingCommandBackend([]) as never, - new WorkerJavaScriptBackend({ loader: { load: () => ({ getEntrypoint: () => ({}) }) } }), + new CodemodeBackend({ loader: { load: () => ({ getEntrypoint: () => ({}) }) } }), ], }); const tools = createTanStackTools({ workspace, format: "object" }); @@ -287,7 +287,7 @@ describe("createTanStackTools", () => { required?: string[]; }; - expect(schema.properties.backend?.enum).toEqual(["shell", "worker-javascript"]); + expect(schema.properties.backend?.enum).toEqual(["shell", "codemode"]); expect(schema.required).toContain("backend"); // Only the callable backend takes structured input. expect(schema.properties).toHaveProperty("input"); diff --git a/packages/computer/tests/script-runner-worker.ts b/packages/computer/tests/script-runner-worker.ts index 5d763fca0..b28d46097 100644 --- a/packages/computer/tests/script-runner-worker.ts +++ b/packages/computer/tests/script-runner-worker.ts @@ -1,10 +1,7 @@ import { DurableObject, RpcTarget, WorkerEntrypoint } from "cloudflare:workers"; import type { ShellRPC, SyncRPC } from "@cloudflare/computer-rpc"; import type { WorkspaceBackend } from "../src/backend"; -import { - WorkerJavaScriptBackend, - type WorkerJavaScriptBackendOptions, -} from "../src/backends/worker-javascript/index"; +import { CodemodeBackend, type CodemodeBackendOptions } from "../src/backends/codemode/index"; import { createGitClient } from "../src/git/index"; import type { WorkspaceExecutionMetadata, @@ -230,19 +227,19 @@ const testJobs = jobsModule.module({ // The Worker Loader resolves imports differently under the legacy and // the new module registry, so the same backend runs on both. -const NEW_REGISTRY_BACKEND = "worker-javascript-new-registry"; -const NETWORK_BACKEND = "worker-javascript-network"; +const NEW_REGISTRY_BACKEND = "codemode-new-registry"; +const NETWORK_BACKEND = "codemode-network"; -function javascriptBackend( +function codemodeBackend( env: Env, overrides: Partial< Pick< - WorkerJavaScriptBackendOptions, + CodemodeBackendOptions, "id" | "compatibilityFlags" | "loader" | "egress" | "maxStdioBytes" > > = {}, ) { - return new WorkerJavaScriptBackend({ + return new CodemodeBackend({ loader: env.LOADER, ...overrides, maxStdioBytes: overrides.maxStdioBytes ?? 64, @@ -370,8 +367,8 @@ export class HostDO extends DurableObject { waitUntil: ctx.waitUntil.bind(ctx), git: createGitClient(), backends: [ - javascriptBackend(env), - javascriptBackend(env, { + codemodeBackend(env), + codemodeBackend(env, { id: NEW_REGISTRY_BACKEND, compatibilityFlags: ["nodejs_compat", "new_module_registry"], // Local workerd still marks the new registry experimental. @@ -380,7 +377,7 @@ export class HostDO extends DurableObject { env.LOADER.load({ ...code, allowExperimental: true }), }, }), - javascriptBackend(env, { + codemodeBackend(env, { id: NETWORK_BACKEND, egress: { mode: "http-gateway", gateway: ctx.exports.TestGateway }, maxStdioBytes: 4096, @@ -422,7 +419,7 @@ export class HostDO extends DurableObject { backend?: string; }) { const handle = await this.#workspace.runtime.exec(input.source, { - backend: input.backend ?? "worker-javascript", + backend: input.backend ?? "codemode", cwd: input.cwd, input: input.value, id: input.id, @@ -435,7 +432,7 @@ export class HostDO extends DurableObject { async startRuntime(input: { source: string; id: string }) { const handle = await this.#workspace.runtime.exec(input.source, { - backend: "worker-javascript", + backend: "codemode", id: input.id, }); void handle.result().catch(() => undefined); @@ -445,7 +442,7 @@ export class HostDO extends DurableObject { async getRuntime(id: string, resume?: "tail", network?: boolean) { try { const handle = await this.#workspace.runtime.getExec(id, { - backend: network === true ? NETWORK_BACKEND : "worker-javascript", + backend: network === true ? NETWORK_BACKEND : "codemode", encoding: "utf8", resume, }); @@ -471,7 +468,7 @@ export class HostDO extends DurableObject { executions.length = 0; await this.#workspace.fs.mkdir("/workspace", { recursive: true }); const handle = await this.#workspace.runtime.exec(input.source, { - backend: input.network === true ? NETWORK_BACKEND : "worker-javascript", + backend: input.network === true ? NETWORK_BACKEND : "codemode", id: input.id, onInterrupt: input.onInterrupt, metadata: input.metadata, @@ -494,7 +491,7 @@ export class HostDO extends DurableObject { durableCalls.clear(); waits.length = 0; const handle = await this.#workspace.runtime.exec(input.source, { - backend: "worker-javascript", + backend: "codemode", id: input.id, onInterrupt: input.onInterrupt ?? "replay", timeoutMs: input.timeoutMs, @@ -551,11 +548,11 @@ export class HostDO extends DurableObject { } killRuntime(id: string) { - return this.#workspace.runtime.killExec(id, { backend: "worker-javascript" }); + return this.#workspace.runtime.killExec(id, { backend: "codemode" }); } disposeRuntime(id: string) { - return this.#workspace.runtime.disposeExec(id, { backend: "worker-javascript" }); + return this.#workspace.runtime.disposeExec(id, { backend: "codemode" }); } } @@ -767,7 +764,7 @@ export default class extends WorkerEntrypoint { // SAFETY: The object's getWorkspace returns a WorkspaceStub; RPC typing can't follow it that deep. using workspace = (await stub.getWorkspace()) as unknown as WorkspaceStub; using handle = await workspace.runtime.exec(body.source, { - backend: "worker-javascript", + backend: "codemode", encoding: "utf8", metadata: body.metadata, }); diff --git a/packages/computer/tests/script-runner.test.ts b/packages/computer/tests/script-runner.test.ts index 0c712264a..94f26b8c5 100644 --- a/packages/computer/tests/script-runner.test.ts +++ b/packages/computer/tests/script-runner.test.ts @@ -865,8 +865,8 @@ describe("WorkspaceRuntime", () => { // The Worker Loader resolves import specifiers differently under its // legacy and new module registries, so each case runs on both. describe.each([ - ["the legacy module registry", "worker-javascript"], - ["the new module registry", "worker-javascript-new-registry"], + ["the legacy module registry", "codemode"], + ["the new module registry", "codemode-new-registry"], ])("module resolution on %s", (_label, backend) => { async function run(source: string, cwd = "/workspace/app") { const response = await runtime({ source, cwd, backend }); @@ -1485,7 +1485,7 @@ describe("interrupts", () => { await start("wait-answer", approval); const call = await waitingCall("wait-answer"); expect(call).toMatchObject({ - backend: "worker-javascript", + backend: "codemode", callId: expect.stringMatching(/^wait-answer:\d+$/), payload: { kind: "approval", action: "deploy" }, metadata: {}, diff --git a/packages/computer/tsdown.config.ts b/packages/computer/tsdown.config.ts index 9b80940d2..6193cad9c 100644 --- a/packages/computer/tsdown.config.ts +++ b/packages/computer/tsdown.config.ts @@ -42,7 +42,7 @@ export default defineConfig({ "modules/browser": "src/modules/browser.ts", "backends/container-legacy/index": "src/backends/container-legacy/index.ts", "backends/container/index": "src/backends/container/index.ts", - "backends/worker-javascript/index": "src/backends/worker-javascript/index.ts", + "backends/codemode/index": "src/backends/codemode/index.ts", "backends/worker-shell/index": "src/backends/worker-shell/index.ts", // The shell-module groups build-bundle.mjs emits. Each is its // own entry so it lands at the dist path the ./shell/* package