@tanstack/ai-code-mode 0.2.9 → 0.2.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 CHANGED
@@ -96,7 +96,7 @@ Lower-level functions if you need only the tool or only the prompt. `createCodeM
96
96
 
97
97
  These utilities are used internally and exported for custom pipelines:
98
98
 
99
- - **`stripTypeScript(code)`** — Strips TypeScript syntax using esbuild.
99
+ - **`stripTypeScript(code)`** — Strips TypeScript syntax using sucrase (edge-safe, no native binary).
100
100
  - **`toolsToBindings(tools, prefix?)`** — Converts tools to `ToolBinding` records for sandbox injection.
101
101
  - **`generateTypeStubs(bindings, options?)`** — Generates TypeScript type declarations from tool bindings.
102
102
 
@@ -1,4 +1,4 @@
1
- import { convertSchemaToJsonSchema } from "@tanstack/ai";
1
+ import { convertSchemaToJsonSchema, isStandardSchema, parseWithStandardSchema } from "@tanstack/ai";
2
2
  function toolsToBindings(tools, prefix = "") {
3
3
  const bindings = {};
4
4
  for (const tool of tools) {
@@ -19,8 +19,30 @@ function toolToBinding(tool, prefix = "") {
19
19
  );
20
20
  }
21
21
  const toolExecute = tool.execute;
22
- const execute = (args, context) => {
23
- return Promise.resolve(toolExecute(args, context));
22
+ const execute = async (args, context) => {
23
+ let input = args;
24
+ if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {
25
+ try {
26
+ input = parseWithStandardSchema(tool.inputSchema, args);
27
+ } catch (error) {
28
+ const message = error instanceof Error ? error.message : "Validation failed";
29
+ throw new Error(
30
+ `Input validation failed for tool ${tool.name}: ${message}`
31
+ );
32
+ }
33
+ }
34
+ let result = await Promise.resolve(toolExecute(input, context));
35
+ if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {
36
+ try {
37
+ result = parseWithStandardSchema(tool.outputSchema, result);
38
+ } catch (error) {
39
+ const message = error instanceof Error ? error.message : "Validation failed";
40
+ throw new Error(
41
+ `Output validation failed for tool ${tool.name}: ${message}`
42
+ );
43
+ }
44
+ }
45
+ return result;
24
46
  };
25
47
  return {
26
48
  name: `${prefix}${tool.name}`,
@@ -1 +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 if (typeof tool.execute !== 'function') {\n throw new Error(\n `Tool \"${tool.name}\" does not have an execute function. ` +\n `Code Mode requires server tools with implementations.`,\n )\n }\n\n const toolExecute = tool.execute\n const 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\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: ToolExecutionContext['emitCustomEvent'],\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;AAEJ,MAAI,OAAO,KAAK,YAAY,YAAY;AACtC,UAAM,IAAI;AAAA,MACR,SAAS,KAAK,IAAI;AAAA,IAAA;AAAA,EAGtB;AAEA,QAAM,cAAc,KAAK;AACzB,QAAM,UAAU,CAAC,MAAe,YAAmC;AAEjE,WAAO,QAAQ,QAAQ,YAAY,MAAM,OAAO,CAAC;AAAA,EACnD;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;"}
1
+ {"version":3,"file":"tool-to-binding.js","sources":["../../../src/bindings/tool-to-binding.ts"],"sourcesContent":["import {\n convertSchemaToJsonSchema,\n isStandardSchema,\n parseWithStandardSchema,\n} 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 if (typeof tool.execute !== 'function') {\n throw new Error(\n `Tool \"${tool.name}\" does not have an execute function. ` +\n `Code Mode requires server tools with implementations.`,\n )\n }\n\n const toolExecute = tool.execute\n const execute = async (args: unknown, context?: ToolExecutionContext) => {\n let input = args\n if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {\n try {\n input = parseWithStandardSchema(tool.inputSchema, args)\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Validation failed'\n throw new Error(\n `Input validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n let result = await Promise.resolve(toolExecute(input, context))\n\n if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {\n try {\n result = parseWithStandardSchema(tool.outputSchema, result)\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Validation failed'\n throw new Error(\n `Output validation failed for tool ${tool.name}: ${message}`,\n )\n }\n }\n\n return result\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: ToolExecutionContext['emitCustomEvent'],\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":";AAcO,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;AAEJ,MAAI,OAAO,KAAK,YAAY,YAAY;AACtC,UAAM,IAAI;AAAA,MACR,SAAS,KAAK,IAAI;AAAA,IAAA;AAAA,EAGtB;AAEA,QAAM,cAAc,KAAK;AACzB,QAAM,UAAU,OAAO,MAAe,YAAmC;AACvE,QAAI,QAAQ;AACZ,QAAI,KAAK,eAAe,iBAAiB,KAAK,WAAW,GAAG;AAC1D,UAAI;AACF,gBAAQ,wBAAwB,KAAK,aAAa,IAAI;AAAA,MACxD,SAAS,OAAO;AACd,cAAM,UACJ,iBAAiB,QAAQ,MAAM,UAAU;AAC3C,cAAM,IAAI;AAAA,UACR,oCAAoC,KAAK,IAAI,KAAK,OAAO;AAAA,QAAA;AAAA,MAE7D;AAAA,IACF;AAEA,QAAI,SAAS,MAAM,QAAQ,QAAQ,YAAY,OAAO,OAAO,CAAC;AAE9D,QAAI,KAAK,gBAAgB,iBAAiB,KAAK,YAAY,GAAG;AAC5D,UAAI;AACF,iBAAS,wBAAwB,KAAK,cAAc,MAAM;AAAA,MAC5D,SAAS,OAAO;AACd,cAAM,UACJ,iBAAiB,QAAQ,MAAM,UAAU;AAC3C,cAAM,IAAI;AAAA,UACR,qCAAqC,KAAK,IAAI,KAAK,OAAO;AAAA,QAAA;AAAA,MAE9D;AAAA,IACF;AAEA,WAAO;AAAA,EACT;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;"}
@@ -23,7 +23,8 @@ function createCodeModeTool(config) {
23
23
  tools,
24
24
  timeout = 3e4,
25
25
  memoryLimit = 128,
26
- getSkillBindings
26
+ getSkillBindings,
27
+ transpile = stripTypeScript
27
28
  } = config;
28
29
  if (tools.length === 0) {
29
30
  throw new Error("At least one tool must be provided to createCodeModeTool");
@@ -57,7 +58,7 @@ function createCodeModeTool(config) {
57
58
  try {
58
59
  let strippedCode;
59
60
  try {
60
- strippedCode = await stripTypeScript(typescriptCode);
61
+ strippedCode = await transpile(typescriptCode);
61
62
  } catch (error) {
62
63
  return {
63
64
  success: false,
@@ -1 +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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"}
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 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 // 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 // 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":";;;;AAkBA,MAAM,+BAA+B,EAAE,OAAO;AAAA,EAC5C,gBAAgB,EACb,OAAA,EACA;AAAA,IACC;AAAA,EAAA;AAIN,CAAC;AAKD,MAAM,gCAAgC,EAAE,OAAO;AAAA,EAC7C,SAAS,EAAE,UAAU,SAAS,4CAA4C;AAAA,EAC1E,QAAQ,EACL,QAAA,EACA,SAAA,EACA,SAAS,qCAAqC;AAAA,EACjD,MAAM,EACH,MAAM,EAAE,OAAA,CAAQ,EAChB,SAAA,EACA,SAAS,0CAA0C;AAAA,EACtD,OAAO,EACJ,OAAO;AAAA,IACN,SAAS,EAAE,OAAA;AAAA,IACX,MAAM,EAAE,OAAA,EAAS,SAAA;AAAA,IACjB,MAAM,EAAE,OAAA,EAAS,SAAA;AAAA,EAAS,CAC3B,EACA,WACA,SAAS,mCAAmC;AACjD,CAAC;AAiCM,SAAS,mBACd,QAKA;AACA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,cAAc;AAAA,IACd;AAAA,IACA,YAAY;AAAA,EAAA,IACV;AAGJ,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,MAAM,0DAA0D;AAAA,EAC5E;AAGA,QAAM,iBAAiB,gBAAgB,OAAO,WAAW;AAGzD,QAAM,aAAa,eAAe;AAAA,IAChC,MAAM;AAAA,IACN,aAAa,qBAAqB,KAAK;AAAA,IACvC,aAAa;AAAA,IACb,cAAc;AAAA,EAAA,CACf;AAGD,SAAO,WAAW;AAAA,IAChB,OACE,OACA,gBACgC;AAChC,YAAM,EAAE,mBAAmB;AAG3B,YAAM,kBAAkB,aAAa,oBAAoB,MAAM;AAAA,MAAC;AAEhE,UAAI,CAAC,kBAAkB,OAAO,mBAAmB,UAAU;AACzD,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO;AAAA,YACL,SAAS;AAAA,YACT,MAAM;AAAA,UAAA;AAAA,QACR;AAAA,MAEJ;AAGA,UAAI,iBAAwC;AAG5C,sBAAgB,+BAA+B;AAAA,QAC7C,WAAW,KAAK,IAAA;AAAA,QAChB,YAAY,eAAe;AAAA,MAAA,CAC5B;AAED,UAAI;AAGF,YAAI;AACJ,YAAI;AACF,yBAAe,MAAM,UAAU,cAAc;AAAA,QAC/C,SAAS,OAAO;AAEd,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,OAAO;AAAA,cACL,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,cAC9D,MAAM;AAAA,YAAA;AAAA,UACR;AAAA,QAEJ;AAGA,cAAM,gBAAgB,mBAAmB,MAAM,iBAAA,IAAqB,CAAA;AAGpE,cAAM,cAAc,EAAE,GAAG,gBAAgB,GAAG,cAAA;AAC5C,cAAM,qBAAqB;AAAA,UACzB;AAAA,UACA;AAAA,QAAA;AAIF,yBAAiB,MAAM,OAAO,cAAc;AAAA,UAC1C,UAAU;AAAA,UACV;AAAA,UACA;AAAA,QAAA,CACD;AAGD,cAAM,kBAAkB,MAAM,eAAe,QAAQ,YAAY;AAGjE,YAAI,gBAAgB,QAAQ,gBAAgB,KAAK,SAAS,GAAG;AAC3D,qBAAW,OAAO,gBAAgB,MAAM;AAEtC,gBAAI,QAA2C;AAC/C,gBAAI,UAAU;AAEd,gBAAI,IAAI,WAAW,SAAS,GAAG;AAC7B,sBAAQ;AACR,wBAAU,IAAI,MAAM,CAAC;AAAA,YACvB,WAAW,IAAI,WAAW,QAAQ,GAAG;AACnC,sBAAQ;AACR,wBAAU,IAAI,MAAM,CAAC;AAAA,YACvB,WAAW,IAAI,WAAW,QAAQ,GAAG;AACnC,sBAAQ;AACR,wBAAU,IAAI,MAAM,CAAC;AAAA,YACvB;AAEA,4BAAgB,qBAAqB;AAAA,cACnC;AAAA,cACA;AAAA,cACA,WAAW,KAAK,IAAA;AAAA,YAAI,CACrB;AAAA,UACH;AAAA,QACF;AAEA,YAAI,gBAAgB,SAAS;AAC3B,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,QAAQ,gBAAgB;AAAA,YACxB,MAAM,gBAAgB;AAAA,UAAA;AAAA,QAE1B,OAAO;AACL,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,OAAO,gBAAgB,QACnB;AAAA,cACE,SAAS,gBAAgB,MAAM;AAAA,cAC/B,MAAM,gBAAgB,MAAM;AAAA,YAAA,IAE9B,EAAE,SAAS,0BAAA;AAAA,YACf,MAAM,gBAAgB;AAAA,UAAA;AAAA,QAE1B;AAAA,MACF,SAAS,OAAO;AACd,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO;AAAA,YACL,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,YAC9D,MAAM,iBAAiB,QAAQ,MAAM,OAAO;AAAA,UAAA;AAAA,QAC9C;AAAA,MAEJ,UAAA;AAEE,YAAI,gBAAgB;AAClB,gBAAM,eAAe,QAAA;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,EAAA;AAEJ;AAKA,SAAS,qBAAqB,OAAoC;AAChE,QAAM,oBAAoB,MAAM,IAAI,CAAC,MAAM,YAAY,EAAE,IAAI,EAAE,EAAE,KAAK,IAAI;AAE1E,SACE,2GACkD,iBAAiB;AAIvE;"}
@@ -5,19 +5,40 @@
5
5
  * code with type annotations, it will be converted to valid JavaScript
6
6
  * before being sent to the sandbox for execution.
7
7
  *
8
- * Uses esbuild's transform API which is extremely fast and handles all
9
- * TypeScript syntax including:
8
+ * Uses sucrase's pure-JavaScript `transform`, which strips the TypeScript
9
+ * syntax that LLM-generated snippets use in practice:
10
10
  * - Type annotations (: string, : number, etc.)
11
11
  * - Generic types (Array<T>, Record<K, V>, etc.)
12
12
  * - Interface and type declarations
13
13
  * - Type assertions
14
14
  * - Enums (converted to JavaScript objects)
15
15
  *
16
+ * Unlike esbuild, sucrase has no native binary and pulls in no Node-only
17
+ * built-ins on its `transform` path, so this module is safe to bundle for
18
+ * browsers and edge runtimes (Cloudflare Workers/Pages etc.).
19
+ *
20
+ * Limitations vs esbuild: sucrase is a type-stripper, not a down-leveler.
21
+ * `disableESTransforms` leaves modern ECMAScript syntax untouched (the sandbox
22
+ * engines are modern), and sucrase does NOT compile a few exotic constructs:
23
+ * - TypeScript value `namespace`/`module` blocks are DROPPED (not emitted as an
24
+ * IIFE), so referencing the namespace at runtime throws `ReferenceError`.
25
+ * - Decorators and the `accessor` keyword pass through un-lowered, so the
26
+ * sandbox sees invalid syntax.
27
+ * - Post-ES2022 syntax (`using` declarations, RegExp `/v`·`/d` flags) is passed
28
+ * through; it runs on modern V8/Node sandboxes but may fail on older engines
29
+ * (e.g. QuickJS).
30
+ * If you need any of these, supply a heavier (Node-only) transpiler via the
31
+ * `transpile` option on `createCodeModeTool`.
32
+ *
16
33
  * The code is wrapped in an async function before transformation to allow
17
34
  * top-level `return` and `await` statements, then unwrapped after.
18
35
  *
36
+ * Note on errors: sucrase reports syntax errors with a position relative to the
37
+ * *wrapped* code (offset by the one-line wrapper prefix), so any line numbers
38
+ * surfaced downstream (e.g. `CodeModeToolResult.error.line`) are approximate.
39
+ *
19
40
  * @param code - TypeScript or JavaScript code
20
41
  * @returns Plain JavaScript code with all type syntax removed
21
- * @throws Error if esbuild fails (e.g., syntax error) or wrapper extraction fails
42
+ * @throws Error if sucrase fails (e.g., syntax error) or wrapper extraction fails
22
43
  */
23
44
  export declare function stripTypeScript(code: string): Promise<string>;
@@ -1,18 +1,15 @@
1
- import { transform } from "esbuild";
1
+ import { transform } from "sucrase";
2
2
  const WRAPPER_START = "___TANSTACK_WRAPPER_START___";
3
3
  const WRAPPER_END = "___TANSTACK_WRAPPER_END___";
4
4
  async function stripTypeScript(code) {
5
5
  const wrappedCode = `async function ${WRAPPER_START}() {
6
6
  ${code}
7
7
  }; ${WRAPPER_END}`;
8
- const result = await transform(wrappedCode, {
9
- loader: "ts",
10
- // Don't minify - keep the code readable for debugging
11
- minify: false,
12
- // Don't use keepNames as it adds __name() helper calls that aren't available in the sandbox
13
- keepNames: false,
14
- // Target modern JavaScript (ES2022 has top-level await)
15
- target: "es2022"
8
+ const result = transform(wrappedCode, {
9
+ // Only strip/lower TypeScript-specific syntax...
10
+ transforms: ["typescript"],
11
+ // ...and leave modern ECMAScript syntax untouched for the sandbox engines.
12
+ disableESTransforms: true
16
13
  });
17
14
  const transformed = result.code;
18
15
  const functionStart = transformed.indexOf(`async function ${WRAPPER_START}()`);
@@ -1 +1 @@
1
- {"version":3,"file":"strip-typescript.js","sources":["../../src/strip-typescript.ts"],"sourcesContent":["import { transform } from 'esbuild'\n\n// Unique markers for wrapping/unwrapping code\nconst WRAPPER_START = '___TANSTACK_WRAPPER_START___'\nconst WRAPPER_END = '___TANSTACK_WRAPPER_END___'\n\n/**\n * Strip TypeScript syntax from code, converting it to plain JavaScript.\n *\n * This is a safety net to ensure that even if an LLM generates TypeScript\n * code with type annotations, it will be converted to valid JavaScript\n * before being sent to the sandbox for execution.\n *\n * Uses esbuild's transform API which is extremely fast and handles all\n * TypeScript syntax including:\n * - Type annotations (: string, : number, etc.)\n * - Generic types (Array<T>, Record<K, V>, etc.)\n * - Interface and type declarations\n * - Type assertions\n * - Enums (converted to JavaScript objects)\n *\n * The code is wrapped in an async function before transformation to allow\n * top-level `return` and `await` statements, then unwrapped after.\n *\n * @param code - TypeScript or JavaScript code\n * @returns Plain JavaScript code with all type syntax removed\n * @throws Error if esbuild fails (e.g., syntax error) or wrapper extraction fails\n */\nexport async function stripTypeScript(code: string): Promise<string> {\n // Wrap the code in an async function to allow top-level return/await\n // This is necessary because esbuild's ESM format doesn't allow top-level returns\n const wrappedCode = `async function ${WRAPPER_START}() {\\n${code}\\n}; ${WRAPPER_END}`\n\n const result = await transform(wrappedCode, {\n loader: 'ts',\n // Don't minify - keep the code readable for debugging\n minify: false,\n // Don't use keepNames as it adds __name() helper calls that aren't available in the sandbox\n keepNames: false,\n // Target modern JavaScript (ES2022 has top-level await)\n target: 'es2022',\n })\n\n // Extract the code from inside the wrapper function\n const transformed = result.code\n\n // Find the function declaration start\n const functionStart = transformed.indexOf(`async function ${WRAPPER_START}()`)\n if (functionStart === -1) {\n throw new Error(\n '[stripTypeScript] Could not find wrapper function start in transformed output',\n )\n }\n\n // Find the opening brace of the function\n const openBrace = transformed.indexOf('{', functionStart)\n if (openBrace === -1) {\n throw new Error(\n '[stripTypeScript] Could not find opening brace in transformed output',\n )\n }\n\n // Find the end marker (regardless of formatting)\n const endMarkerIndex = transformed.indexOf(WRAPPER_END)\n if (endMarkerIndex === -1) {\n throw new Error(\n '[stripTypeScript] Could not find end marker in transformed output',\n )\n }\n\n // Find the closing brace of the function (last } before the end marker)\n // We need to find the } that matches the function opening\n const codeBeforeEndMarker = transformed.substring(\n openBrace + 1,\n endMarkerIndex,\n )\n\n // Find the last } before the end marker, accounting for the semicolon\n // The code will be: ...function body...}; WRAPPER_END or ...};\\nWRAPPER_END\n const closingBraceIndex = codeBeforeEndMarker.lastIndexOf('}')\n\n if (closingBraceIndex === -1) {\n throw new Error(\n '[stripTypeScript] Could not find closing brace in transformed output',\n )\n }\n\n // Extract the function body (between { and })\n const functionBody = codeBeforeEndMarker\n .substring(0, closingBraceIndex)\n .trim()\n\n return functionBody\n}\n"],"names":[],"mappings":";AAGA,MAAM,gBAAgB;AACtB,MAAM,cAAc;AAwBpB,eAAsB,gBAAgB,MAA+B;AAGnE,QAAM,cAAc,kBAAkB,aAAa;AAAA,EAAS,IAAI;AAAA,KAAQ,WAAW;AAEnF,QAAM,SAAS,MAAM,UAAU,aAAa;AAAA,IAC1C,QAAQ;AAAA;AAAA,IAER,QAAQ;AAAA;AAAA,IAER,WAAW;AAAA;AAAA,IAEX,QAAQ;AAAA,EAAA,CACT;AAGD,QAAM,cAAc,OAAO;AAG3B,QAAM,gBAAgB,YAAY,QAAQ,kBAAkB,aAAa,IAAI;AAC7E,MAAI,kBAAkB,IAAI;AACxB,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AAGA,QAAM,YAAY,YAAY,QAAQ,KAAK,aAAa;AACxD,MAAI,cAAc,IAAI;AACpB,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AAGA,QAAM,iBAAiB,YAAY,QAAQ,WAAW;AACtD,MAAI,mBAAmB,IAAI;AACzB,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AAIA,QAAM,sBAAsB,YAAY;AAAA,IACtC,YAAY;AAAA,IACZ;AAAA,EAAA;AAKF,QAAM,oBAAoB,oBAAoB,YAAY,GAAG;AAE7D,MAAI,sBAAsB,IAAI;AAC5B,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AAGA,QAAM,eAAe,oBAClB,UAAU,GAAG,iBAAiB,EAC9B,KAAA;AAEH,SAAO;AACT;"}
1
+ {"version":3,"file":"strip-typescript.js","sources":["../../src/strip-typescript.ts"],"sourcesContent":["import { transform } from 'sucrase'\n\n// Unique markers for wrapping/unwrapping code\nconst WRAPPER_START = '___TANSTACK_WRAPPER_START___'\nconst WRAPPER_END = '___TANSTACK_WRAPPER_END___'\n\n/**\n * Strip TypeScript syntax from code, converting it to plain JavaScript.\n *\n * This is a safety net to ensure that even if an LLM generates TypeScript\n * code with type annotations, it will be converted to valid JavaScript\n * before being sent to the sandbox for execution.\n *\n * Uses sucrase's pure-JavaScript `transform`, which strips the TypeScript\n * syntax that LLM-generated snippets use in practice:\n * - Type annotations (: string, : number, etc.)\n * - Generic types (Array<T>, Record<K, V>, etc.)\n * - Interface and type declarations\n * - Type assertions\n * - Enums (converted to JavaScript objects)\n *\n * Unlike esbuild, sucrase has no native binary and pulls in no Node-only\n * built-ins on its `transform` path, so this module is safe to bundle for\n * browsers and edge runtimes (Cloudflare Workers/Pages etc.).\n *\n * Limitations vs esbuild: sucrase is a type-stripper, not a down-leveler.\n * `disableESTransforms` leaves modern ECMAScript syntax untouched (the sandbox\n * engines are modern), and sucrase does NOT compile a few exotic constructs:\n * - TypeScript value `namespace`/`module` blocks are DROPPED (not emitted as an\n * IIFE), so referencing the namespace at runtime throws `ReferenceError`.\n * - Decorators and the `accessor` keyword pass through un-lowered, so the\n * sandbox sees invalid syntax.\n * - Post-ES2022 syntax (`using` declarations, RegExp `/v`·`/d` flags) is passed\n * through; it runs on modern V8/Node sandboxes but may fail on older engines\n * (e.g. QuickJS).\n * If you need any of these, supply a heavier (Node-only) transpiler via the\n * `transpile` option on `createCodeModeTool`.\n *\n * The code is wrapped in an async function before transformation to allow\n * top-level `return` and `await` statements, then unwrapped after.\n *\n * Note on errors: sucrase reports syntax errors with a position relative to the\n * *wrapped* code (offset by the one-line wrapper prefix), so any line numbers\n * surfaced downstream (e.g. `CodeModeToolResult.error.line`) are approximate.\n *\n * @param code - TypeScript or JavaScript code\n * @returns Plain JavaScript code with all type syntax removed\n * @throws Error if sucrase fails (e.g., syntax error) or wrapper extraction fails\n */\n// sucrase's transform is synchronous, but we keep the published Promise-returning\n// signature so existing `await stripTypeScript(...)` callers (and a custom async\n// `transpile` hook) stay source-compatible across this swap.\n// eslint-disable-next-line @typescript-eslint/require-await\nexport async function stripTypeScript(code: string): Promise<string> {\n // Wrap the code in an async function to allow top-level return/await.\n // This is necessary because top-level `return` is invalid outside a function.\n const wrappedCode = `async function ${WRAPPER_START}() {\\n${code}\\n}; ${WRAPPER_END}`\n\n const result = transform(wrappedCode, {\n // Only strip/lower TypeScript-specific syntax...\n transforms: ['typescript'],\n // ...and leave modern ECMAScript syntax untouched for the sandbox engines.\n disableESTransforms: true,\n })\n\n // Extract the code from inside the wrapper function\n const transformed = result.code\n\n // Find the function declaration start\n const functionStart = transformed.indexOf(`async function ${WRAPPER_START}()`)\n if (functionStart === -1) {\n throw new Error(\n '[stripTypeScript] Could not find wrapper function start in transformed output',\n )\n }\n\n // Find the opening brace of the function\n const openBrace = transformed.indexOf('{', functionStart)\n if (openBrace === -1) {\n throw new Error(\n '[stripTypeScript] Could not find opening brace in transformed output',\n )\n }\n\n // Find the end marker (regardless of formatting)\n const endMarkerIndex = transformed.indexOf(WRAPPER_END)\n if (endMarkerIndex === -1) {\n throw new Error(\n '[stripTypeScript] Could not find end marker in transformed output',\n )\n }\n\n // Find the closing brace of the function (last } before the end marker)\n // We need to find the } that matches the function opening\n const codeBeforeEndMarker = transformed.substring(\n openBrace + 1,\n endMarkerIndex,\n )\n\n // Find the last } before the end marker, accounting for the semicolon\n // The code will be: ...function body...}; WRAPPER_END or ...};\\nWRAPPER_END\n const closingBraceIndex = codeBeforeEndMarker.lastIndexOf('}')\n\n if (closingBraceIndex === -1) {\n throw new Error(\n '[stripTypeScript] Could not find closing brace in transformed output',\n )\n }\n\n // Extract the function body (between { and })\n const functionBody = codeBeforeEndMarker\n .substring(0, closingBraceIndex)\n .trim()\n\n return functionBody\n}\n"],"names":[],"mappings":";AAGA,MAAM,gBAAgB;AACtB,MAAM,cAAc;AAiDpB,eAAsB,gBAAgB,MAA+B;AAGnE,QAAM,cAAc,kBAAkB,aAAa;AAAA,EAAS,IAAI;AAAA,KAAQ,WAAW;AAEnF,QAAM,SAAS,UAAU,aAAa;AAAA;AAAA,IAEpC,YAAY,CAAC,YAAY;AAAA;AAAA,IAEzB,qBAAqB;AAAA,EAAA,CACtB;AAGD,QAAM,cAAc,OAAO;AAG3B,QAAM,gBAAgB,YAAY,QAAQ,kBAAkB,aAAa,IAAI;AAC7E,MAAI,kBAAkB,IAAI;AACxB,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AAGA,QAAM,YAAY,YAAY,QAAQ,KAAK,aAAa;AACxD,MAAI,cAAc,IAAI;AACpB,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AAGA,QAAM,iBAAiB,YAAY,QAAQ,WAAW;AACtD,MAAI,mBAAmB,IAAI;AACzB,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AAIA,QAAM,sBAAsB,YAAY;AAAA,IACtC,YAAY;AAAA,IACZ;AAAA,EAAA;AAKF,QAAM,oBAAoB,oBAAoB,YAAY,GAAG;AAE7D,MAAI,sBAAsB,IAAI;AAC5B,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AAGA,QAAM,eAAe,oBAClB,UAAU,GAAG,iBAAiB,EAC9B,KAAA;AAEH,SAAO;AACT;"}
@@ -152,6 +152,42 @@ export interface CodeModeToolConfig {
152
152
  * ```
153
153
  */
154
154
  getSkillBindings?: () => Promise<Record<string, ToolBinding>>;
155
+ /**
156
+ * Optional escape hatch to swap out the TypeScript-stripping step.
157
+ *
158
+ * Receives the raw model-generated code and must return runnable JavaScript
159
+ * with all TypeScript syntax removed. Defaults to the built-in
160
+ * {@link stripTypeScript}, which uses sucrase and is safe to bundle for
161
+ * browsers and edge runtimes (Cloudflare Workers/Pages etc.).
162
+ *
163
+ * Provide your own only to trade the edge-safe default for a faster
164
+ * Node-only transpiler. A custom transpiler MUST tolerate top-level `return`
165
+ * and `await` in its input (the default wraps the code in an async function
166
+ * internally to allow this).
167
+ *
168
+ * NOTE: This only affects `createCodeModeTool`. The skills helpers
169
+ * (`skillsToTools`, `codeModeWithSkills` in `@tanstack/ai-code-mode-skills`)
170
+ * call the exported `stripTypeScript` directly, so they ignore this hook — but
171
+ * they still get the edge-safe sucrase default, so #487 is fixed for them too;
172
+ * they just can't be pointed at a different transpiler.
173
+ *
174
+ * @example
175
+ * ```typescript
176
+ * // Node-only fast path using esbuild (NOT edge-safe — Node only). esbuild
177
+ * // rejects top-level `return`, so reuse the same async-function wrapper the
178
+ * // default uses, then slice the body back out. `keepNames: false` stops
179
+ * // esbuild injecting `__name()` helpers the sandbox can't resolve.
180
+ * import { transformSync } from 'esbuild'
181
+ * transpile: (code) => {
182
+ * const out = transformSync(`async function _w(){\n${code}\n}`, {
183
+ * loader: 'ts',
184
+ * keepNames: false,
185
+ * }).code
186
+ * return out.slice(out.indexOf('{') + 1, out.lastIndexOf('}'))
187
+ * }
188
+ * ```
189
+ */
190
+ transpile?: (code: string) => string | Promise<string>;
155
191
  }
156
192
  /**
157
193
  * Result returned by the execute_typescript tool
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-code-mode",
3
- "version": "0.2.9",
3
+ "version": "0.2.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",
@@ -50,16 +50,16 @@
50
50
  "tanstack-intent"
51
51
  ],
52
52
  "dependencies": {
53
- "esbuild": "^0.25.12"
53
+ "sucrase": "^3.35.0"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "zod": "^3.0.0 || ^4.0.0",
57
- "@tanstack/ai": "0.32.0"
57
+ "@tanstack/ai": "0.34.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@vitest/coverage-v8": "4.0.14",
61
61
  "zod": "^4.2.0",
62
- "@tanstack/ai": "0.32.0"
62
+ "@tanstack/ai": "0.34.0"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "vite build",
@@ -1,4 +1,8 @@
1
- import { convertSchemaToJsonSchema } from '@tanstack/ai'
1
+ import {
2
+ convertSchemaToJsonSchema,
3
+ isStandardSchema,
4
+ parseWithStandardSchema,
5
+ } from '@tanstack/ai'
2
6
  import type { ToolExecutionContext } from '@tanstack/ai'
3
7
  import type { CodeModeTool, ToolBinding } from '../types'
4
8
 
@@ -51,9 +55,35 @@ export function toolToBinding(
51
55
  }
52
56
 
53
57
  const toolExecute = tool.execute
54
- const execute = (args: unknown, context?: ToolExecutionContext) => {
55
- // Pass context to the underlying tool so it can emit custom events
56
- return Promise.resolve(toolExecute(args, context))
58
+ const execute = async (args: unknown, context?: ToolExecutionContext) => {
59
+ let input = args
60
+ if (tool.inputSchema && isStandardSchema(tool.inputSchema)) {
61
+ try {
62
+ input = parseWithStandardSchema(tool.inputSchema, args)
63
+ } catch (error) {
64
+ const message =
65
+ error instanceof Error ? error.message : 'Validation failed'
66
+ throw new Error(
67
+ `Input validation failed for tool ${tool.name}: ${message}`,
68
+ )
69
+ }
70
+ }
71
+
72
+ let result = await Promise.resolve(toolExecute(input, context))
73
+
74
+ if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {
75
+ try {
76
+ result = parseWithStandardSchema(tool.outputSchema, result)
77
+ } catch (error) {
78
+ const message =
79
+ error instanceof Error ? error.message : 'Validation failed'
80
+ throw new Error(
81
+ `Output validation failed for tool ${tool.name}: ${message}`,
82
+ )
83
+ }
84
+ }
85
+
86
+ return result
57
87
  }
58
88
 
59
89
  return {
@@ -93,6 +93,7 @@ export function createCodeModeTool(
93
93
  timeout = 30000,
94
94
  memoryLimit = 128,
95
95
  getSkillBindings,
96
+ transpile = stripTypeScript,
96
97
  } = config
97
98
 
98
99
  // Validate tools
@@ -142,12 +143,13 @@ export function createCodeModeTool(
142
143
  })
143
144
 
144
145
  try {
145
- // Step 1: Strip TypeScript (also serves as syntax validation via esbuild)
146
+ // Step 1: Strip TypeScript (also serves as syntax validation via the
147
+ // transpiler — sucrase by default, or a user-supplied `transpile`)
146
148
  let strippedCode: string
147
149
  try {
148
- strippedCode = await stripTypeScript(typescriptCode)
150
+ strippedCode = await transpile(typescriptCode)
149
151
  } catch (error) {
150
- // Type/syntax error from esbuild
152
+ // Type/syntax error from the transpiler
151
153
  return {
152
154
  success: false,
153
155
  error: {
@@ -1,4 +1,4 @@
1
- import { transform } from 'esbuild'
1
+ import { transform } from 'sucrase'
2
2
 
3
3
  // Unique markers for wrapping/unwrapping code
4
4
  const WRAPPER_START = '___TANSTACK_WRAPPER_START___'
@@ -11,34 +11,56 @@ const WRAPPER_END = '___TANSTACK_WRAPPER_END___'
11
11
  * code with type annotations, it will be converted to valid JavaScript
12
12
  * before being sent to the sandbox for execution.
13
13
  *
14
- * Uses esbuild's transform API which is extremely fast and handles all
15
- * TypeScript syntax including:
14
+ * Uses sucrase's pure-JavaScript `transform`, which strips the TypeScript
15
+ * syntax that LLM-generated snippets use in practice:
16
16
  * - Type annotations (: string, : number, etc.)
17
17
  * - Generic types (Array<T>, Record<K, V>, etc.)
18
18
  * - Interface and type declarations
19
19
  * - Type assertions
20
20
  * - Enums (converted to JavaScript objects)
21
21
  *
22
+ * Unlike esbuild, sucrase has no native binary and pulls in no Node-only
23
+ * built-ins on its `transform` path, so this module is safe to bundle for
24
+ * browsers and edge runtimes (Cloudflare Workers/Pages etc.).
25
+ *
26
+ * Limitations vs esbuild: sucrase is a type-stripper, not a down-leveler.
27
+ * `disableESTransforms` leaves modern ECMAScript syntax untouched (the sandbox
28
+ * engines are modern), and sucrase does NOT compile a few exotic constructs:
29
+ * - TypeScript value `namespace`/`module` blocks are DROPPED (not emitted as an
30
+ * IIFE), so referencing the namespace at runtime throws `ReferenceError`.
31
+ * - Decorators and the `accessor` keyword pass through un-lowered, so the
32
+ * sandbox sees invalid syntax.
33
+ * - Post-ES2022 syntax (`using` declarations, RegExp `/v`·`/d` flags) is passed
34
+ * through; it runs on modern V8/Node sandboxes but may fail on older engines
35
+ * (e.g. QuickJS).
36
+ * If you need any of these, supply a heavier (Node-only) transpiler via the
37
+ * `transpile` option on `createCodeModeTool`.
38
+ *
22
39
  * The code is wrapped in an async function before transformation to allow
23
40
  * top-level `return` and `await` statements, then unwrapped after.
24
41
  *
42
+ * Note on errors: sucrase reports syntax errors with a position relative to the
43
+ * *wrapped* code (offset by the one-line wrapper prefix), so any line numbers
44
+ * surfaced downstream (e.g. `CodeModeToolResult.error.line`) are approximate.
45
+ *
25
46
  * @param code - TypeScript or JavaScript code
26
47
  * @returns Plain JavaScript code with all type syntax removed
27
- * @throws Error if esbuild fails (e.g., syntax error) or wrapper extraction fails
48
+ * @throws Error if sucrase fails (e.g., syntax error) or wrapper extraction fails
28
49
  */
50
+ // sucrase's transform is synchronous, but we keep the published Promise-returning
51
+ // signature so existing `await stripTypeScript(...)` callers (and a custom async
52
+ // `transpile` hook) stay source-compatible across this swap.
53
+ // eslint-disable-next-line @typescript-eslint/require-await
29
54
  export async function stripTypeScript(code: string): Promise<string> {
30
- // Wrap the code in an async function to allow top-level return/await
31
- // This is necessary because esbuild's ESM format doesn't allow top-level returns
55
+ // Wrap the code in an async function to allow top-level return/await.
56
+ // This is necessary because top-level `return` is invalid outside a function.
32
57
  const wrappedCode = `async function ${WRAPPER_START}() {\n${code}\n}; ${WRAPPER_END}`
33
58
 
34
- const result = await transform(wrappedCode, {
35
- loader: 'ts',
36
- // Don't minify - keep the code readable for debugging
37
- minify: false,
38
- // Don't use keepNames as it adds __name() helper calls that aren't available in the sandbox
39
- keepNames: false,
40
- // Target modern JavaScript (ES2022 has top-level await)
41
- target: 'es2022',
59
+ const result = transform(wrappedCode, {
60
+ // Only strip/lower TypeScript-specific syntax...
61
+ transforms: ['typescript'],
62
+ // ...and leave modern ECMAScript syntax untouched for the sandbox engines.
63
+ disableESTransforms: true,
42
64
  })
43
65
 
44
66
  // Extract the code from inside the wrapper function
package/src/types.ts CHANGED
@@ -195,6 +195,43 @@ export interface CodeModeToolConfig {
195
195
  * ```
196
196
  */
197
197
  getSkillBindings?: () => Promise<Record<string, ToolBinding>>
198
+
199
+ /**
200
+ * Optional escape hatch to swap out the TypeScript-stripping step.
201
+ *
202
+ * Receives the raw model-generated code and must return runnable JavaScript
203
+ * with all TypeScript syntax removed. Defaults to the built-in
204
+ * {@link stripTypeScript}, which uses sucrase and is safe to bundle for
205
+ * browsers and edge runtimes (Cloudflare Workers/Pages etc.).
206
+ *
207
+ * Provide your own only to trade the edge-safe default for a faster
208
+ * Node-only transpiler. A custom transpiler MUST tolerate top-level `return`
209
+ * and `await` in its input (the default wraps the code in an async function
210
+ * internally to allow this).
211
+ *
212
+ * NOTE: This only affects `createCodeModeTool`. The skills helpers
213
+ * (`skillsToTools`, `codeModeWithSkills` in `@tanstack/ai-code-mode-skills`)
214
+ * call the exported `stripTypeScript` directly, so they ignore this hook — but
215
+ * they still get the edge-safe sucrase default, so #487 is fixed for them too;
216
+ * they just can't be pointed at a different transpiler.
217
+ *
218
+ * @example
219
+ * ```typescript
220
+ * // Node-only fast path using esbuild (NOT edge-safe — Node only). esbuild
221
+ * // rejects top-level `return`, so reuse the same async-function wrapper the
222
+ * // default uses, then slice the body back out. `keepNames: false` stops
223
+ * // esbuild injecting `__name()` helpers the sandbox can't resolve.
224
+ * import { transformSync } from 'esbuild'
225
+ * transpile: (code) => {
226
+ * const out = transformSync(`async function _w(){\n${code}\n}`, {
227
+ * loader: 'ts',
228
+ * keepNames: false,
229
+ * }).code
230
+ * return out.slice(out.indexOf('{') + 1, out.lastIndexOf('}'))
231
+ * }
232
+ * ```
233
+ */
234
+ transpile?: (code: string) => string | Promise<string>
198
235
  }
199
236
 
200
237
  /**