### Displaying Warning Messages Source: https://ai-sdk.dev/docs/ai-sdk-ui/error-handling Example of the format used for AI SDK warning messages in the console. ```text AI SDK Warning: The feature "temperature" is not supported by this model ``` -------------------------------- ### Implement a complete chat interface with data parts Source: https://ai-sdk.dev/docs/ai-sdk-ui/streaming-data A full example demonstrating message history management, transient notification handling, and rendering of persistent weather data. ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; import type { MyUIMessage } from '@/ai/types'; export default function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat({ api: '/api/chat', onData: dataPart => { // Handle transient notifications if (dataPart.type === 'data-notification') { console.log('Notification:', dataPart.data.message); } }, }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); sendMessage({ text: input }); setInput(''); }; return ( <> {messages?.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {/* Render weather data */} {message.parts .filter(part => part.type === 'data-weather') .map((part, index) => ( {part.data.status === 'loading' ? ( <>Getting weather for {part.data.city}... ) : ( <> Weather in {part.data.city}: {part.data.weather} )} ))} {/* Render text content */} {message.parts .filter(part => part.type === 'text') .map((part, index) => (
{part.text}
))}
))}
setInput(e.target.value)} placeholder="Ask about the weather..." />
); } ``` -------------------------------- ### Implement useCompletion in React Source: https://ai-sdk.dev/docs/ai-sdk-ui/completion Basic setup for the useCompletion hook in a client component and its corresponding API route. ```tsx 'use client'; import { useCompletion } from '@ai-sdk/react'; export default function Page() { const { completion, input, handleInputChange, handleSubmit } = useCompletion({ api: '/api/completion', }); return (
{completion}
); } ``` ```ts import { streamText } from 'ai'; __PROVIDER_IMPORT__; // Allow streaming responses up to 30 seconds export const maxDuration = 30; export async function POST(req: Request) { const { prompt }: { prompt: string } = await req.json(); const result = streamText({ model: __MODEL__, prompt, }); return result.toUIMessageStreamResponse(); } ``` -------------------------------- ### Display Usage Metrics in UI Components Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot Access message-level metadata on the client to display usage metrics. This example shows how to render total usage tokens directly within the chat messages. ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; import type { MyUIMessage } from './api/chat/route'; import { DefaultChatTransport } from 'ai'; export default function Chat() { // Use custom message type defined on the server (optional for type-safety) const { messages } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), }); return (
{messages.map(m => (
{m.role === 'user' ? 'User: ' : 'AI: '} {m.parts.map(part => { if (part.type === 'text') { return part.text; } })} {/* Render usage via metadata */} {m.metadata?.totalUsage && (
Total usage: {m.metadata?.totalUsage.totalTokens} tokens
)}
))}
); } ``` -------------------------------- ### Define a New Stock Tool Source: https://ai-sdk.dev/docs/ai-sdk-ui/generative-user-interfaces Define a new tool for fetching stock prices. This example includes input schema validation and a simulated asynchronous API call. ```typescript export const stockTool = createTool({ description: 'Get price for a stock', inputSchema: z.object({ symbol: z.string().describe('The stock symbol to get the price for'), }), execute: async function ({ symbol }) { // Simulated API call await new Promise(resolve => setTimeout(resolve, 2000)); return { symbol, price: 100 }; }, }); ``` -------------------------------- ### Control Message IDs with createUIMessageStream Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence Use createUIMessageStream to manually write a start message part with a custom server-side ID. ```tsx import { generateId, streamText, createUIMessageStream, createUIMessageStreamResponse, } from 'ai'; export async function POST(req: Request) { const { messages, chatId } = await req.json(); const stream = createUIMessageStream({ execute: ({ writer }) => { // Write start message part with custom ID writer.write({ type: 'start', messageId: generateId(), // Generate server-side ID for persistence }); const result = streamText({ model: 'openai/gpt-5-mini', messages: await convertToModelMessages(messages), }); writer.merge(result.toUIMessageStream({ sendStart: false })); // omit start message part }, originalMessages: messages, onFinish: ({ responseMessage }) => { // save your chat here }, }); return createUIMessageStreamResponse({ stream }); } ``` -------------------------------- ### Create New Chat and Redirect Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence Initiates a new chat session by generating a unique ID and then redirects the user to the chat page with this new ID. This is typically used when a user starts a conversation without a pre-existing chat ID. ```tsx import { redirect } from 'next/navigation'; import { createChat } from '@util/chat-store'; export default async function Page() { const id = await createChat(); // create a new chat redirect(`/chat/${id}`); // redirect to chat page, see below } ``` -------------------------------- ### Define Message Metadata Schema with Zod Source: https://ai-sdk.dev/docs/ai-sdk-ui/message-metadata Define your metadata schema using Zod for type safety. This example shows how to create a schema for creation timestamp, model name, and total tokens. ```tsx import { UIMessage, } from 'ai'; import { z } from 'zod'; // Define your metadata schema export const messageMetadataSchema = z.object({ createdAt: z.number().optional(), model: z.string().optional(), totalTokens: z.number().optional(), }); export type MessageMetadata = z.infer; // Create a typed UIMessage export type MyUIMessage = UIMessage; ``` -------------------------------- ### Initialize useChat with default transport Source: https://ai-sdk.dev/docs/ai-sdk-ui/transport Demonstrates the default behavior of useChat and its explicit equivalent using DefaultChatTransport. ```tsx import { useChat } from '@ai-sdk/react'; // Uses default HTTP transport const { messages, sendMessage } = useChat(); ``` ```tsx import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), }); ``` -------------------------------- ### Access Message Metadata on the Client Source: https://ai-sdk.dev/docs/ai-sdk-ui/message-metadata Access message metadata through the `message.metadata` property in your client-side components. This example shows how to display creation time and token count. ```tsx 'use client'; import { useChat, } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; import type { MyUIMessage } from '@/types'; export default function Chat() { const { messages } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), }); return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.metadata?.createdAt && ( {new Date(message.metadata.createdAt).toLocaleTimeString()} )}
{/* Render message content */} {message.parts.map((part, index) => part.type === 'text' ?
{part.text}
: null, )} {/* Display additional metadata */} {message.metadata?.totalTokens && (
{message.metadata.totalTokens} tokens
)}
))}
); } ``` -------------------------------- ### Display Step Boundaries for Multi-Step Calls Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-tool-usage Utilize 'step-start' parts to visually separate multi-step tool calls in the UI by rendering horizontal lines between steps. ```tsx // ... // where you render the message parts: message.parts.map((part, index) => { switch (part.type) { case 'step-start': // show step boundaries as horizontal lines: return index > 0 ? (

) : null; case 'text': // ... case 'tool-askForConfirmation': case 'tool-getLocation': case 'tool-getWeatherInformation': // ... } }); // ... ``` -------------------------------- ### Send Message Metadata from Server Source: https://ai-sdk.dev/docs/ai-sdk-ui/message-metadata Use the `messageMetadata` callback in `toUIMessageStreamResponse` to send metadata at different streaming stages. Metadata can be sent at the start and finish of the stream. ```ts import { convertToModelMessages, streamText, } from 'ai'; __PROVIDER_IMPORT__; import type { MyUIMessage } from '@/types'; export async function POST(req: Request) { const { messages }: { messages: MyUIMessage[] } = await req.json(); const result = streamText({ model: __MODEL__, messages: await convertToModelMessages(messages), }); return result.toUIMessageStreamResponse({ originalMessages: messages, // pass this in for type-safe return objects messageMetadata: ({ part }) => { // Send metadata when streaming starts if (part.type === 'start') { return { createdAt: Date.now(), model: 'your-model-id', }; } // Send additional metadata when streaming completes if (part.type === 'finish') { return { totalTokens: part.totalUsage.totalTokens, }; } }, }); } ``` -------------------------------- ### Initialize and Use useChat Hook Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence Set up the `useChat` hook with a chat ID, initial messages, and a transport configuration. Handles message sending and state management for the chat UI. ```typescript 'use client'; import { UIMessage, useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; import { useState } from 'react'; export default function Chat({ id, initialMessages, }: { id?: string | undefined; initialMessages?: UIMessage[] } = {}) { const [input, setInput] = useState(''); const { sendMessage, messages } = useChat({ id, // use the provided chat ID messages: initialMessages, // load initial messages transport: new DefaultChatTransport({ api: '/api/chat', }), }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (input.trim()) { sendMessage({ text: input }); setInput(''); } }; // simplified rendering code, extend as needed: return (
{messages.map(m => (
{m.role === 'user' ? 'User: ' : 'AI: '} {m.parts .map(part => (part.type === 'text' ? part.text : '')) .join('')}
))}
setInput(e.target.value)} placeholder="Type a message..." />
); } ``` -------------------------------- ### Enable Sources in AI SDK Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot Configure the AI SDK to send sources (web pages or documents) to the client. Set `sendSources: true` in the response. This is supported by providers like Perplexity and Google Generative AI. ```typescript import { convertToModelMessages, streamText, UIMessage } from 'ai'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: 'perplexity/sonar-pro', messages: await convertToModelMessages(messages), }); return result.toUIMessageStreamResponse({ sendSources: true, }); } ``` -------------------------------- ### Implement Client-Side Approval UI Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-tool-usage Use addToolApprovalResponse to handle user approval or denial when a tool part state is set to approval-requested. ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; export default function Chat() { const { messages, addToolApprovalResponse } = useChat(); return ( <> {messages.map(message => (
{message.parts.map(part => { if (part.type === 'tool-getWeather') { switch (part.state) { case 'approval-requested': return (

Get weather for {part.input.city}?

); case 'output-available': return (
Weather in {part.input.city}: {part.output}
); } } // Handle other part types... })}
))} ); } ``` -------------------------------- ### Tool Execution Parts Source: https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol Manage the lifecycle of tool calls, including input streaming, availability, and output results. ```json data: {"type":"tool-input-start","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","toolName":"getWeatherInformation"} ``` ```json data: {"type":"tool-input-delta","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","inputTextDelta":"San Francisco"} ``` ```json data: {"type":"tool-input-available","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","toolName":"getWeatherInformation","input":{"city":"San Francisco"}} ``` ```json data: {"type":"tool-output-available","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","output":{"city":"San Francisco","weather":"sunny"}} ``` -------------------------------- ### File-Based Chat Store: Create Chat Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence Generates a unique chat ID and creates an empty JSON file to store chat messages for that ID. This function is part of a file-based persistence strategy, suitable for examples but recommended to be replaced with a database in production. ```typescript import { generateId } from 'ai'; import { existsSync, mkdirSync } from 'fs'; import { writeFile } from 'fs/promises'; import path from 'path'; export async function createChat(): Promise { const id = generateId(); // generate a unique chat ID await writeFile(getChatFile(id), '[]'); // create an empty chat file return id; } function getChatFile(id: string): string { const chatDir = path.join(process.cwd(), '.chats'); if (!existsSync(chatDir)) mkdirSync(chatDir, { recursive: true }); return path.join(chatDir, `${id}.json`); } ``` -------------------------------- ### Implement Event Callbacks Source: https://ai-sdk.dev/docs/ai-sdk-ui/object-generation Use onFinish and onError callbacks to handle lifecycle events like logging or custom UI updates. ```tsx 'use client'; import { experimental_useObject as useObject } from '@ai-sdk/react'; import { notificationSchema } from './api/notifications/schema'; export default function Page() { const { object, submit } = useObject({ api: '/api/notifications', schema: notificationSchema, onFinish({ object, error }) { // typed object, undefined if schema validation fails: console.log('Object generation completed:', object); // error, undefined if schema validation succeeds: console.log('Schema validation error:', error); }, onError(error) { // error during fetch request: console.error('An error occurred:', error); }, }); return (
{object?.notifications?.map((notification, index) => (

{notification?.name}

{notification?.message}

))}
); } ``` -------------------------------- ### Render URL and Document Sources on Client Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot Display URL and document sources from the message object on the client. Filter parts by `part.type === 'source-url'` or `part.type === 'source-document'` and render accordingly. ```tsx messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {/* Render URL sources */} {message.parts .filter(part => part.type === 'source-url') .map(part => ( [ {part.title ?? new URL(part.url).hostname} ] ))} {/* Render document sources */} {message.parts .filter(part => part.type === 'source-document') .map(part => ( [{part.title ?? `Document ${part.id}`}] ))}
)); ``` -------------------------------- ### Implement GET Handler for Resuming Chat Streams Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-resume-streams This handler reads the chat ID, loads chat data to check for an active stream, returns 204 if no stream is active, and resumes the existing stream if found. It uses `createResumableStreamContext` to manage the stream. ```typescript import { readChat, } from '@util/chat-store'; import { UI_MESSAGE_STREAM_HEADERS, } from 'ai'; import { after } from 'next/server'; import { createResumableStreamContext, } from 'resumable-stream'; export async function GET( _: Request, { params }: { params: Promise<{ id: string }> }, ) { const { id } = await params; const chat = await readChat(id); if (chat.activeStreamId == null) { // no content response when there is no active stream return new Response(null, { status: 204 }); } const streamContext = createResumableStreamContext({ waitUntil: after, }); return new Response( await streamContext.resumeExistingStream(chat.activeStreamId), { headers: UI_MESSAGE_STREAM_HEADERS }, ); } ``` -------------------------------- ### Handle Plain Text Streams with `TextStreamChatTransport` Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot Configure `useChat` to handle plain text streams by setting the `streamProtocol` option to `text`. This is useful for backend servers that stream plain text. ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; import { TextStreamChatTransport } from 'ai'; export default function Chat() { const { messages } = useChat({ transport: new TextStreamChatTransport({ api: '/api/chat', }), }); return <>...; } ``` -------------------------------- ### Implement Client-side Chat with Tool Calls Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-tool-usage Demonstrates using the useChat hook to handle client-side tool execution and rendering message parts in a React component. ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls, } from 'ai'; import { useState } from 'react'; export default function Chat() { const { messages, sendMessage, addToolOutput } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, // run client-side tools that are automatically executed: async onToolCall({ toolCall }) { // Check if it's a dynamic tool first for proper type narrowing if (toolCall.dynamic) { return; } if (toolCall.toolName === 'getLocation') { const cities = ['New York', 'Los Angeles', 'Chicago', 'San Francisco']; // No await - avoids potential deadlocks addToolOutput({ tool: 'getLocation', toolCallId: toolCall.toolCallId, output: cities[Math.floor(Math.random() * cities.length)], }); } }, }); const [input, setInput] = useState(''); return ( <> {messages?.map(message => (
{`${message.role}: `} {message.parts.map(part => { switch (part.type) { // render text parts as simple text: case 'text': return part.text; // for tool parts, use the typed tool part names: case 'tool-askForConfirmation': { const callId = part.toolCallId; switch (part.state) { case 'input-streaming': return (
Loading confirmation request...
); case 'input-available': return (
{part.input.message}
); case 'output-available': return (
Location access allowed: {part.output}
); case 'output-error': return
Error: {part.errorText}
; } break; } ``` -------------------------------- ### Enable Reasoning Tokens in AI SDK Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot Configure the AI SDK to send reasoning tokens to the client. Ensure the model supports reasoning tokens and set `sendReasoning: true` in the response. ```typescript import { convertToModelMessages, streamText, UIMessage } from 'ai'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: 'deepseek/deepseek-r1', messages: await convertToModelMessages(messages), }); return result.toUIMessageStreamResponse({ sendReasoning: true, }); } ``` -------------------------------- ### AI SDK UI Components Source: https://ai-sdk.dev/docs/ai-sdk-ui Hooks and utilities for building user interfaces with the AI SDK. ```APIDOC ## AI SDK UI Components This section covers the UI-specific components and hooks provided by the AI SDK. ### useChat - **Description**: Hook for managing chat interactions in the UI. - **Endpoint**: `/docs/reference/ai-sdk-ui/use-chat` ### useCompletion - **Description**: Hook for handling text completion in the UI. - **Endpoint**: `/docs/reference/ai-sdk-ui/use-completion` ### useObject - **Description**: Hook for managing object-based data in the UI. - **Endpoint**: `/docs/reference/ai-sdk-ui/use-object` ### convertToModelMessages - **Description**: Converts UI messages to model-compatible messages. - **Endpoint**: `/docs/reference/ai-sdk-ui/convert-to-model-messages` ### pruneMessages - **Description**: Prunes message history to manage length. - **Endpoint**: `/docs/reference/ai-sdk-ui/prune-messages` ### createUIMessageStream - **Description**: Creates a stream for UI messages. - **Endpoint**: `/docs/reference/ai-sdk-ui/create-ui-message-stream` ### createUIMessageStreamResponse - **Description**: Creates a response from a UI message stream. - **Endpoint**: `/docs/reference/ai-sdk-ui/create-ui-message-stream-response` ### pipeUIMessageStreamToResponse - **Description**: Pipes a UI message stream to a response. - **Endpoint**: `/docs/reference/ai-sdk-ui/pipe-ui-message-stream-to-response` ### readUIMessageStream - **Description**: Reads data from a UI message stream. - **Endpoint**: `/docs/reference/ai-sdk-ui/read-ui-message-stream` ### InferUITools - **Description**: Infers UI tools from a given context. - **Endpoint**: `/docs/reference/ai-sdk-ui/infer-ui-tools` ### InferUITool - **Description**: Infers a single UI tool. - **Endpoint**: `/docs/reference/ai-sdk-ui/infer-ui-tool` ### DirectChatTransport - **Description**: A transport layer for direct chat communication. - **Endpoint**: `/docs/reference/ai-sdk-ui/direct-chat-transport` ``` -------------------------------- ### Define Weather Tool Source: https://ai-sdk.dev/docs/ai-sdk-ui/generative-user-interfaces Creates a tool using the AI SDK to simulate fetching weather data with a schema-validated input. ```ts import { tool as createTool } from 'ai'; import { z } from 'zod'; export const weatherTool = createTool({ description: 'Display the weather for a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async function ({ location }) { await new Promise(resolve => setTimeout(resolve, 2000)); return { weather: 'Sunny', temperature: 75, location }; }, }); export const tools = { displayWeather: weatherTool, }; ``` -------------------------------- ### Configuring Global Warning Settings Source: https://ai-sdk.dev/docs/ai-sdk-ui/error-handling Methods to disable all warnings or provide a custom handler function for warning events. ```ts globalThis.AI_SDK_LOG_WARNINGS = false; ``` ```ts globalThis.AI_SDK_LOG_WARNINGS = ({ warnings, provider, model }) => { // Handle warnings your own way }; ``` -------------------------------- ### Reasoning Parts Source: https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol Use these parts to stream incremental reasoning content and signal the completion of a reasoning block. ```json data: {"type":"reasoning-delta","id":"reasoning_123","delta":"This is some reasoning"} ``` ```json data: {"type":"reasoning-end","id":"reasoning_123"} ``` -------------------------------- ### Source and File Parts Source: https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol Reference external URLs, documents, or files within the stream. ```json data: {"type":"source-url","sourceId":"https://example.com","url":"https://example.com"} ``` ```json data: {"type":"source-document","sourceId":"https://example.com","mediaType":"file","title":"Title"} ``` ```json data: {"type":"file","url":"https://example.com/file.png","mediaType":"image/png"} ``` -------------------------------- ### Create Stock Information Component Source: https://ai-sdk.dev/docs/ai-sdk-ui/generative-user-interfaces Create a React component to display stock information. This component receives price and symbol as props and renders them in a structured format. ```tsx type StockProps = { price: number; symbol: string; }; export const Stock = ({ price, symbol }: StockProps) => { return (

