Skill: anderskev/vercel-ai-sdk (version 1.0.1)

--- name: vercel-ai-sdk description: Vercel AI SDK for building chat interfaces with streaming. Use when implementing useChat hook, handling tool calls, streaming responses, or building chat UI. Triggers on useChat, @ai-sdk/react, UIMessage, ChatStatus, streamText, toUIMessageStreamResponse, addToolOutput, onToolCall, sendMessage. --- # Vercel AI SDK The Vercel AI SDK provides React hooks and server utilities for building streaming chat interfaces with support for tool calls, file attachments, and multi-step reasoning. ## Quick Reference ### Basic useChat Setup ```typescript import { useChat } from '@ai-sdk/react'; const { messages, status, sendMessage, stop, regenerate } = useChat({ id: 'chat-id', messages: initialMessages, onFinish: ({ message, messages, isAbort, isError }) => { console.log('Chat finished'); }, onError: (error) => { console.error('Chat error:', error); } }); // Send a message sendMessage({ text: 'Hello', metadata: { createdAt: Date.now() } }); // Send with files sendMessage({ text: 'Analyze this', files: fileList // FileList or FileUIPart[] }); ``` ### ChatStatus States The `status` field indicates the current state of the chat: - **`ready`**: Chat is idle and ready to accept new messages - **`submitted`**: Message sent to API, awaiting response stream start - **`streaming`**: Response actively streaming from the API - **`error`**: An error occurred during the request ### Message Structure Messages use the `UIMessage` type with a parts-based structure: ```typescript interface UIMessage { id: string; role: 'system' | 'user' | 'assistant'; metadata?: unknown; parts: Array<UIMessagePart>; // text, file, tool-*, reasoning, etc. } ``` Part types include: - `text`: Text content with optional streaming state - `file`: File attachments (images, documents) - `tool-{toolName}`: Tool invocations with state machine - `reasoning`: AI reasoning traces - `data-{typeName}`: Custom data parts ### Server-Side Streaming ```typescript import { streamText } from 'ai'; import { convertToModelMessages } from 'ai'; const result = streamText({ model: openai('gpt-4'), messages: convertToModelMessages(uiMessages), tools: { getWeather: tool({ description: 'Get weather', inputSchema: z.object({ city: z.string() }), execute: async ({ city }) => { return { temperature: 72, weather: 'sunny' }; } }) } }); return result.toUIMessageStreamResponse({ originalMessages: uiMessages, onFinish: ({ messages }) => { // Save to database } }); ``` ### Tool Handling Patterns **Client-Side Tool Execution:** ```typescript const { addToolOutput } = useChat({ onToolCall: async ({ toolCall }) => { if (toolCall.toolName === 'getLocation') { addToolOutput({ tool: 'getLocation', toolCallId: toolCall.toolCallId, output: 'San Francisco' }); } } }); ``` **Rendering Tool States:** ```typescript {message.parts.map(part => { if (part.type === 'tool-getWeather') { switch (part.state) { case 'input-streaming': return <pre>{JSON.stringify(part.input, null, 2)}</pre>; case 'input-available': return <div>Getting weather for {part.input.city}...</div>; case 'output-available': return <div>Weather: {part.output.weather}</div>; case 'output-error': return <div>Error: {part.errorText}</div>; } } })} ``` ## Reference Files Detailed documentation on specific aspects: - **[use-chat.md](references/use-chat.md)**: Complete useChat API reference - **[messages.md](references/messages.md)**: UIMessage structure and part types - **[streaming.md](references/streaming.md)**: Server-side streaming implementation - **[tools.md](references/tools.md)**: Tool definition and execution patterns ## Common Patterns ### Error Handling ```typescript const { error, clearError } = useChat({ onError: (error) => { toast.error(error.message); } }); // Clear error and reset to ready state if (error) { clearError(); } ``` ### Message Regeneration ```typescript const { regenerate } = useChat(); // Regenerate last assistant message await regenerate(); // Regenerate specific message await regenerate({ messageId: 'msg-123' }); ``` ### Custom Transport ```typescript import { DefaultChatTransport } from 'ai'; const { messages } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', prepareSendMessagesRequest: ({ id, messages, trigger, messageId }) => ({ body: { chatId: id, lastMessage: messages[messages.length - 1], trigger, messageId } }) }) }); ``` ### Performance Optimization ```typescript // Throttle UI updates to reduce re-renders const chat = useChat({ experimental_throttle: 100 // Update max once per 100ms }); ``` ### Automatic Message Sending ```typescript import { lastAssistantMessageIsCompleteWithToolCalls } from 'ai'; const chat = useChat({ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls // Automatically resend when all tool calls have outputs }); ``` ## Type Safety The SDK provides full type inference for tools and messages: ```typescript import { InferUITools, UIMessage } from 'ai'; const tools = { getWeather: tool({ inputSchema: z.object({ city: z.string() }), execute: async ({ city }) => ({ weather: 'sunny' }) }) }; type MyMessage = UIMessage< { createdAt: number }, // Metadata type UIDataTypes, InferUITools<typeof tools> // Tool types >; const { messages } = useChat<MyMessage>(); ``` ## Key Concepts ### Parts-Based Architecture Messages use a parts array instead of a single content field. This allows: - Streaming text while maintaining other parts - Tool calls with independent state machines - File attachments and custom data mixed with text ### Tool State Machine Tool parts progress through states: 1. `input-streaming`: Tool input streaming (optional) 2. `input-available`: Tool input complete 3. `approval-requested`: Waiting for user approval (optional) 4. `approval-responded`: User approved/denied (optional) 5. `output-available`: Tool execution complete 6. `output-error`: Tool execution failed 7. `output-denied`: User denied approval ### Streaming Protocol The SDK uses Server-Sent Events (SSE) with UIMessageChunk types: - `text-start`, `text-delta`, `text-end` - `tool-input-available`, `tool-output-available` - `reasoning-start`, `reasoning-delta`, `reasoning-end` - `start`, `finish`, `abort` ### Client vs Server Tools **Server-side tools** have an `execute` function and run on the API route. **Client-side tools** omit `execute` and are handled via `onToolCall` and `addToolOutput`. ## Gates Use this **sequence**; treat a step as incomplete until the pass condition is true in code or UI (not “should work”). 1. **Streaming route** — *Pass if:* the chat handler chains `convertToModelMessages` → `streamText` (or the SDK pattern your app standardizes) → `toUIMessageStreamResponse` (or equivalent stream response). *Fail if:* responses are plain JSON strings without the UI message stream contract. 2. **Client ↔ route** — *Pass if:* `useChat` `id` / `DefaultChatTransport` `api` (and `prepareSendMessagesRequest` body) matches the route path and the body the server reads. *Fail if:* client posts to a different path or shape than the handler expects. 3. **Tools closed loop** — *Pass if:* every tool in `tools` has server `execute` **or** `onToolCall` + `addToolOutput` with the same `toolCallId`, and the UI handles the `tool-*` part states you surface. *Fail if:* a tool name exists in `tools` but has no handler or missing states in the renderer. 4. **Persistence (if any)** — *Pass if:* before saving, the server runs `validateUIMessages` (or stricter validation). *Fail if:* unvalidated client payloads are written to storage. ## Best Practices 1. Always handle the `error` state and provide user feedback 2. Use `experimental_throttle` for high-frequency updates 3. Implement proper loading states based on `status` 4. Type your messages with custom metadata and tools 5. Use `sendAutomaticallyWhen` for multi-turn tool workflows 6. Handle all tool states in the UI for better UX 7. Use `stop()` to allow users to cancel long-running requests 8. Validate messages with `validateUIMessages` on the server

path: references/messages.md
content: # UIMessage Structure Reference

Complete reference for the UIMessage type system and message parts.

## Table of Contents

