UNPKG

@tanstack/ai

Version:

Core TanStack AI library - Open source AI SDK

160 lines (159 loc) 5.32 kB
import { convertSchemaToJsonSchema } from "./schema-converter.js"; const DISCOVERY_TOOL_NAME = "__lazy__tool__discovery__"; class LazyToolManager { constructor(tools, messages) { const eager = []; this.lazyToolMap = /* @__PURE__ */ new Map(); this.discoveredTools = /* @__PURE__ */ new Set(); this.hasNewDiscoveries = false; for (const tool of tools) { if (tool.lazy) { this.lazyToolMap.set(tool.name, tool); } else { eager.push(tool); } } this.eagerTools = eager; if (this.lazyToolMap.size === 0) { this.discoveryTool = null; return; } this.scanMessageHistory(messages); this.discoveryTool = this.createDiscoveryTool(); } /** * Returns the set of tools that should be sent to the LLM: * eager tools + discovered lazy tools + discovery tool (if undiscovered tools remain). * Resets the hasNewDiscoveries flag. */ getActiveTools() { this.hasNewDiscoveries = false; const active = [...this.eagerTools]; for (const name of this.discoveredTools) { const tool = this.lazyToolMap.get(name); if (tool) { active.push(tool); } } if (this.discoveryTool && this.discoveredTools.size < this.lazyToolMap.size) { active.push(this.discoveryTool); } return active; } /** * Returns whether new tools have been discovered since the last getActiveTools() call. */ hasNewlyDiscoveredTools() { return this.hasNewDiscoveries; } /** * Returns true if the given name is a lazy tool that has not yet been discovered. */ isUndiscoveredLazyTool(name) { return this.lazyToolMap.has(name) && !this.discoveredTools.has(name); } /** * Returns a helpful error message for when an undiscovered lazy tool is called. */ getUndiscoveredToolError(name) { return `Error: Tool '${name}' must be discovered first. Call ${DISCOVERY_TOOL_NAME} with toolNames: ['${name}'] to discover it.`; } /** * Scans message history to find previously discovered lazy tools. * Looks for assistant messages with discovery tool calls and their * corresponding tool result messages. */ scanMessageHistory(messages) { const discoveryCallIds = /* @__PURE__ */ new Set(); for (const msg of messages) { if (msg.role === "assistant" && msg.toolCalls) { for (const tc of msg.toolCalls) { if (tc.function.name === DISCOVERY_TOOL_NAME) { discoveryCallIds.add(tc.id); } } } } if (discoveryCallIds.size === 0) return; for (const msg of messages) { if (msg.role === "tool" && msg.toolCallId && discoveryCallIds.has(msg.toolCallId)) { try { const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content); const parsed = JSON.parse(content); if (parsed && Array.isArray(parsed.tools)) { for (const tool of parsed.tools) { if (tool && typeof tool.name === "string" && this.lazyToolMap.has(tool.name)) { this.discoveredTools.add(tool.name); } } } } catch { } } } } /** * Creates the synthetic discovery tool that the LLM can call * to discover lazy tools' descriptions and schemas. */ createDiscoveryTool() { const undiscoveredNames = () => { const names = []; for (const [name] of this.lazyToolMap) { if (!this.discoveredTools.has(name)) { names.push(name); } } return names; }; const lazyToolMap = this.lazyToolMap; const allLazyNames = Array.from(this.lazyToolMap.keys()); const description = `You have access to additional tools that can be discovered. Available tools: [${allLazyNames.join(", ")}]. Call this tool with a list of tool names to discover their full descriptions and argument schemas before using them.`; const manager = this; return { name: DISCOVERY_TOOL_NAME, description, inputSchema: { type: "object", properties: { toolNames: { type: "array", items: { type: "string" }, description: "List of tool names to discover. Each name must match one of the available tools." } }, required: ["toolNames"] }, execute: (args) => { const tools = []; const errors = []; for (const name of args.toolNames) { const tool = lazyToolMap.get(name); if (tool) { manager.discoveredTools.add(name); manager.hasNewDiscoveries = true; const jsonSchema = tool.inputSchema ? convertSchemaToJsonSchema(tool.inputSchema) : void 0; tools.push({ name: tool.name, description: tool.description, ...jsonSchema ? { inputSchema: jsonSchema } : {} }); } else { errors.push( `Unknown tool: '${name}'. Available tools: [${undiscoveredNames().join(", ")}]` ); } } const result = { tools }; if (errors.length > 0) { result.errors = errors; } return result; } }; } } export { LazyToolManager }; //# sourceMappingURL=lazy-tool-manager.js.map