Persistence (Advanced)
- Overview
- Quick Start
- Providers
- Harnesses
- Harness Auth
- Workspace
- Tools
- Policy
- Lifecycle & Snapshots
- Portable Snapshots
- Keep Files After Reload
- Save a Named Version
- Branch From a Version
- Send a Frozen File
- Let the Agent Save and Fork
- Pick Which Files to Keep
- What a Snapshot Stores
- Instance Durability
- Durable Runs
- Events
Sandboxes (Advanced)
Tree-Shaking
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.
Design Philosophy#
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.
Activity Functions#
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 { generateLiveVideo } from '@tanstack/ai' // Live video sessions
import { generateWorld } from '@tanstack/ai' // Live world sessionsExample: Chat Only#
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-5.5'),
messages: [{ role: 'user', content: 'Hello!' }],
})Your bundle will not include:
- Summarization logic
- Image generation logic
- Other activity implementations
Adapter Functions#
Each provider package exports individual adapter functions for each activity type:
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'Anthropic#
import {
anthropicText, // Chat/text generation
anthropicSummarize, // Summarization
} from '@tanstack/ai-anthropic'Gemini#
import {
geminiText, // Chat/text generation
geminiSummarize, // Summarization
geminiImage, // Image generation
geminiSpeech, // Text-to-speech (experimental)
} from '@tanstack/ai-gemini'Ollama#
import {
ollamaText, // Chat/text generation
ollamaSummarize, // Summarization
} from '@tanstack/ai-ollama'Complete Example#
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
Using Multiple Activities#
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.
Type Safety#
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 errorCreate Options Functions#
The 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'),
})Bundle Size Benefits#
The functional, modular design provides significant bundle size benefits:
Importing Everything (Less Efficient)#
// ❌ Importing more than needed
import * as ai from '@tanstack/ai'
import * as openai from '@tanstack/ai-openai'
// This bundles all exports from both packagesImporting Only What You Need (Recommended)#
// ✅ 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 dependenciesReal-World Impact#
For 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.
How It Works#
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
Best Practices#
- 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'Adapter Types#
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
- LiveVideoAdapter - Provides createLiveVideo() method for live video sessions
- WorldAdapter - Provides createWorld() method for live world sessions and world jobs
All adapters have a kind property that indicates their type:
import { openaiText, openaiSummarize } from '@tanstack/ai-openai'
const chatAdapter = openaiText('gpt-5.5')
console.log(chatAdapter.kind) // 'text'
const summarizeAdapter = openaiSummarize('gpt-5.4-mini')
console.log(summarizeAdapter.kind) // 'summarize'Summary#
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.