import { Type } from "typebox"; import { CacheManager } from "../shared/cache"; import { getCacheConfig } from "../shared/env"; import { serializeResponseWithMeta, type SerializedToolResult } from "../shared/serialize"; /** * Aggregated library documentation search across all providers. * * Mirrors python `server._locate_library_docs` + `search_library_docs` tool. * * The factory takes a map of provider-name -> instantiated Provider (only those that * support library search are queried). This keeps the tool decoupled from the exact * provider registry; index.ts wires the real registry in. */ const KEY_MAP: Record = { pypi: "pypi", godocs: "godocs", github: "github_repos", }; export interface SearchLibraryDeps { cacheManager: CacheManager; /** provider name -> Provider instance (already constructed). Only supportsLibrarySearch ones are used. */ providers: Record; } async function locateLibraryDocs( deps: SearchLibraryDeps, library: string, limit = 5, ): Promise> { const result: Record = { library }; const [cacheEnabled, cacheTtl] = getCacheConfig(); const cacheKey = `search:${library}:${limit}`; if (cacheEnabled) { const cached = deps.cacheManager.get(cacheKey); if (cached) { const age = Date.now() / 1000 - cached.timestamp; if (age < cacheTtl) { return cached.data as Record; } } } for (const [providerName, provider] of Object.entries(deps.providers)) { const metadata = provider.getMetadata(); if (!metadata.supportsLibrarySearch) continue; const providerResult = await provider.searchLibrary(library, limit); if (providerResult.success) { const resultKey = KEY_MAP[providerName] ?? providerName; result[resultKey] = providerResult.data; } else if (providerResult.error !== null && providerResult.error !== undefined) { const errorKey = `${providerName}_error`; result[errorKey] = providerResult.error; } // error === null (e.g. npm/crates/godocs/gcp 404) → silent, no key added } if (cacheEnabled) { deps.cacheManager.set(cacheKey, result); } return result; } export function createSearchLibraryDocsTool(deps: SearchLibraryDeps) { return { name: "search_library_docs", label: "Search Library Docs", description: "Search for library docs across PyPI, GoDocs, GitHub. Returns metadata with stats.", promptSnippet: "Aggregate library-documentation search across PyPI, GoDocs, GitHub, and more", parameters: Type.Object({ library: Type.String({ description: "Library/package name to search for" }), limit: Type.Optional( Type.Integer({ description: "Maximum results per provider", default: 5 }), ), }), async execute(_toolCallId: string, params: { library: string; limit?: number }): Promise { const limit = params.limit ?? 5; const result = await locateLibraryDocs(deps, params.library, limit); return serializeResponseWithMeta(result); }, }; }