Skip to content
Code Mode

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.

  1. Install the snippets package:
shell
npm i @tanstack/ai-code-mode-snippets
  1. Replace createCodeMode in your route with codeModeWithSnippets. It needs a storage and a cheap model that picks the snippets for each request:
ts
// 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)
}
  1. Send "Compare the weather in Tokyo and Paris. Save the code as a snippet."

  2. Look in ./.snippets/. It holds a new folder with code.ts and meta.json.

  3. 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:

  1. codeModeWithSnippets loads the snippet index. The index holds names and descriptions, with no code.
  2. The cheap model reads the last 5 messages and the index, then picks the snippets that match.
  3. The registry gets the Code Mode tool, the snippet management tools, and one tool for each picked snippet.
  4. 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.
ts
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:

ts
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:

OptionTypeDescription
configCodeModeToolConfigThe same options as createCodeMode.
adaptertext adapterThe model that picks snippets. A cheap, fast model is enough.
snippets.storageSnippetStorageWhere snippets live.
snippets.maxSnippetsInContextnumberMost snippets to load for one request. Default: 5.
messagesArray<ModelMessage>The conversation, for snippet selection.
snippetsAsToolsbooleanExposes picked snippets as direct tools. Default: true.

Return value:

PropertyTypeDescription
toolsRegistryToolRegistryAll the tools. Give them to chat() with tools: toolsRegistry.getTools().
systemPromptstringThe Code Mode prompt plus the snippet library docs.
selectedSnippetsArray<Snippet>The snippets that the selection model picked.

How selection works

On each request, selectRelevantSnippets:

  1. Takes the last 5 messages.
  2. Builds a catalog from the index: name, description, and the first usage hint of each snippet.
  3. Asks the adapter for a JSON array of matching snippet names, with at most maxSnippetsInContext names.
  4. 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:

ModeHow the model calls a snippetGoodLess good
As tools (true)A direct tool call to the snippetSimpler 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_typescriptSnippets 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:

  1. The snippet code gets const input = <serialized input>; in front of it.
  2. The TypeScript syntax is stripped.
  3. A fresh sandbox context starts with the external_* bindings.
  4. The code runs and returns its result.
  5. 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:

MethodDescription
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:

plaintext
.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.ts

createFileSnippetStorage 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.

ts
import {
  createDefaultTrustStrategy,
  createAlwaysTrustedStrategy,
  createRelaxedTrustStrategy,
  createCustomTrustStrategy,
} from '@tanstack/ai-code-mode-snippets'
StrategyStarts atProvisionalTrusted
Defaultuntrusted10+ runs, 90%+ success100+ runs, 95%+ success
Relaxeduntrusted3+ runs, 80%+ success10+ runs, 90%+ success
Always trustedtrustedn/an/a
CustomYou chooseYou chooseYou choose
ts
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.

EventWhenPayload
code_mode:snippet_callA snippet tool starts{ snippet, input, timestamp }
code_mode:snippet_resultA snippet tool succeeds{ snippet, result, duration, timestamp }
code_mode:snippet_errorA snippet tool fails{ snippet, error, duration, timestamp }
snippet:registeredregister_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.