Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

アクション

最終更新 Markdown で表示Agent セットアップ

アクションは、必要な機能が揃ったサーバー側ツールです。素の AI SDK tool() が説明、スキーマ、execute 関数だけなのに対し、action() は、実際の副作用を持つツールで手作業だと面倒で危険なものを足します。

  • べき等性 — 耐久台帳が安定キーで確定結果を再生し、復旧再試行で副作用を再実行しません。
  • 承認 — 人手の後ろに呼び出しを置きます。インライン(ターンが待つ)か、耐久的(ターンを駐車し、ライブソケットのないダッシュボードからでもあとで再開)です。
  • 認可 — 呼び出しが必要とする権限を宣言し、ターンごとに付与します。
  • 返信添付 — モデルが見る内容は変えず、助言的な配送メタデータ(下書きメール、カード、ボイスノート)を記録します。

アクションは 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再実行せず保存済み結果を返すので、正常系では副作用は最大 1 回です。

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 を宣言したアクションだけです。そのキーは、キー付き副作用の再実行が安全だという表明だからです。新しい pending 行(または明示キーのない行)は、代わりに ActionPendingError 結果を返し、モデルが未知状態を盲目再試行しないようにします。actionLedgerPendingRetryLeaseMs = false にすると回収を完全に止め、古い行には常に ActionPendingError を出します。

承認

approval でアクションを人手の後ろに置きます。メカニズムは 2 つあり、kind で選びます。

承認ゲート(ターンが待つ)

kind なしで approval を設定したときのデフォルトです。アクションは 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 は boolean、または述語 ({ 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 実行をマージするので、1 つの承認 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) をオーバーライドします。アクション名、kind、入力、必要な権限と付与済み権限を受け取ります。

返信添付

アクションは 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() は no-op です。添付は execute から記録します。

組み込みの ReplyAttachmentemail_draftcardvoice_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[] いいえ なし この呼び出しが必要とする権限です(認可を参照)。
approval boolean | ({ input, ctx }) => boolean いいえ なし 呼び出しを人手の後ろに置きます。
approvalSummary string いいえ description 承認記述子内の人が読める要約です。
approvalRisk "low" | "medium" | "high" いいえ 承認記述子内のリスクヒントです。
kind "server" | "approval-gated" | "durable-pause" いいえ 推論 approval があるときは approval-gated、なければ serverdurable-pause は明示設定します。
timeoutMs number いいえ 30000 アクションごとの実行タイムアウトです(ctx.signal も駆動します)。

エージェント上のフックとメソッド

メンバー 説明
getActions() ツールにコンパイルするアクション記述子を返します。
authorizeTurn(ctx) ターンごとに一度、付与する権限を決めます。デフォルトは完全付与です。
authorizeAction(ctx) アクション呼び出しごとに認可を決めます。デフォルトは authorizeTurn の付与を確認します。
pendingApprovals(executionId?) 承認待ちの駐車アクションと一時停止中の Codemode 実行を一覧します。
approveExecution(executionId) 駐車中の実行を承認します。execute を走らせ、ターンを自動継続します。
rejectExecution(executionId, reason?) 実行せずに駐車中の実行を拒否します。
replyAttachments(requestId?) ターン中に記録した助言的添付を読みます。
actionLedgerPendingRetryLeaseMs 古い pending の回収窓です(デフォルト 300000。無効化は false)。

関連

  • ツール — ワークスペースツール、コード実行、拡張です。
  • Human in the loop — 承認フローの全体です。
  • チャネル — 添付と帯域外通知の配送です。

役に立ちましたか?