@tanstack/ai
Version:
Core TanStack AI library - Open source AI SDK
160 lines (159 loc) • 5.32 kB
JavaScript
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