- name
- ai-code-mode
- description
- LLM-generated TypeScript execution in sandboxed environments: createCodeModeTool() with isolate drivers (createNodeIsolateDriver, createQuickJSIsolateDriver, createQuickJSBunIsolateDriver, createCloudflareIsolateDriver), codeModeWithSnippets() for persistent snippet libraries, trust strategies, snippet storage (FileSystem, LocalStorage, InMemory, Mongo), client-side execution progress via code_mode:* custom events in useChat.
- type
- core
- library
- tanstack-ai
- library_version
- 0.3.8
- sources
- ["TanStack/ai:docs/code-mode/code-mode.md","TanStack/ai:docs/code-mode/code-mode-isolates.md","TanStack/ai:docs/code-mode/code-mode-with-snippets.md","TanStack/ai:docs/code-mode/client-integration.md","TanStack/ai:docs/code-mode/lazy-tools.md"]
> **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 } = await request.json()
const stream = chat({
adapter: openaiText('gpt-5.5'),
messages,
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 } = await request.json()
const stream = chat({
adapter: openaiText('gpt-5.5'),
systemPrompts: ['You are a helpful assistant.', systemPrompt],
tools: [tool],
messages,
})
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 } = 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,
})
const stream = chat({
adapter: openaiText('gpt-5.5'),
tools: toolsRegistry.getTools(),
messages,
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'),
onCustomEvent: handleCustomEvent,
})
return (
<div>
{messages.map((message) => (
<div key={message.id}>
{message.parts.map((part, index) => {
if (part.type === 'text') {
return <p key={index}>{part.content}</p>
}
if (
part.type === 'tool-call' &&
part.name === 'execute_typescript'
) {
const events = toolCallEvents.get(part.id) || []
return (
<div key={part.id}>
<pre>{JSON.parse(part.arguments)?.typescriptCode}</pre>
{events.map((evt) => (
<div key={evt.id}>
{evt.eventType}: {JSON.stringify(evt.data)}
</div>
View on GitHub