Vercel AI SDK
Gate tool calls in the Vercel AI SDK behind Cheqpoint approval — works with useChat, generateText, and streamText in any Next.js app.
Installation
bash
npm install @cheqpoint/sdk aiStep 1 — Wrap a tool with Cheqpoint approval
TypeScript
// lib/tools/processRefund.ts
import { tool } from "ai";
import { z } from "zod";
import { CheqpointClient, RejectedError } from "@cheqpoint/sdk";
const cheqpoint = new CheqpointClient({ connectionKey: process.env.CHEQPOINT_CONNECTION_KEY! });
export const processRefund = tool({
description: "Process a customer refund — requires human approval",
parameters: z.object({
orderId: z.string(),
amount: z.number().positive(),
reason: z.string(),
}),
execute: async ({ orderId, amount, reason }) => {
const approval = await cheqpoint.checkpoint({
action: "process_refund",
summary: `Refund $${amount} for order ${orderId}`,
details: { orderId, amount, reason },
riskLevel: amount > 500 ? "high" : "medium",
});
if (approval.status !== "APPROVED") {
throw new RejectedError(`Refund declined: ${approval.decisionNote ?? "No reason provided"}`);
}
// Reviewer may have changed the amount — always prefer modifiedDetails
const payload = approval.modifiedDetails ?? { orderId, amount };
const refund = await stripe.refunds.create({ charge: await lookupCharge(payload.orderId), amount: Math.round(payload.amount * 100) });
return { success: true, refundId: refund.id, amount: payload.amount };
},
});Step 2 — Use in a Next.js API route
TypeScript
// app/api/chat/route.ts
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
import { processRefund } from "@/lib/tools/processRefund";
import { deleteRecord } from "@/lib/tools/deleteRecord";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = await streamText({
model: openai("gpt-4o"),
messages,
tools: { processRefund, deleteRecord },
maxSteps: 5,
});
return result.toDataStreamResponse();
}Step 3 — Render tool state in useChat
TypeScript
// app/components/Chat.tsx
"use client";
import { useChat } from "ai/react";
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat();
return (
<div>
{messages.map((m) => (
<div key={m.id}>
<strong>{m.role}:</strong> {m.content}
{/* Show tool invocations */}
{m.toolInvocations?.map((tool) => (
<div key={tool.toolCallId} className="text-xs text-muted-foreground">
{tool.state === "call" && `⏳ Waiting for approval: ${tool.toolName}...`}
{tool.state === "result" && `✅ ${tool.toolName} completed`}
</div>
))}
</div>
))}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} placeholder="Ask the Assistant..." />
<button type="submit">Send</button>
</form>
</div>
);
}Non-streaming with generateText
TypeScript
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { processRefund } from "@/lib/tools/processRefund";
const { text, steps } = await generateText({
model: openai("gpt-4o"),
prompt: "Process a $150 refund for order #8821",
tools: { processRefund },
maxSteps: 3,
});
// steps contains each tool call + its approval result
console.log("Final response:", text);