🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

Vercel Ai Sdk

by @anderskev

Vercel AI SDK for building chat interfaces with streaming. Use when implementing useChat hook, handling tool calls, streaming responses, or building chat UI....

Versionv1.0.1
Downloads901
TERMINAL
clawhub install vercel-ai-sdk

πŸ“– About This Skill


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

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:

    interface UIMessage {
      id: string;
      role: 'system' | 'user' | 'assistant';
      metadata?: unknown;
      parts: Array; // 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

    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:

    const { addToolOutput } = useChat({
      onToolCall: async ({ toolCall }) => {
        if (toolCall.toolName === 'getLocation') {
          addToolOutput({
            tool: 'getLocation',
            toolCallId: toolCall.toolCallId,
            output: 'San Francisco'
          });
        }
      }
    });
    

    Rendering Tool States:

    {message.parts.map(part => {
      if (part.type === 'tool-getWeather') {
        switch (part.state) {
          case 'input-streaming':
            return 
    {JSON.stringify(part.input, null, 2)}
    ; case 'input-available': return
    Getting weather for {part.input.city}...
    ; case 'output-available': return
    Weather: {part.output.weather}
    ; case 'output-error': return
    Error: {part.errorText}
    ; } } })}

    Reference Files

    Detailed documentation on specific aspects:

  • use-chat.md: Complete useChat API reference
  • messages.md: UIMessage structure and part types
  • streaming.md: Server-side streaming implementation
  • tools.md: Tool definition and execution patterns
  • Common Patterns

    Error Handling

    const { error, clearError } = useChat({
      onError: (error) => {
        toast.error(error.message);
      }
    });

    // Clear error and reset to ready state if (error) { clearError(); }

    Message Regeneration

    const { regenerate } = useChat();

    // Regenerate last assistant message await regenerate();

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

    Custom Transport

    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

    // Throttle UI updates to reduce re-renders
    const chat = useChat({
      experimental_throttle: 100 // Update max once per 100ms
    });
    

    Automatic Message Sending

    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:

    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 // Tool types >;

    const { messages } = useChat();

    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

    πŸ“‹ Tips & 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