@tanstack/ai-code-mode 0.4.7 → 0.4.9

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/README.md CHANGED
@@ -1,3 +1,23 @@
1
+ <div align="center">
2
+ <picture>
3
+ <source
4
+ media="(prefers-color-scheme: dark)"
5
+ srcset="https://tanstack.com/api/readme/ai.png?theme=dark"
6
+ />
7
+ <source
8
+ media="(prefers-color-scheme: light)"
9
+ srcset="https://tanstack.com/api/readme/ai.png"
10
+ />
11
+ <img
12
+ src="https://tanstack.com/api/readme/ai.png"
13
+ alt="TanStack AI"
14
+ width="900"
15
+ />
16
+ </picture>
17
+ </div>
18
+
19
+ <br />
20
+
1
21
  # @tanstack/ai-code-mode
2
22
 
3
23
  Code Mode for TanStack AI — let LLMs write and execute TypeScript in secure sandboxes with typed tool access.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-code-mode",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "Secure TypeScript Code Mode for TanStack AI agents to execute sandboxed tool orchestration programs.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -54,12 +54,12 @@
54
54
  },
55
55
  "peerDependencies": {
56
56
  "zod": "^3.0.0 || ^4.0.0",
57
- "@tanstack/ai": "^0.52.0"
57
+ "@tanstack/ai": "^0.54.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@vitest/coverage-v8": "4.1.10",
61
61
  "zod": "^4.2.0",
62
- "@tanstack/ai": "0.52.0"
62
+ "@tanstack/ai": "0.54.0"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "vite build",
@@ -26,11 +26,10 @@ sources:
26
26
  Complete Code Mode setup with Node.js isolate driver:
27
27
 
28
28
  ```typescript
29
- import { chat, toServerSentEventsResponse } from '@tanstack/ai'
29
+ import { chat, toServerSentEventsResponse, toolDefinition } from '@tanstack/ai'
30
30
  import { openaiText } from '@tanstack/ai-openai'
31
31
  import { createCodeModeTool } from '@tanstack/ai-code-mode'
32
32
  import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
33
- import { toolDefinition } from '@tanstack/ai'
34
33
  import { z } from 'zod'
35
34
 
36
35
  // Define a tool that code can call
@@ -54,22 +53,37 @@ const codeModeTool = createCodeModeTool({
54
53
  })
55
54
 
56
55
  // Use in chat
57
- const stream = chat({
58
- adapter: openaiText('gpt-5.2'),
59
- messages,
60
- tools: [codeModeTool],
61
- })
56
+ export async function POST(request: Request) {
57
+ const { messages } = await request.json()
58
+
59
+ const stream = chat({
60
+ adapter: openaiText('gpt-5.5'),
61
+ messages,
62
+ tools: [codeModeTool],
63
+ })
62
64
 
63
- return toServerSentEventsResponse(stream)
65
+ return toServerSentEventsResponse(stream)
66
+ }
64
67
  ```
65
68
 
66
69
  The recommended higher-level entry point is `createCodeMode()`, which returns both the tool and a matching system prompt:
67
70
 
68
71
  ```typescript
69
- import { chat } from '@tanstack/ai'
72
+ import { chat, toServerSentEventsResponse, toolDefinition } from '@tanstack/ai'
70
73
  import { createCodeMode } from '@tanstack/ai-code-mode'
71
74
  import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
72
75
  import { openaiText } from '@tanstack/ai-openai'
76
+ import { z } from 'zod'
77
+
78
+ const fetchWeather = toolDefinition({
79
+ name: 'fetchWeather',
80
+ description: 'Get current weather for a city',
81
+ inputSchema: z.object({ city: z.string() }),
82
+ outputSchema: z.object({ temp: z.number(), condition: z.string() }),
83
+ }).server(async ({ city }) => {
84
+ const res = await fetch(`https://api.weather.com/${city}`)
85
+ return res.json()
86
+ })
73
87
 
