@bulwark/vercel-ai

Bulwark integration for the Vercel AI SDK — authenticated tools for useChat and streamText.

Installation

npm install @bulwark/vercel-ai

Quick Start

Server-side (Route Handler)

// app/api/chat/route.ts
import { streamText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { bulwarkTools } from "@bulwark/vercel-ai";
import { getSession } from "@bulwark/nextjs/server";

export async function POST(req: Request) {
  const { messages } = await req.json();

  // Get the authenticated user from the Bulwark session
  const session = await getSession();

  // Create a Bulwark agent session with credential-injected tools
  const { tools, cleanup } = await bulwarkTools({
    tenantId: process.env.BULWARK_TENANT_ID!,
    agentId: process.env.BULWARK_AGENT_ID!,
    agentApiKey: process.env.BULWARK_AGENT_KEY!,
    userId: session?.user.id,
    scopes: ["read:github", "write:slack"],
  });

  const result = streamText({
    model: anthropic("claude-opus-4-6"),
    messages,
    tools,
    onFinish: cleanup, // closes Bulwark session when done
  });

  return result.toDataStreamResponse();
}

Client-side

"use client";
import { useChat } from "ai/react";

export default function Chat() {
  const { messages, input, handleSubmit, setInput } = useChat({
    api: "/api/chat",
  });

  return (
    <div>
      {messages.map((m) => (
        <div key={m.id}>{m.content}</div>
      ))}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={(e) => setInput(e.target.value)} />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}

bulwarkTools

const { tools, cleanup, session } = await bulwarkTools({
  tenantId: string;
  agentId: string;
  agentApiKey: string;
  userId?: string;
  scopes?: string[];
  baseUrl?: string;
});

Returns:

  • tools — Vercel AI SDK-compatible tool definitions with Bulwark credential injection
  • cleanup — Call when the AI run is complete to close the Bulwark session
  • session — Raw Bulwark session for advanced use

Custom Tool with Credential Proxy

import { createBulwarkTool } from "@bulwark/vercel-ai";
import { z } from "zod";

const githubSearch = createBulwarkTool({
  session,
  credentialId: "cred_github",
  name: "github_search",
  description: "Search GitHub repositories",
  parameters: z.object({
    query: z.string().describe("Search query"),
  }),
  execute: async ({ query }, { proxyRequest }) => {
    const result = await proxyRequest({
      method: "GET",
      url: `https://api.github.com/search/repositories?q=${encodeURIComponent(query)}`,
    });
    return result.items.slice(0, 5).map((r: { full_name: string; description: string }) => ({
      name: r.full_name,
      description: r.description,
    }));
  },
});

CIBA Tool

import { createCIBATool } from "@bulwark/vercel-ai";

const approvePayment = createCIBATool({
  session,
  userId: session.userId,
  name: "approve_payment",
  description: "Request user approval before sending a payment",
  approvalScope: "approve:payment",
  parameters: z.object({
    amount: z.number(),
    recipient: z.string(),
  }),
  buildMessage: ({ amount, recipient }) =>
    `Approve payment of $${amount} to ${recipient}?`,
  onApproved: async ({ amount, recipient }) => {
    // execute payment
    return `Payment of $${amount} to ${recipient} sent.`;
  },
});