Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Codemode

Beta

Codemode 让 LLM 编写并执行编排你的工具的代码,而不是一次调用一个。它受 CodeAct ↗ 启发,之所以可行,是因为 LLM 在编写代码方面比进行单个工具调用更擅长 — 它们见过数百万行真实世界的代码,但只见过人为构造的工具调用示例。

@cloudflare/codemode 包从你的工具中生成 TypeScript 类型定义,为 LLM 提供单个 “write code” 工具,并在安全的、隔离的 Worker 沙箱中执行生成的 JavaScript。

警告

Codemode 是实验性的,在未来版本中可能有破坏性更改。在生产环境中谨慎使用。

何时使用 Codemode

Codemode 在 LLM 需要执行以下操作时最有用:

  • 链接多个工具调用,在它们之间使用逻辑(条件、循环、错误处理)
  • 组合不同工具的结果,在返回之前
  • 使用暴露许多细粒度操作的 MCP 服务器
  • 执行多步工作流,这些工作流使用标准工具调用会需要多次往返

对于简单的单工具调用,标准 AI SDK 工具调用更简单且足够。

安装

终端窗口


npm install @cloudflare/codemode


如果你使用 @cloudflare/codemode/ai,还需安装 aizod 对等依赖:

终端窗口


npm install ai zod


快速开始

1. 定义你的工具

使用标准 AI SDK 的 tool() 函数:

JavaScript


import { tool } from "ai";

import { z } from "zod";


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}`,

  }),

};


Explain Code

TypeScript


import { tool } from "ai";

import { z } from "zod";


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}`,

  }),

};


Explain Code

2. 创建 codemode 工具

createCodeTool 接收你的工具和一个执行器,返回一个 AI SDK 工具:

JavaScript


import { createCodeTool } from "@cloudflare/codemode/ai";

import { DynamicWorkerExecutor } from "@cloudflare/codemode";


const executor = new DynamicWorkerExecutor({

  loader: env.LOADER,

});


const codemode = createCodeTool({ tools, executor });


TypeScript


import { createCodeTool } from "@cloudflare/codemode/ai";

import { DynamicWorkerExecutor } from "@cloudflare/codemode";


const executor = new DynamicWorkerExecutor({

  loader: env.LOADER,

});


const codemode = createCodeTool({ tools, executor });


3. 与 streamText 一起使用

像使用其他工具一样,将 codemode 工具传递给 streamTextgenerateText。你可以选择模型:

JavaScript


import { streamText } from "ai";


const result = streamText({

  model,

  system: "You are a helpful assistant.",

  messages,

  tools: { codemode },

});


TypeScript


import { streamText } from "ai";


const result = streamText({

  model,

  system: "You are a helpful assistant.",

  messages,

  tools: { codemode },

});


当 LLM 决定使用 codemode 时,它会编写一个像这样的异步箭头函数:

JavaScript


async () => {

  const weather = await codemode.getWeather({ location: "London" });

  if (weather.includes("sunny")) {

    await codemode.sendEmail({

      to: "[email protected]",

      subject: "Nice day!",

      body: `It's ${weather}`,

    });

  }

  return { weather, notified: true };

};


Explain Code

代码在隔离的 Worker 沙箱中运行,工具调用通过 Workers RPC 派回主机,结果返回给 LLM。

配置

Wrangler 绑定

向你的 wrangler.jsonc 添加一个 worker_loaders 绑定。这是唯一必需的绑定:

JSONC


{

  "$schema": "./node_modules/wrangler/config-schema.json",

  "worker_loaders": [

    {

      "binding": "LOADER"

    }

  ],

  "compatibility_flags": [

    "nodejs_compat"

  ]

}


Explain Code

TOML


worker_loaders = [{ binding = "LOADER" }]

compatibility_flags = ["nodejs_compat"]


工作原理

  1. createCodeTool 从你的工具中生成 TypeScript 类型定义,并构建一个 LLM 可以读取的描述。
  2. LLM 编写一个异步箭头函数,调用 codemode.toolName(args)
  3. 代码通过 AST 解析(acorn)进行规范化,然后发送到执行器。
  4. DynamicWorkerExecutor 通过 WorkerLoader 启动一个隔离的 Worker。
  5. 在沙箱内,一个 Proxy 拦截 codemode.* 调用,并通过 Workers RPC 将其路由回主机(ToolDispatcher extends RpcTarget)。
  6. 控制台输出(console.logconsole.warnconsole.error)被捕获并在结果中返回。

