- 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
Code Mode with Snippets
A snippet is a TypeScript function you put in the Code Mode prompt. The model calls that function instead of writing the same program again.
Snippets let the model save working code under a name. On later requests, the saved snippets load from storage, and the model calls them as tools.
Note
These snippets are runtime code that the chat model saves. To teach your coding assistant (Claude Code, Cursor) how TanStack AI works, see Agent Skills (TanStack Intent).
Quick setup#
You need the route from the Code Mode quick setup first.
- Install the snippets package:
npm i @tanstack/ai-code-mode-snippetspnpm add @tanstack/ai-code-mode-snippetsyarn add @tanstack/ai-code-mode-snippetsbun add @tanstack/ai-code-mode-snippets- Replace createCodeMode in your route with codeModeWithSnippets. It needs a storage and a cheap model that picks the snippets for each request:
// app/api/chat/route.ts (Next.js). Any route that takes a Request works.
import {
chat,
chatParamsFromRequest,
convertMessagesToModelMessages,
maxIterations,
toServerSentEventsResponse,
toolDefinition,
} from '@tanstack/ai'
import { codeModeWithSnippets } from '@tanstack/ai-code-mode-snippets'
import { createFileSnippetStorage } from '@tanstack/ai-code-mode-snippets/storage'
import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
import { openaiText } from '@tanstack/ai-openai'
import { z } from 'zod'
const fetchWeather = toolDefinition({
name: 'fetchWeather',
description: 'Get current weather for a city',
inputSchema: z.object({ location: z.string() }),
outputSchema: z.object({ temperature: z.number(), condition: z.string() }),
}).server(async ({ location }) => ({
temperature: location.length + 10,
condition: 'sunny',
}))
const storage = createFileSnippetStorage({ directory: './.snippets' })
const driver = createNodeIsolateDriver()
export async function POST(request: Request) {
const { messages, threadId, runId } = await chatParamsFromRequest(request)
const { toolsRegistry, systemPrompt } = await codeModeWithSnippets({
config: { driver, tools: [fetchWeather] },
adapter: openaiText('gpt-5.4-mini'), // cheap model that picks snippets
snippets: { storage, maxSnippetsInContext: 5 },
messages: convertMessagesToModelMessages(messages),
threadId,
})
const stream = chat({
adapter: openaiText('gpt-6.1-sol'), // strong model for the real work
systemPrompts: ['You are a helpful weather assistant.', systemPrompt],
tools: toolsRegistry.getTools(),
messages,
threadId,
runId,
agentLoopStrategy: maxIterations(15),
})
return toServerSentEventsResponse(stream)
}Send "Compare the weather in Tokyo and Paris. Save the code as a snippet."
Look in ./.snippets/. It holds a new folder with code.ts and meta.json.
Start a new chat and ask the same kind of question. The cheap model picks the saved snippet, and the main model calls it as a tool instead of writing the code again.
How it works#
Each request goes through these steps:
- codeModeWithSnippets loads the snippet index. The index holds names and descriptions, with no code.
- The cheap model reads the last 5 messages and the index, then picks the snippets that match.
- The registry gets the Code Mode tool, the snippet management tools, and one tool for each picked snippet.
- The strong model in chat() can call a snippet tool, write new code with execute_typescript, or save new code with register_snippet.
If storage is empty or there are no messages, step 2 makes no model call.
The registry holds these tools:
- execute_typescript: the Code Mode sandbox. Inside it, every saved snippet is also a snippet_<name> function.
- search_snippets: searches the library by query and returns the snippet metadata.
- get_snippet: returns one snippet in full, code included.
- register_snippet: saves working code as a new snippet.
- One tool for each picked snippet. Its description starts with [SNIPPET].
A new snippet is callable inside execute_typescript right away, as snippet_<name>. It shows up as a direct tool on the next request.
Pick a storage#
- createFileSnippetStorage: saves snippets to disk. It imports node:fs and node:path, so it is only on the @tanstack/ai-code-mode-snippets/storage subpath. Use it on Node.
- createMemorySnippetStorage: keeps snippets in memory. It has no node:fs dependency, so the package root exports it, and it runs in Workers and browsers. Use it for tests, demos, and the edge.
import { createMemorySnippetStorage } from '@tanstack/ai-code-mode-snippets'
const storage = createMemorySnippetStorage()The memory storage is also on the /storage subpath.
Advanced#
Pick the snippets yourself#
codeModeWithSnippets asks a model to pick snippets. To skip that model call, load the snippets yourself and build the tools with the lower-level functions. The ts-code-mode-web example uses this approach.
This example loads every snippet. It uses the fetchWeather tool from the quick setup, moved to ./tools:
import {
chat,
chatParamsFromRequest,
maxIterations,
toServerSentEventsResponse,
} from '@tanstack/ai'
import { createCodeMode } from '@tanstack/ai-code-mode'
import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
import {
createAlwaysTrustedStrategy,
createSnippetManagementTools,
createSnippetsSystemPrompt,
snippetsToTools,
} from '@tanstack/ai-code-mode-snippets'
import { createFileSnippetStorage } from '@tanstack/ai-code-mode-snippets/storage'
import { openaiText } from '@tanstack/ai-openai'
import { fetchWeather } from './tools'
const trustStrategy = createAlwaysTrustedStrategy()
const storage = createFileSnippetStorage({
directory: './.snippets',
trustStrategy,
})
const driver = createNodeIsolateDriver()
export async function POST(request: Request) {
const { messages, threadId, runId } = await chatParamsFromRequest(request)
// 1. The Code Mode tool and prompt
const { tool: codeModeTool, systemPrompt: codeModePrompt } = createCodeMode({
driver,
tools: [fetchWeather],
})
// 2. Load every snippet and turn each one into a tool
const allSnippets = await storage.loadAll()
const snippetIndex = await storage.loadIndex()
const snippetTools = snippetsToTools({
snippets: allSnippets,
driver,
tools: [fetchWeather],
storage,
})
// 3. The search, get, and register tools
const managementTools = createSnippetManagementTools({
storage,
trustStrategy,
})
// 4. The prompt that documents the snippet library
const snippetsPrompt = createSnippetsSystemPrompt({
selectedSnippets: allSnippets,
totalSnippetCount: snippetIndex.length,
snippetsAsTools: true,
})
const stream = chat({
adapter: openaiText('gpt-6.1-sol'),
tools: [codeModeTool, ...managementTools, ...snippetTools],
messages,
threadId,
runId,
systemPrompts: ['You are a helpful assistant.', codeModePrompt, snippetsPrompt],
agentLoopStrategy: maxIterations(15),
})
return toServerSentEventsResponse(stream)
}snippetsToTools also takes timeout (default 30000) and memoryLimit (default 128).
What codeModeWithSnippets takes and returns#
Options:
| Option | Type | Description |
|---|---|---|
| config | CodeModeToolConfig | The same options as createCodeMode. |
| adapter | text adapter | The model that picks snippets. A cheap, fast model is enough. |
| snippets.storage | SnippetStorage | Where snippets live. |
| snippets.maxSnippetsInContext | number | Most snippets to load for one request. Default: 5. |
| messages | Array<ModelMessage> | The conversation, for snippet selection. |
| snippetsAsTools | boolean | Exposes picked snippets as direct tools. Default: true. |
Return value:
| Property | Type | Description |
|---|---|---|
| toolsRegistry | ToolRegistry | All the tools. Give them to chat() with tools: toolsRegistry.getTools(). |
| systemPrompt | string | The Code Mode prompt plus the snippet library docs. |
| selectedSnippets | Array<Snippet> | The snippets that the selection model picked. |
How selection works#
On each request, selectRelevantSnippets:
- Takes the last 5 messages.
- Builds a catalog from the index: name, description, and the first usage hint of each snippet.
- Asks the adapter for a JSON array of matching snippet names, with at most maxSnippetsInContext names.
- Loads the full snippets for those names.
If the model returns invalid JSON, the selection is empty. The request continues with no preloaded snippets, and the model can still search and use snippets with the management tools.
Snippets as tools or as sandbox functions#
snippetsAsTools (default true) controls how the model calls a picked snippet:
| Mode | How the model calls a snippet | Good | Less good |
|---|---|---|---|
| As tools (true) | A direct tool call to the snippet | Simpler for the model. Shows in the tool-call UI. Validates the input. | One tool per snippet in the tool list. |
| As sandbox functions (false) | await snippet_fetch_data({ ... }) inside execute_typescript | Snippets combine in code. Fewer top-level tools. | The model must write code to use them. |
With true, the system prompt documents each snippet with its schema, usage hints, and an example call. With false, snippets appear as typed snippet_* functions in the sandbox type stubs.
Snippet lifecycle#
Saving. After useful code runs in execute_typescript, the system prompt tells the model to call register_snippet with these fields:
- name: a snake_case name. It becomes the tool name.
- description: what the snippet does.
- code: TypeScript that reads an input variable.
- inputSchema and outputSchema: JSON Schema strings.
- usageHints: when to use the snippet.
- dependsOn: other snippets that this one calls.
Running. When the model calls a snippet tool:
- The snippet code gets const input = <serialized input>; in front of it.
- The TypeScript syntax is stripped.
- A fresh sandbox context starts with the external_* bindings.
- The code runs and returns its result.
- The success or failure count updates in the background.
With codeModeWithSnippets, code in execute_typescript can call both external_* and snippet_* functions. The code of a snippet tool gets only the external_* functions.
Storage#
Both storages implement SnippetStorage:
| Method | Description |
|---|---|
| loadIndex() | Loads the metadata of all snippets, with no code. |
| loadAll() | Loads all snippets, code included. |
| get(name) | Gets one snippet by name. |
| save(snippet) | Creates or updates a snippet. |
| delete(name) | Removes a snippet. |
| search(query, options?) | Searches snippets by text. |
| updateStats(name, success) | Records one execution result, for trust tracking. |
The file storage writes this layout:
.snippets/
_index.json # Small catalog for fast loading
fetch_github_stats/
meta.json # Description, schemas, hints, stats
code.ts # TypeScript source
compare_npm_packages/
meta.json
code.tscreateFileSnippetStorage takes directory and an optional trustStrategy (default: createDefaultTrustStrategy()).
Trust strategies#
A snippet starts untrusted and earns trust with successful runs. The trust level is metadata only. It does not block a run.
import {
createDefaultTrustStrategy,
createAlwaysTrustedStrategy,
createRelaxedTrustStrategy,
createCustomTrustStrategy,
} from '@tanstack/ai-code-mode-snippets'| Strategy | Starts at | Provisional | Trusted |
|---|---|---|---|
| Default | untrusted | 10+ runs, 90%+ success | 100+ runs, 95%+ success |
| Relaxed | untrusted | 3+ runs, 80%+ success | 10+ runs, 90%+ success |
| Always trusted | trusted | n/a | n/a |
| Custom | You choose | You choose | You choose |
const strategy = createCustomTrustStrategy({
initialLevel: 'untrusted',
provisionalThreshold: { executions: 5, successRate: 0.85 },
trustedThreshold: { executions: 50, successRate: 0.95 },
})Custom events#
Snippets send these events, next to the Code Mode events. Showing Code Mode in the UI shows how to catch them.
| Event | When | Payload |
|---|---|---|
| code_mode:snippet_call | A snippet tool starts | { snippet, input, timestamp } |
| code_mode:snippet_result | A snippet tool succeeds | { snippet, result, duration, timestamp } |
| code_mode:snippet_error | A snippet tool fails | { snippet, error, duration, timestamp } |
| snippet:registered | register_snippet saves a new snippet | { id, name, description, timestamp } |
Tips#
- Use a cheap model for selection. It only matches snippet names to the conversation. gpt-5.4-mini or claude-haiku-5-5 are enough.
- Start with no snippets. Get Code Mode working first. When your tools produce patterns that repeat, add snippets.
- Watch the snippet count. When the library grows, increase maxSnippetsInContext, or pick the snippets yourself.