TanStack AI is designed from the ground up for maximum tree-shakeability. The entire system—from activity functions to adapters—uses a functional, modular architecture that ensures you only bundle the code you actually use.
Instead of a monolithic API that includes everything, TanStack AI provides:
Individual activity functions - Import only the activities you need (chat, summarize, etc.)
Individual adapter functions - Import only the adapters you need (openaiText, openaiSummarize, etc.)
Functional API design - Pure functions that can be easily eliminated by bundlers
Separate modules - Each activity and adapter lives in its own module
This design means that if you only use chat with OpenAI, you won't bundle code for summarization, image generation, or other providers.
Each AI activity is exported as a separate function from @tanstack/ai:
// Import only the activities you need
import { chat } from '@tanstack/ai' // Chat/text generation
import { summarize } from '@tanstack/ai' // Summarization
import { generateImage } from '@tanstack/ai' // Image generation
import { generateSpeech } from '@tanstack/ai' // Text-to-speech
import { generateTranscription } from '@tanstack/ai' // Audio transcription
import { generateVideo } from '@tanstack/ai' // Video generationIf you only need chat functionality:
// Only chat code is bundled
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
const stream = chat({
adapter: openaiText('gpt-5.5'),
messages: [{ role: 'user', content: 'Hello!' }],
})Your bundle will not include:
Summarization logic
Image generation logic
Other activity implementations
Each provider package exports individual adapter functions for each activity type:
import {
openaiText, // Chat/text generation
openaiSummarize, // Summarization
openaiImage, // Image generation
openaiSpeech, // Text-to-speech
openaiTranscription, // Audio transcription
openaiVideo, // Video generation
} from '@tanstack/ai-openai'import {
anthropicText, // Chat/text generation
anthropicSummarize, // Summarization
} from '@tanstack/ai-anthropic'import {
geminiText, // Chat/text generation
geminiSummarize, // Summarization
geminiImage, // Image generation
geminiSpeech, // Text-to-speech (experimental)
} from '@tanstack/ai-gemini'import {
ollamaText, // Chat/text generation
ollamaSummarize, // Summarization
} from '@tanstack/ai-ollama'Here's how the tree-shakeable design works in practice:
// Only import what you need
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
// Chat generation - returns AsyncIterable<StreamChunk>
const chatResult = chat({
adapter: openaiText('gpt-5.5'),
messages: [{ role: 'user', content: 'Hello!' }],
})
for await (const chunk of chatResult) {
console.log(chunk)
}What gets bundled:
✅ chat function and its dependencies
✅ openaiText adapter and its dependencies
✅ Chat-specific streaming and tool handling logic
What doesn't get bundled:
❌ summarize function
❌ generateImage function
❌ Other adapter implementations (Anthropic, Gemini, etc.)
❌ Other activity implementations
If you need multiple activities, import only what you use:
import { chat, summarize } from '@tanstack/ai'
import {
openaiText,
openaiSummarize
} from '@tanstack/ai-openai'
// Each activity is independent
const chatResult = chat({
adapter: openaiText('gpt-5.5'),
messages: [{ role: 'user', content: 'Hello!' }],
})
const summarizeResult = await summarize({
adapter: openaiSummarize('gpt-5.4-mini'),
text: 'Long text to summarize...',
})Each activity is in its own module, so bundlers can eliminate unused ones.
The tree-shakeable design doesn't sacrifice type safety. Each adapter provides full type safety for its supported models:
import { openaiText, type OpenAIChatModel } from '@tanstack/ai-openai'
const adapter = openaiText('gpt-5.5')
// TypeScript knows the exact models supported
const model: OpenAIChatModel = 'gpt-5.5' // ✓ Valid
const model2: OpenAIChatModel = 'invalid' // ✗ Type errorThe create___Options functions are also tree-shakeable:
import {
createChatOptions,
createImageOptions
} from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
// Only import what you need
const chatOptions = createChatOptions({
adapter: openaiText('gpt-5.5'),
})The functional, modular design provides significant bundle size benefits:
// ❌ Importing more than needed
import * as ai from '@tanstack/ai'
import * as openai from '@tanstack/ai-openai'
// This bundles all exports from both packages// ✅ Only what you use gets bundled
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
// You only get:
// - Chat activity implementation
// - OpenAI text adapter
// - Chat-specific dependenciesFor a typical chat application, importing a single activity and one adapter pulls in substantially less code than bundling every activity and every provider adapter. Because each activity and adapter lives in its own side-effect-free module, your bundler drops everything you don't reference — so the more providers and activities the library supports, the larger the difference between a focused import and a namespace import.
The tree-shakeability is achieved through:
ES Module exports - Each function is a named export, not a default export
Separate modules - Each activity and adapter lives in its own file
No side effects - Functions are pure and don't have module-level side effects
Functional composition - Functions compose together, allowing dead code elimination
Type-only imports - Type imports are stripped at build time
Modern bundlers (Vite, Webpack, Rollup, esbuild) can easily eliminate unused code because:
Functions are statically analyzable
No dynamic imports of unused code
No module-level side effects
Clear dependency graphs
Import only what you need - Don't import entire namespaces
Use specific adapter functions - Import openaiText not openai
Separate activities by route - Different API routes can use different activities
Lazy load when possible - Use dynamic imports for code-split routes
Keep mobile chat bundles client-only - React Native and Expo chat screens should import useChat and chat connection adapters, not provider SDKs, server response helpers, React DOM UI, devtools UI, or other framework packages. See Quick Start: React Native for the server-only provider boundary and mobile transport setup.
// ✅ Good - Only imports chat
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
// ❌ Bad - Imports everything
import * as ai from '@tanstack/ai'
import * as openai from '@tanstack/ai-openai'Each adapter type implements a specific interface:
ChatAdapter - Provides chatStream() method for streaming chat responses
SummarizeAdapter - Provides summarize() method for text summarization
ImageAdapter - Provides generateImage() method for image generation
TTSAdapter - Provides generateSpeech() method for text-to-speech
TranscriptionAdapter - Provides generateTranscription() method for audio transcription
VideoAdapter - Provides generateVideo() method for video generation
All adapters have a kind property that indicates their type:
const chatAdapter = openaiText('gpt-5.5')
console.log(chatAdapter.kind) // 'text'
const summarizeAdapter = openaiSummarize('gpt-5.4-mini')
console.log(summarizeAdapter.kind) // 'summarize'TanStack AI's tree-shakeable design means:
✅ Smaller bundles - Only include code you actually use
✅ Faster load times - Less JavaScript to download and parse
✅ Better performance - Less code means faster execution
✅ Type safety - Full TypeScript support without runtime overhead
✅ Flexibility - Mix and match activities and adapters as needed
The functional, modular architecture ensures that modern bundlers can eliminate unused code effectively, resulting in optimal bundle sizes for your application.