Replace the code interpreter·续
Calls to one context wait for each other, as they did in 0.12. Calls to different contexts run at the same time. Because only one call to a context runs at a time, the time limit starts when the call reaches the kernel, not while it waits for an earlier call, and the interrupt reaches the cell of the call that timed out. The queue also keeps two first calls from starting the same kernel twice.
0.12 had no default timeout, and a cell that timed out kept running. Here, a call that runs longer than timeout throws a TimeoutError, and the kernel stops the cell with KeyboardInterrupt. Variables that earlier calls defined stay. If the kernel exits, for example because the code runs out of memory, the call throws and the next call starts a kernel without variables.
6. Add methods that replace createCodeContext(), listCodeContexts(), and deleteCodeContext():
src/index.tsts
export class MySandbox extends DurableObject<Env> {
// ...
private async getContext(id: string): Promise<CodeContext> {
if (id === DEFAULT_CONTEXT.id) {
return DEFAULT_CONTEXT;
}
const context = await this.ctx.storage.get<CodeContext>(
`context:${id}`,
);
if (!context) {
throw new Error(`No code context ${id}`);
}
return context;
}
async createCodeContext(
options: { cwd?: string; env?: Record<string, string> } = {},
): Promise<CodeContext> {
// Take the lowest port that no stored context uses.
const stored = await this.ctx.storage.list<CodeContext>({
prefix: "context:",
});
const used = new Set([...stored.values()].map((c) => c.port));
let port = DEFAULT_CONTEXT.port + 1;
while (used.has(port)) {
port++;
}
const context: CodeContext = {
id: crypto.randomUUID(),
port,
cwd: options.cwd ?? "/workspace",
env: options.env,
};
await this.ctx.storage.put(`context:${context.id}`, context);
return context;
}
async listCodeContexts(): Promise<CodeContext[]> {
const contexts = await this.ctx.storage.list<CodeContext>({
prefix: "context:",
});
return [DEFAULT_CONTEXT, ...contexts.values()];
}
async deleteCodeContext(id: string): Promise<void> {
// Wait for the calls already queued for the context.
return this.inContext(id, async () => {
const context = await this.getContext(id);
if (this.container.running) {
await stopServer(this.container, kernelDir(context));
this.kernels.delete(context.port);
}
await this.ctx.storage.delete(`context:${id}`);
});
}
}
```
`createCodeContext()` gives each new context the lowest port after `8800` that no stored context uses, so a new context can take the port of a deleted one.
The contexts share one container, so code in one context can read the files of the others and reach their ports. Give each user their own sandbox name.
7. Replace each call. Pass the `id` of the context, and `env` for `envVars`:
*src/index.ts (0.12)ts*
```ts
const sandbox = getSandbox(env.Sandbox, "ada");
const context = await sandbox.createCodeContext({
language: "python",
envVars: { REGION: "east" },
});
const execution = await sandbox.runCode(
"import os; os.environ['REGION']",
{ context, timeout: 30_000 },
);
```
*src/index.ts (1.0)ts*
```ts
const sandbox = env.Sandbox.getByName("ada");
const context = await sandbox.createCodeContext({
env: { REGION: "east" },
});
const execution = await sandbox.runCode(
"import os; os.environ['REGION']",
{ context: context.id, timeout: 30_000 },
);
```
Then change the code that reads the result:
| 0.12 | 1.0 |
| --- | --- |
| `results[i].text`, `html`, `png`, `jpeg`, `svg`, `latex`, `markdown`, `javascript`, `json` | The same keys. 0.12 returned each format as its own result, such as one `html` result and one `text` result for a pandas DataFrame. The kernel returns one result with both keys. |
| `results[i].formats()` | `Object.keys(results[i])`. |
| `results[i].chart`, `results[i].data` | Not returned. The 0.12 Python interpreter did not set them. |
| `logs.stdout`, `logs.stderr` | The same, with one string for each call. 0.12 also wrote the value of the last line to `stdout`, such as `Out[0]: 215`. The kernel returns that value only in `results`. |
| `error.name`, `error.message`, `error.traceback` | The same. `error` is `null` when the code succeeds, not `undefined`. |
| `executionCount` | The same. |
8. Deploy, and send two calls to one sandbox. In this example, a Worker route at `/sandboxes/<name>/code` passes the request body to `runCode()`:
```sh
WORKER="https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev"
curl "$WORKER/sandboxes/ada/code" --data-binary 'sales = [120, 95]'
curl "$WORKER/sandboxes/ada/code" --data-binary 'sum(sales)'
```
The second call reads the variable that the first call defined:
```json
{
"logs": { "stdout": [], "stderr": [] },
"results": [{ "text": "215" }],
"error": null,
"executionCount": 2
}
```
Variables that 0.12 contexts held stay in the 0.12 container. After the switch, run your setup code again, and create your contexts again, because 0.12 context IDs are not in the storage of your Durable Object.
## Run JavaScript in a Dynamic Worker
Run JavaScript in a [Dynamic Worker](https://developers.cloudflare.com/dynamic-workers/) that your Worker loads, so JavaScript needs no container.
1. Add a Worker Loader binding to your Wrangler configuration:
```jsonc
{
"worker_loaders": [
{
"binding": "LOADER",
},
],
}
```
```toml
[[worker_loaders]]
binding = "LOADER"
```
npmyarnpnpm
npx wrangler types ```
yarn wrangler types
```
pnpm wrangler types ```
- Create
src/sandbox.tswith therunJavaScript()function from Build an AI code interpreter. - Replace each
runCode()call for JavaScript:
src/index.ts (0.12)ts
const execution = await sandbox.runCode(
"[2, 3, 5, 7].reduce((a, b) => a + b)",
{ language: "javascript" },
);
```
*src/index.ts (1.0)ts*
```ts
import { runJavaScript } from "./sandbox";
const output = await runJavaScript(
env,
"return [2, 3, 5, 7].reduce((a, b) => a + b);",
);
```
`output` is `{ "result": 17, "logs": [] }`, and `logs` holds the `console.log()` output. The code runs as the body of an async function, so it must `return` its result. 0.12 returned the value of the last expression instead. The code has no network access. For the limits that `runJavaScript()` sets, refer to [Build an AI code interpreter](https://developers.cloudflare.com/sandbox/get-started/build-an-ai-code-interpreter/#2-run-code-in-a-sandbox).
Each call loads a new Dynamic Worker, so variables do not carry over. Put the values that the code needs into the code. 0.12 compiled TypeScript before running it. To run TypeScript, compile it first with [`@cloudflare/worker-bundler`](https://developers.cloudflare.com/dynamic-workers/getting-started/#using-typescript-and-npm-dependencies).
## Replace streaming output
The kernel returns the output of a cell when the cell finishes, so `runCodeStream()`, `onStdout`, `onStderr`, and `onResult` have no 1.0 version. To send output while code runs, run the code as a command with `python3 -`, pass it on standard input, and return `stdout` from the process, as in [Move commands](https://developers.cloudflare.com/sandbox/sdk/migrate/commands/#stream-output). A command does not keep variables.
## Related resources
- [Run Python code](https://developers.cloudflare.com/sandbox/commands/run-python-code/)
- [Build an AI code interpreter](https://developers.cloudflare.com/sandbox/get-started/build-an-ai-code-interpreter/)
- [Dynamic Workers](https://developers.cloudflare.com/dynamic-workers/)
- [Stream command output](https://developers.cloudflare.com/sandbox/commands/stream-command-output/)
- [Durable Object Container API](https://developers.cloudflare.com/containers/api/durable-object-container/)
Was this helpful?
YesNo
## On this page
[Docs](https://developers.cloudflare.com/)
```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/sandbox/sdk/migrate/code-interpreter/#page","headline":"Replace the code interpreter","description":"Replace @cloudflare/sandbox 0.12 runCode(), code contexts, and runCodeStream() with an IPython kernel for Python and a Dynamic Worker for JavaScript.","url":"https://developers.cloudflare.com/sandbox/sdk/migrate/code-interpreter/","inLanguage":"en","image":"https://developers.cloudflare.com/sandbox/sdk/migrate/code-interpreter/og.png?v=93bda7a31bc337fe","dateModified":"2026-10-23","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Replace @cloudflare/sandbox 0.12 exec(), execStream(), sessions, and command timeouts with container.exec() in your Durable Object. title: Change command calls image: https://developers.cloudflare.com/sandbox/sdk/migrate/commands/og.png?v=90c2757a3d000943
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/sandbox/llms.txt
Use this file to discover all available pages before exploring further.
