@tanstack/ai-code-mode 0.2.13 → 0.3.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/dist/esm/create-code-mode-tool.js +5 -2
- package/dist/esm/create-code-mode-tool.js.map +1 -1
- package/dist/esm/create-code-mode.d.ts +7 -25
- package/dist/esm/create-code-mode.js +9 -2
- package/dist/esm/create-code-mode.js.map +1 -1
- package/dist/esm/create-discovery-tool.d.ts +28 -0
- package/dist/esm/create-discovery-tool.js +62 -0
- package/dist/esm/create-discovery-tool.js.map +1 -0
- package/dist/esm/create-system-prompt.js +17 -7
- package/dist/esm/create-system-prompt.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types.d.ts +23 -1
- package/package.json +3 -3
- package/skills/ai-code-mode/SKILL.md +79 -0
- package/src/create-code-mode-tool.ts +8 -2
- package/src/create-code-mode.ts +23 -13
- package/src/create-discovery-tool.ts +108 -0
- package/src/create-system-prompt.ts +25 -10
- package/src/index.ts +2 -0
- package/src/types.ts +25 -0
|
@@ -134,8 +134,11 @@ function createCodeModeTool(config) {
|
|
|
134
134
|
);
|
|
135
135
|
}
|
|
136
136
|
function buildToolDescription(tools) {
|
|
137
|
-
const
|
|
138
|
-
|
|
137
|
+
const eager = tools.filter((t) => !t.lazy);
|
|
138
|
+
const hasLazy = tools.some((t) => t.lazy);
|
|
139
|
+
const externalFunctions = eager.map((t) => `external_${t.name}`).join(", ");
|
|
140
|
+
const discoverable = hasLazy ? ` Additional functions can be discovered via the discover_tools tool.` : "";
|
|
141
|
+
return `Execute TypeScript code in a secure sandbox environment. The code can use these external API functions: ${externalFunctions}.${discoverable} All external_* calls are async and must be awaited. Return a value to pass results back. Use console.log() for debugging.`;
|
|
139
142
|
}
|
|
140
143
|
export {
|
|
141
144
|
createCodeModeTool
|
|
@@ -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 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":"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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 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"],"names":[],"mappings":"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"}
|
|
@@ -1,41 +1,23 @@
|
|
|
1
|
-
import { CodeModeToolConfig } from './types.js';
|
|
1
|
+
import { CodeModeToolConfig, CreateCodeModeResult } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Create
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* This is the recommended way to set up Code Mode — it ensures the tool and
|
|
7
|
-
* system prompt always stay in sync.
|
|
3
|
+
* Create the `execute_typescript` tool, its matching system prompt, and (when
|
|
4
|
+
* any tools are marked `lazy: true`) a `discover_tools` companion tool.
|
|
8
5
|
*
|
|
9
6
|
* @example
|
|
10
7
|
* ```typescript
|
|
11
8
|
* import { createCodeMode } from '@tanstack/ai-code-mode'
|
|
12
9
|
* import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
|
|
13
10
|
*
|
|
14
|
-
* const {
|
|
11
|
+
* const { tools, systemPrompt } = createCodeMode({
|
|
15
12
|
* driver: createNodeIsolateDriver(),
|
|
16
|
-
* tools: [weatherTool,
|
|
17
|
-
* timeout: 30000,
|
|
13
|
+
* tools: [weatherTool, rarelyUsedTool], // mark rarelyUsedTool lazy: true
|
|
18
14
|
* })
|
|
19
15
|
*
|
|
20
16
|
* chat({
|
|
21
17
|
* systemPrompts: [myPrompt, systemPrompt],
|
|
22
|
-
* tools: [
|
|
18
|
+
* tools: [...tools, ...otherTools],
|
|
23
19
|
* messages,
|
|
24
20
|
* })
|
|
25
21
|
* ```
|
|
26
22
|
*/
|
|
27
|
-
export declare function createCodeMode(config: CodeModeToolConfig):
|
|
28
|
-
tool: import('@tanstack/ai').ServerTool<import('zod').ZodObject<{
|
|
29
|
-
typescriptCode: import('zod').ZodString;
|
|
30
|
-
}, import('zod/v4/core').$strip>, import('zod').ZodObject<{
|
|
31
|
-
success: import('zod').ZodBoolean;
|
|
32
|
-
result: import('zod').ZodOptional<import('zod').ZodUnknown>;
|
|
33
|
-
logs: import('zod').ZodOptional<import('zod').ZodArray<import('zod').ZodString>>;
|
|
34
|
-
error: import('zod').ZodOptional<import('zod').ZodObject<{
|
|
35
|
-
message: import('zod').ZodString;
|
|
36
|
-
name: import('zod').ZodOptional<import('zod').ZodString>;
|
|
37
|
-
line: import('zod').ZodOptional<import('zod').ZodNumber>;
|
|
38
|
-
}, import('zod/v4/core').$strip>>;
|
|
39
|
-
}, import('zod/v4/core').$strip>, "execute_typescript", unknown>;
|
|
40
|
-
systemPrompt: string;
|
|
41
|
-
};
|
|
23
|
+
export declare function createCodeMode(config: CodeModeToolConfig): CreateCodeModeResult;
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { createCodeModeTool } from "./create-code-mode-tool.js";
|
|
2
2
|
import { createCodeModeSystemPrompt } from "./create-system-prompt.js";
|
|
3
|
+
import { createDiscoveryTool } from "./create-discovery-tool.js";
|
|
3
4
|
function createCodeMode(config) {
|
|
5
|
+
const tool = createCodeModeTool(config);
|
|
6
|
+
const systemPrompt = createCodeModeSystemPrompt(config);
|
|
7
|
+
const lazyTools = config.tools.filter((t) => t.lazy);
|
|
8
|
+
const discoveryTool = lazyTools.length > 0 ? createDiscoveryTool(lazyTools, config.lazyToolsConfig) : null;
|
|
4
9
|
return {
|
|
5
|
-
tool
|
|
6
|
-
|
|
10
|
+
tool,
|
|
11
|
+
discoveryTool,
|
|
12
|
+
tools: discoveryTool ? [tool, discoveryTool] : [tool],
|
|
13
|
+
systemPrompt
|
|
7
14
|
};
|
|
8
15
|
}
|
|
9
16
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-code-mode.js","sources":["../../src/create-code-mode.ts"],"sourcesContent":["import { createCodeModeTool } from './create-code-mode-tool'\nimport { createCodeModeSystemPrompt } from './create-system-prompt'\nimport type { CodeModeToolConfig } from './types'\n\n/**\n * Create
|
|
1
|
+
{"version":3,"file":"create-code-mode.js","sources":["../../src/create-code-mode.ts"],"sourcesContent":["import { createCodeModeTool } from './create-code-mode-tool'\nimport { createCodeModeSystemPrompt } from './create-system-prompt'\nimport { createDiscoveryTool } from './create-discovery-tool'\nimport type { CodeModeToolConfig, CreateCodeModeResult } from './types'\n\n/**\n * Create the `execute_typescript` tool, its matching system prompt, and (when\n * any tools are marked `lazy: true`) a `discover_tools` companion tool.\n *\n * @example\n * ```typescript\n * import { createCodeMode } from '@tanstack/ai-code-mode'\n * import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'\n *\n * const { tools, systemPrompt } = createCodeMode({\n * driver: createNodeIsolateDriver(),\n * tools: [weatherTool, rarelyUsedTool], // mark rarelyUsedTool lazy: true\n * })\n *\n * chat({\n * systemPrompts: [myPrompt, systemPrompt],\n * tools: [...tools, ...otherTools],\n * messages,\n * })\n * ```\n */\nexport function createCodeMode(\n config: CodeModeToolConfig,\n): CreateCodeModeResult {\n const tool = createCodeModeTool(config)\n const systemPrompt = createCodeModeSystemPrompt(config)\n\n const lazyTools = config.tools.filter((t) => t.lazy)\n const discoveryTool =\n lazyTools.length > 0\n ? createDiscoveryTool(lazyTools, config.lazyToolsConfig)\n : null\n\n return {\n tool,\n discoveryTool,\n tools: discoveryTool ? [tool, discoveryTool] : [tool],\n systemPrompt,\n }\n}\n"],"names":[],"mappings":";;;AA0BO,SAAS,eACd,QACsB;AACtB,QAAM,OAAO,mBAAmB,MAAM;AACtC,QAAM,eAAe,2BAA2B,MAAM;AAEtD,QAAM,YAAY,OAAO,MAAM,OAAO,CAAC,MAAM,EAAE,IAAI;AACnD,QAAM,gBACJ,UAAU,SAAS,IACf,oBAAoB,WAAW,OAAO,eAAe,IACrD;AAEN,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,gBAAgB,CAAC,MAAM,aAAa,IAAI,CAAC,IAAI;AAAA,IACpD;AAAA,EAAA;AAEJ;"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { LazyToolsConfig, ServerTool } from '@tanstack/ai';
|
|
3
|
+
import { CodeModeTool } from './types.js';
|
|
4
|
+
declare const discoverInputSchema: z.ZodObject<{
|
|
5
|
+
toolNames: z.ZodArray<z.ZodString>;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
declare const discoverOutputSchema: z.ZodObject<{
|
|
8
|
+
tools: z.ZodArray<z.ZodObject<{
|
|
9
|
+
name: z.ZodString;
|
|
10
|
+
description: z.ZodString;
|
|
11
|
+
typeStub: z.ZodString;
|
|
12
|
+
}, z.core.$strip>>;
|
|
13
|
+
errors: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
/**
|
|
16
|
+
* Build the `discover_tools` sibling tool for Code Mode lazy tools. The model
|
|
17
|
+
* calls it with lazy tool names and receives each one's TypeScript type stub +
|
|
18
|
+
* description, which it can then use to write correctly-typed `external_*`
|
|
19
|
+
* calls inside `execute_typescript`. The bindings themselves are always present
|
|
20
|
+
* in the sandbox — this only reveals documentation.
|
|
21
|
+
*
|
|
22
|
+
* Tools are catalogued in `external_<name>` form to match the "Discoverable
|
|
23
|
+
* APIs" section of the Code Mode system prompt; lookups tolerate either form.
|
|
24
|
+
* `lazyToolsConfig.includeDescription` controls how much of each tool's
|
|
25
|
+
* description appears in this tool's own catalog (mirroring the system prompt).
|
|
26
|
+
*/
|
|
27
|
+
export declare function createDiscoveryTool(lazyTools: Array<CodeModeTool>, lazyToolsConfig?: LazyToolsConfig): ServerTool<typeof discoverInputSchema, typeof discoverOutputSchema, 'discover_tools'>;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { renderLazyCatalogEntry, toolDefinition } from "@tanstack/ai";
|
|
3
|
+
import { toolToBinding } from "./bindings/tool-to-binding.js";
|
|
4
|
+
import { generateTypeStubs } from "./type-generator/json-schema-to-ts.js";
|
|
5
|
+
const discoverInputSchema = z.object({
|
|
6
|
+
toolNames: z.array(z.string()).describe(
|
|
7
|
+
'Names of tools to discover, exactly as shown in the Discoverable APIs catalog. The external_ prefix is optional — both "external_fetchStocks" and "fetchStocks" resolve.'
|
|
8
|
+
)
|
|
9
|
+
});
|
|
10
|
+
const discoverOutputSchema = z.object({
|
|
11
|
+
tools: z.array(
|
|
12
|
+
z.object({
|
|
13
|
+
name: z.string().describe("The sandbox function name, e.g. external_fetchStocks"),
|
|
14
|
+
description: z.string(),
|
|
15
|
+
typeStub: z.string().describe("TypeScript declaration for the function")
|
|
16
|
+
})
|
|
17
|
+
),
|
|
18
|
+
errors: z.array(z.string()).optional()
|
|
19
|
+
});
|
|
20
|
+
const EXTERNAL_PREFIX = "external_";
|
|
21
|
+
function stripExternalPrefix(name) {
|
|
22
|
+
return name.startsWith(EXTERNAL_PREFIX) ? name.slice(EXTERNAL_PREFIX.length) : name;
|
|
23
|
+
}
|
|
24
|
+
function createDiscoveryTool(lazyTools, lazyToolsConfig) {
|
|
25
|
+
const lazyMap = new Map(lazyTools.map((t) => [t.name, t]));
|
|
26
|
+
const include = lazyToolsConfig?.includeDescription ?? "none";
|
|
27
|
+
const catalog = lazyTools.map(
|
|
28
|
+
(t) => renderLazyCatalogEntry(
|
|
29
|
+
`${EXTERNAL_PREFIX}${t.name}`,
|
|
30
|
+
t.description,
|
|
31
|
+
include
|
|
32
|
+
)
|
|
33
|
+
).join(", ");
|
|
34
|
+
return toolDefinition({
|
|
35
|
+
name: "discover_tools",
|
|
36
|
+
description: `Discover full TypeScript signatures for additional sandbox APIs before using them inside execute_typescript. Discoverable tools: [${catalog}]. Pass the names exactly as shown (the external_ prefix is optional).`,
|
|
37
|
+
inputSchema: discoverInputSchema,
|
|
38
|
+
outputSchema: discoverOutputSchema
|
|
39
|
+
}).server(async ({ toolNames }) => {
|
|
40
|
+
const tools = [];
|
|
41
|
+
const errors = [];
|
|
42
|
+
for (const name of toolNames) {
|
|
43
|
+
const tool = lazyMap.get(stripExternalPrefix(name));
|
|
44
|
+
if (!tool) {
|
|
45
|
+
errors.push(`Unknown tool: '${name}'. Discoverable tools: [${catalog}]`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const binding = toolToBinding(tool, EXTERNAL_PREFIX);
|
|
49
|
+
const typeStub = generateTypeStubs({ [binding.name]: binding });
|
|
50
|
+
tools.push({
|
|
51
|
+
name: binding.name,
|
|
52
|
+
description: tool.description,
|
|
53
|
+
typeStub
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return errors.length > 0 ? { tools, errors } : { tools };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
createDiscoveryTool
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=create-discovery-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-discovery-tool.js","sources":["../../src/create-discovery-tool.ts"],"sourcesContent":["import { z } from 'zod'\nimport { renderLazyCatalogEntry, toolDefinition } from '@tanstack/ai'\nimport { toolToBinding } from './bindings/tool-to-binding'\nimport { generateTypeStubs } from './type-generator/json-schema-to-ts'\nimport type { LazyToolsConfig, ServerTool } from '@tanstack/ai'\nimport type { CodeModeTool } from './types'\n\nconst discoverInputSchema = z.object({\n toolNames: z\n .array(z.string())\n .describe(\n 'Names of tools to discover, exactly as shown in the Discoverable APIs ' +\n 'catalog. The external_ prefix is optional — both \"external_fetchStocks\" ' +\n 'and \"fetchStocks\" resolve.',\n ),\n})\n\nconst discoverOutputSchema = z.object({\n tools: z.array(\n z.object({\n name: z\n .string()\n .describe('The sandbox function name, e.g. external_fetchStocks'),\n description: z.string(),\n typeStub: z.string().describe('TypeScript declaration for the function'),\n }),\n ),\n errors: z.array(z.string()).optional(),\n})\n\nconst EXTERNAL_PREFIX = 'external_'\n\n/**\n * Strip a single leading `external_` prefix so the model can pass either the\n * catalog name (`external_fetchStocks`) or the bare name (`fetchStocks`).\n */\nfunction stripExternalPrefix(name: string): string {\n return name.startsWith(EXTERNAL_PREFIX)\n ? name.slice(EXTERNAL_PREFIX.length)\n : name\n}\n\n/**\n * Build the `discover_tools` sibling tool for Code Mode lazy tools. The model\n * calls it with lazy tool names and receives each one's TypeScript type stub +\n * description, which it can then use to write correctly-typed `external_*`\n * calls inside `execute_typescript`. The bindings themselves are always present\n * in the sandbox — this only reveals documentation.\n *\n * Tools are catalogued in `external_<name>` form to match the \"Discoverable\n * APIs\" section of the Code Mode system prompt; lookups tolerate either form.\n * `lazyToolsConfig.includeDescription` controls how much of each tool's\n * description appears in this tool's own catalog (mirroring the system prompt).\n */\nexport function createDiscoveryTool(\n lazyTools: Array<CodeModeTool>,\n lazyToolsConfig?: LazyToolsConfig,\n): ServerTool<\n typeof discoverInputSchema,\n typeof discoverOutputSchema,\n 'discover_tools'\n> {\n const lazyMap = new Map(lazyTools.map((t) => [t.name, t]))\n const include = lazyToolsConfig?.includeDescription ?? 'none'\n const catalog = lazyTools\n .map((t) =>\n renderLazyCatalogEntry(\n `${EXTERNAL_PREFIX}${t.name}`,\n t.description,\n include,\n ),\n )\n .join(', ')\n\n return toolDefinition({\n name: 'discover_tools' as const,\n description:\n `Discover full TypeScript signatures for additional sandbox APIs before ` +\n `using them inside execute_typescript. Discoverable tools: [${catalog}]. ` +\n `Pass the names exactly as shown (the external_ prefix is optional).`,\n inputSchema: discoverInputSchema,\n outputSchema: discoverOutputSchema,\n }).server(async ({ toolNames }) => {\n const tools: Array<{\n name: string\n description: string\n typeStub: string\n }> = []\n const errors: Array<string> = []\n\n for (const name of toolNames) {\n const tool = lazyMap.get(stripExternalPrefix(name))\n if (!tool) {\n errors.push(`Unknown tool: '${name}'. Discoverable tools: [${catalog}]`)\n continue\n }\n const binding = toolToBinding(tool, EXTERNAL_PREFIX)\n const typeStub = generateTypeStubs({ [binding.name]: binding })\n tools.push({\n name: binding.name,\n description: tool.description,\n typeStub,\n })\n }\n\n return errors.length > 0 ? { tools, errors } : { tools }\n })\n}\n"],"names":[],"mappings":";;;;AAOA,MAAM,sBAAsB,EAAE,OAAO;AAAA,EACnC,WAAW,EACR,MAAM,EAAE,OAAA,CAAQ,EAChB;AAAA,IACC;AAAA,EAAA;AAIN,CAAC;AAED,MAAM,uBAAuB,EAAE,OAAO;AAAA,EACpC,OAAO,EAAE;AAAA,IACP,EAAE,OAAO;AAAA,MACP,MAAM,EACH,SACA,SAAS,sDAAsD;AAAA,MAClE,aAAa,EAAE,OAAA;AAAA,MACf,UAAU,EAAE,OAAA,EAAS,SAAS,yCAAyC;AAAA,IAAA,CACxE;AAAA,EAAA;AAAA,EAEH,QAAQ,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,SAAA;AAC9B,CAAC;AAED,MAAM,kBAAkB;AAMxB,SAAS,oBAAoB,MAAsB;AACjD,SAAO,KAAK,WAAW,eAAe,IAClC,KAAK,MAAM,gBAAgB,MAAM,IACjC;AACN;AAcO,SAAS,oBACd,WACA,iBAKA;AACA,QAAM,UAAU,IAAI,IAAI,UAAU,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AACzD,QAAM,UAAU,iBAAiB,sBAAsB;AACvD,QAAM,UAAU,UACb;AAAA,IAAI,CAAC,MACJ;AAAA,MACE,GAAG,eAAe,GAAG,EAAE,IAAI;AAAA,MAC3B,EAAE;AAAA,MACF;AAAA,IAAA;AAAA,EACF,EAED,KAAK,IAAI;AAEZ,SAAO,eAAe;AAAA,IACpB,MAAM;AAAA,IACN,aACE,qIAC8D,OAAO;AAAA,IAEvE,aAAa;AAAA,IACb,cAAc;AAAA,EAAA,CACf,EAAE,OAAO,OAAO,EAAE,gBAAgB;AACjC,UAAM,QAID,CAAA;AACL,UAAM,SAAwB,CAAA;AAE9B,eAAW,QAAQ,WAAW;AAC5B,YAAM,OAAO,QAAQ,IAAI,oBAAoB,IAAI,CAAC;AAClD,UAAI,CAAC,MAAM;AACT,eAAO,KAAK,kBAAkB,IAAI,2BAA2B,OAAO,GAAG;AACvE;AAAA,MACF;AACA,YAAM,UAAU,cAAc,MAAM,eAAe;AACnD,YAAM,WAAW,kBAAkB,EAAE,CAAC,QAAQ,IAAI,GAAG,SAAS;AAC9D,YAAM,KAAK;AAAA,QACT,MAAM,QAAQ;AAAA,QACd,aAAa,KAAK;AAAA,QAClB;AAAA,MAAA,CACD;AAAA,IACH;AAEA,WAAO,OAAO,SAAS,IAAI,EAAE,OAAO,OAAA,IAAW,EAAE,MAAA;AAAA,EACnD,CAAC;AACH;"}
|
|
@@ -1,13 +1,23 @@
|
|
|
1
|
+
import { renderLazyCatalogEntry } from "@tanstack/ai";
|
|
1
2
|
import { toolsToBindings } from "./bindings/tool-to-binding.js";
|
|
2
3
|
import { generateTypeStubs } from "./type-generator/json-schema-to-ts.js";
|
|
3
4
|
function createCodeModeSystemPrompt(config) {
|
|
4
5
|
const { tools } = config;
|
|
5
|
-
const
|
|
6
|
+
const include = config.lazyToolsConfig?.includeDescription ?? "none";
|
|
7
|
+
const eagerTools = tools.filter((t) => !t.lazy);
|
|
8
|
+
const lazyTools = tools.filter((t) => t.lazy);
|
|
9
|
+
const bindings = toolsToBindings(eagerTools, "external_");
|
|
6
10
|
const typeStubs = generateTypeStubs(bindings);
|
|
7
|
-
const functionDocs = Object.entries(bindings).map(([name, binding]) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
const functionDocs = Object.entries(bindings).map(([name, binding]) => `- \`${name}(input)\`: ${binding.description}`).join("\n");
|
|
12
|
+
const discoverableSection = lazyTools.length > 0 ? `
|
|
13
|
+
|
|
14
|
+
### Discoverable APIs
|
|
15
|
+
|
|
16
|
+
These additional functions are available but not yet documented. Before calling \`external_<name>\` for any of them inside \`execute_typescript\`, call the \`discover_tools\` tool with their names to get full TypeScript signatures:
|
|
17
|
+
|
|
18
|
+
${lazyTools.map(
|
|
19
|
+
(t) => `- ${renderLazyCatalogEntry(`external_${t.name}`, t.description, include)}`
|
|
20
|
+
).join("\n")}` : "";
|
|
11
21
|
return `## Code Execution Tool
|
|
12
22
|
|
|
13
23
|
You have access to \`execute_typescript\` which runs TypeScript code in a sandboxed environment.
|
|
@@ -32,7 +42,7 @@ ${functionDocs}
|
|
|
32
42
|
|
|
33
43
|
\`\`\`typescript
|
|
34
44
|
${typeStubs}
|
|
35
|
-
|
|
45
|
+
\`\`\`${discoverableSection}
|
|
36
46
|
|
|
37
47
|
### Example
|
|
38
48
|
|
|
@@ -44,7 +54,7 @@ const results = await Promise.all(
|
|
|
44
54
|
);
|
|
45
55
|
|
|
46
56
|
// Find the warmest city
|
|
47
|
-
const warmest = results.reduce((prev, curr) =>
|
|
57
|
+
const warmest = results.reduce((prev, curr) =>
|
|
48
58
|
curr.temperature > prev.temperature ? curr : prev
|
|
49
59
|
);
|
|
50
60
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-system-prompt.js","sources":["../../src/create-system-prompt.ts"],"sourcesContent":["import { toolsToBindings } from './bindings/tool-to-binding'\nimport { generateTypeStubs } from './type-generator/json-schema-to-ts'\nimport type { CodeModeToolConfig } from './types'\n\n/**\n * Create a system prompt snippet that documents the execute_typescript tool\n * and all available external_* functions.\n *\n * Add this to your system prompts array when using createCodeModeTool.\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],\n * })\n *\n * chat({\n * systemPrompts: ['You are a helpful assistant.', systemPrompt],\n * tools: [tool, ...otherTools],\n * })\n * ```\n */\nexport function createCodeModeSystemPrompt(config: CodeModeToolConfig): string {\n const { tools } = config\n
|
|
1
|
+
{"version":3,"file":"create-system-prompt.js","sources":["../../src/create-system-prompt.ts"],"sourcesContent":["import { renderLazyCatalogEntry } from '@tanstack/ai'\nimport { toolsToBindings } from './bindings/tool-to-binding'\nimport { generateTypeStubs } from './type-generator/json-schema-to-ts'\nimport type { CodeModeToolConfig } from './types'\n\n/**\n * Create a system prompt snippet that documents the execute_typescript tool\n * and all available external_* functions.\n *\n * Add this to your system prompts array when using createCodeModeTool.\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],\n * })\n *\n * chat({\n * systemPrompts: ['You are a helpful assistant.', systemPrompt],\n * tools: [tool, ...otherTools],\n * })\n * ```\n */\nexport function createCodeModeSystemPrompt(config: CodeModeToolConfig): string {\n const { tools } = config\n const include = config.lazyToolsConfig?.includeDescription ?? 'none'\n\n const eagerTools = tools.filter((t) => !t.lazy)\n const lazyTools = tools.filter((t) => t.lazy)\n\n // Only eager tools get full type stubs + doc lines.\n const bindings = toolsToBindings(eagerTools, 'external_')\n const typeStubs = generateTypeStubs(bindings)\n\n const functionDocs = Object.entries(bindings)\n .map(([name, binding]) => `- \\`${name}(input)\\`: ${binding.description}`)\n .join('\\n')\n\n const discoverableSection =\n lazyTools.length > 0\n ? `\n\n### Discoverable APIs\n\nThese additional functions are available but not yet documented. Before calling \\`external_<name>\\` for any of them inside \\`execute_typescript\\`, call the \\`discover_tools\\` tool with their names to get full TypeScript signatures:\n\n${lazyTools\n .map(\n (t) =>\n `- ${renderLazyCatalogEntry(`external_${t.name}`, t.description, include)}`,\n )\n .join('\\n')}`\n : ''\n\n return `## Code Execution Tool\n\nYou have access to \\`execute_typescript\\` which runs TypeScript code in a sandboxed environment.\n\n### When to Use\n\nUse \\`execute_typescript\\` when you need to:\n- Process data with loops, conditionals, or complex logic\n- Make multiple API calls in parallel (Promise.all)\n- Transform, filter, or aggregate data\n- Perform calculations or data analysis\n\nFor simple operations, prefer calling tools directly.\n\n### Available External APIs\n\nInside your TypeScript code, you can call these async functions:\n\n${functionDocs}\n\n### Type Definitions\n\n\\`\\`\\`typescript\n${typeStubs}\n\\`\\`\\`${discoverableSection}\n\n### Example\n\n\\`\\`\\`typescript\n// Fetch weather for multiple cities in parallel\nconst cities = [\"Tokyo\", \"Paris\", \"NYC\"];\nconst results = await Promise.all(\n cities.map(city => external_fetchWeather({ location: city }))\n);\n\n// Find the warmest city\nconst warmest = results.reduce((prev, curr) =>\n curr.temperature > prev.temperature ? curr : prev\n);\n\nreturn { warmestCity: warmest.location, temperature: warmest.temperature };\n\\`\\`\\`\n\n### Important Notes\n\n- All \\`external_*\\` calls are async - always use \\`await\\`\n- Return a value to pass results back to you\n- Use \\`console.log()\\` for debugging (logs are captured)\n- The sandbox is isolated - no network access or file system\n- Each execution is independent (no shared state between calls)\n`\n}\n"],"names":[],"mappings":";;;AA2BO,SAAS,2BAA2B,QAAoC;AAC7E,QAAM,EAAE,UAAU;AAClB,QAAM,UAAU,OAAO,iBAAiB,sBAAsB;AAE9D,QAAM,aAAa,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI;AAC9C,QAAM,YAAY,MAAM,OAAO,CAAC,MAAM,EAAE,IAAI;AAG5C,QAAM,WAAW,gBAAgB,YAAY,WAAW;AACxD,QAAM,YAAY,kBAAkB,QAAQ;AAE5C,QAAM,eAAe,OAAO,QAAQ,QAAQ,EACzC,IAAI,CAAC,CAAC,MAAM,OAAO,MAAM,OAAO,IAAI,cAAc,QAAQ,WAAW,EAAE,EACvE,KAAK,IAAI;AAEZ,QAAM,sBACJ,UAAU,SAAS,IACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMN,UACC;AAAA,IACC,CAAC,MACC,KAAK,uBAAuB,YAAY,EAAE,IAAI,IAAI,EAAE,aAAa,OAAO,CAAC;AAAA,EAAA,EAE5E,KAAK,IAAI,CAAC,KACL;AAEN,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBP,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA,EAKZ,SAAS;AAAA,QACH,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2B3B;"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -2,9 +2,10 @@ export { createCodeModeTool } from './create-code-mode-tool.js';
|
|
|
2
2
|
export type { ExecuteTypescriptInput, ExecuteTypescriptOutput, } from './create-code-mode-tool.js';
|
|
3
3
|
export { createCodeModeSystemPrompt } from './create-system-prompt.js';
|
|
4
4
|
export { createCodeMode } from './create-code-mode.js';
|
|
5
|
+
export { createDiscoveryTool } from './create-discovery-tool.js';
|
|
5
6
|
export { InMemoryAgentStore, generateAgentName, type AgentSession, type AgentStore, } from './agent-store.js';
|
|
6
7
|
export { toolToBinding, toolsToBindings, createEventAwareBindings, } from './bindings/tool-to-binding.js';
|
|
7
8
|
export { generateTypeStubs, jsonSchemaToTypeScript, type TypeGeneratorOptions, } from './type-generator/json-schema-to-ts.js';
|
|
8
9
|
export { stripTypeScript } from './strip-typescript.js';
|
|
9
10
|
export { wrapCode } from './code-wrapper.js';
|
|
10
|
-
export type { CodeModeToolConfig, CodeModeToolResult, IsolateDriver, IsolateConfig, IsolateContext, ExecutionResult, NormalizedError, ToolBinding, CodeModeTool, ToolExecutionContext, } from './types.js';
|
|
11
|
+
export type { CodeModeToolConfig, CreateCodeModeResult, CodeModeToolResult, IsolateDriver, IsolateConfig, IsolateContext, ExecutionResult, NormalizedError, ToolBinding, CodeModeTool, ToolExecutionContext, } from './types.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createCodeModeTool } from "./create-code-mode-tool.js";
|
|
2
2
|
import { createCodeModeSystemPrompt } from "./create-system-prompt.js";
|
|
3
3
|
import { createCodeMode } from "./create-code-mode.js";
|
|
4
|
+
import { createDiscoveryTool } from "./create-discovery-tool.js";
|
|
4
5
|
import { InMemoryAgentStore, generateAgentName } from "./agent-store.js";
|
|
5
6
|
import { createEventAwareBindings, toolToBinding, toolsToBindings } from "./bindings/tool-to-binding.js";
|
|
6
7
|
import { generateTypeStubs, jsonSchemaToTypeScript } from "./type-generator/json-schema-to-ts.js";
|
|
@@ -11,6 +12,7 @@ export {
|
|
|
11
12
|
createCodeMode,
|
|
12
13
|
createCodeModeSystemPrompt,
|
|
13
14
|
createCodeModeTool,
|
|
15
|
+
createDiscoveryTool,
|
|
14
16
|
createEventAwareBindings,
|
|
15
17
|
generateAgentName,
|
|
16
18
|
generateTypeStubs,
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SchemaInput, ServerTool, ToolExecutionContext } from '@tanstack/ai';
|
|
1
|
+
import { LazyToolsConfig, SchemaInput, ServerTool, ToolExecutionContext } from '@tanstack/ai';
|
|
2
2
|
/**
|
|
3
3
|
* Interface for isolate/sandbox drivers
|
|
4
4
|
* Each runtime environment implements this to provide sandboxed code execution
|
|
@@ -152,6 +152,13 @@ export interface CodeModeToolConfig {
|
|
|
152
152
|
* ```
|
|
153
153
|
*/
|
|
154
154
|
getSkillBindings?: () => Promise<Record<string, ToolBinding>>;
|
|
155
|
+
/**
|
|
156
|
+
* Optional lazy-tool discovery config. Tools marked `lazy: true` are kept out
|
|
157
|
+
* of the system prompt's full documentation and listed in a Discoverable APIs
|
|
158
|
+
* catalog instead; this tunes how much of each lazy tool's description that
|
|
159
|
+
* catalog shows. Optional — defaults to `{ includeDescription: 'none' }`.
|
|
160
|
+
*/
|
|
161
|
+
lazyToolsConfig?: LazyToolsConfig;
|
|
155
162
|
/**
|
|
156
163
|
* Optional escape hatch to swap out the TypeScript-stripping step.
|
|
157
164
|
*
|
|
@@ -214,3 +221,18 @@ export interface CodeModeToolResult {
|
|
|
214
221
|
line?: number | undefined;
|
|
215
222
|
} | undefined;
|
|
216
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Return shape of `createCodeMode`. `tool` (execute_typescript) and
|
|
226
|
+
* `systemPrompt` are preserved for backward compatibility; `discoveryTool` and
|
|
227
|
+
* `tools` are additive. Spread `tools` into `chat({ tools })`.
|
|
228
|
+
*/
|
|
229
|
+
export interface CreateCodeModeResult {
|
|
230
|
+
/** The execute_typescript tool. */
|
|
231
|
+
tool: ServerTool<SchemaInput, SchemaInput, 'execute_typescript'>;
|
|
232
|
+
/** The discover_tools tool, or null when there are no lazy tools. */
|
|
233
|
+
discoveryTool: ServerTool<SchemaInput, SchemaInput, 'discover_tools'> | null;
|
|
234
|
+
/** [tool] or [tool, discoveryTool] — the array to spread into chat({ tools }). */
|
|
235
|
+
tools: Array<ServerTool<SchemaInput, SchemaInput, string>>;
|
|
236
|
+
/** The matching system prompt. */
|
|
237
|
+
systemPrompt: string;
|
|
238
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-code-mode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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.
|
|
57
|
+
"@tanstack/ai": "0.37.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@vitest/coverage-v8": "4.0.14",
|
|
61
61
|
"zod": "^4.2.0",
|
|
62
|
-
"@tanstack/ai": "0.
|
|
62
|
+
"@tanstack/ai": "0.37.0"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "vite build",
|
|
@@ -15,6 +15,7 @@ sources:
|
|
|
15
15
|
- 'TanStack/ai:docs/code-mode/code-mode-isolates.md'
|
|
16
16
|
- 'TanStack/ai:docs/code-mode/code-mode-with-skills.md'
|
|
17
17
|
- 'TanStack/ai:docs/code-mode/client-integration.md'
|
|
18
|
+
- 'TanStack/ai:docs/code-mode/lazy-tools.md'
|
|
18
19
|
---
|
|
19
20
|
|
|
20
21
|
> **Note**: This skill requires familiarity with ai-core and ai-core/chat-experience. Code Mode is always used on top of a chat experience.
|
|
@@ -328,6 +329,84 @@ Skill-specific events (when using `codeModeWithSkills`):
|
|
|
328
329
|
| `code_mode:skill_error` | Skill failed | `skill`, `error`, `duration` |
|
|
329
330
|
| `skill:registered` | New skill saved | `id`, `name`, `description` |
|
|
330
331
|
|
|
332
|
+
### 4. Lazy Tools
|
|
333
|
+
|
|
334
|
+
When a large tool catalog would bloat the `execute_typescript` system prompt, mark low-priority tools `lazy: true`. Lazy tools are kept out of the full type-stub documentation and listed in a compact "Discoverable APIs" catalog instead. All sandbox bindings are always injected — `lazy` defers documentation, not callability.
|
|
335
|
+
|
|
336
|
+
**Marking a tool lazy:**
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import { toolDefinition } from '@tanstack/ai'
|
|
340
|
+
import { z } from 'zod'
|
|
341
|
+
|
|
342
|
+
const rarelyUsedTool = toolDefinition({
|
|
343
|
+
name: 'fetchStocks',
|
|
344
|
+
description: 'Get stock prices for a ticker. Returns a price quote.',
|
|
345
|
+
inputSchema: z.object({ ticker: z.string() }),
|
|
346
|
+
outputSchema: z.object({ price: z.number() }),
|
|
347
|
+
lazy: true, // <-- opt out of full system-prompt documentation
|
|
348
|
+
}).server(async ({ ticker }) => {
|
|
349
|
+
// ...
|
|
350
|
+
return { price: 0 }
|
|
351
|
+
})
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**`createCodeMode` return shape:**
|
|
355
|
+
|
|
356
|
+
`createCodeMode()` returns `{ tool, discoveryTool, tools, systemPrompt }`. When lazy tools are present `discoveryTool` is a `discover_tools` server tool; otherwise it is `null`. Always spread `tools` (not just `tool`) into `chat()` so the discovery tool is registered:
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
import { chat } from '@tanstack/ai'
|
|
360
|
+
import { createCodeMode } from '@tanstack/ai-code-mode'
|
|
361
|
+
import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
|
|
362
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
363
|
+
|
|
364
|
+
const { tools, systemPrompt } = createCodeMode({
|
|
365
|
+
driver: createNodeIsolateDriver(),
|
|
366
|
+
tools: [eagerTool, rarelyUsedTool], // rarelyUsedTool has lazy: true
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
const stream = chat({
|
|
370
|
+
adapter: openaiText('gpt-5.5'),
|
|
371
|
+
systemPrompts: ['You are a helpful assistant.', systemPrompt],
|
|
372
|
+
tools: [...tools, ...otherTools], // spread tools, not just tool
|
|
373
|
+
messages,
|
|
374
|
+
})
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
`tools` equals `[tool]` when there are no lazy tools (backward compatible) and `[tool, discoveryTool]` when lazy tools exist.
|
|
378
|
+
|
|
379
|
+
**`discover_tools` flow:**
|
|
380
|
+
|
|
381
|
+
When the model encounters a lazy tool it has not seen before, it calls `discover_tools` with the bare name (no `external_` prefix). The tool returns each requested tool's TypeScript type stub and description. The model then writes correctly-typed `external_<name>` calls inside `execute_typescript`.
|
|
382
|
+
|
|
383
|
+
```text
|
|
384
|
+
Model sees: "Discoverable APIs: external_fetchStocks"
|
|
385
|
+
Model calls: discover_tools({ toolNames: ["fetchStocks"] })
|
|
386
|
+
Response: { tools: [{ name: "external_fetchStocks", description: "...", typeStub: "declare function external_fetchStocks(...)" }] }
|
|
387
|
+
Model writes inside execute_typescript: const result = await external_fetchStocks({ ticker: "AAPL" })
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**`lazyToolsConfig.includeDescription`:**
|
|
391
|
+
|
|
392
|
+
Control how much of each lazy tool's description appears in the Discoverable APIs catalog (the pre-discovery list):
|
|
393
|
+
|
|
394
|
+
| Value | Catalog entry |
|
|
395
|
+
| ------------------ | ----------------------------------------------------------------- |
|
|
396
|
+
| `'none'` | `external_fetchStocks` (name only — default) |
|
|
397
|
+
| `'first-sentence'` | `external_fetchStocks — Get stock prices.` |
|
|
398
|
+
| `'full'` | `external_fetchStocks — Get stock prices. Returns a price quote.` |
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
const { tools, systemPrompt } = createCodeMode({
|
|
402
|
+
driver: createNodeIsolateDriver(),
|
|
403
|
+
tools: [eagerTool, rarelyUsedTool],
|
|
404
|
+
lazyToolsConfig: { includeDescription: 'first-sentence' },
|
|
405
|
+
})
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
The same `lazyToolsConfig` option is accepted by plain `chat()` for its own lazy-tool discovery catalog (see `ai-core/tool-calling/SKILL.md`).
|
|
409
|
+
|
|
331
410
|
## Common Mistakes
|
|
332
411
|
|
|
333
412
|
### CRITICAL: Passing API keys or secrets to the sandbox environment
|
|
@@ -245,11 +245,17 @@ export function createCodeModeTool(
|
|
|
245
245
|
* Build the tool description including available external functions
|
|
246
246
|
*/
|
|
247
247
|
function buildToolDescription(tools: Array<CodeModeTool>): string {
|
|
248
|
-
const
|
|
248
|
+
const eager = tools.filter((t) => !t.lazy)
|
|
249
|
+
const hasLazy = tools.some((t) => t.lazy)
|
|
250
|
+
const externalFunctions = eager.map((t) => `external_${t.name}`).join(', ')
|
|
251
|
+
|
|
252
|
+
const discoverable = hasLazy
|
|
253
|
+
? ` Additional functions can be discovered via the discover_tools tool.`
|
|
254
|
+
: ''
|
|
249
255
|
|
|
250
256
|
return (
|
|
251
257
|
`Execute TypeScript code in a secure sandbox environment. ` +
|
|
252
|
-
`The code can use these external API functions: ${externalFunctions}
|
|
258
|
+
`The code can use these external API functions: ${externalFunctions}.${discoverable} ` +
|
|
253
259
|
`All external_* calls are async and must be awaited. ` +
|
|
254
260
|
`Return a value to pass results back. Use console.log() for debugging.`
|
|
255
261
|
)
|
package/src/create-code-mode.ts
CHANGED
|
@@ -1,35 +1,45 @@
|
|
|
1
1
|
import { createCodeModeTool } from './create-code-mode-tool'
|
|
2
2
|
import { createCodeModeSystemPrompt } from './create-system-prompt'
|
|
3
|
-
import
|
|
3
|
+
import { createDiscoveryTool } from './create-discovery-tool'
|
|
4
|
+
import type { CodeModeToolConfig, CreateCodeModeResult } from './types'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
* Create
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* This is the recommended way to set up Code Mode — it ensures the tool and
|
|
10
|
-
* system prompt always stay in sync.
|
|
7
|
+
* Create the `execute_typescript` tool, its matching system prompt, and (when
|
|
8
|
+
* any tools are marked `lazy: true`) a `discover_tools` companion tool.
|
|
11
9
|
*
|
|
12
10
|
* @example
|
|
13
11
|
* ```typescript
|
|
14
12
|
* import { createCodeMode } from '@tanstack/ai-code-mode'
|
|
15
13
|
* import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
|
|
16
14
|
*
|
|
17
|
-
* const {
|
|
15
|
+
* const { tools, systemPrompt } = createCodeMode({
|
|
18
16
|
* driver: createNodeIsolateDriver(),
|
|
19
|
-
* tools: [weatherTool,
|
|
20
|
-
* timeout: 30000,
|
|
17
|
+
* tools: [weatherTool, rarelyUsedTool], // mark rarelyUsedTool lazy: true
|
|
21
18
|
* })
|
|
22
19
|
*
|
|
23
20
|
* chat({
|
|
24
21
|
* systemPrompts: [myPrompt, systemPrompt],
|
|
25
|
-
* tools: [
|
|
22
|
+
* tools: [...tools, ...otherTools],
|
|
26
23
|
* messages,
|
|
27
24
|
* })
|
|
28
25
|
* ```
|
|
29
26
|
*/
|
|
30
|
-
export function createCodeMode(
|
|
27
|
+
export function createCodeMode(
|
|
28
|
+
config: CodeModeToolConfig,
|
|
29
|
+
): CreateCodeModeResult {
|
|
30
|
+
const tool = createCodeModeTool(config)
|
|
31
|
+
const systemPrompt = createCodeModeSystemPrompt(config)
|
|
32
|
+
|
|
33
|
+
const lazyTools = config.tools.filter((t) => t.lazy)
|
|
34
|
+
const discoveryTool =
|
|
35
|
+
lazyTools.length > 0
|
|
36
|
+
? createDiscoveryTool(lazyTools, config.lazyToolsConfig)
|
|
37
|
+
: null
|
|
38
|
+
|
|
31
39
|
return {
|
|
32
|
-
tool
|
|
33
|
-
|
|
40
|
+
tool,
|
|
41
|
+
discoveryTool,
|
|
42
|
+
tools: discoveryTool ? [tool, discoveryTool] : [tool],
|
|
43
|
+
systemPrompt,
|
|
34
44
|
}
|
|
35
45
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { renderLazyCatalogEntry, toolDefinition } from '@tanstack/ai'
|
|
3
|
+
import { toolToBinding } from './bindings/tool-to-binding'
|
|
4
|
+
import { generateTypeStubs } from './type-generator/json-schema-to-ts'
|
|
5
|
+
import type { LazyToolsConfig, ServerTool } from '@tanstack/ai'
|
|
6
|
+
import type { CodeModeTool } from './types'
|
|
7
|
+
|
|
8
|
+
const discoverInputSchema = z.object({
|
|
9
|
+
toolNames: z
|
|
10
|
+
.array(z.string())
|
|
11
|
+
.describe(
|
|
12
|
+
'Names of tools to discover, exactly as shown in the Discoverable APIs ' +
|
|
13
|
+
'catalog. The external_ prefix is optional — both "external_fetchStocks" ' +
|
|
14
|
+
'and "fetchStocks" resolve.',
|
|
15
|
+
),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const discoverOutputSchema = z.object({
|
|
19
|
+
tools: z.array(
|
|
20
|
+
z.object({
|
|
21
|
+
name: z
|
|
22
|
+
.string()
|
|
23
|
+
.describe('The sandbox function name, e.g. external_fetchStocks'),
|
|
24
|
+
description: z.string(),
|
|
25
|
+
typeStub: z.string().describe('TypeScript declaration for the function'),
|
|
26
|
+
}),
|
|
27
|
+
),
|
|
28
|
+
errors: z.array(z.string()).optional(),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const EXTERNAL_PREFIX = 'external_'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Strip a single leading `external_` prefix so the model can pass either the
|
|
35
|
+
* catalog name (`external_fetchStocks`) or the bare name (`fetchStocks`).
|
|
36
|
+
*/
|
|
37
|
+
function stripExternalPrefix(name: string): string {
|
|
38
|
+
return name.startsWith(EXTERNAL_PREFIX)
|
|
39
|
+
? name.slice(EXTERNAL_PREFIX.length)
|
|
40
|
+
: name
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Build the `discover_tools` sibling tool for Code Mode lazy tools. The model
|
|
45
|
+
* calls it with lazy tool names and receives each one's TypeScript type stub +
|
|
46
|
+
* description, which it can then use to write correctly-typed `external_*`
|
|
47
|
+
* calls inside `execute_typescript`. The bindings themselves are always present
|
|
48
|
+
* in the sandbox — this only reveals documentation.
|
|
49
|
+
*
|
|
50
|
+
* Tools are catalogued in `external_<name>` form to match the "Discoverable
|
|
51
|
+
* APIs" section of the Code Mode system prompt; lookups tolerate either form.
|
|
52
|
+
* `lazyToolsConfig.includeDescription` controls how much of each tool's
|
|
53
|
+
* description appears in this tool's own catalog (mirroring the system prompt).
|
|
54
|
+
*/
|
|
55
|
+
export function createDiscoveryTool(
|
|
56
|
+
lazyTools: Array<CodeModeTool>,
|
|
57
|
+
lazyToolsConfig?: LazyToolsConfig,
|
|
58
|
+
): ServerTool<
|
|
59
|
+
typeof discoverInputSchema,
|
|
60
|
+
typeof discoverOutputSchema,
|
|
61
|
+
'discover_tools'
|
|
62
|
+
> {
|
|
63
|
+
const lazyMap = new Map(lazyTools.map((t) => [t.name, t]))
|
|
64
|
+
const include = lazyToolsConfig?.includeDescription ?? 'none'
|
|
65
|
+
const catalog = lazyTools
|
|
66
|
+
.map((t) =>
|
|
67
|
+
renderLazyCatalogEntry(
|
|
68
|
+
`${EXTERNAL_PREFIX}${t.name}`,
|
|
69
|
+
t.description,
|
|
70
|
+
include,
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
.join(', ')
|
|
74
|
+
|
|
75
|
+
return toolDefinition({
|
|
76
|
+
name: 'discover_tools' as const,
|
|
77
|
+
description:
|
|
78
|
+
`Discover full TypeScript signatures for additional sandbox APIs before ` +
|
|
79
|
+
`using them inside execute_typescript. Discoverable tools: [${catalog}]. ` +
|
|
80
|
+
`Pass the names exactly as shown (the external_ prefix is optional).`,
|
|
81
|
+
inputSchema: discoverInputSchema,
|
|
82
|
+
outputSchema: discoverOutputSchema,
|
|
83
|
+
}).server(async ({ toolNames }) => {
|
|
84
|
+
const tools: Array<{
|
|
85
|
+
name: string
|
|
86
|
+
description: string
|
|
87
|
+
typeStub: string
|
|
88
|
+
}> = []
|
|
89
|
+
const errors: Array<string> = []
|
|
90
|
+
|
|
91
|
+
for (const name of toolNames) {
|
|
92
|
+
const tool = lazyMap.get(stripExternalPrefix(name))
|
|
93
|
+
if (!tool) {
|
|
94
|
+
errors.push(`Unknown tool: '${name}'. Discoverable tools: [${catalog}]`)
|
|
95
|
+
continue
|
|
96
|
+
}
|
|
97
|
+
const binding = toolToBinding(tool, EXTERNAL_PREFIX)
|
|
98
|
+
const typeStub = generateTypeStubs({ [binding.name]: binding })
|
|
99
|
+
tools.push({
|
|
100
|
+
name: binding.name,
|
|
101
|
+
description: tool.description,
|
|
102
|
+
typeStub,
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return errors.length > 0 ? { tools, errors } : { tools }
|
|
107
|
+
})
|
|
108
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { renderLazyCatalogEntry } from '@tanstack/ai'
|
|
1
2
|
import { toolsToBindings } from './bindings/tool-to-binding'
|
|
2
3
|
import { generateTypeStubs } from './type-generator/json-schema-to-ts'
|
|
3
4
|
import type { CodeModeToolConfig } from './types'
|
|
@@ -26,21 +27,35 @@ import type { CodeModeToolConfig } from './types'
|
|
|
26
27
|
*/
|
|
27
28
|
export function createCodeModeSystemPrompt(config: CodeModeToolConfig): string {
|
|
28
29
|
const { tools } = config
|
|
30
|
+
const include = config.lazyToolsConfig?.includeDescription ?? 'none'
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
+
const eagerTools = tools.filter((t) => !t.lazy)
|
|
33
|
+
const lazyTools = tools.filter((t) => t.lazy)
|
|
32
34
|
|
|
33
|
-
//
|
|
35
|
+
// Only eager tools get full type stubs + doc lines.
|
|
36
|
+
const bindings = toolsToBindings(eagerTools, 'external_')
|
|
34
37
|
const typeStubs = generateTypeStubs(bindings)
|
|
35
38
|
|
|
36
|
-
// Build function documentation
|
|
37
39
|
const functionDocs = Object.entries(bindings)
|
|
38
|
-
.map(([name, binding]) => {
|
|
39
|
-
const doc = `- \`${name}(input)\`: ${binding.description}`
|
|
40
|
-
return doc
|
|
41
|
-
})
|
|
40
|
+
.map(([name, binding]) => `- \`${name}(input)\`: ${binding.description}`)
|
|
42
41
|
.join('\n')
|
|
43
42
|
|
|
43
|
+
const discoverableSection =
|
|
44
|
+
lazyTools.length > 0
|
|
45
|
+
? `
|
|
46
|
+
|
|
47
|
+
### Discoverable APIs
|
|
48
|
+
|
|
49
|
+
These additional functions are available but not yet documented. Before calling \`external_<name>\` for any of them inside \`execute_typescript\`, call the \`discover_tools\` tool with their names to get full TypeScript signatures:
|
|
50
|
+
|
|
51
|
+
${lazyTools
|
|
52
|
+
.map(
|
|
53
|
+
(t) =>
|
|
54
|
+
`- ${renderLazyCatalogEntry(`external_${t.name}`, t.description, include)}`,
|
|
55
|
+
)
|
|
56
|
+
.join('\n')}`
|
|
57
|
+
: ''
|
|
58
|
+
|
|
44
59
|
return `## Code Execution Tool
|
|
45
60
|
|
|
46
61
|
You have access to \`execute_typescript\` which runs TypeScript code in a sandboxed environment.
|
|
@@ -65,7 +80,7 @@ ${functionDocs}
|
|
|
65
80
|
|
|
66
81
|
\`\`\`typescript
|
|
67
82
|
${typeStubs}
|
|
68
|
-
|
|
83
|
+
\`\`\`${discoverableSection}
|
|
69
84
|
|
|
70
85
|
### Example
|
|
71
86
|
|
|
@@ -77,7 +92,7 @@ const results = await Promise.all(
|
|
|
77
92
|
);
|
|
78
93
|
|
|
79
94
|
// Find the warmest city
|
|
80
|
-
const warmest = results.reduce((prev, curr) =>
|
|
95
|
+
const warmest = results.reduce((prev, curr) =>
|
|
81
96
|
curr.temperature > prev.temperature ? curr : prev
|
|
82
97
|
);
|
|
83
98
|
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type {
|
|
|
7
7
|
|
|
8
8
|
export { createCodeModeSystemPrompt } from './create-system-prompt'
|
|
9
9
|
export { createCodeMode } from './create-code-mode'
|
|
10
|
+
export { createDiscoveryTool } from './create-discovery-tool'
|
|
10
11
|
|
|
11
12
|
export {
|
|
12
13
|
InMemoryAgentStore,
|
|
@@ -39,6 +40,7 @@ export { wrapCode } from './code-wrapper'
|
|
|
39
40
|
export type {
|
|
40
41
|
// Tool-based API types
|
|
41
42
|
CodeModeToolConfig,
|
|
43
|
+
CreateCodeModeResult,
|
|
42
44
|
CodeModeToolResult,
|
|
43
45
|
// Isolate driver interfaces (used by driver packages)
|
|
44
46
|
IsolateDriver,
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
LazyToolsConfig,
|
|
2
3
|
SchemaInput,
|
|
3
4
|
ServerTool,
|
|
4
5
|
ToolExecutionContext,
|
|
@@ -196,6 +197,14 @@ export interface CodeModeToolConfig {
|
|
|
196
197
|
*/
|
|
197
198
|
getSkillBindings?: () => Promise<Record<string, ToolBinding>>
|
|
198
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Optional lazy-tool discovery config. Tools marked `lazy: true` are kept out
|
|
202
|
+
* of the system prompt's full documentation and listed in a Discoverable APIs
|
|
203
|
+
* catalog instead; this tunes how much of each lazy tool's description that
|
|
204
|
+
* catalog shows. Optional — defaults to `{ includeDescription: 'none' }`.
|
|
205
|
+
*/
|
|
206
|
+
lazyToolsConfig?: LazyToolsConfig
|
|
207
|
+
|
|
199
208
|
/**
|
|
200
209
|
* Optional escape hatch to swap out the TypeScript-stripping step.
|
|
201
210
|
*
|
|
@@ -264,3 +273,19 @@ export interface CodeModeToolResult {
|
|
|
264
273
|
}
|
|
265
274
|
| undefined
|
|
266
275
|
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Return shape of `createCodeMode`. `tool` (execute_typescript) and
|
|
279
|
+
* `systemPrompt` are preserved for backward compatibility; `discoveryTool` and
|
|
280
|
+
* `tools` are additive. Spread `tools` into `chat({ tools })`.
|
|
281
|
+
*/
|
|
282
|
+
export interface CreateCodeModeResult {
|
|
283
|
+
/** The execute_typescript tool. */
|
|
284
|
+
tool: ServerTool<SchemaInput, SchemaInput, 'execute_typescript'>
|
|
285
|
+
/** The discover_tools tool, or null when there are no lazy tools. */
|
|
286
|
+
discoveryTool: ServerTool<SchemaInput, SchemaInput, 'discover_tools'> | null
|
|
287
|
+
/** [tool] or [tool, discoveryTool] — the array to spread into chat({ tools }). */
|
|
288
|
+
tools: Array<ServerTool<SchemaInput, SchemaInput, string>>
|
|
289
|
+
/** The matching system prompt. */
|
|
290
|
+
systemPrompt: string
|
|
291
|
+
}
|