@tanstack/ai-code-mode 0.1.0
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 +196 -0
- package/dist/esm/agent-store.d.ts +21 -0
- package/dist/esm/agent-store.js +31 -0
- package/dist/esm/agent-store.js.map +1 -0
- package/dist/esm/bindings/tool-to-binding.d.ts +24 -0
- package/dist/esm/bindings/tool-to-binding.js +78 -0
- package/dist/esm/bindings/tool-to-binding.js.map +1 -0
- package/dist/esm/code-wrapper.d.ts +9 -0
- package/dist/esm/code-wrapper.js +18 -0
- package/dist/esm/code-wrapper.js.map +1 -0
- package/dist/esm/create-code-mode-tool.d.ts +50 -0
- package/dist/esm/create-code-mode-tool.js +142 -0
- package/dist/esm/create-code-mode-tool.js.map +1 -0
- package/dist/esm/create-code-mode.d.ts +41 -0
- package/dist/esm/create-code-mode.js +12 -0
- package/dist/esm/create-code-mode.js.map +1 -0
- package/dist/esm/create-system-prompt.d.ts +24 -0
- package/dist/esm/create-system-prompt.js +66 -0
- package/dist/esm/create-system-prompt.js.map +1 -0
- package/dist/esm/index.d.ts +10 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/strip-typescript.d.ts +23 -0
- package/dist/esm/strip-typescript.js +52 -0
- package/dist/esm/strip-typescript.js.map +1 -0
- package/dist/esm/type-generator/json-schema-to-ts.d.ts +31 -0
- package/dist/esm/type-generator/json-schema-to-ts.js +100 -0
- package/dist/esm/type-generator/json-schema-to-ts.js.map +1 -0
- package/dist/esm/types.d.ts +176 -0
- package/package.json +62 -0
- package/src/agent-store.ts +43 -0
- package/src/bindings/tool-to-binding.ts +132 -0
- package/src/code-wrapper.ts +24 -0
- package/src/create-code-mode-tool.ts +254 -0
- package/src/create-code-mode.ts +35 -0
- package/src/create-system-prompt.ts +95 -0
- package/src/index.ts +55 -0
- package/src/strip-typescript.ts +94 -0
- package/src/type-generator/json-schema-to-ts.ts +188 -0
- package/src/types.ts +225 -0
package/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# @tanstack/ai-code-mode
|
|
2
|
+
|
|
3
|
+
Code Mode for TanStack AI — let LLMs write and execute TypeScript in secure sandboxes with typed tool access.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Code Mode gives your AI agent an `execute_typescript` tool. Instead of one tool call per action, the LLM writes a small TypeScript program that orchestrates multiple tool calls with loops, conditionals, `Promise.all`, and data transformations — all running in an isolated sandbox.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @tanstack/ai-code-mode
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
You also need an isolate driver:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Node.js (fastest, uses V8 isolates via isolated-vm)
|
|
19
|
+
pnpm add @tanstack/ai-isolate-node
|
|
20
|
+
|
|
21
|
+
# QuickJS WASM (browser-compatible, no native deps)
|
|
22
|
+
pnpm add @tanstack/ai-isolate-quickjs
|
|
23
|
+
|
|
24
|
+
# Cloudflare Workers (edge execution)
|
|
25
|
+
pnpm add @tanstack/ai-isolate-cloudflare
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { chat, toolDefinition } from '@tanstack/ai'
|
|
32
|
+
import { createCodeMode } from '@tanstack/ai-code-mode'
|
|
33
|
+
import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
|
|
34
|
+
import { z } from 'zod'
|
|
35
|
+
|
|
36
|
+
// Define tools that the LLM can call from inside the sandbox
|
|
37
|
+
const weatherTool = toolDefinition({
|
|
38
|
+
name: 'fetchWeather',
|
|
39
|
+
description: 'Get weather for a city',
|
|
40
|
+
inputSchema: z.object({ location: z.string() }),
|
|
41
|
+
outputSchema: z.object({ temperature: z.number(), condition: z.string() }),
|
|
42
|
+
}).server(async ({ location }) => {
|
|
43
|
+
// Your implementation
|
|
44
|
+
return { temperature: 72, condition: 'sunny' }
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// Create the execute_typescript tool and system prompt
|
|
48
|
+
const { tool, systemPrompt } = createCodeMode({
|
|
49
|
+
driver: createNodeIsolateDriver(),
|
|
50
|
+
tools: [weatherTool],
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const result = await chat({
|
|
54
|
+
adapter: yourAdapter,
|
|
55
|
+
model: 'gpt-4o',
|
|
56
|
+
systemPrompts: ['You are a helpful assistant.', systemPrompt],
|
|
57
|
+
tools: [tool],
|
|
58
|
+
messages: [
|
|
59
|
+
{ role: 'user', content: 'Compare weather in Tokyo, Paris, and NYC' },
|
|
60
|
+
],
|
|
61
|
+
})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The LLM will generate code like:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const cities = ['Tokyo', 'Paris', 'NYC']
|
|
68
|
+
const results = await Promise.all(
|
|
69
|
+
cities.map((city) => external_fetchWeather({ location: city })),
|
|
70
|
+
)
|
|
71
|
+
const warmest = results.reduce((prev, curr) =>
|
|
72
|
+
curr.temperature > prev.temperature ? curr : prev,
|
|
73
|
+
)
|
|
74
|
+
return { warmestCity: warmest.location, temperature: warmest.temperature }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## API Reference
|
|
78
|
+
|
|
79
|
+
### `createCodeMode(config)`
|
|
80
|
+
|
|
81
|
+
Creates both the `execute_typescript` tool and its matching system prompt. This is the recommended entry point.
|
|
82
|
+
|
|
83
|
+
**Config:**
|
|
84
|
+
|
|
85
|
+
- `driver` — An `IsolateDriver` (Node, QuickJS, or Cloudflare)
|
|
86
|
+
- `tools` — Array of `ServerTool` or `ToolDefinition` instances. Exposed as `external_*` functions in the sandbox
|
|
87
|
+
- `timeout` — Execution timeout in ms (default: 30000)
|
|
88
|
+
- `memoryLimit` — Memory limit in MB (default: 128, supported by Node and QuickJS drivers)
|
|
89
|
+
- `getSkillBindings` — Optional async function returning dynamic bindings
|
|
90
|
+
|
|
91
|
+
### `createCodeModeTool(config)` / `createCodeModeSystemPrompt(config)`
|
|
92
|
+
|
|
93
|
+
Lower-level functions if you need only the tool or only the prompt. `createCodeMode` calls both internally.
|
|
94
|
+
|
|
95
|
+
### Advanced
|
|
96
|
+
|
|
97
|
+
These utilities are used internally and exported for custom pipelines:
|
|
98
|
+
|
|
99
|
+
- **`stripTypeScript(code)`** — Strips TypeScript syntax using esbuild.
|
|
100
|
+
- **`toolsToBindings(tools, prefix?)`** — Converts tools to `ToolBinding` records for sandbox injection.
|
|
101
|
+
- **`generateTypeStubs(bindings, options?)`** — Generates TypeScript type declarations from tool bindings.
|
|
102
|
+
|
|
103
|
+
## Driver Selection Guide
|
|
104
|
+
|
|
105
|
+
| Driver | Best For | Native Deps | Browser | Memory Limit |
|
|
106
|
+
| --------------------------------- | -------------------------------------------- | ------------------- | ------- | ------------ |
|
|
107
|
+
| `@tanstack/ai-isolate-node` | Server-side Node.js apps | Yes (`isolated-vm`) | No | Yes |
|
|
108
|
+
| `@tanstack/ai-isolate-quickjs` | Browser, edge, or no-native-dep environments | No (WASM) | Yes | Yes |
|
|
109
|
+
| `@tanstack/ai-isolate-cloudflare` | Cloudflare Workers deployments | No | N/A | N/A |
|
|
110
|
+
|
|
111
|
+
## Custom Events
|
|
112
|
+
|
|
113
|
+
Code Mode emits custom events during execution that you can observe via the TanStack AI event system:
|
|
114
|
+
|
|
115
|
+
| Event | Description |
|
|
116
|
+
| ----------------------------- | --------------------------------------------------- |
|
|
117
|
+
| `code_mode:execution_started` | Emitted when code execution begins |
|
|
118
|
+
| `code_mode:console` | Emitted for each `console.log/error/warn/info` call |
|
|
119
|
+
| `code_mode:external_call` | Emitted before each `external_*` function call |
|
|
120
|
+
| `code_mode:external_result` | Emitted after a successful `external_*` call |
|
|
121
|
+
| `code_mode:external_error` | Emitted when an `external_*` call fails |
|
|
122
|
+
|
|
123
|
+
## Models eval (development)
|
|
124
|
+
|
|
125
|
+
The benchmark lives in a **separate workspace package** so `@tanstack/ai-code-mode` does not depend on `@tanstack/ai-isolate-node` (avoids an Nx build cycle). See `models-eval/package.json` (`@tanstack/ai-code-mode-models-eval`).
|
|
126
|
+
|
|
127
|
+
1. `packages/typescript/ai-code-mode/models-eval/pull-models.sh` — pull recommended Ollama models
|
|
128
|
+
2. `pnpm --filter @tanstack/ai-code-mode-models-eval eval:capture` — run models and capture raw outputs/telemetry only (no judge LLM call)
|
|
129
|
+
3. `pnpm --filter @tanstack/ai-code-mode-models-eval eval:judge` — judge latest captured session from logs (no model rerun)
|
|
130
|
+
4. `pnpm --filter @tanstack/ai-code-mode-models-eval eval` — single-pass run+judge (legacy convenience mode)
|
|
131
|
+
5. `pnpm --filter @tanstack/ai-code-mode-models-eval eval -- --ollama-only` — only Ollama models from `eval-config.ts`
|
|
132
|
+
6. `pnpm --filter @tanstack/ai-code-mode-models-eval eval -- --ollama-only --models qwen3-coder` — one or more model ids (comma-separated)
|
|
133
|
+
|
|
134
|
+
Judge-phase flags:
|
|
135
|
+
|
|
136
|
+
- `--judge-latest` judge latest captured session
|
|
137
|
+
- `--rejudge` re-run judging even if logs already contain judge fields
|
|
138
|
+
|
|
139
|
+
The default list omits some small Ollama models that rarely complete code-mode successfully (see comments in `eval-config.ts`). You can still benchmark them with `--models granite4:3b` etc. if pulled locally.
|
|
140
|
+
|
|
141
|
+
### Model comparison metrics
|
|
142
|
+
|
|
143
|
+
The models eval now tracks seven decision-oriented metrics plus an overall rating:
|
|
144
|
+
|
|
145
|
+
- `accuracy` (1-10): numerical/factual correctness vs gold report
|
|
146
|
+
- `comprehensiveness` (1-10): whether the response covers everything requested by the user query
|
|
147
|
+
- `typescriptQuality` (1-10): quality/readability/type-safety of generated TypeScript
|
|
148
|
+
- `codeModeEfficiency` (1-10): how efficiently the model uses code-mode/tooling to reach the answer
|
|
149
|
+
- `speedTier` (1-5): relative wall-clock speed against peers in the same category (`local` or `cloud`)
|
|
150
|
+
- `tokenEfficiencyTier` (1-5): relative token efficiency against peers in the same category
|
|
151
|
+
- `stabilityTier` (1-5): success consistency over the latest 5 logged runs for that model
|
|
152
|
+
- `stars` (1-3): weighted rollup score across all metrics
|
|
153
|
+
|
|
154
|
+
Raw run telemetry also includes compile/runtime failures, redundant schema checks, total tool calls, TTFT, token totals, stability sample size/rate, and per-model logs.
|
|
155
|
+
|
|
156
|
+
### Methodology
|
|
157
|
+
|
|
158
|
+
Canonical output is written to `packages/typescript/ai-code-mode/models-eval/results.json` after each capture or judge run.
|
|
159
|
+
|
|
160
|
+
- Benchmark: single code-mode benchmark prompt over the in-memory `customers` / `products` / `purchases` dataset
|
|
161
|
+
- Primary quality scores (judge): `accuracy`, `comprehensiveness`, `typescriptQuality`, `codeModeEfficiency`
|
|
162
|
+
- Computed comparative scores: `speedTier`, `tokenEfficiencyTier`, `stabilityTier`
|
|
163
|
+
- Stability definition: a run is "stable" if it has no top-level run error, produces a non-empty candidate report, and has at least one successful `execute_typescript` call
|
|
164
|
+
- Star rollup weights:
|
|
165
|
+
- accuracy: 25%
|
|
166
|
+
- comprehensiveness: 15%
|
|
167
|
+
- typescriptQuality: 15%
|
|
168
|
+
- codeModeEfficiency (with compile/runtime failure penalty): 10%
|
|
169
|
+
- speedTier: 10%
|
|
170
|
+
- tokenEfficiencyTier: 10%
|
|
171
|
+
- stabilityTier: 15%
|
|
172
|
+
|
|
173
|
+
### Model comparison table
|
|
174
|
+
|
|
175
|
+
The table below is transcribed from canonical `models-eval/results.json` (session `2026-03-26T15:38:44.006Z`).
|
|
176
|
+
|
|
177
|
+
| Provider | Model | Category | Stars | Accuracy | Comprehensiveness | TypeScript | Code-Mode | Speed Tier | Token Tier | Stability Tier |
|
|
178
|
+
| --------- | ----------------------------- | -------- | ----- | -------- | ----------------- | ---------- | --------- | ---------- | ---------- | -------------- |
|
|
179
|
+
| Ollama | `gpt-oss:20b` | local | ★★★ | 10 | 8 | 5 | 5 | 5 | 5 | 5 |
|
|
180
|
+
| Ollama | `nemotron-cascade-2` | local | ★★☆ | 3 | 5 | 6 | 5 | 1 | 5 | 5 |
|
|
181
|
+
| Anthropic | `claude-haiku-4-5` | cloud | ★★★ | 10 | 10 | 6 | 7 | 3 | 2 | 5 |
|
|
182
|
+
| OpenAI | `gpt-4o-mini` | cloud | ★★★ | 10 | 8 | 7 | 9 | 3 | 1 | 5 |
|
|
183
|
+
| Gemini | `gemini-2.5-flash` | cloud | ★★★ | 10 | 8 | 7 | 10 | 4 | 2 | 5 |
|
|
184
|
+
| xAI | `grok-4-1-fast-non-reasoning` | cloud | ★★★ | 10 | 8 | 6 | 10 | 4 | 5 | 5 |
|
|
185
|
+
| Groq | `llama-3.3-70b-versatile` | cloud | ★★★ | 10 | 7 | 6 | 9 | 5 | 3 | 4 |
|
|
186
|
+
| Groq | `qwen/qwen3-32b` | cloud | ★★☆ | 10 | 8 | 5 | 4 | 1 | 2 | 5 |
|
|
187
|
+
|
|
188
|
+
Suggested interpretation:
|
|
189
|
+
|
|
190
|
+
- **Local-first**: favor `stars >= 2` with high speed tier.
|
|
191
|
+
- **Cloud-first quality**: favor high `accuracy` + `typescriptQuality`, then compare stars.
|
|
192
|
+
- **Cost-sensitive**: prioritize `tokenEfficiencyTier` and `speedTier` together.
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface AgentSession {
|
|
2
|
+
name: string;
|
|
3
|
+
systemPrompt: string;
|
|
4
|
+
memory: Record<string, unknown>;
|
|
5
|
+
createdAt: number;
|
|
6
|
+
lastUsedAt: number;
|
|
7
|
+
}
|
|
8
|
+
export interface AgentStore {
|
|
9
|
+
get: (name: string) => Promise<AgentSession | null>;
|
|
10
|
+
set: (name: string, session: AgentSession) => Promise<void>;
|
|
11
|
+
delete: (name: string) => Promise<void>;
|
|
12
|
+
list: () => Promise<Array<string>>;
|
|
13
|
+
}
|
|
14
|
+
export declare class InMemoryAgentStore implements AgentStore {
|
|
15
|
+
private sessions;
|
|
16
|
+
get(name: string): Promise<AgentSession | null>;
|
|
17
|
+
set(name: string, session: AgentSession): Promise<void>;
|
|
18
|
+
delete(name: string): Promise<void>;
|
|
19
|
+
list(): Promise<Array<string>>;
|
|
20
|
+
}
|
|
21
|
+
export declare function generateAgentName(): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class InMemoryAgentStore {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
4
|
+
}
|
|
5
|
+
get(name) {
|
|
6
|
+
return Promise.resolve(this.sessions.get(name) ?? null);
|
|
7
|
+
}
|
|
8
|
+
set(name, session) {
|
|
9
|
+
this.sessions.set(name, session);
|
|
10
|
+
return Promise.resolve();
|
|
11
|
+
}
|
|
12
|
+
delete(name) {
|
|
13
|
+
this.sessions.delete(name);
|
|
14
|
+
return Promise.resolve();
|
|
15
|
+
}
|
|
16
|
+
list() {
|
|
17
|
+
return Promise.resolve(Array.from(this.sessions.keys()));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function generateAgentName() {
|
|
21
|
+
const hex = Array.from(
|
|
22
|
+
{ length: 8 },
|
|
23
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
24
|
+
).join("");
|
|
25
|
+
return `agent_${hex}`;
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
InMemoryAgentStore,
|
|
29
|
+
generateAgentName
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=agent-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-store.js","sources":["../../src/agent-store.ts"],"sourcesContent":["export interface AgentSession {\n name: string\n systemPrompt: string\n memory: Record<string, unknown>\n createdAt: number\n lastUsedAt: number\n}\n\nexport interface AgentStore {\n get: (name: string) => Promise<AgentSession | null>\n set: (name: string, session: AgentSession) => Promise<void>\n delete: (name: string) => Promise<void>\n list: () => Promise<Array<string>>\n}\n\nexport class InMemoryAgentStore implements AgentStore {\n private sessions = new Map<string, AgentSession>()\n\n get(name: string): Promise<AgentSession | null> {\n return Promise.resolve(this.sessions.get(name) ?? null)\n }\n\n set(name: string, session: AgentSession): Promise<void> {\n this.sessions.set(name, session)\n return Promise.resolve()\n }\n\n delete(name: string): Promise<void> {\n this.sessions.delete(name)\n return Promise.resolve()\n }\n\n list(): Promise<Array<string>> {\n return Promise.resolve(Array.from(this.sessions.keys()))\n }\n}\n\nexport function generateAgentName(): string {\n const hex = Array.from({ length: 8 }, () =>\n Math.floor(Math.random() * 16).toString(16),\n ).join('')\n return `agent_${hex}`\n}\n"],"names":[],"mappings":"AAeO,MAAM,mBAAyC;AAAA,EAA/C,cAAA;AACL,SAAQ,+BAAe,IAAA;AAAA,EAA0B;AAAA,EAEjD,IAAI,MAA4C;AAC9C,WAAO,QAAQ,QAAQ,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI;AAAA,EACxD;AAAA,EAEA,IAAI,MAAc,SAAsC;AACtD,SAAK,SAAS,IAAI,MAAM,OAAO;AAC/B,WAAO,QAAQ,QAAA;AAAA,EACjB;AAAA,EAEA,OAAO,MAA6B;AAClC,SAAK,SAAS,OAAO,IAAI;AACzB,WAAO,QAAQ,QAAA;AAAA,EACjB;AAAA,EAEA,OAA+B;AAC7B,WAAO,QAAQ,QAAQ,MAAM,KAAK,KAAK,SAAS,KAAA,CAAM,CAAC;AAAA,EACzD;AACF;AAEO,SAAS,oBAA4B;AAC1C,QAAM,MAAM,MAAM;AAAA,IAAK,EAAE,QAAQ,EAAA;AAAA,IAAK,MACpC,KAAK,MAAM,KAAK,WAAW,EAAE,EAAE,SAAS,EAAE;AAAA,EAAA,EAC1C,KAAK,EAAE;AACT,SAAO,SAAS,GAAG;AACrB;"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CodeModeTool, ToolBinding } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Convert an array of TanStack AI tools to a Record of ToolBindings
|
|
4
|
+
*
|
|
5
|
+
* @param tools - Array of tools to convert
|
|
6
|
+
* @param prefix - Optional prefix to add to binding names (e.g., 'external_')
|
|
7
|
+
*/
|
|
8
|
+
export declare function toolsToBindings(tools: Array<CodeModeTool>, prefix?: string): Record<string, ToolBinding>;
|
|
9
|
+
/**
|
|
10
|
+
* Convert a single TanStack AI tool to a ToolBinding
|
|
11
|
+
*
|
|
12
|
+
* @param tool - Tool to convert
|
|
13
|
+
* @param prefix - Optional prefix to add to binding name (e.g., 'external_')
|
|
14
|
+
* @throws Error if the tool doesn't have an execute function
|
|
15
|
+
*/
|
|
16
|
+
export declare function toolToBinding(tool: CodeModeTool, prefix?: string): ToolBinding;
|
|
17
|
+
/**
|
|
18
|
+
* Create event-aware bindings that emit custom events for each external function call.
|
|
19
|
+
* Wraps each binding's execute function to emit events before and after execution.
|
|
20
|
+
*
|
|
21
|
+
* @param bindings - Original tool bindings
|
|
22
|
+
* @param emitCustomEvent - Callback to emit custom events to the stream
|
|
23
|
+
*/
|
|
24
|
+
export declare function createEventAwareBindings(bindings: Record<string, ToolBinding>, emitCustomEvent: (eventName: string, data: Record<string, any>) => void): Record<string, ToolBinding>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { convertSchemaToJsonSchema } from "@tanstack/ai";
|
|
2
|
+
function toolsToBindings(tools, prefix = "") {
|
|
3
|
+
const bindings = {};
|
|
4
|
+
for (const tool of tools) {
|
|
5
|
+
const bindingName = `${prefix}${tool.name}`;
|
|
6
|
+
bindings[bindingName] = toolToBinding(tool, prefix);
|
|
7
|
+
}
|
|
8
|
+
return bindings;
|
|
9
|
+
}
|
|
10
|
+
function toolToBinding(tool, prefix = "") {
|
|
11
|
+
const inputSchema = convertSchemaToJsonSchema(tool.inputSchema) || {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {}
|
|
14
|
+
};
|
|
15
|
+
const outputSchema = tool.outputSchema ? convertSchemaToJsonSchema(tool.outputSchema) : void 0;
|
|
16
|
+
let execute;
|
|
17
|
+
if ("execute" in tool && typeof tool.execute === "function") {
|
|
18
|
+
const toolExecute = tool.execute;
|
|
19
|
+
execute = (args, context) => {
|
|
20
|
+
return Promise.resolve(toolExecute(args, context));
|
|
21
|
+
};
|
|
22
|
+
} else if ("__toolSide" in tool && tool.__toolSide === "definition") {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Tool "${tool.name}" is a ToolDefinition without an execute function. Call .server(fn) to provide an implementation before using with Code Mode.`
|
|
25
|
+
);
|
|
26
|
+
} else {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Tool "${tool.name}" does not have an execute function. Code Mode requires tools with implementations.`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
name: `${prefix}${tool.name}`,
|
|
33
|
+
description: tool.description,
|
|
34
|
+
inputSchema,
|
|
35
|
+
outputSchema,
|
|
36
|
+
execute
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function createEventAwareBindings(bindings, emitCustomEvent) {
|
|
40
|
+
const wrapped = {};
|
|
41
|
+
for (const [name, binding] of Object.entries(bindings)) {
|
|
42
|
+
wrapped[name] = {
|
|
43
|
+
...binding,
|
|
44
|
+
execute: async (args) => {
|
|
45
|
+
emitCustomEvent("code_mode:external_call", {
|
|
46
|
+
function: name,
|
|
47
|
+
args,
|
|
48
|
+
timestamp: Date.now()
|
|
49
|
+
});
|
|
50
|
+
const startTime = Date.now();
|
|
51
|
+
try {
|
|
52
|
+
const toolContext = { emitCustomEvent };
|
|
53
|
+
const result = await binding.execute(args, toolContext);
|
|
54
|
+
emitCustomEvent("code_mode:external_result", {
|
|
55
|
+
function: name,
|
|
56
|
+
result,
|
|
57
|
+
duration: Date.now() - startTime
|
|
58
|
+
});
|
|
59
|
+
return result;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
emitCustomEvent("code_mode:external_error", {
|
|
62
|
+
function: name,
|
|
63
|
+
error: error instanceof Error ? error.message : String(error),
|
|
64
|
+
duration: Date.now() - startTime
|
|
65
|
+
});
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return wrapped;
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
createEventAwareBindings,
|
|
75
|
+
toolToBinding,
|
|
76
|
+
toolsToBindings
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=tool-to-binding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-to-binding.js","sources":["../../../src/bindings/tool-to-binding.ts"],"sourcesContent":["import { convertSchemaToJsonSchema } from '@tanstack/ai'\nimport type { ToolExecutionContext } from '@tanstack/ai'\nimport type { CodeModeTool, ToolBinding } from '../types'\n\n/**\n * Convert an array of TanStack AI tools to a Record of ToolBindings\n *\n * @param tools - Array of tools to convert\n * @param prefix - Optional prefix to add to binding names (e.g., 'external_')\n */\nexport function toolsToBindings(\n tools: Array<CodeModeTool>,\n prefix: string = '',\n): Record<string, ToolBinding> {\n const bindings: Record<string, ToolBinding> = {}\n\n for (const tool of tools) {\n const bindingName = `${prefix}${tool.name}`\n bindings[bindingName] = toolToBinding(tool, prefix)\n }\n\n return bindings\n}\n\n/**\n * Convert a single TanStack AI tool to a ToolBinding\n *\n * @param tool - Tool to convert\n * @param prefix - Optional prefix to add to binding name (e.g., 'external_')\n * @throws Error if the tool doesn't have an execute function\n */\nexport function toolToBinding(\n tool: CodeModeTool,\n prefix: string = '',\n): ToolBinding {\n // Convert schemas (Zod or Standard Schema) to JSON Schema\n const inputSchema = convertSchemaToJsonSchema(tool.inputSchema) || {\n type: 'object',\n properties: {},\n }\n\n const outputSchema = tool.outputSchema\n ? convertSchemaToJsonSchema(tool.outputSchema)\n : undefined\n\n // Get execute function\n // ServerTool has execute, ToolDefinition (without .server()) does not\n let execute: (\n args: unknown,\n context?: ToolExecutionContext,\n ) => Promise<unknown>\n\n if ('execute' in tool && typeof tool.execute === 'function') {\n const toolExecute = tool.execute\n execute = (args: unknown, context?: ToolExecutionContext) => {\n // Pass context to the underlying tool so it can emit custom events\n return Promise.resolve(toolExecute(args, context))\n }\n } else if ('__toolSide' in tool && tool.__toolSide === 'definition') {\n throw new Error(\n `Tool \"${tool.name}\" is a ToolDefinition without an execute function. ` +\n `Call .server(fn) to provide an implementation before using with Code Mode.`,\n )\n } else {\n throw new Error(\n `Tool \"${tool.name}\" does not have an execute function. ` +\n `Code Mode requires tools with implementations.`,\n )\n }\n\n return {\n name: `${prefix}${tool.name}`,\n description: tool.description,\n inputSchema,\n outputSchema,\n execute,\n }\n}\n\n/**\n * Create event-aware bindings that emit custom events for each external function call.\n * Wraps each binding's execute function to emit events before and after execution.\n *\n * @param bindings - Original tool bindings\n * @param emitCustomEvent - Callback to emit custom events to the stream\n */\nexport function createEventAwareBindings(\n bindings: Record<string, ToolBinding>,\n emitCustomEvent: (eventName: string, data: Record<string, any>) => void,\n): Record<string, ToolBinding> {\n const wrapped: Record<string, ToolBinding> = {}\n\n for (const [name, binding] of Object.entries(bindings)) {\n wrapped[name] = {\n ...binding,\n execute: async (args: unknown) => {\n // Emit call event\n emitCustomEvent('code_mode:external_call', {\n function: name,\n args,\n timestamp: Date.now(),\n })\n\n const startTime = Date.now()\n try {\n // Create context for the underlying tool so it can also emit events\n const toolContext: ToolExecutionContext = { emitCustomEvent }\n const result = await binding.execute(args, toolContext)\n\n // Emit result event\n emitCustomEvent('code_mode:external_result', {\n function: name,\n result,\n duration: Date.now() - startTime,\n })\n\n return result\n } catch (error) {\n // Emit error event\n emitCustomEvent('code_mode:external_error', {\n function: name,\n error: error instanceof Error ? error.message : String(error),\n duration: Date.now() - startTime,\n })\n throw error\n }\n },\n }\n }\n\n return wrapped\n}\n"],"names":[],"mappings":";AAUO,SAAS,gBACd,OACA,SAAiB,IACY;AAC7B,QAAM,WAAwC,CAAA;AAE9C,aAAW,QAAQ,OAAO;AACxB,UAAM,cAAc,GAAG,MAAM,GAAG,KAAK,IAAI;AACzC,aAAS,WAAW,IAAI,cAAc,MAAM,MAAM;AAAA,EACpD;AAEA,SAAO;AACT;AASO,SAAS,cACd,MACA,SAAiB,IACJ;AAEb,QAAM,cAAc,0BAA0B,KAAK,WAAW,KAAK;AAAA,IACjE,MAAM;AAAA,IACN,YAAY,CAAA;AAAA,EAAC;AAGf,QAAM,eAAe,KAAK,eACtB,0BAA0B,KAAK,YAAY,IAC3C;AAIJ,MAAI;AAKJ,MAAI,aAAa,QAAQ,OAAO,KAAK,YAAY,YAAY;AAC3D,UAAM,cAAc,KAAK;AACzB,cAAU,CAAC,MAAe,YAAmC;AAE3D,aAAO,QAAQ,QAAQ,YAAY,MAAM,OAAO,CAAC;AAAA,IACnD;AAAA,EACF,WAAW,gBAAgB,QAAQ,KAAK,eAAe,cAAc;AACnE,UAAM,IAAI;AAAA,MACR,SAAS,KAAK,IAAI;AAAA,IAAA;AAAA,EAGtB,OAAO;AACL,UAAM,IAAI;AAAA,MACR,SAAS,KAAK,IAAI;AAAA,IAAA;AAAA,EAGtB;AAEA,SAAO;AAAA,IACL,MAAM,GAAG,MAAM,GAAG,KAAK,IAAI;AAAA,IAC3B,aAAa,KAAK;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AASO,SAAS,yBACd,UACA,iBAC6B;AAC7B,QAAM,UAAuC,CAAA;AAE7C,aAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACtD,YAAQ,IAAI,IAAI;AAAA,MACd,GAAG;AAAA,MACH,SAAS,OAAO,SAAkB;AAEhC,wBAAgB,2BAA2B;AAAA,UACzC,UAAU;AAAA,UACV;AAAA,UACA,WAAW,KAAK,IAAA;AAAA,QAAI,CACrB;AAED,cAAM,YAAY,KAAK,IAAA;AACvB,YAAI;AAEF,gBAAM,cAAoC,EAAE,gBAAA;AAC5C,gBAAM,SAAS,MAAM,QAAQ,QAAQ,MAAM,WAAW;AAGtD,0BAAgB,6BAA6B;AAAA,YAC3C,UAAU;AAAA,YACV;AAAA,YACA,UAAU,KAAK,QAAQ;AAAA,UAAA,CACxB;AAED,iBAAO;AAAA,QACT,SAAS,OAAO;AAEd,0BAAgB,4BAA4B;AAAA,YAC1C,UAAU;AAAA,YACV,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,YAC5D,UAAU,KAAK,QAAQ;AAAA,UAAA,CACxB;AACD,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IAAA;AAAA,EAEJ;AAEA,SAAO;AACT;"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap user code in an async IIFE to support top-level await
|
|
3
|
+
*
|
|
4
|
+
* The code is wrapped so that:
|
|
5
|
+
* 1. Top-level await works
|
|
6
|
+
* 2. Return values are captured and serialized (for isolate boundary transfer)
|
|
7
|
+
* 3. Errors propagate correctly
|
|
8
|
+
*/
|
|
9
|
+
export declare function wrapCode(code: string): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function wrapCode(code) {
|
|
2
|
+
return `
|
|
3
|
+
(async function() {
|
|
4
|
+
try {
|
|
5
|
+
const __userResult = await (async function() {
|
|
6
|
+
${code}
|
|
7
|
+
})();
|
|
8
|
+
return JSON.stringify(__userResult);
|
|
9
|
+
} catch (__error) {
|
|
10
|
+
throw __error;
|
|
11
|
+
}
|
|
12
|
+
})()
|
|
13
|
+
`;
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
wrapCode
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=code-wrapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-wrapper.js","sources":["../../src/code-wrapper.ts"],"sourcesContent":["/**\n * Wrap user code in an async IIFE to support top-level await\n *\n * The code is wrapped so that:\n * 1. Top-level await works\n * 2. Return values are captured and serialized (for isolate boundary transfer)\n * 3. Errors propagate correctly\n */\nexport function wrapCode(code: string): string {\n // Results must be serialized to JSON because objects can't be transferred\n // directly across the isolate boundary in isolated-vm\n return `\n(async function() {\n try {\n const __userResult = await (async function() {\n${code}\n })();\n return JSON.stringify(__userResult);\n } catch (__error) {\n throw __error;\n }\n})()\n`\n}\n"],"names":[],"mappings":"AAQO,SAAS,SAAS,MAAsB;AAG7C,SAAO;AAAA;AAAA;AAAA;AAAA,EAIP,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQN;"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ServerTool } from '@tanstack/ai';
|
|
3
|
+
import { CodeModeToolConfig } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Schema for the execute_typescript tool input
|
|
6
|
+
*/
|
|
7
|
+
declare const executeTypescriptInputSchema: z.ZodObject<{
|
|
8
|
+
typescriptCode: z.ZodString;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
/**
|
|
11
|
+
* Schema for the execute_typescript tool output
|
|
12
|
+
*/
|
|
13
|
+
declare const executeTypescriptOutputSchema: z.ZodObject<{
|
|
14
|
+
success: z.ZodBoolean;
|
|
15
|
+
result: z.ZodOptional<z.ZodUnknown>;
|
|
16
|
+
logs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
17
|
+
error: z.ZodOptional<z.ZodObject<{
|
|
18
|
+
message: z.ZodString;
|
|
19
|
+
name: z.ZodOptional<z.ZodString>;
|
|
20
|
+
line: z.ZodOptional<z.ZodNumber>;
|
|
21
|
+
}, z.core.$strip>>;
|
|
22
|
+
}, z.core.$strip>;
|
|
23
|
+
export type ExecuteTypescriptInput = z.infer<typeof executeTypescriptInputSchema>;
|
|
24
|
+
export type ExecuteTypescriptOutput = z.infer<typeof executeTypescriptOutputSchema>;
|
|
25
|
+
/**
|
|
26
|
+
* Create an execute_typescript tool that can be used alongside other agent tools.
|
|
27
|
+
*
|
|
28
|
+
* This tool allows an LLM to execute TypeScript code in a secure sandbox.
|
|
29
|
+
* Tools passed in the config become `external_*` functions available inside the sandbox.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* import { createCodeMode } from '@tanstack/ai-code-mode'
|
|
34
|
+
* import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
|
|
35
|
+
*
|
|
36
|
+
* const { tool, systemPrompt } = createCodeMode({
|
|
37
|
+
* driver: createNodeIsolateDriver(),
|
|
38
|
+
* tools: [weatherTool, dbTool], // Become external_fetchWeather, external_dbQuery
|
|
39
|
+
* timeout: 30000,
|
|
40
|
+
* })
|
|
41
|
+
*
|
|
42
|
+
* chat({
|
|
43
|
+
* systemPrompts: [myPrompt, systemPrompt],
|
|
44
|
+
* tools: [tool, searchTool, emailTool],
|
|
45
|
+
* messages,
|
|
46
|
+
* })
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function createCodeModeTool(config: CodeModeToolConfig): ServerTool<typeof executeTypescriptInputSchema, typeof executeTypescriptOutputSchema, 'execute_typescript'>;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { toolDefinition } from "@tanstack/ai";
|
|
3
|
+
import { toolsToBindings, createEventAwareBindings } from "./bindings/tool-to-binding.js";
|
|
4
|
+
import { stripTypeScript } from "./strip-typescript.js";
|
|
5
|
+
const executeTypescriptInputSchema = z.object({
|
|
6
|
+
typescriptCode: z.string().describe(
|
|
7
|
+
"TypeScript code to execute in the sandbox. Use external_* functions to call available APIs. Return a value to pass results back."
|
|
8
|
+
)
|
|
9
|
+
});
|
|
10
|
+
const executeTypescriptOutputSchema = z.object({
|
|
11
|
+
success: z.boolean().describe("Whether execution completed without errors"),
|
|
12
|
+
result: z.unknown().optional().describe("Return value from the executed code"),
|
|
13
|
+
logs: z.array(z.string()).optional().describe("Console output captured during execution"),
|
|
14
|
+
error: z.object({
|
|
15
|
+
message: z.string(),
|
|
16
|
+
name: z.string().optional(),
|
|
17
|
+
line: z.number().optional()
|
|
18
|
+
}).optional().describe("Error details if execution failed")
|
|
19
|
+
});
|
|
20
|
+
function createCodeModeTool(config) {
|
|
21
|
+
const {
|
|
22
|
+
driver,
|
|
23
|
+
tools,
|
|
24
|
+
timeout = 3e4,
|
|
25
|
+
memoryLimit = 128,
|
|
26
|
+
getSkillBindings
|
|
27
|
+
} = config;
|
|
28
|
+
if (tools.length === 0) {
|
|
29
|
+
throw new Error("At least one tool must be provided to createCodeModeTool");
|
|
30
|
+
}
|
|
31
|
+
const staticBindings = toolsToBindings(tools, "external_");
|
|
32
|
+
const definition = toolDefinition({
|
|
33
|
+
name: "execute_typescript",
|
|
34
|
+
description: buildToolDescription(tools),
|
|
35
|
+
inputSchema: executeTypescriptInputSchema,
|
|
36
|
+
outputSchema: executeTypescriptOutputSchema
|
|
37
|
+
});
|
|
38
|
+
return definition.server(
|
|
39
|
+
async (input, toolContext) => {
|
|
40
|
+
const { typescriptCode } = input;
|
|
41
|
+
const emitCustomEvent = toolContext?.emitCustomEvent || (() => {
|
|
42
|
+
});
|
|
43
|
+
if (!typescriptCode || typeof typescriptCode !== "string") {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
error: {
|
|
47
|
+
message: "typescriptCode must be a non-empty string",
|
|
48
|
+
name: "ValidationError"
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
let isolateContext = null;
|
|
53
|
+
emitCustomEvent("code_mode:execution_started", {
|
|
54
|
+
timestamp: Date.now(),
|
|
55
|
+
codeLength: typescriptCode.length
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
let strippedCode;
|
|
59
|
+
try {
|
|
60
|
+
strippedCode = await stripTypeScript(typescriptCode);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
error: {
|
|
65
|
+
message: error instanceof Error ? error.message : String(error),
|
|
66
|
+
name: "TypeScriptError"
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const skillBindings = getSkillBindings ? await getSkillBindings() : {};
|
|
71
|
+
const allBindings = { ...staticBindings, ...skillBindings };
|
|
72
|
+
const eventAwareBindings = createEventAwareBindings(
|
|
73
|
+
allBindings,
|
|
74
|
+
emitCustomEvent
|
|
75
|
+
);
|
|
76
|
+
isolateContext = await driver.createContext({
|
|
77
|
+
bindings: eventAwareBindings,
|
|
78
|
+
timeout,
|
|
79
|
+
memoryLimit
|
|
80
|
+
});
|
|
81
|
+
const executionResult = await isolateContext.execute(strippedCode);
|
|
82
|
+
if (executionResult.logs && executionResult.logs.length > 0) {
|
|
83
|
+
for (const log of executionResult.logs) {
|
|
84
|
+
let level = "log";
|
|
85
|
+
let message = log;
|
|
86
|
+
if (log.startsWith("ERROR: ")) {
|
|
87
|
+
level = "error";
|
|
88
|
+
message = log.slice(7);
|
|
89
|
+
} else if (log.startsWith("WARN: ")) {
|
|
90
|
+
level = "warn";
|
|
91
|
+
message = log.slice(6);
|
|
92
|
+
} else if (log.startsWith("INFO: ")) {
|
|
93
|
+
level = "info";
|
|
94
|
+
message = log.slice(6);
|
|
95
|
+
}
|
|
96
|
+
emitCustomEvent("code_mode:console", {
|
|
97
|
+
level,
|
|
98
|
+
message,
|
|
99
|
+
timestamp: Date.now()
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (executionResult.success) {
|
|
104
|
+
return {
|
|
105
|
+
success: true,
|
|
106
|
+
result: executionResult.value,
|
|
107
|
+
logs: executionResult.logs
|
|
108
|
+
};
|
|
109
|
+
} else {
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
error: executionResult.error ? {
|
|
113
|
+
message: executionResult.error.message,
|
|
114
|
+
name: executionResult.error.name
|
|
115
|
+
} : { message: "Unknown execution error" },
|
|
116
|
+
logs: executionResult.logs
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
return {
|
|
121
|
+
success: false,
|
|
122
|
+
error: {
|
|
123
|
+
message: error instanceof Error ? error.message : String(error),
|
|
124
|
+
name: error instanceof Error ? error.name : "Error"
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
} finally {
|
|
128
|
+
if (isolateContext) {
|
|
129
|
+
await isolateContext.dispose();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
function buildToolDescription(tools) {
|
|
136
|
+
const externalFunctions = tools.map((t) => `external_${t.name}`).join(", ");
|
|
137
|
+
return `Execute TypeScript code in a secure sandbox environment. The code can use these external API functions: ${externalFunctions}. All external_* calls are async and must be awaited. Return a value to pass results back. Use console.log() for debugging.`;
|
|
138
|
+
}
|
|
139
|
+
export {
|
|
140
|
+
createCodeModeTool
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=create-code-mode-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-code-mode-tool.js","sources":["../../src/create-code-mode-tool.ts"],"sourcesContent":["import { z } from 'zod'\nimport { toolDefinition } from '@tanstack/ai'\nimport {\n createEventAwareBindings,\n toolsToBindings,\n} from './bindings/tool-to-binding'\nimport { stripTypeScript } from './strip-typescript'\nimport type { ServerTool, ToolExecutionContext } from '@tanstack/ai'\nimport type {\n CodeModeTool,\n CodeModeToolConfig,\n CodeModeToolResult,\n IsolateContext,\n} from './types'\n\n/**\n * Schema for the execute_typescript tool input\n */\nconst executeTypescriptInputSchema = z.object({\n typescriptCode: z\n .string()\n .describe(\n 'TypeScript code to execute in the sandbox. ' +\n 'Use external_* functions to call available APIs. ' +\n 'Return a value to pass results back.',\n ),\n})\n\n/**\n * Schema for the execute_typescript tool output\n */\nconst executeTypescriptOutputSchema = z.object({\n success: z.boolean().describe('Whether execution completed without errors'),\n result: z\n .unknown()\n .optional()\n .describe('Return value from the executed code'),\n logs: z\n .array(z.string())\n .optional()\n .describe('Console output captured during execution'),\n error: z\n .object({\n message: z.string(),\n name: z.string().optional(),\n line: z.number().optional(),\n })\n .optional()\n .describe('Error details if execution failed'),\n})\n\nexport type ExecuteTypescriptInput = z.infer<\n typeof executeTypescriptInputSchema\n>\nexport type ExecuteTypescriptOutput = z.infer<\n typeof executeTypescriptOutputSchema\n>\n\n/**\n * Create an execute_typescript tool that can be used alongside other agent tools.\n *\n * This tool allows an LLM to execute TypeScript code in a secure sandbox.\n * Tools passed in the config become `external_*` functions available inside the sandbox.\n *\n * @example\n * ```typescript\n * import { createCodeMode } from '@tanstack/ai-code-mode'\n * import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'\n *\n * const { tool, systemPrompt } = createCodeMode({\n * driver: createNodeIsolateDriver(),\n * tools: [weatherTool, dbTool], // Become external_fetchWeather, external_dbQuery\n * timeout: 30000,\n * })\n *\n * chat({\n * systemPrompts: [myPrompt, systemPrompt],\n * tools: [tool, searchTool, emailTool],\n * messages,\n * })\n * ```\n */\nexport function createCodeModeTool(\n config: CodeModeToolConfig,\n): ServerTool<\n typeof executeTypescriptInputSchema,\n typeof executeTypescriptOutputSchema,\n 'execute_typescript'\n> {\n const {\n driver,\n tools,\n timeout = 30000,\n memoryLimit = 128,\n getSkillBindings,\n } = config\n\n // Validate tools\n if (tools.length === 0) {\n throw new Error('At least one tool must be provided to createCodeModeTool')\n }\n\n // Transform tools to bindings with external_ prefix (static bindings)\n const staticBindings = toolsToBindings(tools, 'external_')\n\n // Create the tool definition\n const definition = toolDefinition({\n name: 'execute_typescript' as const,\n description: buildToolDescription(tools),\n inputSchema: executeTypescriptInputSchema,\n outputSchema: executeTypescriptOutputSchema,\n })\n\n // Return server tool with execute function that accepts context\n return definition.server(\n async (\n input,\n toolContext?: ToolExecutionContext,\n ): Promise<CodeModeToolResult> => {\n const { typescriptCode } = input\n\n // Get emitCustomEvent from context or use no-op\n const emitCustomEvent = toolContext?.emitCustomEvent || (() => {})\n\n if (!typescriptCode || typeof typescriptCode !== 'string') {\n return {\n success: false,\n error: {\n message: 'typescriptCode must be a non-empty string',\n name: 'ValidationError',\n },\n }\n }\n\n // Create a fresh sandbox context for this execution\n let isolateContext: IsolateContext | null = null\n\n // Emit execution started event immediately\n emitCustomEvent('code_mode:execution_started', {\n timestamp: Date.now(),\n codeLength: typescriptCode.length,\n })\n\n try {\n // Step 1: Strip TypeScript (also serves as syntax validation via esbuild)\n let strippedCode: string\n try {\n strippedCode = await stripTypeScript(typescriptCode)\n } catch (error) {\n // Type/syntax error from esbuild\n return {\n success: false,\n error: {\n message: error instanceof Error ? error.message : String(error),\n name: 'TypeScriptError',\n },\n }\n }\n\n // Step 2: Get dynamic skill bindings if available\n const skillBindings = getSkillBindings ? await getSkillBindings() : {}\n\n // Step 3: Merge static and dynamic bindings, then wrap with event awareness\n const allBindings = { ...staticBindings, ...skillBindings }\n const eventAwareBindings = createEventAwareBindings(\n allBindings,\n emitCustomEvent,\n )\n\n // Step 4: Create sandbox context with event-aware bindings\n isolateContext = await driver.createContext({\n bindings: eventAwareBindings,\n timeout,\n memoryLimit,\n })\n\n // Step 5: Execute the code in the sandbox\n const executionResult = await isolateContext.execute(strippedCode)\n\n // Emit console logs as custom events\n if (executionResult.logs && executionResult.logs.length > 0) {\n for (const log of executionResult.logs) {\n // Parse log level from prefix (added by sandbox console implementation)\n let level: 'log' | 'warn' | 'error' | 'info' = 'log'\n let message = log\n\n if (log.startsWith('ERROR: ')) {\n level = 'error'\n message = log.slice(7)\n } else if (log.startsWith('WARN: ')) {\n level = 'warn'\n message = log.slice(6)\n } else if (log.startsWith('INFO: ')) {\n level = 'info'\n message = log.slice(6)\n }\n\n emitCustomEvent('code_mode:console', {\n level,\n message,\n timestamp: Date.now(),\n })\n }\n }\n\n if (executionResult.success) {\n return {\n success: true,\n result: executionResult.value,\n logs: executionResult.logs,\n }\n } else {\n return {\n success: false,\n error: executionResult.error\n ? {\n message: executionResult.error.message,\n name: executionResult.error.name,\n }\n : { message: 'Unknown execution error' },\n logs: executionResult.logs,\n }\n }\n } catch (error) {\n return {\n success: false,\n error: {\n message: error instanceof Error ? error.message : String(error),\n name: error instanceof Error ? error.name : 'Error',\n },\n }\n } finally {\n // Always clean up the sandbox context\n if (isolateContext) {\n await isolateContext.dispose()\n }\n }\n },\n )\n}\n\n/**\n * Build the tool description including available external functions\n */\nfunction buildToolDescription(tools: Array<CodeModeTool>): string {\n const externalFunctions = tools.map((t) => `external_${t.name}`).join(', ')\n\n return (\n `Execute TypeScript code in a secure sandbox environment. ` +\n `The code can use these external API functions: ${externalFunctions}. ` +\n `All external_* calls are async and must be awaited. ` +\n `Return a value to pass results back. Use console.log() for debugging.`\n )\n}\n"],"names":[],"mappings":"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"}
|