- [UIMessage Interface](#uimessage-interface)
- [Message Parts](#message-parts)
- [Text Parts](#text-parts)
- [Tool Parts](#tool-parts)
- [File Parts](#file-parts)
- [Reasoning Parts](#reasoning-parts)
- [Data Parts](#data-parts)
- [Type Guards](#type-guards)
- [Type Inference](#type-inference)

## UIMessage Interface

```typescript
interface UIMessage<
  METADATA = unknown,
  DATA_PARTS extends UIDataTypes = UIDataTypes,
  TOOLS extends UITools = UITools,
> {
  // Unique identifier
  id: string;

  // Message role
  role: 'system' | 'user' | 'assistant';

  // Optional custom metadata
  metadata?: METADATA;

  // Array of message parts
  parts: Array<UIMessagePart<DATA_PARTS, TOOLS>>;
}
```

### Role Guidelines

- **`system`**: System prompts (avoid in UI messages, set on server instead)
- **`user`**: User-generated messages (text, files)
- **`assistant`**: AI-generated messages (text, reasoning, tools, files)

### Parts-Based Architecture

Unlike traditional chat systems with single `content` fields, UIMessages use a `parts` array. This enables:

- Multiple content types in a single message
- Independent streaming states for each part
- Tool calls with their own state machines
- File attachments mixed with text
- Custom data parts for specialized UI

## Message Parts

```typescript
type UIMessagePart<DATA_TYPES, TOOLS> =
  | TextUIPart
  | ReasoningUIPart
  | ToolUIPart<TOOLS>
  | DynamicToolUIPart
  | SourceUrlUIPart
  | SourceDocumentUIPart
  | FileUIPart
  | DataUIPart<DATA_TYPES>
  | StepStartUIPart;
```

## Text Parts

### TextUIPart

```typescript
interface TextUIPart {
  type: 'text';
  text: string;
  state?: 'streaming' | 'done';
  providerMetadata?: ProviderMetadata;
}
```

### Usage

```typescript
// Complete text
const textPart: TextUIPart = {
  type: 'text',
  text: 'Hello, world!',
  state: 'done'
};

// Streaming text
const streamingPart: TextUIPart = {
  type: 'text',
  text: 'Hello, wor',
  state: 'streaming'
};
```

### Rendering

```typescript
function renderTextPart(part: TextUIPart) {
  return (
    <div className={part.state === 'streaming' ? 'opacity-70' : ''}>
      {part.text}
      {part.state === 'streaming' && <Cursor />}
    </div>
  );
}
```

## Tool Parts

### ToolUIPart

Type-safe tool parts with tool name in the type.

```typescript
type ToolUIPart<TOOLS extends UITools> = ValueOf<{
  [NAME in keyof TOOLS & string]: {
    type: `tool-${NAME}`;
  } & UIToolInvocation<TOOLS[NAME]>;
}>;
```

### UIToolInvocation States

Tool parts have a state machine with the following states:

```typescript
type UIToolInvocation<TOOL> =
  | { state: 'input-streaming'; input: Partial<ToolInput> | undefined; }
  | { state: 'input-available'; input: ToolInput; callProviderMetadata?: ProviderMetadata; }
  | { state: 'approval-requested'; input: ToolInput; approval: { id: string }; }
  | { state: 'approval-responded'; input: ToolInput; approval: { id: string; approved: boolean; reason?: string }; }
  | { state: 'output-available'; input: ToolInput; output: ToolOutput; preliminary?: boolean; }
  | { state: 'output-error'; input: ToolInput | undefined; errorText: string; }
  | { state: 'output-denied'; input: ToolInput; approval: { id: string; approved: false; reason?: string }; }
```

**Common fields:**
- `toolCallId: string` - Unique identifier for this tool call
- `title?: string` - Optional display title
- `providerExecuted?: boolean` - True if provider executed the tool

### State Progression

```
input-streaming → input-available → [approval flow] → output-available
                                  ↓
                            approval-requested → approval-responded → output-available/output-denied
                                                                    ↓
                                                              (at any point) → output-error
```

### Example: Rendering Tool Parts

```typescript
function renderToolPart(part: ToolUIPart<MyTools>) {
  // Extract common fields
  const { toolCallId, title } = part;

  // Type narrows based on tool type
  if (part.type === 'tool-getWeather') {
    switch (part.state) {
      case 'input-streaming':
        return (
          <div>
            Preparing weather request...
            <pre>{JSON.stringify(part.input, null, 2)}</pre>
          </div>
        );

      case 'input-available':
        return <div>Fetching weather for {part.input.city}...</div>;

      case 'output-available':
        return (
          <div>
            Weather in {part.input.city}: {part.output.weather}
            {part.preliminary && <Badge>Preliminary</Badge>}
          </div>
        );

      case 'output-error':
        return <div className="error">{part.errorText}</div>;
    }
  }

  if (part.type === 'tool-askConfirmation') {
    switch (part.state) {
      case 'approval-requested':
        return (
          <div>
            {part.input.message}
            <button onClick={() => approve(part.approval.id)}>Yes</button>
            <button onClick={() => deny(part.approval.id)}>No</button>
          </div>
        );

      case 'approval-responded':
        return (
          <div>
            User {part.approval.approved ? 'approved' : 'denied'}
            {part.approval.reason && `: ${part.approval.reason}`}
          </div>
        );
    }
  }
}
```

### DynamicToolUIPart

For tools not known at compile time.

```typescript
interface DynamicToolUIPart {
  type: 'dynamic-tool';
  toolName: string; // Name as string, not in type
  toolCallId: string;
  title?: string;
  providerExecuted?: boolean;
  // Same state union as UIToolInvocation but with unknown types
  state: 'input-streaming' | 'input-available' | ...;
  input: unknown;
  output?: unknown;
  errorText?: string;
}
```

### Tool Type Utilities

```typescript
// Check if part is a tool part
if (isToolUIPart(part)) {
  const toolName = getToolName(part); // Type-safe tool name
}

// Check if tool or dynamic tool
if (isToolOrDynamicToolUIPart(part)) {
  const name = getToolOrDynamicToolName(part); // string
}

// Check if dynamic tool
if (isDynamicToolUIPart(part)) {
  console.log(part.toolName); // Access toolName field
}
```

## File Parts

### FileUIPart

```typescript
interface FileUIPart {
  type: 'file';
  mediaType: string; // IANA media type
  filename?: string;
  url: string; // Hosted URL or Data URL
  providerMetadata?: ProviderMetadata;
}
```

### Usage

```typescript
// Image file
const imagePart: FileUIPart = {
  type: 'file',
  mediaType: 'image/png',
  filename: 'screenshot.png',
  url: 'data:image/png;base64,iVBORw0KG...'
};

// PDF document
const pdfPart: FileUIPart = {
  type: 'file',
  mediaType: 'application/pdf',
  filename: 'report.pdf',
  url: 'https://example.com/files/report.pdf'
};
```

### Rendering

```typescript
function renderFilePart(part: FileUIPart) {
  if (part.mediaType.startsWith('image/')) {
    return <img src={part.url} alt={part.filename} />;
  }

  if (part.mediaType.startsWith('video/')) {
    return <video src={part.url} controls />;
  }

  return (
    <a href={part.url} download={part.filename}>
      {part.filename || 'Download file'}
    </a>
  );
}
```

## Reasoning Parts

### ReasoningUIPart

For AI reasoning traces (e.g., from OpenAI o1 models).

```typescript
interface ReasoningUIPart {
  type: 'reasoning';
  text: string;
  state?: 'streaming' | 'done';
  providerMetadata?: ProviderMetadata;
}
```

### Usage

```typescript
function renderReasoningPart(part: ReasoningUIPart) {
  return (
    <details>
      <summary>Reasoning</summary>
      <div className="reasoning">
        {part.text}
        {part.state === 'streaming' && <Spinner />}
      </div>
    </details>
  );
}
```

## Data Parts

### DataUIPart

Custom data parts for specialized UI components.

```typescript
type DataUIPart<DATA_TYPES extends UIDataTypes> = ValueOf<{
  [NAME in keyof DATA_TYPES & string]: {
    type: `data-${NAME}`;
    id?: string;
    data: DATA_TYPES[NAME];
  };
}>;
```

### Defining Custom Data Types

```typescript
// Define data types
type MyDataTypes = {
  progress: { percent: number; status: string };
  chart: { data: number[]; labels: string[] };
};

// Use in message type
type MyMessage = UIMessage<unknown, MyDataTypes>;

// Create data parts
const progressPart: DataUIPart<MyDataTypes> = {
  type: 'data-progress',
  data: { percent: 75, status: 'Processing...' }
};

const chartPart: DataUIPart<MyDataTypes> = {
  type: 'data-chart',
  id: 'chart-1',
  data: {
    data: [10, 20, 30],
    labels: ['A', 'B', 'C']
  }
};
```

### Rendering Custom Data

```typescript
function renderDataPart(part: UIMessagePart<MyDataTypes, MyTools>) {
  if (isDataUIPart(part)) {
    if (part.type === 'data-progress') {
      return (
        <ProgressBar
          percent={part.data.percent}
          label={part.data.status}
        />
      );
    }

    if (part.type === 'data-chart') {
      return <Chart data={part.data.data} labels={part.data.labels} />;
    }
  }
}
```

## Source Parts

### SourceUrlUIPart

```typescript
interface SourceUrlUIPart {
  type: 'source-url';
  sourceId: string;
  url: string;
  title?: string;
  providerMetadata?: ProviderMetadata;
}
```

### SourceDocumentUIPart

```typescript
interface SourceDocumentUIPart {
  type: 'source-document';
  sourceId: string;
  mediaType: string;
  title: string;
  filename?: string;
  providerMetadata?: ProviderMetadata;
}
```

### Usage

```typescript
// URL source
const urlSource: SourceUrlUIPart = {
  type: 'source-url',
  sourceId: 'src-1',
  url: 'https://example.com/article',
  title: 'Example Article'
};

// Document source
const docSource: SourceDocumentUIPart = {
  type: 'source-document',
  sourceId: 'src-2',
  mediaType: 'application/pdf',
  title: 'Research Paper',
  filename: 'paper.pdf'
};
```

## Step Parts

### StepStartUIPart

Marks the beginning of a new reasoning/execution step.

```typescript
interface StepStartUIPart {
  type: 'step-start';
}
```

### Rendering

```typescript
function renderMessage(message: UIMessage) {
  return (
    <div>
      {message.parts.map((part, index) => {
        if (part.type === 'step-start' && index > 0) {
          return <hr key={index} className="step-divider" />;
        }
        return renderPart(part, index);
      })}
    </div>
  );
}
```

## Type Guards

```typescript
// Text
if (isTextUIPart(part)) {
  console.log(part.text);
}

// File
if (isFileUIPart(part)) {
  console.log(part.url, part.mediaType);
}

// Reasoning
if (isReasoningUIPart(part)) {
  console.log(part.text);
}

// Tool (static)
if (isToolUIPart(part)) {
  const toolName = getToolName(part);
}

// Dynamic tool
if (isDynamicToolUIPart(part)) {
  console.log(part.toolName);
}

// Tool or dynamic tool
if (isToolOrDynamicToolUIPart(part)) {
  const name = getToolOrDynamicToolName(part);
}

// Data
if (isDataUIPart(part)) {
  // Check specific data type
  if (part.type === 'data-progress') {
    console.log(part.data.percent);
  }
}
```

## Type Inference

### Infer from UIMessage

```typescript
type MyMessage = UIMessage<
  { createdAt: number },
  { progress: { percent: number } },
  { getTool: { input: string; output: number } }
>;

// Infer metadata type
type Metadata = InferUIMessageMetadata<MyMessage>;
// { createdAt: number }

// Infer data types
type DataTypes = InferUIMessageData<MyMessage>;
// { progress: { percent: number } }

// Infer tool types
type Tools = InferUIMessageTools<MyMessage>;
// { getTool: { input: string; output: number } }

// Infer tool outputs
type ToolOutputs = InferUIMessageToolOutputs<MyMessage>;
// number

// Infer tool calls
type ToolCall = InferUIMessageToolCall<MyMessage>;
// ToolCall<'getTool', string> | ...

// Infer part type
type Part = InferUIMessagePart<MyMessage>;
// TextUIPart | ToolUIPart<...> | DataUIPart<...> | ...
```

### Infer Tool Types

```typescript
import { InferUITool, InferUITools } from 'ai';

const weatherTool = tool({
  inputSchema: z.object({ city: z.string() }),
  execute: async ({ city }) => ({ temp: 72 })
});

// Infer single tool
type WeatherTool = InferUITool<typeof weatherTool>;
// { input: { city: string }; output: { temp: number } }

// Infer tool set
const tools = { weather: weatherTool };
type MyTools = InferUITools<typeof tools>;
// { weather: { input: { city: string }; output: { temp: number } } }
```

## Complete Example

```typescript
import { UIMessage, InferUITools, isTextUIPart, isToolUIPart } from 'ai';

// Define tools
const tools = {
  getWeather: tool({
    inputSchema: z.object({ city: z.string() }),
    execute: async ({ city }) => ({ weather: 'sunny', temp: 72 })
  })
};

// Define message type
type MyMessage = UIMessage<
  { createdAt: number },
  { progress: { percent: number } },
  InferUITools<typeof tools>
>;

// Render function
function Message({ message }: { message: MyMessage }) {
  return (
    <div className={`message message-${message.role}`}>
      <div className="timestamp">
        {new Date(message.metadata.createdAt).toLocaleString()}
      </div>

      <div className="parts">
        {message.parts.map((part, index) => {
          if (isTextUIPart(part)) {
            return <div key={index}>{part.text}</div>;
          }

          if (part.type === 'tool-getWeather') {
            if (part.state === 'output-available') {
              return (
                <div key={index}>
                  Weather: {part.output.weather}, {part.output.temp}°F
                </div>
              );
            }
          }

          if (part.type === 'data-progress') {
            return (
              <ProgressBar
                key={index}
                percent={part.data.percent}
              />
            );
          }

          return null;
        })}
      </div>
    </div>
  );
}
```


path: references/streaming.md
content: # Streaming Reference

Server-side streaming implementation with the Vercel AI SDK.

## Table of Contents

- [Basic Streaming Setup](#basic-streaming-setup)
- [streamText Function](#streamtext-function)
- [toUIMessageStreamResponse](#touimessagestreamresponse)
- [UIMessageChunk Types](#uimessagechunk-types)
- [SSE Protocol](#sse-protocol)
- [Tool Execution Flow](#tool-execution-flow)
- [Error Handling](#error-handling)
- [Advanced Patterns](#advanced-patterns)

## Basic Streaming Setup

### Server Route

```typescript
import { streamText, convertToModelMessages } from 'ai';
import { openai } from '@ai-sdk/openai';

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

  const result = streamText({
    model: openai('gpt-4'),
    messages: convertToModelMessages(messages)
  });

  return result.toUIMessageStreamResponse();
}
```

### Client Setup

```typescript
import { useChat } from '@ai-sdk/react';

function Chat() {
  const { messages, sendMessage } = useChat({
    api: '/api/chat'
  });

  return <ChatUI messages={messages} onSend={sendMessage} />;
}
```

## streamText Function

### Basic Options

```typescript
const result = streamText({
  // Model to use
  model: openai('gpt-4'),

  // Messages (converted from UIMessage)
  messages: convertToModelMessages(uiMessages),

  // System prompt (optional)
  system: 'You are a helpful assistant.',

  // Temperature, max tokens, etc.
  temperature: 0.7,
  maxTokens: 2000,

  // Abort signal
  abortSignal: abortController.signal
});
```

### With Tools

```typescript
import { tool } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: openai('gpt-4'),
  messages: convertToModelMessages(uiMessages),

  tools: {
    getWeather: tool({
      description: 'Get weather for a city',
      inputSchema: z.object({
        city: z.string()
      }),
      execute: async ({ city }) => {
        const data = await fetchWeather(city);
        return { temperature: data.temp, weather: data.conditions };
      }
    })
  }
});
```

### Multi-Step Tool Execution

```typescript
import { stepCountIs } from 'ai';

const result = streamText({
  model: openai('gpt-4'),
  messages: convertToModelMessages(uiMessages),
  tools: {
    getWeather: weatherTool,
    searchWeb: searchTool
  },

  // Allow up to 5 steps (model can call tools multiple times)
  stopWhen: stepCountIs(5)
});
```

### Streaming Tool Outputs

```typescript
const getWeatherTool = tool({
  description: 'Get weather information',
  inputSchema: z.object({ city: z.string() }),

  // Generator function for streaming outputs
  async *execute({ city }) {
    // Yield preliminary results
    yield { state: 'loading' as const };

    const data = await fetchWeather(city);

    // Yield intermediate results
    yield {
      state: 'partial' as const,
      temperature: data.temp
    };

    // Yield final result
    yield {
      state: 'complete' as const,
      temperature: data.temp,
      weather: data.conditions,
      forecast: data.forecast
    };
  }
});
```

### Callbacks

```typescript
const result = streamText({
  model: openai('gpt-4'),
  messages: convertToModelMessages(uiMessages),

  // Called on each chunk
  onChunk({ chunk }) {
    console.log('Chunk:', chunk);
  },

  // Called when a step finishes (with multi-step)
  onStepFinish({ request, response, toolCalls, toolResults }) {
    console.log('Step finished');
    console.log('Tool calls:', toolCalls);
    console.log('Tool results:', toolResults);
  },

  // Called when generation finishes
  onFinish({ text, finishReason, usage }) {
    console.log('Generated text:', text);
    console.log('Finish reason:', finishReason);
    console.log('Token usage:', usage);
  }
});
```

## toUIMessageStreamResponse

Convert streamText result to a UIMessage stream response.

### Basic Usage

```typescript
const result = streamText({
  model: openai('gpt-4'),
  messages: convertToModelMessages(uiMessages)
});

return result.toUIMessageStreamResponse();
```

### With Options

```typescript
return result.toUIMessageStreamResponse({
  // Include original messages in the stream
  originalMessages: uiMessages,

  // Custom message ID generator
  generateMessageId: () => crypto.randomUUID(),

  // Add/update message metadata
  messageMetadata: ({ part, message }) => {
    if (part.type === 'start') {
      return { createdAt: Date.now() };
    }
    if (part.type === 'finish') {
      return { finishedAt: Date.now() };
    }
  },

  // Called when streaming finishes
  onFinish: ({ messages, finishReason }) => {
    // Save to database
    saveMessages(messages);
  }
});
```

### Custom Headers

```typescript
const response = result.toUIMessageStreamResponse({
  originalMessages: uiMessages
});

// Add custom headers
response.headers.set('X-Custom-Header', 'value');

return response;
```

## UIMessageChunk Types

Chunks sent over the stream as Server-Sent Events.

### Text Chunks

```typescript
// Text streaming starts
{ type: 'text-start', id: 'text-1' }

// Text delta (incremental update)
{ type: 'text-delta', id: 'text-1', delta: 'Hello' }
{ type: 'text-delta', id: 'text-1', delta: ' world' }

// Text streaming ends
{ type: 'text-end', id: 'text-1' }
```

### Reasoning Chunks

```typescript
// Reasoning starts
{ type: 'reasoning-start', id: 'reasoning-1' }

// Reasoning delta
{ type: 'reasoning-delta', id: 'reasoning-1', delta: 'First, ' }
{ type: 'reasoning-delta', id: 'reasoning-1', delta: 'I will...' }

// Reasoning ends
{ type: 'reasoning-end', id: 'reasoning-1' }
```

### Tool Chunks

```typescript
// Tool input streaming starts
{
  type: 'tool-input-start',
  toolCallId: 'call-123',
  toolName: 'getWeather',
  dynamic: false
}

// Tool input delta (for large inputs)
{
  type: 'tool-input-delta',
  toolCallId: 'call-123',
  inputTextDelta: '{"city": '
}

// Tool input complete
{
  type: 'tool-input-available',
  toolCallId: 'call-123',
  toolName: 'getWeather',
  input: { city: 'San Francisco' }
}

// Tool output available
{
  type: 'tool-output-available',
  toolCallId: 'call-123',
  output: { temperature: 72, weather: 'sunny' },
  preliminary: false
}

// Tool execution error
{
  type: 'tool-output-error',
  toolCallId: 'call-123',
  errorText: 'API unavailable'
}
```

### Tool Approval Chunks

```typescript
// Tool needs approval
{
  type: 'tool-approval-request',
  approvalId: 'approval-1',
  toolCallId: 'call-123'
}

// User responded (handled client-side, not streamed)
```

### Control Chunks

```typescript
// Stream starts
{
  type: 'start',
  messageId: 'msg-123',
  messageMetadata: { createdAt: 1234567890 }
}

// Stream finishes
{
  type: 'finish',
  finishReason: 'stop', // or 'length', 'tool-calls', 'content-filter'
  messageMetadata: { finishedAt: 1234567890 }
}

// Stream aborted
{ type: 'abort' }

// Error occurred
{ type: 'error', errorText: 'Something went wrong' }

// Metadata update
{
  type: 'message-metadata',
  messageMetadata: { updated: true }
}
```

### Step Chunks

```typescript
// New step starts (for multi-step reasoning)
{ type: 'start-step' }

// Step finishes
{ type: 'finish-step' }
```

### Data Chunks

```typescript
// Custom data part
{
  type: 'data-progress',
  id: 'progress-1',
  data: { percent: 50, status: 'Processing...' },
  transient: false // If true, not added to final message
}
```

### File Chunks

```typescript
{
  type: 'file',
  url: 'https://example.com/image.png',
  mediaType: 'image/png'
}
```

## SSE Protocol

### Format

```
event: message
data: {"type":"text-start","id":"text-1"}

event: message
data: {"type":"text-delta","id":"text-1","delta":"Hello"}

event: message
data: {"type":"text-delta","id":"text-1","delta":" world"}

event: message
data: {"type":"text-end","id":"text-1"}

event: message
data: {"type":"finish","finishReason":"stop"}
```

### Client-Side Parsing

The SDK handles parsing automatically, but for custom implementations:

```typescript
const eventSource = new EventSource('/api/chat');

eventSource.addEventListener('message', (event) => {
  const chunk: UIMessageChunk = JSON.parse(event.data);

  switch (chunk.type) {
    case 'text-delta':
      appendText(chunk.id, chunk.delta);
      break;

    case 'tool-output-available':
      updateToolOutput(chunk.toolCallId, chunk.output);
      break;

    case 'finish':
      console.log('Finished:', chunk.finishReason);
      eventSource.close();
      break;
  }
});
```

## Tool Execution Flow

### Server-Side Tools

```typescript
// 1. Client sends message
sendMessage({ text: 'What is the weather in SF?' });

// 2. Server streams tool invocation
{ type: 'tool-input-available', toolCallId: 'call-1', input: { city: 'San Francisco' } }

// 3. Server executes tool and streams output
{ type: 'tool-output-available', toolCallId: 'call-1', output: { temp: 72 } }

// 4. Server continues with model response
{ type: 'text-delta', delta: 'The weather is sunny...' }
```

### Client-Side Tools

```typescript
// 1. Client sends message
sendMessage({ text: 'Get my location' });

// 2. Server streams tool call (no execute)
{ type: 'tool-input-available', toolCallId: 'call-1', toolName: 'getLocation', input: {} }

// 3. Client handles tool call
onToolCall: async ({ toolCall }) => {
  const location = await getCurrentLocation();
  addToolOutput({
    toolCallId: toolCall.toolCallId,
    tool: 'getLocation',
    output: location
  });
}

// 4. Client sends tool output back to server
// 5. Server streams final response
```

### Preliminary Tool Outputs

For streaming tool results:

```typescript
// Tool yields intermediate result
{
  type: 'tool-output-available',
  toolCallId: 'call-1',
  output: { state: 'loading' },
  preliminary: true
}

// Tool yields final result
{
  type: 'tool-output-available',
  toolCallId: 'call-1',
  output: { state: 'complete', data: {...} },
  preliminary: false // or omitted
}
```

## Error Handling

### Server-Side Errors

```typescript
try {
  const result = streamText({
    model: openai('gpt-4'),
    messages: convertToModelMessages(uiMessages)
  });

  return result.toUIMessageStreamResponse();
} catch (error) {
  // Stream error chunk
  return new Response(
    new ReadableStream({
      start(controller) {
        const chunk: UIMessageChunk = {
          type: 'error',
          errorText: error.message
        };
        controller.enqueue(
          new TextEncoder().encode(
            `event: message\ndata: ${JSON.stringify(chunk)}\n\n`
          )
        );
        controller.close();
      }
    }),
    {
      headers: {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache'
      }
    }
  );
}
```

### Tool Execution Errors

```typescript
const weatherTool = tool({
  inputSchema: z.object({ city: z.string() }),
  async execute({ city }) {
    try {
      return await fetchWeather(city);
    } catch (error) {
      // Error is automatically converted to tool-output-error chunk
      throw new Error(`Failed to fetch weather: ${error.message}`);
    }
  }
});
```

### Client-Side Error Handling

```typescript
const { error } = useChat({
  onError: (error) => {
    console.error('Stream error:', error);
    toast.error(error.message);
  }
});
```

## Advanced Patterns

### Custom Data Streaming

```typescript
const result = streamText({
  model: openai('gpt-4'),
  messages: convertToModelMessages(uiMessages),

  onChunk({ chunk }) {
    // Stream custom progress updates
    if (chunk.type === 'text-delta') {
      // Calculate progress based on tokens
      const progress = calculateProgress(chunk);
      // Custom data will be sent via data-* chunk
    }
  }
});
```

### Resumable Streams

```typescript
import { createResumableStreamContext } from 'resumable-stream';

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

  const result = streamText({
    model: openai('gpt-4'),
    messages: convertToModelMessages(uiMessages)
  });

  return result.toUIMessageStreamResponse({
    async consumeSseStream({ stream }) {
      const streamId = generateId();
      const streamContext = createResumableStreamContext();

      // Store stream for resumption
      await streamContext.createNewResumableStream(streamId, () => stream);

      // Save stream ID to database
      await saveActiveStreamId(chatId, streamId);
    }
  });
}
```

### Backpressure Handling

The SDK automatically handles backpressure, but for custom implementations:

```typescript
const result = streamText({
  model: openai('gpt-4'),
  messages: convertToModelMessages(uiMessages),

  // Throttle chunk processing
  onChunk: throttle(async ({ chunk }) => {
    await processChunk(chunk);
  }, 100)
});
```

### Conditional Streaming

```typescript
const result = streamText({
  model: openai('gpt-4'),
  messages: convertToModelMessages(uiMessages),

  onChunk({ chunk }) {
    // Only stream certain content
    if (shouldFilterChunk(chunk)) {
      return; // Skip this chunk
    }
  }
});
```

### Custom Transform

```typescript
import { processUIMessageStream } from 'ai';

const stream = await transport.sendMessages({...});

const transformedStream = processUIMessageStream({
  stream,
  onToolCall,
  onData,
  runUpdateMessageJob: async ({ state, write }) => {
    // Custom message update logic
    customTransform(state.message);
    write();
  }
});
```

## Performance Considerations

### Throttling

```typescript
// Client-side throttling
const { messages } = useChat({
  experimental_throttle: 100 // Max one update per 100ms
});

// Server-side throttling
const result = streamText({
  model: openai('gpt-4'),
  messages,
  onChunk: throttle(processChunk, 100)
});
```

### Batch Updates

```typescript
let pendingDeltas: string[] = [];

const result = streamText({
  model: openai('gpt-4'),
  messages,
  onChunk({ chunk }) {
    if (chunk.type === 'text-delta') {
      pendingDeltas.push(chunk.delta);

      // Flush every 10 deltas or 100ms
      if (pendingDeltas.length >= 10) {
        flushDeltas();
      }
    }
  }
});
```

### Memory Management

```typescript
// Clean up old messages to prevent memory leaks
const { messages, setMessages } = useChat();

useEffect(() => {
  if (messages.length > 100) {
    setMessages(messages.slice(-50)); // Keep last 50
  }
}, [messages]);
```

## Complete Example

```typescript
// Server route
export async function POST(req: Request) {
  const { messages, chatId } = await req.json();

  const result = streamText({
    model: openai('gpt-4'),
    messages: convertToModelMessages(messages),
    tools: {
      getWeather: tool({
        description: 'Get weather',
        inputSchema: z.object({ city: z.string() }),
        async *execute({ city }) {
          yield { state: 'loading' };
          const data = await fetchWeather(city);
          yield { state: 'complete', ...data };
        }
      })
    },
    stopWhen: stepCountIs(5),
    onStepFinish({ toolCalls }) {
      console.log('Tools called:', toolCalls);
    }
  });

  return result.toUIMessageStreamResponse({
    originalMessages: messages,
    messageMetadata: ({ part }) => {
      if (part.type === 'start') {
        return { createdAt: Date.now() };
      }
    },
    onFinish: async ({ messages }) => {
      await saveChat(chatId, messages);
    }
  });
}

// Client component
function Chat() {
  const { messages, sendMessage, status } = useChat({
    experimental_throttle: 100,
    onError: (error) => toast.error(error.message),
    onFinish: ({ finishReason }) => {
      console.log('Finished:', finishReason);
    }
  });

  return (
    <div>
      {messages.map(msg => (
        <Message key={msg.id} message={msg} />
      ))}
      <ChatInput
        disabled={status !== 'ready'}
        onSubmit={sendMessage}
      />
    </div>
  );
}
```


path: references/tools.md
content: # Tools Reference

Complete reference for tool definition and execution in the Vercel AI SDK.

## Table of Contents

- [Tool Definition](#tool-definition)
- [Server-Side Tools](#server-side-tools)
- [Client-Side Tools](#client-side-tools)
- [Tool State Machine](#tool-state-machine)
- [Tool Approval Flow](#tool-approval-flow)
- [Rendering Tool States](#rendering-tool-states)
- [Type Safety](#type-safety)
- [Advanced Patterns](#advanced-patterns)

## Tool Definition

### Basic Tool

```typescript
import { tool } from 'ai';
import { z } from 'zod';

const getWeatherTool = tool({
  // Description for the AI model
  description: 'Get current weather for a city',

  // Input schema using Zod
  inputSchema: z.object({
    city: z.string().describe('The city name'),
    units: z.enum(['celsius', 'fahrenheit']).optional()
  }),

  // Optional output schema
  outputSchema: z.object({
    temperature: z.number(),
    weather: z.string(),
    humidity: z.number()
  }),

  // Execution function (server-side only)
  execute: async ({ city, units = 'celsius' }) => {
    const data = await fetchWeather(city);
    return {
      temperature: convertTemp(data.temp, units),
      weather: data.conditions,
      humidity: data.humidity
    };
  }
});
```

### Tool Without Execute (Client-Side)

```typescript
const askConfirmationTool = tool({
  description: 'Ask the user for confirmation',
  inputSchema: z.object({
    message: z.string()
  }),
  outputSchema: z.string()
  // No execute function - handled on client
});
```

## Server-Side Tools

Tools with `execute` functions run on the server during streaming.

### Simple Execution

```typescript
const searchTool = tool({
  description: 'Search the web',
  inputSchema: z.object({
    query: z.string()
  }),
  async execute({ query }) {
    const results = await searchWeb(query);
    return {
      results: results.slice(0, 5),
      count: results.length
    };
  }
});
```

### Streaming Tool Outputs

Use generator functions to stream intermediate results:

```typescript
const analysisTool = tool({
  description: 'Analyze data',
  inputSchema: z.object({
    data: z.array(z.number())
  }),

  async *execute({ data }) {
    // Yield preliminary status
    yield { state: 'processing', progress: 0 };

    // Perform analysis in stages
    const mean = calculateMean(data);
    yield { state: 'processing', progress: 33, mean };

    const median = calculateMedian(data);
    yield { state: 'processing', progress: 66, mean, median };

    const stdDev = calculateStdDev(data);

    // Yield final result
    yield {
      state: 'complete',
      progress: 100,
      mean,
      median,
      stdDev
    };
  }
});
```

### Tool Callbacks

```typescript
const verboseTool = tool({
  description: 'Tool with callbacks',
  inputSchema: z.object({ query: z.string() }),

  // Called when input streaming starts
  onInputStart: () => {
    console.log('Tool input starting');
  },

  // Called on each input delta (for large inputs)
  onInputDelta: ({ inputTextDelta }) => {
    console.log('Input delta:', inputTextDelta);
  },

  // Called when input is complete
  onInputAvailable: ({ input }) => {
    console.log('Input available:', input);
  },

  async execute({ query }) {
    return await search(query);
  }
});
```

### Error Handling

```typescript
const fallibleTool = tool({
  description: 'Tool that might fail',
  inputSchema: z.object({ id: z.string() }),

  async execute({ id }) {
    try {
      const data = await fetchData(id);
      if (!data) {
        throw new Error('Data not found');
      }
      return data;
    } catch (error) {
      // Error is automatically sent as tool-output-error chunk
      throw new Error(`Failed to fetch data: ${error.message}`);
    }
  }
});
```

### Multi-Step Tools

Tools can trigger additional model calls:

```typescript
import { streamText, stepCountIs } from 'ai';

const result = streamText({
  model: openai('gpt-4'),
  messages: convertToModelMessages(uiMessages),

  tools: {
    search: searchTool,
    analyze: analyzeTool,
    summarize: summarizeTool
  },

  // Allow up to 5 steps (model can call tools multiple times)
  stopWhen: stepCountIs(5),

  onStepFinish({ toolCalls, toolResults }) {
    console.log('Step finished');
    console.log('Called:', toolCalls.map(t => t.toolName));
    console.log('Results:', toolResults);
  }
});
```

## Client-Side Tools

Tools without `execute` are handled on the client via `onToolCall` and `addToolOutput`.

### Automatic Execution

```typescript
const { addToolOutput } = useChat({
  onToolCall: async ({ toolCall }) => {
    if (toolCall.toolName === 'getLocation') {
      try {
        const location = await getCurrentLocation();
        addToolOutput({
          tool: 'getLocation',
          toolCallId: toolCall.toolCallId,
          output: location
        });
      } catch (error) {
        addToolOutput({
          state: 'output-error',
          tool: 'getLocation',
          toolCallId: toolCall.toolCallId,
          errorText: error.message
        });
      }
    }
  }
});
```

### User Interaction

For tools that require user input, handle them in the render phase:

```typescript
function Message({ message, addToolOutput }) {
  return (
    <div>
      {message.parts.map(part => {
        if (part.type === 'tool-askConfirmation') {
          if (part.state === 'input-available') {
            return (
              <div>
                <p>{part.input.message}</p>
                <button
                  onClick={() =>
                    addToolOutput({
                      tool: 'askConfirmation',
                      toolCallId: part.toolCallId,
                      output: 'Yes, confirmed'
                    })
                  }
                >
                  Confirm
                </button>
                <button
                  onClick={() =>
                    addToolOutput({
                      state: 'output-error',
                      tool: 'askConfirmation',
                      toolCallId: part.toolCallId,
                      errorText: 'User declined'
                    })
                  }
                >
                  Cancel
                </button>
              </div>
            );
          }

          if (part.state === 'output-available') {
            return <div>User confirmed: {part.output}</div>;
          }
        }
      })}
    </div>
  );
}
```

### Automatic Resending

```typescript
import { lastAssistantMessageIsCompleteWithToolCalls } from 'ai';

const { messages } = useChat({
  // Automatically resend when all tool outputs are available
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,

  onToolCall: async ({ toolCall }) => {
    // Execute client-side tools
    // Message will automatically be resent when all tools complete
  }
});
```

## Tool State Machine

Tool parts progress through states:

### State Flow

```
1. input-streaming (optional)
   ↓
2. input-available
   ↓
3a. [No approval needed] → output-available
   OR
3b. approval-requested → approval-responded → output-available/output-denied
   OR
   (at any point) → output-error
```

### State Definitions

```typescript
type ToolState =
  | 'input-streaming'     // Tool input streaming (large inputs)
  | 'input-available'     // Tool input complete
  | 'approval-requested'  // Waiting for user approval
  | 'approval-responded'  // User responded (approved/denied)
  | 'output-available'    // Tool execution complete
  | 'output-error'        // Tool execution failed
  | 'output-denied';      // User denied approval
```

### State Properties

```typescript
// input-streaming
{
  state: 'input-streaming',
  input: Partial<ToolInput> | undefined // Partial input while streaming
}

// input-available
{
  state: 'input-available',
  input: ToolInput, // Complete input
  callProviderMetadata?: ProviderMetadata
}

// approval-requested
{
  state: 'approval-requested',
  input: ToolInput,
  approval: {
    id: string // Approval ID for response
  }
}

// approval-responded
{
  state: 'approval-responded',
  input: ToolInput,
  approval: {
    id: string,
    approved: boolean,
    reason?: string
  }
}

// output-available
{
  state: 'output-available',
  input: ToolInput,
  output: ToolOutput,
  preliminary?: boolean, // True for intermediate streaming outputs
  approval?: {
    id: string,
    approved: true,
    reason?: string
  }
}

// output-error
{
  state: 'output-error',
  input: ToolInput | undefined,
  errorText: string,
  approval?: { ... } // If error after approval
}

// output-denied
{
  state: 'output-denied',
  input: ToolInput,
  approval: {
    id: string,
    approved: false,
    reason?: string
  }
}
```

## Tool Approval Flow

### Defining Approval Requirements

```typescript
const deleteTool = tool({
  description: 'Delete a file',
  inputSchema: z.object({
    filename: z.string()
  }),

  // Request approval before execution
  requiresApproval: true,

  async execute({ filename }) {
    await deleteFile(filename);
    return { deleted: filename };
  }
});
```

### Handling Approval Requests

```typescript
function ToolApproval({ part, addToolApprovalResponse }) {
  if (part.type === 'tool-deleteFile' && part.state === 'approval-requested') {
    return (
      <div>
        <p>Delete {part.input.filename}?</p>
        <button
          onClick={() =>
            addToolApprovalResponse({
              id: part.approval.id,
              approved: true,
              reason: 'User confirmed deletion'
            })
          }
        >
          Approve
        </button>
        <button
          onClick={() =>
            addToolApprovalResponse({
              id: part.approval.id,
              approved: false,
              reason: 'User cancelled'
            })
          }
        >
          Deny
        </button>
      </div>
    );
  }
}
```

### Automatic Resending After Approval

```typescript
const { addToolApprovalResponse } = useChat({
  sendAutomaticallyWhen: ({ messages }) => {
    const lastMsg = messages[messages.length - 1];
    return lastMsg.parts.every(part => {
      if (part.type.startsWith('tool-')) {
        // Resend when all tools are either:
        // - output-available
        // - output-error
        // - output-denied
        // - approval-responded (waiting for backend)
        return ['output-available', 'output-error', 'output-denied', 'approval-responded']
          .includes(part.state);
      }
      return true;
    });
  }
});
```

## Rendering Tool States

### Complete Tool Renderer

```typescript
function ToolPart({ part, addToolOutput, addToolApprovalResponse }) {
  // Type guard
  if (!part.type.startsWith('tool-')) return null;

  const toolName = part.type.replace('tool-', '');

  switch (part.state) {
    case 'input-streaming':
      return (
        <div className="tool-streaming">
          <Spinner />
          <span>Preparing {toolName}...</span>
          <pre>{JSON.stringify(part.input, null, 2)}</pre>
        </div>
      );

    case 'input-available':
      return (
        <div className="tool-executing">
          <Spinner />
          <span>Executing {toolName}...</span>
          {part.title && <h4>{part.title}</h4>}
        </div>
      );

    case 'approval-requested':
      return (
        <div className="tool-approval">
          <h4>Approval Required</h4>
          <p>Allow {toolName}?</p>
          <div className="tool-input">
            <pre>{JSON.stringify(part.input, null, 2)}</pre>
          </div>
          <button
            onClick={() =>
              addToolApprovalResponse({
                id: part.approval.id,
                approved: true
              })
            }
          >
            Approve
          </button>
          <button
            onClick={() =>
              addToolApprovalResponse({
                id: part.approval.id,
                approved: false,
                reason: 'User declined'
              })
            }
          >
            Deny
          </button>
        </div>
      );

    case 'approval-responded':
      return (
        <div className="tool-approval-responded">
          {part.approval.approved ? (
            <span>✓ Approved, executing...</span>
          ) : (
            <span>✗ Denied: {part.approval.reason}</span>
          )}
        </div>
      );

    case 'output-available':
      return (
        <div className="tool-output">
          <h4>{toolName} Result</h4>
          {part.preliminary && <Badge>Preliminary</Badge>}
          <ToolOutput toolName={toolName} output={part.output} />
        </div>
      );

    case 'output-error':
      return (
        <div className="tool-error">
          <h4>{toolName} Error</h4>
          <p className="error">{part.errorText}</p>
          {part.input && (
            <details>
              <summary>Input</summary>
              <pre>{JSON.stringify(part.input, null, 2)}</pre>
            </details>
          )}
        </div>
      );

    case 'output-denied':
      return (
        <div className="tool-denied">
          <span>✗ Tool execution denied</span>
          {part.approval.reason && <p>{part.approval.reason}</p>}
        </div>
      );

    default:
      return null;
  }
}
```

### Tool-Specific Renderers

```typescript
function WeatherToolOutput({ part }) {
  if (part.type !== 'tool-getWeather') return null;

  if (part.state === 'output-available') {
    const { weather, temperature } = part.output;
    return (
      <div className="weather-card">
        <WeatherIcon condition={weather} />
        <span>{temperature}°F</span>
        <span>{weather}</span>
      </div>
    );
  }

  if (part.state === 'input-available') {
    return <div>Fetching weather for {part.input.city}...</div>;
  }

  return null;
}
```

## Type Safety

### Typed Tools

```typescript
const tools = {
  getWeather: tool({
    inputSchema: z.object({ city: z.string() }),
    execute: async ({ city }) => ({ temp: 72, weather: 'sunny' })
  }),
  searchWeb: tool({
    inputSchema: z.object({ query: z.string() }),
    execute: async ({ query }) => ({ results: [] })
  })
} as const;

// Infer tool types
type MyTools = InferUITools<typeof tools>;
// {
//   getWeather: { input: { city: string }; output: { temp: number; weather: string } }
//   searchWeb: { input: { query: string }; output: { results: any[] } }
// }

// Use in message type
type MyMessage = UIMessage<unknown, UIDataTypes, MyTools>;
```

### Type-Safe Tool Rendering

```typescript
function renderToolPart(part: InferUIMessagePart<MyMessage>) {
  // TypeScript knows the exact tool types
  if (part.type === 'tool-getWeather') {
    if (part.state === 'output-available') {
      // part.output is typed as { temp: number; weather: string }
      return <div>{part.output.weather}: {part.output.temp}°F</div>;
    }
  }

  if (part.type === 'tool-searchWeb') {
    if (part.state === 'output-available') {
      // part.output is typed as { results: any[] }
      return <SearchResults results={part.output.results} />;
    }
  }
}
```

### Type-Safe addToolOutput

```typescript
const { addToolOutput } = useChat<MyMessage>();

// TypeScript enforces correct tool name and output type
addToolOutput({
  tool: 'getWeather', // Must be a key in MyTools
  toolCallId: 'call-123',
  output: { temp: 72, weather: 'sunny' } // Must match getWeather output type
});

// Error: Type '"invalid"' is not assignable to type '"getWeather" | "searchWeb"'
addToolOutput({
  tool: 'invalid',
  toolCallId: 'call-123',
  output: {}
});
```

## Advanced Patterns

### Conditional Tool Availability

```typescript
const result = streamText({
  model: openai('gpt-4'),
  messages: convertToModelMessages(uiMessages),
  tools: user.isPremium
    ? { search: searchTool, analyze: analyzeTool }
    : { search: searchTool }
});
```

### Tool Chaining

```typescript
const tools = {
  search: tool({
    description: 'Search for information',
    inputSchema: z.object({ query: z.string() }),
    async execute({ query }) {
      return await searchWeb(query);
    }
  }),
  analyze: tool({
    description: 'Analyze search results',
    inputSchema: z.object({ results: z.array(z.any()) }),
    async execute({ results }) {
      return await analyzeResults(results);
    }
  })
};

const result = streamText({
  model: openai('gpt-4'),
  messages,
  tools,
  stopWhen: stepCountIs(5) // Allow chaining
});
```

### Parallel Tool Execution

The AI model can invoke multiple tools in parallel:

```typescript
const result = streamText({
  model: openai('gpt-4'),
  messages,
  tools: {
    getWeather: weatherTool,
    getNews: newsTool,
    getStocks: stocksTool
  },
  maxToolRoundtrips: 1 // One round with multiple parallel tools
});
```

### Tool Metadata

```typescript
const enrichedTool = tool({
  description: 'Search with metadata',
  inputSchema: z.object({ query: z.string() }),

  async execute({ query }, { messages, abortSignal }) {
    // Access message history
    const context = extractContext(messages);

    // Support cancellation
    const results = await searchWithAbort(query, abortSignal);

    return {
      results,
      metadata: {
        searchTime: Date.now(),
        contextUsed: context
      }
    };
  }
});
```

### Custom Tool Validation

```typescript
const validatedTool = tool({
  description: 'Tool with validation',
  inputSchema: z.object({
    amount: z.number().min(0).max(1000)
  }),

  async execute({ amount }) {
    // Additional runtime validation
    if (amount > 500) {
      throw new Error('Amount requires additional approval');
    }

    return await processPayment(amount);
  }
});
```

## Complete Example

```typescript
// Server route
const tools = {
  // Server-side tool
  getWeather: tool({
    description: 'Get weather for a city',
    inputSchema: z.object({ city: z.string() }),
    async *execute({ city }) {
      yield { state: 'loading' };
      const data = await fetchWeather(city);
      yield { state: 'complete', ...data };
    }
  }),

  // Client-side tool with approval
  getLocation: tool({
    description: 'Get user location (requires permission)',
    inputSchema: z.object({}),
    outputSchema: z.string(),
    requiresApproval: true
  })
};

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

  const result = streamText({
    model: openai('gpt-4'),
    messages: convertToModelMessages(messages),
    tools
  });

  return result.toUIMessageStreamResponse();
}

// Client component
type MyMessage = UIMessage<unknown, UIDataTypes, InferUITools<typeof tools>>;

function Chat() {
  const { messages, addToolOutput, addToolApprovalResponse } = useChat<MyMessage>({
    // Handle client-side tool automatically
    onToolCall: async ({ toolCall }) => {
      if (toolCall.toolName === 'getLocation') {
        const location = await getCurrentLocation();
        addToolOutput({
          tool: 'getLocation',
          toolCallId: toolCall.toolCallId,
          output: location
        });
      }
    },

    // Resend when all tools complete
    sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls
  });

  return (
    <div>
      {messages.map(msg => (
        <div key={msg.id}>
          {msg.parts.map((part, i) => (
            <ToolPart
              key={i}
              part={part}
              addToolOutput={addToolOutput}
              addToolApprovalResponse={addToolApprovalResponse}
            />
          ))}
        </div>
      ))}
    </div>
  );
}
```


path: references/use-chat.md
content: # useChat Hook Reference

Complete API reference for the `useChat` hook from `@ai-sdk/react`.

## Table of Contents

- [Hook Signature](#hook-signature)
- [Options](#options)
- [Return Values](#return-values)
- [Methods](#methods)
- [Callbacks](#callbacks)
- [Types](#types)

## Hook Signature

```typescript
function useChat<UI_MESSAGE extends UIMessage = UIMessage>(
  options?: UseChatOptions<UI_MESSAGE>
): UseChatHelpers<UI_MESSAGE>
```

## Options

### Basic Options

```typescript
interface UseChatOptions<UI_MESSAGE extends UIMessage> {
  // Chat instance (existing) or initialization parameters
  chat?: Chat<UI_MESSAGE>;

  // Chat identifier (auto-generated if not provided)
  id?: string;

  // Initial messages
  messages?: UI_MESSAGE[];

  // Whether to resume an ongoing stream on mount
  resume?: boolean;

  // Custom transport for API communication
  transport?: ChatTransport<UI_MESSAGE>;

  // ID generator function
  generateId?: IdGenerator;
}
```

### Schema Options

```typescript
interface UseChatOptions<UI_MESSAGE extends UIMessage> {
  // Schema for message metadata validation
  messageMetadataSchema?: FlexibleSchema<InferUIMessageMetadata<UI_MESSAGE>>;

  // Schemas for custom data parts
  dataPartSchemas?: UIDataTypesToSchemas<InferUIMessageData<UI_MESSAGE>>;
}
```

### Performance Options

```typescript
interface UseChatOptions<UI_MESSAGE extends UIMessage> {
  // Throttle message updates (in milliseconds)
  // Default: undefined (no throttling)
  experimental_throttle?: number;
}
```

### Callback Options

```typescript
interface UseChatOptions<UI_MESSAGE extends UIMessage> {
  // Called when an error occurs
  onError?: (error: Error) => void;

  // Called when a tool call is received
  onToolCall?: (options: {
    toolCall: InferUIMessageToolCall<UI_MESSAGE>;
  }) => void | PromiseLike<void>;

  // Called when streaming finishes
  onFinish?: (options: {
    message: UI_MESSAGE;
    messages: UI_MESSAGE[];
    isAbort: boolean;
    isDisconnect: boolean;
    isError: boolean;
    finishReason?: FinishReason;
  }) => void;

  // Called when a data part is received
  onData?: (dataPart: DataUIPart<InferUIMessageData<UI_MESSAGE>>) => void;
}
```

### Automation Options

```typescript
interface UseChatOptions<UI_MESSAGE extends UIMessage> {
  // Automatically send messages when condition is met
  sendAutomaticallyWhen?: (options: {
    messages: UI_MESSAGE[];
  }) => boolean | PromiseLike<boolean>;
}
```

## Return Values

```typescript
interface UseChatHelpers<UI_MESSAGE extends UIMessage> {
  // Chat identifier
  readonly id: string;

  // Current chat status
  status: ChatStatus;

  // Current error (if any)
  error: Error | undefined;

  // Array of all messages
  messages: UI_MESSAGE[];

  // Methods (see below)
  sendMessage: (message?, options?) => Promise<void>;
  regenerate: (options?) => Promise<void>;
  stop: () => Promise<void>;
  resumeStream: (options?) => Promise<void>;
  setMessages: (messages) => void;
  clearError: () => void;
  addToolOutput: (options) => Promise<void>;
  addToolApprovalResponse: (options) => Promise<void>;

  // Deprecated
  addToolResult: (options) => Promise<void>; // Use addToolOutput
}
```

### ChatStatus Type

```typescript
type ChatStatus = 'ready' | 'submitted' | 'streaming' | 'error';
```

- **`ready`**: No active request, ready for new messages
- **`submitted`**: Request sent, awaiting stream start
- **`streaming`**: Response actively streaming
- **`error`**: Error occurred during request

## Methods

### sendMessage

Send a new user message or replace an existing one.

```typescript
// Send text message
await sendMessage({ text: 'Hello' });

// Send with files
await sendMessage({
  text: 'Analyze this image',
  files: fileList // FileList or FileUIPart[]
});

// Send with metadata
await sendMessage({
  text: 'Hello',
  metadata: { createdAt: Date.now(), userId: '123' }
});

// Replace existing message
await sendMessage({
  text: 'Updated message',
  messageId: 'msg-123'
});

// Send full UIMessage structure
await sendMessage({
  parts: [
    { type: 'text', text: 'Hello' },
    { type: 'file', url: 'data:...', mediaType: 'image/png' }
  ],
  metadata: { custom: 'data' }
});

// Send with request options
await sendMessage(
  { text: 'Hello' },
  {
    headers: { 'X-Custom': 'value' },
    body: { extra: 'data' },
    metadata: { custom: 'metadata' }
  }
);

// Continue existing conversation (no message)
await sendMessage();
```

**Signature:**
```typescript
sendMessage: (
  message?:
    | { text: string; files?: FileList | FileUIPart[]; metadata?: Metadata; messageId?: string }
    | { files: FileList | FileUIPart[]; metadata?: Metadata; messageId?: string }
    | CreateUIMessage<UI_MESSAGE> & { messageId?: string },
  options?: ChatRequestOptions
) => Promise<void>
```

### regenerate

Regenerate an assistant message.

```typescript
// Regenerate last assistant message
await regenerate();

// Regenerate specific message
await regenerate({ messageId: 'msg-123' });

// With request options
await regenerate({
  messageId: 'msg-123',
  headers: { 'X-Custom': 'value' }
});
```

**Signature:**
```typescript
regenerate: (options?: {
  messageId?: string;
  headers?: Record<string, string> | Headers;
  body?: object;
  metadata?: unknown;
}) => Promise<void>
```

### stop

Stop the current streaming request.

```typescript
await stop();
```

Aborts the active request and sets status to `ready`. Keeps any tokens generated so far.

### resumeStream

Resume an interrupted streaming response.

```typescript
await resumeStream();

// With options
await resumeStream({
  headers: { 'X-Session': 'token' }
});
```

**Signature:**
```typescript
resumeStream: (options?: ChatRequestOptions) => Promise<void>
```

### setMessages

Update messages locally without triggering a request.

```typescript
// Set messages directly
setMessages([...newMessages]);

// Update with function
setMessages(current => current.filter(m => m.role !== 'system'));

// Clear all messages
setMessages([]);
```

**Signature:**
```typescript
setMessages: (
  messages: UI_MESSAGE[] | ((messages: UI_MESSAGE[]) => UI_MESSAGE[])
) => void
```

### clearError

Clear error state and reset to ready.

```typescript
if (error) {
  clearError();
}
```

### addToolOutput

Provide output for a client-side tool call.

```typescript
// Successful tool execution
await addToolOutput({
  tool: 'getWeather',
  toolCallId: 'call-123',
  output: { temperature: 72, weather: 'sunny' }
});

// Tool execution error
await addToolOutput({
  state: 'output-error',
  tool: 'getWeather',
  toolCallId: 'call-123',
  errorText: 'API unavailable'
});
```

**Signature:**
```typescript
addToolOutput: <TOOL extends keyof Tools>(
  options:
    | {
        state?: 'output-available';
        tool: TOOL;
        toolCallId: string;
        output: ToolOutput<TOOL>;
      }
    | {
        state: 'output-error';
        tool: TOOL;
        toolCallId: string;
        errorText: string;
      }
) => Promise<void>
```

### addToolApprovalResponse

Respond to a tool approval request.

```typescript
// Approve tool execution
await addToolApprovalResponse({
  id: 'approval-123',
  approved: true,
  reason: 'Safe to proceed'
});

// Deny tool execution
await addToolApprovalResponse({
  id: 'approval-123',
  approved: false,
  reason: 'User denied location access'
});
```

**Signature:**
```typescript
addToolApprovalResponse: (options: {
  id: string;
  approved: boolean;
  reason?: string;
}) => Promise<void>
```

## Callbacks

### onError

Called when any error occurs during the chat.

```typescript
useChat({
  onError: (error) => {
    console.error('Chat error:', error);
    toast.error(error.message);
  }
});
```

### onToolCall

Called when a tool call is received. Use for automatic client-side tool execution.

```typescript
useChat({
  onToolCall: async ({ toolCall }) => {
    // Handle different tools
    if (toolCall.toolName === 'getLocation') {
      const location = await getCurrentLocation();
      addToolOutput({
        tool: 'getLocation',
        toolCallId: toolCall.toolCallId,
        output: location
      });
    }
  }
});
```

**Important**: This callback is for automatic execution. For user-interactive tools (like confirmations), handle them in the render phase.

### onFinish

Called when streaming completes (success, abort, or error).

```typescript
useChat({
  onFinish: ({ message, messages, isAbort, isDisconnect, isError, finishReason }) => {
    if (isError) {
      console.error('Stream ended with error');
      return;
    }

    if (isAbort) {
      console.log('User aborted request');
      return;
    }

    if (isDisconnect) {
      console.warn('Network disconnected');
      return;
    }

    // Save to database
    saveMessages(messages);

    console.log('Finish reason:', finishReason); // 'stop' | 'length' | 'tool-calls' | ...
  }
});
```

### onData

Called when custom data parts are received.

```typescript
useChat<UIMessage<never, { progress: { percent: number } }>>({
  onData: (dataPart) => {
    if (dataPart.type === 'data-progress') {
      updateProgressBar(dataPart.data.percent);
    }
  }
});
```

## Types

### ChatRequestOptions

```typescript
interface ChatRequestOptions {
  // Additional headers for the API request
  headers?: Record<string, string> | Headers;

  // Additional body properties for the API request
  body?: object;

  // Request-specific metadata
  metadata?: unknown;
}
```

### CreateUIMessage

```typescript
type CreateUIMessage<UI_MESSAGE extends UIMessage> = Omit<
  UI_MESSAGE,
  'id' | 'role'
> & {
  id?: UI_MESSAGE['id'];
  role?: UI_MESSAGE['role'];
};
```

Used for creating messages without requiring `id` and `role` (auto-generated).

## Examples

### Basic Chat

```typescript
function ChatComponent() {
  const { messages, status, sendMessage } = useChat({
    id: 'my-chat',
    onError: (error) => toast.error(error.message)
  });

  return (
    <div>
      {messages.map(msg => (
        <div key={msg.id}>{msg.role}: {msg.parts[0]?.text}</div>
      ))}
      <input
        disabled={status !== 'ready'}
        onSubmit={(text) => sendMessage({ text })}
      />
    </div>
  );
}
```

### With Tools

```typescript
function ChatWithTools() {
  const { messages, addToolOutput } = useChat<MyToolMessage>({
    onToolCall: async ({ toolCall }) => {
      if (toolCall.toolName === 'autoExecute') {
        const result = await executeAutomatically();
        addToolOutput({
          tool: 'autoExecute',
          toolCallId: toolCall.toolCallId,
          output: result
        });
      }
    },
    sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls
  });

  return (
    <div>
      {messages.map(msg => (
        <Message
          key={msg.id}
          message={msg}
          onToolApprove={(toolCallId) =>
            addToolOutput({
              tool: 'userConfirm',
              toolCallId,
              output: 'confirmed'
            })
          }
        />
      ))}
    </div>
  );
}
```

### Custom Transport

```typescript
const { messages } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/my-chat',
    prepareSendMessagesRequest: ({ id, messages, trigger, messageId }) => ({
      url: `/api/my-chat/${id}`,
      body: {
        lastMessage: messages[messages.length - 1],
        action: trigger,
        targetMessageId: messageId
      },
      headers: {
        'X-Session-Token': getSessionToken()
      }
    })
  })
});
```

### Throttled Updates

```typescript
// Update UI at most once per 100ms during streaming
const { messages } = useChat({
  experimental_throttle: 100
});
```

### Resume Interrupted Stream

```typescript
function ChatComponent({ chatId }) {
  const [shouldResume, setShouldResume] = useState(false);

  useEffect(() => {
    // Check if there's an active stream for this chat
    checkActiveStream(chatId).then(setShouldResume);
  }, [chatId]);

  const chat = useChat({
    id: chatId,
    resume: shouldResume
  });

  return <ChatUI {...chat} />;
}
```


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)