Help us improve
Share bugs, ideas, or general feedback.
From tanstack-ai
Integrates TanStack AI SDK for provider-agnostic streaming, tool calling, Zod-structured outputs, multimodal support, and React/Solid hooks in JS/TS/Python/PHP apps.
npx claudepluginhub tanstack-skills/tanstack-skills --plugin tanstack-aiHow this skill is triggered — by the user, by Claude, or both
Slash command
/tanstack-ai:tanstack-aiThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
TanStack AI is a modular, provider-agnostic AI SDK with tree-shakeable adapters for OpenAI, Anthropic, Gemini, Ollama, and more. It provides streaming-first text generation, tool calling with approval workflows, structured output with Zod schemas, multimodal content support, and React hooks for chat/completion UIs.
Delivers provider-agnostic type-safe LLM chat SDK with streaming, tools, agent loops for OpenAI, Anthropic, Gemini, Ollama in React, Solid, Next.js apps.
Provides Vercel AI SDK v5 React hooks (useChat, useCompletion, useObject) for streaming AI chat UIs in Next.js apps. Fixes parse stream errors, no responses, and streaming issues.
Delivers production-ready backend AI using Vercel AI SDK v5 for text generation, structured output, tools, agents with multi-provider support (OpenAI, Anthropic, Google). Resolves errors like AI_APICallError and streaming issues.
Share bugs, ideas, or general feedback.
TanStack AI is a modular, provider-agnostic AI SDK with tree-shakeable adapters for OpenAI, Anthropic, Gemini, Ollama, and more. It provides streaming-first text generation, tool calling with approval workflows, structured output with Zod schemas, multimodal content support, and React hooks for chat/completion UIs.
Core: @tanstack/ai
Vanilla Client: @tanstack/ai-client (framework-agnostic)
React: @tanstack/ai-react
Solid: @tanstack/ai-solid
Adapters: @tanstack/ai-openai, @tanstack/ai-anthropic, @tanstack/ai-gemini, @tanstack/ai-ollama
Languages: TypeScript/JavaScript, PHP, Python
Status: Alpha
npm install @tanstack/ai @tanstack/ai-react
# Or for framework-agnostic vanilla client:
npm install @tanstack/ai @tanstack/ai-client
# Provider adapters (install only what you need):
npm install @tanstack/ai-openai
npm install @tanstack/ai-anthropic
npm install @tanstack/ai-gemini
npm install @tanstack/ai-ollama
composer require tanstack/ai tanstack/ai-openai
pip install tanstack-ai tanstack-ai-openai
import { generate } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai/adapters'
const result = await generate({
adapter: openaiText({ model: 'gpt-4o' }),
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Explain React hooks in 3 sentences.' },
],
})
// Streaming with async iteration
for await (const chunk of result) {
process.stdout.write(chunk.text)
}
import { openaiText } from '@tanstack/ai-openai/adapters'
import { anthropicText } from '@tanstack/ai-anthropic/adapters'
import { geminiText } from '@tanstack/ai-gemini/adapters'
import { ollamaText } from '@tanstack/ai-ollama/adapters'
// OpenAI
const openai = openaiText({ model: 'gpt-4o' })
// Anthropic
const anthropic = anthropicText({ model: 'claude-sonnet-4-20250514' })
// Google Gemini
const gemini = geminiText({ model: 'gemini-pro' })
// Ollama (local)
const ollama = ollamaText({ model: 'llama3' })
// Runtime adapter switching
const adapter = process.env.AI_PROVIDER === 'anthropic' ? anthropic : openai
import { useChat } from '@tanstack/ai-react'
function ChatUI() {
const { messages, input, setInput, handleSubmit, isLoading } = useChat({
adapter: openaiText({ model: 'gpt-4o' }),
})
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>
<strong>{msg.role}:</strong> {msg.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message..."
/>
<button type="submit" disabled={isLoading}>
Send
</button>
</form>
</div>
)
}
import { useCompletion } from '@tanstack/ai-react'
function CompletionUI() {
const { completion, input, setInput, handleSubmit, isLoading } = useCompletion({
adapter: openaiText({ model: 'gpt-4o' }),
})
return (
<div>
<form onSubmit={handleSubmit}>
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Enter prompt..."
/>
<button type="submit" disabled={isLoading}>Generate</button>
</form>
{completion && <div>{completion}</div>}
</div>
)
}
import { createChat } from '@tanstack/ai-solid'
function ChatUI() {
const chat = createChat({
adapter: openaiText({ model: 'gpt-4o' }),
})
return (
<div>
<For each={chat.messages()}>
{(msg) => (
<div>
<strong>{msg.role}:</strong> {msg.content}
</div>
)}
</For>
<form onSubmit={chat.handleSubmit}>
<input
value={chat.input()}
onInput={(e) => chat.setInput(e.target.value)}
placeholder="Type a message..."
/>
<button type="submit" disabled={chat.isLoading()}>
Send
</button>
</form>
</div>
)
}
For framework-agnostic usage without React or Solid:
import { createAIClient } from '@tanstack/ai-client'
import { openaiText } from '@tanstack/ai-openai/adapters'
const client = createAIClient({
adapter: openaiText({ model: 'gpt-4o' }),
})
// Subscribe to state changes
client.subscribe((state) => {
console.log('Messages:', state.messages)
console.log('Loading:', state.isLoading)
})
// Send a message
await client.send('Hello, world!')
// Clear conversation
client.clear()
import { generate } from '@tanstack/ai'
// Default: stream chunks as they arrive
const result = await generate({
adapter: openaiText({ model: 'gpt-4o' }),
messages: [...],
stream: true,
})
for await (const chunk of result) {
// Process each chunk
console.log(chunk.text)
}
Available streaming strategies:
// Server-side SSE endpoint
import { createReplayStream } from '@tanstack/ai'
export async function handler(req: Request) {
const stream = createReplayStream({
adapter: openaiText({ model: 'gpt-4o' }),
messages: await req.json(),
})
return new Response(stream, {
headers: { 'Content-Type': 'text/event-stream' },
})
}
import { generate } from '@tanstack/ai'
import { convertZodToJsonSchema } from '@tanstack/ai'
import { z } from 'zod'
const RecipeSchema = z.object({
name: z.string(),
ingredients: z.array(z.object({
item: z.string(),
amount: z.string(),
})),
steps: z.array(z.string()),
cookTime: z.number(),
})
const result = await generate({
adapter: openaiText({ model: 'gpt-4o' }),
messages: [{ role: 'user', content: 'Give me a pasta recipe' }],
schema: convertZodToJsonSchema(RecipeSchema),
})
// result is typed as z.infer<typeof RecipeSchema>
console.log(result.name, result.ingredients)
import { generate } from '@tanstack/ai'
const result = await generate({
adapter: openaiText({ model: 'gpt-4o' }),
messages: [{ role: 'user', content: 'What is the weather in NYC?' }],
tools: {
getWeather: {
description: 'Get weather for a location',
parameters: z.object({
location: z.string(),
unit: z.enum(['celsius', 'fahrenheit']).optional(),
}),
execute: async ({ location, unit }) => {
const data = await fetchWeather(location, unit)
return data
},
},
},
})
import { ToolCallManager } from '@tanstack/ai'
const manager = new ToolCallManager({
tools: {
deleteUser: {
description: 'Delete a user account',
parameters: z.object({ userId: z.string() }),
requiresApproval: true, // Requires human approval
execute: async ({ userId }) => {
await deleteUser(userId)
return { success: true }
},
},
},
onApprovalRequired: async (toolCall) => {
// Present to user for approval
return await showApprovalDialog(toolCall)
},
})
const result = await generate({
adapter: openaiText({ model: 'gpt-4o' }),
messages: [{ role: 'user', content: 'Research and summarize the topic' }],
tools: { search, summarize, writeReport },
maxIterations: 10, // Limit agent loop iterations
})
// Images
const result = await generate({
adapter: openaiText({ model: 'gpt-4o' }),
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'What is in this image?' },
{ type: 'image_url', image_url: { url: 'https://example.com/photo.jpg' } },
],
}],
})
// Image generation with DALL-E
import { openaiImage } from '@tanstack/ai-openai/adapters'
const image = await generate({
adapter: openaiImage({ model: 'dall-e-3' }),
messages: [{ role: 'user', content: 'A sunset over mountains' }],
})
// Image generation with Gemini Imagen
import { geminiImage } from '@tanstack/ai-gemini/adapters'
const image = await generate({
adapter: geminiImage({ model: 'imagen-3' }),
messages: [{ role: 'user', content: 'A futuristic cityscape at night' }],
})
Support for models with extended reasoning/thinking capabilities:
import { generate } from '@tanstack/ai'
import { anthropicText } from '@tanstack/ai-anthropic/adapters'
const result = await generate({
adapter: anthropicText({ model: 'claude-sonnet-4-20250514' }),
messages: [{ role: 'user', content: 'Solve this complex math problem step by step...' }],
thinking: {
enabled: true,
budget: 10000, // Max thinking tokens
},
})
// Access thinking/reasoning output
console.log('Thinking:', result.thinking)
console.log('Response:', result.text)
// Streaming with thinking tokens
for await (const chunk of result) {
if (chunk.type === 'thinking') {
console.log('[Thinking]', chunk.text)
} else {
process.stdout.write(chunk.text)
}
}
import { generateMessageId, normalizeToUIMessage } from '@tanstack/ai'
// Generate unique message IDs
const id = generateMessageId()
// Normalize provider-specific messages to UI format
const uiMessage = normalizeToUIMessage(providerMessage)
const result = await generate({
adapter: openaiText({ model: 'gpt-4o' }),
messages: [...],
onEvent: (event) => {
// Structured, typed events
switch (event.type) {
case 'text':
console.log('Text chunk:', event.data)
break
case 'tool_call':
console.log('Tool called:', event.name)
break
case 'error':
console.error('Error:', event.error)
break
}
},
})
TanStack AI includes a dedicated devtools panel for debugging AI workflows:
import { TanStackDevtools } from '@tanstack/react-devtools'
import { AIDevtoolsPanel } from '@tanstack/ai-react/devtools'
function App() {
return (
<TanStackDevtools
plugins={[
{
id: 'ai',
name: 'AI',
render: () => <AIDevtoolsPanel />,
},
]}
/>
)
}
AI Devtools features:
// Shared implementation between AI tools and server functions
import { createServerFn } from '@tanstack/react-start'
import { generate } from '@tanstack/ai'
const aiChat = createServerFn({ method: 'POST' })
.validator(z.object({ messages: z.array(messageSchema) }))
.handler(async ({ data }) => {
const result = await generate({
adapter: openaiText({ model: 'gpt-4o' }),
messages: data.messages,
})
return result
})
For streaming structured output that arrives incrementally:
import { parsePartialJson } from '@tanstack/ai'
// Parse incomplete JSON during streaming
const partial = parsePartialJson('{"name": "Pasta", "ingredients": [{"item": "flour"')
// Returns: { name: "Pasta", ingredients: [{ item: "flour" }] }
maxIterations on agentic loops to prevent runaway executionrequiresApproval for destructive tool callsonEvent for observability and debugging in developmentmaxIterations in agentic loops (can run indefinitely)