INTEGRITY Dokumentace

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

  1. Nainstalujte balíček Code Mode:

    npm i @cloudflare/codemode
  2. Přidejte vazbu Worker Loader. DynamicWorkerExecutor pomocí 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"
  3. Přidejte pluginy Agents a Code Mode do vite.config.ts:

    vite.config.js
    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.ts
    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()],
    });

    Plugin exportuje CodemodeRuntime tří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řes ctx.exports. Pokud plugin nepoužíváte, přidejte export ručně:

    export { CodemodeRuntime } from "@cloudflare/codemode";
  4. 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.js
    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.ts
    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() výsledek se stane globální proměnnou sandboxu, v tomto případě notes. requiresApproval: true se pozastaví před createNote se provede. Volitelné revert funkce umožňuje runtime.rollback() kompenzuje aplikované volání.

    Použijte McpConnector pro nástroje MCP nebo OpenApiConnector pro operace OpenAPI. Nastavení specifické pro MCP najdete v Použití nástrojů MCP s Code Mode.

  5. Importujte konektor a vytvořte runtime ve svém Agentovi:

    src/server.js
    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.ts
    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();
    	}
    }

    Nahraďte model import 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í.