网络隔离

外部 fetch()connect() 默认被阻止 — 通过 globalOutbound: null 在 Workers 运行时级别强制执行。沙箱代码只能通过 codemode.* 工具调用与主机交互。

要允许受控的出站访问,传递一个 Fetcher:

JavaScript


const executor = new DynamicWorkerExecutor({

  loader: env.LOADER,

  globalOutbound: null, // default — fully isolated

  // globalOutbound: env.MY_OUTBOUND_SERVICE  // route through a Fetcher

});


TypeScript


const executor = new DynamicWorkerExecutor({

  loader: env.LOADER,

  globalOutbound: null, // default — fully isolated

  // globalOutbound: env.MY_OUTBOUND_SERVICE  // route through a Fetcher

});


与 Agent 一起使用

典型的模式是在 Agent 的消息处理器内创建执行器和 codemode 工具:

JavaScript


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 {

  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...

  }

}


Explain Code

TypeScript


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...

  }

}


Explain Code

与 MCP 工具一起使用

MCP 工具的工作方式相同 — 将它们合并到工具集中:

JavaScript


const codemode = createCodeTool({

  tools: {

    ...myTools,

    ...this.mcp.getAITools(),

  },

  executor,

});


TypeScript


const codemode = createCodeTool({

  tools: {

    ...myTools,

    ...this.mcp.getAITools(),

  },

  executor,

});


带有连字符或点的工具名(在 MCP 中常见)会自动清理为有效的 JavaScript 标识符(例如,my-server.list-items 变为 my_server_list_items)。

MCP 服务器包装器

@cloudflare/codemode/mcp 导出提供两个用 Code Mode 包装 MCP 服务器的函数。

codeMcpServer

使用单个 code 工具包装现有的 MCP 服务器。每个上游工具在沙箱内成为一个类型化的 codemode.* 方法:

JavaScript


import { codeMcpServer } from "@cloudflare/codemode/mcp";

import { DynamicWorkerExecutor } from "@cloudflare/codemode";


const executor = new DynamicWorkerExecutor({ loader: env.LOADER });

const server = await codeMcpServer({ server: upstreamMcp, executor });


TypeScript


import { codeMcpServer } from "@cloudflare/codemode/mcp";

import { DynamicWorkerExecutor } from "@cloudflare/codemode";


const executor = new DynamicWorkerExecutor({ loader: env.LOADER });

const server = await codeMcpServer({ server: upstreamMcp, executor });


openApiMcpServer

从 OpenAPI 规范创建一个带有 searchexecute 工具的 MCP 服务器。所有 $ref 指针在传递到沙箱之前被解析,主机端的 request 处理器将认证保持在沙箱之外:

JavaScript


import { openApiMcpServer } from "@cloudflare/codemode/mcp";

import { DynamicWorkerExecutor } from "@cloudflare/codemode";


const executor = new DynamicWorkerExecutor({ loader: env.LOADER });

const server = openApiMcpServer({

  spec: openApiSpec,

  executor,

  request: async ({ method, path, query, body }) => {

    // Runs on the host — add auth headers here

    const res = await fetch(`https://api.example.com${path}`, {

      method,

      headers: { Authorization: `Bearer ${token}` },

      body: body ? JSON.stringify(body) : undefined,

    });

    return res.json();

  },

});


Explain Code

TypeScript


import { openApiMcpServer } from "@cloudflare/codemode/mcp";

import { DynamicWorkerExecutor } from "@cloudflare/codemode";


const executor = new DynamicWorkerExecutor({ loader: env.LOADER });

const server = openApiMcpServer({

  spec: openApiSpec,

  executor,

  request: async ({ method, path, query, body }) => {

    // Runs on the host — add auth headers here

    const res = await fetch(`https://api.example.com${path}`, {

      method,

      headers: { Authorization: `Bearer ${token}` },

      body: body ? JSON.stringify(body) : undefined,

    });

    return res.json();

  },

});


Explain Code

Executor 接口

Executor 接口刻意精简 — 实现它即可在任何沙箱中运行代码:

TypeScript


interface Executor {

