跳转到内容
搜索文档

操作(Actions)

最后更新 查看 MarkdownAgent 设置

Actions(操作)是开箱即用的服务端工具。普通 AI SDK tool() 只有描述、schema 与 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 与普通工具

getTools() 的普通 tool() 仍可用,适合只读或简单工具。当工具有不能执行两次的副作用、需要人工审批或声明式授权时用 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 的操作回退到按 toolCallId 的键,仅在同一工具调用内去重,不能跨重试。

待处理行与重试租约

execute 运行前账本行写为 pending,成功时翻转为 settled(抛出或超时的 execute 删除行以便干净重试)。隔离环境在执行中途死亡时行留在 pending。默认过期行在超过 actionLedgerPendingRetryLeaseMs(默认 5 分钟)后被回收并重跑操作 — 但仅对声明显式 idempotencyKey 的操作,因该键表示带键副作用重跑安全。新鲜的待处理行(或无显式键)则返回 ActionPendingError,避免模型盲目重试未知状态。设 actionLedgerPendingRetryLeaseMs = false 完全禁用回收,过期行始终抛出 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,可对风险输入才要求审批。approvalSummaryapprovalRisk"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 执行,单一审批 UI 可驱动两者。(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 失败则丢弃。账本重放不会再次触发附件(副作用已发生);从 permissionsapprovalidempotencyKey 回调调用 attachReply() 为空操作 — 从 execute 记录附件。

内置 ReplyAttachment 覆盖 email_draftcardvoice_note;任意 { type: string; ... } 可用于自定义投递。重写 renderAttachment() 将附件转为渠道通知。

参考

action(config)

字段 类型 必需 默认值 描述
description string 展示给模型的工具描述。
inputSchema FlexibleSchema(Zod 或 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[] 此调用所需权限(见授权)。
approval boolean | ({ input, ctx }) => boolean 在人工门控后执行。
approvalSummary string description 审批描述符中人类可读摘要。
approvalRisk "low" | "medium" | "high" 审批描述符中的风险提示。
kind "server" | "approval-gated" | "durable-pause" 推断 approval 时为 approval-gated,否则 server;显式设 durable-pause
timeoutMs number 30000 按操作的执行超时(也驱动 ctx.signal)。

Agent 上的钩子与方法

成员 描述
getActions() 返回编译为工具的操作描述符。
authorizeTurn(ctx) 每轮决定已授予权限。默认完全授权。
authorizeAction(ctx) 按次操作调用决定授权。默认检查 authorizeTurn 授权。
pendingApprovals(executionId?) 列出等待审批的已挂起操作与已暂停 Codemode 执行。
approveExecution(executionId) 批准已挂起执行;运行 execute 并自动继续轮次。
rejectExecution(executionId, reason?) 拒绝已挂起执行且不运行。
replyAttachments(requestId?) 读取轮次期间记录的建议性附件。
actionLedgerPendingRetryLeaseMs 过期待处理回收窗口(默认 300000false 禁用)。

相关

  • 工具 — 工作区工具、代码执行与扩展。
  • 人机协同 — 端到端审批流程。
  • 渠道 — 投递附件与带外通知。

这篇文档对您有帮助吗?