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:
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 generation
// 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 generation
If 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-4o'),
messages: [{ role: 'user', content: 'Hello!' }],
})
// Only chat code is bundled
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
const stream = chat({
adapter: openaiText('gpt-4o'),
messages: [{ role: 'user', content: 'Hello!' }],
})
Your bundle will not include:
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 {
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 {
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 {
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'
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-4o'),
messages: [{ role: 'user', content: 'Hello!' }],
})
for await (const chunk of chatResult) {
console.log(chunk)
}
// 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-4o'),
messages: [{ role: 'user', content: 'Hello!' }],
})
for await (const chunk of chatResult) {
console.log(chunk)
}
What gets bundled:
What doesn't get bundled:
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-4o'),
messages: [{ role: 'user', content: 'Hello!' }],
})
const summarizeResult = await summarize({
adapter: openaiSummarize('gpt-4o-mini'),
text: 'Long text to summarize...',
})
import { chat, summarize } from '@tanstack/ai'
import {
openaiText,
openaiSummarize
} from '@tanstack/ai-openai'
// Each activity is independent
const chatResult = chat({
adapter: openaiText('gpt-4o'),
messages: [{ role: 'user', content: 'Hello!' }],
})
const summarizeResult = await summarize({
adapter: openaiSummarize('gpt-4o-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()
// TypeScript knows the exact models supported
const model: OpenAIChatModel = 'gpt-4o' // ✓ Valid
const model2: OpenAIChatModel = 'invalid' // ✗ Type error
import { openaiText, type OpenAIChatModel } from '@tanstack/ai-openai'
const adapter = openaiText()
// TypeScript knows the exact models supported
const model: OpenAIChatModel = 'gpt-4o' // ✓ Valid
const model2: OpenAIChatModel = 'invalid' // ✗ Type error
The create___Options functions are also tree-shakeable:
import {
createChatOptions,
createImageOptions
} from '@tanstack/ai'
// Only import what you need
const chatOptions = createChatOptions({
adapter: openaiText('gpt-4o'),
})
import {
createChatOptions,
createImageOptions
} from '@tanstack/ai'
// Only import what you need
const chatOptions = createChatOptions({
adapter: openaiText('gpt-4o'),
})
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
// ❌ 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 dependencies
// ✅ 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 dependencies
For a typical chat application:
That's a 75% reduction in bundle size for most applications!
The tree-shakeability is achieved through:
Modern bundlers (Vite, Webpack, Rollup, esbuild) can easily eliminate unused code because:
// ✅ 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'
// ✅ 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:
All adapters have a kind property that indicates their type:
const chatAdapter = openaiText()
console.log(chatAdapter.kind) // 'text'
const summarizeAdapter = openaiSummarize()
console.log(summarizeAdapter.kind) // 'summarize'
const chatAdapter = openaiText()
console.log(chatAdapter.kind) // 'text'
const summarizeAdapter = openaiSummarize()
console.log(summarizeAdapter.kind) // 'summarize'
TanStack AI's tree-shakeable design means:
The functional, modular architecture ensures that modern bundlers can eliminate unused code effectively, resulting in optimal bundle sizes for your application.
