# Vercel AI SDK

Install Brizz alongside the Vercel AI SDK and tag sessions.

Quickstart for the Vercel AI SDK integration shown during onboarding. For the full TypeScript SDK reference, see the [TypeScript SDK guide](/docs/sdks/typescript.md).

## Install

:::tabs
:::tab[npm]
```bash
npm install @brizz/sdk
```
:::tab[yarn]
```bash
yarn add @brizz/sdk
```
:::tab[pnpm]
```bash
pnpm add @brizz/sdk
```
:::

On Vercel AI SDK **v7+** (`ai@7`), also install `@ai-sdk/otel` — v7 moved AI SDK span
collection into that package, so without it your `generateText` / `streamText` calls
produce no spans. It isn't needed on `ai@6`.

```bash
npm install @ai-sdk/otel
```

## Initialize

```typescript
import { Brizz } from '@brizz/sdk';

Brizz.initialize({
  apiKey: 'your-brizzai-api-key',
  appName: 'my-app',
});
```

## Usage

You **must** set `experimental_telemetry: { isEnabled: true }` on every Vercel AI SDK call so Brizz can capture spans. Then wrap your function with `withSessionId` to group calls under a single session.

```typescript
import { withSessionId } from '@brizz/sdk';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

async function chat(userMessage: string) {
  return await generateText({
    model: openai('gpt-4'),
    prompt: userMessage,
    experimental_telemetry: { isEnabled: true }, // Required
  });
}

const sessionedChat = withSessionId('conversation-123', chat);
await sessionedChat('Hello!');
```

## Human-in-the-loop tool approvals

Vercel AI SDK 6 supports tools that pause for human approval before executing. Add `needsApproval: true` to a `tool()` definition, then resume the run with a `tool-approval-response`. Brizz captures the approval request and the user's verdict, and renders them in the conversation view as an interrupt item next to the user response — once as **approved**, again as **denied** if the user rejected.

```typescript
import { generateText, stepCountIs, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
import { startSession } from '@brizz/sdk';

const askUserQuestions = tool({
  description: 'Confirm a change with the user before applying it.',
  inputSchema: z.object({ question: z.string() }),
  needsApproval: true,
  // Runs only after the user approves; the "work" is the question itself.
  execute: async () => ({}),
});

await startSession('approval-demo', async () => {
  const turn1 = await generateText({
    model: openai('gpt-4o-mini'),
    tools: { ask_user_questions: askUserQuestions },
    prompt: 'Apply the mapping change. Use ask_user_questions to confirm first.',
    stopWhen: stepCountIs(1),
    experimental_telemetry: { isEnabled: true },
  });

  // Find the approval request the model produced.
  const approvalReq = turn1.content.find((p: any) => p.type === 'tool-approval-request');

  // In a real app, the user's verdict comes from your UI. Feed it back in:
  const turn2 = await generateText({
    model: openai('gpt-4o-mini'),
    tools: { ask_user_questions: askUserQuestions },
    messages: [
      { role: 'user', content: 'Apply the mapping change.' },
      ...turn1.response.messages,
      {
        role: 'tool',
        content: [{
          type: 'tool-approval-response',
          approvalId: (approvalReq as any).approvalId,
          approved: true,
        }],
      },
    ],
    stopWhen: stepCountIs(2),
    experimental_telemetry: { isEnabled: true },
  });
});
```

A full runnable version lives at `apps/sdk/typescript/examples/vercel-ai-hitl.ts`.

## Complete example

A single file you can copy, set `BRIZZ_API_KEY` + `OPENAI_API_KEY`, and run with `node chat.mjs`.

```typescript
// chat.ts
import { Brizz, withSessionId, withProperties, emitEvent } from '@brizz/sdk';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

Brizz.initialize({
  apiKey: process.env.BRIZZ_API_KEY!,
  appName: 'my-app',
  environment: process.env.NODE_ENV ?? 'development',
});

// Vercel AI SDK does NOT emit spans by default — you MUST pass
// experimental_telemetry: { isEnabled: true } on every call.
async function chat(userMessage: string) {
  const result = await generateText({
    model: openai('gpt-4o-mini'),
    prompt: userMessage,
    experimental_telemetry: {
      isEnabled: true,
      // Optional — lands on the span as attributes (filters, beforeSendSpan).
      metadata: { feature: 'support-bot' },
    },
  });
  // User feedback — map "feedback.positive" / "feedback.negative" to system
  // events in Org Settings -> Event so it powers filters and the Overview chart.
  emitEvent(
    'feedback.positive',
    { category: 'helpfulness' },
    { comment: 'Exactly what I needed!', context: 'support-agent' },
  );
  return result.text;
}

const sessioned = withProperties(
  { userId: 'user-42' },
  withSessionId('conversation-123', chat),
);

console.log(await sessioned('Hello!'));

// Streaming works the same way — flip experimental_telemetry on streamText too:
// const stream = streamText({ model: openai('gpt-4o-mini'), prompt: '...',
//   experimental_telemetry: { isEnabled: true } });
```

**Gotchas**

- Forgetting `experimental_telemetry: { isEnabled: true }` is the #1 reason calls don't show up. Add it to `generateText`, `streamText`, `generateObject`, `streamObject`, and `embed`.
- `experimental_telemetry.metadata` lands on the span as attributes — usable for dashboard filters and `beforeSendSpan`.

## See also

- [Node.js / TypeScript SDK](/docs/sdks/typescript.md) — full SDK reference.
- [Sessions](/docs/instrument/sessions.md) — session capture patterns.
- [Custom events](/docs/instrument/custom-events.md) — emit business events from `generateText` / `streamText` flows.
- [Troubleshooting](/docs/help/troubleshooting.md) — common issue: forgetting `experimental_telemetry: { isEnabled: true }`.