  execute(

    code: string,

    fns: Record<string, (...args: unknown[]) => Promise<unknown>>,

  ): Promise<ExecuteResult>;

}


interface ExecuteResult {

  result: unknown;

  error?: string;

  logs?: string[];

}


Explain Code

DynamicWorkerExecutor 是内置的 Cloudflare Workers 实现。你可以为 Node VM、QuickJS、容器或任何其他沙箱构建自己的实现。

API 参考

createCodeTool(options)

返回一个 AI SDK 兼容的 Tool

选项类型默认值描述
toolsToolSet | ToolDescriptors必需你的工具(AI SDK tool() 或原始 descriptor)
executorExecutor必需在何处运行生成的代码
descriptionstring自动生成自定义工具描述。使用 \{\{types\}\} 表示类型定义

DynamicWorkerExecutor

通过 WorkerLoader 在隔离的 Cloudflare Worker 中执行代码。

选项类型默认值描述
loaderWorkerLoader必需来自 env.LOADER 的 Worker Loader 绑定
timeoutnumber30000执行超时(毫秒)
globalOutboundFetcher | nullnull网络访问控制。null = 阻止,Fetcher = 路由
modulesRecord<string, string>沙箱中可用的自定义 ES 模块。键是 specifier,值是源代码。

代码和工具名在内部被规范化和清理 — 你不需要在传递给 execute() 之前调用 normalizeCode()sanitizeToolName()

generateTypes(tools)

从你的工具中生成 TypeScript 类型定义。在内部由 createCodeTool 使用,但导出供自定义使用(例如,在前端显示类型)。

JavaScript


import { generateTypes } from "@cloudflare/codemode/ai";


const types = generateTypes(myTools);

// Returns:

// type CreateProjectInput = { name: string; description?: string }

// declare const codemode: {

//   createProject: (input: CreateProjectInput) => Promise<unknown>;

// }


TypeScript


import { generateTypes } from "@cloudflare/codemode/ai";


const types = generateTypes(myTools);

// Returns:

// type CreateProjectInput = { name: string; description?: string }

// declare const codemode: {

//   createProject: (input: CreateProjectInput) => Promise<unknown>;

// }


对于不依赖 AI SDK 的 JSON Schema 输入,使用主入口点:

JavaScript


import { generateTypesFromJsonSchema } from "@cloudflare/codemode";


const types = generateTypesFromJsonSchema(jsonSchemaToolDescriptors);


TypeScript


import { generateTypesFromJsonSchema } from "@cloudflare/codemode";


const types = generateTypesFromJsonSchema(jsonSchemaToolDescriptors);


sanitizeToolName(name)

将工具名转换为有效的 JavaScript 标识符。

JavaScript


import { sanitizeToolName } from "@cloudflare/codemode";


sanitizeToolName("get-weather"); // "get_weather"

sanitizeToolName("3d-render"); // "_3d_render"

sanitizeToolName("delete"); // "delete_"


TypeScript


import { sanitizeToolName } from "@cloudflare/codemode";


sanitizeToolName("get-weather"); // "get_weather"

sanitizeToolName("3d-render"); // "_3d_render"

sanitizeToolName("delete"); // "delete_"


安全考虑

  • 代码在隔离的 Worker 沙箱中运行 — 每次执行获得自己的 Worker 实例。
  • 外部网络访问(fetchconnect)在运行时级别默认被阻止
  • 工具调用通过 Workers RPC 派发,而不是网络请求。
  • 执行有可配置的超时(默认 30 秒)。
  • 控制台输出被单独捕获,不会泄漏到主机。

当前限制

  • 尚不支持工具批准(needsApproval)。 带有 needsApproval: true 的工具在沙箱内立即执行,不会暂停以等待批准。计划在 codemode 中支持批准流程。目前,不要将需要批准的工具传递给 createCodeTool — 而是通过标准 AI SDK 工具调用使用它们。
  • DynamicWorkerExecutor 需要 Cloudflare Workers 环境。
  • 仅限 JavaScript 执行。
  • LLM 代码质量取决于提示工程和模型能力。

相关资源

Codemode example 完整可用的示例 — 一个使用 codemode 和 SQLite 的项目管理助手。

Using AI Models 在你的 Agent 中使用 AI 模型。

MCP Client 连接到 MCP 服务器并通过 codemode 使用其工具。