@tanstack/ai-code-mode 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tanner Linsley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json CHANGED
@@ -1,11 +1,8 @@
1
1
  {
2
2
  "name": "@tanstack/ai-code-mode",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Code Mode for TanStack AI - LLM-driven code execution in secure sandboxes",
5
5
  "author": "",
6
- "publishConfig": {
7
- "access": "public"
8
- },
9
6
  "license": "MIT",
10
7
  "repository": {
11
8
  "type": "git",
@@ -27,36 +24,38 @@
27
24
  },
28
25
  "files": [
29
26
  "dist",
30
- "src"
27
+ "src",
28
+ "skills"
31
29
  ],
32
- "scripts": {
33
- "build": "vite build",
34
- "clean": "premove ./build ./dist",
35
- "lint:fix": "eslint ./src --fix",
36
- "test:build": "publint --strict",
37
- "test:eslint": "eslint ./src",
38
- "test:lib": "vitest",
39
- "test:lib:dev": "pnpm test:lib --watch",
40
- "test:types": "tsc"
41
- },
42
30
  "keywords": [
43
31
  "ai",
44
32
  "tanstack",
45
33
  "code-mode",
46
34
  "llm",
47
35
  "sandbox",
48
- "isolate"
36
+ "isolate",
37
+ "tanstack-intent"
49
38
  ],
50
39
  "dependencies": {
51
40
  "esbuild": "^0.25.12"
52
41
  },
53
42
  "peerDependencies": {
54
- "@tanstack/ai": "workspace:*",
55
- "zod": "^3.0.0 || ^4.0.0"
43
+ "zod": "^3.0.0 || ^4.0.0",
44
+ "@tanstack/ai": "0.10.2"
56
45
  },
57
46
  "devDependencies": {
58
- "@tanstack/ai": "workspace:*",
59
47
  "@vitest/coverage-v8": "4.0.14",
60
- "zod": "^4.2.0"
48
+ "zod": "^4.2.0",
49
+ "@tanstack/ai": "0.10.2"
50
+ },
51
+ "scripts": {
52
+ "build": "vite build",
53
+ "clean": "premove ./build ./dist",
54
+ "lint:fix": "eslint ./src --fix",
55
+ "test:build": "publint --strict",
56
+ "test:eslint": "eslint ./src",
57
+ "test:lib": "vitest",
58
+ "test:lib:dev": "pnpm test:lib --watch",
59
+ "test:types": "tsc"
61
60
  }
62
61
  }
