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 按操作覆盖。
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,可对风险输入才要求审批。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 执行,单一审批 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 失败则丢弃。账本重放不会再次触发附件(副作用已发生);从 permissions、approval 或 idempotencyKey 回调调用 attachReply() 为空操作 — 从 execute 记录附件。
内置 ReplyAttachment 覆盖 email_draft、card、voice_note;任意 { type: string; ... } 可用于自定义投递。重写 renderAttachment() 将附件转为渠道通知。
| 字段 | 类型 | 必需 | 默认值 | 描述 |
|---|---|---|---|---|
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)。 |
| 成员 | 描述 |
|---|---|
getActions() |
返回编译为工具的操作描述符。 |
authorizeTurn(ctx) |
每轮决定已授予权限。默认完全授权。 |
authorizeAction(ctx) |
按次操作调用决定授权。默认检查 authorizeTurn 授权。 |
pendingApprovals(executionId?) |
列出等待审批的已挂起操作与已暂停 Codemode 执行。 |
approveExecution(executionId) |
批准已挂起执行;运行 execute 并自动继续轮次。 |
rejectExecution(executionId, reason?) |
拒绝已挂起执行且不运行。 |
replyAttachments(requestId?) |
读取轮次期间记录的建议性附件。 |
actionLedgerPendingRetryLeaseMs |
过期待处理回收窗口(默认 300000;false 禁用)。 |