Your chat must get chunks from the server to the UI. The best way to do that depends on where your code runs: a web server, an edge runtime, a phone, or one process.
TanStack AI works with all of them. A connection adapter is the only part that touches the network. Chunk processing, messages, tool calls, and UI updates work the same on every transport.
Pass the adapter to useChat as connection:
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
const { messages, sendMessage } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});For the full server and client path, see Streaming.
| You have | Use | Page |
|---|---|---|
| A normal HTTP server | fetchServerSentEvents | Server-Sent Events |
| A runtime or proxy that blocks SSE | fetchHttpStream | HTTP Stream |
| A host or proxy that buffers or breaks streams, or you want one response per run | fetchJson | JSON |
| React Native or Expo | xhrHttpStream or xhrServerSentEvents | React Native |
| A function that returns an AsyncIterable<StreamChunk> synchronously (in-process chat(), an RSC stream, tests) | stream | Server Functions |
| An async function, such as a TanStack Start server function, that resolves to a Response or an AsyncIterable<StreamChunk> | fetcher | Server Functions |
| An RPC framework such as Cap'n Web, gRPC-Web, or tRPC | rpcStream | Server Functions |
| One long-lived, resumable WebSocket for many runs | webSocket | WebSockets |
| BroadcastChannel, postMessage, a shared worker, or a different persistent channel | Your own subscribe / send adapter | Custom Transports |
| SSE with a wrapped fetch (auth refresh, retries) | fetchServerSentEvents with fetchClient | Request Options |
| A different protocol, such as HTTP/3 | Your own connect adapter | Custom Transports |
All adapters produce the same StreamChunk events (AG-UI Protocol). The choice changes only the transport.
To send headers, a request body, or an auth token, see Request Options.
Every adapter gets an AbortSignal. The stop() function from useChat triggers the signal and aborts the active run:
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
const { stop } = useChat({ connection: fetchServerSentEvents("/api/chat") });
stop(); // aborts the active streamThe fetch adapters cancel the response body in these cases:
A response that gets to its normal end keeps all of its chunks.
Custom cancellation hooks do not delay errors or early iterator returns. The adapters release the reader lock, also when cancellation fails or stays pending.
An adapter throws on a transport error: an HTTP status that is not 2xx, a parse failure, or a dropped socket. The ChatClient catches the error. If no RUN_ERROR chunk was emitted yet, the client emits one. Then it shows the error through onError and the error state:
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
const { error } = useChat({
connection: fetchServerSentEvents("/api/chat"),
onError: (err) => console.error("Chat failed:", err),
});If the connection drops in the middle of a line, the stream adapters throw StreamTruncatedError.
Do not catch and hide an AbortError in a custom adapter. Let it propagate, so that the client knows the abort worked.
For the full type signatures, see API Reference: @tanstack/ai-client.
Pick a row in the table and send a message. The reply streams into messages on every transport.