Package Exports
- @cloudflare/codemode
- @cloudflare/codemode/ai
- @cloudflare/codemode/mcp
Readme
@cloudflare/codemode
Instead of asking LLMs to call tools directly, Code Mode lets them write executable code that orchestrates multiple operations. LLMs are better at writing code than calling tools — they've seen millions of lines of real-world code but only contrived tool-calling examples.
Code Mode generates TypeScript type definitions from your tools for LLM context, and executes the generated JavaScript in secure, isolated sandboxes with millisecond startup times.
Experimental — may have breaking changes. Use with caution in production.
Installation
# Full AI SDK integration
npm install @cloudflare/codemode agents ai zod
# Utilities only (no ai/zod peer deps needed)
npm install @cloudflare/codemodeThe main entry point (@cloudflare/codemode) has no peer dependency on ai or zod. The ai and zod packages are only required when importing from @cloudflare/codemode/ai.
Quick Start
createCodeTool takes your tools and an executor, and returns a single AI SDK tool that lets the LLM write code instead of making individual tool calls.
import { createCodeTool } from "@cloudflare/codemode/ai";
import { DynamicWorkerExecutor } from "@cloudflare/codemode";
import { streamText, tool } from "ai";
import { z } from "zod";
// 1. Define your tools using the AI SDK tool() wrapper
const tools = {
getWeather: tool({
description: "Get weather for a location",
inputSchema: z.object({ location: z.string() }),
execute: async ({ location }) => `Weather in ${location}: 72°F, sunny`
}),
sendEmail: tool({
description: "Send an email",
inputSchema: z.object({
to: z.string(),
subject: z.string(),
body: z.string()
}),
execute: async ({ to, subject, body }) => `Email sent to ${to}`
})
};
// 2. Create an executor (runs code in an isolated Worker)
const executor = new DynamicWorkerExecutor({
loader: env.LOADER
});
// 3. Create the codemode tool
const codemode = createCodeTool({ tools, executor });
// 4. Use it with streamText — the LLM writes code that calls your tools
const result = streamText({
model,
system: "You are a helpful assistant.",
messages,
tools: { codemode }
});The LLM sees a typed codemode object and writes code like:
async () => {
const weather = await codemode.getWeather({ location: "London" });
if (weather.includes("sunny")) {
await codemode.sendEmail({
to: "team@example.com",
subject: "Nice day!",
body: `It's ${weather}`
});
}
return { weather, notified: true };
};Architecture
How it works
┌─────────────┐ ┌──────────────────────────────────────┐
│ │ │ Dynamic Worker (isolated sandbox) │
│ Host │ RPC │ │
│ Worker │◄──────►│ LLM-generated code runs here │
│ │ │ codemode.myTool() → dispatcher.call()│
│ ToolDispatcher │ │
│ holds tool fns │ fetch() blocked by default │
└─────────────┘ └──────────────────────────────────────┘createCodeToolgenerates TypeScript type definitions from your tools and builds a description the LLM can read- The LLM writes an async arrow function that calls
codemode.toolName(args) - Code is normalized via AST parsing (acorn) and sent to the executor
DynamicWorkerExecutorspins up an isolated Worker viaWorkerLoader- Inside the sandbox, a
Proxyinterceptscodemode.*calls and routes them back to the host via Workers RPC (ToolDispatcher extends RpcTarget) - Console output is captured and returned alongside the result
Network isolation
External fetch() and connect() are blocked by default — enforced at the Workers runtime level via globalOutbound: null. Sandboxed code can only interact with the host through codemode.* tool calls.
To allow controlled outbound access, pass a Fetcher:
const executor = new DynamicWorkerExecutor({
loader: env.LOADER,
globalOutbound: null // default — fully isolated
// globalOutbound: env.MY_OUTBOUND_SERVICE, // route through a Fetcher
});The Executor Interface
The Executor interface is deliberately minimal — implement it to run code in any sandbox:
interface Executor {
execute(
code: string,
fns: Record<string, (...args: unknown[]) => Promise<unknown>>
): Promise<ExecuteResult>;
}
interface ExecuteResult {
result: unknown;
error?: string;
logs?: string[];
}DynamicWorkerExecutor is the Cloudflare Workers implementation, but you can build your own for Node VM, QuickJS, containers, or anything else.
// Example: a simple Node VM executor
class NodeVMExecutor implements Executor {
async execute(code, fns): Promise<ExecuteResult> {
try {
const fn = new AsyncFunction("codemode", `return await (${code})()`);
const result = await fn(fns);
return { result };
} catch (err) {
return { result: undefined, error: err.message };
}
}
}Configuration
Wrangler bindings
// wrangler.jsonc
{
"worker_loaders": [{ "binding": "LOADER" }],
"compatibility_flags": ["nodejs_compat"]
}DynamicWorkerExecutor options
| Option | Type | Default | Description |
|---|---|---|---|
loader |
WorkerLoader |
required | Worker Loader binding from env.LOADER |
timeout |
number |
30000 |
Execution timeout in ms |
globalOutbound |
Fetcher | null |
null |
Network access control. null = blocked, Fetcher = routed |
createCodeTool options
| Option | Type | Default | Description |
|---|---|---|---|
tools |
ToolSet | ToolDescriptors |
required | Your tools (AI SDK tool() or raw descriptors) |
executor |
Executor |
required | Where to run the generated code |
description |
string |
auto-generated | Custom tool description. Use {{types}} for type defs |
Agent Integration
The user sends a message, the agent passes it to an LLM with the codemode tool, and the LLM writes and executes code to fulfill the request.
import { Agent } from "agents";
import { createCodeTool } from "@cloudflare/codemode/ai";
import { DynamicWorkerExecutor } from "@cloudflare/codemode";
import { streamText, convertToModelMessages, stepCountIs } from "ai";
export class MyAgent extends Agent<Env, State> {
async onChatMessage() {
const executor = new DynamicWorkerExecutor({
loader: this.env.LOADER
});
const codemode = createCodeTool({
tools: myTools,
executor
});
const result = streamText({
model,
system: "You are a helpful assistant.",
messages: await convertToModelMessages(this.state.messages),
tools: { codemode },
stopWhen: stepCountIs(10)
});
// Stream response back to client...
}
}With MCP tools
MCP tools work the same way — just merge them into the tool set:
const codemode = createCodeTool({
tools: {
...myTools,
...this.mcp.getAITools()
},
executor
});Utilities
sanitizeToolName(name)
Converts tool names into valid JavaScript identifiers. Handles hyphens, dots, digits, reserved words. Called automatically by DynamicWorkerExecutor on fns keys — you only need this for custom use cases.
import { sanitizeToolName } from "@cloudflare/codemode";
sanitizeToolName("my-tool"); // "my_tool"
sanitizeToolName("3d-render"); // "_3d_render"
sanitizeToolName("delete"); // "delete_"normalizeCode(code)
Normalizes LLM-generated code into a valid async arrow function. Strips markdown code fences, handles various function formats. Called automatically by DynamicWorkerExecutor — you only need this for custom use cases.
import { normalizeCode } from "@cloudflare/codemode";
normalizeCode("```js\nconst x = 1;\nx\n```");
// "async () => {\nconst x = 1;\nreturn (x)\n}"generateTypesFromJsonSchema(tools)
Generates TypeScript type definitions from tool descriptors with plain JSON Schema. No AI SDK or Zod dependency required.
import { generateTypesFromJsonSchema } from "@cloudflare/codemode";
const types = generateTypesFromJsonSchema({
getWeather: {
description: "Get weather for a city",
inputSchema: {
type: "object",
properties: {
city: { type: "string", description: "City name" }
},
required: ["city"]
}
}
});
// Returns TypeScript declarations like:
// type GetWeatherInput = { city: string }
// declare const codemode: { getWeather: (input: GetWeatherInput) => Promise<...>; }generateTypes(tools) (AI SDK)
Generates TypeScript type definitions from AI SDK tools or Zod-based tool descriptors. Requires ai and zod peer dependencies.
import { generateTypes } from "@cloudflare/codemode/ai";
const types = generateTypes(myAiSdkTools);Module Structure
| Module | Requires ai/zod |
Exports |
|---|---|---|
@cloudflare/codemode |
No | sanitizeToolName, normalizeCode, generateTypesFromJsonSchema, jsonSchemaToType, DynamicWorkerExecutor, ToolDispatcher |
@cloudflare/codemode/ai |
Yes | createCodeTool, generateTypes, ToolDescriptor, ToolDescriptors |
Limitations
- Tool approval (
needsApproval) is not supported yet. Tools withneedsApproval: trueexecute immediately inside the sandbox without pausing for approval. Support for approval flows within codemode is planned. For now, do not pass approval-required tools tocreateCodeTool— use them through standard AI SDK tool calling instead. - Requires Cloudflare Workers environment for
DynamicWorkerExecutor - Limited to JavaScript execution
Examples
examples/codemode/— Full working example with task management tools
License
MIT