- MCP Server ToolsUpdated
- Managed MCP with chat()Updated
- Manual MCPUpdated
- MCP Type GenerationUpdated
- MCP Client InputUpdated
- MCP Server Tools: AdvancedNew
- MCP EventsNew
- MCP AppsUpdated
- Serve Tools over HTTPUpdated
- Serve Resources and PromptsUpdated
- Ask for InputUpdated
- Draft Text from a ToolUpdated
- MCP Server TasksUpdated
- MCP Server AuthUpdated
- MCP Server SessionsUpdated
- MCP Server on stdioUpdated
Persistence (Advanced)
- GenerationsUpdated
- Realtime Voice ChatUpdated
- Text-to-SpeechUpdated
- TranscriptionUpdated
- Transcription: AdvancedNew
- Voice CreationUpdated
- Audio RecordingUpdated
- Audio GenerationUpdated
- Image GenerationUpdated
- Image Generation: AdvancedNew
- Video GenerationUpdated
- Video Generation: AdvancedNew
- Live GenerationUpdated
- World GenerationUpdated
- Generation HooksUpdated
- OverviewUpdated
- Quick StartUpdated
- ProvidersUpdated
- HarnessesUpdated
- Harness AuthUpdated
- WorkspaceUpdated
- ToolsUpdated
- PolicyUpdated
- Lifecycle & SnapshotsUpdated
- Portable SnapshotsUpdated
- Keep Files After ReloadUpdated
- Save a Named VersionUpdated
- Branch From a VersionUpdated
- Send a Frozen FileUpdated
- Let the Agent Save and ForkUpdated
- Pick Which Files to KeepUpdated
- What a Snapshot StoresUpdated
- Instance DurabilityUpdated
- Durable RunsUpdated
- EventsUpdated
Sandboxes (Advanced)
- Runtime ContextUpdated
- Bring Your Own KeyUpdated
- Debug LoggingUpdated
- Multimodal ContentUpdated
- Files APIUpdated
- Per-Model Type SafetyUpdated
- Runtime Adapter SwitchingUpdated
- Tree-ShakingUpdated
- Extend AdapterUpdated
- Extend Adapter: AdvancedNew
- Typed Pre-Configured OptionsUpdated
- Test with a Fake ModelUpdated
- Prompt CachingUpdated
- Mid-Conversation ChangesUpdated
- Approval Flow ProcessingUpdated
Server Functions
Pass a server function to useChat when the client can call that function directly. Pick the helper from the return value:
| Your function | Use |
|---|---|
| Returns a Promise, such as a TanStack Start server function | fetcher |
| Returns an AsyncIterable<StreamChunk> synchronously (in-process chat(), an RSC stream, tests) | stream() |
| Is an RPC method that returns an async iterable | rpcStream() |
All three become the same request-scoped transport. So stop(), abort, errors, and tool calls work the same.
Quick setup#
This setup uses a TanStack Start server function.
1. Write the server function#
End the server function with toServerSentEventsResponse(...). It resolves to a Response:
// server/chat.server.ts
import { createServerFn } from "@tanstack/react-start";
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import type { UIMessage } from "@tanstack/ai";
export const chatFn = createServerFn({ method: "POST" })
.inputValidator((data: { messages: Array<UIMessage>; threadId: string }) => data)
.handler(({ data }) =>
toServerSentEventsResponse(
chat({
adapter: openaiText("gpt-6.1-sol"),
messages: data.messages,
threadId: data.threadId,
}),
),
);2. Pass it to useChat as fetcher#
Use the top-level fetcher option, not connection:
import { useChat } from "@tanstack/ai-react";
import { chatFn } from "./server/chat.server";
export function Chat() {
const { messages, sendMessage } = useChat({
fetcher: ({ messages, threadId }, { signal }) =>
chatFn({ data: { messages, threadId }, signal }),
threadId: "support-chat",
});
return (
<div>
{messages.map((message) => (
<p key={message.id}>
{message.parts.map((part) =>
part.type === "text" ? part.content : "",
)}
</p>
))}
<button onClick={() => void sendMessage("Hello")}>Send</button>
</div>
);
}Click Send. The reply streams into messages, and your app has no chat route of its own.
How it works#
- sendMessage calls your fetcher with the messages and an AbortSignal.
- The server function runs chat() and returns an SSE Response.
- The chat client parses the SSE body, the same as with fetchServerSentEvents.
The choice of helper depends on whether your function is sync or async. Both stream() and fetcher can give back an AsyncIterable<StreamChunk>. A server function returns a Promise, so it does not type-check in stream().
In-process streams#
stream() takes a factory that returns an AsyncIterable<StreamChunk> synchronously. It connects that iterable straight to the client.
On the server side, read the run input with chatParamsFromRequestBody, then stream chat():
// server/chat.server.ts
import { chat, chatParamsFromRequestBody } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
export async function* chatServerFn(input: unknown) {
const { messages, threadId, runId, resume } =
await chatParamsFromRequestBody(input);
yield* chat({
adapter: openaiText("gpt-6.1-sol"),
messages,
threadId,
runId,
resume,
});
}Then pass the factory to stream(). The 4th argument is the run input:
import { useChat, stream } from "@tanstack/ai-react";
import { chatServerFn } from "./server/chat.server";
export function useInProcessChat() {
return useChat({
connection: stream((_messages, _data, _signal, input) =>
chatServerFn(input),
),
threadId: "support-chat",
});
}The factory gets these arguments:
- messages: the conversation messages.
- data: the per-request data that you passed to sendMessage.
- signal: fires on stop().
- input: the AG-UI run input, the same body the HTTP adapters send. It holds threadId, runId, resume, the client tools, and forwardedProps.
Return any async iterable that yields StreamChunk objects: a generator, the output of chat(), or a transformed stream.
stream() is request-scoped. For each sendMessage, it calls the factory once, runs the iterable to its end, and closes the connection. For one long-lived channel that carries many sends, see Custom Transports.
RPC clients with rpcStream#
rpcStream() works the same as stream(). The name reads better when you call an RPC client. Use it with Cap'n Web, gRPC-Web, tRPC subscriptions, or any RPC framework that returns an async iterable:
import { useChat, rpcStream } from "@tanstack/ai-react";
import { api } from "./rpc-client";
// `api.chat.stream` is your RPC method. It must return an AsyncIterable<StreamChunk>.
export function useRpcChat() {
return useChat({
connection: rpcStream((_messages, _data, _signal, input) =>
api.chat.stream(input),
),
threadId: "support-chat",
});
}On the server, read input with chatParamsFromRequestBody, the same as the stream() server function above.
Advanced#
Async functions with fetcher#
Give useChat exactly one of fetcher or connection. The option works like fetcher on the generation hooks.
The fetcher gets two arguments:
- Input: { messages, data, threadId, runId }. A child run also gets parentRunId. A resumed run also gets resume.
- Options: { signal, headers }. The signal fires on stop(), or when a new send replaces the active one. headers holds extra request headers for this run, such as BYOK keys.
Return one of these:
- A Response: the chat client parses its SSE body for you.
- An AsyncIterable<StreamChunk>: the client reads it directly. Use this when your server function returns the stream itself, not a Response.
The fetcher can return the value directly or in a Promise.
Persistence handlers#
stream() and rpcStream() take an optional second argument with persistence handlers. With them, server-driven persistence (persistence: true) works with no HTTP endpoint. Each one is usually a one-line call into your server:
- hydrate: restores a chat thread.
- hydrateGeneration: restores the last run of a generation.
- joinRun: replays a run that is still in flight.
The generation hooks (useGenerateImage and the others) also accept hydrateGeneration and joinRun options next to their fetcher. So persistence: true hydrates and rejoins through server functions too.
For the full wiring, see Generation Persistence: Server functions / direct.