pliuzv0.1.x

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.

install
npm install @pliuz/sdk
TypeScript
import { 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.

Enforcement at runtime, not documentation after the fact
needsApproval can pause a tool call inside the request, but a forged client message can replay it and observability tools only describe what already happened. Pliuz blocks the action server-side until a named human approves, then anchors the approve, reject, or edit in an append-only audit trail. That is how EU AI Act Article 14 (human oversight) and Article 12 (logging) get implemented mechanically rather than promised in a policy doc.

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