1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
17 small wins to finish your pathNext lesson →
Tool approval before a refund
toolApproval lets a tool call wait for a person. The call ends with a tool-approval-request; your app sends an approval response and calls again.
import { tool } from "ai";
import { z } from "zod";
export const refundOrder = tool({
description: "Refund part or all of an order.",
inputSchema: z.object({ orderId: z.string(), amount: z.number() }),
execute: async ({ orderId, amount }) => `refunded ${amount} euros on ${orderId}`,
});import { generateText, isStepCount, type ModelMessage } from "ai";
import { shopModel } from "./shop-model.ts";
import { refundOrder } from "./refund.ts";
const settings = {
model: shopModel,
tools: { refundOrder },
toolApproval: { refundOrder: async ({ amount }) => (amount > 50 ? "user-approval" : undefined) },
stopWhen: isStepCount(5),
};
const messages: ModelMessage[] = [{ role: "user", content: "Refund 90 euros on A-1002" }];
const first = await generateText({ ...settings, messages });
console.log(first.content.map((part) => part.type));npx tsx approval.tstoolApproval maps a tool name to a policy. This one is a function of the parsed input: over 50 euros returns "user-approval", anything else runs normally. The model called refundOrder with 90, and instead of running it the SDK added a tool-approval-request and stopped.
const request = first.content.find((part) => part.type === "tool-approval-request");
messages.push(...first.response.messages, {
role: "tool",
content: [{ type: "tool-approval-response", approvalId: request.approvalId, approved: true }],
});
const second = await generateText({ ...settings, messages });
console.log(second.text);npx tsx approval.tsThe second call gets the first call's response messages plus a tool message with a tool-approval-response for that approvalId. Approved, the tool runs and the model answers. approved: false with a reason sends the denial to the model instead, and the tool never runs.
Who approves
If the browser sends the approval, a customer could approve their own refund. Check on the server that the person approving is allowed to, or use the SDK's
experimental_toolApprovalSecret to sign approval requests.Try it yourself
- Refund 30 euros and print the parts.
- Deny the request with a reason and print the second answer.
- Return
{ type: "denied", reason: "Refunds are closed" }from the policy.
This is what real progress feels like.