INTEGRITY Документация

Действия

Actions представляют собой серверные инструменты со всем необходимым из коробки. Там, где обычный AI SDK tool() это всего лишь описание, схема и execute функцию, action() добавляет то, что утомительно и рискованно реализовать вручную для инструмента с реальными побочными эффектами:

Actions компилируются в инструменты Think, поэтому модель вызывает их точно так же, как любой другой инструмент. Возвращайте их из getActions(); Think объединяет их в набор инструментов вместе с getTools(), инструменты рабочей области, расширения и MCP-инструменты.

Определите действие

Используйте action() фабрику дескрипторов и возвращайте карту действий из getActions(). Ключом карты служит имя инструмента, которое видит модель (если не задать name). execute тип входных данных выводится из 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 колбэк получает проверенные входные данные и 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;
};

Результат действия нормализуется в JSON и усекается перед показом модели (длинные результаты ограничиваются). Всё, что выбрасывается из execute становится структурированным { error: { name, message } } результат инструмента, вместо того чтобы аварийно завершать ход. У каждого действия тайм-аут по умолчанию составляет 30 секунд; переопределите его для отдельного действия с помощью timeoutMs.

Actions против обычных инструментов

Обычный tool() от getTools() всё ещё работает и является правильным выбором для инструмента, доступного только для чтения, или тривиального инструмента. Обратитесь к action() когда у инструмента есть побочные эффекты, которые нельзя выполнять дважды, требуется подтверждение человеком или декларативная авторизация: реестр, дескрипторы подтверждения, тайм-аут по умолчанию и сопоставление структурированных ошибок применяются только к действиям.

Идемпотентность и журнал действий

Когда действие объявляет idempotencyKey, Think записывает устоявшийся результат в устойчивый журнал, индексированный по action:<name>:<key>. Если тот же ключ встречается снова: при повторной попытке восстановления, повторном подключении или дублирующемся входящем событии, Think возвращает сохранённый результат без повторный запуск execute, чтобы побочный эффект происходил не более одного раза в штатном сценарии.

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 это строка либо функция ({ input, ctx }) => string. Выберите ключ, который сохраняется при повторных попытках восстановления (например, id заказа или id входящего события), а не значение, которое меняется при каждой попытке. Действие без idempotencyKey переключается на per-toolCallId ключ, который устраняет дублирование только в рамках одного вызова инструмента, но не между повторными попытками.

Ожидающие строки и право на повторную попытку

Строка реестра записывается как pending перед execute выполняется и переключается в settled при успехе (выброшенный или завершившийся по тайм-ауту execute удаляет строку, чтобы вызов можно было корректно повторить). Если isolate завершает работу в процессе выполнения, строка остаётся pending. По умолчанию такая устаревшая запись освобождается, а действие повторяется, как только запись становится старше actionLedgerPendingRetryLeaseMs (по умолчанию 5 минут): но только для действий, которые явно объявляют idempotencyKey, поскольку этот ключ служит вашим подтверждением того, что повторный запуск побочного эффекта с данным ключом безопасен. Новая ожидающая запись (или запись без явного ключа) вместо этого возвращает ActionPendingError результат, чтобы модель не повторяла вслепую попытку в неизвестном состоянии. Установите actionLedgerPendingRetryLeaseMs = false чтобы полностью отключить reclaim и всегда показывать ActionPendingError для устаревшей строки.

Подтверждения

Требуйте одобрения человека для действия с помощью approval. Есть два механизма, выбор между ними задаётся kind.

Требует подтверждения (ход ожидает)

Значение по умолчанию при установке approval без kind. Действие компилируется в инструмент с помощью AI SDK needsApproval флаг: поток приостанавливается с approval-requested часть, клиент подтверждает или отклоняет, и ход продолжается непосредственно в потоке. execute выполняется только после подтверждения.

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 это булево значение либо предикат ({ input, ctx }) => boolean, чтобы вы могли требовать подтверждение только для рискованных входных данных. approvalSummary и approvalRisk ("low" | "medium" | "high") заполняют дескриптор одобрения, который отрисовывает ваш UI.

Устойчивая пауза (ход приостанавливается и позже возобновляется)

Задайте kind: "durable-pause" когда подтверждение может занять минуты или дни, а держать соединение открытым нежелательно. Действие приостанавливается в устойчивом хранилище, и ход завершается; execute ещё не выполняется. Возобновите позже, откуда угодно, включая панель управления без активного WebSocket-соединения, с помощью approveExecution() или 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() выполняет execute один раз и автоматически продолжает ход, даже если ни один клиент не подключён; rejectExecution() разрешает действие, не выполняя его. pendingApprovals() объединяет отложенные действия и приостановленные Codemode выполнений, поэтому оба может обслуживать единый интерфейс одобрения. (durable-pause требует approval политику: действие, которое никогда не приостанавливается, отклоняется уже на этапе определения.)

