Vercel AI SDK tool approval: confirm a paid video call

In the AI SDK, set toolApproval to 'user-approval' on a tool that starts a paid video job. The call pauses until your app sends back a decision.

5 min readSume
All posts

To make the Vercel AI SDK ask before a tool spends money, pass toolApproval to generateText, streamText, or ToolLoopAgent and return 'user-approval' for that tool. The call then returns a tool-approval-request instead of running the tool, and the tool runs only after you send back a tool-approval-response with approved: true.

The AI SDK facts come from its Tool Calling and MCP pages, read 2026-09-29; the page is written for AI SDK 7.x. The Sume facts come from Video Generation and Jobs and results. Sume has no connector for the AI SDK: the tool below is a plain HTTPS call from your server. The tool itself is explained in Vercel AI SDK: generate video with a Sume API tool call.

How do I require approval for one tool?

Give toolApproval a per-tool map. The AI SDK's docs say the older needsApproval property on tool() is deprecated, and that new code should move approval logic to toolApproval. The reason you set is emitted on the approval request so your UI can show it to the approver.

import { generateText, tool } from "ai";
import { z } from "zod";

const startVideo = tool({
  description: "Start a Sume video job. Returns a job id.",
  inputSchema: z.object({ prompt: z.string(), duration: z.number().optional() }),
  execute: async (input, { toolCallId }) => {
    const res = await fetch("https://api.sume.com/v1/videos", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.SUME_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": `chat-video-${toolCallId}`,
      },
      body: JSON.stringify({ model: "sume/auto", ...input }),
    });
    if (!res.ok) throw new Error(`Sume returned ${res.status}`);
    return res.json();
  },
});

export const first = (model: Parameters<typeof generateText>[0]["model"]) =>
  generateText({
    model,
    tools: { startVideo },
    toolApproval: {
      startVideo: { type: "user-approval", reason: "Starting a video spends balance" },
    },
    prompt: "Make a 5-second vertical clip of a desk lamp.",
  });

What happens between the request and the run?

Per the AI SDK docs, generateText and streamText do not pause: they complete and return the request, so manual approval takes two calls to the model.

  • Call generateText or streamText with toolApproval; the model makes the tool call.
  • Read result.content for parts of type tool-approval-request. Each carries approvalId, toolCall (with the tool name and input), and reason.
  • Ask your user. Then push a message with role: 'tool' whose content is a tool-approval-response with the same approvalId, approved: true or false, and an optional reason.
  • Call again with the updated messages. If approved, the tool runs; if denied, the model sees the denial.
  • Add a system instruction such as "When a tool execution is not approved, do not retry it", which the docs suggest so the model does not ask again.

Which approval statuses can I return?

The per-tool value is a status string, an object with a type and reason, or a function that returns one of them (or undefined).

From the AI SDK Tool Calling page, read 2026-09-29.
StatusWhat the docs say happens
'not-applicable'The tool runs without an approval flow. This is the default.
'approved'Records an automatic approval by emitting approval request and response parts, then runs the tool.
'denied'Records an automatic denial, then surfaces a denied tool output.
'user-approval'Emits an approval request and waits for an explicit response.

Can I ask only for long or expensive clips?

Yes. The docs show a per-tool async function that receives the tool input and returns 'user-approval' for some inputs and undefined for the rest; their example asks only when a payment amount is above 1000. For Sume, the same shape can ask when duration is set above a limit you choose. Sume reserves a video's cost on submit at provider list times 1.25, plus a 5.5% agent fee by default; how much does an AI video cost covers the math.

What about Sume's MCP tools in the AI SDK?

The AI SDK's MCP client exposes each tool's annotations on metadata.annotations and says the client does not turn them into an approval policy on its own. Its docs give a conservative policy: run a tool automatically only when readOnlyHint is true, and ask for approval otherwise.

In current code, Sume's read tools such as tools_list, jobs_status, and jobs_wait set readOnlyHint: true, while write and paid tools such as generate_video set readOnlyHint: false. That policy therefore runs status reads freely and asks before a paid submit. Sume's docs also say idempotency_key on hosted MCP is for transport and dedup, not human approval, so the approval still has to come from your app.

What does approval not cover?

Approval is one layer. These limits are worth keeping in mind before you rely on it.

  • The AI SDK docs say toolApproval only controls tools the SDK executes locally; provider-executed tools run provider-side without considering it.
  • A denied call still leaves the decision to the model: keep a system instruction that stops retries.
  • Approval is not a spend cap. Sume's hosted MCP tools accept an optional max_spend_usd, enforced only when you pass it; see MCP tools and gates.
  • Keep SUME_API_KEY on the server that runs execute; never put it in client code.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume