← Vytvářejte agenty na Cloudflare / agents / tools / codemode
Vytvořte trvalý runtime Code Mode
Tento návod přidává trvalý runtime Code Mode do aplikace postavené na Agents SDK. Runtime uchovává historii provádění, nevyřízená schválení a snippety i přes hibernaci Durable Object.
Předpoklady
Potřebujete existující aplikaci Agents SDK s Durable Object a Vite. Příklad používá AIChatAgent a AI SDK.
Integrace Code Mode
-
Nainstalujte balíček Code Mode:
npm i @cloudflare/codemode -
Přidejte vazbu Worker Loader.
DynamicWorkerExecutorpomocí této vazby spouští kód vygenerovaný modelem v izolovaných Workers:{ "$schema": "./node_modules/wrangler/config-schema.json", // Set this to today's date "compatibility_date": "2026-08-28", "compatibility_flags": [ "nodejs_compat" ], "worker_loaders": [ { "binding": "LOADER" } ] }# Set this to today's date compatibility_date = "2026-08-28" compatibility_flags = ["nodejs_compat"] [[worker_loaders]] binding = "LOADER" -
Přidejte pluginy Agents a Code Mode do
vite.config.ts:vite.config.jsimport { 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.tsimport { 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()], });Plugin exportuje
CodemodeRuntimetřídu facet z vašeho vstupního modulu Workeru. Runtime ukládá stav provádění do facetu Durable Object a Workers runtime vyžaduje, aby třídy facet byly dostupné přesctx.exports. Pokud plugin nepoužíváte, přidejte export ručně:export { CodemodeRuntime } from "@cloudflare/codemode"; -
Vytvořte konektor. Konektory jsou obyčejné třídy: nepotřebují žádný speciální název souboru ani syntaxi importu. Tento příklad ukládá poznámky do úložiště Durable Object daného Agenta:
src/notes-connector.jsimport { 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.tsimport { 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()výsledek se stane globální proměnnou sandboxu, v tomto případěnotes.requiresApproval: truese pozastaví předcreateNotese provede. Volitelnérevertfunkce umožňujeruntime.rollback()kompenzuje aplikované volání.Použijte
McpConnectorpro nástroje MCP neboOpenApiConnectorpro operace OpenAPI. Nastavení specifické pro MCP najdete v Použití nástrojů MCP s Code Mode. -
Importujte konektor a vytvořte runtime ve svém Agentovi:
src/server.jsimport { 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.tsimport { 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(); } }Nahraďte
modelimport se stávajícím nastavením modelu ve vaší aplikaci.
Použití runtime bez AI SDK
Použijte execute(), search(), a describe() když server MCP nebo jiný hostitel volá Code Mode bez adaptéru nástroje AI SDK:
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() a describe() nespouštějí kód sandboxu. Jejich výsledky obsahují requiresApproval: true pro metody konektoru, které se před spuštěním pozastaví.
execute() vrátí stejný trvalý výsledek jako nástroj určený modelu. Výsledek může být dokončený, pozastavený, nebo může obsahovat chybu provádění. Pozastavený výsledek vyřešte pomocí approve() nebo reject().
Ověřte integraci
Požádejte model, aby vypsal uložené poznámky. Model obdrží jeden codemode nástroj a uvnitř sandboxu může zjišťovat metody konektoru:
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 };
};Když model zavolá notes.createNote(), provádění se pozastaví. Použijte pendingApprovals() k zobrazení čekající akce. Předejte její executionId na approveExecution(), nebo předejte obojí executionId a seq na rejectExecution().
Schválení obnoví stejný skript pomocí přehrání. Dokončená volání vrátí zaznamenané výsledky místo opětovného spuštění. Zamítnutí ukončí pozastavené provádění, aniž by vrátilo zpět dřívější akce.
Zavolejte rollbackExecution() pro kompenzaci aplikovaných volání, jejichž aktuálně nakonfigurovaný konektor poskytuje revert. Jako úryvky ukládejte pouze dokončená spuštění.