Class References
Function References
- text
- textOptions
- combineStrategies
- convertMessagesToModelMessages
- convertZodToJsonSchema
- createReplayStream
- embedding
- generateMessageId
- maxIterations
- messages
- modelMessageToUIMessage
- modelMessagesToUIMessages
- normalizeToUIMessage
- parsePartialJSON
- summarize
- toServerSentEventsStream
- toServerSentEventsResponse
- toolDefinition
- uiMessageToModelMessages
- realtimeToken
- untilFinishReason
Interface References
- AIAdapter
- AIAdapterConfig
- AgentLoopState
- ApprovalRequestedStreamChunk
- AudioPart
- AudioVisualization
- BaseStreamChunk
- TextCompletionChunk
- TextOptions
- ChunkRecording
- ChunkStrategy
- ClientTool
- ContentPartSource
- ContentStreamChunk
- DefaultMessageMetadataByModality
- DocumentPart
- DoneStreamChunk
- EmbeddingOptions
- EmbeddingResult
- ErrorStreamChunk
- ImagePart
- InternalToolCallState
- JSONParser
- ModelMessage
- ProcessorResult
- ProcessorState
- RealtimeAdapter
- RealtimeClientOptions
- RealtimeConnection
- RealtimeMessage
- RealtimeSessionConfig
- RealtimeToken
- RealtimeTokenAdapter
- RealtimeTokenOptions
- ResponseFormat
- ServerTool
- StreamProcessorEvents
- StreamProcessorHandlers
- StreamProcessorOptions
- SummarizationOptions
- SummarizationResult
- TextPart
- ThinkingPart
- ThinkingStreamChunk
- Tool
- ToolCall
- ToolCallPart
- ToolCallStreamChunk
- ToolConfig
- ToolDefinition
- ToolDefinitionConfig
- ToolDefinitionInstance
- ToolInputAvailableStreamChunk
- ToolResultPart
- ToolResultStreamChunk
- UIMessage
- VideoPart
Type Alias References
- AgentLoopStrategy
- AnyClientTool
- TextStreamOptionsForModel
- TextStreamOptionsUnion
- ConstrainedContent
- ConstrainedModelMessage
- ContentPart
- ContentPartForModalities
- ExtractModalitiesForModel
- ExtractModelsFromAdapter
- InferToolInput
- InferToolName
- InferToolOutput
- MessagePart
- RealtimeEvent
- RealtimeMessagePart
- RealtimeMode
- RealtimeStatus
- ModalitiesArrayToUnion
- Modality
- StreamChunk
- StreamChunkType
- ToolCallState
- ToolResultState
Variable References
@tanstack/ai-react
React hooks for TanStack AI, providing convenient React bindings for the headless client.
For a typed headless chat UI, see React Chat UI. For React Native, the documented support surface is narrow: useChat with chat connection adapters. React DOM-specific UI packages and TanStack AI devtools UI are not part of the React Native support surface.
For a complete native journey, see Quick Start: React Native.
Installation#
npm i @tanstack/ai-reactpnpm add @tanstack/ai-reactyarn add @tanstack/ai-reactbun add @tanstack/ai-reactuseRegisterWebMCPTools(tools, options?)#
Register executable client tools after the React component mounts. React removes them on cleanup and replaces them when tools or options change.
For a complete setup and behavior guide, see WebMCP Tools.
import {
useRegisterWebMCPTools,
type UseRegisterWebMCPToolsOptions,
} from "@tanstack/ai-react";
import { searchProducts } from "./tools";
const tools = [searchProducts];
const options: UseRegisterWebMCPToolsOptions<typeof tools> = {
onError(error) {
console.error(error);
},
};
function ProductsPage() {
useRegisterWebMCPTools(tools, options);
return null;
}UseRegisterWebMCPToolsOptions<TTools, TContext> contains toolOptions, context, and onError. The hook owns the registration signal.
The context field is required when a tool declares a required runtime context. Keep tools and options stable when their values do not change.
usePageWebMCPTools(options?)#
Read the WebMCP tools on the page as client tools. The array starts empty and updates when the page adds or removes a tool. Pass it to useChat as tools.
import { usePageWebMCPTools } from "@tanstack/ai-react";
export function useSameOriginPageTools() {
return usePageWebMCPTools({
filter: (tool) => tool.origin === location.origin,
});
}filter skips a tool when it returns false. onError gets a failed WebMCP read. For a complete guide, see Page WebMCP Tools in Chat.
createChatHook(options)#
Bind chatOptions once at module scope. Call useChat() in the screen to create the instance. Per-call overrides may set threadId, initialMessages, live, and forwardedProps. They must not change tools, interrupts, or outputSchema.
import { createChatHook, fetchServerSentEvents } from "@tanstack/ai-react";
const chatOptions = {
connection: fetchServerSentEvents("/api/chat"),
};
const { useChat } = createChatHook(chatOptions);
function ChatScreen() {
const chat = useChat({ threadId: "support-1" });
return null;
}useChat(chatOptions) from this package still works when you want to pass the full object at the call site. Rename the bound hook if both are in one file: const { useChat: useSupportChat } = createChatHook(chatOptions).
useChat(options?)#
Main hook for managing chat state in React with full type safety.
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
import {
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
import { useState } from "react";
const updateUIDef = toolDefinition({
name: "updateUI",
description: "Update the UI with a notification",
inputSchema: z.object({
message: z.string(),
}),
outputSchema: z.object({ success: z.boolean() }),
});
function ChatComponent() {
const [notification, setNotification] = useState<string | null>(null);
// Create client tool implementations
const updateUI = updateUIDef.client((input) => {
setNotification(input.message);
return { success: true };
});
// Create typed tools array (no 'as const' needed!)
const tools = [updateUI];
const chatOptions = createChatClientOptions({
connection: fetchServerSentEvents("/api/chat"),
tools,
});
// Fully typed messages!
type ChatMessages = InferChatMessages<typeof chatOptions>;
const { messages, sendMessage, isLoading, error, addToolApprovalResponse } =
useChat(chatOptions);
return <div>{/* Chat UI with typed messages */}</div>;
}Options#
Extends ChatClientOptions from @tanstack/ai-client:
- connection - Connection adapter (required)
- tools? - Array of client tool implementations (with .client() method)
- initialMessages? - Initial messages array
- threadId? - The only identity for this chat. Required when persistence is on. If omitted, minted after mount.
- forwardedProps? - Arbitrary client-controlled JSON forwarded to the server in the AG-UI RunAgentInput.forwardedProps field (e.g., { provider: 'openai', model: 'gpt-5.5' })
- body? - Deprecated. Use forwardedProps instead. Still works for backward compatibility; values are merged into forwardedProps on the wire
- byok? - Optional BYOK keyring from defineByok. On each send the client prepares the resolved provider and stamps x-byok-* request headers. Keys never go in the body
- byokProvider? - Optional function that returns the provider slug for this chat. If it returns a slug, only that key is prepared and sent. Otherwise the merged provider from forwardedProps, body, and per-call sendMessage body is used. Later sources win. If no slug resolves, the send throws instead of attaching every stored key
- context? - Typed client-local runtime context passed to client tool implementations. This value is not serialized to the server
- onResponse? - Callback when response is received
- onChunk? - Callback when stream chunk is received
- onFinish? - Callback when response finishes
- onError? - Callback when error occurs
- onInterruptStateChange? - Callback when interrupt state changes; context source is hydrate for restored state and live for streamed or client-initiated updates
- streamProcessor? - Stream processing configuration
Note: Client tools are now automatically executed - no onToolCall callback needed!
Returns#
import type { UIMessage } from "@tanstack/ai-react";
import type { ModelMessage } from "@tanstack/ai";
import type {
MultimodalContent,
SendMessageOptions,
} from "@tanstack/ai-client";
interface UseChatReturn {
messages: ReadonlyArray<UIMessage>;
sendMessage: (
content: string | MultimodalContent,
options?: SendMessageOptions,
) => Promise<void>;
append: (message: ModelMessage | UIMessage) => Promise<void>;
addToolResult: (result: {
toolCallId: string;
tool: string;
output: any;
state?: "output-available" | "output-error";
errorText?: string;
}) => Promise<void>;
addToolApprovalResponse: (response: {
id: string;
approved: boolean;
}) => Promise<void>;
reload: () => Promise<void>;
stop: () => void;
isLoading: boolean;
isHydrating: boolean;
error: Error | undefined;
setMessages: (messages: ReadonlyArray<UIMessage>) => void;
clear: () => void;
}useByok(client)#
Subscribe to a ByokClient snapshot in React.
import { useByok } from "@tanstack/ai-react";
import { byok } from "./byok";
export function KeyStatus() {
const snapshot = useByok(byok);
const openai = snapshot.status.openai;
const last4 = openai && "masked" in openai ? openai.masked : "No key";
return <p>{last4}</p>;
}snapshot has status, locked, and prompt. Call byok.update(provider, value) from your own UI to save a key. See Bring Your Own Key.
Connection Adapters#
Re-exported from @tanstack/ai-client for convenience:
import {
fetchServerSentEvents,
fetchHttpStream,
fetchJson,
xhrServerSentEvents,
xhrHttpStream,
stream,
type ConnectionAdapter,
type FetchConnectionOptions,
type FetchJsonOptions,
type XhrConnectionOptions,
} from "@tanstack/ai-react";For React Native or Expo chat screens, use an absolute server URL and prefer xhrHttpStream() with a server route that returns toHttpResponse(). Use xhrServerSentEvents() with toServerSentEventsResponse() when you want SSE. Use fetchHttpStream() only when the runtime supports streaming fetch, Response.body.getReader(), and TextDecoder; otherwise it throws UnsupportedResponseStreamError.
If the host cannot stream at all, use fetchJson() with a server route that returns toJsonResponse(). See JSON.
XHR adapter options include headers, withCredentials, signal, body, and xhrFactory. Fetch adapter options include headers, credentials, signal, body, and fetchClient. fetchJson() also takes pollIntervalMs. Both option objects may be provided directly or as a function that resolves per request.
For error narrowing, import UnsupportedResponseStreamError and StreamTruncatedError from @tanstack/ai-client.
Example: Basic Chat#
import { useState } from "react";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
export function Chat() {
const [input, setInput] = useState("");
const { messages, sendMessage, isLoading } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (input.trim() && !isLoading) {
sendMessage(input);
setInput("");
}
};
return (
<div>
<div>
{messages.map((message) => (
<div key={message.id}>
<strong>{message.role}:</strong>
{message.parts.map((part, idx) => {
if (part.type === "thinking") {
return (
<div key={idx} className="text-sm text-gray-500 italic">
💭 Thinking: {part.content}
</div>
);
}
if (part.type === "text") {
return <span key={idx}>{part.content}</span>;
}
return null;
})}
</div>
))}
</div>
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
Send
</button>
</form>
</div>
);
}Example: Tool Approval#
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
export function ChatWithApproval() {
const { messages, sendMessage, addToolApprovalResponse } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});
return (
<div>
{messages.map((message) =>
message.parts.map((part) => {
if (
part.type === "tool-call" &&
part.state === "approval-requested" &&
part.approval
) {
return (
<div key={part.id}>
<p>Approve: {part.name}</p>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval!.id,
approved: true,
})
}
>
Approve
</button>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval!.id,
approved: false,
})
}
>
Deny
</button>
</div>
);
}
return null;
})
)}
</div>
);
}Example: Client Tools with Type Safety#
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
import {
createChatClientOptions,
type InferChatMessages
} from "@tanstack/ai-client";
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
import { useState } from "react";
const updateUIDef = toolDefinition({
name: "updateUI",
description: "Update the UI with a notification",
inputSchema: z.object({
message: z.string(),
type: z.string(),
}),
outputSchema: z.object({ success: z.boolean() }),
});
const saveToStorageDef = toolDefinition({
name: "saveToStorage",
description: "Save a value to storage",
inputSchema: z.object({
key: z.string(),
value: z.string(),
}),
outputSchema: z.object({ saved: z.boolean() }),
});
export function ChatWithClientTools() {
const [notification, setNotification] = useState<{ message: string; type: string } | null>(null);
// Create client implementations
const updateUI = updateUIDef.client((input) => {
// ✅ input is fully typed!
setNotification({ message: input.message, type: input.type });
return { success: true };
});
const saveToStorage = saveToStorageDef.client((input) => {
localStorage.setItem(input.key, input.value);
return { saved: true };
});
// Create typed tools array (no 'as const' needed!)
const tools = [updateUI, saveToStorage];
const { messages, sendMessage } = useChat({
connection: fetchServerSentEvents("/api/chat"),
tools, // ✅ Automatic execution, full type safety
});
return (
<div>
{messages.map((message) =>
message.parts.map((part) => {
if (part.type === "tool-call" && part.name === "updateUI") {
// ✅ part.input and part.output are fully typed!
return <div key={part.id}>Tool executed: {part.name}</div>;
}
return null;
})
)}
</div>
);
}createChatClientOptions(options)#
Helper to create typed chat options (re-exported from @tanstack/ai-client).
import {
createChatClientOptions,
fetchServerSentEvents,
type InferChatMessages
} from "@tanstack/ai-client";
import { tool1, tool2 } from "./tools";
// Create typed tools array (no 'as const' needed!)
const tools = [tool1, tool2];
const chatOptions = createChatClientOptions({
connection: fetchServerSentEvents("/api/chat"),
tools,
});
type Messages = InferChatMessages<typeof chatOptions>;Types#
Re-exported from @tanstack/ai-client:
- UIMessage<TTools> - Message type with tool type parameter
- MessagePart<TTools> - Message part with tool type parameter
- TextPart - Text content part
- ThinkingPart - Thinking content part
- ToolCallPart<TTools> - Tool call part (discriminated union)
- ToolResultPart - Tool result part
- ChatClientOptions<TTools, TContext> - Chat client options with typed client runtime context
- ConnectionAdapter - Connection adapter interface
- InferChatMessages<T> - Extract message type from options
Re-exported from @tanstack/ai:
- toolDefinition() - Create isomorphic tool definition
- ToolDefinitionInstance - Tool definition type
- ClientTool - Client tool type
- ServerTool - Server tool type
Next Steps#
- Getting Started - Learn the basics
- Bring Your Own Key - Store keys and pass byok into useChat
- Tools Guide - Learn about the isomorphic tool system
- Client Tools - Learn about client-side tools