@tanstack/ai-code-mode 0.4.8 → 0.4.10
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/package.json +3 -3
- package/skills/ai-code-mode/SKILL.md +136 -59
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-code-mode",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
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.
|
|
57
|
+
"@tanstack/ai": "^0.55.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@vitest/coverage-v8": "4.1.10",
|
|
61
61
|
"zod": "^4.2.0",
|
|
62
|
-
"@tanstack/ai": "0.
|
|
62
|
+
"@tanstack/ai": "0.55.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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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 {
|
|
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
|
-
|
|
184
|
-
const {
|
|
185
|
-
|
|
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: [
|
|
224
|
+
tools: [fetchWeather],
|
|
189
225
|
timeout: 60_000,
|
|
190
226
|
memoryLimit: 128,
|
|
191
227
|
},
|
|
192
|
-
adapter: openaiText('gpt-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
```
|
|
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={
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
|