useChat hook for conversational UI
The AI SDK's useChat hook enables building conversational interfaces. It handles streaming chat messages from AI providers, manages state for chat input, and automatically updates the UI as new messages are received. By default, useChat sends a POST request to the /api/chat endpoint with messages in the request body.
Render tool calls in chat UI
Example code to render tool calls and their inputs in the chat UI:
{m.parts.map(part => {
switch (part.type) {
case 'text':
return <p>{part.text}</p>;
case 'tool-addResource':
case 'tool-getInformation':
return (
<p>
call{part.state === 'output-available' ? 'ed' : 'ing'}{' '}
tool: {part.type}
<pre className="my-4 bg-zinc-100 p-2 rounded-sm">
{JSON.stringify(part.input, null, 2)}
</pre>
</p>
);
}
})}
Basic chat UI with useChat hook
Example component using useChat for conversational interface:
'use client';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
export default function Chat() {
const [input, setInput] = useState('');
const { messages, sendMessage } = useChat();
return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
<div className="space-y-4">
{messages.map(m => (
<div key={m.id} className="whitespace-pre-wrap">
<div>
<div className="font-bold">{m.role}</div>
{m.parts.map(part => {
switch (part.type) {
case 'text':
return <p>{part.text}</p>;
}
})}
</div>
</div>
))}
</div>
<form
onSubmit={e => {
e.preventDefault();
sendMessage({ text: input });
setInput('');
}}
>
<input
className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={e => setInput(e.currentTarget.value)}
/>
</form>
</div>
);
}
useChat hook implementation for Gemini 3 Pro chat UI
The following example shows how to use the useChat hook from @ai-sdk/react to build a chat interface with Gemini 3 Pro:
```tsx filename="app/page.tsx"
'use client';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
export default function Chat() {
const [input, setInput] = useState('');
const { messages, sendMessage } = useChat();
return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
{messages.map(message => (
<div key={message.id} className="whitespace-pre-wrap">
{message.role === 'user' ? 'User: ' : 'Gemini: '}
{message.parts.map((part, i) => {
switch (part.type) {
case 'text':
return <div key={`${message.id}-${i}`}>{part.text}</div>;
}
})}
</div>
))}
<form
onSubmit={e => {
e.preventDefault();
sendMessage({ text: input });
setInput('');
}}
>
<input
className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={e => setInput(e.currentTarget.value)}
/>
</form>
</div>
);
}
```
Accessing reasoning tokens in useChat messages
When using useChat hook with Claude 4, you can access the model's reasoning tokens via the reasoning part in message.parts. The reasoning text is available in the text property of the reasoning part. To forward reasoning tokens to the client, enable sendReasoning: true in the toUIMessageStream helper function.
useChat hook with DefaultChatTransport for Claude 4 chatbot
This example shows how to implement a chat interface in app/page.tsx using the useChat hook from '@ai-sdk/react' with DefaultChatTransport from 'ai'. The component manages input state with useState, creates useChat with transport pointing to '/api/chat' endpoint, and handles form submission by calling sendMessage with the input text. Messages are displayed in a scrollable container, mapping over message.parts to render different part types including 'text' and 'reasoning' parts. Reasoning parts are displayed in a collapsible details element.
```tsx
'use client';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useState } from 'react';
export default function Page() {
const [input, setInput] = useState('');
const { messages, sendMessage } = useChat({
transport: new DefaultChatTransport({ api: '/api/chat' }),
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (input.trim()) {
sendMessage({ text: input });
setInput('');
}
};
return (
<div className="flex flex-col h-screen max-w-2xl mx-auto p-4">
<div className="flex-1 overflow-y-auto space-y-4 mb-4">
{messages.map(message => (
<div
key={message.id}
className={`p-3 rounded-lg ${
message.role === 'user' ? 'bg-blue-50 ml-auto' : 'bg-gray-50'
}`}
>
<p className="font-semibold">
{message.role === 'user' ? 'You' : 'Claude 4'}
</p>
{message.parts.map((part, index) => {
if (part.type === 'text') {
return (
<div key={index} className="mt-1">
{part.text}
</div>
);
}
if (part.type === 'reasoning') {
return (
<pre
key={index}
className="bg-gray-100 p-2 rounded mt-2 text-xs overflow-x-auto"
>
<details>
<summary className="cursor-pointer">
View reasoning
</summary>
{part.text}
</details>
</pre>
);
}
})}
</div>
))}
</div>
<form onSubmit={handleSubmit} className="flex gap-2">
<input
name="prompt"
value={input}
onChange={e => setInput(e.target.value)}
className="flex-1 p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Ask Claude 4 something..."
/>
<button
type="submit"
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Send
</button>
</form>
</div>
);
}
```
AI SDK UI hooks for different interaction patterns
AI SDK UI provides four main hooks: useChat for real-time chat capabilities, useCompletion for text completions, and useObject for streamed JSON and interactive assistant features.
Handle reasoning and text parts in useChat messages
Messages returned from useChat have a parts array where each part has a type property. Type 'text' contains text output, and type 'reasoning' contains reasoning steps from extended thinking. Iterate over message.parts to render different content based on part.type.
Use useChat hook for real-time chat interface
The useChat hook from '@ai-sdk/react' enables real-time chat capabilities. Initialize it with a transport object: const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat' }) }). The hook returns messages array and sendMessage function to send new messages.
Chat UI example with useChat and reasoning display
Example of building a chat interface with useChat hook in Next.js (app/page.tsx): Import useChat from '@ai-sdk/react', DefaultChatTransport from 'ai', and useState. Initialize useChat with the chat API endpoint. In the render, map over messages and check each part's type: if part.type === 'text' render text, if part.type === 'reasoning' render reasoning in a pre tag. Handle form submission by calling sendMessage({ text: input }) and clearing input.
useChat hook for chat UI
The useChat hook from '@ai-sdk/react' provides messages and sendMessage functions. Messages contain id, role, and parts array. Parts can be of type 'text' with a text property. Call sendMessage({ text: input }) to send user messages.
useChat hook with Llama 3.1 frontend
Use the useChat hook from '@ai-sdk/react' on the frontend. The hook provides messages array and sendMessage function. Implement a form that calls sendMessage({text: input}) on submit. Map through messages and render each message's parts, checking if part.type is 'text' before displaying. The hook automatically sends requests to the chat API endpoint and streams responses in real-time.
streamUI for generative UI with Llama 3.1
Use streamUI from '@ai-sdk/rsc' to stream React components from server to client. Define tools with a generate async generator function that yields UI elements. Example getWeather tool yields a loading div, fetches data, then returns a weather display component. Tools can yield intermediate UI while working, then return final results as React components.
Next.js chat UI with useChat hook for o1
This example shows a Next.js chat UI using the useChat hook:
```tsx filename="app/page.tsx"
'use client';
import { useChat } from '@ai-sdk/react';
export default function Page() {
const { messages, input, handleInputChange, handleSubmit, error } = useChat();
return (
<>
{messages.map(message => (
<div key={message.id}>
{message.role === 'user' ? 'User: ' : 'AI: '}
{message.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input name="prompt" value={input} onChange={handleInputChange} />
<button type="submit">Submit</button>
</form>
</>
);
}
```
The useChat hook makes a request to the API endpoint whenever the user submits a message, and messages are displayed in the chat UI.
useChat hook with o3-mini in Next.js
This example shows how to build a chatbot UI in Next.js using the useChat hook:
```tsx
'use client';
import { useChat } from '@ai-sdk/react';
export default function Page() {
const { messages, input, handleInputChange, handleSubmit, error } = useChat();
return (
<>
{messages.map(message => (
<div key={message.id}>
{message.role === 'user' ? 'User: ' : 'AI: '}
{message.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input name="prompt" value={input} onChange={handleInputChange} />
<button type="submit">Submit</button>
</form>
</>
);
}
```
DeepSeek R1 chat UI with useChat hook
Update the root page (`app/page.tsx`) to use the `useChat` hook and display reasoning tokens:
```tsx
'use client';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
export default function Page() {
const [input, setInput] = useState('');
const { messages, sendMessage } = useChat();
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (input.trim()) {
sendMessage({ text: input });
setInput('');
}
};
return (
<>
{messages.map(message => (
<div key={message.id}>
{message.role === 'user' ? 'User: ' : 'AI: '}
{message.parts.map((part, index) => {
if (part.type === 'reasoning') {
return <pre key={index}>{part.text}</pre>;
}
if (part.type === 'text') {
return <span key={index}>{part.text}</span>;
}
return null;
})}
</div>
))}
<form onSubmit={handleSubmit}>
<input
name="prompt"
value={input}
onChange={e => setInput(e.target.value)}
/>
<button type="submit">Submit</button>
</form>
</>
);
}
```
Access model reasoning tokens through the `parts` array on the message object, where reasoning parts have `type: 'reasoning'`.
AI SDK UI hooks for interactive interfaces
The AI SDK UI provides three main hooks for building interactive AI-driven interfaces: useChat for real-time chat capabilities, useCompletion for text completions, and useObject for streamed JSON. These hooks simplify managing chat streams and UI updates on the frontend when building with frameworks like Next.js, Nuxt, and SvelteKit.
useChat hook with DeepSeek V3.2
```tsx
'use client';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
export default function Page() {
const [input, setInput] = useState('');
const { messages, sendMessage } = useChat();
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (input.trim()) {
sendMessage({ text: input });
setInput('');
}
};
return (
<>
{messages.map(message => (
<div key={message.id}>
{message.role === 'user' ? 'User: ' : 'AI: '}
{message.parts.map((part, index) => {
if (part.type === 'text' || part.type === 'reasoning') {
return <div key={index}>{part.text}</div>;
}
return null;
})}
</div>
))}
<form onSubmit={handleSubmit}>
<input
name="prompt"
value={input}
onChange={e => setInput(e.target.value)}
/>
<button type="submit">Submit</button>
</form>
</>
);
}
```
This example shows how to build a chat UI in Next.js using the useChat hook from @ai-sdk/react. Messages can include text or reasoning parts, which are displayed after being submitted to the chat API endpoint.
Client-side chat UI with message rendering
The following is a complete example of a Next.js client component for chat interaction:
```tsx
'use client';
import type { ModelMessage } from 'ai';
import { useState } from 'react';
export default function Page() {
const [input, setInput] = useState('');
const [messages, setMessages] = useState<ModelMessage[]>([]);
return (
<div>
<input
value={input}
onChange={event => {
setInput(event.target.value);
}}
onKeyDown={async event => {
if (event.key === 'Enter') {
setMessages(currentMessages => [
...currentMessages,
{ role: 'user', content: input },
]);
const response = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({
messages: [...messages, { role: 'user', content: input }],
}),
});
const { messages: newMessages } = await response.json();
setMessages(currentMessages => [
...currentMessages,
...newMessages,
]);
}
}}
/>
{messages.map((message, index) => (
<div key={`${message.role}-${index}`}>
{typeof message.content === 'string'
? message.content
: message.content
.filter(part => part.type === 'text')
.map((part, partIndex) => (
<div key={partIndex}>{part.text}</div>
))}
</div>
))}
</div>
);
}
```
This component uses React hooks to manage chat state, handles Enter key presses to submit messages, calls the /api/chat endpoint, and renders both user and assistant messages.
Client-side chat interface setup
Create a Next.js client component with useState to manage input and messages state. Use an input field with onKeyDown handler to detect Enter key, send messages to API endpoint, and update the messages state with both user and assistant responses. The input should be cleared after sending and the component should render message history with role-based display.
Chat message type with tools
Use type ChatMessage = UIMessage<never, never, ChatTools> to properly type messages in the client, where ChatTools is inferred from the server tools object using InferUITools<typeof tools>. This provides type safety for tool invocations and results in the UI.
useChat hook for image generation
Use useChat from '@ai-sdk/react' with a DefaultChatTransport configured with the api endpoint ('/api/chat'). The hook returns messages (UIMessage array) and sendMessage function. Messages contain parts array where each part has a type ('text' or 'tool-*') and tool invocations include state ('input-available' or 'output-available').
Render image tool results in chat UI
Check message.parts for type === 'tool-generateImage'. When state is 'input-available', show 'Generating image...' placeholder. When state is 'output-available', access part.output.image (base64 string) and part.input.prompt, then render using Next Image component with src formatted as 'data:image/png;base64,{output.image}'.
Client-side Enter key message submission
In the input field's onKeyDown handler, check if event.key equals 'Enter', then call sendMessage with the input text wrapped in a parts array with type 'text'. Clear the input after sending.
Client-side chat message handling with useChat
The useChat hook manages messages array containing the chat history. Messages are structured with parts array, where each part has a type (e.g., 'text') and content. The sendMessage function accepts an object with parts array to send user messages to the server.
Complete client-side streaming text generation example
'use client';
import { useCompletion } from '@ai-sdk/react';
export default function Page() {
const { completion, complete } = useCompletion({
api: '/api/completion',
});
return (
<div>
<div
onClick={async () => {
await complete('Why is the sky blue?');
}}
>
Generate
</div>
{completion}
</div>
);
}
This example shows a Next.js client component using the useCompletion hook to stream text generation from an API endpoint, displaying the generated text in real-time as it is received.
useCompletion hook from @ai-sdk/react
The useCompletion hook is imported from '@ai-sdk/react' and used in React client components to handle text generation. It takes a configuration object with an 'api' property that specifies the endpoint URL (e.g., '/api/completion'). The hook returns an object with 'completion' (the streamed text) and 'complete' (an async function that accepts a prompt string) properties.
useChat hook sends multimodal messages with parts array
The useChat hook from @ai-sdk/react includes a sendMessage function that accepts messages with a parts array. Each part can be of type 'text' or 'file', allowing multimodal content to be sent in a single message.
Render message parts by type in useChat
When rendering messages from useChat, iterate over m.parts and handle each part based on its type. For 'text' type, render part.text. For 'file' type, render an img element with src set to part.url and alt set to part.filename or a default value.
Stream text with image prompt complete example
This example shows how to stream text responses when a user sends both an image URL and text prompt using the AI SDK with Next.js. The server uses convertToModelMessages to handle multimodal content from UIMessages, calls streamText with the model 'openai/gpt-4.1', and wraps the stream with createUIMessageStreamResponse and toUIMessageStream. The client uses useChat to send messages with a parts array containing both file (image) and text parts, conditionally including the image only if an imageUrl is provided. Messages are rendered by iterating over parts and switching on type to render text or img elements.
Text part structure in useChat messages
Text parts in useChat messages are structured with: type set to 'text' (as const) and text set to the text string.
Example: useChat hook client for caching middleware demo
import { useChat } from '@ai-sdk/react';
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit, error } = useChat();
if (error) return <div>{error.message}</div>;
return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
<div className="space-y-4">
{messages.map(m => (
<div key={m.id} className="whitespace-pre-wrap">
<div>
<div className="font-bold">{m.role}</div>
{m.toolInvocations ? (
<pre>{JSON.stringify(m.toolInvocations, null, 2)}</pre>
) : (
<p>{m.content}</p>
)}
</div>
</div>
))}
</div>
<form onSubmit={handleSubmit}>
<input
className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={handleInputChange}
/>
</form>
</div>
);
}
This example shows a basic chat interface using useChat hook to stream responses.
streamText multi-step client implementation
Example showing multi-step streaming client using useChat hook. Displays messages with parts array handling text and tool-extractGoal types. Renders text parts as spans and tool parts as JSON. Handles form submission by calling sendMessage with text input.
Install dependencies for markdown rendering
Install react-markdown and marked packages to enable Markdown parsing and rendering: npm install react-markdown marked
Markdown memoization prevents re-parsing on each token
When streaming responses with Markdown, the entire conversation history gets re-rendered on each token update. This causes exponential performance degradation for long conversations. Memoization caches parsed Markdown blocks and reuses them, eliminating redundant parsing and rendering operations after a block is fully parsed.
Parse Markdown into blocks using marked.lexer
The marked library's lexer method tokenizes Markdown content into discrete elements. Each token has a raw property containing the original Markdown source. This allows you to split the full Markdown into individual blocks that can be memoized separately.
MemoizedMarkdownBlock component with custom comparison
```tsx
const MemoizedMarkdownBlock = memo(
({ content }: { content: string }) => {
return <ReactMarkdown>{content}</ReactMarkdown>;
},
(prevProps, nextProps) => {
if (prevProps.content !== nextProps.content) return false;
return true;
},
);
MemoizedMarkdownBlock.displayName = 'MemoizedMarkdownBlock';
```
MemoizedMarkdown component structure
The MemoizedMarkdown component accepts content as a string and an id. It uses useMemo to parse the Markdown into blocks only when content changes. It then maps over the blocks, rendering each in a separate MemoizedMarkdownBlock with a key of format `${id}-block_${index}`.
Use throttle option in useChat to reduce re-renders
Pass a throttle option to useChat to throttle data updates to a specified interval. This helps manage rendering performance by reducing the frequency of re-renders when receiving streaming tokens.
Share Chat instance between components for synchronized state
Create a single Chat instance with DefaultChatTransport and pass it to multiple useChat hooks. This allows you to split UI components (like MessageInput) from message display components while keeping their state synchronized without prop drilling.
Message parts iteration pattern for streaming responses
Iterate through message.parts array, checking part.type to handle different content types. For text parts, access the text content via part.text. This allows rendering different content types appropriately within each message.
Message rendering with type-based part handling
Messages returned from useChat contain a parts array where each part has a type. Text parts have type 'text' with a text property. Tool calls have type matching the tool name (e.g., 'tool-getLocation'). Rendering should switch on part.type to handle different part kinds appropriately. Tool parts can be JSON stringified for display.
useChat hook with DefaultChatTransport for tool calling
The useChat hook from @ai-sdk/react can be configured with a DefaultChatTransport to call an API endpoint. The transport takes an api parameter specifying the endpoint URL. Messages are sent using the sendMessage method which accepts an object with a text property. The hook returns messages which contain parts that can be text or tool calls (identified by type like 'tool-getLocation'). Messages should be typed with a ChatMessage type parameter for the useChat generic.
Handling undefined values in streamed objects
When rendering streamed objects on the client, use optional chaining (?.) to handle undefined values during partial updates. For example, use notification?.name and notification?.message instead of direct property access, since the object fields are populated incrementally as the stream receives data.
Example: useObject hook with loading and stop
Client code: const { object, submit, isLoading, stop } = useObject({ api: '/api/use-object', schema: notificationSchema, }); return ( <div> <button onClick={() => submit('Messages during finals week.')} disabled={isLoading}> Generate notifications </button> {isLoading && <div>Loading...<button onClick={() => stop()}>Stop</button></div>} {object?.notifications?.map((notification, index) => (<div key={index}><p>{notification?.name}</p><p>{notification?.message}</p></div>))} </div> );
Example: streaming array with Output.array
Server code: import { streamText, Output, createTextStreamResponse, toTextStream } from 'ai'; export const maxDuration = 30; export async function POST(req: Request) { const context = await req.json(); const result = streamText({ model: 'openai/gpt-4.1', output: Output.array({ element: notificationSchema }), prompt: `Generate 3 notifications for a messages app in this context:` + context, }); return createTextStreamResponse({ stream: toTextStream({ stream: result.stream }), }); } Client code: const { object, submit, isLoading, stop } = useObject({ api: '/api/use-object', schema: z.array(notificationSchema), }); {object?.map((notification, index) => (<div key={index}><p>{notification?.name}</p><p>{notification?.message}</p></div>))}
useObject hook configuration and API
The useObject hook from @ai-sdk/react accepts an object with the following properties: api (string, the endpoint to call), schema (Zod schema for validation). It returns an object containing: object (the partial or complete generated object), submit (function to trigger generation), isLoading (boolean indicating if generation is in progress), and stop (function to stop generation).
Client-side MCP completion with useCompletion hook
Use the useCompletion hook from @ai-sdk/react in a React component. Call complete(prompt) to invoke the /api/completion endpoint, which handles MCP tools server-side. The hook returns completion state and a complete function. Example: const { completion, complete } = useCompletion({ api: '/api/completion' }).
Share chat instance with useSharedChatContext hook
Use the useSharedChatContext() hook to access the shared chat instance and clearChat function from any component within the ChatProvider. The hook throws an error if called outside of a ChatProvider. In components, call const { chat, clearChat } = useSharedChatContext() to get access to the shared state.
Create Chat Context with ChatProvider
Create a React context to hold a Chat instance and provide methods to interact with it. The ChatContext holds a Chat<UIMessage> object and a clearChat function. The ChatProvider component initializes the chat using createChat() and exposes useSharedChatContext() hook to access the context. The ChatProvider must wrap the app in layout.tsx to make the chat context available to all child components.
Chat Context interface structure
The ChatContextValue interface defines two properties: chat (type Chat<UIMessage>) holds the chat instance, and clearChat (a function with no parameters returning void) resets the chat by creating a new Chat instance.
DefaultChatTransport configuration
DefaultChatTransport takes an api parameter pointing to the chat endpoint. Initialize it as new DefaultChatTransport({ api: '/api/chat' }) when creating a new Chat instance.
Display messages from shared chat with useChat
In a component consuming the shared chat context, call useChat({ chat }) passing the chat instance from useSharedChatContext(). This returns messages and other state. Map over messages and access message.id, message.role, and message.parts (array where each part has type and text properties for text parts).
Send messages from input component
In a separate component, call useChat({ chat }) with the shared chat instance. Call sendMessage({ text }) to send a message. The hook returns status ('ready', 'streaming', 'submitted'), stop function to cancel streaming, and sendMessage function to submit messages.
Clear chat button implementation
Add a button that calls clearChat() to reset the chat state. Disable the button when messages.length === 0 to prevent clearing an already-empty chat.
Input form with stop button
Create a form that submits with sendMessage({ text }). The input should be disabled when status !== 'ready'. Show a Stop button when stop exists and status is 'streaming' or 'submitted'. Call stop() to cancel the stream. Clear the input field after sending by calling setText('').
Complete example: shared chat context implementation
This example shows three components: (1) app/chat-context.tsx exports ChatProvider wrapping the app and useSharedChatContext hook to access the shared Chat instance; (2) app/layout.tsx wraps children with ChatProvider; (3) app/page.tsx displays messages and clear button; (4) app/chat-input.tsx handles message input and sending; (5) app/api/chat/route.ts handles streaming responses on the server.
lastAssistantMessageIsCompleteWithApprovalResponses for auto-submit
Use `sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses` in the `useChat` hook configuration to automatically send the message after all tool approvals in the last assistant message have been responded to. Without this, you must manually call `sendMessage()` after each approval.
useChat with custom request body to send only last message
This example demonstrates sending only the last message text to the server using prepareSendMessagesRequest. The client imports useChat from '@ai-sdk/react' and DefaultChatTransport from 'ai', then configures the transport with a function that returns an object containing the message ID and only the last message from the messages array. The server receives this custom format, loads the full message history from storage, appends the new message, and streams the response back.
prepareSendMessagesRequest option customizes request body
The prepareSendMessagesRequest option in useChat allows you to customize the entire body content sent to the server. The function receives the message list, request data, and request body from the append call, and must return the body content that will be sent to the server.