このガイドでは、Agents SDK アプリケーションに耐久的な Code Mode ランタイムを追加します。ランタイムは、実行履歴、保留中の承認、スニペットを Durable Object のハイバネーションを越えて保存します。
Durable Object と Vite を使う既存の Agents SDK アプリケーションが必要です。例では AIChatAgent と AI SDK を使います。
-
Code Mode パッケージをインストールします。
npm i @cloudflare/codemodeyarn add @cloudflare/codemodepnpm add @cloudflare/codemodebun add @cloudflare/codemode -
Worker Loader バインディングを追加します。
DynamicWorkerExecutorはこのバインディングを使い、モデルが生成したコードを分離された Workers で実行します。{ "$schema": "./node_modules/wrangler/config-schema.json", // Set this to today's date "compatibility_date": "2026-09-20", "compatibility_flags": [ "nodejs_compat" ], "worker_loaders": [ { "binding": "LOADER" } ] }# Set this to today's date compatibility_date = "2026-09-20" 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"; -
コネクタを作成します。コネクタは通常のクラスで、特別なファイル名や import 構文は不要です。この例では、メモを Agent の Durable Object ストレージに保存します。
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()の結果がサンドボックスのグローバルになります。この場合はnotesです。requiresApproval: trueは、createNoteの実行前に一時停止します。任意のrevert関数により、runtime.rollback()は適用済みの呼び出しを補償できます。MCP ツールには
McpConnector、OpenAPI 操作にはOpenApiConnectorを使います。MCP 固有の設定は Code Mode で MCP ツールを使う を参照してください。 -
コネクタを import し、Agent 内でランタイムを作成します。
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(); } }modelの import は、アプリケーション既存のモデル設定に置き換えてください。
MCP サーバーや別ホストが、AI SDK のツールアダプターなしで 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 ツールを 1 つ受け取り、サンドボックス内でコネクタメソッドを発見できます。
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 };
};モデルが notes.createNote() を呼ぶと、実行は一時停止します。保留中のアクションは pendingApprovals() で表示します。承認するにはその executionId を approveExecution() に渡し、却下するには executionId と seq の両方を rejectExecution() に渡します。
承認すると、同じスクリプトを replay(再実行)で再開します。完了済みの呼び出しは再実行せず、記録済みの結果を返します。却下すると、それまでのアクションは取り消さずに、一時停止中の実行を終了します。
現在設定されているコネクタが revert を提供する適用済み呼び出しを補償するには、rollbackExecution() を呼びます。スニペットとして保存できるのは、完了した実行だけです。