本指南为 Agents SDK 应用添加持久 Code Mode 运行时。该运行时在 Durable Object 休眠后仍存储执行历史、待审批项与代码片段。
你需要一个现有 Agents SDK 应用,包含 Durable Object 与 Vite。示例使用 AIChatAgent 与 AI SDK。
-
安装 Code Mode 包:
npm i @cloudflare/codemodeyarn add @cloudflare/codemodepnpm add @cloudflare/codemodebun add @cloudflare/codemode -
添加 Worker Loader 绑定。
DynamicWorkerExecutor使用该绑定在隔离 Worker 中运行 model 生成的代码:{ "$schema": "./node_modules/wrangler/config-schema.json", // Set this to today's date "compatibility_date": "2026-08-17", "compatibility_flags": [ "nodejs_compat" ], "worker_loaders": [ { "binding": "LOADER" } ] }# Set this to today's date compatibility_date = "2026-08-17" compatibility_flags = ["nodejs_compat"] [[worker_loaders]] binding = "LOADER" -
在
vite.config.ts中添加 Agents 与 Code Mode 插件:vite.config.jsjs import { cloudflare } from "@cloudflare/vite-plugin"; import codemode from "@cloudflare/codemode/vite"; import agents from "agents/vite"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [agents(), codemode(), cloudflare()], });vite.config.tsts import { cloudflare } from "@cloudflare/vite-plugin"; import codemode from "@cloudflare/codemode/vite"; import agents from "agents/vite"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [agents(), codemode(), cloudflare()], });该插件从 Worker 入口模块导出
CodemodeRuntimefacet 类。运行时将执行状态存储在 Durable Object facet 中,Workers 运行时要求 facet 类通过ctx.exports可用。若不使用插件,需手动添加导出:export { CodemodeRuntime } from "@cloudflare/codemode"; -
创建 connector。Connector 是普通类——无需特殊文件名或 import 语法。本示例将 note 存储在 Agent 的 Durable Object storage 中:
src/notes-connector.jsjs import { CodemodeConnector } from "@cloudflare/codemode"; export class NotesConnector extends CodemodeConnector { storage; constructor(ctx, env) { super(ctx, env); this.storage = ctx.storage; } name() { return "notes"; } instructions() { return "Use this connector to list and create saved notes."; } tools() { return { listNotes: { description: "List saved notes.", execute: async () => (await this.storage.get("notes")) ?? [], }, createNote: { description: "Create a saved note.", inputSchema: { type: "object", properties: { text: { type: "string" } }, required: ["text"], }, requiresApproval: true, execute: async (input) => { const { text } = input; const note = { id: crypto.randomUUID(), text }; const notes = (await this.storage.get("notes")) ?? []; await this.storage.put("notes", [...notes, note]); return note; }, revert: async (_input, result) => { const { id } = result; const notes = (await this.storage.get("notes")) ?? []; await this.storage.put( "notes", notes.filter((note) => note.id !== id), ); }, }, }; } }src/notes-connector.tsts import { CodemodeConnector, type ConnectorTools, } from "@cloudflare/codemode"; type Note = { id: string; text: string }; export class NotesConnector extends CodemodeConnector<Env> { private storage: DurableObjectStorage; constructor(ctx: DurableObjectState, env: Env) { super(ctx, env); this.storage = ctx.storage; } override name() { return "notes"; } protected override instructions() { return "Use this connector to list and create saved notes."; } protected override tools(): ConnectorTools { return { listNotes: { description: "List saved notes.", execute: async () => (await this.storage.get<Note[]>("notes")) ?? [], }, createNote: { description: "Create a saved note.", inputSchema: { type: "object", properties: { text: { type: "string" } }, required: ["text"], }, requiresApproval: true, execute: async (input) => { const { text } = input as { text: string }; const note = { id: crypto.randomUUID(), text }; const notes = (await this.storage.get<Note[]>("notes")) ?? []; await this.storage.put("notes", [...notes, note]); return note; }, revert: async (_input, result) => { const { id } = result as Note; const notes = (await this.storage.get<Note[]>("notes")) ?? []; await this.storage.put( "notes", notes.filter((note) => note.id !== id), ); }, }, }; } }name()的返回值成为 sandbox 全局变量,本例为notes。requiresApproval: true会在createNote执行前暂停。可选的revert函数让runtime.rollback()补偿已应用的调用。对 MCP tool 使用
McpConnector,对 OpenAPI 操作使用OpenApiConnector。MCP 特定设置请参阅在 Code Mode 中使用 MCP tool。 -
导入 connector 并在 Agent 中创建 runtime:
src/server.jsjs import { AIChatAgent } from "@cloudflare/ai-chat"; import { createCodemodeRuntime, DynamicWorkerExecutor, } from "@cloudflare/codemode"; import { callable } from "agents"; import { convertToModelMessages, stepCountIs, streamText } from "ai"; import { NotesConnector } from "./notes-connector"; import { model } from "./model"; export class Chat extends AIChatAgent { #runtime() { return createCodemodeRuntime({ ctx: this.ctx, executor: new DynamicWorkerExecutor({ loader: this.env.LOADER }), connectors: [new NotesConnector(this.ctx, this.env)], }); } async onChatMessage() { const result = streamText({ model, messages: await convertToModelMessages(this.messages), tools: { codemode: this.#runtime().tool() }, stopWhen: stepCountIs(10), }); return result.toUIMessageStreamResponse(); } @callable() async pendingApprovals() { return this.#runtime().pending(); } @callable() async approveExecution(executionId) { return this.#runtime().approve({ executionId }); } @callable() async rejectExecution(executionId, seq) { return this.#runtime().reject({ executionId, seq }); } @callable() async rollbackExecution(executionId) { await this.#runtime().rollback({ executionId }); } @callable() async executionHistory() { return this.#runtime().executions(20); } @callable() async saveSnippet(name, description, executionId) { const runtime = this.#runtime(); const execution = (await runtime.executions()).find( (item) => item.id === executionId, ); if (execution?.status !== "completed") { throw new Error("Only completed executions can be saved as snippets."); } return runtime.saveSnippet(name, { description, executionId }); } @callable() async snippets() { return this.#runtime().snippets(); } }src/server.tsts import { AIChatAgent } from "@cloudflare/ai-chat"; import { createCodemodeRuntime, DynamicWorkerExecutor, type CodemodeRuntimeHandle, type ExecutionState, type PendingAction, type Snippet, } from "@cloudflare/codemode"; import { callable } from "agents"; import { convertToModelMessages, stepCountIs, streamText } from "ai"; import { NotesConnector } from "./notes-connector"; import { model } from "./model"; export class Chat extends AIChatAgent<Env> { #runtime(): CodemodeRuntimeHandle { return createCodemodeRuntime({ ctx: this.ctx, executor: new DynamicWorkerExecutor({ loader: this.env.LOADER }), connectors: [new NotesConnector(this.ctx, this.env)], }); } async onChatMessage() { const result = streamText({ model, messages: await convertToModelMessages(this.messages), tools: { codemode: this.#runtime().tool() }, stopWhen: stepCountIs(10), }); return result.toUIMessageStreamResponse(); } @callable() async pendingApprovals(): Promise<PendingAction[]> { return this.#runtime().pending(); } @callable() async approveExecution(executionId: string) { return this.#runtime().approve({ executionId }); } @callable() async rejectExecution(executionId: string, seq: number): Promise<boolean> { return this.#runtime().reject({ executionId, seq }); } @callable() async rollbackExecution(executionId: string): Promise<void> { await this.#runtime().rollback({ executionId }); } @callable() async executionHistory(): Promise<ExecutionState[]> { return this.#runtime().executions(20); } @callable() async saveSnippet( name: string, description: string, executionId: string, ): Promise<Snippet> { const runtime = this.#runtime(); const execution = (await runtime.executions()).find( (item) => item.id === executionId, ); if (execution?.status !== "completed") { throw new Error("Only completed executions can be saved as snippets."); } return runtime.saveSnippet(name, { description, executionId }); } @callable() async snippets(): Promise<Snippet[]> { return this.#runtime().snippets(); } }将
modelimport 替换为应用中现有的 model 设置。
当 MCP 服务器或其他 host 在不使用 AI SDK tool 适配器的情况下调用 Code Mode 时,使用 execute()、search() 与 describe():
const runtime = this.#runtime();
const matches = await runtime.search("create note");
const method = matches.results[0];
const docs = await runtime.describe(method.path);
const outcome = await runtime.execute({
code: `async () => notes.createNote({ text: "Follow up" })`,
});const runtime = this.#runtime();
const matches = await runtime.search("create note");
const method = matches.results[0];
const docs = await runtime.describe(method.path);
const outcome = await runtime.execute({
code: `async () => notes.createNote({ text: "Follow up" })`,
});search() 与 describe() 不运行沙箱代码。其结果对执行前暂停的连接器方法包含 requiresApproval: true。
execute() 返回与面向模型的工具相同的持久结果。结果可能完成、暂停或包含执行错误。用 approve() 或 reject() 解决暂停结果。
让模型列出已保存笔记。模型收到一个 codemode 工具,可在沙箱内发现连接器方法:
async () => {
const matches = await codemode.search("list saved notes");
const docs = await codemode.describe(matches.results[0].path);
const savedNotes = await notes.listNotes();
return { docs, savedNotes };
};当 model 调用 notes.createNote() 时,执行暂停。用 pendingApprovals() 显示待处理操作。将其 executionId 传给 approveExecution(),或将 executionId 与 seq 传给 rejectExecution()。
审批通过 replay 恢复同一脚本。已完成的调用返回记录结果而非再次运行。拒绝会结束暂停的执行,不会撤销更早的操作。
调用 rollbackExecution() 以补偿当前配置的 connector 提供 revert 的已应用调用。仅将已完成的执行保存为 snippet。