Aira
Integrations

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:

  1. Per-tool execute function — user-defined code that runs BEFORE the model sees the tool result. Wrapping it is the only place you can synchronously gate execution.
  2. onStepFinish / onFinish callbacks on generateText / 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 ai

Full example — gated tool call

gated-vercel-ai.ts
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

  1. The model decides to call sendWireTransfer.
  2. wrapTool's wrapper function runs first. It calls aira.authorize().
  3. The policy engine runs. If it denies, wrapTool throws an Error with the backend's code and message.
  4. Vercel AI treats the tool call as failed and surfaces the error to the model so the agent can react.
  5. 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): if authorize() 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: if authorize() 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 strictstrict 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 toolYou want to gate a specific branch inside a tool
You're using Vercel AI's standard tool() helperYou need the action_uuid in your tool body for some reason
You're OK with onStepFinish/onFinish being audit-onlyYou want to gate multi-step generations, not just tools

Known limits

  • onStepFinish and onFinish can't gate. Vercel AI has no pre-step hook. Use wrapTool() 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 INTEGRATIONS registry (src/extras/index.ts) declares this as kind: "gate" with preExecutionGate: true. Tests pin the registry.

On this page