- 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
Migration from Vercel AI SDK
Move a Vercel AI SDK app (ai and @ai-sdk/*) to TanStack AI by swapping the package names, the server call, the client hook, and the tool helpers.
- streamText becomes chat().
- useChat from @ai-sdk/react becomes useChat from @tanstack/ai-react.
- tool() becomes toolDefinition().
Do the steps below in order. The "Before" samples are AI SDK v5 and v6 code. Where v4 is different, the page says so. For every other API, see Vercel AI SDK: full API mapping.
Port a chat app#
1. Swap the packages#
| Vercel AI SDK | TanStack AI |
|---|---|
| ai | @tanstack/ai |
| @ai-sdk/openai | @tanstack/ai-openai |
| @ai-sdk/anthropic | @tanstack/ai-anthropic |
| @ai-sdk/google | @tanstack/ai-gemini |
| @ai-sdk/google-vertex | @tanstack/ai-vertex (Gemini), @tanstack/ai-anthropic/vertex (Claude), @tanstack/ai-grok/vertex (Grok), or @tanstack/ai-mistral/vertex (Mistral) |
| @ai-sdk/react | @tanstack/ai-react |
| @ai-sdk/vue | @tanstack/ai-vue |
| @ai-sdk/solid | @tanstack/ai-solid |
| @ai-sdk/svelte | @tanstack/ai-svelte |
Note
In AI SDK v4, the framework hooks came from subpaths such as ai/react. Since v5, they come from packages such as @ai-sdk/react.
Remove the ai and @ai-sdk/* packages from your package.json. Then install TanStack AI with the adapters that you need:
npm i @tanstack/ai @tanstack/ai-react @tanstack/ai-openai @tanstack/ai-anthropicpnpm add @tanstack/ai @tanstack/ai-react @tanstack/ai-openai @tanstack/ai-anthropicyarn add @tanstack/ai @tanstack/ai-react @tanstack/ai-openai @tanstack/ai-anthropicbun add @tanstack/ai @tanstack/ai-react @tanstack/ai-openai @tanstack/ai-anthropic2. Port the server route#
Before (Vercel AI SDK):
import { streamText, convertToModelMessages } from 'ai'
import { openai } from '@ai-sdk/openai'
export async function POST(request: Request) {
const { messages } = await request.json()
const result = streamText({
model: openai('gpt-4o'),
messages: await convertToModelMessages(messages),
})
return result.toUIMessageStreamResponse()
// (v4: result.toDataStreamResponse())
}After (TanStack AI):
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
export async function POST(request: Request) {
const { messages, threadId } = await request.json()
const stream = chat({
adapter: openaiText('gpt-6.1-sol'),
messages,
threadId,
})
return toServerSentEventsResponse(stream)
}What changed:
- model becomes adapter. The model id goes into an adapter function for one activity: openai('gpt-4o') becomes openaiText('gpt-4o').
- No convertToModelMessages(). chat() takes the UI messages from the client as they are. The adapter converts them.
- The response is a separate helper. result.toUIMessageStreamResponse() becomes toServerSentEventsResponse(stream).
If you set system, pass systemPrompts: ['You are a helpful assistant.']. If you set temperature or maxOutputTokens, move them into modelOptions. See Model options.
3. Port the client#
Before (Vercel AI SDK v5+):
import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport } from 'ai'
import { useState } from 'react'
export function Chat() {
const [input, setInput] = useState('')
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({ api: '/api/chat' }),
})
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (input.trim() && status !== 'streaming') {
sendMessage({ text: input })
setInput('')
}
}
return (
<div>
{messages.map((m) => (
<div key={m.id}>
{m.role}:{' '}
{m.parts.map((p, i) => (p.type === 'text' ? <span key={i}>{p.text}</span> : null))}
</div>
))}
<form onSubmit={handleSubmit}>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button type="submit">Send</button>
</form>
</div>
)
}After (TanStack AI):
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'),
threadId: 'support-chat',
})
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (input.trim() && !isLoading) {
sendMessage(input)
setInput('')
}
}
return (
<div>
{messages.map((message) => (
<div key={message.id}>
{message.role}:{' '}
{message.parts.map((part, idx) =>
part.type === 'text' ? <span key={idx}>{part.content}</span> : null
)}
</div>
))}
<form onSubmit={handleSubmit}>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button type="submit">Send</button>
</form>
</div>
)
}The hook is headless, the same as AI SDK v5 and later. You keep your own input state. The rest maps like this:
| Vercel AI SDK (v5+) | TanStack AI |
|---|---|
| transport: new DefaultChatTransport({ api: '/api/chat' }) | connection: fetchServerSentEvents('/api/chat') |
| sendMessage({ text }) | sendMessage(text). It takes a string or multimodal content. To add a full UIMessage, use append(). |
| status | status, with the same four values ('submitted', 'streaming', 'ready', 'error'). For a boolean, read isLoading. |
| regenerate() | reload() |
| stop() | stop() |
| setMessages(messages) | setMessages(messages) |
| addToolOutput({ tool, toolCallId, output }) (v6; addToolResult in v5) | addToolResult({ tool, toolCallId, output }) |
| addToolApprovalResponse({ id, approved }) (v6) | interrupt.resolveInterrupt(approved) on a tool-approval item from interrupts |
| p.text on a text part | part.content on a text part |
4. Port your tools#
You define a tool once with toolDefinition(). Then you add a .server() or a .client() implementation.
Before (Vercel AI SDK v5+):
import { streamText, tool } from 'ai'
import { openai } from '@ai-sdk/openai'
import { z } from 'zod'
import { fetchWeather } from './weather'
const messages = [{ role: 'user' as const, content: 'Hello' }]
const result = streamText({
model: openai('gpt-4o'),
messages,
tools: {
getWeather: tool({
description: 'Get weather for a location',
inputSchema: z.object({ // renamed from `parameters` in v5
location: z.string(),
}),
execute: async ({ location }) => {
const weather = await fetchWeather(location)
return weather
},
}),
},
})After (TanStack AI):
import { chat, toolDefinition, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { z } from 'zod'
import { fetchWeather } from './weather'
// 1. Define the tool
const getWeatherDef = toolDefinition({
name: 'getWeather',
description: 'Get weather for a location',
inputSchema: z.object({
location: z.string(),
}),
outputSchema: z.object({
temperature: z.number(),
conditions: z.string(),
}),
})
// 2. Add the server implementation
const getWeather = getWeatherDef.server(async ({ location }) => {
const weather = await fetchWeather(location)
return weather
})
// 3. Use it in chat
export async function POST(request: Request) {
const { messages, threadId } = await request.json()
const stream = chat({
adapter: openaiText('gpt-6.1-sol'),
messages,
threadId,
tools: [getWeather],
})
return toServerSentEventsResponse(stream)
}| Vercel AI SDK | TanStack AI |
|---|---|
| parameters (v4) / inputSchema (v5+) | inputSchema |
| No equivalent | outputSchema (optional, types the result from end to end) |
| execute on the server | .server() or .client() on the definition |
| An object with tool names as keys | An array of tools |
Do you have client tools (onToolCall with addToolOutput) or approvals (needsApproval)? See Client tools and Tool approval.
5. Run it#
Start your app, and send a message. The reply streams in, and the model calls your tools.
Find the rest of your code#
| Vercel AI SDK | TanStack AI | Details |
|---|---|---|
| generateText() | chat({ stream: false }), which resolves to { text, chunks } | Non-streaming generation |
| generateObject(), streamObject(), Output.object() | chat({ outputSchema }) | Structured output |
| stopWhen, prepareStep | agentLoopStrategy, middleware | Agent loop control |
| wrapLanguageModel(), experimental_transform | middleware | Middleware |
| providerOptions: { openai: {...} } | modelOptions: {...} | Model options |
| embed(), embedMany() | embed() | Embeddings |
| Each streamText option and result member | streamText options |
Done#
Your server route streams with chat(), your client renders message.parts, and your tools run. No file imports ai or @ai-sdk/*.