@tanstack/ai-code-mode 0.3.11 → 0.4.1

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
@@ -89,7 +89,7 @@ Creates both the `execute_typescript` tool and its matching system prompt. This
89
89
  - `tools` — Array of `ServerTool` or `ToolDefinition` instances. Exposed as `external_*` functions in the sandbox
90
90
  - `timeout` — Execution timeout in ms (default: 30000)
91
91
  - `memoryLimit` — Memory limit in MB (default: 128, supported by the Node, QuickJS, and QuickJS Bun drivers)
92
- - `getSkillBindings` — Optional async function returning dynamic bindings
92
+ - `getSnippetBindings` — Optional async function returning dynamic bindings
93
93
 
94
94
  ### `createCodeModeTool(config)` / `createCodeModeSystemPrompt(config)`
95
95
 
@@ -47,7 +47,7 @@ var executeTypescriptOutputSchema = z.object({
47
47
  * ```
48
48
  */
49
49
  function createCodeModeTool(config) {
50
- const { driver, tools, timeout = 3e4, memoryLimit = 128, getSkillBindings, onSecretParameter, transpile = stripTypeScript } = config;
50
+ const { driver, tools, timeout = 3e4, memoryLimit = 128, getSnippetBindings, onSecretParameter, transpile = stripTypeScript } = config;
51
51
  if (tools.length === 0) throw new Error("At least one tool must be provided to createCodeModeTool");
52
52
  const staticBindings = toolsToBindings(tools, "external_");
53
53
  const secretDedupCache = /* @__PURE__ */ new Set();
@@ -114,16 +114,17 @@ function createCodeModeTool(config) {
114
114
  }
115
115
  }, "transpile");
116
116
  }
117
- const skillBindings = getSkillBindings ? await getSkillBindings() : {};
118
- const skillBindingValues = Object.values(skillBindings);
119
- if (skillBindingValues.length > 0) warnIfBindingsExposeSecrets(skillBindingValues, {
117
+ const snippetBindings = getSnippetBindings ? await getSnippetBindings() : {};
118
+ const snippetBindingValues = Object.values(snippetBindings);
119
+ if (snippetBindingValues.length > 0) warnIfBindingsExposeSecrets(snippetBindingValues, {
120
120
  handler: onSecretParameter,
121
121
  dedupCache: secretDedupCache
122
122
  });
123
- const eventAwareBindings = createEventAwareBindings({
123
+ const allBindings = {
124
124
  ...staticBindings,
125
- ...skillBindings
126
- }, emitCustomEvent);
125
+ ...snippetBindings
126
+ };
127
+ const eventAwareBindings = createEventAwareBindings(allBindings, emitCustomEvent);
127
128
  try {
128
129
  isolateContext = await driver.createContext({
129
130
  bindings: eventAwareBindings,
@@ -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 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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"}
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 getSnippetBindings,\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 (snippet) 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 snippet bindings if available\n const snippetBindings = getSnippetBindings\n ? await getSnippetBindings()\n : {}\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 snippetBindingValues = Object.values(snippetBindings)\n if (snippetBindingValues.length > 0) {\n warnIfBindingsExposeSecrets(snippetBindingValues, {\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, ...snippetBindings }\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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"}
@@ -46,7 +46,8 @@ var WRAPPER_END = "___TANSTACK_WRAPPER_END___";
46
46
  * @throws Error if sucrase fails (e.g., syntax error) or wrapper extraction fails
47
47
  */
48
48
  async function stripTypeScript(code) {
49
- const transformed = transform(`async function ${WRAPPER_START}() {\n${code}\n}; ${WRAPPER_END}`, {
49
+ const wrappedCode = `async function ${WRAPPER_START}() {\n${code}\n}; ${WRAPPER_END}`;
50
+ const transformed = transform(wrappedCode, {
50
51
  transforms: ["typescript"],
51
52
  disableESTransforms: true
52
53
  }).code;
@@ -1 +1 @@
1
- {"version":3,"file":"strip-typescript.js","names":[],"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"],"mappings":";;AAGA,IAAM,gBAAgB;AACtB,IAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDpB,eAAsB,gBAAgB,MAA+B;CAanE,MAAM,cARS,UAAU,kBAFa,cAAc,QAAQ,KAAK,OAAO,eAElC;EAEpC,YAAY,CAAC,YAAY;EAEzB,qBAAqB;CACvB,CAGoB,CAAA,CAAO;CAG3B,MAAM,gBAAgB,YAAY,QAAQ,kBAAkB,cAAc,GAAG;CAC7E,IAAI,kBAAkB,IACpB,MAAM,IAAI,MACR,+EACF;CAIF,MAAM,YAAY,YAAY,QAAQ,KAAK,aAAa;CACxD,IAAI,cAAc,IAChB,MAAM,IAAI,MACR,sEACF;CAIF,MAAM,iBAAiB,YAAY,QAAQ,WAAW;CACtD,IAAI,mBAAmB,IACrB,MAAM,IAAI,MACR,mEACF;CAKF,MAAM,sBAAsB,YAAY,UACtC,YAAY,GACZ,cACF;CAIA,MAAM,oBAAoB,oBAAoB,YAAY,GAAG;CAE7D,IAAI,sBAAsB,IACxB,MAAM,IAAI,MACR,sEACF;CAQF,OAJqB,oBAClB,UAAU,GAAG,iBAAiB,CAAC,CAC/B,KAEI;AACT"}
1
+ {"version":3,"file":"strip-typescript.js","names":[],"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"],"mappings":";;AAGA,IAAM,gBAAgB;AACtB,IAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDpB,eAAsB,gBAAgB,MAA+B;CAGnE,MAAM,cAAc,kBAAkB,cAAc,QAAQ,KAAK,OAAO;CAUxE,MAAM,cARS,UAAU,aAAa;EAEpC,YAAY,CAAC,YAAY;EAEzB,qBAAqB;CACvB,CAGoB,CAAA,CAAO;CAG3B,MAAM,gBAAgB,YAAY,QAAQ,kBAAkB,cAAc,GAAG;CAC7E,IAAI,kBAAkB,IACpB,MAAM,IAAI,MACR,+EACF;CAIF,MAAM,YAAY,YAAY,QAAQ,KAAK,aAAa;CACxD,IAAI,cAAc,IAChB,MAAM,IAAI,MACR,sEACF;CAIF,MAAM,iBAAiB,YAAY,QAAQ,WAAW;CACtD,IAAI,mBAAmB,IACrB,MAAM,IAAI,MACR,mEACF;CAKF,MAAM,sBAAsB,YAAY,UACtC,YAAY,GACZ,cACF;CAIA,MAAM,oBAAoB,oBAAoB,YAAY,GAAG;CAE7D,IAAI,sBAAsB,IACxB,MAAM,IAAI,MACR,sEACF;CAQF,OAJqB,oBAClB,UAAU,GAAG,iBAAiB,CAAC,CAC/B,KAEI;AACT"}
@@ -139,20 +139,20 @@ export interface CodeModeToolConfig {
139
139
  memoryLimit?: number;
140
140
  /**
141
141
  * Optional function to get additional bindings dynamically.
142
- * Called at execution time (each execute_typescript call) to get current skill bindings.
142
+ * Called at execution time (each execute_typescript call) to get current snippet bindings.
143
143
  * These are merged with the static external_* bindings.
144
144
  *
145
- * @returns Record of skill bindings with skill_ prefix
145
+ * @returns Record of snippet bindings with snippet_ prefix
146
146
  *
147
147
  * @example
148
148
  * ```typescript
149
- * getSkillBindings: async () => {
150
- * const skills = await storage.loadAll()
151
- * return skillsToBindings(skills, 'skill_')
149
+ * getSnippetBindings: async () => {
150
+ * const snippets = await storage.loadAll()
151
+ * return snippetsToBindings(snippets, 'snippet_')
152
152
  * }
153
153
  * ```
154
154
  */
155
- getSkillBindings?: () => Promise<Record<string, ToolBinding>>;
155
+ getSnippetBindings?: () => Promise<Record<string, ToolBinding>>;
156
156
  /**
157
157
  * How to surface tool parameters whose names look like secrets.
158
158
  * Defaults to `'warn'` (logs via `console.warn`).
@@ -186,8 +186,8 @@ export interface CodeModeToolConfig {
186
186
  * and `await` in its input (the default wraps the code in an async function
187
187
  * internally to allow this).
188
188
  *
189
- * NOTE: This only affects `createCodeModeTool`. The skills helpers
190
- * (`skillsToTools`, `codeModeWithSkills` in `@tanstack/ai-code-mode-skills`)
189
+ * NOTE: This only affects `createCodeModeTool`. The snippet helpers
190
+ * (`snippetsToTools`, `codeModeWithSnippets` in `@tanstack/ai-code-mode-snippets`)
191
191
  * call the exported `stripTypeScript` directly, so they ignore this hook — but
192
192
  * they still get the edge-safe sucrase default, so #487 is fixed for them too;
193
193
  * they just can't be pointed at a different transpiler.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-code-mode",
3
- "version": "0.3.11",
3
+ "version": "0.4.1",
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.44.0"
57
+ "@tanstack/ai": "^0.46.0"
58
58
  },
59
59
  "devDependencies": {
60
- "@vitest/coverage-v8": "4.0.14",
60
+ "@vitest/coverage-v8": "4.1.10",
61
61
  "zod": "^4.2.0",
62
- "@tanstack/ai": "0.44.0"
62
+ "@tanstack/ai": "0.46.0"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "vite build",
@@ -5,8 +5,8 @@ description: >
5
5
  createCodeModeTool() with isolate drivers (createNodeIsolateDriver,
6
6
  createQuickJSIsolateDriver, createQuickJSBunIsolateDriver,
7
7
  createCloudflareIsolateDriver),
8
- codeModeWithSkills() for persistent skill libraries, trust strategies,
9
- skill storage (FileSystem, LocalStorage, InMemory, Mongo), client-side
8
+ codeModeWithSnippets() for persistent snippet libraries, trust strategies,
9
+ snippet storage (FileSystem, LocalStorage, InMemory, Mongo), client-side
10
10
  execution progress via code_mode:* custom events in useChat.
11
11
  type: core
12
12
  library: tanstack-ai
@@ -14,7 +14,7 @@ library_version: '0.3.8'
14
14
  sources:
15
15
  - 'TanStack/ai:docs/code-mode/code-mode.md'
16
16
  - 'TanStack/ai:docs/code-mode/code-mode-isolates.md'
17
- - 'TanStack/ai:docs/code-mode/code-mode-with-skills.md'
17
+ - 'TanStack/ai:docs/code-mode/code-mode-with-snippets.md'
18
18
  - 'TanStack/ai:docs/code-mode/client-integration.md'
19
19
  - 'TanStack/ai:docs/code-mode/lazy-tools.md'
20
20
  ---
@@ -149,23 +149,23 @@ const driver = createCloudflareIsolateDriver({
149
149
  | QuickJS Bun | Bun servers | None | No | Fast (native QuickJS) |
150
150
  | Cloudflare | Edge deployments | None | N/A | Fast (V8 on edge) |
151
151
 
152
- ### 2. Adding Persistent Skills with codeModeWithSkills()
152
+ ### 2. Adding Persistent Snippets with codeModeWithSnippets()
153
153
 
154
- Skills let the LLM save reusable code snippets. On future requests, relevant skills are loaded and exposed as callable tools.
154
+ Snippets let the LLM save reusable code snippets. On future requests, relevant snippets are loaded and exposed as callable tools.
155
155
 
156
156
  ```typescript
157
157
  import { chat, maxIterations } from '@tanstack/ai'
158
158
  import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
159
- import { codeModeWithSkills } from '@tanstack/ai-code-mode-skills'
160
- import { createFileSkillStorage } from '@tanstack/ai-code-mode-skills/storage'
159
+ import { codeModeWithSnippets } from '@tanstack/ai-code-mode-snippets'
160
+ import { createFileSnippetStorage } from '@tanstack/ai-code-mode-snippets/storage'
161
161
  import {
162
162
  createDefaultTrustStrategy,
163
163
  createAlwaysTrustedStrategy,
164
164
  createCustomTrustStrategy,
165
- } from '@tanstack/ai-code-mode-skills'
165
+ } from '@tanstack/ai-code-mode-snippets'
166
166
  import { openaiText } from '@tanstack/ai-openai'
167
167
 
168
- // Trust strategies control how skills earn trust through executions
168
+ // Trust strategies control how snippets earn trust through executions
169
169
  // Default: untrusted -> provisional (10+ runs, >=90%) -> trusted (100+ runs, >=95%)
170
170
  // Relaxed: untrusted -> provisional (3+ runs, >=80%) -> trusted (10+ runs, >=90%)
171
171
  // Always trusted: immediately trusted (dev/testing)
@@ -173,26 +173,26 @@ import { openaiText } from '@tanstack/ai-openai'
173
173
  const trustStrategy = createDefaultTrustStrategy()
174
174
 
175
175
  // Storage options: file system (production) or memory (testing)
176
- const storage = createFileSkillStorage({
177
- directory: './.skills',
176
+ const storage = createFileSnippetStorage({
177
+ directory: './.snippets',
178
178
  trustStrategy,
179
179
  })
180
180
 
181
181
  const driver = createNodeIsolateDriver()
182
182
 
183
- // High-level API: automatic LLM-based skill selection
184
- const { toolsRegistry, systemPrompt, selectedSkills } =
185
- await codeModeWithSkills({
183
+ // High-level API: automatic LLM-based snippet selection
184
+ const { toolsRegistry, systemPrompt, selectedSnippets } =
185
+ await codeModeWithSnippets({
186
186
  config: {
187
187
  driver,
188
188
  tools: [myTool1, myTool2],
189
189
  timeout: 60_000,
190
190
  memoryLimit: 128,
191
191
  },
192
- adapter: openaiText('gpt-4o-mini'), // cheap model for skill selection
193
- skills: {
192
+ adapter: openaiText('gpt-4o-mini'), // cheap model for snippet selection
193
+ snippets: {
194
194
  storage,
195
- maxSkillsInContext: 5,
195
+ maxSnippetsInContext: 5,
196
196
  },
197
197
  messages,
198
198
  })
@@ -206,7 +206,7 @@ const stream = chat({
206
206
  })
207
207
  ```
208
208
 
209
- The registry includes: `execute_typescript`, `search_skills`, `get_skill`, `register_skill`, and one tool per selected skill.
209
+ The registry includes: `execute_typescript`, `search_snippets`, `get_snippet`, `register_snippet`, and one tool per selected snippet.
210
210
 
211
211
  Custom trust strategy example:
212
212
 
@@ -221,13 +221,13 @@ const strategy = createCustomTrustStrategy({
221
221
  Storage implementations:
222
222
 
223
223
  ```typescript
224
- // File storage (production) -- persists skills as files on disk
225
- import { createFileSkillStorage } from '@tanstack/ai-code-mode-skills/storage'
226
- const fileStorage = createFileSkillStorage({ directory: './.skills' })
224
+ // File storage (production) -- persists snippets as files on disk
225
+ import { createFileSnippetStorage } from '@tanstack/ai-code-mode-snippets/storage'
226
+ const fileStorage = createFileSnippetStorage({ directory: './.snippets' })
227
227
 
228
228
  // Memory storage (testing) -- in-memory, lost on restart
229
- import { createMemorySkillStorage } from '@tanstack/ai-code-mode-skills/storage'
230
- const memStorage = createMemorySkillStorage()
229
+ import { createMemorySnippetStorage } from '@tanstack/ai-code-mode-snippets/storage'
230
+ const memStorage = createMemorySnippetStorage()
231
231
  ```
232
232
 
233
233
  ### 3. Client-Side Execution Progress Display
@@ -334,14 +334,14 @@ The `onCustomEvent` callback signature is identical across all framework integra
334
334
  (eventType: string, data: unknown, context: { toolCallId?: string }) => void
335
335
  ```
336
336
 
337
- Skill-specific events (when using `codeModeWithSkills`):
337
+ Snippet-specific events (when using `codeModeWithSnippets`):
338
338
 
339
- | Event | When | Key fields |
340
- | ------------------------ | ------------------ | ----------------------------- |
341
- | `code_mode:skill_call` | Skill tool invoked | `skill`, `input`, `timestamp` |
342
- | `code_mode:skill_result` | Skill completed | `skill`, `result`, `duration` |
343
- | `code_mode:skill_error` | Skill failed | `skill`, `error`, `duration` |
344
- | `skill:registered` | New skill saved | `id`, `name`, `description` |
339
+ | Event | When | Key fields |
340
+ | -------------------------- | -------------------- | ------------------------------- |
341
+ | `code_mode:snippet_call` | Snippet tool invoked | `snippet`, `input`, `timestamp` |
342
+ | `code_mode:snippet_result` | Snippet completed | `snippet`, `result`, `duration` |
343
+ | `code_mode:snippet_error` | Snippet failed | `snippet`, `error`, `duration` |
344
+ | `snippet:registered` | New snippet saved | `id`, `name`, `description` |
345
345
 
346
346
  ### 4. Lazy Tools
347
347
 
@@ -94,7 +94,7 @@ export function createCodeModeTool(
94
94
  tools,
95
95
  timeout = 30000,
96
96
  memoryLimit = 128,
97
- getSkillBindings,
97
+ getSnippetBindings,
98
98
  onSecretParameter,
99
99
  transpile = stripTypeScript,
100
100
  } = config
@@ -107,7 +107,7 @@ export function createCodeModeTool(
107
107
  // Transform tools to bindings with external_ prefix (static bindings)
108
108
  const staticBindings = toolsToBindings(tools, 'external_')
109
109
 
110
- // Shared across static + dynamic (skill) binding scans so a given
110
+ // Shared across static + dynamic (snippet) binding scans so a given
111
111
  // (toolName, paramPath) pair surfaces at most once per code-mode instance.
112
112
  const secretDedupCache = new Set<string>()
113
113
 
@@ -220,22 +220,24 @@ export function createCodeModeTool(
220
220
  )
221
221
  }
222
222
 
223
- // Step 2: Get dynamic skill bindings if available
224
- const skillBindings = getSkillBindings ? await getSkillBindings() : {}
223
+ // Step 2: Get dynamic snippet bindings if available
224
+ const snippetBindings = getSnippetBindings
225
+ ? await getSnippetBindings()
226
+ : {}
225
227
 
226
228
  // Scan dynamic bindings too — their schemas are equally in-scope for
227
229
  // the same exfiltration threat. Dedup cache prevents repeat warnings
228
230
  // when the same binding reappears across executions.
229
- const skillBindingValues = Object.values(skillBindings)
230
- if (skillBindingValues.length > 0) {
231
- warnIfBindingsExposeSecrets(skillBindingValues, {
231
+ const snippetBindingValues = Object.values(snippetBindings)
232
+ if (snippetBindingValues.length > 0) {
233
+ warnIfBindingsExposeSecrets(snippetBindingValues, {
232
234
  handler: onSecretParameter,
233
235
  dedupCache: secretDedupCache,
234
236
  })
235
237
  }
236
238
 
237
239
  // Step 3: Merge static and dynamic bindings, then wrap with event awareness
238
- const allBindings = { ...staticBindings, ...skillBindings }
240
+ const allBindings = { ...staticBindings, ...snippetBindings }
239
241
  const eventAwareBindings = createEventAwareBindings(
240
242
  allBindings,
241
243
  emitCustomEvent,
package/src/types.ts CHANGED
@@ -183,20 +183,20 @@ export interface CodeModeToolConfig {
183
183
 
184
184
  /**
185
185
  * Optional function to get additional bindings dynamically.
186
- * Called at execution time (each execute_typescript call) to get current skill bindings.
186
+ * Called at execution time (each execute_typescript call) to get current snippet bindings.
187
187
  * These are merged with the static external_* bindings.
188
188
  *
189
- * @returns Record of skill bindings with skill_ prefix
189
+ * @returns Record of snippet bindings with snippet_ prefix
190
190
  *
191
191
  * @example
192
192
  * ```typescript
193
- * getSkillBindings: async () => {
194
- * const skills = await storage.loadAll()
195
- * return skillsToBindings(skills, 'skill_')
193
+ * getSnippetBindings: async () => {
194
+ * const snippets = await storage.loadAll()
195
+ * return snippetsToBindings(snippets, 'snippet_')
196
196
  * }
197
197
  * ```
198
198
  */
199
- getSkillBindings?: () => Promise<Record<string, ToolBinding>>
199
+ getSnippetBindings?: () => Promise<Record<string, ToolBinding>>
200
200
 
201
201
  /**
202
202
  * How to surface tool parameters whose names look like secrets.
@@ -233,8 +233,8 @@ export interface CodeModeToolConfig {
233
233
  * and `await` in its input (the default wraps the code in an async function
234
234
  * internally to allow this).
235
235
  *
236
- * NOTE: This only affects `createCodeModeTool`. The skills helpers
237
- * (`skillsToTools`, `codeModeWithSkills` in `@tanstack/ai-code-mode-skills`)
236
+ * NOTE: This only affects `createCodeModeTool`. The snippet helpers
237
+ * (`snippetsToTools`, `codeModeWithSnippets` in `@tanstack/ai-code-mode-snippets`)
238
238
  * call the exported `stripTypeScript` directly, so they ignore this hook — but
239
239
  * they still get the edge-safe sucrase default, so #487 is fixed for them too;
240
240
  * they just can't be pointed at a different transpiler.