74
88
  const { tool, systemPrompt } = createCodeMode({
75
89
  driver: createNodeIsolateDriver(),
@@ -77,12 +91,18 @@ const { tool, systemPrompt } = createCodeMode({
77
91
  timeout: 30_000,
78
92
  })
79
93
 
80
- const stream = chat({
81
- adapter: openaiText('gpt-4o'),
82
- systemPrompts: ['You are a helpful assistant.', systemPrompt],
83
- tools: [tool],
84
- messages,
85
- })
94
+ export async function POST(request: Request) {
95
+ const { messages } = await request.json()
96
+
97
+ const stream = chat({
98
+ adapter: openaiText('gpt-5.5'),
99
+ systemPrompts: ['You are a helpful assistant.', systemPrompt],
100
+ tools: [tool],
101
+ messages,
102
+ })
103
+
104
+ return toServerSentEventsResponse(stream)
105
+ }
86
106
  ```
87
107
 
88
108
  `createCodeMode` calls `createCodeModeTool` and `createCodeModeSystemPrompt` internally. The system prompt includes generated TypeScript type stubs for each tool so the LLM writes correct calls.
@@ -154,22 +174,36 @@ const driver = createCloudflareIsolateDriver({
154
174
  Snippets let the LLM save reusable code snippets. On future requests, relevant snippets are loaded and exposed as callable tools.
155
175
 
156
176
  ```typescript
157
- import { chat, maxIterations } from '@tanstack/ai'
177
+ import {
178
+ chat,
179
+ maxIterations,
180
+ toServerSentEventsResponse,
181
+ toolDefinition,
182
+ } from '@tanstack/ai'
158
183
  import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
159
- import { codeModeWithSnippets } from '@tanstack/ai-code-mode-snippets'
160
- import { createFileSnippetStorage } from '@tanstack/ai-code-mode-snippets/storage'
161
184
  import {
185
+ codeModeWithSnippets,
162
186
  createDefaultTrustStrategy,
163
- createAlwaysTrustedStrategy,
164
- createCustomTrustStrategy,
165
187
  } from '@tanstack/ai-code-mode-snippets'
188
+ import { createFileSnippetStorage } from '@tanstack/ai-code-mode-snippets/storage'
166
189
  import { openaiText } from '@tanstack/ai-openai'
190
+ import { z } from 'zod'
191
+
192
+ const fetchWeather = toolDefinition({
193
+ name: 'fetchWeather',
194
+ description: 'Get current weather for a city',
195
+ inputSchema: z.object({ city: z.string() }),
196
+ outputSchema: z.object({ temp: z.number(), condition: z.string() }),
197
+ }).server(async ({ city }) => {
198
+ const res = await fetch(`https://api.weather.com/${city}`)
199
+ return res.json()
200
+ })
167
201
 
168
202
  // Trust strategies control how snippets earn trust through executions
169
- // Default: untrusted -> provisional (10+ runs, >=90%) -> trusted (100+ runs, >=95%)
170
- // Relaxed: untrusted -> provisional (3+ runs, >=80%) -> trusted (10+ runs, >=90%)
171
- // Always trusted: immediately trusted (dev/testing)
172
- // Custom: configurable thresholds
203
+ // Default (createDefaultTrustStrategy): untrusted -> provisional (10+ runs, >=90%) -> trusted (100+ runs, >=95%)
204
+ // Relaxed (createRelaxedTrustStrategy): untrusted -> provisional (3+ runs, >=80%) -> trusted (10+ runs, >=90%)
205
+ // Always trusted (createAlwaysTrustedStrategy): immediately trusted (dev/testing)
206
+ // Custom (createCustomTrustStrategy): configurable thresholds
173
207
  const trustStrategy = createDefaultTrustStrategy()
174
208
 
175
209
  // Storage options: file system (production) or memory (testing)
@@ -180,16 +214,18 @@ const storage = createFileSnippetStorage({
180
214
 
181
215
  const driver = createNodeIsolateDriver()
182
216
 
183
- // High-level API: automatic LLM-based snippet selection
184
- const { toolsRegistry, systemPrompt, selectedSnippets } =
185
- await codeModeWithSnippets({
217
+ export async function POST(request: Request) {
218
+ const { messages } = await request.json()
219
+
220
+ // High-level API: automatic LLM-based snippet selection
221
+ const { toolsRegistry, systemPrompt } = await codeModeWithSnippets({
186
222
  config: {
187
223
  driver,
188
- tools: [myTool1, myTool2],
224
+ tools: [fetchWeather],
189
225
  timeout: 60_000,
190
226
  memoryLimit: 128,
191
227
  },
192
- adapter: openaiText('gpt-4o-mini'), // cheap model for snippet selection
228
+ adapter: openaiText('gpt-5-mini'), // cheap model for snippet selection
193
229
  snippets: {
194
230
  storage,
195
231
  maxSnippetsInContext: 5,
@@ -197,13 +233,16 @@ const { toolsRegistry, systemPrompt, selectedSnippets } =
197
233
  messages,
198
234
  })
199
235
 
200
- const stream = chat({
201
- adapter: openaiText('gpt-4o'),
202
- tools: toolsRegistry.getTools(),
203
- messages,
204
- systemPrompts: ['You are a helpful assistant.', systemPrompt],
205
- agentLoopStrategy: maxIterations(15),
206
- })
236
+ const stream = chat({
237
+ adapter: openaiText('gpt-5.5'),
238
+ tools: toolsRegistry.getTools(),
239
+ messages,
240
+ systemPrompts: ['You are a helpful assistant.', systemPrompt],
241
+ agentLoopStrategy: maxIterations(15),
242
+ })
243
+
244
+ return toServerSentEventsResponse(stream)
245
+ }
207
246
  ```
208
247
 
209
248
  The registry includes: `execute_typescript`, `search_snippets`, `get_snippet`, `register_snippet`, and one tool per selected snippet.
@@ -211,6 +250,8 @@ The registry includes: `execute_typescript`, `search_snippets`, `get_snippet`, `
211
250
  Custom trust strategy example:
212
251
 
213
252
  ```typescript
253
+ import { createCustomTrustStrategy } from '@tanstack/ai-code-mode-snippets'
254
+
214
255
  const strategy = createCustomTrustStrategy({
215
256
  initialLevel: 'untrusted',
216
257
  provisionalThreshold: { executions: 5, successRate: 0.85 },
@@ -244,7 +285,7 @@ Events emitted:
244
285
  | `code_mode:external_result` | After successful external\_\* call | `function`, `result`, `duration` |
245
286
  | `code_mode:external_error` | When external\_\* call fails | `function`, `error`, `duration` |
246
287
 
247
- ```typescript
288
+ ```tsx
248
289
  import { useCallback, useRef, useState } from 'react'
249
290
  import { useChat, fetchServerSentEvents } from '@tanstack/ai-react'
250
291
 
@@ -262,11 +303,7 @@ export function CodeModeChat() {
262
303
  const eventIdCounter = useRef(0)
263
304
 
264
305
  const handleCustomEvent = useCallback(
265
- (
266
- eventType: string,
267
- data: unknown,
268
- context: { toolCallId?: string },
269
- ) => {
306
+ (eventType: string, data: unknown, context: { toolCallId?: string }) => {
270
307
  const { toolCallId } = context
271
308
  if (!toolCallId) return
272
309
 
@@ -296,9 +333,9 @@ export function CodeModeChat() {
296
333
  <div>
297
334
  {messages.map((message) => (
298
335
  <div key={message.id}>
299
- {message.parts.map((part) => {
336
+ {message.parts.map((part, index) => {
300
337
  if (part.type === 'text') {
301
- return <p key={part.id}>{part.content}</p>
338
+ return <p key={index}>{part.content}</p>
302
339
  }
303
340
  if (
304
341
  part.type === 'tool-call' &&
@@ -331,7 +368,11 @@ export function CodeModeChat() {
331
368
  The `onCustomEvent` callback signature is identical across all framework integrations (`@tanstack/ai-react`, `@tanstack/ai-solid`, `@tanstack/ai-vue`, `@tanstack/ai-svelte`):
332
369
 
333
370
  ```typescript
334
- (eventType: string, data: unknown, context: { toolCallId?: string }) => void
371
+ type OnCustomEvent = (
372
+ eventType: string,
373
+ data: unknown,
374
+ context: { toolCallId?: string },
375
+ ) => void
335
376
  ```
336
377
 
337
378
  Snippet-specific events (when using `codeModeWithSnippets`):
@@ -349,10 +390,20 @@ When a large tool catalog would bloat the `execute_typescript` system prompt, ma
349
390
 
350
391
  **Marking a tool lazy:**
351
392
 
352
- ```typescript
393
+ ```typescript group=lazy-tools
353
394
  import { toolDefinition } from '@tanstack/ai'
354
395
  import { z } from 'zod'
355
396
 
397
+ const eagerTool = toolDefinition({
398
+ name: 'fetchWeather',
399
+ description: 'Get current weather for a city',
400
+ inputSchema: z.object({ city: z.string() }),
401
+ outputSchema: z.object({ temp: z.number(), condition: z.string() }),
402
+ }).server(async ({ city }) => {
403
+ const res = await fetch(`https://api.weather.com/${city}`)
404
+ return res.json()
405
+ })
406
+
356
407
  const rarelyUsedTool = toolDefinition({
357
408
  name: 'fetchStocks',
358
409
  description: 'Get stock prices for a ticker. Returns a price quote.',
@@ -360,8 +411,8 @@ const rarelyUsedTool = toolDefinition({
360
411
  outputSchema: z.object({ price: z.number() }),
361
412
  lazy: true, // <-- opt out of full system-prompt documentation
362
413
  }).server(async ({ ticker }) => {
363
- // ...
364
- return { price: 0 }
414
+ const res = await fetch(`https://api.stocks.com/${ticker}`)
415
+ return res.json()
365
416
  })
366
417
  ```
367
418
 
@@ -369,8 +420,8 @@ const rarelyUsedTool = toolDefinition({
369
420
 
370
421
  `createCodeMode()` returns `{ tool, discoveryTool, tools, systemPrompt }`. When lazy tools are present `discoveryTool` is a `discover_tools` server tool; otherwise it is `null`. Always spread `tools` (not just `tool`) into `chat()` so the discovery tool is registered:
371
422
 
372
- ```typescript
373
- import { chat } from '@tanstack/ai'
423
+ ```typescript group=lazy-tools
424
+ import { chat, toServerSentEventsResponse } from '@tanstack/ai'
374
425
  import { createCodeMode } from '@tanstack/ai-code-mode'
375
426
  import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
376
427
  import { openaiText } from '@tanstack/ai-openai'
@@ -380,12 +431,18 @@ const { tools, systemPrompt } = createCodeMode({
380
431
  tools: [eagerTool, rarelyUsedTool], // rarelyUsedTool has lazy: true
381
432
  })
382
433
 
383
- const stream = chat({
384
- adapter: openaiText('gpt-5.5'),
385
- systemPrompts: ['You are a helpful assistant.', systemPrompt],
386
- tools: [...tools, ...otherTools], // spread tools, not just tool
387
- messages,
388
- })
434
+ export async function POST(request: Request) {
435
+ const { messages } = await request.json()
436
+
437
+ const stream = chat({
438
+ adapter: openaiText('gpt-5.5'),
439
+ systemPrompts: ['You are a helpful assistant.', systemPrompt],
440
+ tools: [...tools], // spread tools, not just tool
441
+ messages,
442
+ })
443
+
444
+ return toServerSentEventsResponse(stream)
445
+ }
389
446
  ```
390
447
 
391
448
  `tools` equals `[tool]` when there are no lazy tools (backward compatible) and `[tool, discoveryTool]` when lazy tools exist.
@@ -412,6 +469,10 @@ Control how much of each lazy tool's description appears in the Discoverable API
412
469
  | `'full'` | `external_fetchStocks — Get stock prices. Returns a price quote.` |
413
470
 
414
471
  ```typescript
472
+ import { createCodeMode } from '@tanstack/ai-code-mode'
473
+ import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
474
+ import { eagerTool, rarelyUsedTool } from './tools'
475
+
415
476
  const { tools, systemPrompt } = createCodeMode({
416
477
  driver: createNodeIsolateDriver(),
417
478
  tools: [eagerTool, rarelyUsedTool],
@@ -430,11 +491,17 @@ Code Mode executes LLM-generated code. Any secrets available in the sandbox cont
430
491
  Wrong:
431
492
 
432
493
  ```typescript
494
+ import { toolDefinition } from '@tanstack/ai'
495
+ import { createCodeModeTool } from '@tanstack/ai-code-mode'
496
+ import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
497
+ import { z } from 'zod'
498
+
433
499
  const codeModeTool = createCodeModeTool({
434
- driver,
500
+ driver: createNodeIsolateDriver(),
435
501
  tools: [
436
502
  toolDefinition({
437
503
  name: 'callApi',
504
+ description: 'Call an HTTP API',
438
505
  inputSchema: z.object({ url: z.string(), apiKey: z.string() }),
439
506
  outputSchema: z.any(),
440
507
  }).server(async ({ url, apiKey }) =>
@@ -449,16 +516,22 @@ const codeModeTool = createCodeModeTool({
449
516
  Right:
450
517
 
451
518
  ```typescript
519
+ import { toolDefinition } from '@tanstack/ai'
520
+ import { createCodeModeTool } from '@tanstack/ai-code-mode'
521
+ import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
522
+ import { z } from 'zod'
523
+
452
524
  const codeModeTool = createCodeModeTool({
453
- driver,
525
+ driver: createNodeIsolateDriver(),
454
526
  tools: [
455
527
  toolDefinition({
456
528
  name: 'callApi',
529
+ description: 'Call an HTTP API',
457
530
  inputSchema: z.object({ url: z.string() }),
458
531
  outputSchema: z.any(),
459
532
  }).server(async ({ url }) =>
460
533
  fetch(url, {
461
- headers: { Authorization: process.env.API_KEY }, // secret stays in host
534
+ headers: { Authorization: `Bearer ${process.env.API_KEY}` }, // secret stays in host
462
535
  }),
463
536
  ),
464
537
  ],
@@ -474,12 +547,16 @@ LLM-generated code may contain infinite loops. The default timeout is 30s, but d
474
547
  Wrong:
475
548
 
476
549
  ```typescript
550
+ import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
551
+
477
552
  const driver = createNodeIsolateDriver({ timeout: 0 })
478
553
  ```
479
554
 
480
555
  Right:
481
556
 
482
557
  ```typescript
558
+ import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
559
+
483
560
  const driver = createNodeIsolateDriver({ timeout: 30_000 })
484
561
  ```
485
562