← Vytvářejte agenty na Cloudflare / agents / harnesses / think
Akce
Actions jsou serverové nástroje se vším vybavením. Zatímco běžný AI SDK tool() je jen popis, schéma a execute funkci, action() přidává věci, které je pro nástroj s reálnými vedlejšími účinky zdlouhavé a riskantní ručně správně implementovat:
- Idempotence : Trvalý ledger přehraje vypořádaný výsledek podle stabilního klíče, místo aby se při opakování po obnově spouštěl vedlejší efekt znovu.
- Schválení : podmíní volání schválením člověka, buď přímo (kolo čeká), nebo trvale (kolo se zaparkuje a později pokračuje, třeba i z dashboardu bez živého socketu).
- Autorizace : Deklarujte oprávnění, která volání vyžaduje, a udělte je pro jednotlivé tahy.
- Přílohy odpovědi : zaznamená informativní metadata o doručení (koncept e-mailu, kartu, hlasovou zprávu), aniž by to změnilo, co vidí model.
Actions se zkompilují do nástrojů Think, takže je model volá úplně stejně jako jakýkoli jiný nástroj. Vraťte je z getActions(); Think je sloučí do sady nástrojů společně s getTools(), nástroje pracovního prostoru, rozšíření a MCP nástroje.
Definujte akci
Použijte action() tovární funkci deskriptoru a vrátit mapu akcí z getActions(). Klíč v mapě je název nástroje, který vidí model (pokud nenastavíte name). execute typ vstupu se odvozuje z inputSchema:
import { Think, action } from "@cloudflare/think";
import { z } from "zod";
export class Support extends Think {
getActions() {
return {
refundOrder: action({
description: "Refund a customer order.",
inputSchema: z.object({
orderId: z.string(),
amountCents: z.number().int().positive(),
}),
execute: async ({ orderId, amountCents }, ctx) => {
const result = await refund(orderId, amountCents);
return { refundId: result.id, status: result.status };
},
}),
};
}
}import { Think, action } from "@cloudflare/think";
import { z } from "zod";
export class Support extends Think<Env> {
getActions() {
return {
refundOrder: action({
description: "Refund a customer order.",
inputSchema: z.object({
orderId: z.string(),
amountCents: z.number().int().positive(),
}),
execute: async ({ orderId, amountCents }, ctx) => {
const result = await refund(orderId, amountCents);
return { refundId: result.id, status: result.status };
},
}),
};
}
} execute callback dostane ověřený vstup a ActionContext:
type ActionContext = {
agent: Think;
env: Cloudflare.Env;
requestId: string;
toolCallId: string;
messages: ReadonlyArray<ModelMessage>;
signal: AbortSignal; // aborts on turn cancel or after `timeoutMs`
attachReply(attachment: ReplyAttachment): void;
};Výstup akce se před zobrazením modelu normalizuje do JSON a zkrátí (dlouhé výstupy mají omezenou délku). Cokoli je vyvoláno z execute se stává strukturovaným { error: { name, message } } výsledek nástroje, místo aby tah havaroval. Každá akce má výchozí časový limit 30 sekund; přepište ho pro jednotlivou akci pomocí timeoutMs.
Actions oproti běžným nástrojům
Obyčejný tool() z getTools() stále funguje a je správnou volbou pro nástroj, který je jen pro čtení nebo triviální. Sáhněte po action() když má nástroj vedlejší efekty, které nesmíte spustit dvakrát, potřebuje schválení člověkem nebo potřebuje deklarativní autorizaci: evidence, deskriptory schválení, výchozí časový limit a strukturované mapování chyb se vztahují pouze na akce.
Idempotence a deník akcí
Když akce deklaruje idempotencyKey, Think zaznamená ustálený výsledek do trvalé evidence indexované podle action:<name>:<key>. Pokud se stejný klíč objeví znovu, ať už při opakování po obnově, opětovném připojení nebo duplicitní příchozí události, Think vrátí uložený výsledek bez opětovné spuštění execute, takže za normálního průběhu nastane vedlejší efekt nejvýše jednou.
const chargeInvoice = action({
description: "Charge an invoice.",
inputSchema: z.object({ invoiceId: z.string() }),
// Use a stable domain identifier — never a timestamp, request id, or random value.
idempotencyKey: ({ input }) => `invoice:${input.invoiceId}`,
execute: async ({ invoiceId }) => charge(invoiceId),
});const chargeInvoice = action({
description: "Charge an invoice.",
inputSchema: z.object({ invoiceId: z.string() }),
// Use a stable domain identifier — never a timestamp, request id, or random value.
idempotencyKey: ({ input }) => `invoice:${input.invoiceId}`,
execute: async ({ invoiceId }) => charge(invoiceId),
});idempotencyKey je řetězec nebo funkce ({ input, ctx }) => string. Zvolte klíč, který přežije opakované pokusy o obnovení: ID objednávky, ID příchozí události, a ne hodnotu, která se mění při každém pokusu. Akce bez idempotencyKey spadá zpět na per-toolCallId klíč, který deduplikuje jen v rámci stejného volání nástroje, nikoli napříč opakováními.
Čekající řádky a právo na opakování
Řádek deníku se zapisuje jako pending před execute se spustí a přepne se do settled při úspěchu (vyhozená nebo časově vypršelá execute odstraní řádek, aby bylo možné volání znovu čistě zopakovat). Pokud izolát zemře uprostřed provádění, řádek zůstane pending. Ve výchozím nastavení se takový zastaralý řádek uvolní a akce se znovu spustí, jakmile je řádek starší než actionLedgerPendingRetryLeaseMs (výchozí 5 minut): ale pouze pro akce, které deklarují explicitní idempotencyKey, protože tento klíč je vaše ujištění, že opětovné spuštění vedlejšího efektu s tímto klíčem je bezpečné. Nový nevyřízený řádek (nebo řádek bez výslovného klíče) namísto toho vrátí ActionPendingError výsledek, aby model neopakoval neznámý stav naslepo. Nastavte actionLedgerPendingRetryLeaseMs = false k úplnému vypnutí obnovy a k trvalému zobrazování ActionPendingError pro zastaralý řádek.
Schválení
Podmiňte akci schválením člověka pomocí approval. Existují dva mechanismy, mezi nimiž se vybírá pomocí kind.
Podmíněné schválením (kolo čeká)
Výchozí nastavení, když nastavíte approval bez kind. Akce se zkompiluje do nástroje pomocí AI SDK needsApproval příznak: stream se pozastaví s approval-requested část, klient ji schválí nebo zamítne a kolo pokračuje inline. execute se spustí až po schválení.
const deleteAccount = action({
description: "Permanently delete a user account.",
inputSchema: z.object({ userId: z.string() }),
approval: true, // or ({ input }) => input.userId !== currentUser
approvalSummary: "Delete an account",
approvalRisk: "high",
execute: async ({ userId }) => deleteAccount(userId),
});const deleteAccount = action({
description: "Permanently delete a user account.",
inputSchema: z.object({ userId: z.string() }),
approval: true, // or ({ input }) => input.userId !== currentUser
approvalSummary: "Delete an account",
approvalRisk: "high",
execute: async ({ userId }) => deleteAccount(userId),
});approval je booleovská hodnota nebo predikát ({ input, ctx }) => boolean, takže můžete vyžadovat schválení jen u rizikových vstupů. approvalSummary a approvalRisk ("low" | "medium" | "high") naplní deskriptor schválení, který vaše UI vykreslí.
Trvalá pauza (kolo se zaparkuje a později pokračuje)
Nastavte kind: "durable-pause" když schválení může trvat minuty až dny a nechcete držet spojení otevřené. Akce se zaparkuje do trvalého úložiště a kolo skončí; execute ještě neběží. Pokračujte později, odkudkoli, včetně dashboardu bez živého WebSocketu, pomocí approveExecution() nebo rejectExecution():
const deploy = action({
description: "Deploy to production.",
inputSchema: z.object({ ref: z.string() }),
kind: "durable-pause",
approvalSummary: "Deploy to production",
approvalRisk: "high",
permissions: ["deploy:run"],
execute: async ({ ref }) => deploy(ref),
});const deploy = action({
description: "Deploy to production.",
inputSchema: z.object({ ref: z.string() }),
kind: "durable-pause",
approvalSummary: "Deploy to production",
approvalRisk: "high",
permissions: ["deploy:run"],
execute: async ({ ref }) => deploy(ref),
});// List everything waiting on a human (cold-load reconciliation):
const pending = await agent.pendingApprovals();
// [{ executionId, source: "action" | "codemode", descriptor }]
// Approve or reject by execution id (idempotent — a second call is a no-op):
await agent.approveExecution(executionId);
await agent.rejectExecution(executionId, "Not this release");// List everything waiting on a human (cold-load reconciliation):
const pending = await agent.pendingApprovals();
// [{ executionId, source: "action" | "codemode", descriptor }]
// Approve or reject by execution id (idempotent — a second call is a no-op):
await agent.approveExecution(executionId);
await agent.rejectExecution(executionId, "Not this release");approveExecution() spouští execute jednou a automaticky pokračuje v kole, i když není připojen žádný klient; rejectExecution() vyřeší akci, aniž by ji spustil. pendingApprovals() sloučí zaparkované akce a pozastavené Codemode provedení, takže obě dvě může řídit jediné UI pro schválení. (durable-pause vyžaduje approval zásady: akce, která by nikdy nebyla pozastavena, se odmítne už při definici.)
Jak části podmíněné schválením, tak trvale pozastavené části nesou stabilní ActionApprovalDescriptor ({ requestId, toolCallId, action, summary, input, permissions, risk, kind }) takže vaše UI má vše potřebné k vykreslení promptu.
Autorizace
Deklarujte oprávnění, která akce vyžaduje, pomocí permissions, poté je udělte pro jednotlivá kola zvlášť. Ve výchozím nastavení je každé kolo plně autorizované, autorizaci je tedy nutné explicitně povolit.
const refundOrder = action({
description: "Refund a customer order.",
inputSchema: z.object({ orderId: z.string() }),
permissions: ["billing:refund"], // or ({ input }) => [...]
execute: async ({ orderId }) => refund(orderId),
});const refundOrder = action({
description: "Refund a customer order.",
inputSchema: z.object({ orderId: z.string() }),
permissions: ["billing:refund"], // or ({ input }) => [...]
execute: async ({ orderId }) => refund(orderId),
});Přepsat authorizeTurn() k rozhodnutí, jednou za každé kolo, o tom, která oprávnění se udělí. Vrácení seznamu zúží udělený rozsah. Jakákoli akce vyžadující oprávnění mimo tuto množinu se zamítne se strukturovanou ActionAuthorizationError (model nikdy nevolá execute):
export class Support extends Think {
authorizeTurn(ctx) {
const role = ctx.body?.role;
if (role === "admin") return true; // full grant (the default)
return { allowed: true, grantedPermissions: ["billing:read"] };
}
}export class Support extends Think<Env> {
override authorizeTurn(ctx: TurnContext): ActionAuthorizationDecision {
const role = (ctx.body as { role?: string })?.role;
if (role === "admin") return true; // full grant (the default)
return { allowed: true, grantedPermissions: ["billing:read"] };
}
}authorizeTurn() vrací true (úplné udělení oprávnění), false (zakázat vše), nebo { allowed, reason?, grantedPermissions? }. Pro logiku specifickou pro jednotlivá volání přepište authorizeAction(ctx) místo toho: přijímá název akce, druh, vstup a požadovaná i udělená oprávnění.
Přílohy odpovědi
Akce může pro dané kolo zaznamenat informativní metadata o doručení, například koncept e-mailu, kartu nebo hlasovou zprávu, pomocí ctx.attachReply(). Přílohy nikdy nemění výstup nástroje, který model vidí. Jedou vedle odpovědi, aby je mohla vykreslit vaše doručovací vrstva.
const draftReply = action({
description: "Draft an email reply.",
inputSchema: z.object({ to: z.string(), subject: z.string() }),
execute: async ({ to, subject }, ctx) => {
ctx.attachReply({ type: "email_draft", to: [to], subject });
return { drafted: true };
},
});const draftReply = action({
description: "Draft an email reply.",
inputSchema: z.object({ to: z.string(), subject: z.string() }),
execute: async ({ to, subject }, ctx) => {
ctx.attachReply({ type: "email_draft", to: [to], subject });
return { drafted: true };
},
});Přečíst přílohy po kole z onChatResponse() hook, nebo z replyAttachments(requestId?) getter:
export class Support extends Think {
async onChatResponse(result) {
for (const attachment of result.attachments ?? []) {
// attachment.type === "email_draft" | "card" | "voice_note" | custom
}
}
}export class Support extends Think<Env> {
override async onChatResponse(result: ChatResponseResult) {
for (const attachment of result.attachments ?? []) {
// attachment.type === "email_draft" | "card" | "voice_note" | custom
}
}
}Přílohy se při čtení normalizují do formátu JSON a hluboce kopírují, jejich počet je v rámci kola omezen a zahazují se, pokud execute který je zaznamenal, selže. Přehrání deníku znovu nespouští přílohy (vedlejší efekt už nastal) a attachReply() nic neudělá, když se volá z permissions, approval, nebo idempotencyKey callback: zaznamenejte přílohy z execute.
Vestavěný ReplyAttachment pokrývá email_draft, card, a voice_note; libovolný { type: string; ... } tvar je povolen pro vlastní doručení. Přepište renderAttachment() k převedení přílohy na oznámení kanálu.
Referenční informace
action(config)
| Pole | Typ | Povinné | Výchozí | Popis |
|---|---|---|---|---|
description |
string |
Ano | , | Popis nástroje zobrazený modelu. |
inputSchema |
FlexibleSchema (Zod, Valibot nebo AI SDK jsonSchema) |
Ano | , | Ověřuje a typuje execute vstup. |
execute |
(input, ctx) => Output | Promise<Output> |
Ano | , | Tělo akce. Přijímá ověřený vstup a ActionContext. |
name |
string |
Ne | klíč mapy | Přepíše název nástroje. |
idempotencyKey |
string | ({ input, ctx }) => string |
Ne | za volání nástroje | Stabilní klíč pro přehrání deníku. Použijte doménový identifikátor. |
permissions |
readonly string[] | ({ input, ctx }) => readonly string[] |
Ne | žádné | Oprávnění, která toto volání vyžaduje (viz Authorization). |
approval |
boolean | ({ input, ctx }) => boolean |
Ne | žádné | Podmiňte volání schválením člověka. |
approvalSummary |
string |
Ne | description |
Člověku srozumitelné shrnutí v deskriptoru schválení. |
approvalRisk |
"low" | "medium" | "high" |
Ne | , | Nápověda k riziku v deskriptoru schválení. |
kind |
"server" | "approval-gated" | "durable-pause" |
Ne | odvozeno | approval-gated když approval je nastaven, jinak server; nastavte durable-pause explicitně. |
timeoutMs |
number |
Ne | 30000 |
Časový limit provedení na akci (řídí také ctx.signal). |
Hooky a metody agenta
| Člen | Popis |
|---|---|
getActions() |
Vraťte deskriptory akcí ke kompilaci do nástrojů. |
authorizeTurn(ctx) |
O udělených oprávněních rozhoduje jednou za kolo. Výchozí nastavení uděluje oprávnění v plném rozsahu. |
authorizeAction(ctx) |
Rozhoduje o autorizaci u každého volání akce. Výchozí chování kontroluje authorizeTurn uděluje. |
pendingApprovals(executionId?) |
Vypsat zaparkované akce a pozastavená spuštění Codemode čekající na schválení. |
approveExecution(executionId) |
Schválí zaparkované provádění; spustí execute a automaticky pokračuje v kole. |
rejectExecution(executionId, reason?) |
Odmítněte zaparkované spuštění, aniž byste ho spustili. |
replyAttachments(requestId?) |
Přečíst informativní přílohy zaznamenané během kola. |
actionLedgerPendingRetryLeaseMs |
Okno pro obnovu zastaralých nevyřízených položek (výchozí 300000; false k vypnutí). |
Související
- Nástroje : nástroje pracovního prostoru, spouštění kódu a rozšíření.
- Člověk ve smyčce : celý postup schvalování od začátku do konce.
- Kanály : doručuje přílohy a mimopásmová oznámení.