{"version":3,"file":"mcp-deferred.d.ts","sourceRoot":"","sources":["../../../src/extensions/core/mcp-deferred.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAEzE,wFAAwF;AACxF,MAAM,WAAW,oBAAoB;IACpC,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,sEAAoE;IACpE,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAkBtC;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,oBAAoB,EAAE,EAAE,KAAK,SAAsB,GAAG,MAAM,CAuB1G;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,oBAAoB,EAAE,CAczG;AAED,0DAA0D;AAC1D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAG,aAAa,EAAE,CASpG","sourcesContent":["/**\n * Deferred MCP tool schemas (spec §2) — the light, testable half.\n *\n * MCP tool schemas are the only genuinely heavy thing an installed plugin's\n * server adds to context: every tool's full JSON schema lands up front. When\n * deferral is enabled (opt-in, top-level agent only), the loader injects tool\n * *names* only and materializes each schema on demand — the same deferred-tool /\n * ToolSearch shape the harness uses. This module holds the format-agnostic\n * catalog helpers; the connect/register machinery lives in mcp-loader.ts.\n *\n * Only half the cost used to be deferred. Schemas were withheld, but the full\n * catalog — every name and description — was rendered into the resolver's\n * description on every request, and against a ~2,710-token tool-schema budget an\n * unbounded catalog dominates. That was forced by the matcher: exact-string-only\n * selection means a name you cannot see is a tool you cannot reach, so the dump\n * could not be trimmed. Retrieval (`core/capabilities`) breaks the deadlock, and\n * {@link formatDeferredCatalog} spends the budget accordingly — full list while\n * it is cheap, servers and counts once it is not.\n *\n * See docs/plugin-system-architecture.md §6.1–§6.3.\n */\n\nimport type { CapabilityDoc } from \"../../core/capabilities/registry.js\";\n\n/** A deferred tool as surfaced to the model: name + one-line description, no schema. */\nexport interface DeferredMcpToolEntry {\n\t/** Registered tool name (`mcp_<server>_<tool>`). */\n\ttoolName: string;\n\t/** Server that owns the tool. */\n\tserver: string;\n\t/** Short description (light — the JSON schema is what we defer). */\n\tdescription: string;\n}\n\n/**\n * Catalog size at which the full listing stops being the cheaper option.\n *\n * Below it, a flat catalog is both cheaper than a retrieval round-trip and\n * strictly more deterministic — the model can see every tool it has, with no\n * query it had to think to write (§6.4). Above it, the listing is the dominant\n * cost and the search path is worth the indirection. Thirty is the low end of\n * the §6.3 range: at ~15 tokens an entry that is roughly 450 tokens of budget,\n * and the entries past it are increasingly ones this session will never touch.\n */\nexport const CATALOG_EAGER_LIMIT = 30;\n\n/** One catalog line: name and a clipped first line of description. */\nfunction entryLine(e: DeferredMcpToolEntry): string {\n\tconst desc = e.description.split(\"\\n\")[0]?.slice(0, 120) ?? \"\";\n\treturn `  ${e.toolName}${desc ? ` — ${desc}` : \"\"}`;\n}\n\nfunction groupByServer(entries: DeferredMcpToolEntry[]): Map<string, DeferredMcpToolEntry[]> {\n\tconst byServer = new Map<string, DeferredMcpToolEntry[]>();\n\tfor (const e of entries) {\n\t\tconst list = byServer.get(e.server) ?? [];\n\t\tbyServer.set(e.server, list);\n\t\tlist.push(e);\n\t}\n\treturn byServer;\n}\n\n/**\n * Render the catalog for the resolver's description.\n *\n * Two shapes, chosen by size. The summary is not merely a truncated list: it\n * names every server and its tool count, so the model still knows the whole\n * surface exists and what it is about — it just has to ask for the part it\n * wants. A list cut off at thirty entries would instead hide the tail\n * completely, which is the failure mode worth avoiding.\n */\nexport function formatDeferredCatalog(entries: DeferredMcpToolEntry[], limit = CATALOG_EAGER_LIMIT): string {\n\tif (entries.length === 0) return \"(no MCP tools available)\";\n\tconst byServer = groupByServer(entries);\n\n\tif (entries.length <= limit) {\n\t\tconst lines: string[] = [];\n\t\tfor (const [server, list] of byServer) {\n\t\t\tlines.push(`${server}:`);\n\t\t\tfor (const e of list) lines.push(entryLine(e));\n\t\t}\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tconst lines = [`${entries.length} tools across ${byServer.size} server(s):`];\n\tfor (const [server, list] of byServer) {\n\t\tconst sample = list\n\t\t\t.slice(0, 4)\n\t\t\t.map((e) => e.toolName.replace(/^mcp_[^_]+_/, \"\"))\n\t\t\t.join(\", \");\n\t\tlines.push(`  ${server} — ${list.length} tool(s): ${sample}${list.length > 4 ? \", …\" : \"\"}`);\n\t}\n\tlines.push('Search for the rest by capability (e.g. `query: \"create a pull request\"`).');\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Select the catalog entries a resolve request refers to. Matches on the full\n * registered name (`mcp_server_tool`), and also on a bare tool name when it is\n * unambiguous, so the model can ask by either.\n *\n * Stays exact on purpose. Retrieval is a separate parameter, and collapsing the\n * two would make \"resolve exactly these tools\" a fuzzy operation — which is the\n * one thing a caller naming a tool it already knows about does not want.\n */\nexport function selectResolvable(entries: DeferredMcpToolEntry[], names: string[]): DeferredMcpToolEntry[] {\n\tconst wanted = new Set(names.map((n) => n.trim()).filter(Boolean));\n\tif (wanted.size === 0) return [];\n\tconst out: DeferredMcpToolEntry[] = [];\n\tconst seen = new Set<string>();\n\tfor (const entry of entries) {\n\t\tif (seen.has(entry.toolName)) continue;\n\t\tconst bareTool = entry.toolName.replace(/^mcp_[^_]+_/, \"\");\n\t\tif (wanted.has(entry.toolName) || wanted.has(bareTool)) {\n\t\t\tout.push(entry);\n\t\t\tseen.add(entry.toolName);\n\t\t}\n\t}\n\treturn out;\n}\n\n/** The catalog as capability documents, for the index. */\nexport function toCapabilityDocs(entries: DeferredMcpToolEntry[], deferred: boolean): CapabilityDoc[] {\n\treturn entries.map((e) => ({\n\t\tid: e.toolName,\n\t\tkind: \"mcp-tool\" as const,\n\t\tname: e.toolName,\n\t\tdescription: e.description,\n\t\tsource: e.server,\n\t\tdeferred,\n\t}));\n}\n"]}