---
title: Vercel AI SDK
description: Use @codespar/vercel to integrate CodeSpar tools with the Vercel AI SDK for streaming agent interactions.
---

import { Callout } from "fumadocs-ui/components/callout";
import { Tab, Tabs } from "fumadocs-ui/components/tabs";

# Vercel AI SDK Adapter

<VersionBadge pkg="@codespar/vercel" />

The `@codespar/vercel` adapter integrates CodeSpar tools with the [Vercel AI SDK](https://sdk.vercel.ai/), giving you streaming-first agent interactions with built-in tool execution. The Vercel AI SDK handles the tool-call loop automatically via `maxSteps`, so you do not need to write a manual loop. This is the recommended adapter for Next.js applications.

<Callout type="info">
**Pick this adapter when** you want token-by-token streaming into a React UI (`useChat`), provider-agnostic switching between Anthropic / OpenAI / Google / Mistral with one API, or you are shipping on Next.js and want zero-config deployment on Vercel. See the [Streaming Chat cookbook](/docs/cookbooks/streaming-chat).
</Callout>

## Framework-specific notes

- **No manual tool-call loop** — `getTools(session)` returns tools with `.execute` pre-wired. Pass them to `streamText({ tools, maxSteps: 10 })` and the SDK iterates until the model stops calling tools or `maxSteps` hits.
- **`maxSteps` is a budget, not a guarantee** — set it to bound runaway agent loops. Commerce flows rarely need more than 5 steps; start at 5 and raise if you see truncation.
- **`onFinish` runs on success and error** — the safest place to call `await session.close()`. Wrapping `try/finally` does not help here because `streamText` returns before the stream completes.
- **Provider switching is one line** — `model: anthropic('claude-sonnet-4')` → `model: openai('gpt-4o')`, tools unchanged. Good fit for A/B-testing tool-calling accuracy without rewriting the agent.
- **`useChat` + tool indicators** — the React hook exposes `toolInvocations` on each message; render pending vs completed states inline for a better UX. See the cookbook for the pattern.

## Installation

<Tabs items={["npm", "pnpm", "yarn"]}>
<Tab value="npm">
```bash
npm install @codespar/sdk @codespar/vercel ai @ai-sdk/anthropic
```
</Tab>
<Tab value="pnpm">
```bash
pnpm add @codespar/sdk @codespar/vercel ai @ai-sdk/anthropic
```
</Tab>
<Tab value="yarn">
```bash
yarn add @codespar/sdk @codespar/vercel ai @ai-sdk/anthropic
```
</Tab>
</Tabs>

<Callout type="info">
You can use any Vercel AI SDK provider -- `@ai-sdk/anthropic`, `@ai-sdk/openai`, `@ai-sdk/google`, `@ai-sdk/mistral`, etc. The CodeSpar adapter is provider-agnostic. Install whichever provider you prefer.
</Callout>
## API Reference

### `getTools(session): Promise<Record<string, CoreTool>>`

Fetches all tools from the session and returns them in the Vercel AI SDK `tools` format. Each tool includes a `description`, `parameters` (Zod schema), and an `execute` function already wired to the session. This means the Vercel AI SDK can call tools automatically -- you do not need `handleToolUse` or `handleToolCall` like with the Claude and OpenAI adapters.

```typescript
import { CodeSpar } from "@codespar/sdk";
import { getTools } from "@codespar/vercel";

const codespar = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY });
const session = await codespar.create("user_123", {
  servers: ["stripe", "mercadopago"],
});

const tools = await getTools(session);

// tools is a Record<string, CoreTool>, keyed by tool name
// {
//   codespar_discover: { description: "...", parameters: ZodSchema, execute: fn },
//   codespar_charge:   { description: "...", parameters: ZodSchema, execute: fn },
//   codespar_pay:      { description: "...", parameters: ZodSchema, execute: fn },
//   ...
// }
```

<Callout type="warn">
`getTools` is async because it calls `session.tools()` under the hood. Always `await` it. The `execute` functions are pre-bound to the session, so tool calls are routed automatically when the Vercel AI SDK invokes them.
</Callout>
### `toVercelTool(session, tool): CoreTool`

Converts a single CodeSpar tool definition to a Vercel AI SDK `CoreTool`. Use this when you want to convert tools individually -- for example, to filter or augment the tool set before passing it to `generateText` or `streamText`.

```typescript
import { toVercelTool } from "@codespar/vercel";

const allTools = await session.tools();

// Filter to only payment tools and convert
const paymentTools = Object.fromEntries(
  allTools
    .filter((t) => ["codespar_charge", "codespar_pay"].includes(t.name))
    .map((t) => [t.name, toVercelTool(session, t)])
);

const { text } = await generateText({
  model: anthropic("claude-sonnet-4-20250514"),
  tools: paymentTools,
  maxSteps: 5,
  prompt: "Create a R$49.90 Pix charge",
});
```

## `generateText` example

Use `generateText` for simple request-response interactions where you do not need to stream output. The Vercel AI SDK handles the full tool-call loop automatically via `maxSteps`:

```typescript title="generate.ts"
import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { CodeSpar } from "@codespar/sdk";
import { getTools } from "@codespar/vercel";

const codespar = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY });

const session = await codespar.create("user_123", {
  servers: ["stripe", "mercadopago", "correios"],
});

const tools = await getTools(session);

const { text, steps } = await generateText({
  model: anthropic("claude-sonnet-4-20250514"),
  tools,
  maxSteps: 5,
  system:
    "You are a commerce assistant for a Brazilian e-commerce store. " +
    "Use the available tools for payments, invoicing, and shipping. " +
    "Always confirm amounts before processing.",
  prompt: "Create a R$79.90 charge for 'Starter Plan' via Stripe",
});

console.log("Response:", text);
console.log("Steps taken:", steps.length);

// Inspect tool calls made during the interaction
for (const step of steps) {
  if (step.toolCalls) {
    for (const call of step.toolCalls) {
      console.log(`  Tool: ${call.toolName}`, call.args);
    }
  }
}

await session.close();
```

```json title="Example tool call result (from steps)"
{
  "toolName": "codespar_charge",
  "args": {
    "provider": "stripe",
    "amount": 7990,
    "currency": "BRL",
    "description": "Starter Plan",
    "payment_methods": ["pix", "card"]
  },
  "result": {
    "charge_id": "pay_3m4n5o6p7q8r",
    "qr_code": "00020126360014BR.GOV.BCB.PIX0114...",
    "amount": 7990,
    "currency": "BRL",
    "status": "pending",
    "expires_at": "2026-04-16T14:30:00Z"
  }
}
```

<Callout type="info">
`maxSteps` controls how many tool-call rounds the SDK will automatically execute. Set it to match your expected tool-chain depth. For most commerce operations, 3-5 is sufficient. If the agent needs to discover, then checkout, then notify, that is 3 steps.
</Callout>
## `streamText` example

Use `streamText` for real-time streaming in a Next.js API route. This is the recommended approach for chat interfaces:

```typescript title="app/api/chat/route.ts"
import { streamText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { CodeSpar } from "@codespar/sdk";
import { getTools } from "@codespar/vercel";

const codespar = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY });

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

  const session = await codespar.create("user_123", {
    servers: ["stripe", "correios", "twilio"],
  });

  const tools = await getTools(session);

  const result = streamText({
    model: anthropic("claude-sonnet-4-20250514"),
    tools,
    maxSteps: 5,
    system:
      "You are a commerce assistant for a Brazilian e-commerce store. " +
      "Use tools to process payments, create shipping labels, and send notifications. " +
      "Always confirm amounts and details before processing.",
    messages,
    onFinish: async () => {
      // Clean up the session after the stream completes
      await session.close();
    },
  });

  return result.toDataStreamResponse();
}
```

<Callout type="info">
Use the `onFinish` callback to close the session after the stream completes. This ensures the session is cleaned up even if the client disconnects mid-stream.
</Callout>
### Client-side usage with `useChat`

Pair the streaming API route with the `useChat` hook for a complete chat experience:

```tsx title="app/chat/page.tsx"
"use client";

import { useChat } from "ai/react";

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

  return (
    <div className="flex flex-col h-screen max-w-2xl mx-auto p-4">
      <div className="flex-1 overflow-y-auto space-y-4">
        {messages.map((m) => (
          <div
            key={m.id}
            className={`p-3 rounded-lg ${
              m.role === "user"
                ? "bg-blue-100 ml-auto max-w-xs"
                : "bg-gray-100 mr-auto max-w-md"
            }`}
          >
            <p className="text-sm font-medium">
              {m.role === "user" ? "You" : "Assistant"}
            </p>
            <p>{m.content}</p>
          </div>
        ))}
      </div>
      <form onSubmit={handleSubmit} className="flex gap-2 pt-4">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Ask about payments, shipping, or invoicing..."
          className="flex-1 border rounded-lg px-4 py-2"
          disabled={isLoading}
        />
        <button
          type="submit"
          disabled={isLoading}
          className="bg-blue-600 text-white px-4 py-2 rounded-lg disabled:opacity-50"
        >
          Send
        </button>
      </form>
    </div>
  );
}
```

## Using with different providers

The CodeSpar adapter is provider-agnostic. Swap the model provider to use GPT, Gemini, or Mistral:

<Tabs items={["OpenAI", "Google", "Mistral"]}>
<Tab value="OpenAI">
```typescript
import { openai } from "@ai-sdk/openai";

const { text } = await generateText({
  model: openai("gpt-4o"),
  tools,
  maxSteps: 5,
  prompt: "Check shipping rates from Sao Paulo to Rio de Janeiro for 2kg",
});
```
</Tab>
<Tab value="Google">
```typescript
import { google } from "@ai-sdk/google";

const { text } = await generateText({
  model: google("gemini-1.5-pro"),
  tools,
  maxSteps: 5,
  prompt: "Create a boleto for R$500 due in 10 days",
});
```
</Tab>
<Tab value="Mistral">
```typescript
import { mistral } from "@ai-sdk/mistral";

const { text } = await generateText({
  model: mistral("mistral-large-latest"),
  tools,
  maxSteps: 5,
  prompt: "Send an order confirmation via WhatsApp to +5511999887766",
});
```
</Tab>
</Tabs>

## Error handling

The Vercel AI SDK surfaces tool execution errors through the `steps` array. You can also add error handling in the `onFinish` callback:

```typescript title="app/api/chat/route.ts"
const result = streamText({
  model: anthropic("claude-sonnet-4-20250514"),
  tools,
  maxSteps: 5,
  messages,
  onFinish: async ({ finishReason, steps }) => {
    // Log any tool errors
    for (const step of steps) {
      if (step.toolResults) {
        for (const result of step.toolResults) {
          if (result.result?.error) {
            console.error(
              `Tool ${result.toolName} failed:`,
              result.result.error
            );
          }
        }
      }
    }

    await session.close();
  },
});
```

For more control over error handling, wrap `getTools` and add error-aware execute functions using `toVercelTool`:

```typescript
import { toVercelTool } from "@codespar/vercel";

const allTools = await session.tools();

const tools = Object.fromEntries(
  allTools.map((t) => {
    const vercelTool = toVercelTool(session, t);
    return [
      t.name,
      {
        ...vercelTool,
        execute: async (args: unknown) => {
          try {
            return await vercelTool.execute(args);
          } catch (error) {
            return {
              error: error instanceof Error ? error.message : "Tool call failed",
              tool_name: t.name,
            };
          }
        },
      },
    ];
  })
);
```

<Callout type="info">
The Vercel AI SDK will pass error results back to the model automatically. The model can then reason about the error, retry, or ask the user for clarification -- just like with the Claude and OpenAI adapters.
</Callout>
## Best practices

1. **Use `maxSteps` wisely.** Start with 5 and adjust based on your use case. Too low and the agent cannot complete multi-step operations. Too high and you risk runaway tool calls and increased latency.

2. **Close sessions in `onFinish`.** For streaming routes, always clean up the session in the `onFinish` callback, not after the response is returned.

3. **Scope servers narrowly.** Only connect the MCP servers your agent needs. Fewer servers means fewer tools, which improves model accuracy.

4. **Use `streamText` for chat UIs.** It provides a better user experience than `generateText` because users see the response as it is generated.

5. **Inspect steps for debugging.** The `steps` array from `generateText` contains the full trace of tool calls and results. Use it for logging, debugging, and monitoring.

## Newer SDK wrappers

`getTools(session)` covers the LLM-driven tool-call surface. For higher-level flows, the session exposes typed wrappers you can call directly from your route — same routing infrastructure, no LLM hop:

- `session.discover(query)` / `session.charge(args)` / `session.pay(args)` / `session.ship(args)` — typed shortcuts for the meta-tools.
- `session.connectionWizard(serverId)` — open a hosted auth flow for a missing connection.
- `session.paymentStatus(toolCallId)` and `session.paymentStatusStream(toolCallId)` — async settlement correlation (poll or SSE).
- `session.verificationStatus(toolCallId)` and `session.verificationStatusStream(toolCallId)` — KYC outcome polling / SSE.

Full reference at [/docs/api/sdk](/docs/api/sdk).

## Next steps

<NextStepsGrid items={[
  { label: "COOKBOOK", title: "Streaming Chat Agent", description: "useChat + streamText end-to-end on Next.js App Router.", href: "/docs/cookbooks/streaming-chat" },
  { label: "CONCEPT", title: "Sessions", description: "Session lifecycle and server configuration.", href: "/docs/concepts/sessions" },
  { label: "CONCEPT", title: "Tools & Meta-Tools", description: "Meta-tools and how routing works.", href: "/docs/concepts/tools" },
  { label: "PROVIDER", title: "Claude Adapter", description: "Direct Anthropic SDK integration with manual tool loop.", href: "/docs/providers/claude" },
  { label: "PROVIDER", title: "OpenAI Adapter", description: "Direct OpenAI SDK integration.", href: "/docs/providers/openai" },
  { label: "PROVIDER", title: "MCP", description: "Use CodeSpar tools in Claude Desktop and Cursor.", href: "/docs/providers/mcp" },
]} />