И части, требующие подтверждения, и части с устойчивой паузой несут стабильный ActionApprovalDescriptor ({ requestId, toolCallId, action, summary, input, permissions, risk, kind }) поэтому у вашего UI есть всё необходимое для отрисовки промпта.

Авторизация

Объявите разрешения, которые требуются действию, с помощью permissions, затем выдавайте их на каждый ход. По умолчанию каждый ход полностью авторизован, поэтому применение авторизации является добровольным.

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),
});

Переопределить authorizeTurn() чтобы раз в ход решать, какие разрешения предоставляются. Возврат списка сужает выданные права: любое действие, требующее разрешения вне этого набора, отклоняется со структурированной ActionAuthorizationError (модель никогда не вызывает 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() возвращает true (полное разрешение), false (запретить всё), или { allowed, reason?, grantedPermissions? }. Для логики на уровне отдельного вызова переопределите authorizeAction(ctx) вместо этого: он получает имя действия, тип, входные данные, а также требуемые и предоставленные разрешения.

Вложения ответа

Действие может записать для хода информативные метаданные доставки, например черновик письма, карточку или голосовую заметку, с помощью ctx.attachReply(). Вложения никогда не изменяют вывод инструмента, который видит модель: они передаются вместе с ответом, чтобы ваш слой доставки мог их отобразить.

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 };
	},
});

Прочитайте вложения после хода из onChatResponse() хук, или из replyAttachments(requestId?) геттер:

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
		}
	}
}

Вложения нормализуются в JSON, глубоко копируются при чтении, ограничиваются по количеству за ход и отбрасываются, если execute который их записал, завершается сбоем. Повторное воспроизведение реестра не запускает вложения заново (побочный эффект уже произошёл), и attachReply() не выполняет никаких действий при вызове из permissions, approval, или idempotencyKey колбэк: записывайте вложения из execute.

Встроенный ReplyAttachment покрывает email_draft, card, а также voice_note; любой { type: string; ... } форма разрешена для пользовательской доставки. Переопределите renderAttachment() чтобы превратить вложение в уведомление канала.

Справочник

action(config)

Поле Тип Обязательный По умолчанию Описание
description string Да , Описание инструмента, показываемое модели.
inputSchema FlexibleSchema (Zod, Valibot или AI SDK jsonSchema) Да , Валидирует и типизирует execute вход.
execute (input, ctx) => Output | Promise<Output> Да , Тело действия. Получает проверенные входные данные и ActionContext.
name string Нет ключ карты Переопределяет имя инструмента.
idempotencyKey string | ({ input, ctx }) => string Нет за вызов инструмента Устойчивый ключ для воспроизведения реестра. Используйте идентификатор домена.
permissions readonly string[] | ({ input, ctx }) => readonly string[] Нет нет Разрешения, которые требуются для этого вызова (см. Authorization).
approval boolean | ({ input, ctx }) => boolean Нет нет Требуйте одобрения человека для вызова.
approvalSummary string Нет description Понятное человеку описание в дескрипторе подтверждения.
approvalRisk "low" | "medium" | "high" Нет , Подсказка о риске в дескрипторе подтверждения.
kind "server" | "approval-gated" | "durable-pause" Нет выведено approval-gated когда approval задан, иначе server; задайте durable-pause явно.
timeoutMs number Нет 30000 Тайм-аут выполнения на действие (также определяет ctx.signal).

Хуки и методы агента

Участник Описание
getActions() Верните дескрипторы действий для компиляции в инструменты.
authorizeTurn(ctx) Определяйте предоставленные разрешения один раз за ход. По умолчанию предоставляются все права.
authorizeAction(ctx) Определяйте авторизацию для каждого вызова действия. По умолчанию выполняется проверка authorizeTurn предоставляет.
pendingApprovals(executionId?) Выводит список отложенных действий и приостановленных выполнений Codemode, ожидающих подтверждения.
approveExecution(executionId) Подтвердить приостановленное выполнение; запускает execute и автоматически продолжает ход.
rejectExecution(executionId, reason?) Отклоните приостановленное выполнение, не запуская его.
replyAttachments(requestId?) Прочитайте рекомендательные вложения, записанные в течение хода.
actionLedgerPendingRetryLeaseMs Окно освобождения устаревших ожидающих (по умолчанию 300000; false чтобы отключить).