Vercel AI SDK
Gate Vercel AI tool calls through Aira's policy engine using wrapTool(). Denied tools never run; onFinish callbacks are audit-only.
Kind: gate (for tools via wrapTool()) + audit (for onStepFinish / onFinish) · Pre-execution gate: yes, on tools · Peer dep: ai
What this integration actually does
Vercel AI SDK has two integration points:
- Per-tool
executefunction — user-defined code that runs BEFORE the model sees the tool result. Wrapping it is the only place you can synchronously gate execution. onStepFinish/onFinishcallbacks ongenerateText/streamText— fire AFTER each step or after the whole generation. Post-hoc only; they cannot block a tool from running.
AiraVercelMiddleware.wrapTool() takes a tool function, calls aira.authorize() before invoking it, and aira.notarize() after. This is the real gate. The onStepFinish / onFinish helpers are explicitly labeled audit-only in the source.
Install
npm install aira-sdk
# peer dep (usually already present)
npm install aiFull example — gated tool call
import { Aira } from "aira-sdk";
import { AiraVercelMiddleware } from "aira-sdk/extras/vercel-ai";
import { generateText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
const aira = new Aira({ apiKey: "aira_live_..." });
const middleware = new AiraVercelMiddleware(aira, "payments-agent", {
modelId: "gpt-4o",
strict: false, // fail open on authorize network errors; set to true to fail closed
});
// Your raw tool function
async function sendWireTransferImpl({ amount, to }: { amount: number; to: string }) {
// real side effect — banking API, Stripe, whatever
return `Wire sent: €${amount} to ${to}`;
}
// Wrap it — this is where the gate lives
const sendWireTransfer = tool({
description: "Send a wire transfer",
parameters: z.object({
amount: z.number(),
to: z.string(),
}),
execute: middleware.wrapTool(sendWireTransferImpl, "send_wire_transfer"),
});
const result = await generateText({
model: openai("gpt-4o"),
tools: { sendWireTransfer },
prompt: "Send €75,000 to vendor-x",
});What happens when a policy denies
- The model decides to call
sendWireTransfer. wrapTool's wrapper function runs first. It callsaira.authorize().- The policy engine runs. If it denies,
wrapToolthrows anErrorwith the backend's code and message. - Vercel AI treats the tool call as failed and surfaces the error to the model so the agent can react.
- The real transfer function never runs.
strict mode — fail open vs fail closed
new AiraVercelMiddleware(aira, "payments-agent", {
strict: true, // throws on network errors — tool doesn't run if authorize() can't reach Aira
});strict: false(default): ifauthorize()fails due to a network error or 5xx, log a warning and let the tool run. Your receipt is missing but the agent is not wedged.strict: true: ifauthorize()can't reach Aira, throw. The tool never runs. Use this for high-stakes deployments where "no signature" means "don't act".
POLICY_DENIED always throws regardless of strict — strict only controls what happens when Aira itself is unreachable.
Trust checks for counterparty agents
If your agent interacts with another org's agent, AiraVercelMiddleware supports a trustPolicy:
const middleware = new AiraVercelMiddleware(aira, "payments-agent", {
trustPolicy: {
requireRegistered: true,
minReputation: 70,
blockOnRevokedVc: true,
},
});
const trust = await middleware.checkTrust("did:web:partner.com:agents:their-agent");
if (trust.blocked) {
throw new Error(`Blocked counterparty: ${trust.recommendation}`);
}When to use wrapTool() vs inline aira.authorize()
Use wrapTool() when... | Call aira.authorize() inline when... |
|---|---|
| You want a clean, declarative gate on every tool | You want to gate a specific branch inside a tool |
You're using Vercel AI's standard tool() helper | You need the action_uuid in your tool body for some reason |
You're OK with onStepFinish/onFinish being audit-only | You want to gate multi-step generations, not just tools |
Known limits
onStepFinishandonFinishcan't gate. Vercel AI has no pre-step hook. UsewrapTool()for gating and those callbacks for audit receipts only.- The wrapped function receives the original args verbatim. If your tool takes an object and Aira's details string needs something from that object, build it yourself before calling
wrapTool.
Proof it works
- TypeScript:
tests/extras-vercel-ai.test.ts(178 lines, 14 tests) - The SDK's
INTEGRATIONSregistry (src/extras/index.ts) declares this askind: "gate"withpreExecutionGate: true. Tests pin the registry.
Related
- OpenAI Agents — same pre-execution-wrap pattern
- LangChain — callback-based alternative for LangChain agents
- Public verification
LangChain
Gate LangChain tool calls through Aira's policy engine. Denied tools never run; chain and LLM steps are audit-only because LangChain has no pre-chain abort hook.
OpenAI Agents
Wrap OpenAI Agents tools through Aira's policy engine. authorize() runs before the tool body. Denied calls never run. Python and TypeScript.