INTEGRITY Dokumentace

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:

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í).