@code-yeongyu/senpi 2026.7.17-2 → 2026.7.17-4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/core/extensions/builtin/mcp/expose/native-search.d.ts +1 -1
- package/dist/core/extensions/builtin/mcp/expose/native-search.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/native-search.js +1 -1
- package/dist/core/extensions/builtin/mcp/expose/native-search.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/policy.js +1 -1
- package/dist/core/extensions/builtin/mcp/expose/policy.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/status.js +1 -1
- package/dist/core/extensions/builtin/mcp/expose/status.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/tier-b.d.ts +3 -3
- package/dist/core/extensions/builtin/mcp/expose/tier-b.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/tier-b.js +10 -10
- package/dist/core/extensions/builtin/mcp/expose/tier-b.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/tool-search.d.ts +3 -3
- package/dist/core/extensions/builtin/mcp/expose/tool-search.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/tool-search.js +14 -14
- package/dist/core/extensions/builtin/mcp/expose/tool-search.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/index.js +5 -5
- package/dist/core/extensions/builtin/mcp/index.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/service.d.ts +2 -2
- package/dist/core/extensions/builtin/mcp/service.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/service.js +2 -2
- package/dist/core/extensions/builtin/mcp/service.js.map +1 -1
- package/dist/core/extensions/builtin/websearch/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/websearch/index.js +20 -3
- package/dist/core/extensions/builtin/websearch/index.js.map +1 -1
- package/docs/AGENTS.md +70 -0
- package/docs/mcp.md +5 -5
- package/examples/AGENTS.md +3 -0
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +24 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +3 -3
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +19 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +0 -21
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js +6 -6
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +0 -21
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +3 -21
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/tool-schema-compat.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/tool-schema-compat.js +45 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/tool-schema-compat.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +1 -1
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2026.7.17-4] - 2026-07-17
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed Kimi/Moonshot function parameters with root object unions by flattening them into a compatible object schema.
|
|
14
|
+
|
|
15
|
+
### Removed
|
|
16
|
+
|
|
17
|
+
## [2026.7.17-3] - 2026-07-17
|
|
18
|
+
|
|
19
|
+
### Breaking Changes
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Renamed the MCP discovery tool from `mcp_search` to `tool_search` so its public name matches its behavior ([#227](https://github.com/code-yeongyu/senpi/pull/227)).
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Fixed payload hooks reintroducing Moonshot-incompatible function schemas by normalizing the final tool list after `onPayload` and immediately before request submission.
|
|
30
|
+
|
|
31
|
+
### Removed
|
|
32
|
+
|
|
3
33
|
## [2026.7.17-2] - 2026-07-17
|
|
4
34
|
|
|
5
35
|
### Breaking Changes
|
|
@@ -3,7 +3,7 @@ export declare const ANTHROPIC_TOOL_SEARCH_NAME = "tool_search";
|
|
|
3
3
|
/** Anthropic caps a request at 10k tools; beyond that native search is invalid. */
|
|
4
4
|
export declare const ANTHROPIC_MAX_TOOLS = 10000;
|
|
5
5
|
export interface AnthropicNativeInjectionConfig {
|
|
6
|
-
/** Never defer this tool (our custom
|
|
6
|
+
/** Never defer this tool (our custom tool_search), and it is non-deferrable. */
|
|
7
7
|
readonly searchToolName?: string;
|
|
8
8
|
/** True for MCP catalog tools eligible for deferral. */
|
|
9
9
|
readonly isDeferrable: (toolName: string) => boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-search.d.ts","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/native-search.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,0BAA0B,mCAAmC,CAAC;AAC3E,eAAO,MAAM,0BAA0B,gBAAgB,CAAC;AACxD,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AAEzC,MAAM,WAAW,8BAA8B;IAC9C
|
|
1
|
+
{"version":3,"file":"native-search.d.ts","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/native-search.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,0BAA0B,mCAAmC,CAAC;AAC3E,eAAO,MAAM,0BAA0B,gBAAgB,CAAC;AACxD,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AAEzC,MAAM,WAAW,8BAA8B;IAC9C,gFAAgF;IAChF,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,wDAAwD;IACxD,QAAQ,CAAC,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CACrD;AAMD;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC3C,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,8BAA8B,GACpC,OAAO,CAUT;AAaD;6EAC6E;AAC7E,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAE7G;AAED,MAAM,WAAW,0BAA2B,SAAQ,8BAA8B;IACjF,+EAA+E;IAC/E,OAAO,IAAI,OAAO,CAAC;IACnB,gEAAgE;IAChE,UAAU,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;;;GAKG;AACH,qBAAa,gCAAgC;;IAM5C,YAAY,IAAI,EAAE,0BAA0B,EAE3C;IAED,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAMrE;IAED,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAMvC;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,cAAc,IAAI,MAAM,GAAG,IAAI,CAElC;CACD"}
|
|
@@ -85,7 +85,7 @@ export class AnthropicNativeToolSearchAdapter {
|
|
|
85
85
|
return;
|
|
86
86
|
this.#disabled = true;
|
|
87
87
|
this.#fallbackReason =
|
|
88
|
-
"Anthropic returned 400 for native tool-search; disabled it and fell back to local
|
|
88
|
+
"Anthropic returned 400 for native tool-search; disabled it and fell back to local tool_search for this session.";
|
|
89
89
|
this.#deps.onFallback?.(this.#fallbackReason);
|
|
90
90
|
}
|
|
91
91
|
get disabled() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-search.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/native-search.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"native-search.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/native-search.ts"],"names":[],"mappings":"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","sourcesContent":["// Native provider tool-search adapters (todo 33 — Anthropic half).\n//\n// Gated on the todo-29 spike verdict: Anthropic Messages is GO-pure-extension —\n// server_tool_use / tool_search_tool_result blocks round-trip pi-ai's\n// parse->persist->re-serialise cycle untouched (native-search-spike.md), so this\n// adapter injects request-side fields via the extension before_provider_request\n// event ONLY (the sole ExtensionAPI payload surface) with no pi-ai change.\n//\n// It injects Anthropic's native `tool_search_tool_bm25_20251119` tool and marks\n// deferrable MCP tools `defer_loading:true`, honouring the API HARD RULES, and\n// auto-falls-back to local Tier-B on a 400. The OpenAI half is deferred\n// (spike = GO-with-ai-seam; see native-search-spike.md).\n\nexport const ANTHROPIC_TOOL_SEARCH_TYPE = \"tool_search_tool_bm25_20251119\";\nexport const ANTHROPIC_TOOL_SEARCH_NAME = \"tool_search\";\n/** Anthropic caps a request at 10k tools; beyond that native search is invalid. */\nexport const ANTHROPIC_MAX_TOOLS = 10000;\n\nexport interface AnthropicNativeInjectionConfig {\n\t/** Never defer this tool (our custom tool_search), and it is non-deferrable. */\n\treadonly searchToolName?: string;\n\t/** True for MCP catalog tools eligible for deferral. */\n\treadonly isDeferrable: (toolName: string) => boolean;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\n/**\n * Pure payload transform. Adds exactly one native tool-search tool (idempotent)\n * and sets `defer_loading:true` on eligible MCP tools, enforcing the HARD RULES:\n * never defer the search tool, never combine defer_loading with cache_control\n * (400), keep >=1 non-deferred tool (the native search tool guarantees this),\n * and skip entirely above the 10k tool cap.\n */\nexport function addAnthropicNativeToolSearch(\n\tapi: string | undefined,\n\tpayload: unknown,\n\tconfig: AnthropicNativeInjectionConfig,\n): unknown {\n\tif (api !== \"anthropic-messages\" || !isRecord(payload)) return payload;\n\tconst tools = Array.isArray(payload.tools) ? payload.tools : [];\n\tif (tools.length > ANTHROPIC_MAX_TOOLS) return payload;\n\tconst deferred = tools.map((tool) => maybeDefer(tool, config));\n\tconst hasSearchTool = deferred.some((tool) => isRecord(tool) && tool.type === ANTHROPIC_TOOL_SEARCH_TYPE);\n\tconst nextTools = hasSearchTool\n\t\t? deferred\n\t\t: [...deferred, { type: ANTHROPIC_TOOL_SEARCH_TYPE, name: ANTHROPIC_TOOL_SEARCH_NAME }];\n\treturn { ...payload, tools: nextTools };\n}\n\nfunction maybeDefer(tool: unknown, config: AnthropicNativeInjectionConfig): unknown {\n\tif (!isRecord(tool)) return tool;\n\tconst name = typeof tool.name === \"string\" ? tool.name : undefined;\n\tif (name === undefined) return tool; // server/native tools are type-based, never deferred\n\tif (name === config.searchToolName) return tool; // never defer the search tool\n\tif (!config.isDeferrable(name)) return tool; // non-MCP tools untouched\n\tif (\"cache_control\" in tool) return tool; // never defer_loading + cache_control (400)\n\tif (tool.defer_loading === true) return tool; // idempotent\n\treturn { ...tool, defer_loading: true };\n}\n\n/** `tool_reference` blocks emitted inside a tool_result so the API expands the\n * referenced (deferred) tools — the cache-prefix-safe custom-search path. */\nexport function buildToolReferenceBlocks(names: readonly string[]): { type: \"tool_reference\"; name: string }[] {\n\treturn names.map((name) => ({ type: \"tool_reference\", name }));\n}\n\nexport interface AnthropicNativeAdapterDeps extends AnthropicNativeInjectionConfig {\n\t/** Resolved config gate (nativeToolSearch auto|true AND provider supports). */\n\tenabled(): boolean;\n\t/** Invoked once when a 400 forces the local-search fallback. */\n\tonFallback?(reason: string): void;\n}\n\n/**\n * Stateful adapter driving the before_provider_request injection and the\n * after_provider_response 400 detector. Once a 400 lands on a request we\n * injected, native search is disabled for the rest of the session and the\n * extension falls back to local Tier-B (which is always registered).\n */\nexport class AnthropicNativeToolSearchAdapter {\n\t#disabled = false;\n\t#injectedLastRequest = false;\n\t#fallbackReason: string | null = null;\n\treadonly #deps: AnthropicNativeAdapterDeps;\n\n\tconstructor(deps: AnthropicNativeAdapterDeps) {\n\t\tthis.#deps = deps;\n\t}\n\n\tapplyBeforeRequest(api: string | undefined, payload: unknown): unknown {\n\t\tthis.#injectedLastRequest = false;\n\t\tif (this.#disabled || !this.#deps.enabled()) return payload;\n\t\tconst next = addAnthropicNativeToolSearch(api, payload, this.#deps);\n\t\tthis.#injectedLastRequest = next !== payload;\n\t\treturn next;\n\t}\n\n\tnoteResponseStatus(status: number): void {\n\t\tif (status !== 400 || !this.#injectedLastRequest || this.#disabled) return;\n\t\tthis.#disabled = true;\n\t\tthis.#fallbackReason =\n\t\t\t\"Anthropic returned 400 for native tool-search; disabled it and fell back to local tool_search for this session.\";\n\t\tthis.#deps.onFallback?.(this.#fallbackReason);\n\t}\n\n\tget disabled(): boolean {\n\t\treturn this.#disabled;\n\t}\n\n\tget fallbackReason(): string | null {\n\t\treturn this.#fallbackReason;\n\t}\n}\n"]}
|
|
@@ -33,7 +33,7 @@ export function computeMcpExposurePolicy(entries, config, settings) {
|
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
// Tier-B search mode: register the full catalog but keep only directTools
|
|
36
|
-
// active;
|
|
36
|
+
// active; tool_search promotes the rest on demand.
|
|
37
37
|
if (config.exposure === "search") {
|
|
38
38
|
return searchResult(filteredEntries, directEntries, "explicit");
|
|
39
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/policy.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,WAAW,CAAC;AAelC,MAAM,iBAAiB,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AAEpD,MAAM,UAAU,wBAAwB,CACvC,OAAuC,EACvC,MAAuB,EACvB,QAAqB;IAErB,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACrG,MAAM,aAAa,GAAG,UAAU,CAAC,0BAA0B,CAAC,eAAe,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;IAClG,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClC,OAAO;YACN,aAAa,EAAE,EAAE;YACjB,eAAe;YACf,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,UAAU;YAClB,iBAAiB,EAAE,EAAE;YACrB,QAAQ,EAAE,CAAC,cAAc,UAAU,CAAC,OAAO,CAAC,+DAA+D,CAAC;SAC5G,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QACjC,OAAO,YAAY,CAAC,eAAe,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;IACtE,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,YAAY,CAAC,eAAe,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC;IACnE,CAAC;IACD,yEAAyE;IACzE,0EAA0E;IAC1E,+EAA+E;IAC/E,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,OAAO;YACN,aAAa,EAAE,EAAE;YACjB,eAAe;YACf,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,UAAU;YAClB,iBAAiB,EAAE,EAAE;YACrB,QAAQ,EAAE,EAAE;SACZ,CAAC;IACH,CAAC;IACD,0EAA0E;IAC1E,
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/policy.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,WAAW,CAAC;AAelC,MAAM,iBAAiB,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AAEpD,MAAM,UAAU,wBAAwB,CACvC,OAAuC,EACvC,MAAuB,EACvB,QAAqB;IAErB,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACrG,MAAM,aAAa,GAAG,UAAU,CAAC,0BAA0B,CAAC,eAAe,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;IAClG,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClC,OAAO;YACN,aAAa,EAAE,EAAE;YACjB,eAAe;YACf,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,UAAU;YAClB,iBAAiB,EAAE,EAAE;YACrB,QAAQ,EAAE,CAAC,cAAc,UAAU,CAAC,OAAO,CAAC,+DAA+D,CAAC;SAC5G,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QACjC,OAAO,YAAY,CAAC,eAAe,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;IACtE,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,YAAY,CAAC,eAAe,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC;IACnE,CAAC;IACD,yEAAyE;IACzE,0EAA0E;IAC1E,+EAA+E;IAC/E,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,OAAO;YACN,aAAa,EAAE,EAAE;YACjB,eAAe;YACf,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,UAAU;YAClB,iBAAiB,EAAE,EAAE;YACrB,QAAQ,EAAE,EAAE;SACZ,CAAC;IACH,CAAC;IACD,0EAA0E;IAC1E,mDAAmD;IACnD,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,YAAY,CAAC,eAAe,EAAE,aAAa,EAAE,UAAU,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,eAAe,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,eAAe,IAAI,EAAE,CAAC,EAAE,CAAC;QAChE,OAAO,YAAY,CAAC,eAAe,EAAE,eAAe,EAAE,WAAW,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,YAAY,CAAC,eAAe,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,YAAY,CACpB,eAA+C,EAC/C,aAA6C,EAC7C,MAAyC;IAEzC,OAAO;QACN,aAAa,EAAE,UAAU,CAAC,aAAa,CAAC;QACxC,eAAe,EAAE,UAAU,CAAC,eAAe,CAAC;QAC5C,IAAI,EAAE,QAAQ;QACd,MAAM;QACN,iBAAiB,EAAE,UAAU,CAAC,eAAe,CAAC;QAC9C,QAAQ,EAAE,EAAE;KACZ,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CACpB,eAA+C,EAC/C,aAA6C,EAC7C,MAAyC;IAEzC,OAAO;QACN,aAAa,EAAE,UAAU,CAAC,aAAa,CAAC;QACxC,eAAe,EAAE,UAAU,CAAC,eAAe,CAAC;QAC5C,IAAI,EAAE,QAAQ;QACd,MAAM;QACN,iBAAiB,EAAE,UAAU,CAAC,aAAa,CAAC;QAC5C,QAAQ,EAAE,EAAE;KACZ,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAgB,EAAE,MAAuB;IACnE,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtF,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5B,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,0BAA0B,CAClC,OAAuC,EACvC,WAA2C;IAE3C,IAAI,WAAW,KAAK,IAAI;QAAE,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC;IACrD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACvE,MAAM,QAAQ,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;IAC9C,OAAO,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/F,CAAC;AAED,SAAS,eAAe,CAAC,QAAuC;IAC/D,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,WAAW,CAAC,OAAe;IACnC,IAAI,CAAC;QACJ,OAAO,SAAS,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,OAAO,CAAC;IAC3C,CAAC;AACF,CAAC;AAED,SAAS,UAAU,CAAC,OAAuC;IAC1D,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CACvB,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAC/F,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,OAAuC;IAC1D,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,WAAW,CAAC;AAC1C,CAAC","sourcesContent":["import picomatch from \"picomatch\";\nimport type { McpToolCatalogEntry } from \"../catalog.ts\";\nimport type { McpServerConfig, McpSettings } from \"../config-schema.ts\";\n\nexport interface McpExposurePolicyResult {\n\treadonly activeEntries: McpToolCatalogEntry[];\n\treadonly filteredEntries: McpToolCatalogEntry[];\n\treadonly mode: \"direct\" | \"search\" | \"proxy\";\n\treadonly reason: \"explicit\" | \"threshold\" | \"directTools\";\n\treadonly registeredEntries: McpToolCatalogEntry[];\n\treadonly warnings: string[];\n}\n\ntype ToolMatcher = (toolName: string) => boolean;\n\nconst PICOMATCH_OPTIONS = { bash: true, dot: true };\n\nexport function computeMcpExposurePolicy(\n\tentries: readonly McpToolCatalogEntry[],\n\tconfig: McpServerConfig,\n\tsettings: McpSettings,\n): McpExposurePolicyResult {\n\tconst filteredEntries = stableSort(entries.filter((entry) => passesToolFilters(entry.tool, config)));\n\tconst directEntries = stableSort(entriesMatchingDirectTools(filteredEntries, config.directTools));\n\tif (filteredEntries.length === 0) {\n\t\treturn {\n\t\t\tactiveEntries: [],\n\t\t\tfilteredEntries,\n\t\t\tmode: \"direct\",\n\t\t\treason: \"explicit\",\n\t\t\tregisteredEntries: [],\n\t\t\twarnings: [`MCP server ${serverName(entries)} has 0 exposed tools after includeTools/excludeTools filters.`],\n\t\t};\n\t}\n\n\tif (config.directTools === true) {\n\t\treturn directResult(filteredEntries, filteredEntries, \"directTools\");\n\t}\n\tif (config.exposure === \"direct\") {\n\t\treturn directResult(filteredEntries, filteredEntries, \"explicit\");\n\t}\n\t// Tier-C proxy mode (todo 38): per-server OPT-IN only. The whole catalog\n\t// stays behind a single gateway tool; nothing registers directly and auto\n\t// never selects this mode (SPEC §5: -27.3pp GSM8K structured-output evidence).\n\tif (config.exposure === \"proxy\") {\n\t\treturn {\n\t\t\tactiveEntries: [],\n\t\t\tfilteredEntries,\n\t\t\tmode: \"proxy\",\n\t\t\treason: \"explicit\",\n\t\t\tregisteredEntries: [],\n\t\t\twarnings: [],\n\t\t};\n\t}\n\t// Tier-B search mode: register the full catalog but keep only directTools\n\t// active; tool_search promotes the rest on demand.\n\tif (config.exposure === \"search\") {\n\t\treturn searchResult(filteredEntries, directEntries, \"explicit\");\n\t}\n\tif (filteredEntries.length <= (settings.searchThreshold ?? 10)) {\n\t\treturn directResult(filteredEntries, filteredEntries, \"threshold\");\n\t}\n\treturn searchResult(filteredEntries, directEntries, \"threshold\");\n}\n\nfunction searchResult(\n\tfilteredEntries: readonly McpToolCatalogEntry[],\n\tdirectEntries: readonly McpToolCatalogEntry[],\n\treason: McpExposurePolicyResult[\"reason\"],\n): McpExposurePolicyResult {\n\treturn {\n\t\tactiveEntries: stableSort(directEntries),\n\t\tfilteredEntries: stableSort(filteredEntries),\n\t\tmode: \"search\",\n\t\treason,\n\t\tregisteredEntries: stableSort(filteredEntries),\n\t\twarnings: [],\n\t};\n}\n\nfunction directResult(\n\tfilteredEntries: readonly McpToolCatalogEntry[],\n\tactiveEntries: readonly McpToolCatalogEntry[],\n\treason: McpExposurePolicyResult[\"reason\"],\n): McpExposurePolicyResult {\n\treturn {\n\t\tactiveEntries: stableSort(activeEntries),\n\t\tfilteredEntries: stableSort(filteredEntries),\n\t\tmode: \"direct\",\n\t\treason,\n\t\tregisteredEntries: stableSort(activeEntries),\n\t\twarnings: [],\n\t};\n}\n\nfunction passesToolFilters(toolName: string, config: McpServerConfig): boolean {\n\tconst include = compileMatchers(config.includeTools);\n\tconst exclude = compileMatchers(config.excludeTools);\n\tconst included = include.length === 0 || include.some((isMatch) => isMatch(toolName));\n\tif (!included) return false;\n\treturn !exclude.some((isMatch) => isMatch(toolName));\n}\n\nfunction entriesMatchingDirectTools(\n\tentries: readonly McpToolCatalogEntry[],\n\tdirectTools: McpServerConfig[\"directTools\"],\n): McpToolCatalogEntry[] {\n\tif (directTools === true) return stableSort(entries);\n\tif (!Array.isArray(directTools) || directTools.length === 0) return [];\n\tconst matchers = compileMatchers(directTools);\n\treturn stableSort(entries.filter((entry) => matchers.some((isMatch) => isMatch(entry.tool))));\n}\n\nfunction compileMatchers(patterns: readonly string[] | undefined): ToolMatcher[] {\n\treturn (patterns ?? []).map((pattern) => safeMatcher(pattern));\n}\n\nfunction safeMatcher(pattern: string): ToolMatcher {\n\ttry {\n\t\treturn picomatch(pattern, PICOMATCH_OPTIONS);\n\t} catch {\n\t\treturn (toolName) => toolName === pattern;\n\t}\n}\n\nfunction stableSort(entries: readonly McpToolCatalogEntry[]): McpToolCatalogEntry[] {\n\treturn [...entries].sort(\n\t\t(left, right) => left.server.localeCompare(right.server) || left.tool.localeCompare(right.tool),\n\t);\n}\n\nfunction serverName(entries: readonly McpToolCatalogEntry[]): string {\n\treturn entries[0]?.server ?? \"<unknown>\";\n}\n"]}
|
|
@@ -6,7 +6,7 @@ export function getMcpCatalogExposureStatus(catalog, serverConfig, settings) {
|
|
|
6
6
|
}
|
|
7
7
|
// Report the total exposed tools; in search mode note how many are active now.
|
|
8
8
|
const hint = policy.mode === "search"
|
|
9
|
-
? `search mode: ${policy.activeEntries.length} active now, ${policy.registeredEntries.length} searchable via
|
|
9
|
+
? `search mode: ${policy.activeEntries.length} active now, ${policy.registeredEntries.length} searchable via tool_search`
|
|
10
10
|
: undefined;
|
|
11
11
|
return { hint, toolCount: policy.registeredEntries.length, mode: policy.mode };
|
|
12
12
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/status.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAQvD,MAAM,UAAU,2BAA2B,CAC1C,OAAuC,EACvC,YAA6B,EAC7B,QAAqB;IAErB,MAAM,MAAM,GAAG,wBAAwB,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;IACzE,IAAI,MAAM,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzC,OAAO,EAAE,IAAI,EAAE,yDAAyD,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IAC7G,CAAC;IACD,+EAA+E;IAC/E,MAAM,IAAI,GACT,MAAM,CAAC,IAAI,KAAK,QAAQ;QACvB,CAAC,CAAC,gBAAgB,MAAM,CAAC,aAAa,CAAC,MAAM,gBAAgB,MAAM,CAAC,iBAAiB,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/status.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAQvD,MAAM,UAAU,2BAA2B,CAC1C,OAAuC,EACvC,YAA6B,EAC7B,QAAqB;IAErB,MAAM,MAAM,GAAG,wBAAwB,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;IACzE,IAAI,MAAM,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzC,OAAO,EAAE,IAAI,EAAE,yDAAyD,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IAC7G,CAAC;IACD,+EAA+E;IAC/E,MAAM,IAAI,GACT,MAAM,CAAC,IAAI,KAAK,QAAQ;QACvB,CAAC,CAAC,gBAAgB,MAAM,CAAC,aAAa,CAAC,MAAM,gBAAgB,MAAM,CAAC,iBAAiB,CAAC,MAAM,6BAA6B;QACzH,CAAC,CAAC,SAAS,CAAC;IACd,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;AAChF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC/C,IAAY,EACZ,UAA4B,EAC5B,YAA6B,EAC7B,QAAqB;IAErB,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QACvE,MAAM,OAAO,GAA0B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClE,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,UAAU;YACV,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,gBAAgB,EAAE,YAAY,CAAC,gBAAgB;YAC/C,MAAM,EAAE,IAAI,CAAC,WAAW;YACxB,MAAM,EAAE,IAAI;YACZ,IAAI,EAAE,IAAI,CAAC,IAAI;SACf,CAAC,CAAC,CAAC;QACJ,OAAO,2BAA2B,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC5B,CAAC;AACF,CAAC","sourcesContent":["import type { McpToolCatalogEntry } from \"../catalog.ts\";\nimport type { McpServerConfig, McpSettings } from \"../config-schema.ts\";\nimport type { ServerConnection } from \"../connection.ts\";\nimport { computeMcpExposurePolicy } from \"./policy.ts\";\n\nexport interface McpServerExposureStatus {\n\treadonly hint?: string;\n\treadonly toolCount: number | null;\n\treadonly mode?: \"direct\" | \"search\" | \"proxy\";\n}\n\nexport function getMcpCatalogExposureStatus(\n\tcatalog: readonly McpToolCatalogEntry[],\n\tserverConfig: McpServerConfig,\n\tsettings: McpSettings,\n): McpServerExposureStatus {\n\tconst policy = computeMcpExposurePolicy(catalog, serverConfig, settings);\n\tif (policy.filteredEntries.length === 0) {\n\t\treturn { hint: \"No MCP tools matched includeTools/excludeTools filters.\", toolCount: 0, mode: policy.mode };\n\t}\n\t// Report the total exposed tools; in search mode note how many are active now.\n\tconst hint =\n\t\tpolicy.mode === \"search\"\n\t\t\t? `search mode: ${policy.activeEntries.length} active now, ${policy.registeredEntries.length} searchable via tool_search`\n\t\t\t: undefined;\n\treturn { hint, toolCount: policy.registeredEntries.length, mode: policy.mode };\n}\n\nexport async function getMcpServerExposureStatus(\n\tname: string,\n\tconnection: ServerConnection,\n\tserverConfig: McpServerConfig,\n\tsettings: McpSettings,\n): Promise<McpServerExposureStatus> {\n\ttry {\n\t\tconst result = await connection.client.listTools({}, { timeout: 500 });\n\t\tconst catalog: McpToolCatalogEntry[] = result.tools.map((tool) => ({\n\t\t\tannotations: tool.annotations,\n\t\t\tconnection,\n\t\t\tdescription: tool.description,\n\t\t\trequestTimeoutMs: serverConfig.requestTimeoutMs,\n\t\t\tschema: tool.inputSchema,\n\t\t\tserver: name,\n\t\t\ttool: tool.name,\n\t\t}));\n\t\treturn getMcpCatalogExposureStatus(catalog, serverConfig, settings);\n\t} catch {\n\t\treturn { toolCount: null };\n\t}\n}\n"]}
|
|
@@ -26,19 +26,19 @@ export interface McpTierBRegistration {
|
|
|
26
26
|
readonly searchable: SearchableMcpTool[];
|
|
27
27
|
/**
|
|
28
28
|
* Replay activation markers found in session history through the SAME
|
|
29
|
-
* activation path
|
|
29
|
+
* activation path tool_search uses (stub swap + stable ordering), so a
|
|
30
30
|
* resumed/compacted session restores its promoted tools without
|
|
31
31
|
* re-searching. Returns the newly activated names (empty when nothing new).
|
|
32
32
|
*/
|
|
33
33
|
rehydrateFromHistory(messages: readonly unknown[]): string[];
|
|
34
34
|
/** Activate registered tools by name through the same stable path
|
|
35
|
-
*
|
|
35
|
+
* tool_search uses (skill lazy-reveal, todo 37). Unknown names ignored. */
|
|
36
36
|
activate(names: readonly string[]): void;
|
|
37
37
|
}
|
|
38
38
|
/** Register MCP tools honouring Tier-B search mode + prompt-cache mitigations. */
|
|
39
39
|
export declare function registerMcpTierBTools(pi: McpToolRegistrar, input: McpTierBRegistrationInput, warn?: WarnFn): McpTierBRegistration;
|
|
40
40
|
/** A 30-70 token placeholder for an inactive search-mode tool. Keeps the tools
|
|
41
|
-
* array length-stable under stubSwap; guides the model to
|
|
41
|
+
* array length-stable under stubSwap; guides the model to tool_search. */
|
|
42
42
|
export declare function buildMcpStubDefinition(name: string): McpToolDefinition;
|
|
43
43
|
export {};
|
|
44
44
|
//# sourceMappingURL=tier-b.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tier-b.d.ts","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/tier-b.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,EAEN,KAAK,iBAAiB,EAGtB,MAAM,eAAe,CAAC;AACvB,OAAO,
|
|
1
|
+
{"version":3,"file":"tier-b.d.ts","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/tier-b.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,EAEN,KAAK,iBAAiB,EAGtB,MAAM,eAAe,CAAC;AACvB,OAAO,EAGN,KAAK,iBAAiB,EAGtB,MAAM,kBAAkB,CAAC;AAE1B,KAAK,gBAAgB,GAAG,IAAI,CAAC,YAAY,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,cAAc,CAAC,CAAC;AACjG,KAAK,MAAM,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAExC,MAAM,WAAW,yBAAyB;IACzC,wEAAwE;IACxE,QAAQ,CAAC,iBAAiB,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC3D,0EAA0E;IAC1E,QAAQ,CAAC,aAAa,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACvD,6DAA6D;IAC7D,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,2EAA2E;IAC3E,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAA;KAAE,EAAE,CAAC;IAChG,mFAAmF;IACnF,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrD,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACpC,sEAAsE;IACtE,QAAQ,CAAC,UAAU,EAAE,iBAAiB,EAAE,CAAC;IACzC;;;;;OAKG;IACH,oBAAoB,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,EAAE,CAAC;IAC7D;+EAC2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;CACzC;AAID,kFAAkF;AAClF,wBAAgB,qBAAqB,CACpC,EAAE,EAAE,gBAAgB,EACpB,KAAK,EAAE,yBAAyB,EAChC,IAAI,CAAC,EAAE,MAAM,GACX,oBAAoB,CAwFtB;AA+BD;0EAC0E;AAC1E,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAiBtE"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Completes exposure:"auto": a server whose filtered tool count exceeds
|
|
4
4
|
// searchThreshold enters SEARCH mode — the full catalog is registered but only
|
|
5
|
-
// directTools stay active, and an always-active
|
|
5
|
+
// directTools stay active, and an always-active tool_search promotes the rest on
|
|
6
6
|
// demand. Prompt-cache mitigations (SPEC §5): stable name sort everywhere;
|
|
7
7
|
// activation turns accept a cache miss (documented); opt-in settings.stubSwap
|
|
8
8
|
// registers 30-70-token stubs so the tools array stays length-stable and only
|
|
@@ -12,7 +12,7 @@ import { Type } from "typebox";
|
|
|
12
12
|
import { registerToolsPreservingActiveSet } from "../active-set.js";
|
|
13
13
|
import { createMcpProxyTool } from "./proxy.js";
|
|
14
14
|
import { buildMcpToolDefinitions, mapMcpCatalogNames, } from "./register.js";
|
|
15
|
-
import { createMcpSearchTool,
|
|
15
|
+
import { createMcpSearchTool, rehydrateActiveToolsFromHistory, TOOL_SEARCH_TOOL_NAME, unionStable, } from "./tool-search.js";
|
|
16
16
|
const NOOP_REHYDRATE = () => [];
|
|
17
17
|
/** Register MCP tools honouring Tier-B search mode + prompt-cache mitigations. */
|
|
18
18
|
export function registerMcpTierBTools(pi, input, warn) {
|
|
@@ -76,14 +76,14 @@ export function registerMcpTierBTools(pi, input, warn) {
|
|
|
76
76
|
activateMcpTools(unionStable(current, fresh));
|
|
77
77
|
return fresh;
|
|
78
78
|
};
|
|
79
|
-
//
|
|
79
|
+
// tool_search carries a distinct param schema, so register it on its own
|
|
80
80
|
// rather than mixing it into the broadly-typed catalog def array.
|
|
81
81
|
pi.registerTool(searchTool);
|
|
82
82
|
if (!stubSwap) {
|
|
83
|
-
// Default search mode: full defs registered, only directTools +
|
|
83
|
+
// Default search mode: full defs registered, only directTools + tool_search
|
|
84
84
|
// active. Newly promoted tools enter the array on their activation turn
|
|
85
85
|
// (an accepted cache miss).
|
|
86
|
-
const active = orderActiveSet([...currentBase,
|
|
86
|
+
const active = orderActiveSet([...currentBase, TOOL_SEARCH_TOOL_NAME, ...activeMcpNames], reference);
|
|
87
87
|
registerToolsPreservingActiveSet(pi, fullDefs, active);
|
|
88
88
|
return { activate, searchable, rehydrateFromHistory };
|
|
89
89
|
}
|
|
@@ -96,7 +96,7 @@ export function registerMcpTierBTools(pi, input, warn) {
|
|
|
96
96
|
stubbed.add(def.name);
|
|
97
97
|
return buildMcpStubDefinition(def.name);
|
|
98
98
|
});
|
|
99
|
-
const active = orderActiveSet([...currentBase,
|
|
99
|
+
const active = orderActiveSet([...currentBase, TOOL_SEARCH_TOOL_NAME, ...fullDefs.map((def) => def.name)], reference);
|
|
100
100
|
registerToolsPreservingActiveSet(pi, toRegister, active);
|
|
101
101
|
return { activate, searchable, rehydrateFromHistory };
|
|
102
102
|
}
|
|
@@ -113,7 +113,7 @@ function swapStubsToFull(pi, names, stubbed, fullByName) {
|
|
|
113
113
|
}
|
|
114
114
|
/** Order the active set deterministically WITHOUT disturbing non-MCP (base)
|
|
115
115
|
* tools: base tools keep their existing relative order (by `reference` index,
|
|
116
|
-
* new ones appended), MCP tools (incl.
|
|
116
|
+
* new ones appended), MCP tools (incl. tool_search) are sorted for cache
|
|
117
117
|
* stability. Sorting base tools would churn the system-prompt tool listing. */
|
|
118
118
|
function orderActiveSet(names, reference) {
|
|
119
119
|
const unique = [...new Set(names)];
|
|
@@ -125,17 +125,17 @@ function orderActiveSet(names, reference) {
|
|
|
125
125
|
return [...base, ...mcp];
|
|
126
126
|
}
|
|
127
127
|
/** A 30-70 token placeholder for an inactive search-mode tool. Keeps the tools
|
|
128
|
-
* array length-stable under stubSwap; guides the model to
|
|
128
|
+
* array length-stable under stubSwap; guides the model to tool_search. */
|
|
129
129
|
export function buildMcpStubDefinition(name) {
|
|
130
130
|
return {
|
|
131
131
|
name,
|
|
132
132
|
label: name,
|
|
133
|
-
description: `Inactive MCP tool. Run
|
|
133
|
+
description: `Inactive MCP tool. Run tool_search to activate ${name}, then call it on your next turn.`,
|
|
134
134
|
parameters: Type.Object({}),
|
|
135
135
|
executionMode: "parallel",
|
|
136
136
|
async execute() {
|
|
137
137
|
return {
|
|
138
|
-
content: [{ type: "text", text: `${name} is not active. Use
|
|
138
|
+
content: [{ type: "text", text: `${name} is not active. Use tool_search to activate it, then call it.` }],
|
|
139
139
|
details: undefined,
|
|
140
140
|
};
|
|
141
141
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tier-b.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/tier-b.ts"],"names":[],"mappings":"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","sourcesContent":["// Tier-B adaptive exposure wiring (todo 32).\n//\n// Completes exposure:\"auto\": a server whose filtered tool count exceeds\n// searchThreshold enters SEARCH mode — the full catalog is registered but only\n// directTools stay active, and an always-active mcp_search promotes the rest on\n// demand. Prompt-cache mitigations (SPEC §5): stable name sort everywhere;\n// activation turns accept a cache miss (documented); opt-in settings.stubSwap\n// registers 30-70-token stubs so the tools array stays length-stable and only\n// the activated entry's bytes change (stub -> full) on promotion.\n\nimport type { AgentToolResult } from \"@earendil-works/pi-agent-core\";\nimport { Text } from \"@earendil-works/pi-tui\";\nimport { Type } from \"typebox\";\nimport type { ExtensionAPI } from \"../../../types.ts\";\nimport { registerToolsPreservingActiveSet } from \"../active-set.ts\";\nimport type { McpToolCatalogEntry } from \"../catalog.ts\";\nimport type { McpSettings } from \"../config-schema.ts\";\nimport { createMcpProxyTool } from \"./proxy.ts\";\nimport {\n\tbuildMcpToolDefinitions,\n\ttype McpToolDefinition,\n\ttype McpToolDetails,\n\tmapMcpCatalogNames,\n} from \"./register.ts\";\nimport {\n\tcreateMcpSearchTool,\n\tMCP_SEARCH_TOOL_NAME,\n\trehydrateActiveToolsFromHistory,\n\ttype SearchableMcpTool,\n\tunionStable,\n} from \"./tool-search.ts\";\n\ntype McpToolRegistrar = Pick<ExtensionAPI, \"getActiveTools\" | \"setActiveTools\" | \"registerTool\">;\ntype WarnFn = (message: string) => void;\n\nexport interface McpTierBRegistrationInput {\n\t/** Full catalog to register (all filtered tools across all servers). */\n\treadonly registeredEntries: readonly McpToolCatalogEntry[];\n\t/** Subset kept active immediately (direct-mode servers + directTools). */\n\treadonly activeEntries: readonly McpToolCatalogEntry[];\n\t/** True when at least one server resolved to search mode. */\n\treadonly searchMode: boolean;\n\t/** Tier-C proxy servers (todo 38): one always-active gateway tool each. */\n\treadonly proxyGateways?: readonly { server: string; entries: readonly McpToolCatalogEntry[] }[];\n\t/** Always-active utility tools (todo 39: mcp_list_resources/mcp_read_resource). */\n\treadonly utilityTools?: readonly McpToolDefinition[];\n\treadonly settings: McpSettings;\n}\n\nexport interface McpTierBRegistration {\n\t/** Searchable catalog (for reuse by /mcp status and list_changed). */\n\treadonly searchable: SearchableMcpTool[];\n\t/**\n\t * Replay activation markers found in session history through the SAME\n\t * activation path mcp_search uses (stub swap + stable ordering), so a\n\t * resumed/compacted session restores its promoted tools without\n\t * re-searching. Returns the newly activated names (empty when nothing new).\n\t */\n\trehydrateFromHistory(messages: readonly unknown[]): string[];\n\t/** Activate registered tools by name through the same stable path\n\t * mcp_search uses (skill lazy-reveal, todo 37). Unknown names ignored. */\n\tactivate(names: readonly string[]): void;\n}\n\nconst NOOP_REHYDRATE = (): string[] => [];\n\n/** Register MCP tools honouring Tier-B search mode + prompt-cache mitigations. */\nexport function registerMcpTierBTools(\n\tpi: McpToolRegistrar,\n\tinput: McpTierBRegistrationInput,\n\twarn?: WarnFn,\n): McpTierBRegistration {\n\tconst named = mapMcpCatalogNames(input.registeredEntries, warn);\n\tconst searchable: SearchableMcpTool[] = named.map(({ entry, name }) => ({\n\t\tname,\n\t\ttoolName: entry.tool,\n\t\tdescription: entry.description,\n\t\tserver: entry.server,\n\t}));\n\tconst fullDefs = buildMcpToolDefinitions(input.registeredEntries, warn);\n\tconst fullByName = new Map(fullDefs.map((def) => [def.name, def] as const));\n\tconst gatewayNames: string[] = [];\n\tfor (const gateway of input.proxyGateways ?? []) {\n\t\tconst tool = createMcpProxyTool(gateway.server, gateway.entries);\n\t\tpi.registerTool(tool);\n\t\tgatewayNames.push(tool.name);\n\t}\n\tfor (const tool of input.utilityTools ?? []) {\n\t\tpi.registerTool(tool);\n\t\tgatewayNames.push(tool.name);\n\t}\n\tconst activeMcpNames = [...mapMcpCatalogNames(input.activeEntries).map(({ name }) => name), ...gatewayNames];\n\tconst reference = pi.getActiveTools();\n\t// Base tools carry over; any stale mcp_* names from a prior generation are\n\t// dropped (membership = base + the intended mcp set; reference orders only).\n\tconst currentBase = reference.filter((name) => !name.startsWith(\"mcp_\"));\n\n\tif (!input.searchMode) {\n\t\tregisterToolsPreservingActiveSet(pi, fullDefs, orderActiveSet([...currentBase, ...activeMcpNames], reference));\n\t\t// Direct mode: everything registerable is already active, nothing to replay.\n\t\tconst activateDirect = (names: readonly string[]): void => {\n\t\t\tpi.setActiveTools(orderActiveSet(unionStable(pi.getActiveTools(), names), pi.getActiveTools()));\n\t\t};\n\t\treturn { activate: activateDirect, searchable, rehydrateFromHistory: NOOP_REHYDRATE };\n\t}\n\n\tconst stubSwap = input.settings.stubSwap === true;\n\tconst stubbed = new Set<string>();\n\tconst activateMcpTools = (names: readonly string[]): void => {\n\t\tif (stubSwap) swapStubsToFull(pi, names, stubbed, fullByName);\n\t\tpi.setActiveTools(orderActiveSet(names, pi.getActiveTools()));\n\t};\n\tconst searchTool = createMcpSearchTool({\n\t\tgetSearchableTools: () => searchable,\n\t\tgetActiveTools: () => pi.getActiveTools(),\n\t\tsetActiveTools: activateMcpTools,\n\t});\n\tconst registeredNames = new Set(fullDefs.map((def) => def.name));\n\tconst activate = (names: readonly string[]): void => {\n\t\tconst known = names.filter((name) => registeredNames.has(name));\n\t\tif (known.length === 0) return;\n\t\tactivateMcpTools(unionStable(pi.getActiveTools(), known));\n\t};\n\tconst rehydrateFromHistory = (messages: readonly unknown[]): string[] => {\n\t\tconst restored = rehydrateActiveToolsFromHistory(messages, registeredNames);\n\t\tconst current = pi.getActiveTools();\n\t\tconst fresh = restored.filter((name) => !current.includes(name));\n\t\tif (fresh.length === 0) return [];\n\t\tactivateMcpTools(unionStable(current, fresh));\n\t\treturn fresh;\n\t};\n\n\t// mcp_search carries a distinct param schema, so register it on its own\n\t// rather than mixing it into the broadly-typed catalog def array.\n\tpi.registerTool(searchTool);\n\n\tif (!stubSwap) {\n\t\t// Default search mode: full defs registered, only directTools + mcp_search\n\t\t// active. Newly promoted tools enter the array on their activation turn\n\t\t// (an accepted cache miss).\n\t\tconst active = orderActiveSet([...currentBase, MCP_SEARCH_TOOL_NAME, ...activeMcpNames], reference);\n\t\tregisterToolsPreservingActiveSet(pi, fullDefs, active);\n\t\treturn { activate, searchable, rehydrateFromHistory };\n\t}\n\n\t// stubSwap: every search-mode tool is registered as a tiny stub and kept\n\t// active so the tools array is length-stable; direct tools stay full.\n\tconst directActive = new Set(activeMcpNames);\n\tconst toRegister: McpToolDefinition[] = fullDefs.map((def) => {\n\t\tif (directActive.has(def.name)) return def;\n\t\tstubbed.add(def.name);\n\t\treturn buildMcpStubDefinition(def.name);\n\t});\n\tconst active = orderActiveSet([...currentBase, MCP_SEARCH_TOOL_NAME, ...fullDefs.map((def) => def.name)], reference);\n\tregisterToolsPreservingActiveSet(pi, toRegister, active);\n\treturn { activate, searchable, rehydrateFromHistory };\n}\n\nfunction swapStubsToFull(\n\tpi: McpToolRegistrar,\n\tnames: readonly string[],\n\tstubbed: Set<string>,\n\tfullByName: ReadonlyMap<string, McpToolDefinition>,\n): void {\n\tfor (const name of names) {\n\t\tif (!stubbed.has(name)) continue;\n\t\tconst full = fullByName.get(name);\n\t\tif (full === undefined) continue;\n\t\tpi.registerTool(full);\n\t\tstubbed.delete(name);\n\t}\n}\n\n/** Order the active set deterministically WITHOUT disturbing non-MCP (base)\n * tools: base tools keep their existing relative order (by `reference` index,\n * new ones appended), MCP tools (incl. mcp_search) are sorted for cache\n * stability. Sorting base tools would churn the system-prompt tool listing. */\nfunction orderActiveSet(names: readonly string[], reference: readonly string[]): string[] {\n\tconst unique = [...new Set(names)];\n\tconst rank = new Map(reference.map((name, index) => [name, index] as const));\n\tconst base = unique\n\t\t.filter((name) => !name.startsWith(\"mcp_\"))\n\t\t.sort((a, b) => (rank.get(a) ?? Number.MAX_SAFE_INTEGER) - (rank.get(b) ?? Number.MAX_SAFE_INTEGER));\n\tconst mcp = unique.filter((name) => name.startsWith(\"mcp_\")).sort();\n\treturn [...base, ...mcp];\n}\n\n/** A 30-70 token placeholder for an inactive search-mode tool. Keeps the tools\n * array length-stable under stubSwap; guides the model to mcp_search. */\nexport function buildMcpStubDefinition(name: string): McpToolDefinition {\n\treturn {\n\t\tname,\n\t\tlabel: name,\n\t\tdescription: `Inactive MCP tool. Run mcp_search to activate ${name}, then call it on your next turn.`,\n\t\tparameters: Type.Object({}),\n\t\texecutionMode: \"parallel\",\n\t\tasync execute(): Promise<AgentToolResult<McpToolDetails | undefined>> {\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: `${name} is not active. Use mcp_search to activate it, then call it.` }],\n\t\t\t\tdetails: undefined,\n\t\t\t};\n\t\t},\n\t\trenderCall(_args, theme) {\n\t\t\treturn new Text(theme.fg(\"toolOutput\", `${name} (inactive stub)`), 0, 0);\n\t\t},\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tier-b.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/tier-b.ts"],"names":[],"mappings":"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","sourcesContent":["// Tier-B adaptive exposure wiring (todo 32).\n//\n// Completes exposure:\"auto\": a server whose filtered tool count exceeds\n// searchThreshold enters SEARCH mode — the full catalog is registered but only\n// directTools stay active, and an always-active tool_search promotes the rest on\n// demand. Prompt-cache mitigations (SPEC §5): stable name sort everywhere;\n// activation turns accept a cache miss (documented); opt-in settings.stubSwap\n// registers 30-70-token stubs so the tools array stays length-stable and only\n// the activated entry's bytes change (stub -> full) on promotion.\n\nimport type { AgentToolResult } from \"@earendil-works/pi-agent-core\";\nimport { Text } from \"@earendil-works/pi-tui\";\nimport { Type } from \"typebox\";\nimport type { ExtensionAPI } from \"../../../types.ts\";\nimport { registerToolsPreservingActiveSet } from \"../active-set.ts\";\nimport type { McpToolCatalogEntry } from \"../catalog.ts\";\nimport type { McpSettings } from \"../config-schema.ts\";\nimport { createMcpProxyTool } from \"./proxy.ts\";\nimport {\n\tbuildMcpToolDefinitions,\n\ttype McpToolDefinition,\n\ttype McpToolDetails,\n\tmapMcpCatalogNames,\n} from \"./register.ts\";\nimport {\n\tcreateMcpSearchTool,\n\trehydrateActiveToolsFromHistory,\n\ttype SearchableMcpTool,\n\tTOOL_SEARCH_TOOL_NAME,\n\tunionStable,\n} from \"./tool-search.ts\";\n\ntype McpToolRegistrar = Pick<ExtensionAPI, \"getActiveTools\" | \"setActiveTools\" | \"registerTool\">;\ntype WarnFn = (message: string) => void;\n\nexport interface McpTierBRegistrationInput {\n\t/** Full catalog to register (all filtered tools across all servers). */\n\treadonly registeredEntries: readonly McpToolCatalogEntry[];\n\t/** Subset kept active immediately (direct-mode servers + directTools). */\n\treadonly activeEntries: readonly McpToolCatalogEntry[];\n\t/** True when at least one server resolved to search mode. */\n\treadonly searchMode: boolean;\n\t/** Tier-C proxy servers (todo 38): one always-active gateway tool each. */\n\treadonly proxyGateways?: readonly { server: string; entries: readonly McpToolCatalogEntry[] }[];\n\t/** Always-active utility tools (todo 39: mcp_list_resources/mcp_read_resource). */\n\treadonly utilityTools?: readonly McpToolDefinition[];\n\treadonly settings: McpSettings;\n}\n\nexport interface McpTierBRegistration {\n\t/** Searchable catalog (for reuse by /mcp status and list_changed). */\n\treadonly searchable: SearchableMcpTool[];\n\t/**\n\t * Replay activation markers found in session history through the SAME\n\t * activation path tool_search uses (stub swap + stable ordering), so a\n\t * resumed/compacted session restores its promoted tools without\n\t * re-searching. Returns the newly activated names (empty when nothing new).\n\t */\n\trehydrateFromHistory(messages: readonly unknown[]): string[];\n\t/** Activate registered tools by name through the same stable path\n\t * tool_search uses (skill lazy-reveal, todo 37). Unknown names ignored. */\n\tactivate(names: readonly string[]): void;\n}\n\nconst NOOP_REHYDRATE = (): string[] => [];\n\n/** Register MCP tools honouring Tier-B search mode + prompt-cache mitigations. */\nexport function registerMcpTierBTools(\n\tpi: McpToolRegistrar,\n\tinput: McpTierBRegistrationInput,\n\twarn?: WarnFn,\n): McpTierBRegistration {\n\tconst named = mapMcpCatalogNames(input.registeredEntries, warn);\n\tconst searchable: SearchableMcpTool[] = named.map(({ entry, name }) => ({\n\t\tname,\n\t\ttoolName: entry.tool,\n\t\tdescription: entry.description,\n\t\tserver: entry.server,\n\t}));\n\tconst fullDefs = buildMcpToolDefinitions(input.registeredEntries, warn);\n\tconst fullByName = new Map(fullDefs.map((def) => [def.name, def] as const));\n\tconst gatewayNames: string[] = [];\n\tfor (const gateway of input.proxyGateways ?? []) {\n\t\tconst tool = createMcpProxyTool(gateway.server, gateway.entries);\n\t\tpi.registerTool(tool);\n\t\tgatewayNames.push(tool.name);\n\t}\n\tfor (const tool of input.utilityTools ?? []) {\n\t\tpi.registerTool(tool);\n\t\tgatewayNames.push(tool.name);\n\t}\n\tconst activeMcpNames = [...mapMcpCatalogNames(input.activeEntries).map(({ name }) => name), ...gatewayNames];\n\tconst reference = pi.getActiveTools();\n\t// Base tools carry over; any stale mcp_* names from a prior generation are\n\t// dropped (membership = base + the intended mcp set; reference orders only).\n\tconst currentBase = reference.filter((name) => !name.startsWith(\"mcp_\"));\n\n\tif (!input.searchMode) {\n\t\tregisterToolsPreservingActiveSet(pi, fullDefs, orderActiveSet([...currentBase, ...activeMcpNames], reference));\n\t\t// Direct mode: everything registerable is already active, nothing to replay.\n\t\tconst activateDirect = (names: readonly string[]): void => {\n\t\t\tpi.setActiveTools(orderActiveSet(unionStable(pi.getActiveTools(), names), pi.getActiveTools()));\n\t\t};\n\t\treturn { activate: activateDirect, searchable, rehydrateFromHistory: NOOP_REHYDRATE };\n\t}\n\n\tconst stubSwap = input.settings.stubSwap === true;\n\tconst stubbed = new Set<string>();\n\tconst activateMcpTools = (names: readonly string[]): void => {\n\t\tif (stubSwap) swapStubsToFull(pi, names, stubbed, fullByName);\n\t\tpi.setActiveTools(orderActiveSet(names, pi.getActiveTools()));\n\t};\n\tconst searchTool = createMcpSearchTool({\n\t\tgetSearchableTools: () => searchable,\n\t\tgetActiveTools: () => pi.getActiveTools(),\n\t\tsetActiveTools: activateMcpTools,\n\t});\n\tconst registeredNames = new Set(fullDefs.map((def) => def.name));\n\tconst activate = (names: readonly string[]): void => {\n\t\tconst known = names.filter((name) => registeredNames.has(name));\n\t\tif (known.length === 0) return;\n\t\tactivateMcpTools(unionStable(pi.getActiveTools(), known));\n\t};\n\tconst rehydrateFromHistory = (messages: readonly unknown[]): string[] => {\n\t\tconst restored = rehydrateActiveToolsFromHistory(messages, registeredNames);\n\t\tconst current = pi.getActiveTools();\n\t\tconst fresh = restored.filter((name) => !current.includes(name));\n\t\tif (fresh.length === 0) return [];\n\t\tactivateMcpTools(unionStable(current, fresh));\n\t\treturn fresh;\n\t};\n\n\t// tool_search carries a distinct param schema, so register it on its own\n\t// rather than mixing it into the broadly-typed catalog def array.\n\tpi.registerTool(searchTool);\n\n\tif (!stubSwap) {\n\t\t// Default search mode: full defs registered, only directTools + tool_search\n\t\t// active. Newly promoted tools enter the array on their activation turn\n\t\t// (an accepted cache miss).\n\t\tconst active = orderActiveSet([...currentBase, TOOL_SEARCH_TOOL_NAME, ...activeMcpNames], reference);\n\t\tregisterToolsPreservingActiveSet(pi, fullDefs, active);\n\t\treturn { activate, searchable, rehydrateFromHistory };\n\t}\n\n\t// stubSwap: every search-mode tool is registered as a tiny stub and kept\n\t// active so the tools array is length-stable; direct tools stay full.\n\tconst directActive = new Set(activeMcpNames);\n\tconst toRegister: McpToolDefinition[] = fullDefs.map((def) => {\n\t\tif (directActive.has(def.name)) return def;\n\t\tstubbed.add(def.name);\n\t\treturn buildMcpStubDefinition(def.name);\n\t});\n\tconst active = orderActiveSet(\n\t\t[...currentBase, TOOL_SEARCH_TOOL_NAME, ...fullDefs.map((def) => def.name)],\n\t\treference,\n\t);\n\tregisterToolsPreservingActiveSet(pi, toRegister, active);\n\treturn { activate, searchable, rehydrateFromHistory };\n}\n\nfunction swapStubsToFull(\n\tpi: McpToolRegistrar,\n\tnames: readonly string[],\n\tstubbed: Set<string>,\n\tfullByName: ReadonlyMap<string, McpToolDefinition>,\n): void {\n\tfor (const name of names) {\n\t\tif (!stubbed.has(name)) continue;\n\t\tconst full = fullByName.get(name);\n\t\tif (full === undefined) continue;\n\t\tpi.registerTool(full);\n\t\tstubbed.delete(name);\n\t}\n}\n\n/** Order the active set deterministically WITHOUT disturbing non-MCP (base)\n * tools: base tools keep their existing relative order (by `reference` index,\n * new ones appended), MCP tools (incl. tool_search) are sorted for cache\n * stability. Sorting base tools would churn the system-prompt tool listing. */\nfunction orderActiveSet(names: readonly string[], reference: readonly string[]): string[] {\n\tconst unique = [...new Set(names)];\n\tconst rank = new Map(reference.map((name, index) => [name, index] as const));\n\tconst base = unique\n\t\t.filter((name) => !name.startsWith(\"mcp_\"))\n\t\t.sort((a, b) => (rank.get(a) ?? Number.MAX_SAFE_INTEGER) - (rank.get(b) ?? Number.MAX_SAFE_INTEGER));\n\tconst mcp = unique.filter((name) => name.startsWith(\"mcp_\")).sort();\n\treturn [...base, ...mcp];\n}\n\n/** A 30-70 token placeholder for an inactive search-mode tool. Keeps the tools\n * array length-stable under stubSwap; guides the model to tool_search. */\nexport function buildMcpStubDefinition(name: string): McpToolDefinition {\n\treturn {\n\t\tname,\n\t\tlabel: name,\n\t\tdescription: `Inactive MCP tool. Run tool_search to activate ${name}, then call it on your next turn.`,\n\t\tparameters: Type.Object({}),\n\t\texecutionMode: \"parallel\",\n\t\tasync execute(): Promise<AgentToolResult<McpToolDetails | undefined>> {\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: `${name} is not active. Use tool_search to activate it, then call it.` }],\n\t\t\t\tdetails: undefined,\n\t\t\t};\n\t\t},\n\t\trenderCall(_args, theme) {\n\t\t\treturn new Text(theme.fg(\"toolOutput\", `${name} (inactive stub)`), 0, 0);\n\t\t},\n\t};\n}\n"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import type { ToolDefinition } from "../../../types.ts";
|
|
3
3
|
import { type Bm25Doc } from "./bm25.ts";
|
|
4
|
-
export declare const
|
|
4
|
+
export declare const TOOL_SEARCH_TOOL_NAME = "tool_search";
|
|
5
5
|
/** Stable, machine-parseable marker embedded in every activating result so
|
|
6
6
|
* activations survive compaction/restart. Kept human-readable on purpose. */
|
|
7
|
-
export declare const
|
|
7
|
+
export declare const TOOL_SEARCH_ACTIVATION_MARKER = "[tool_search:activated]";
|
|
8
8
|
export type SearchableMcpTool = Bm25Doc;
|
|
9
9
|
export interface McpSearchDeps {
|
|
10
10
|
/** Full current catalog (all MCP tools, active or not) to rank over. */
|
|
@@ -30,7 +30,7 @@ interface RankedMatch {
|
|
|
30
30
|
}
|
|
31
31
|
export declare function buildMcpSearchResultText(query: string, matches: readonly RankedMatch[], server: string | undefined): string;
|
|
32
32
|
/**
|
|
33
|
-
* Replay
|
|
33
|
+
* Replay tool_search activations recorded in prior history so a
|
|
34
34
|
* compacted/restarted session restores its active tool set without re-searching.
|
|
35
35
|
* Only names still present in `validNames` are restored (removed tools stay
|
|
36
36
|
* dropped). Deterministic: returns a de-duplicated, sorted list.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-search.d.ts","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/tool-search.ts"],"names":[],"mappings":"AAYA,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,KAAK,OAAO,EAAkB,MAAM,WAAW,CAAC;AAEzD,eAAO,MAAM,
|
|
1
|
+
{"version":3,"file":"tool-search.d.ts","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/tool-search.ts"],"names":[],"mappings":"AAYA,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,KAAK,OAAO,EAAkB,MAAM,WAAW,CAAC;AAEzD,eAAO,MAAM,qBAAqB,gBAAgB,CAAC;AACnD;6EAC6E;AAC7E,eAAO,MAAM,6BAA6B,4BAA4B,CAAC;AAGvE,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAExC,MAAM,WAAW,aAAa;IAC7B,wEAAwE;IACxE,kBAAkB,IAAI,SAAS,iBAAiB,EAAE,CAAC;IACnD,0DAA0D;IAC1D,cAAc,IAAI,MAAM,EAAE,CAAC;IAC3B,4EAA4E;IAC5E,cAAc,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;CAC/C;AAED,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,QAAA,MAAM,YAAY;;;EAGhB,CAAC;AAGH,KAAK,aAAa,GAAG,cAAc,CAAC,OAAO,YAAY,EAAE,gBAAgB,EAAE,OAAO,CAAC,CAAC;AAEpF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CAmCtE;AAED,UAAU,WAAW;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,iBAAiB,CAAC;CAChC;AAED,wBAAgB,wBAAwB,CACvC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,MAAM,EAAE,MAAM,GAAG,SAAS,GACxB,MAAM,CAgBR;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC9C,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,GAC7B,MAAM,EAAE,CAUV;AAsBD;2CAC2C;AAC3C,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAE1F"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
//
|
|
1
|
+
// tool_search tool + promotion engine (todo 31).
|
|
2
2
|
//
|
|
3
|
-
// `
|
|
3
|
+
// `tool_search` is an always-active tool that ranks the full MCP catalog with
|
|
4
4
|
// the local BM25 engine and PROMOTES matched tools into the active set via
|
|
5
5
|
// setActiveTools (effective the NEXT turn — senpi semantics). Promotion state
|
|
6
6
|
// is derivable from history: each result embeds a stable activation marker so a
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
import { Text } from "@earendil-works/pi-tui";
|
|
11
11
|
import { Type } from "typebox";
|
|
12
12
|
import { buildBm25Index } from "./bm25.js";
|
|
13
|
-
export const
|
|
13
|
+
export const TOOL_SEARCH_TOOL_NAME = "tool_search";
|
|
14
14
|
/** Stable, machine-parseable marker embedded in every activating result so
|
|
15
15
|
* activations survive compaction/restart. Kept human-readable on purpose. */
|
|
16
|
-
export const
|
|
16
|
+
export const TOOL_SEARCH_ACTIVATION_MARKER = "[tool_search:activated]";
|
|
17
17
|
const MAX_RESULTS = 10;
|
|
18
18
|
const ParamsSchema = Type.Object({
|
|
19
19
|
query: Type.String({ description: "Natural-language description of the capability you need." }),
|
|
@@ -21,7 +21,7 @@ const ParamsSchema = Type.Object({
|
|
|
21
21
|
});
|
|
22
22
|
export function createMcpSearchTool(deps) {
|
|
23
23
|
return {
|
|
24
|
-
name:
|
|
24
|
+
name: TOOL_SEARCH_TOOL_NAME,
|
|
25
25
|
label: "MCP tool search",
|
|
26
26
|
description: "Search the catalog of available MCP tools by capability. Matched tools are activated and become callable on your NEXT turn. Use this before calling an MCP tool that is not already active.",
|
|
27
27
|
promptSnippet: "Search MCP servers for a tool by capability; matched tools activate next turn.",
|
|
@@ -43,13 +43,13 @@ export function createMcpSearchTool(deps) {
|
|
|
43
43
|
},
|
|
44
44
|
renderCall(args, theme) {
|
|
45
45
|
const filter = args.server ? ` @${args.server}` : "";
|
|
46
|
-
return new Text(theme.fg("toolTitle", theme.bold(`${
|
|
46
|
+
return new Text(theme.fg("toolTitle", theme.bold(`${TOOL_SEARCH_TOOL_NAME} "${args.query}"${filter}`)), 0, 0);
|
|
47
47
|
},
|
|
48
48
|
renderResult(result, options, theme) {
|
|
49
49
|
const count = result.details?.activated.length ?? 0;
|
|
50
50
|
const title = options.isPartial
|
|
51
|
-
? `${
|
|
52
|
-
: `${
|
|
51
|
+
? `${TOOL_SEARCH_TOOL_NAME}: searching`
|
|
52
|
+
: `${TOOL_SEARCH_TOOL_NAME}: ${count} tool(s) activated`;
|
|
53
53
|
return new Text(theme.fg("toolOutput", title), 0, 0);
|
|
54
54
|
},
|
|
55
55
|
};
|
|
@@ -57,7 +57,7 @@ export function createMcpSearchTool(deps) {
|
|
|
57
57
|
export function buildMcpSearchResultText(query, matches, server) {
|
|
58
58
|
const scope = server ? ` on server "${server}"` : "";
|
|
59
59
|
if (matches.length === 0) {
|
|
60
|
-
return `No MCP tools matched "${query}"${scope}. No tools were activated; try different keywords or run
|
|
60
|
+
return `No MCP tools matched "${query}"${scope}. No tools were activated; try different keywords or run tool_search with a broader query. Your active tool set is unchanged.`;
|
|
61
61
|
}
|
|
62
62
|
const bullets = matches
|
|
63
63
|
.map((match) => `- ${match.name} — ${oneLine(match.doc.description) ?? "(no description)"}`)
|
|
@@ -68,11 +68,11 @@ export function buildMcpSearchResultText(query, matches, server) {
|
|
|
68
68
|
"",
|
|
69
69
|
bullets,
|
|
70
70
|
"",
|
|
71
|
-
`${
|
|
71
|
+
`${TOOL_SEARCH_ACTIVATION_MARKER} ${names}`,
|
|
72
72
|
].join("\n");
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
|
-
* Replay
|
|
75
|
+
* Replay tool_search activations recorded in prior history so a
|
|
76
76
|
* compacted/restarted session restores its active tool set without re-searching.
|
|
77
77
|
* Only names still present in `validNames` are restored (removed tools stay
|
|
78
78
|
* dropped). Deterministic: returns a de-duplicated, sorted list.
|
|
@@ -98,14 +98,14 @@ function extractActivationSegments(message) {
|
|
|
98
98
|
return [];
|
|
99
99
|
}
|
|
100
100
|
const segments = [];
|
|
101
|
-
let cursor = blob.indexOf(
|
|
101
|
+
let cursor = blob.indexOf(TOOL_SEARCH_ACTIVATION_MARKER);
|
|
102
102
|
while (cursor >= 0) {
|
|
103
|
-
const rest = blob.slice(cursor +
|
|
103
|
+
const rest = blob.slice(cursor + TOOL_SEARCH_ACTIVATION_MARKER.length);
|
|
104
104
|
// Tool names are [a-zA-Z0-9_-]; stop at the first JSON string terminator
|
|
105
105
|
// (quote / escape) or newline so we never swallow the rest of the blob.
|
|
106
106
|
const end = rest.search(/["\\\n]/);
|
|
107
107
|
segments.push(end < 0 ? rest : rest.slice(0, end));
|
|
108
|
-
cursor = blob.indexOf(
|
|
108
|
+
cursor = blob.indexOf(TOOL_SEARCH_ACTIVATION_MARKER, cursor + TOOL_SEARCH_ACTIVATION_MARKER.length);
|
|
109
109
|
}
|
|
110
110
|
return segments;
|
|
111
111
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-search.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/tool-search.ts"],"names":[],"mappings":"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","sourcesContent":["// mcp_search tool + promotion engine (todo 31).\n//\n// `mcp_search` is an always-active tool that ranks the full MCP catalog with\n// the local BM25 engine and PROMOTES matched tools into the active set via\n// setActiveTools (effective the NEXT turn — senpi semantics). Promotion state\n// is derivable from history: each result embeds a stable activation marker so a\n// compaction/restart can replay activations at session_start\n// (rehydrateActiveToolsFromHistory). Activated tools persist for the session;\n// there is no auto-eviction in v1. Every setActiveTools call is stable-sorted.\n\nimport type { AgentToolResult } from \"@earendil-works/pi-agent-core\";\nimport { Text } from \"@earendil-works/pi-tui\";\nimport { type Static, Type } from \"typebox\";\nimport type { ToolDefinition } from \"../../../types.ts\";\nimport { type Bm25Doc, buildBm25Index } from \"./bm25.ts\";\n\nexport const MCP_SEARCH_TOOL_NAME = \"mcp_search\";\n/** Stable, machine-parseable marker embedded in every activating result so\n * activations survive compaction/restart. Kept human-readable on purpose. */\nexport const MCP_SEARCH_ACTIVATION_MARKER = \"[mcp_search:activated]\";\nconst MAX_RESULTS = 10;\n\nexport type SearchableMcpTool = Bm25Doc;\n\nexport interface McpSearchDeps {\n\t/** Full current catalog (all MCP tools, active or not) to rank over. */\n\tgetSearchableTools(): readonly SearchableMcpTool[];\n\t/** Current active tool names (includes non-MCP tools). */\n\tgetActiveTools(): string[];\n\t/** Replace the active set. Implementations should preserve registration. */\n\tsetActiveTools(names: readonly string[]): void;\n}\n\nexport interface McpSearchDetails {\n\tquery: string;\n\tactivated: string[];\n}\n\nconst ParamsSchema = Type.Object({\n\tquery: Type.String({ description: \"Natural-language description of the capability you need.\" }),\n\tserver: Type.Optional(Type.String({ description: \"Optional: restrict the search to a single MCP server name.\" })),\n});\ntype Params = Static<typeof ParamsSchema>;\n\ntype McpSearchTool = ToolDefinition<typeof ParamsSchema, McpSearchDetails, unknown>;\n\nexport function createMcpSearchTool(deps: McpSearchDeps): McpSearchTool {\n\treturn {\n\t\tname: MCP_SEARCH_TOOL_NAME,\n\t\tlabel: \"MCP tool search\",\n\t\tdescription:\n\t\t\t\"Search the catalog of available MCP tools by capability. Matched tools are activated and become callable on your NEXT turn. Use this before calling an MCP tool that is not already active.\",\n\t\tpromptSnippet: \"Search MCP servers for a tool by capability; matched tools activate next turn.\",\n\t\tparameters: ParamsSchema,\n\t\texecutionMode: \"parallel\",\n\t\tasync execute(_toolCallId, params: Params): Promise<AgentToolResult<McpSearchDetails>> {\n\t\t\tconst searchable = deps.getSearchableTools();\n\t\t\tconst index = buildBm25Index(searchable);\n\t\t\tconst matches = index.search(params.query, MAX_RESULTS, params.server ? { server: params.server } : {});\n\t\t\tconst activated = matches.map((match) => match.name);\n\t\t\tif (activated.length > 0) {\n\t\t\t\tdeps.setActiveTools(unionStable(deps.getActiveTools(), activated));\n\t\t\t}\n\t\t\tconst text = buildMcpSearchResultText(params.query, matches, params.server);\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text }],\n\t\t\t\tdetails: { activated, query: params.query },\n\t\t\t};\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\tconst filter = args.server ? ` @${args.server}` : \"\";\n\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(`${MCP_SEARCH_TOOL_NAME} \"${args.query}\"${filter}`)), 0, 0);\n\t\t},\n\t\trenderResult(result, options, theme) {\n\t\t\tconst count = result.details?.activated.length ?? 0;\n\t\t\tconst title = options.isPartial\n\t\t\t\t? `${MCP_SEARCH_TOOL_NAME}: searching`\n\t\t\t\t: `${MCP_SEARCH_TOOL_NAME}: ${count} tool(s) activated`;\n\t\t\treturn new Text(theme.fg(\"toolOutput\", title), 0, 0);\n\t\t},\n\t};\n}\n\ninterface RankedMatch {\n\treadonly name: string;\n\treadonly doc: SearchableMcpTool;\n}\n\nexport function buildMcpSearchResultText(\n\tquery: string,\n\tmatches: readonly RankedMatch[],\n\tserver: string | undefined,\n): string {\n\tconst scope = server ? ` on server \"${server}\"` : \"\";\n\tif (matches.length === 0) {\n\t\treturn `No MCP tools matched \"${query}\"${scope}. No tools were activated; try different keywords or run mcp_search with a broader query. Your active tool set is unchanged.`;\n\t}\n\tconst bullets = matches\n\t\t.map((match) => `- ${match.name} — ${oneLine(match.doc.description) ?? \"(no description)\"}`)\n\t\t.join(\"\\n\");\n\tconst names = matches.map((match) => match.name).join(\" \");\n\treturn [\n\t\t`Found ${matches.length} MCP tool(s) matching \"${query}\"${scope}. They are now active and callable from your NEXT turn:`,\n\t\t\"\",\n\t\tbullets,\n\t\t\"\",\n\t\t`${MCP_SEARCH_ACTIVATION_MARKER} ${names}`,\n\t].join(\"\\n\");\n}\n\n/**\n * Replay mcp_search activations recorded in prior history so a\n * compacted/restarted session restores its active tool set without re-searching.\n * Only names still present in `validNames` are restored (removed tools stay\n * dropped). Deterministic: returns a de-duplicated, sorted list.\n */\nexport function rehydrateActiveToolsFromHistory(\n\tmessages: readonly unknown[],\n\tvalidNames: ReadonlySet<string>,\n): string[] {\n\tconst restored = new Set<string>();\n\tfor (const message of messages) {\n\t\tfor (const segment of extractActivationSegments(message)) {\n\t\t\tfor (const name of segment.trim().split(/\\s+/)) {\n\t\t\t\tif (name.length > 0 && validNames.has(name)) restored.add(name);\n\t\t\t}\n\t\t}\n\t}\n\treturn [...restored].sort();\n}\n\nfunction extractActivationSegments(message: unknown): string[] {\n\tlet blob: string;\n\ttry {\n\t\tblob = JSON.stringify(message) ?? \"\";\n\t} catch {\n\t\treturn [];\n\t}\n\tconst segments: string[] = [];\n\tlet cursor = blob.indexOf(MCP_SEARCH_ACTIVATION_MARKER);\n\twhile (cursor >= 0) {\n\t\tconst rest = blob.slice(cursor + MCP_SEARCH_ACTIVATION_MARKER.length);\n\t\t// Tool names are [a-zA-Z0-9_-]; stop at the first JSON string terminator\n\t\t// (quote / escape) or newline so we never swallow the rest of the blob.\n\t\tconst end = rest.search(/[\"\\\\\\n]/);\n\t\tsegments.push(end < 0 ? rest : rest.slice(0, end));\n\t\tcursor = blob.indexOf(MCP_SEARCH_ACTIVATION_MARKER, cursor + MCP_SEARCH_ACTIVATION_MARKER.length);\n\t}\n\treturn segments;\n}\n\n/** Stable union: dedupe and sort so the active-tools array is byte-stable\n * across turns (prompt-cache friendly). */\nexport function unionStable(current: readonly string[], added: readonly string[]): string[] {\n\treturn [...new Set([...current, ...added])].sort();\n}\n\nfunction oneLine(text: string | undefined): string | undefined {\n\tif (text === undefined) return undefined;\n\tconst collapsed = text.replace(/\\s+/g, \" \").trim();\n\tif (collapsed.length === 0) return undefined;\n\treturn collapsed.length <= 100 ? collapsed : `${collapsed.slice(0, 97)}...`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tool-search.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/mcp/expose/tool-search.ts"],"names":[],"mappings":"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","sourcesContent":["// tool_search tool + promotion engine (todo 31).\n//\n// `tool_search` is an always-active tool that ranks the full MCP catalog with\n// the local BM25 engine and PROMOTES matched tools into the active set via\n// setActiveTools (effective the NEXT turn — senpi semantics). Promotion state\n// is derivable from history: each result embeds a stable activation marker so a\n// compaction/restart can replay activations at session_start\n// (rehydrateActiveToolsFromHistory). Activated tools persist for the session;\n// there is no auto-eviction in v1. Every setActiveTools call is stable-sorted.\n\nimport type { AgentToolResult } from \"@earendil-works/pi-agent-core\";\nimport { Text } from \"@earendil-works/pi-tui\";\nimport { type Static, Type } from \"typebox\";\nimport type { ToolDefinition } from \"../../../types.ts\";\nimport { type Bm25Doc, buildBm25Index } from \"./bm25.ts\";\n\nexport const TOOL_SEARCH_TOOL_NAME = \"tool_search\";\n/** Stable, machine-parseable marker embedded in every activating result so\n * activations survive compaction/restart. Kept human-readable on purpose. */\nexport const TOOL_SEARCH_ACTIVATION_MARKER = \"[tool_search:activated]\";\nconst MAX_RESULTS = 10;\n\nexport type SearchableMcpTool = Bm25Doc;\n\nexport interface McpSearchDeps {\n\t/** Full current catalog (all MCP tools, active or not) to rank over. */\n\tgetSearchableTools(): readonly SearchableMcpTool[];\n\t/** Current active tool names (includes non-MCP tools). */\n\tgetActiveTools(): string[];\n\t/** Replace the active set. Implementations should preserve registration. */\n\tsetActiveTools(names: readonly string[]): void;\n}\n\nexport interface McpSearchDetails {\n\tquery: string;\n\tactivated: string[];\n}\n\nconst ParamsSchema = Type.Object({\n\tquery: Type.String({ description: \"Natural-language description of the capability you need.\" }),\n\tserver: Type.Optional(Type.String({ description: \"Optional: restrict the search to a single MCP server name.\" })),\n});\ntype Params = Static<typeof ParamsSchema>;\n\ntype McpSearchTool = ToolDefinition<typeof ParamsSchema, McpSearchDetails, unknown>;\n\nexport function createMcpSearchTool(deps: McpSearchDeps): McpSearchTool {\n\treturn {\n\t\tname: TOOL_SEARCH_TOOL_NAME,\n\t\tlabel: \"MCP tool search\",\n\t\tdescription:\n\t\t\t\"Search the catalog of available MCP tools by capability. Matched tools are activated and become callable on your NEXT turn. Use this before calling an MCP tool that is not already active.\",\n\t\tpromptSnippet: \"Search MCP servers for a tool by capability; matched tools activate next turn.\",\n\t\tparameters: ParamsSchema,\n\t\texecutionMode: \"parallel\",\n\t\tasync execute(_toolCallId, params: Params): Promise<AgentToolResult<McpSearchDetails>> {\n\t\t\tconst searchable = deps.getSearchableTools();\n\t\t\tconst index = buildBm25Index(searchable);\n\t\t\tconst matches = index.search(params.query, MAX_RESULTS, params.server ? { server: params.server } : {});\n\t\t\tconst activated = matches.map((match) => match.name);\n\t\t\tif (activated.length > 0) {\n\t\t\t\tdeps.setActiveTools(unionStable(deps.getActiveTools(), activated));\n\t\t\t}\n\t\t\tconst text = buildMcpSearchResultText(params.query, matches, params.server);\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text }],\n\t\t\t\tdetails: { activated, query: params.query },\n\t\t\t};\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\tconst filter = args.server ? ` @${args.server}` : \"\";\n\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(`${TOOL_SEARCH_TOOL_NAME} \"${args.query}\"${filter}`)), 0, 0);\n\t\t},\n\t\trenderResult(result, options, theme) {\n\t\t\tconst count = result.details?.activated.length ?? 0;\n\t\t\tconst title = options.isPartial\n\t\t\t\t? `${TOOL_SEARCH_TOOL_NAME}: searching`\n\t\t\t\t: `${TOOL_SEARCH_TOOL_NAME}: ${count} tool(s) activated`;\n\t\t\treturn new Text(theme.fg(\"toolOutput\", title), 0, 0);\n\t\t},\n\t};\n}\n\ninterface RankedMatch {\n\treadonly name: string;\n\treadonly doc: SearchableMcpTool;\n}\n\nexport function buildMcpSearchResultText(\n\tquery: string,\n\tmatches: readonly RankedMatch[],\n\tserver: string | undefined,\n): string {\n\tconst scope = server ? ` on server \"${server}\"` : \"\";\n\tif (matches.length === 0) {\n\t\treturn `No MCP tools matched \"${query}\"${scope}. No tools were activated; try different keywords or run tool_search with a broader query. Your active tool set is unchanged.`;\n\t}\n\tconst bullets = matches\n\t\t.map((match) => `- ${match.name} — ${oneLine(match.doc.description) ?? \"(no description)\"}`)\n\t\t.join(\"\\n\");\n\tconst names = matches.map((match) => match.name).join(\" \");\n\treturn [\n\t\t`Found ${matches.length} MCP tool(s) matching \"${query}\"${scope}. They are now active and callable from your NEXT turn:`,\n\t\t\"\",\n\t\tbullets,\n\t\t\"\",\n\t\t`${TOOL_SEARCH_ACTIVATION_MARKER} ${names}`,\n\t].join(\"\\n\");\n}\n\n/**\n * Replay tool_search activations recorded in prior history so a\n * compacted/restarted session restores its active tool set without re-searching.\n * Only names still present in `validNames` are restored (removed tools stay\n * dropped). Deterministic: returns a de-duplicated, sorted list.\n */\nexport function rehydrateActiveToolsFromHistory(\n\tmessages: readonly unknown[],\n\tvalidNames: ReadonlySet<string>,\n): string[] {\n\tconst restored = new Set<string>();\n\tfor (const message of messages) {\n\t\tfor (const segment of extractActivationSegments(message)) {\n\t\t\tfor (const name of segment.trim().split(/\\s+/)) {\n\t\t\t\tif (name.length > 0 && validNames.has(name)) restored.add(name);\n\t\t\t}\n\t\t}\n\t}\n\treturn [...restored].sort();\n}\n\nfunction extractActivationSegments(message: unknown): string[] {\n\tlet blob: string;\n\ttry {\n\t\tblob = JSON.stringify(message) ?? \"\";\n\t} catch {\n\t\treturn [];\n\t}\n\tconst segments: string[] = [];\n\tlet cursor = blob.indexOf(TOOL_SEARCH_ACTIVATION_MARKER);\n\twhile (cursor >= 0) {\n\t\tconst rest = blob.slice(cursor + TOOL_SEARCH_ACTIVATION_MARKER.length);\n\t\t// Tool names are [a-zA-Z0-9_-]; stop at the first JSON string terminator\n\t\t// (quote / escape) or newline so we never swallow the rest of the blob.\n\t\tconst end = rest.search(/[\"\\\\\\n]/);\n\t\tsegments.push(end < 0 ? rest : rest.slice(0, end));\n\t\tcursor = blob.indexOf(TOOL_SEARCH_ACTIVATION_MARKER, cursor + TOOL_SEARCH_ACTIVATION_MARKER.length);\n\t}\n\treturn segments;\n}\n\n/** Stable union: dedupe and sort so the active-tools array is byte-stable\n * across turns (prompt-cache friendly). */\nexport function unionStable(current: readonly string[], added: readonly string[]): string[] {\n\treturn [...new Set([...current, ...added])].sort();\n}\n\nfunction oneLine(text: string | undefined): string | undefined {\n\tif (text === undefined) return undefined;\n\tconst collapsed = text.replace(/\\s+/g, \" \").trim();\n\tif (collapsed.length === 0) return undefined;\n\treturn collapsed.length <= 100 ? collapsed : `${collapsed.slice(0, 97)}...`;\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { registerMcpCommands } from "./commands.js";
|
|
2
2
|
import { setMcpElicitationUiProvider } from "./elicitation.js";
|
|
3
3
|
import { AnthropicNativeToolSearchAdapter } from "./expose/native-search.js";
|
|
4
|
-
import {
|
|
4
|
+
import { TOOL_SEARCH_TOOL_NAME } from "./expose/tool-search.js";
|
|
5
5
|
import { injectMcpInstructions, refreshMcpInstructionsForSession } from "./instructions.js";
|
|
6
6
|
import { createMcpLogger } from "./log.js";
|
|
7
7
|
import { registerMcpPromptCommands } from "./prompts.js";
|
|
@@ -22,19 +22,19 @@ export default function mcpExtension(pi) {
|
|
|
22
22
|
// Native provider tool-search adapter (todo 33 — Anthropic, spike = GO).
|
|
23
23
|
// Runs on every request but is a no-op unless settings.nativeToolSearch is
|
|
24
24
|
// auto|true and the model is anthropic-messages; a 400 disables it for the
|
|
25
|
-
// session and falls back to the always-registered local
|
|
25
|
+
// session and falls back to the always-registered local tool_search.
|
|
26
26
|
const nativeAdapter = new AnthropicNativeToolSearchAdapter({
|
|
27
27
|
enabled: () => {
|
|
28
28
|
const setting = getMcpService().getNativeToolSearchSetting();
|
|
29
29
|
return setting === true || setting === "auto";
|
|
30
30
|
},
|
|
31
|
-
isDeferrable: (name) => name.startsWith("mcp_") && name !==
|
|
31
|
+
isDeferrable: (name) => name.startsWith("mcp_") && name !== TOOL_SEARCH_TOOL_NAME,
|
|
32
32
|
onFallback: (reason) => createMcpLogger("service").warn(reason),
|
|
33
|
-
searchToolName:
|
|
33
|
+
searchToolName: TOOL_SEARCH_TOOL_NAME,
|
|
34
34
|
});
|
|
35
35
|
pi.on("before_provider_request", (event, ctx) => nativeAdapter.applyBeforeRequest(ctx.model?.api, event.payload));
|
|
36
36
|
pi.on("after_provider_response", (event) => nativeAdapter.noteResponseStatus(event.status));
|
|
37
|
-
// Resumed/compacted sessions carry
|
|
37
|
+
// Resumed/compacted sessions carry tool_search activation markers in their
|
|
38
38
|
// history but re-enter search mode with only directTools active. The context
|
|
39
39
|
// event (fired before each LLM call, with the full message history) replays
|
|
40
40
|
// the markers as a safety net; the primary replay happens at attach time so
|