Integrations
Human approval gates for Vercel AI SDK tool calls
The Vercel AI SDK can flag a tool call for approval with needsApproval. It does not store the request, route it to a person, or prove what was decided. Pliuz wraps your tool in one line and adds a Slack and web inbox plus a tamper-evident audit trail, enforcing approval at runtime before the action executes.
What Vercel AI SDK gives you
needsApproval (AI SDK 6) with tool-approval-request parts and addToolApprovalResponse. In AI SDK 6 you mark a tool with needsApproval: true (or an async function of the input). When the model calls it, generateText and streamText do not run the tool. They complete and return tool-approval-request parts in the result content. After a human decides, your app sends a tool-approval-response (via the useChat addToolApprovalResponse helper) and calls the model a second time, which then executes the tool or tells the model approval was denied.
needsApproval surfaces the request and threads the decision back through message history, but everything around it is yours to build. The SDK provides no persistence of pending approvals, no routing to a reviewer outside the chat session, and no audit trail of who decided what and when. The default approval is a client-side UX affordance, not a server boundary: the docs warn a malicious client can forge an approval and run a needsApproval tool with schema-valid arguments unless you wire up experimental_toolApprovalSecret yourself. Storage, human routing, and tamper-evident logging are explicitly left to your application.
Add a Pliuz approval gate
One wrapper is the whole integration surface — the gate is enforced at runtime, before the tool executes.
npm install @pliuz/sdkimport { tool, generateText, stepCountIs } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
import { gatedTool } from "@pliuz/sdk/adapters/ai";
// gatedTool wraps tool({...}); the LLM sees it identically — only execute() is gated.
const issueRefund = gatedTool(
{ toolName: "issue_refund", policy: "refund", redact: ["customer_id"] },
tool({
description: "Issue a refund to a customer.",
inputSchema: z.object({
customer_id: z.string(),
amount_cents: z.number().int().positive(),
}),
// Runs only after a human approves in Slack or the Pliuz web inbox.
execute: async ({ customer_id, amount_cents }) =>
stripe.refunds.create({ customer: customer_id, amount: amount_cents }),
}),
);
await generateText({ model: openai("gpt-4o"), tools: { issueRefund }, stopWhen: stepCountIs(5),
prompt: "Refund customer cus_123 for $50." });Where do approvals go in production?
In production with the Vercel AI SDK, approvals go to Pliuz. The SDK's needsApproval flag emits a tool-approval-request, but you still have to decide where that request is stored, who sees it, and how the decision is proven later. Pliuz is that layer: gatedTool wraps your existing tool({...}), holds the call before execute() runs, and routes the request to a human in Slack or a web inbox where they approve, reject, or edit the arguments. Every decision is written to a tamper-evident, append-only, Ed25519-anchored audit trail hosted in the EU, so the gate is enforced at runtime and provable after the fact.
FAQ
How do I add human approval to a Vercel AI SDK tool call?
Wrap your tool with gatedTool from @pliuz/sdk/adapters/ai, passing a required toolName and an optional policy. The wrapper preserves the tool's description and input schema (inputSchema in ai v5/v6) so the model sees it identically, but execute() now runs only after a human approves the call in Slack or the Pliuz web inbox. It works alongside or instead of the SDK's native needsApproval flag, and unlike needsApproval it persists the request, routes it to a reviewer, and records the decision.
Does the Vercel AI SDK have a built-in approval audit trail?
No. AI SDK 6's needsApproval returns a tool-approval-request and lets you thread a tool-approval-response back through message history, but it stores nothing and produces no audit log. The docs note the default approval is a client-side UX affordance a malicious client can forge, not a server boundary. Pliuz adds the missing layer: a tamper-evident, append-only, Ed25519-anchored audit trail of every approve, reject, and edit, hosted in the EU.
Is needsApproval enough for production human-in-the-loop in the Vercel AI SDK?
needsApproval is enough to surface a request inside a single chat session, but production needs more than the SDK ships. You still have to store pending approvals, route them to a real person outside the session, and prove who decided what. Pliuz provides that storage, the Slack and web inbox routing, and the audit trail, and it enforces the gate at runtime before the action executes.
Related
The lifecycle of a gated tool call: request, policy match, human decision in Slack or the web inbox, and execution or denial.
How Pliuz records every decision in an append-only, Ed25519-anchored log built for EU AI Act Article 12.
The same runtime enforcement pattern for LangGraph's interrupt-based human-in-the-loop, with storage and audit included.