AI SDK integration
The @cloudflare/codemode/ai entry point converts AI SDK tools into one Code Mode tool. The model writes JavaScript that calls your tools, and an executor runs that code in an isolated sandbox.
Choose between two integration patterns:
| Pattern | Use case | Approval behavior |
|---|---|---|
createCodeTool() | Simple, stateless execution with one or more tool providers | Excludes tools that use needsApproval |
ToolSetConnector or toolSetConnector() | Durable execution through a Code Mode runtime | Maps needsApproval to durable runtime approval |
createCodeTool() accepts an AI SDK ToolSet or an array of tool providers. It also requires an executor. It returns a standard AI SDK tool for use with streamText() or generateText().
-
Install Code Mode, the AI SDK, and Zod:
npm i @cloudflare/codemode agents ai zodyarn add @cloudflare/codemode agents ai zodpnpm add @cloudflare/codemode agents ai zodbun add @cloudflare/codemode agents ai zod -
Add a Worker Loader binding for
DynamicWorkerExecutor:JSONC {"$schema": "./node_modules/wrangler/config-schema.json",// Set this to today's date"compatibility_date": "2026-06-25","compatibility_flags": ["nodejs_compat"],"worker_loaders": [{"binding": "LOADER"}]}TOML # Set this to today's datecompatibility_date = "2026-06-25"compatibility_flags = ["nodejs_compat"][[worker_loaders]]binding = "LOADER" -
Define executable AI SDK tools. Code Mode uses their schemas to generate types and validate arguments before calling
execute.src/tools.js import { tool } from "ai";import { z } from "zod";export const weatherTools = {getWeather: tool({description: "Get the weather for a city",inputSchema: z.object({city: z.string().describe("City name"),}),outputSchema: z.object({city: z.string(),conditions: z.string(),}),execute: async ({ city }) => ({city,conditions: "sunny",}),}),};src/tools.ts import { tool } from "ai";import { z } from "zod";export const weatherTools = {getWeather: tool({description: "Get the weather for a city",inputSchema: z.object({city: z.string().describe("City name")}),outputSchema: z.object({city: z.string(),conditions: z.string()}),execute: async ({ city }) => ({city,conditions: "sunny"})})};Each sandbox-callable tool needs an
executefunction. Client-side or provider-executed tools cannot run through this server-side executor. -
Create the Code Mode tool and pass it to an AI SDK model call:
src/index.js import { DynamicWorkerExecutor } from "@cloudflare/codemode";import { createCodeTool } from "@cloudflare/codemode/ai";import { generateText, stepCountIs } from "ai";import { model } from "./model";import { weatherTools } from "./tools";export default {async fetch(request, env) {const executor = new DynamicWorkerExecutor({ loader: env.LOADER });const codemode = createCodeTool({ tools: weatherTools, executor });const response = await generateText({model,prompt: await request.text(),tools: { codemode },stopWhen: stepCountIs(5),});return new Response(response.text);},};src/index.ts import { DynamicWorkerExecutor } from "@cloudflare/codemode";import { createCodeTool } from "@cloudflare/codemode/ai";import { generateText, stepCountIs } from "ai";import { model } from "./model";import { weatherTools } from "./tools";export default {async fetch(request, env): Promise<Response> {const executor = new DynamicWorkerExecutor({ loader: env.LOADER });const codemode = createCodeTool({ tools: weatherTools, executor });const response = await generateText({model,prompt: await request.text(),tools: { codemode },stopWhen: stepCountIs(5),});return new Response(response.text);},} satisfies ExportedHandler<Env>;
The example uses generateText() for a completed response. You can pass the same codemode tool to streamText() for streaming. The generated tool description includes TypeScript definitions for getWeather. The model still writes JavaScript, such as:
async () => { const weather = await codemode.getWeather({ city: "Lisbon" }); return weather.conditions;};The default namespace is codemode. createCodeTool() also accepts a custom description. Include {{types}} in that description where Code Mode should insert the generated definitions.
A tool provider groups tools under one sandbox namespace. Pass the tool set directly when every tool belongs under codemode.*.
Use aiTools() when combining AI SDK tools with providers from other packages. The following optional workspace example also requires @cloudflare/shell:
npm i @cloudflare/shell yarn add @cloudflare/shell pnpm add @cloudflare/shell bun add @cloudflare/shell import { AIChatAgent } from "@cloudflare/ai-chat";import { DynamicWorkerExecutor } from "@cloudflare/codemode";import { aiTools, createCodeTool } from "@cloudflare/codemode/ai";import { Workspace } from "@cloudflare/shell";import { stateTools } from "@cloudflare/shell/workers";import { weatherTools } from "./tools";
export class Chat extends AIChatAgent { workspace = new Workspace({ sql: this.ctx.storage.sql });
codemodeTool() { return createCodeTool({ tools: [aiTools(weatherTools), stateTools(this.workspace)], executor: new DynamicWorkerExecutor({ loader: this.env.LOADER }), }); }}import { AIChatAgent } from "@cloudflare/ai-chat";import { DynamicWorkerExecutor } from "@cloudflare/codemode";import { aiTools, createCodeTool } from "@cloudflare/codemode/ai";import { Workspace } from "@cloudflare/shell";import { stateTools } from "@cloudflare/shell/workers";import { weatherTools } from "./tools";
export class Chat extends AIChatAgent<Env> { workspace = new Workspace({ sql: this.ctx.storage.sql });
codemodeTool() { return createCodeTool({ tools: [aiTools(weatherTools), stateTools(this.workspace)], executor: new DynamicWorkerExecutor({ loader: this.env.LOADER }), }); }}This example exposes AI SDK tools as codemode.* and workspace tools as state.*.
To assign custom namespaces, pass provider objects instead:
const executor = new DynamicWorkerExecutor({ loader: env.LOADER });
const codemode = createCodeTool({ tools: [ { name: "weather", tools: weatherTools }, { name: "notifications", tools: notificationTools }, ], executor,});const executor = new DynamicWorkerExecutor({ loader: env.LOADER });
const codemode = createCodeTool({ tools: [ { name: "weather", tools: weatherTools }, { name: "notifications", tools: notificationTools }, ], executor,});The generated code can then call weather.getWeather() and notifications.send(). Provider names must be unique, valid JavaScript identifiers.
Use ToolSetConnector or its toolSetConnector() convenience function when runs need durable state. The connector adapts an AI SDK ToolSet for createCodemodeRuntime(). The helper returns new ToolSetConnector(ctx, options).
Create the connector from inside an Agent or another Durable Object:
import { AIChatAgent } from "@cloudflare/ai-chat";import { createCodemodeRuntime, DynamicWorkerExecutor,} from "@cloudflare/codemode";import { toolSetConnector } from "@cloudflare/codemode/ai";import { convertToModelMessages, streamText } from "ai";import { model } from "./model";import { operationTools } from "./tools";
// Export this manually when the @cloudflare/codemode/vite plugin is not configured.export { CodemodeRuntime } from "@cloudflare/codemode";
export class OperationsAgent extends AIChatAgent { async onChatMessage() { const operations = toolSetConnector(this.ctx, { name: "operations", instructions: "Use these tools to manage customer requests.", tools: operationTools, });
const runtime = createCodemodeRuntime({ ctx: this.ctx, executor: new DynamicWorkerExecutor({ loader: this.env.LOADER }), connectors: [operations], });
const result = streamText({ model, messages: await convertToModelMessages(this.messages), tools: { codemode: runtime.tool() }, });
return result.toUIMessageStreamResponse(); }}import { AIChatAgent } from "@cloudflare/ai-chat";import { createCodemodeRuntime, DynamicWorkerExecutor,} from "@cloudflare/codemode";import { toolSetConnector } from "@cloudflare/codemode/ai";import { convertToModelMessages, streamText } from "ai";import { model } from "./model";import { operationTools } from "./tools";
// Export this manually when the @cloudflare/codemode/vite plugin is not configured.export { CodemodeRuntime } from "@cloudflare/codemode";
export class OperationsAgent extends AIChatAgent<Env> { async onChatMessage() { const operations = toolSetConnector(this.ctx, { name: "operations", instructions: "Use these tools to manage customer requests.", tools: operationTools, });
const runtime = createCodemodeRuntime({ ctx: this.ctx, executor: new DynamicWorkerExecutor({ loader: this.env.LOADER }), connectors: [operations], });
const result = streamText({ model, messages: await convertToModelMessages(this.messages), tools: { codemode: runtime.tool() }, });
return result.toUIMessageStreamResponse(); }}The example exports CodemodeRuntime manually. If you configure the Code Mode Vite plugin as described in Create a durable Code Mode runtime, remove that manual export because the plugin adds it.
The connector defaults to the tools namespace when name is omitted. It excludes tools without an execute function from both generated types and sandbox bindings.
The durable runtime adds an execution log, pause and resume behavior, and on-demand connector discovery. The model can find methods with codemode.search() and inspect their types with codemode.describe().
The two integration patterns handle AI SDK approvals differently.
createCodeTool() filters out tools where needsApproval is true or a function. Filtered tools do not appear in generated types and cannot run from sandbox code. A tool with needsApproval: false remains available.
This stateless path does not pause execution for AI SDK approval. Use a standard AI SDK tool outside Code Mode if that tool needs the AI SDK approval flow.
ToolSetConnector maps AI SDK needsApproval to the durable runtime's requiresApproval annotation. Calling that tool pauses the run. Your application can inspect pending actions and resume the same execution with runtime.approve({ executionId }).
A function-valued needsApproval cannot be evaluated before the sandbox supplies arguments. The connector therefore treats the tool as always requiring approval. needsApproval: false executes without pausing.
This approval uses the Code Mode runtime's durable pause, approval, and replay flow. It does not use the AI SDK per-call approval flow.