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.
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.
| 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-anthropicBefore (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:
If you set system, pass systemPrompts: ['You are a helpful assistant.']. If you set temperature or maxOutputTokens, move them into modelOptions. See Model options.
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 |
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.
Start your app, and send a message. The reply streams in, and the model calls your tools.
| 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 |
Your server route streams with chat(), your client renders message.parts, and your tools run. No file imports ai or @ai-sdk/*.