@tanstack/ai-code-mode 0.3.9 → 0.3.11
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 +11 -7
- package/dist/esm/create-code-mode-tool.d.ts +1 -0
- package/dist/esm/create-code-mode-tool.js +63 -20
- package/dist/esm/create-code-mode-tool.js.map +1 -1
- package/dist/esm/types.d.ts +1 -0
- package/package.json +3 -3
- package/skills/ai-code-mode/SKILL.md +23 -8
- package/src/create-code-mode-tool.ts +113 -33
- package/src/types.ts +1 -0
package/README.md
CHANGED
|
@@ -21,6 +21,9 @@ pnpm add @tanstack/ai-isolate-node
|
|
|
21
21
|
# QuickJS WASM (browser-compatible, no native deps)
|
|
22
22
|
pnpm add @tanstack/ai-isolate-quickjs
|
|
23
23
|
|
|
24
|
+
# Bun servers (native QuickJS via bun:ffi)
|
|
25
|
+
bun add @tanstack/ai-isolate-quickjs-bun
|
|
26
|
+
|
|
24
27
|
# Cloudflare Workers (edge execution)
|
|
25
28
|
pnpm add @tanstack/ai-isolate-cloudflare
|
|
26
29
|
```
|
|
@@ -82,10 +85,10 @@ Creates both the `execute_typescript` tool and its matching system prompt. This
|
|
|
82
85
|
|
|
83
86
|
**Config:**
|
|
84
87
|
|
|
85
|
-
- `driver` — An `IsolateDriver` (Node, QuickJS, or Cloudflare)
|
|
88
|
+
- `driver` — An `IsolateDriver` (Node, QuickJS, QuickJS Bun, or Cloudflare)
|
|
86
89
|
- `tools` — Array of `ServerTool` or `ToolDefinition` instances. Exposed as `external_*` functions in the sandbox
|
|
87
90
|
- `timeout` — Execution timeout in ms (default: 30000)
|
|
88
|
-
- `memoryLimit` — Memory limit in MB (default: 128, supported by Node and QuickJS drivers)
|
|
91
|
+
- `memoryLimit` — Memory limit in MB (default: 128, supported by the Node, QuickJS, and QuickJS Bun drivers)
|
|
89
92
|
- `getSkillBindings` — Optional async function returning dynamic bindings
|
|
90
93
|
|
|
91
94
|
### `createCodeModeTool(config)` / `createCodeModeSystemPrompt(config)`
|
|
@@ -102,11 +105,12 @@ These utilities are used internally and exported for custom pipelines:
|
|
|
102
105
|
|
|
103
106
|
## Driver Selection Guide
|
|
104
107
|
|
|
105
|
-
| Driver
|
|
106
|
-
|
|
|
107
|
-
| `@tanstack/ai-isolate-node`
|
|
108
|
-
| `@tanstack/ai-isolate-quickjs`
|
|
109
|
-
| `@tanstack/ai-isolate-
|
|
108
|
+
| Driver | Best For | Native Deps | Browser | Memory Limit |
|
|
109
|
+
| ---------------------------------- | -------------------------------------------- | ------------------- | ------- | ------------ |
|
|
110
|
+
| `@tanstack/ai-isolate-node` | Server-side Node.js apps | Yes (`isolated-vm`) | No | Yes |
|
|
111
|
+
| `@tanstack/ai-isolate-quickjs` | Browser, edge, or no-native-dep environments | No (WASM) | Yes | Yes |
|
|
112
|
+
| `@tanstack/ai-isolate-quickjs-bun` | Bun servers (native QuickJS via `bun:ffi`) | No | No | Yes |
|
|
113
|
+
| `@tanstack/ai-isolate-cloudflare` | Cloudflare Workers deployments | No | N/A | N/A |
|
|
110
114
|
|
|
111
115
|
## Custom Events
|
|
112
116
|
|
|
@@ -18,6 +18,7 @@ declare const executeTypescriptOutputSchema: z.ZodObject<{
|
|
|
18
18
|
message: z.ZodString;
|
|
19
19
|
name: z.ZodOptional<z.ZodString>;
|
|
20
20
|
line: z.ZodOptional<z.ZodNumber>;
|
|
21
|
+
stack: z.ZodOptional<z.ZodString>;
|
|
21
22
|
}, z.core.$strip>>;
|
|
22
23
|
}, z.core.$strip>;
|
|
23
24
|
export type ExecuteTypescriptInput = z.infer<typeof executeTypescriptInputSchema>;
|
|
@@ -18,7 +18,8 @@ var executeTypescriptOutputSchema = z.object({
|
|
|
18
18
|
error: z.object({
|
|
19
19
|
message: z.string(),
|
|
20
20
|
name: z.string().optional(),
|
|
21
|
-
line: z.number().optional()
|
|
21
|
+
line: z.number().optional(),
|
|
22
|
+
stack: z.string().optional()
|
|
22
23
|
}).optional().describe("Error details if execution failed")
|
|
23
24
|
});
|
|
24
25
|
/**
|
|
@@ -61,14 +62,39 @@ function createCodeModeTool(config) {
|
|
|
61
62
|
outputSchema: executeTypescriptOutputSchema
|
|
62
63
|
}).server(async (input, toolContext) => {
|
|
63
64
|
const { typescriptCode } = input;
|
|
65
|
+
const startedAt = Date.now();
|
|
64
66
|
const emitCustomEvent = toolContext?.emitCustomEvent || (() => {});
|
|
65
|
-
|
|
67
|
+
const finish = (result, phase) => {
|
|
68
|
+
const durationMs = Date.now() - startedAt;
|
|
69
|
+
const payload = {
|
|
70
|
+
timestamp: Date.now(),
|
|
71
|
+
durationMs,
|
|
72
|
+
phase,
|
|
73
|
+
success: result.success,
|
|
74
|
+
logCount: result.logs?.length ?? 0,
|
|
75
|
+
error: result.error ? {
|
|
76
|
+
name: result.error.name,
|
|
77
|
+
message: result.error.message,
|
|
78
|
+
...result.error.stack !== void 0 && { stack: result.error.stack },
|
|
79
|
+
...result.error.line !== void 0 && { line: result.error.line }
|
|
80
|
+
} : void 0
|
|
81
|
+
};
|
|
82
|
+
emitCustomEvent("code_mode:execution_finished", payload);
|
|
83
|
+
if (!result.success) console.error("[code-mode] execute_typescript failed", payload);
|
|
84
|
+
else if (typeof process !== "undefined" && process.env?.CODE_MODE_DEBUG === "1") console.info("[code-mode] execute_typescript ok", {
|
|
85
|
+
durationMs,
|
|
86
|
+
phase,
|
|
87
|
+
logCount: payload.logCount
|
|
88
|
+
});
|
|
89
|
+
return result;
|
|
90
|
+
};
|
|
91
|
+
if (!typescriptCode || typeof typescriptCode !== "string") return finish({
|
|
66
92
|
success: false,
|
|
67
93
|
error: {
|
|
68
94
|
message: "typescriptCode must be a non-empty string",
|
|
69
95
|
name: "ValidationError"
|
|
70
96
|
}
|
|
71
|
-
};
|
|
97
|
+
}, "validate-input");
|
|
72
98
|
let isolateContext = null;
|
|
73
99
|
emitCustomEvent("code_mode:execution_started", {
|
|
74
100
|
timestamp: Date.now(),
|
|
@@ -79,13 +105,14 @@ function createCodeModeTool(config) {
|
|
|
79
105
|
try {
|
|
80
106
|
strippedCode = await transpile(typescriptCode);
|
|
81
107
|
} catch (error) {
|
|
82
|
-
return {
|
|
108
|
+
return finish({
|
|
83
109
|
success: false,
|
|
84
110
|
error: {
|
|
85
111
|
message: error instanceof Error ? error.message : String(error),
|
|
86
|
-
name: "TypeScriptError"
|
|
112
|
+
name: "TypeScriptError",
|
|
113
|
+
...error instanceof Error && error.stack !== void 0 && { stack: error.stack }
|
|
87
114
|
}
|
|
88
|
-
};
|
|
115
|
+
}, "transpile");
|
|
89
116
|
}
|
|
90
117
|
const skillBindings = getSkillBindings ? await getSkillBindings() : {};
|
|
91
118
|
const skillBindingValues = Object.values(skillBindings);
|
|
@@ -97,11 +124,22 @@ function createCodeModeTool(config) {
|
|
|
97
124
|
...staticBindings,
|
|
98
125
|
...skillBindings
|
|
99
126
|
}, emitCustomEvent);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
127
|
+
try {
|
|
128
|
+
isolateContext = await driver.createContext({
|
|
129
|
+
bindings: eventAwareBindings,
|
|
130
|
+
timeout,
|
|
131
|
+
memoryLimit
|
|
132
|
+
});
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return finish({
|
|
135
|
+
success: false,
|
|
136
|
+
error: {
|
|
137
|
+
message: error instanceof Error ? error.message : String(error),
|
|
138
|
+
name: error instanceof Error ? error.name : "CreateContextError",
|
|
139
|
+
...error instanceof Error && error.stack !== void 0 && { stack: error.stack }
|
|
140
|
+
}
|
|
141
|
+
}, "create-context");
|
|
142
|
+
}
|
|
105
143
|
const executionResult = await isolateContext.execute(strippedCode);
|
|
106
144
|
if (executionResult.logs && executionResult.logs.length > 0) for (const log of executionResult.logs) {
|
|
107
145
|
let level = "log";
|
|
@@ -122,27 +160,32 @@ function createCodeModeTool(config) {
|
|
|
122
160
|
timestamp: Date.now()
|
|
123
161
|
});
|
|
124
162
|
}
|
|
125
|
-
if (executionResult.success) return {
|
|
163
|
+
if (executionResult.success) return finish({
|
|
126
164
|
success: true,
|
|
127
165
|
result: executionResult.value,
|
|
128
166
|
logs: executionResult.logs
|
|
129
|
-
};
|
|
130
|
-
|
|
167
|
+
}, "execute");
|
|
168
|
+
return finish({
|
|
131
169
|
success: false,
|
|
132
170
|
error: executionResult.error ? {
|
|
133
171
|
message: executionResult.error.message,
|
|
134
|
-
name: executionResult.error.name
|
|
135
|
-
|
|
172
|
+
name: executionResult.error.name,
|
|
173
|
+
...executionResult.error.stack !== void 0 && { stack: executionResult.error.stack }
|
|
174
|
+
} : {
|
|
175
|
+
message: "Unknown execution error",
|
|
176
|
+
name: "UnknownError"
|
|
177
|
+
},
|
|
136
178
|
logs: executionResult.logs
|
|
137
|
-
};
|
|
179
|
+
}, "execute");
|
|
138
180
|
} catch (error) {
|
|
139
|
-
return {
|
|
181
|
+
return finish({
|
|
140
182
|
success: false,
|
|
141
183
|
error: {
|
|
142
184
|
message: error instanceof Error ? error.message : String(error),
|
|
143
|
-
name: error instanceof Error ? error.name : "Error"
|
|
185
|
+
name: error instanceof Error ? error.name : "Error",
|
|
186
|
+
...error instanceof Error && error.stack !== void 0 && { stack: error.stack }
|
|
144
187
|
}
|
|
145
|
-
};
|
|
188
|
+
}, "unhandled");
|
|
146
189
|
} finally {
|
|
147
190
|
if (isolateContext) await isolateContext.dispose();
|
|
148
191
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-code-mode-tool.js","names":[],"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 { warnIfBindingsExposeSecrets } from './validate-bindings'\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 onSecretParameter,\n transpile = stripTypeScript,\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 // Shared across static + dynamic (skill) binding scans so a given\n // (toolName, paramPath) pair surfaces at most once per code-mode instance.\n const secretDedupCache = new Set<string>()\n\n warnIfBindingsExposeSecrets(Object.values(staticBindings), {\n handler: onSecretParameter,\n dedupCache: secretDedupCache,\n })\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 the\n // transpiler — sucrase by default, or a user-supplied `transpile`)\n let strippedCode: string\n try {\n strippedCode = await transpile(typescriptCode)\n } catch (error) {\n // Type/syntax error from the transpiler\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 // Scan dynamic bindings too — their schemas are equally in-scope for\n // the same exfiltration threat. Dedup cache prevents repeat warnings\n // when the same binding reappears across executions.\n const skillBindingValues = Object.values(skillBindings)\n if (skillBindingValues.length > 0) {\n warnIfBindingsExposeSecrets(skillBindingValues, {\n handler: onSecretParameter,\n dedupCache: secretDedupCache,\n })\n }\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 eager = tools.filter((t) => !t.lazy)\n const hasLazy = tools.some((t) => t.lazy)\n const externalFunctions = eager.map((t) => `external_${t.name}`).join(', ')\n\n const discoverable = hasLazy\n ? ` Additional functions can be discovered via the discover_tools tool.`\n : ''\n\n return (\n `Execute TypeScript code in a secure sandbox environment. ` +\n `The code can use these external API functions: ${externalFunctions}.${discoverable} ` +\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"create-code-mode-tool.js","names":[],"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 { warnIfBindingsExposeSecrets } from './validate-bindings'\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 stack: z.string().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 onSecretParameter,\n transpile = stripTypeScript,\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 // Shared across static + dynamic (skill) binding scans so a given\n // (toolName, paramPath) pair surfaces at most once per code-mode instance.\n const secretDedupCache = new Set<string>()\n\n warnIfBindingsExposeSecrets(Object.values(staticBindings), {\n handler: onSecretParameter,\n dedupCache: secretDedupCache,\n })\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 const startedAt = Date.now()\n\n // Get emitCustomEvent from context or use no-op\n const emitCustomEvent = toolContext?.emitCustomEvent || (() => {})\n\n const finish = (\n result: CodeModeToolResult,\n phase: string,\n ): CodeModeToolResult => {\n const durationMs = Date.now() - startedAt\n const payload = {\n timestamp: Date.now(),\n durationMs,\n phase,\n success: result.success,\n logCount: result.logs?.length ?? 0,\n error: result.error\n ? {\n name: result.error.name,\n message: result.error.message,\n ...(result.error.stack !== undefined && {\n stack: result.error.stack,\n }),\n ...(result.error.line !== undefined && {\n line: result.error.line,\n }),\n }\n : undefined,\n }\n emitCustomEvent('code_mode:execution_finished', payload)\n if (!result.success) {\n console.error('[code-mode] execute_typescript failed', payload)\n } else if (\n typeof process !== 'undefined' &&\n process.env?.CODE_MODE_DEBUG === '1'\n ) {\n console.info('[code-mode] execute_typescript ok', {\n durationMs,\n phase,\n logCount: payload.logCount,\n })\n }\n return result\n }\n\n if (!typescriptCode || typeof typescriptCode !== 'string') {\n return finish(\n {\n success: false,\n error: {\n message: 'typescriptCode must be a non-empty string',\n name: 'ValidationError',\n },\n },\n 'validate-input',\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 the\n // transpiler — sucrase by default, or a user-supplied `transpile`)\n let strippedCode: string\n try {\n strippedCode = await transpile(typescriptCode)\n } catch (error) {\n // Type/syntax error from the transpiler\n return finish(\n {\n success: false,\n error: {\n message: error instanceof Error ? error.message : String(error),\n name: 'TypeScriptError',\n ...(error instanceof Error &&\n error.stack !== undefined && { stack: error.stack }),\n },\n },\n 'transpile',\n )\n }\n\n // Step 2: Get dynamic skill bindings if available\n const skillBindings = getSkillBindings ? await getSkillBindings() : {}\n\n // Scan dynamic bindings too — their schemas are equally in-scope for\n // the same exfiltration threat. Dedup cache prevents repeat warnings\n // when the same binding reappears across executions.\n const skillBindingValues = Object.values(skillBindings)\n if (skillBindingValues.length > 0) {\n warnIfBindingsExposeSecrets(skillBindingValues, {\n handler: onSecretParameter,\n dedupCache: secretDedupCache,\n })\n }\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 try {\n isolateContext = await driver.createContext({\n bindings: eventAwareBindings,\n timeout,\n memoryLimit,\n })\n } catch (error) {\n return finish(\n {\n success: false,\n error: {\n message: error instanceof Error ? error.message : String(error),\n name:\n error instanceof Error ? error.name : 'CreateContextError',\n ...(error instanceof Error &&\n error.stack !== undefined && { stack: error.stack }),\n },\n },\n 'create-context',\n )\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 finish(\n {\n success: true,\n result: executionResult.value,\n logs: executionResult.logs,\n },\n 'execute',\n )\n }\n\n return finish(\n {\n success: false,\n error: executionResult.error\n ? {\n message: executionResult.error.message,\n name: executionResult.error.name,\n ...(executionResult.error.stack !== undefined && {\n stack: executionResult.error.stack,\n }),\n }\n : { message: 'Unknown execution error', name: 'UnknownError' },\n logs: executionResult.logs,\n },\n 'execute',\n )\n } catch (error) {\n return finish(\n {\n success: false,\n error: {\n message: error instanceof Error ? error.message : String(error),\n name: error instanceof Error ? error.name : 'Error',\n ...(error instanceof Error &&\n error.stack !== undefined && { stack: error.stack }),\n },\n },\n 'unhandled',\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 eager = tools.filter((t) => !t.lazy)\n const hasLazy = tools.some((t) => t.lazy)\n const externalFunctions = eager.map((t) => `external_${t.name}`).join(', ')\n\n const discoverable = hasLazy\n ? ` Additional functions can be discovered via the discover_tools tool.`\n : ''\n\n return (\n `Execute TypeScript code in a secure sandbox environment. ` +\n `The code can use these external API functions: ${externalFunctions}.${discoverable} ` +\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"],"mappings":"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"}
|
package/dist/esm/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-code-mode",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.11",
|
|
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.44.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@vitest/coverage-v8": "4.0.14",
|
|
61
61
|
"zod": "^4.2.0",
|
|
62
|
-
"@tanstack/ai": "0.
|
|
62
|
+
"@tanstack/ai": "0.44.0"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "vite build",
|
|
@@ -3,7 +3,8 @@ name: ai-code-mode
|
|
|
3
3
|
description: >
|
|
4
4
|
LLM-generated TypeScript execution in sandboxed environments:
|
|
5
5
|
createCodeModeTool() with isolate drivers (createNodeIsolateDriver,
|
|
6
|
-
createQuickJSIsolateDriver,
|
|
6
|
+
createQuickJSIsolateDriver, createQuickJSBunIsolateDriver,
|
|
7
|
+
createCloudflareIsolateDriver),
|
|
7
8
|
codeModeWithSkills() for persistent skill libraries, trust strategies,
|
|
8
9
|
skill storage (FileSystem, LocalStorage, InMemory, Mongo), client-side
|
|
9
10
|
execution progress via code_mode:* custom events in useChat.
|
|
@@ -90,7 +91,7 @@ const stream = chat({
|
|
|
90
91
|
|
|
91
92
|
### 1. Choosing an Isolate Driver
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
Four drivers implement the `IsolateDriver` interface. All are interchangeable.
|
|
94
95
|
|
|
95
96
|
**Node.js** (`createNodeIsolateDriver`) -- Full V8 with JIT. Fastest option. Requires `isolated-vm` native C++ addon.
|
|
96
97
|
|
|
@@ -116,6 +117,18 @@ const driver = createQuickJSIsolateDriver({
|
|
|
116
117
|
})
|
|
117
118
|
```
|
|
118
119
|
|
|
120
|
+
**QuickJS Bun** (`createQuickJSBunIsolateDriver`) -- Native QuickJS on the Bun runtime via `bun:ffi`. Requires Bun >= 1.3.14 (throws a descriptive error on Node.js). No native deps or build step. Each context gets a dedicated QuickJS runtime with its own memory limit, stack size, and interrupt-based timeout. Recommended QuickJS option on Bun, where the WASM driver's asyncify bridge is unreliable for async host tool calls.
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { createQuickJSBunIsolateDriver } from '@tanstack/ai-isolate-quickjs-bun'
|
|
124
|
+
|
|
125
|
+
const driver = createQuickJSBunIsolateDriver({
|
|
126
|
+
memoryLimit: 128, // MB, default 128
|
|
127
|
+
timeout: 30_000, // ms, default 30000
|
|
128
|
+
maxStackSize: 524288, // bytes, default 512 KiB
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
119
132
|
**Cloudflare** (`createCloudflareIsolateDriver`) -- Edge execution via a deployed Cloudflare Worker. Requires a `workerUrl` pointing to your deployed worker. Network latency on each tool call.
|
|
120
133
|
|
|
121
134
|
```typescript
|
|
@@ -129,11 +142,12 @@ const driver = createCloudflareIsolateDriver({
|
|
|
129
142
|
})
|
|
130
143
|
```
|
|
131
144
|
|
|
132
|
-
| Driver
|
|
133
|
-
|
|
|
134
|
-
| Node
|
|
135
|
-
| QuickJS
|
|
136
|
-
|
|
|
145
|
+
| Driver | Best for | Native deps | Browser support | Performance |
|
|
146
|
+
| ----------- | --------------------------- | --------------- | --------------- | --------------------- |
|
|
147
|
+
| Node | Server-side Node.js | Yes (C++ addon) | No | Fast (V8 JIT) |
|
|
148
|
+
| QuickJS | Browsers, edge, portability | None (WASM) | Yes | Slower (interpreted) |
|
|
149
|
+
| QuickJS Bun | Bun servers | None | No | Fast (native QuickJS) |
|
|
150
|
+
| Cloudflare | Edge deployments | None | N/A | Fast (V8 on edge) |
|
|
137
151
|
|
|
138
152
|
### 2. Adding Persistent Skills with codeModeWithSkills()
|
|
139
153
|
|
|
@@ -495,10 +509,11 @@ Source: ai-isolate-node source (probeIsolatedVm implementation)
|
|
|
495
509
|
|
|
496
510
|
### MEDIUM: Expecting identical behavior across isolate drivers
|
|
497
511
|
|
|
498
|
-
The
|
|
512
|
+
The four drivers have different capabilities. Same code may work in Node but fail elsewhere.
|
|
499
513
|
|
|
500
514
|
- **Node**: Full V8 support, JIT compilation, configurable memory limit
|
|
501
515
|
- **QuickJS**: Interpreted, limited stdlib (no File I/O), configurable stack size, asyncified execution (serialized through global queue)
|
|
516
|
+
- **QuickJS Bun**: Bun runtime only (throws on Node.js), native QuickJS via `bun:ffi`, dedicated runtime per context with per-context memory/stack limits and normalized `MemoryLimitError`/`StackOverflowError`/`TimeoutError`
|
|
502
517
|
- **Cloudflare**: Network latency per tool call round-trip, `maxToolRounds` limit (default 10), requires deployed worker with `UNSAFE_EVAL` or `eval` unsafe binding
|
|
503
518
|
|
|
504
519
|
Test generated code against your target driver. If you need portability, target QuickJS's subset.
|
|
@@ -45,6 +45,7 @@ const executeTypescriptOutputSchema = z.object({
|
|
|
45
45
|
message: z.string(),
|
|
46
46
|
name: z.string().optional(),
|
|
47
47
|
line: z.number().optional(),
|
|
48
|
+
stack: z.string().optional(),
|
|
48
49
|
})
|
|
49
50
|
.optional()
|
|
50
51
|
.describe('Error details if execution failed'),
|
|
@@ -130,18 +131,62 @@ export function createCodeModeTool(
|
|
|
130
131
|
toolContext?: ToolExecutionContext,
|
|
131
132
|
): Promise<CodeModeToolResult> => {
|
|
132
133
|
const { typescriptCode } = input
|
|
134
|
+
const startedAt = Date.now()
|
|
133
135
|
|
|
134
136
|
// Get emitCustomEvent from context or use no-op
|
|
135
137
|
const emitCustomEvent = toolContext?.emitCustomEvent || (() => {})
|
|
136
138
|
|
|
139
|
+
const finish = (
|
|
140
|
+
result: CodeModeToolResult,
|
|
141
|
+
phase: string,
|
|
142
|
+
): CodeModeToolResult => {
|
|
143
|
+
const durationMs = Date.now() - startedAt
|
|
144
|
+
const payload = {
|
|
145
|
+
timestamp: Date.now(),
|
|
146
|
+
durationMs,
|
|
147
|
+
phase,
|
|
148
|
+
success: result.success,
|
|
149
|
+
logCount: result.logs?.length ?? 0,
|
|
150
|
+
error: result.error
|
|
151
|
+
? {
|
|
152
|
+
name: result.error.name,
|
|
153
|
+
message: result.error.message,
|
|
154
|
+
...(result.error.stack !== undefined && {
|
|
155
|
+
stack: result.error.stack,
|
|
156
|
+
}),
|
|
157
|
+
...(result.error.line !== undefined && {
|
|
158
|
+
line: result.error.line,
|
|
159
|
+
}),
|
|
160
|
+
}
|
|
161
|
+
: undefined,
|
|
162
|
+
}
|
|
163
|
+
emitCustomEvent('code_mode:execution_finished', payload)
|
|
164
|
+
if (!result.success) {
|
|
165
|
+
console.error('[code-mode] execute_typescript failed', payload)
|
|
166
|
+
} else if (
|
|
167
|
+
typeof process !== 'undefined' &&
|
|
168
|
+
process.env?.CODE_MODE_DEBUG === '1'
|
|
169
|
+
) {
|
|
170
|
+
console.info('[code-mode] execute_typescript ok', {
|
|
171
|
+
durationMs,
|
|
172
|
+
phase,
|
|
173
|
+
logCount: payload.logCount,
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
return result
|
|
177
|
+
}
|
|
178
|
+
|
|
137
179
|
if (!typescriptCode || typeof typescriptCode !== 'string') {
|
|
138
|
-
return
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
180
|
+
return finish(
|
|
181
|
+
{
|
|
182
|
+
success: false,
|
|
183
|
+
error: {
|
|
184
|
+
message: 'typescriptCode must be a non-empty string',
|
|
185
|
+
name: 'ValidationError',
|
|
186
|
+
},
|
|
143
187
|
},
|
|
144
|
-
|
|
188
|
+
'validate-input',
|
|
189
|
+
)
|
|
145
190
|
}
|
|
146
191
|
|
|
147
192
|
// Create a fresh sandbox context for this execution
|
|
@@ -161,13 +206,18 @@ export function createCodeModeTool(
|
|
|
161
206
|
strippedCode = await transpile(typescriptCode)
|
|
162
207
|
} catch (error) {
|
|
163
208
|
// Type/syntax error from the transpiler
|
|
164
|
-
return
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
209
|
+
return finish(
|
|
210
|
+
{
|
|
211
|
+
success: false,
|
|
212
|
+
error: {
|
|
213
|
+
message: error instanceof Error ? error.message : String(error),
|
|
214
|
+
name: 'TypeScriptError',
|
|
215
|
+
...(error instanceof Error &&
|
|
216
|
+
error.stack !== undefined && { stack: error.stack }),
|
|
217
|
+
},
|
|
169
218
|
},
|
|
170
|
-
|
|
219
|
+
'transpile',
|
|
220
|
+
)
|
|
171
221
|
}
|
|
172
222
|
|
|
173
223
|
// Step 2: Get dynamic skill bindings if available
|
|
@@ -192,11 +242,27 @@ export function createCodeModeTool(
|
|
|
192
242
|
)
|
|
193
243
|
|
|
194
244
|
// Step 4: Create sandbox context with event-aware bindings
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
245
|
+
try {
|
|
246
|
+
isolateContext = await driver.createContext({
|
|
247
|
+
bindings: eventAwareBindings,
|
|
248
|
+
timeout,
|
|
249
|
+
memoryLimit,
|
|
250
|
+
})
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return finish(
|
|
253
|
+
{
|
|
254
|
+
success: false,
|
|
255
|
+
error: {
|
|
256
|
+
message: error instanceof Error ? error.message : String(error),
|
|
257
|
+
name:
|
|
258
|
+
error instanceof Error ? error.name : 'CreateContextError',
|
|
259
|
+
...(error instanceof Error &&
|
|
260
|
+
error.stack !== undefined && { stack: error.stack }),
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
'create-context',
|
|
264
|
+
)
|
|
265
|
+
}
|
|
200
266
|
|
|
201
267
|
// Step 5: Execute the code in the sandbox
|
|
202
268
|
const executionResult = await isolateContext.execute(strippedCode)
|
|
@@ -228,31 +294,45 @@ export function createCodeModeTool(
|
|
|
228
294
|
}
|
|
229
295
|
|
|
230
296
|
if (executionResult.success) {
|
|
231
|
-
return
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
297
|
+
return finish(
|
|
298
|
+
{
|
|
299
|
+
success: true,
|
|
300
|
+
result: executionResult.value,
|
|
301
|
+
logs: executionResult.logs,
|
|
302
|
+
},
|
|
303
|
+
'execute',
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return finish(
|
|
308
|
+
{
|
|
238
309
|
success: false,
|
|
239
310
|
error: executionResult.error
|
|
240
311
|
? {
|
|
241
312
|
message: executionResult.error.message,
|
|
242
313
|
name: executionResult.error.name,
|
|
314
|
+
...(executionResult.error.stack !== undefined && {
|
|
315
|
+
stack: executionResult.error.stack,
|
|
316
|
+
}),
|
|
243
317
|
}
|
|
244
|
-
: { message: 'Unknown execution error' },
|
|
318
|
+
: { message: 'Unknown execution error', name: 'UnknownError' },
|
|
245
319
|
logs: executionResult.logs,
|
|
246
|
-
}
|
|
247
|
-
|
|
320
|
+
},
|
|
321
|
+
'execute',
|
|
322
|
+
)
|
|
248
323
|
} catch (error) {
|
|
249
|
-
return
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
324
|
+
return finish(
|
|
325
|
+
{
|
|
326
|
+
success: false,
|
|
327
|
+
error: {
|
|
328
|
+
message: error instanceof Error ? error.message : String(error),
|
|
329
|
+
name: error instanceof Error ? error.name : 'Error',
|
|
330
|
+
...(error instanceof Error &&
|
|
331
|
+
error.stack !== undefined && { stack: error.stack }),
|
|
332
|
+
},
|
|
254
333
|
},
|
|
255
|
-
|
|
334
|
+
'unhandled',
|
|
335
|
+
)
|
|
256
336
|
} finally {
|
|
257
337
|
// Always clean up the sandbox context
|
|
258
338
|
if (isolateContext) {
|