@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 injectioncleanup— Call when the AI run is complete to close the Bulwark sessionsession— 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.`;
},
});