Note: This skill requires familiarity with ai-core and ai-core/chat-experience. Code Mode is always used on top of a chat experience.
Setup
Complete Code Mode setup with Node.js isolate driver:
typescript
import { chat, toServerSentEventsResponse, toolDefinition } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { createCodeModeTool } from '@tanstack/ai-code-mode'
import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
import { z } from 'zod'
// Define a tool that code can call
const fetchWeather = toolDefinition({
name: 'fetchWeather',
description: 'Get current weather for a city',
inputSchema: z.object({ city: z.string() }),
outputSchema: z.object({ temp: z.number(), condition: z.string() }),
}).server(async ({ city }) => {
const res = await fetch(`https://api.weather.com/${city}`)
return res.json()
})
// Create code mode tool with Node isolate
const codeModeTool = createCodeModeTool({
driver: createNodeIsolateDriver({
memoryLimit: 128,
timeout: 30000,
}),
tools: [fetchWeather],
})
// Use in chat
export async function POST(request: Request) {
const { messages, threadId } = await request.json()
const stream = chat({
adapter: openaiText('gpt-5.5'),
messages,
threadId,
tools: [codeModeTool],
})
return toServerSentEventsResponse(stream)
}
The recommended higher-level entry point is createCodeMode(), which returns both the tool and a matching system prompt:
typescript
import { chat, toServerSentEventsResponse, toolDefinition } from '@tanstack/ai'
import { createCodeMode } from '@tanstack/ai-code-mode'
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({ city: z.string() }),
outputSchema: z.object({ temp: z.number(), condition: z.string() }),
}).server(async ({ city }) => {
const res = await fetch(`https://api.weather.com/${city}`)
return res.json()
})
const { tool, systemPrompt } = createCodeMode({
driver: createNodeIsolateDriver(),
tools: [fetchWeather],
timeout: 30_000,
})
export async function POST(request: Request) {
const { messages, threadId } = await request.json()
const stream = chat({
adapter: openaiText('gpt-5.5'),
systemPrompts: ['You are a helpful assistant.', systemPrompt],
tools: [tool],
messages,
threadId,
})
return toServerSentEventsResponse(stream)
}
createCodeMode calls createCodeModeTool and createCodeModeSystemPrompt internally. The system prompt includes generated TypeScript type stubs for each tool so the LLM writes correct calls.
Core Patterns
1. Choosing an Isolate Driver
Four drivers implement the IsolateDriver interface. All are interchangeable.
Node.js (createNodeIsolateDriver) -- Full V8 with JIT. Fastest option. Requires isolated-vm native C++ addon.
typescript
import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
const driver = createNodeIsolateDriver({
memoryLimit: 128, // MB, default 128
timeout: 30_000, // ms, default 30000
// skipProbe: false -- set true only after verifying compatibility
})
QuickJS (createQuickJSIsolateDriver) -- WASM-based, no native deps. Works in Node.js, browsers, Deno, Bun, and edge runtimes. Slower (interpreted, no JIT). Limited stdlib (no File I/O).
typescript
import { createQuickJSIsolateDriver } from '@tanstack/ai-isolate-quickjs'
const driver = createQuickJSIsolateDriver({
memoryLimit: 128, // MB, default 128
timeout: 30_000, // ms, default 30000
maxStackSize: 524288, // bytes, default 512 KiB
})
QuickJS Bun (createQuickJSBunIsolateDriver) -- Native QuickJS on the Bun runtime via bun:ffi. Requires Bun >= 1.3.14 (throws a descriptive error on Node.js). No native deps or build step. Each context gets a dedicated QuickJS runtime with its own memory limit, stack size, and interrupt-based timeout. Recommended QuickJS option on Bun, where the WASM driver's asyncify bridge is unreliable for async host tool calls.
typescript
import { createQuickJSBunIsolateDriver } from '@tanstack/ai-isolate-quickjs-bun'
const driver = createQuickJSBunIsolateDriver({
memoryLimit: 128, // MB, default 128
timeout: 30_000, // ms, default 30000
maxStackSize: 524288, // bytes, default 512 KiB
})
Cloudflare (createCloudflareIsolateDriver) -- Edge execution via a deployed Cloudflare Worker. Requires a workerUrl pointing to your deployed worker. Network latency on each tool call.
typescript
import { createCloudflareIsolateDriver } from '@tanstack/ai-isolate-cloudflare'
const driver = createCloudflareIsolateDriver({
workerUrl: 'https://my-code-mode-worker.my-account.workers.dev',
authorization: process.env.CODE_MODE_WORKER_SECRET,
timeout: 30_000, // ms, default 30000
maxToolRounds: 10, // max tool-call/result cycles, default 10
})
| Driver | Best for | Native deps | Browser support | Performance | |
|---|
| Node | Server-side Node.js | Yes (C++ addon) | No | Fast (V8 JIT) | |
| QuickJS | Browsers, edge, portability | None (WASM) | Yes | Slower (interpreted) | |
| QuickJS Bun | Bun servers | None | No | Fast (native QuickJS) | |
| Cloudflare | Edge deployments | None | N/A | Fast (V8 on edge) | |
2. Adding Persistent Snippets with codeModeWithSnippets()
Snippets let the LLM save reusable code snippets. On future requests, relevant snippets are loaded and exposed as callable tools.
typescript
import {
chat,
maxIterations,
toServerSentEventsResponse,
toolDefinition,
} from '@tanstack/ai'
import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
import {
codeModeWithSnippets,
createDefaultTrustStrategy,
} from '@tanstack/ai-code-mode-snippets'
import { createFileSnippetStorage } from '@tanstack/ai-code-mode-snippets/storage'
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({ city: z.string() }),
outputSchema: z.object({ temp: z.number(), condition: z.string() }),
}).server(async ({ city }) => {
const res = await fetch(`https://api.weather.com/${city}`)
return res.json()
})
// Trust strategies control how snippets earn trust through executions
// Default (createDefaultTrustStrategy): untrusted -> provisional (10+ runs, >=90%) -> trusted (100+ runs, >=95%)
// Relaxed (createRelaxedTrustStrategy): untrusted -> provisional (3+ runs, >=80%) -> trusted (10+ runs, >=90%)
// Always trusted (createAlwaysTrustedStrategy): immediately trusted (dev/testing)
// Custom (createCustomTrustStrategy): configurable thresholds
const trustStrategy = createDefaultTrustStrategy()
// Storage options: file system (production) or memory (testing)
const storage = createFileSnippetStorage({
directory: './.snippets',
trustStrategy,
})
const driver = createNodeIsolateDriver()
export async function POST(request: Request) {
const { messages, threadId } = await request.json()
// High-level API: automatic LLM-based snippet selection
const { toolsRegistry, systemPrompt } = await codeModeWithSnippets({
config: {
driver,
tools: [fetchWeather],
timeout: 60_000,
memoryLimit: 128,
},
adapter: openaiText('gpt-5-mini'), // cheap model for snippet selection
snippets: {
storage,
maxSnippetsInContext: 5,
},
messages,
threadId,
})
const stream = chat({
adapter: openaiText('gpt-5.5'),
tools: toolsRegistry.getTools(),
messages,
threadId,
systemPrompts: ['You are a helpful assistant.', systemPrompt],
agentLoopStrategy: maxIterations(15),
})
return toServerSentEventsResponse(stream)
}
The registry includes: execute_typescript, search_snippets, get_snippet, register_snippet, and one tool per selected snippet.
Custom trust strategy example:
typescript
import { createCustomTrustStrategy } from '@tanstack/ai-code-mode-snippets'
const strategy = createCustomTrustStrategy({
initialLevel: 'untrusted',
provisionalThreshold: { executions: 5, successRate: 0.85 },
trustedThreshold: { executions: 50, successRate: 0.95 },
})
Storage implementations:
typescript
// File storage (production) -- persists snippets as files on disk
import { createFileSnippetStorage } from '@tanstack/ai-code-mode-snippets/storage'
const fileStorage = createFileSnippetStorage({ directory: './.snippets' })
// Memory storage (testing) -- in-memory, lost on restart
import { createMemorySnippetStorage } from '@tanstack/ai-code-mode-snippets/storage'
const memStorage = createMemorySnippetStorage()
3. Client-Side Execution Progress Display
Code Mode emits custom events during sandbox execution. Handle them in useChat via onCustomEvent.
Events emitted:
| Event | When | Key fields | |
|---|
code_mode:execution_started | Sandbox begins | timestamp, codeLength | |
code_mode:console | Each console.log/error/warn/info | level, message, timestamp | |
code_mode:external_call | Before an external\_\* function runs | function, args, timestamp | |
code_mode:external_result | After successful external\_\* call | function, result, duration | |
code_mode:external_error | When external\_\* call fails | function, error, duration | |
tsx
import { useCallback, useRef, useState } from 'react'
import { useChat, fetchServerSentEvents } from '@tanstack/ai-react'
interface VMEvent {
id: string
eventType: string
data: unknown
timestamp: number
}
export function CodeModeChat() {
const [toolCallEvents, setToolCallEvents] = useState<
Map<string, Array<VMEvent>>
>(new Map())
const eventIdCounter = useRef(0)
const handleCustomEvent = useCallback(
(eventType: string, data: unknown, context: { toolCallId?: string }) => {
const { toolCallId } = context
if (!toolCallId) return
const event: VMEvent = {
id: `event-${eventIdCounter.current++}`,
eventType,
data,
timestamp: Date.now(),
}
setToolCallEvents((prev) => {
const next = new Map(prev)
const events = next.get(toolCallId) || []
next.set(toolCallId, [...events, event])
return next
})
},
[],
)
const { messages, sendMessage, isLoading } = useChat({
connection: fetchServerSentEvents('/api/chat'),
threadId: 'support-42',
onCustomEvent: handleCustomEvent,
})