Use cases for custom useChat request body
Custom request bodies in useChat are useful for: only sending the last message to reduce data transferred, sending additional data alongside messages, and changing the structure of the request body to match specific server requirements.
Server-side handling of custom request body from useChat
When receiving a custom request body from useChat with prepareSendMessagesRequest, the server must parse the custom format, load any required message history from storage, combine it with the new message, and pass the complete message list to streamText. Use convertToModelMessages to prepare messages for the model, and wrap the result with createUIMessageStreamResponse and toUIMessageStream to send the response back to the client.
DefaultChatTransport with prepareSendMessagesRequest configuration
DefaultChatTransport accepts a configuration object with prepareSendMessagesRequest as a key. The prepareSendMessagesRequest function receives an object with 'id' and 'messages' properties. It should return an object with a 'body' property containing the custom request body to send to the server.
Use useChat hook for type-safe agent messages
Import useChat from '@ai-sdk/react' and pass AgentUIMessage as a generic to get type-safe access to messages including typed tool invocations and results. Call useChat() to get messages array and sendMessage function. Access message parts via m.parts array where each part has a type property.
Tool confirmation pattern with Yes/No buttons
For tools requiring user confirmation like askForConfirmation, render Yes and No buttons that call addToolOutput with the respective confirmation message. After confirmation is provided, state changes to 'output-available' and the confirmation result is displayed.
onToolCall handler for client-side tool execution
The onToolCall callback in useChat handles client-side tool execution. It receives a toolCall object containing toolName and toolCallId. Do not await operations in onToolCall to avoid potential deadlocks; instead, call addToolOutput asynchronously.
DefaultChatTransport for client-side chat API communication
The DefaultChatTransport class from the ai SDK is used to configure client-side chat communication with an API endpoint. It accepts an api parameter specifying the endpoint URL, such as '/api/chat'.
lastAssistantMessageIsCompleteWithToolCalls automatic send behavior
The sendAutomaticallyWhen parameter in useChat can be set to lastAssistantMessageIsCompleteWithToolCalls to automatically send tool outputs after all tool calls in an assistant message are complete.
Rendering tool output parts in chat messages
Messages in useChat have a parts array where each part can be of type 'text' or 'tool-{toolName}'. Tool parts have a type, toolCallId, state (either 'output-available' or pending), and an output field containing the tool's result.
Complete client-side weather chat implementation
This example shows a complete Chat component that uses useChat with DefaultChatTransport to communicate with an API, handles onToolCall for client-side tool execution (getLocation), and renders different UI components based on tool types: text parts, weather information with temperature and weekly forecast, location display, and confirmation buttons for askForConfirmation tool.
InferUITools and UIMessage type exports
The ai SDK exports InferUITools<typeof tools> to infer the tool types from a tool set, and UIMessage<never, UIDataTypes, ChatTools> to type chat messages that include UI tool rendering capabilities.
Rendering pending vs completed tool states
Tool parts have a state property: when state is 'output-available', the output field contains the result and should be rendered; otherwise, the tool is pending and a loading message like 'Calling {toolName}...' should be displayed.
Client-side Server-Sent Events parsing
On the client, use fetch to request the streaming endpoint, then use response.body.getReader() to read chunks. Decode chunks with TextDecoder, accumulate data in a buffer, split by newlines, remove the 'data: ' prefix from each line, and parse the remaining JSON to reconstruct StreamEvent objects.
Handle streaming buffer for incomplete events
When parsing Server-Sent Events on the client, maintain a buffer to handle incomplete lines that arrive across multiple read() calls. Split the accumulated text by newlines, keep the incomplete trailing portion in the buffer for the next iteration, and only process complete lines.
Custom streaming example: client component
```tsx
'use client';
import { useState } from 'react';
import { StreamEvent } from './api/stream/route';
export default function Home() {
const [prompt, setPrompt] = useState('');
const [events, setEvents] = useState<StreamEvent[]>([]);
const [isStreaming, setIsStreaming] = useState(false);
const handleSubmit = async () => {
setEvents([]);
setIsStreaming(true);
setPrompt('');
const response = await fetch('/api/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt }),
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
if (reader) {
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.trim()) {
const dataStr = line.replace(/^data: /, '');
const event = JSON.parse(dataStr) as StreamEvent;
setEvents(prev => [...prev, event]);
}
}
}
}
setIsStreaming(false);
};
return (
<div>
<input
value={prompt}
onChange={e => setPrompt(e.target.value)}
placeholder="Enter a prompt..."
/>
<button onClick={handleSubmit} disabled={isStreaming}>
{isStreaming ? 'Streaming...' : 'Send'}
</button>
<pre>{JSON.stringify(events, null, 2)}</pre>
</div>
);
}
```
This example shows a Next.js client component that fetches and parses custom-formatted streaming events from the server.
readStreamableValue hook pattern in RSC client
Use `readStreamableValue` from `@ai-sdk/rsc` to consume streamed values on the client side. Iterate through the stream with a for-await loop to process each delta chunk. Example: `for await (const delta of readStreamableValue(output)) { setGeneration(current => current + delta); }`
readStreamableValue hook for streaming objects
The readStreamableValue function from @ai-sdk/rsc is used on the client to iterate over partial objects from a stream. It is used in a for-await loop to handle partial object updates as they arrive from the server.
Read streamable values on client with readStreamableValue
On the client, import `readStreamableValue` from '@ai-sdk/rsc' and iterate over the streamable value with `for await` to receive text deltas as they arrive. Build up the complete text by concatenating each delta.
Stream text with chat prompt example
Server action using streamText:
```typescript
'use server';
import { streamText } from 'ai';
import { createStreamableValue } from '@ai-sdk/rsc';
export interface Message {
role: 'user' | 'assistant';
content: string;
}
export async function continueConversation(history: Message[]) {
'use server';
const stream = createStreamableValue();
(async () => {
const { textStream } = streamText({
model: 'openai/gpt-5.4',
system:
"You are a dude that doesn't drop character until the DVD commentary.",
messages: history,
});
for await (const text of textStream) {
stream.update(text);
}
stream.done();
})();
return {
messages: history,
newMessage: stream.value,
};
}
```
Client component using readStreamableValue:
```tsx
'use client';
import { useState } from 'react';
import { Message, continueConversation } from './actions';
import { readStreamableValue } from '@ai-sdk/rsc';
export const maxDuration = 30;
export default function Home() {
const [conversation, setConversation] = useState<Message[]>([]);
const [input, setInput] = useState<string>('');
return (
<div>
<div>
{conversation.map((message, index) => (
<div key={index}>
{message.role}: {message.content}
</div>
))}
</div>
<div>
<input
type="text"
value={input}
onChange={event => {
setInput(event.target.value);
}}
/>
<button
onClick={async () => {
const { messages, newMessage } = await continueConversation([
...conversation,
{ role: 'user', content: input },
]);
let textContent = '';
for await (const delta of readStreamableValue(newMessage)) {
textContent = `${textContent}${delta}`;
setConversation([
...messages,
{ role: 'assistant', content: textContent },
]);
}
}}
>
Send Message
</button>
</div>
</div>
);
}
```
useChat import paths
Import useChat from '@ai-sdk/react' for React, Chat from '@ai-sdk/svelte' for Svelte, useChat from '@ai-sdk/vue' for Vue, and Chat from '@ai-sdk/angular' for Angular.
useChat major changes in AI SDK 5.0
AI SDK 5.0 significantly updated the useChat API. It now uses a transport-based architecture and no longer manages input state internally.
useChat parameters: chat and transport
useChat accepts optional 'chat' parameter of type Chat<UIMessage> for using an existing Chat instance (ignores other parameters if provided), and optional 'transport' parameter of type ChatTransport for specifying how messages are sent. DefaultChatTransport defaults to /api/chat endpoint.
DefaultChatTransport configuration options
DefaultChatTransport accepts: api (string = '/api/chat', optional), credentials (RequestCredentials, optional), headers (Record<string, string> | Headers, optional), body (object, optional for extra body properties), fetch (FetchFunction, optional for custom implementation or middleware), prepareSendMessagesRequest (PrepareSendMessagesRequest, optional to customize requests before chat API calls), and prepareReconnectToStreamRequest (PrepareReconnectToStreamRequest, optional to customize reconnect requests).
PrepareSendMessageRequestOptions structure
PrepareSendMessageRequestOptions includes: id (string, the chat ID), messages (UIMessage[], current messages), requestMetadata (unknown), body (Record<string, any> | undefined), credentials (RequestCredentials | undefined), headers (HeadersInit | undefined), api (string, defaults to transport's /api/chat), trigger ('submit-message' | 'regenerate-message'), and messageId (string | undefined).
PrepareReconnectToStreamRequestOptions structure
PrepareReconnectToStreamRequestOptions includes: id (string, the chat ID), requestMetadata (unknown), body (Record<string, any> | undefined), credentials (RequestCredentials | undefined), headers (HeadersInit | undefined), and api (string, defaults to /api/chat/{chatId}/stream).
useChat additional parameters
useChat accepts: id (string, optional, auto-generated if not provided), messages (UIMessage[], optional initial messages), messageMetadataSchema (FlexibleSchema, optional for validating message metadata), dataPartSchemas (UIDataTypesToSchemas, optional for validating data parts), generateId (IdGenerator, optional function for generating unique IDs, defaults to AI SDK generateId), onToolCall ({toolCall: ToolCall} => void | Promise<void>, optional callback when tool call received), sendAutomaticallyWhen ((options: {messages: UIMessage[]}) => boolean | PromiseLike<boolean>, optional to resubmit messages when stream finishes or tool call added), onFinish (OnFinishOptions => void, optional callback when assistant response finishes), onError (Error => void, optional error callback), onData (DataUIPart => void, optional callback when data part received), throttle (number, optional ms for throttling updates, undefined disables throttling), and resume (boolean, optional to resume ongoing generation stream, defaults to false).
OnFinishOptions callback structure
OnFinishOptions includes: message (UIMessage, the response message), messages (UIMessage[], all messages including response), isAbort (boolean, true if aborted by client), isDisconnect (boolean, true if server disconnected), isError (boolean, true if errors stopped response), and finishReason (optional 'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other', undefined if not provided by model).
useChat returns: id and messages
useChat returns id (string, the chat ID) and messages (UIMessage[] array of current chat messages).
UIMessage structure
UIMessage includes: id (string, unique identifier), role ('system' | 'user' | 'assistant'), parts (UIMessagePart[], use for rendering in UI), and metadata (unknown, optional).
useChat returns: status
useChat returns status of type 'submitted' | 'streaming' | 'ready' | 'error'. Ready means idle, submitted means request sent, streaming means receiving response, error means request failed.
useChat returns: error
useChat returns error (Error | undefined, the error object if an error occurred).
sendMessage function signature
sendMessage(message?: { text: string; files?: FileList | FileUIPart[]; metadata?; messageId?: string } | CreateUIMessage, options?: ChatRequestOptions) => Promise<void>. Sends a new message or, if messageId provided, replaces the message (for editing). If no message provided, resubmits current messages (useful after adding tool outputs).
ChatRequestOptions structure
ChatRequestOptions includes: headers (Record<string, string> | Headers, optional additional headers), body (object, optional additional body JSON properties), and metadata (unknown, optional additional data for API endpoint).
regenerate function
regenerate(options?: { messageId?: string } & ChatRequestOptions) => Promise<void>. Regenerates the last assistant message or a specific message if messageId provided. Accepts ChatRequestOptions for headers, body, and metadata.
useChat streaming control methods
useChat provides: stop() to abort current streaming response, clearError() to clear error state, and resumeStream() to resume interrupted streaming (useful for network errors).
setMessages function
setMessages(messages: UIMessage[] | ((messages: UIMessage[]) => UIMessage[])) => void. Updates messages state locally without triggering API call. Useful for optimistic updates.