Stock Information

Symbol: {symbol}

Price: ${price}

); }; ``` -------------------------------- ### Handling Tool Calls in UI Message Streams Source: https://ai-sdk.dev/docs/ai-sdk-ui/reading-ui-message-streams Shows how to process different message part types, including text, tool calls, and tool results, within a stream. ```tsx import { readUIMessageStream, streamText, tool } from 'ai'; __PROVIDER_IMPORT__; import { z } from 'zod'; async function handleToolCalls() { const result = streamText({ model: __MODEL__, tools: { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: ({ location }) => ({ location, temperature: 72 + Math.floor(Math.random() * 21) - 10, }), }), }, prompt: 'What is the weather in Tokyo?', }); for await (const uiMessage of readUIMessageStream({ stream: result.toUIMessageStream(), })) { // Handle different part types uiMessage.parts.forEach(part => { switch (part.type) { case 'text': console.log('Text:', part.text); break; case 'tool-call': console.log('Tool called:', part.toolName, 'with args:', part.args); break; case 'tool-result': console.log('Tool result:', part.result); break; } }); } } ``` -------------------------------- ### Implement file-based chat storage Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence A sample implementation of a `saveChat` function that serializes `UIMessage` objects to a JSON file. ```tsx import { UIMessage } from 'ai'; import { writeFile } from 'fs/promises'; export async function saveChat({ chatId, messages, }: { chatId: string; messages: UIMessage[]; }): Promise { const content = JSON.stringify(messages, null, 2); await writeFile(getChatFile(chatId), content); } // ... rest of the file ``` -------------------------------- ### Chatbot API Endpoint for Streaming Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot Set up a POST endpoint to receive chat messages, stream responses from an AI model, and return them as a UI message stream. Ensure __PROVIDER_IMPORT__ and __MODEL__ are correctly configured. ```ts import { convertToModelMessages, streamText, UIMessage } from 'ai'; __PROVIDER_IMPORT__; // Allow streaming responses up to 30 seconds export const maxDuration = 30; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: __MODEL__, system: 'You are a helpful assistant.', messages: await convertToModelMessages(messages), }); return result.toUIMessageStreamResponse(); } ``` -------------------------------- ### Configure Automatic Submission After Approval Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-tool-usage Use lastAssistantMessageIsCompleteWithApprovalResponses in the useChat hook to automatically continue the conversation flow. ```tsx import { useChat } from '@ai-sdk/react'; import { lastAssistantMessageIsCompleteWithApprovalResponses } from 'ai'; const { messages, addToolApprovalResponse } = useChat({ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses, }); ``` -------------------------------- ### Send Multiple Files with FileList Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot Use the `FileList` object with the `useChat` hook to send multiple files as attachments. The hook automatically converts supported file types (images and text) into multi-modal content parts. ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; import { useRef, useState } from 'react'; export default function Page() { const { messages, sendMessage, status } = useChat(); const [input, setInput] = useState(''); const [files, setFiles] = useState(undefined); const fileInputRef = useRef(null); return (
{messages.map(message => (
{`${message.role}: `}
{message.parts.map((part, index) => { if (part.type === 'text') { return {part.text}; } if ( part.type === 'file' && part.mediaType?.startsWith('image/') ) { return {part.filename}; } return null; })}
))}
{ event.preventDefault(); if (input.trim()) { sendMessage({ text: input, files, }); setInput(''); setFiles(undefined); if (fileInputRef.current) { fileInputRef.current.value = ''; } } }} > { if (event.target.files) { setFiles(event.target.files); } }} multiple ref={fileInputRef} /> setInput(e.target.value)} disabled={status !== 'ready'} />
); } ``` -------------------------------- ### Basic Chatbot UI with useChat Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot Integrate the useChat hook to manage chat state, send messages, and display streamed AI responses in real-time. Requires a '/api/chat' endpoint. ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; import { useState } from 'react'; export default function Page() { const { messages, sendMessage, status } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), }); const [input, setInput] = useState(''); return ( <> {messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, index) => part.type === 'text' ? {part.text} : null, )}
))}
{ e.preventDefault(); if (input.trim()) { sendMessage({ text: input }); setInput(''); } }}> setInput(e.target.value)} disabled={status !== 'ready'} placeholder="Say something..." />
); } ``` -------------------------------- ### Handle Event Callbacks Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot Implement lifecycle hooks for chat completion, errors, and data streaming. ```tsx import { UIMessage } from 'ai'; const { /* ... */ } = useChat({ onFinish: ({ message, messages, isAbort, isDisconnect, isError }) => { // use information to e.g. update other UI states }, onError: error => { console.error('An error occurred:', error); }, onData: data => { console.log('Received data part from server:', data); }, }); ``` -------------------------------- ### InferUITools Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot Infers the input and output types of a complete ToolSet. ```APIDOC ## InferUITools ### Description The `InferUITools` type helper processes a `ToolSet` object to infer the input and output types for all tools contained within the set. ### Usage ```typescript import { InferUITools, ToolSet } from 'ai'; type MyUITools = InferUITools; // Result: { [toolName]: { input: ..., output: ... } } ``` ``` -------------------------------- ### Render Partial Tool Calls in UI Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-tool-usage Use the `useChat` hook to display partial tool calls in the UI. The `state` property of tool parts helps render the correct UI for streaming, available, or error states. ```tsx export default function Chat() { // ... return ( <> {messages?.map(message => (
{message.parts.map(part => { switch (part.type) { case 'tool-askForConfirmation': case 'tool-getLocation': case 'tool-getWeatherInformation': switch (part.state) { case 'input-streaming': return
{JSON.stringify(part.input, null, 2)}
; case 'input-available': return
{JSON.stringify(part.input, null, 2)}
; case 'output-available': return
{JSON.stringify(part.output, null, 2)}
; case 'output-error': return
Error: {part.errorText}
; } } })}
))} ); } ``` -------------------------------- ### Resuming Conversations with UI Message Streams Source: https://ai-sdk.dev/docs/ai-sdk-ui/reading-ui-message-streams Illustrates how to resume a stream from a previous message state by passing an existing UIMessage object. ```tsx import { readUIMessageStream, streamText } from 'ai'; __PROVIDER_IMPORT__; async function resumeConversation(lastMessage: UIMessage) { const result = streamText({ model: __MODEL__, messages: [ { role: 'user', content: 'Continue our previous conversation.' }, ], }); // Resume from the last message for await (const uiMessage of readUIMessageStream({ stream: result.toUIMessageStream(), message: lastMessage, // Resume from this message })) { console.log('Resumed message:', uiMessage); } } ``` -------------------------------- ### Server-Side Multi-Step Tool Calls with streamText Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-tool-usage Implement multi-step tool calls on the server-side using `streamText` when all invoked tools have an `execute` function defined. ```tsx import { convertToModelMessages, streamText, UIMessage, stepCountIs } from 'ai'; __PROVIDER_IMPORT__; import { z } from 'zod'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: __MODEL__, messages: await convertToModelMessages(messages), tools: { getWeatherInformation: { description: 'show the weather in a given city to the user', inputSchema: z.object({ city: z.string() }), // tool has execute function: execute: async ({}: { city: string }) => { const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy', 'windy']; return weatherOptions[ Math.floor(Math.random() * weatherOptions.length) ]; }, }, }, stopWhen: stepCountIs(5), }); return result.toUIMessageStreamResponse(); } ``` -------------------------------- ### Create POST Handler for Resumable Streams Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-resume-streams Implement the POST handler to create resumable streams using `consumeSseStream`. This handler saves the user message, initiates the stream, and updates the chat with the active stream ID for resumption. ```ts import { openai } from '@ai-sdk/openai'; import { readChat, saveChat } from '@util/chat-store'; import { convertToModelMessages, generateId, streamText, type UIMessage, } from 'ai'; import { after } from 'next/server'; import { createResumableStreamContext } from 'resumable-stream'; export async function POST(req: Request) { const { message, id, }: { message: UIMessage | undefined; id: string; } = await req.json(); const chat = await readChat(id); let messages = chat.messages; messages = [...messages, message!]; // Clear any previous active stream and save the user message saveChat({ id, messages, activeStreamId: null }); const result = streamText({ model: 'openai/gpt-5-mini', messages: await convertToModelMessages(messages), }); return result.toUIMessageStreamResponse({ originalMessages: messages, generateMessageId: generateId, onFinish: ({ messages }) => { // Clear the active stream when finished saveChat({ id, messages, activeStreamId: null }); }, async consumeSseStream({ stream }) { const streamId = generateId(); // Create a resumable stream from the SSE stream const streamContext = createResumableStreamContext({ waitUntil: after }); await streamContext.createNewResumableStream(streamId, () => stream); // Update the chat with the active stream ID saveChat({ id, activeStreamId: streamId }); }, }); } ``` -------------------------------- ### Handle Loading State with useObject Source: https://ai-sdk.dev/docs/ai-sdk-ui/object-generation Utilize the isLoading state to display UI indicators like spinners or disable submission buttons during generation. ```tsx 'use client'; import { experimental_useObject as useObject } from '@ai-sdk/react'; export default function Page() { const { isLoading, object, submit } = useObject({ api: '/api/notifications', schema: notificationSchema, }); return ( <> {isLoading && } {object?.notifications?.map((notification, index) => (

{notification?.name}

{notification?.message}

))} ); } ``` -------------------------------- ### Implement Client-Side Text Classification Source: https://ai-sdk.dev/docs/ai-sdk-ui/object-generation Use useObject with an enum schema to categorize input into predefined options. ```tsx 'use client'; import { experimental_useObject as useObject } from '@ai-sdk/react'; import { z } from 'zod'; export default function ClassifyPage() { const { object, submit, isLoading } = useObject({ api: '/api/classify', schema: z.object({ enum: z.enum(['true', 'false']) }), }); return ( <> {object &&
Classification: {object.enum}
} ); } ``` -------------------------------- ### Handle Dynamic Tools in UI Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-tool-usage Use the generic dynamic-tool type to render tools that are not known at compile time. ```tsx { message.parts.map((part, index) => { switch (part.type) { // Static tools with specific (`tool-${toolName}`) types case 'tool-getWeatherInformation': return ; // Dynamic tools use generic `dynamic-tool` type case 'dynamic-tool': return (

Tool: {part.toolName}

{part.state === 'input-streaming' && (
{JSON.stringify(part.input, null, 2)}
)} {part.state === 'output-available' && (
{JSON.stringify(part.output, null, 2)}
)} {part.state === 'output-error' && (
Error: {part.errorText}
)}
); } }); } ``` -------------------------------- ### Handle Loading and Error States Source: https://ai-sdk.dev/docs/ai-sdk-ui/completion Display UI elements based on the loading and error states returned by the hook. ```tsx const { isLoading, ... } = useCompletion() return( <> {isLoading ? : null} ) ``` ```tsx const { error, ... } = useCompletion() useEffect(() => { if (error) { toast.error(error.message) } }, [error]) // Or display the error message in the UI: return ( <> {error ?
{error.message}
: null} ) ``` -------------------------------- ### Configure Server-Side Tool Approval Source: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-tool-usage Set the needsApproval property to true within the tool definition to require user confirmation before execution. ```tsx import { streamText, tool } from 'ai'; __PROVIDER_IMPORT__; import { z } from 'zod'; export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: __MODEL__, messages, tools: { getWeather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ city: z.string(), }), needsApproval: true, execute: async ({ city }) => { const weather = await fetchWeather(city); return weather; }, }), }, }); return result.toUIMessageStreamResponse(); } ``` -------------------------------- ### Configure DirectChatTransport options Source: https://ai-sdk.dev/docs/ai-sdk-ui/transport Customizes stream output and data transmission for direct agent communication. ```tsx const transport = new DirectChatTransport({ agent, // Pass options to the agent options: { customOption: 'value' }, // Configure what's sent to the client sendReasoning: true, sendSources: true, }); ``` -------------------------------- ### Configure Request Options Source: https://ai-sdk.dev/docs/ai-sdk-ui/object-generation Set API endpoints, custom headers, and credentials within the useObject configuration object. ```tsx const { submit, object } = useObject({ api: '/api/use-object', headers: { 'X-Custom-Header': 'CustomValue', }, credentials: 'include', schema: yourSchema, }); ``` -------------------------------- ### Write RAG Sources Source: https://ai-sdk.dev/docs/ai-sdk-ui/streaming-data Use source type parts to reference documents or URLs in RAG implementations. ```tsx writer.write({ type: 'source', value: { type: 'source', sourceType: 'url', id: 'source-1', url: 'https://example.com', title: 'Example Source', }, }); ```