@@ -0,0 +1,432 @@
1
+ ---
2
+ name: ai-code-mode
3
+ description: >
4
+ LLM-generated TypeScript execution in sandboxed environments:
5
+ createCodeModeTool() with isolate drivers (createNodeIsolateDriver,
6
+ createQuickJSIsolateDriver, createCloudflareIsolateDriver),
7
+ codeModeWithSkills() for persistent skill libraries, trust strategies,
8
+ skill storage (FileSystem, LocalStorage, InMemory, Mongo), client-side
9
+ execution progress via code_mode:* custom events in useChat.
10
+ type: core
11
+ library: tanstack-ai
12
+ library_version: '0.10.0'
13
+ sources:
14
+ - 'TanStack/ai:docs/code-mode/code-mode.md'
15
+ - 'TanStack/ai:docs/code-mode/code-mode-isolates.md'
16
+ - 'TanStack/ai:docs/code-mode/code-mode-with-skills.md'
17
+ - 'TanStack/ai:docs/code-mode/client-integration.md'
18
+ ---
19
+
20
+ > **Note**: This skill requires familiarity with ai-core and ai-core/chat-experience. Code Mode is always used on top of a chat experience.
21
+
22
+ ## Setup
23
+
24
+ Complete Code Mode setup with Node.js isolate driver:
25
+
26
+ ```typescript
27
+ import { chat, toServerSentEventsResponse } from '@tanstack/ai'
28
+ import { openaiText } from '@tanstack/ai-openai'
29
+ import { createCodeModeTool } from '@tanstack/ai-code-mode'
30
+ import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
31
+ import { toolDefinition } from '@tanstack/ai'
32
+ import { z } from 'zod'
33
+
34
+ // Define a tool that code can call
35
+ const fetchWeather = toolDefinition({
36
+ name: 'fetchWeather',
37
+ description: 'Get current weather for a city',
38
+ inputSchema: z.object({ city: z.string() }),
39
+ outputSchema: z.object({ temp: z.number(), condition: z.string() }),
40
+ }).server(async ({ city }) => {
41
+ const res = await fetch(`https://api.weather.com/${city}`)
42
+ return res.json()
43
+ })
44
+
45
+ // Create code mode tool with Node isolate
46
+ const codeModeTool = createCodeModeTool({
47
+ driver: createNodeIsolateDriver({
48
+ memoryLimit: 128,
49
+ timeout: 30000,
50
+ }),
51
+ tools: [fetchWeather],
52
+ })
53
+
54
+ // Use in chat
55
+ const stream = chat({
56
+ adapter: openaiText('gpt-5.2'),
57
+ messages,
58
+ tools: [codeModeTool],
59
+ })
60
+
61
+ return toServerSentEventsResponse(stream)
62
+ ```
63
+
64
+ The recommended higher-level entry point is `createCodeMode()`, which returns both the tool and a matching system prompt:
65
+
66
+ ```typescript
67
+ import { chat } from '@tanstack/ai'
68
+ import { createCodeMode } from '@tanstack/ai-code-mode'
69
+ import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
70
+ import { openaiText } from '@tanstack/ai-openai'
71
+
72
+ const { tool, systemPrompt } = createCodeMode({
73
+ driver: createNodeIsolateDriver(),
74
+ tools: [fetchWeather],
75
+ timeout: 30_000,
76
+ })
77
+
78
+ const stream = chat({
79
+ adapter: openaiText('gpt-4o'),
80
+ systemPrompts: ['You are a helpful assistant.', systemPrompt],
81
+ tools: [tool],
82
+ messages,
83
+ })
84
+ ```
85
+
86
+ `createCodeMode` calls `createCodeModeTool` and `createCodeModeSystemPrompt` internally. The system prompt includes generated TypeScript type stubs for each tool so the LLM writes correct calls.
87
+
88
+ ## Core Patterns
89
+
90
+ ### 1. Choosing an Isolate Driver
91
+
92
+ Three drivers implement the `IsolateDriver` interface. All are interchangeable.
93
+
94
+ **Node.js** (`createNodeIsolateDriver`) -- Full V8 with JIT. Fastest option. Requires `isolated-vm` native C++ addon.
95
+
96
+ ```typescript
97
+ import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
98
+
99
+ const driver = createNodeIsolateDriver({
100
+ memoryLimit: 128, // MB, default 128
101
+ timeout: 30_000, // ms, default 30000
102
+ // skipProbe: false -- set true only after verifying compatibility
103
+ })
104
+ ```
105
+
106
+ **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).
107
+
108
+ ```typescript
109
+ import { createQuickJSIsolateDriver } from '@tanstack/ai-isolate-quickjs'
110
+
111
+ const driver = createQuickJSIsolateDriver({
112
+ memoryLimit: 128, // MB, default 128
113
+ timeout: 30_000, // ms, default 30000
114
+ maxStackSize: 524288, // bytes, default 512 KiB
115
+ })
116
+ ```
117
+
118
+ **Cloudflare** (`createCloudflareIsolateDriver`) -- Edge execution via a deployed Cloudflare Worker. Requires a `workerUrl` pointing to your deployed worker. Network latency on each tool call.
119
+
120
+ ```typescript
121
+ import { createCloudflareIsolateDriver } from '@tanstack/ai-isolate-cloudflare'
122
+
123
+ const driver = createCloudflareIsolateDriver({
124
+ workerUrl: 'https://my-code-mode-worker.my-account.workers.dev',
125
+ authorization: process.env.CODE_MODE_WORKER_SECRET,
126
+ timeout: 30_000, // ms, default 30000
127
+ maxToolRounds: 10, // max tool-call/result cycles, default 10
128
+ })
129
+ ```
130
+
131
+ | Driver | Best for | Native deps | Browser support | Performance |
132
+ | ---------- | --------------------------- | --------------- | --------------- | -------------------- |
133
+ | Node | Server-side Node.js | Yes (C++ addon) | No | Fast (V8 JIT) |
134
+ | QuickJS | Browsers, edge, portability | None (WASM) | Yes | Slower (interpreted) |
135
+ | Cloudflare | Edge deployments | None | N/A | Fast (V8 on edge) |
136
+
137
+ ### 2. Adding Persistent Skills with codeModeWithSkills()
138
+
139
+ Skills let the LLM save reusable code snippets. On future requests, relevant skills are loaded and exposed as callable tools.
140
+
141
+ ```typescript
142
+ import { chat, maxIterations } from '@tanstack/ai'
143
+ import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
144
+ import { codeModeWithSkills } from '@tanstack/ai-code-mode-skills'
145
+ import { createFileSkillStorage } from '@tanstack/ai-code-mode-skills/storage'
146
+ import {
147
+ createDefaultTrustStrategy,
148
+ createAlwaysTrustedStrategy,
149
+ createCustomTrustStrategy,
150
+ } from '@tanstack/ai-code-mode-skills'
151
+ import { openaiText } from '@tanstack/ai-openai'
152
+
153
+ // Trust strategies control how skills earn trust through executions
154
+ // Default: untrusted -> provisional (10+ runs, >=90%) -> trusted (100+ runs, >=95%)
155
+ // Relaxed: untrusted -> provisional (3+ runs, >=80%) -> trusted (10+ runs, >=90%)
156
+ // Always trusted: immediately trusted (dev/testing)
157
+ // Custom: configurable thresholds
158
+ const trustStrategy = createDefaultTrustStrategy()
159
+
160
+ // Storage options: file system (production) or memory (testing)
161
+ const storage = createFileSkillStorage({
162
+ directory: './.skills',
163
+ trustStrategy,
164
+ })
165
+
166
+ const driver = createNodeIsolateDriver()
167
+
168
+ // High-level API: automatic LLM-based skill selection
169
+ const { toolsRegistry, systemPrompt, selectedSkills } =
170
+ await codeModeWithSkills({
171
+ config: {
172
+ driver,
173
+ tools: [myTool1, myTool2],
174
+ timeout: 60_000,
175
+ memoryLimit: 128,
176
+ },
177
+ adapter: openaiText('gpt-4o-mini'), // cheap model for skill selection
178
+ skills: {
179
+ storage,
180
+ maxSkillsInContext: 5,
181
+ },
182
+ messages,
183
+ })
184
+
185
+ const stream = chat({
186
+ adapter: openaiText('gpt-4o'),
187
+ tools: toolsRegistry.getTools(),
188
+ messages,
189
+ systemPrompts: ['You are a helpful assistant.', systemPrompt],
190
+ agentLoopStrategy: maxIterations(15),
191
+ })
192
+ ```
193
+
194
+ The registry includes: `execute_typescript`, `search_skills`, `get_skill`, `register_skill`, and one tool per selected skill.
195
+
196
+ Custom trust strategy example:
197
+
198
+ ```typescript
199
+ const strategy = createCustomTrustStrategy({
200
+ initialLevel: 'untrusted',
201
+ provisionalThreshold: { executions: 5, successRate: 0.85 },
202
+ trustedThreshold: { executions: 50, successRate: 0.95 },
203
+ })
204
+ ```
205
+
206
+ Storage implementations:
207
+
208
+ ```typescript
209
+ // File storage (production) -- persists skills as files on disk
210
+ import { createFileSkillStorage } from '@tanstack/ai-code-mode-skills/storage'
211
+ const fileStorage = createFileSkillStorage({ directory: './.skills' })
212
+
213
+ // Memory storage (testing) -- in-memory, lost on restart
214
+ import { createMemorySkillStorage } from '@tanstack/ai-code-mode-skills/storage'
215
+ const memStorage = createMemorySkillStorage()
216
+ ```
217
+
218
+ ### 3. Client-Side Execution Progress Display
219
+
220
+ Code Mode emits custom events during sandbox execution. Handle them in `useChat` via `onCustomEvent`.
221
+
222
+ Events emitted:
223
+
224
+ | Event | When | Key fields |
225
+ | ----------------------------- | ------------------------------------ | -------------------------------- |
226
+ | `code_mode:execution_started` | Sandbox begins | `timestamp`, `codeLength` |
227
+ | `code_mode:console` | Each console.log/error/warn/info | `level`, `message`, `timestamp` |
228
+ | `code_mode:external_call` | Before an external\_\* function runs | `function`, `args`, `timestamp` |
229
+ | `code_mode:external_result` | After successful external\_\* call | `function`, `result`, `duration` |
230
+ | `code_mode:external_error` | When external\_\* call fails | `function`, `error`, `duration` |
231
+
232
+ ```typescript
233
+ import { useCallback, useRef, useState } from 'react'
234
+ import { useChat, fetchServerSentEvents } from '@tanstack/ai-react'
235
+
236
+ interface VMEvent {
237
+ id: string
238
+ eventType: string
239
+ data: unknown
240
+ timestamp: number
241
+ }
242
+
243
+ export function CodeModeChat() {
244
+ const [toolCallEvents, setToolCallEvents] = useState<
245
+ Map<string, Array<VMEvent>>
246
+ >(new Map())
247
+ const eventIdCounter = useRef(0)
248
+
249
+ const handleCustomEvent = useCallback(
250
+ (
251
+ eventType: string,
252
+ data: unknown,
253
+ context: { toolCallId?: string },
254
+ ) => {
255
+ const { toolCallId } = context
256
+ if (!toolCallId) return
257
+
258
+ const event: VMEvent = {
259
+ id: `event-${eventIdCounter.current++}`,
260
+ eventType,
261
+ data,
262
+ timestamp: Date.now(),
263
+ }
264
+
265
+ setToolCallEvents((prev) => {
266
+ const next = new Map(prev)
267
+ const events = next.get(toolCallId) || []
268
+ next.set(toolCallId, [...events, event])
269
+ return next
270
+ })
271
+ },
272
+ [],
273
+ )
274
+
275
+ const { messages, sendMessage, isLoading } = useChat({
276
+ connection: fetchServerSentEvents('/api/chat'),
277
+ onCustomEvent: handleCustomEvent,
278
+ })
279
+
280
+ return (
281
+ <div>
282
+ {messages.map((message) => (
283
+ <div key={message.id}>
284
+ {message.parts.map((part) => {
285
+ if (part.type === 'text') {
286
+ return <p key={part.id}>{part.content}</p>
287
+ }
288
+ if (
289
+ part.type === 'tool-call' &&
290
+ part.name === 'execute_typescript'
291
+ ) {
292
+ const events = toolCallEvents.get(part.id) || []
293
+ return (
294
+ <div key={part.id}>
295
+ <pre>{JSON.parse(part.arguments)?.typescriptCode}</pre>
296
+ {events.map((evt) => (
297
+ <div key={evt.id}>
298
+ {evt.eventType}: {JSON.stringify(evt.data)}
299
+ </div>
300
+ ))}
301
+ {part.output && (
302
+ <pre>{JSON.stringify(part.output, null, 2)}</pre>
303
+ )}
304
+ </div>
305
+ )
306
+ }
307
+ return null
308
+ })}
309
+ </div>
310
+ ))}
311
+ </div>
312
+ )
313
+ }
314
+ ```
315
+
316
+ The `onCustomEvent` callback signature is identical across all framework integrations (`@tanstack/ai-react`, `@tanstack/ai-solid`, `@tanstack/ai-vue`, `@tanstack/ai-svelte`):
317
+
318
+ ```typescript
319
+ (eventType: string, data: unknown, context: { toolCallId?: string }) => void
320
+ ```
321
+
322
+ Skill-specific events (when using `codeModeWithSkills`):
323
+
324
+ | Event | When | Key fields |
325
+ | ------------------------ | ------------------ | ----------------------------- |
326
+ | `code_mode:skill_call` | Skill tool invoked | `skill`, `input`, `timestamp` |
327
+ | `code_mode:skill_result` | Skill completed | `skill`, `result`, `duration` |
328
+ | `code_mode:skill_error` | Skill failed | `skill`, `error`, `duration` |
329
+ | `skill:registered` | New skill saved | `id`, `name`, `description` |
330
+
331
+ ## Common Mistakes
332
+
333
+ ### CRITICAL: Passing API keys or secrets to the sandbox environment
334
+
335
+ Code Mode executes LLM-generated code. Any secrets available in the sandbox context are accessible to generated code, which could exfiltrate them via tool calls. Never pass API keys, database credentials, or tokens into the sandbox. Keep secrets in your tool server implementations, which run in the host process outside the sandbox.
336
+
337
+ Wrong:
338
+
339
+ ```typescript
340
+ const codeModeTool = createCodeModeTool({
341
+ driver,
342
+ tools: [
343
+ toolDefinition({
344
+ name: 'callApi',
345
+ inputSchema: z.object({ url: z.string(), apiKey: z.string() }),
346
+ outputSchema: z.any(),
347
+ }).server(async ({ url, apiKey }) =>
348
+ fetch(url, {
349
+ headers: { Authorization: apiKey },
350
+ }),
351
+ ),
352
+ ],
353
+ })
354
+ ```
355
+
356
+ Right:
357
+
358
+ ```typescript
359
+ const codeModeTool = createCodeModeTool({
360
+ driver,
361
+ tools: [
362
+ toolDefinition({
363
+ name: 'callApi',
364
+ inputSchema: z.object({ url: z.string() }),
365
+ outputSchema: z.any(),
366
+ }).server(async ({ url }) =>
367
+ fetch(url, {
368
+ headers: { Authorization: process.env.API_KEY }, // secret stays in host
369
+ }),
370
+ ),
371
+ ],
372
+ })
373
+ ```
374
+
375
+ Source: docs/code-mode/code-mode.md
376
+
377
+ ### HIGH: Not setting timeout for code execution
378
+
379
+ LLM-generated code may contain infinite loops. The default timeout is 30s, but developers may override to 0 (no timeout). Always set an explicit, finite timeout.
380
+
381
+ Wrong:
382
+
383
+ ```typescript
384
+ const driver = createNodeIsolateDriver({ timeout: 0 })
385
+ ```
386
+
387
+ Right:
388
+
389
+ ```typescript
390
+ const driver = createNodeIsolateDriver({ timeout: 30_000 })
391
+ ```
392
+
393
+ Source: ai-code-mode source (default timeout in CodeModeToolConfig)
394
+
395
+ ### HIGH: Using Node isolated-vm driver without checking platform compatibility
396
+
397
+ `isolated-vm` requires native module compilation. An incompatible build (wrong Node.js version, missing build tools) causes segfaults that no JS error handling can catch. The driver runs a subprocess probe by default. Never set `skipProbe: true` unless you have independently verified compatibility. Use `probeIsolatedVm()` to check before creating the driver.
398
+
399
+ ```typescript
400
+ import {
401
+ createNodeIsolateDriver,
402
+ probeIsolatedVm,
403
+ } from '@tanstack/ai-isolate-node'
404
+
405
+ const probe = probeIsolatedVm()
406
+ if (!probe.compatible) {
407
+ console.error('isolated-vm not compatible:', probe.error)
408
+ // Fall back to QuickJS
409
+ }
410
+
411
+ // Never do this unless you verified compatibility yourself:
412
+ // const driver = createNodeIsolateDriver({ skipProbe: true })
413
+ ```
414
+
415
+ Source: ai-isolate-node source (probeIsolatedVm implementation)
416
+
417
+ ### MEDIUM: Expecting identical behavior across isolate drivers
418
+
419
+ The three drivers have different capabilities. Same code may work in Node but fail elsewhere.
420
+
421
+ - **Node**: Full V8 support, JIT compilation, configurable memory limit
422
+ - **QuickJS**: Interpreted, limited stdlib (no File I/O), configurable stack size, asyncified execution (serialized through global queue)
423
+ - **Cloudflare**: Network latency per tool call round-trip, `maxToolRounds` limit (default 10), requires deployed worker with `UNSAFE_EVAL` or `eval` unsafe binding
424
+
425
+ Test generated code against your target driver. If you need portability, target QuickJS's subset.
426
+
427
+ Source: docs/code-mode/code-mode-isolates.md
428
+
429
+ ## Cross-References
430
+
431
+ - See also: ai-core/tool-calling/SKILL.md -- Code Mode is an alternative to standard tool calling for complex multi-step operations
432
+ - See also: ai-core/chat-experience/SKILL.md -- Code Mode requires handling custom events in useChat