tanstack-ai
Original:🇺🇸 English
Translated
Build AI-powered chat applications with TanStack AI and React. Use when working with @tanstack/ai, @tanstack/ai-react, @tanstack/ai-client, or any TanStack AI packages. Covers useChat hook, streaming, tools (server/client/hybrid), tool approval, structured outputs, multimodal content, adapters (OpenAI, Anthropic, Gemini, Ollama, Grok), agentic cycles, devtools, and type safety patterns. Triggers on AI chat UI, function calling, LLM integration, or streaming response tasks using TanStack AI.
1installs
Sourcefellipeutaka/leon
Added on
NPX Install
npx skill4agent add fellipeutaka/leon tanstack-aiTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →TanStack AI (React)
AI chat framework with isomorphic tools, streaming, and full type safety.
Packages
- — core:
@tanstack/ai,chat(),toolDefinition(),toServerSentEventsResponse()maxIterations() - — React:
@tanstack/ai-reacthook, re-exports connection adaptersuseChat() - — headless:
@tanstack/ai-client,ChatClient,clientTools(),createChatClientOptions()InferChatMessages - — adapter packages
@tanstack/ai-{openai,anthropic,gemini,ollama,grok,openrouter,fal}
Quick Start
Install
bash
npm install @tanstack/ai @tanstack/ai-react @tanstack/ai-openaiServer (Next.js API Route)
typescript
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
export async function POST(request: Request) {
const { messages } = await request.json();
const stream = chat({
adapter: openaiText("gpt-5.2"),
messages,
});
return toServerSentEventsResponse(stream);
}Client (React)
typescript
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"),
});
return (
<div>
{messages.map((message) => (
<div key={message.id}>
<strong>{message.role}:</strong>
{message.parts.map((part, idx) => {
if (part.type === "text") return <span key={idx}>{part.content}</span>;
if (part.type === "thinking") return <em key={idx}>{part.content}</em>;
return null;
})}
</div>
))}
<form onSubmit={(e) => { e.preventDefault(); sendMessage(input); setInput(""); }}>
<input value={input} onChange={(e) => setInput(e.target.value)} disabled={isLoading} />
<button type="submit" disabled={isLoading}>Send</button>
</form>
</div>
);
}useChat Hook
typescript
const {
messages, // UIMessage[] — current messages
sendMessage, // (content: string | MultimodalContent) => Promise<void>
append, // (message: ModelMessage | UIMessage) => Promise<void>
isLoading, // boolean
error, // Error | undefined
stop, // () => void — cancel current stream
reload, // () => Promise<void> — regenerate last response
clear, // () => void — clear all messages
setMessages, // (messages: UIMessage[]) => void
addToolResult, // (result: { toolCallId, tool, output, state? }) => Promise<void>
addToolApprovalResponse, // (response: { id, approved }) => Promise<void>
} = useChat({
connection: fetchServerSentEvents("/api/chat"),
tools?, // client tool implementations
initialMessages?, // UIMessage[]
id?, // string — unique chat instance id
body?, // additional body params sent with every request
onResponse?, // (response) => void
onChunk?, // (chunk) => void
onFinish?, // (message) => void
onError?, // (error) => void
});Message Structure
Messages use with a array:
UIMessagepartstypescript
interface UIMessage {
id: string;
role: "user" | "assistant";
parts: (TextPart | ThinkingPart | ToolCallPart | ToolResultPart)[];
}Render parts by type:
- —
part.type === "text"(string)part.content - —
part.type === "thinking"(model reasoning, UI-only, not sent back)part.content - —
part.type === "tool-call",part.name,part.input,part.outputpart.state - —
part.type === "tool-result",part.outputpart.state
Connection Adapters
typescript
import { fetchServerSentEvents, fetchHttpStream, stream } from "@tanstack/ai-react";
// SSE (recommended — auto-reconnection)
fetchServerSentEvents("/api/chat", { headers: { Authorization: "Bearer token" } })
// HTTP stream (NDJSON)
fetchHttpStream("/api/chat")
// Custom
stream(async (messages, data, signal) => { /* return async iterable */ })Adapters
Model passed to adapter factory — one function per activity for tree-shaking:
typescript
import { openaiText } from "@tanstack/ai-openai"; // openaiText('gpt-5.2')
import { anthropicText } from "@tanstack/ai-anthropic"; // anthropicText('claude-sonnet-4-5')
import { geminiText } from "@tanstack/ai-gemini"; // geminiText('gemini-2.5-pro')
import { ollamaText } from "@tanstack/ai-ollama"; // ollamaText('llama3')
import { grokText } from "@tanstack/ai-grok"; // grokText('grok-4')
import { openRouterText } from "@tanstack/ai-openrouter"; // openRouterText('openai/gpt-5')Tools Overview
Two-step process: define schema with , then implement with or .
toolDefinition().server().client()typescript
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
const getWeatherDef = toolDefinition({
name: "get_weather",
description: "Get current weather for a location",
inputSchema: z.object({ location: z.string() }),
outputSchema: z.object({ temperature: z.number(), conditions: z.string() }),
needsApproval: false, // optional
});
// Server implementation — runs on server with DB/API access
const getWeather = getWeatherDef.server(async ({ location }) => {
const data = await fetchWeather(location);
return { temperature: data.temp, conditions: data.conditions };
});
// Client implementation — runs in browser for UI/localStorage
const getWeatherClient = getWeatherDef.client((input) => {
return { temperature: 72, conditions: "cached" };
});For detailed tool patterns (server, client, hybrid, approval, agentic cycle), see references/tools.md.
Type Safety
Use + + for full type inference:
clientTools()createChatClientOptions()InferChatMessagestypescript
import { clientTools, createChatClientOptions, type InferChatMessages } from "@tanstack/ai-client";
const tools = clientTools(updateUI, saveToStorage); // no 'as const' needed
const chatOptions = createChatClientOptions({
connection: fetchServerSentEvents("/api/chat"),
tools,
});
type ChatMessages = InferChatMessages<typeof chatOptions>;
// In component:
const { messages } = useChat(chatOptions);
// messages typed — part.name is discriminated union, part.input/output typed from Zod schemasDevtools
bash
npm install -D @tanstack/react-ai-devtools @tanstack/react-devtoolstsx
import { TanStackDevtools } from "@tanstack/react-devtools";
import { aiDevtoolsPlugin } from "@tanstack/react-ai-devtools";
<TanStackDevtools
plugins={[aiDevtoolsPlugin()]}
eventBusConfig={{ connectToServerBus: true }}
/>Additional Guides
- Server setup patterns (Next.js, TanStack Start): see references/server-setup.md
- Tool system (server, client, hybrid, approval, agentic cycle): see references/tools.md
- Advanced features (multimodal, structured outputs, runtime adapter switching): see references/advanced.md