← Vytvářejte agenty na Cloudflare / agents / tools / codemode
Jak funguje Code Mode
Code Mode je vzor, ve kterém model píše kód pro skládání nástrojů. @cloudflare/codemode balíček implementuje tento vzor pomocí izolovaného executoru, konektorů služeb a trvalého runtime.
Tyto části mají oddělené odpovědnosti. Executor spouští kód, ale neukládá žádný stav. Konektory poskytují schopnosti, ale neřídí přehrávání. Runtime zaznamenává provádění a řídí schválení, přehrávání, vrácení změn a opětovné použití.
Co vidí model
Ve standardním nastavení model obdrží jeden vnější nástroj s názvem codemode. Tento nástroj přijímá jedno pole a vrací trvalý výsledek provedení:
type CodeModeInput = {
code: string;
};
type PendingAction = {
executionId: string;
seq: number;
connector: string;
method: string;
args: unknown;
};
type CodeModeOutput =
| { status: "completed"; executionId: string; result: unknown; logs?: string[] }
| { status: "paused"; executionId: string; pending: PendingAction[] }
| { status: "error"; executionId: string; error: string; logs?: string[] };Popis říká modelu, aby napsal asynchronní šipkovou funkci JavaScriptu. Popis dále uvádí názvy nakonfigurovaných jmenných prostorů konektoru, například github nebo stripe, ale neobsahuje všechny metody a schémata konektoru.
Model může použít jedno spuštění Code Mode ke zjištění relevantních metod a v dalším spuštění pak využít vrácené cesty a typy. Kompletní katalog nástrojů tak zůstává mimo počáteční kontext modelu.
Platform SDK
Uvnitř sandboxu codemode globální proměnná poskytuje SDK na úrovni platformy:
declare const codemode: {
search(query: string): Promise<SearchOutput>;
describe(target: string): Promise<DescribeOutput>;
step<T>(name: string, fn: () => T | Promise<T>): Promise<T>;
run(name: string, input?: unknown): Promise<unknown>;
};
type SearchOutput = {
results: Array<{
path: string;
connector: string;
method: string;
description?: string;
kind: "method" | "snippet";
score: number;
}>;
total: number;
truncated: boolean;
};
type DescribeOutput = {
path: string;
description?: string;
types: string;
kind: "connector" | "method" | "snippet";
};codemode.search() prohledá metody konektoru a uložené fragmenty kódu. Vrací seřazené cesty, nikoli úplná schémata. Model pak může jednu cestu předat codemode.describe() k vyžádání cílené dokumentace TypeScript.
codemode.step() zaznamenává nedeterministickou práci v sandboxu s vedlejšími efekty pro přehrání. codemode.run() vyvolá uložený úryvek.
SDK pro konektory
Každý nakonfigurovaný konektor se stane další globální proměnnou sandboxu. Konektor s názvem github je k dispozici jako github, a jeho metody se objevují na cestách jako github.list_pull_requests.
Popis na úrovni konektoru vrací deklarace podobné těmto:
type ListPullRequestsInput = {
owner: string;
repo: string;
state?: "open" | "closed";
};
type ListPullRequestsOutput = unknown;
declare const github: {
list_pull_requests(
input: ListPullRequestsInput,
): Promise<ListPullRequestsOutput>;
};Tyto deklarace se generují ze schémat konektorů. Slouží jen jako ukázka: skutečné názvy metod, vstupní pole a výstupní typy závisí na konkrétním konektoru.
Sandbox také zahrnuje standardní globální objekty JavaScriptu. Nezpřístupňuje Node.js API, přihlašovací údaje hostitele, process, require, nebo neomezený přístup k síti. Všechny externí operace procházejí přes globální proměnné konektoru, pokud executor výslovně neposkytne jinou schopnost.
Executor, konektory a runtime
Executor
Executor jednou spustí jeden blok kódu vygenerovaného modelem. Přijímá volatelné jmenné prostory a vrací výsledek, chybu a zachycený výstup konzole. Historii provádění si neuchovává.
DynamicWorkerExecutor používá Dynamic Worker Loader pro vytvoření izolovaného Workeru pro každý průchod spuštění. Obnovené spuštění spustí kód znovu v dalším průchodu. Trvalý stav proto nemůže existovat uvnitř sandboxu.
Externí fetch() a connect() volání jsou ve výchozím nastavení blokovaná. DynamicWorkerExecutor konfiguruje globalOutbound: null pokud neuvedete jinou hodnotu. Můžete zadat Fetcher ke směrování odchozích požadavků přes řízenou službu.
Connectory
Konektory propojují služby na straně hostitele se sandboxem. Konektor může obalovat server Model Context Protocol (MCP), dokument OpenAPI, sadu nástrojů AI SDK nebo vlastní kód.
Každý konektor se stane globálním jmenným prostorem. Například konektor s názvem github zpřístupňuje volání, jako je github.list_pull_requests(). Vygenerovaný kód nikdy nezíská přihlašovací údaje konektoru ani klientské objekty.
Volání konektorů překračují hranici sandboxu pomocí Vzdálená volání procedur (RPC) ve Workers. Runtime zachytí každé volání ještě předtím, než jej konektor provede. Toto zachycení uplatňuje schválení, protokolování, přehrávání a zásady vracení změn.
codemode globální proměnná poskytuje operace zjišťování a operace za běhu. codemode.search() najde metody konektorů a uložené úryvky. codemode.describe() vrátí zaměřenou dokumentaci TypeScript, aniž by do kontextu modelu umístil schéma každého konektoru.
Trvalý runtime
Runtime propojuje executor a konektory. Ukládá záznamy o spuštění, protokoly volání konektorů, čekající schválení a úryvky do izolovaného úložiště SQLite. Tento stav přetrvá dokončení požadavku i hibernaci Durable Object.
Instance executoru a konektoru zůstávají dočasné. Vaše aplikace je poskytne znovu, když zpracovává pozdější schválení nebo požadavek.
Typický Agent vytváří všechny tři části společně:
import {
createCodemodeRuntime,
DynamicWorkerExecutor,
} from "@cloudflare/codemode";
const runtime = createCodemodeRuntime({
ctx: this.ctx,
executor: new DynamicWorkerExecutor({ loader: this.env.LOADER }),
connectors: [github, repoApi],
});
const tools = { codemode: runtime.tool() };import {
createCodemodeRuntime,
DynamicWorkerExecutor,
} from "@cloudflare/codemode";
const runtime = createCodemodeRuntime({
ctx: this.ctx,
executor: new DynamicWorkerExecutor({ loader: this.env.LOADER }),
connectors: [github, repoApi],
});
const tools = { codemode: runtime.tool() };Code Mode ukládá tento stav do facetu Durable Object. Facet je trvalý potomek Agenta s vlastním úložištěm SQLite. createCodemodeRuntime() a plugin Vite tento implementační detail řeší za vás. Facet nevytváříte ani neadresujete přímo.
Použití více runtimů
Většina Agents potřebuje pouze jeden runtime Code Mode. Pokud vynecháte name, runtime použije název default.
Nastavte name když jeden Agent potřebuje oddělené historie Code Mode. Například runtime pojmenovaný research a další s názvem operations si udržují oddělené záznamy o spuštění a kolekce úryvků:
const researchRuntime = createCodemodeRuntime({
ctx: this.ctx,
executor,
connectors: researchConnectors,
name: "research",
});
const operationsRuntime = createCodemodeRuntime({
ctx: this.ctx,
executor,
connectors: operationsConnectors,
name: "operations",
});const researchRuntime = createCodemodeRuntime({
ctx: this.ctx,
executor,
connectors: researchConnectors,
name: "research",
});
const operationsRuntime = createCodemodeRuntime({
ctx: this.ctx,
executor,
connectors: operationsConnectors,
name: "operations",
});Název runtime identifikuje jeho trvalé úložiště. Nepojmenovává model, konektor, nástroj ani jednotlivé provedení.
Změna sady konektorů nevytváří další runtime. Každé spuštění zaznamená všechny konektory nakonfigurované při jeho zahájení a uložený úryvek tento seznam zdědí. Opakované schvalování i spuštění úryvku vyžadují, aby všechny zaznamenané konektory zůstaly dostupné, i když je původní kód nevolal všechny.
Log trvalého provádění
Runtime přiřadí každému spuštění stabilní ID. Každé volání konektoru a codemode.step() položka obdrží pořadové číslo. Log zaznamenává její argumenty, stav, zásadu přehrávání a případně i výsledek.
Runtime označí volání jako executing před voláním jeho konektoru. Označí volání jako applied po zaznamenání výsledku. Pokud hostitel zastaví ještě před dokončením průchodu, provádění může zůstat ve stavu running. Pozdější expirePaused() údržbové volání označí toto zastaralé spuštění jako chybu a uvolní jeho prostředky. Schválení neobnoví zastaralý running spuštění.
Tento log je páteří pro přehrávání. Podporuje také auditní zobrazení pro vývojáře a určuje, které akce lze vrátit zpět. Nejde o obecnou paměť konverzace a nenahrazuje stav Agent.
Schválení pomocí přerušení a přehrání
Metoda konektoru může vyžadovat schválení uživatelem. Jakmile vygenerovaný kód tuto metodu dosáhne, runtime zaznamená akci jako čekající a přeruší aktuální průchod. Akce nedostane prozatímní výsledek.
Aplikace může uživateli zobrazit čekající metodu a její argumenty. Schválení spustí další průchod se stejným zdrojovým kódem a ID spuštění. Volání již označená jako použitá vrátí své zaznamenané výsledky, aniž by se prováděla znovu. Schválená akce se poté provede a kód pokračuje až do dokončení nebo dalšího schválení.
first pass: read ── execute ──> result
write ── pause
approval
second pass: read ── replay ───> recorded result
write ── execute ─> result
next call ────────> continueTento návrh umožňuje, aby schválení čekalo i po skončení požadavku nebo po hibernaci. Vygenerovaný kód zůstává lineární a neobsahuje logiku pro pozastavení nebo obnovení.
Pokračovat může pouze pozastavené provedení. Zastaralé schválení nemůže obnovit dokončené, zamítnuté nebo vrácené provedení. Zamítnutí akce provedení ukončí, ale nezruší dřívější akce. Vrácení změn je samostatná operace.
Chyby spuštění se vracejí agentní smyčce jako data. Chyby sandboxu a divergence při přehrávání proto nemusí unikat jako nezachycené výjimky RPC.
Deterministické přehrávání
Přehrávání vyžaduje, aby volání konektoru a kroky probíhaly ve stejném pořadí. V každém průchodu musí dané pořadové číslo používat stejný konektor, metodu a argumenty. Neshoda ukončí spuštění chybou divergence přehrávání.
Zaznamenané výsledky konektoru zajišťují stabilitu běžných větví závislých na datech. Přesto se hodnoty z Date.now(), Math.random(), nebo jiné nedeterministické zdroje mohou změnit tok řízení nebo argumenty akce.
Použijte codemode.step() pro jednorázové zachycení takové práce. Runtime zaznamená výsledek closure a při přehrání schválení tuto hodnotu vrátí:
async () => {
const createdAt = await codemode.step("created-at", () => Date.now());
return github.create_issue({
owner: "cloudflare",
repo: "agents",
title: `Review created at ${createdAt}`,
});
};Volání konektorů již procházejí runtime, a proto nepotřebují obal typu step. Step použijte pro nedeterministickou práci nebo práci s vedlejšími efekty mimo volání konektorů. Pokud výslovně povolíte přímý přístup k síti, patří sem i přímé síťové operace, které se nesmí opakovat při přehrávání schválení.
Volání konektoru vydávejte postupně, pokud provádění může být pozastaveno. Host přiřazuje pořadová čísla, jakmile volání dorazí. Volání v Promise.all() mohou v různých průchodech přicházet v odlišném pořadí a způsobit divergenci při přehrávání.
Životnost prostředků
Některé konektory potřebují prostředky přesahující jedno volání metody. Příkladem jsou relace prohlížeče, databázové transakce a dočasné pracovní prostory. Metody konektoru dostávají stabilní ID provedení, které lze použít jako klíč pro trvalá metadata prostředků napříč jednotlivými průchody.
Code Mode rozlišuje dvě životnosti prostředků:
- Předávání zdrojů platí jen pro jeden průchod sandboxem. Runtime volá
onPassEnd()po dokončených, neúspěšných a pozastavených průchodech. - Prostředky spuštění platí po celou dobu provádění. Runtime volá
disposeExecution()po dokončení, selhání, odmítnutí nebo vrácení zpět, ale ne po pozastavení.
Pozastavené provedení může pokračovat v jiném vyvolání Workeru. Hooky životního cyklu konektoru nesmí záviset na paměti instance. Úklid musí být rovněž idempotentní, protože dokončené provedení může být později vráceno zpět a znovu zlikvidováno.
Runtime volá lifecycle hooky pro každý nakonfigurovaný konektor. Konektor, který nealokoval žádný prostředek, by měl bezpečně nedělat nic. Chyby při úklidu se ignorují, aby dokončené spuštění nezměnily na neúspěšné.
Zásady přehrávání
Ve výchozím nastavení runtime uloží výsledek konektoru a při dalších průchodech ho přehraje. Tím se zachová přesně ta hodnota, kterou pozoroval původní kód.
Konektor může volání označit replay: "reexecute". Runtime přesto zaznamená jeho pořadí a argumenty, výsledek ale neukládá. Metodu konektoru znovu spustí až pozdější průchod.
Použijte tuto zásadu pouze pro idempotentní čtení s velkými, levnými výsledky. Výsledek se může mezi jednotlivými průchody měnit, takže vygenerovaný kód musí tuto změnu tolerovat. Metody vyžadující schválení nemohou použít replay: "reexecute" protože přehrání (replay) by mohlo schválený vedlejší efekt použít víckrát než jednou.
Vrácení zpět
Vrácení změn prochází aplikovaná volání konektoru v opačném pořadí. Volá revert implementaci pro každou použitou metodu, která ji poskytuje, bez ohledu na to, zda daná metoda vyžadovala schválení.
Pro každé použité volání konektoru runtime požádá aktuálně nakonfigurovaný konektor, aby spustil jeho revert implementace. Metody bez revert zůstávají použity. Chybějící konektory se také přeskočí. Neúspěšné vrácení změn nezastaví další pokusy o kompenzaci a runtime hlásí selhání až po vyzkoušení zbývajících volání. Provádění pokračuje do rolled_back pouze pokud je vráceno alespoň jedno volání.
Vrácení změn je kompenzace, nikoli izolace databázových transakcí. Autoři konektorů definují, co zrušení znamená pro každou akci. Externí systém se navíc může mezi původním voláním a jeho kompenzací změnit.
Uchovávání a zastaralá provedení
Deník běhů je auditní stopa, která postupně roste. Když začne nový běh, runtime ho nejprve vloží a teprve poté odstraní starší ukončené běhy. maxExecutions výchozí hodnota je 50. Protože běžící provedení není v koncovém stavu, dokončení může dočasně zanechat 51 koncových záznamů, dokud nezačne další běh nebo dokud nezavoláte pruneExecutions().
Spuštěná a pozastavená provedení se automaticky neodstraňují. Mohou ještě potřebovat dokončení nebo pokračování. Použijte expirePaused() z opakované údržby pro obnovu zastaralých neterminálních běhů. Runtime označí zastaralé pozastavené běhy jako odmítnuté a zastaralé běžící běhy jako chybové, poté uvolní jejich prostředky pro spuštění.
Jednotlivé záznamy o spuštění můžete také odstranit nebo explicitně vyčistit ukončenou historii. Smazání neukončeného spuštění uvolní jeho prostředky vázané na dané spuštění.
Limity trvalé hodnoty a výsledku
Každá hodnota uložená pro trvalé přehrávání má limit serializované délky 1,000,000 znaků. Implementace kontroluje délku řetězce JavaScriptu po serializaci. Tento limit platí pro argumenty konektoru, zaznamenané výsledky konektoru, výsledky kroků a zdrojový kód provedení.
Runtime nemůže tyto hodnoty zkrátit. Zkrácení by při replay poskytlo jiná data. Příliš velká nebo neserializovatelná hodnota pro replay proto způsobí selhání spuštění a doporučí uložit data jinam a předat pouze malý odkaz, například cestu k souboru.
Konečný výsledek se chová jinak, protože přehrávání ho nespotřebovává. Provedení se může dokončit a modelu vrátit skutečný výsledek. Pokud se výsledek nevejde do auditního záznamu, runtime tam místo něj uloží zprávu o vynechání.
transformResult může přetvořit dokončený výsledek dříve, než ho obdrží model. Transformace se spustí až poté, co se runtime pokusí zaznamenat surový výsledek. Auditní záznam si ponechá původní hodnotu, pokud se do něj vejde, zatímco model může dostat menší reprezentaci.
Snippets
Úryvek je uložený zdrojový kód z provedení. Úryvky mění programy napsané modelem na opakovaně použitelné recepty. Zůstávají dostupné napříč požadavky i hibernací.
Model svůj vlastní kód sám neschvaluje. Vaše aplikace zkontroluje spuštění a zavolá runtime.saveSnippet() s jeho ID provedení. API přijímá libovolný stav provedení, proto před uložením ověřte, že se provedení úspěšně dokončilo. Model pak úryvek najde pomocí codemode.search(), prozkoumejte ho pomocí codemode.describe(), a volejte ho s codemode.run().
const runs = await runtime.executions(20);
await runtime.saveSnippet("list-open-prs", {
executionId: runs[0].id,
description: "List open pull requests for a repository.",
});const runs = await runtime.executions(20);
await runtime.saveSnippet("list-open-prs", {
executionId: runs[0].id,
description: "List open pull requests for a repository.",
});Úryvek může přijímat vstupní hodnotu. Jeho volání konektorů se při spuštění modelem připojí k aktuálnímu protokolu provedení. Úryvek si také zachovává seznam konektorů ze svého zdrojového provedení. Pokud zaznamenaný konektor není dostupný, codemode.run() se vyřeší na objekt s error vlastnost. Sama o sobě nevyvolává výjimku.