{"version":3,"file":"toolDiscoveryCache.cjs","sources":["../../../src/utils/toolDiscoveryCache.ts"],"sourcesContent":["// src/utils/toolDiscoveryCache.ts\nimport type { BaseMessage } from '@langchain/core/messages';\nimport { Constants, MessageTypes } from '@/common';\nimport { TOOL_DISCOVERY_CACHE_MAX_SIZE } from '@/common/constants';\n\n/**\n * Cached tool discovery entry.\n * Stores the tool name and the message index where it was discovered,\n * enabling efficient lookups without re-parsing conversation history.\n */\nexport interface ToolDiscoveryEntry {\n  /** The tool name that was discovered */\n  toolName: string;\n  /** Message index in conversation history where discovery occurred */\n  discoveredAtIndex: number;\n}\n\n/**\n * ToolDiscoveryCache provides a run-scoped cache of tool search results.\n *\n * Problem: Without caching, every LLM iteration re-parses the full message\n * history via extractToolDiscoveries() to find tool_search results. In long\n * conversations with many tool iterations, this is redundant work.\n *\n * Solution: Cache discovered tool names by message index. On each iteration,\n * only scan messages AFTER the last scanned index. Already-seen discoveries\n * are returned from cache instantly.\n *\n * This mirrors the pattern used by VS Code Copilot Chat where tool search\n * results from prior turns are cached to avoid re-discovery.\n *\n * @example\n * ```ts\n * const cache = new ToolDiscoveryCache();\n *\n * // First call: scans all messages\n * const newTools = cache.getNewDiscoveries(messages);\n * // Returns: ['web_search', 'file_read']\n *\n * // Second call (3 new messages added): only scans new messages\n * const moreTools = cache.getNewDiscoveries(messages);\n * // Returns: ['code_exec'] (only newly discovered)\n * ```\n */\nexport class ToolDiscoveryCache {\n  /** Set of all discovered tool names (deduped) */\n  private _discoveredTools: Set<string> = new Set();\n  /** Last message index that was scanned */\n  private _lastScannedIndex: number = -1;\n\n  /**\n   * Scan messages for new tool_search results since the last scan.\n   * Only processes messages after `_lastScannedIndex` to avoid redundant work.\n   *\n   * @param messages - Full conversation message array\n   * @returns Array of newly discovered tool names (not previously cached)\n   */\n  getNewDiscoveries(messages: BaseMessage[]): string[] {\n    if (messages.length === 0) {\n      return [];\n    }\n\n    const startIndex = this._lastScannedIndex + 1;\n    if (startIndex >= messages.length) {\n      return [];\n    }\n\n    const newDiscoveries: string[] = [];\n\n    for (let i = startIndex; i < messages.length; i++) {\n      const msg = messages[i];\n      if (msg.getType() !== MessageTypes.TOOL) {\n        continue;\n      }\n\n      // Check if this is a tool_search result\n      if ((msg as { name?: string }).name !== Constants.TOOL_SEARCH) {\n        continue;\n      }\n\n      // Extract tool references from artifact\n      const artifact = (msg as { artifact?: unknown }).artifact;\n      if (typeof artifact === 'object' && artifact != null) {\n        const refs = (\n          artifact as { tool_references?: Array<{ tool_name: string }> }\n        ).tool_references;\n        if (refs && refs.length > 0) {\n          for (const ref of refs) {\n            if (!this._discoveredTools.has(ref.tool_name)) {\n              // Enforce cache size limit\n              if (this._discoveredTools.size >= TOOL_DISCOVERY_CACHE_MAX_SIZE) {\n                break;\n              }\n              this._discoveredTools.add(ref.tool_name);\n              newDiscoveries.push(ref.tool_name);\n            }\n          }\n        }\n      }\n    }\n\n    this._lastScannedIndex = messages.length - 1;\n    return newDiscoveries;\n  }\n\n  /**\n   * Returns all tool names discovered so far (across all scans).\n   */\n  getAllDiscoveredTools(): string[] {\n    return [...this._discoveredTools];\n  }\n\n  /**\n   * Check if a specific tool has been discovered.\n   */\n  has(toolName: string): boolean {\n    return this._discoveredTools.has(toolName);\n  }\n\n  /**\n   * Number of unique tools discovered.\n   */\n  get size(): number {\n    return this._discoveredTools.size;\n  }\n\n  /**\n   * Reset the cache (e.g., on graph reset).\n   */\n  reset(): void {\n    this._discoveredTools.clear();\n    this._lastScannedIndex = -1;\n  }\n\n  /**\n   * Seed the cache with previously known tool names (e.g., from prior conversation turns).\n   * Does not affect _lastScannedIndex — the next getNewDiscoveries call will still\n   * scan all messages from the beginning.\n   *\n   * @param toolNames - Tool names to pre-seed into the cache\n   */\n  seed(toolNames: string[]): void {\n    for (const name of toolNames) {\n      if (this._discoveredTools.size >= TOOL_DISCOVERY_CACHE_MAX_SIZE) {\n        break;\n      }\n      this._discoveredTools.add(name);\n    }\n  }\n}\n"],"names":["MessageTypes","Constants","TOOL_DISCOVERY_CACHE_MAX_SIZE"],"mappings":";;;;;;AAiBA;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BG;MACU,kBAAkB,CAAA;;AAErB,IAAA,gBAAgB,GAAgB,IAAI,GAAG,EAAE;;IAEzC,iBAAiB,GAAW,EAAE;AAEtC;;;;;;AAMG;AACH,IAAA,iBAAiB,CAAC,QAAuB,EAAA;AACvC,QAAA,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE;AACzB,YAAA,OAAO,EAAE;QACX;AAEA,QAAA,MAAM,UAAU,GAAG,IAAI,CAAC,iBAAiB,GAAG,CAAC;AAC7C,QAAA,IAAI,UAAU,IAAI,QAAQ,CAAC,MAAM,EAAE;AACjC,YAAA,OAAO,EAAE;QACX;QAEA,MAAM,cAAc,GAAa,EAAE;AAEnC,QAAA,KAAK,IAAI,CAAC,GAAG,UAAU,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACjD,YAAA,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC;YACvB,IAAI,GAAG,CAAC,OAAO,EAAE,KAAKA,kBAAY,CAAC,IAAI,EAAE;gBACvC;YACF;;YAGA,IAAK,GAAyB,CAAC,IAAI,KAAKC,eAAS,CAAC,WAAW,EAAE;gBAC7D;YACF;;AAGA,YAAA,MAAM,QAAQ,GAAI,GAA8B,CAAC,QAAQ;YACzD,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,IAAI,IAAI,EAAE;AACpD,gBAAA,MAAM,IAAI,GACR,QACD,CAAC,eAAe;gBACjB,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE;AAC3B,oBAAA,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE;AACtB,wBAAA,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE;;4BAE7C,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,IAAIC,uCAA6B,EAAE;gCAC/D;4BACF;4BACA,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC;AACxC,4BAAA,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;wBACpC;oBACF;gBACF;YACF;QACF;QAEA,IAAI,CAAC,iBAAiB,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC;AAC5C,QAAA,OAAO,cAAc;IACvB;AAEA;;AAEG;IACH,qBAAqB,GAAA;AACnB,QAAA,OAAO,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC;IACnC;AAEA;;AAEG;AACH,IAAA,GAAG,CAAC,QAAgB,EAAA;QAClB,OAAO,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC5C;AAEA;;AAEG;AACH,IAAA,IAAI,IAAI,GAAA;AACN,QAAA,OAAO,IAAI,CAAC,gBAAgB,CAAC,IAAI;IACnC;AAEA;;AAEG;IACH,KAAK,GAAA;AACH,QAAA,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE;AAC7B,QAAA,IAAI,CAAC,iBAAiB,GAAG,EAAE;IAC7B;AAEA;;;;;;AAMG;AACH,IAAA,IAAI,CAAC,SAAmB,EAAA;AACtB,QAAA,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE;YAC5B,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,IAAIA,uCAA6B,EAAE;gBAC/D;YACF;AACA,YAAA,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC;QACjC;IACF;AACD;;;;"}