import type { AgentMessage, AgentTool } from "../../internal/harness.js"; import type { Model } from "../../internal/llm.js"; import type { ToolSpec } from "../types.js"; /** * Deferred-tool dynamic disclosure (design/36). A deployment with hundreds of tools / several MCP * servers can't inline every full JSON Schema into every request — turn-1 cache-creation tokens * explode and any tool-description drift breaks the prefix cache. Instead, deferred tools ship as * lightweight placeholders ({name, one-line hint, empty params}); a resident `tool_search` lets the * model activate them by name or keyword, at which point core MATERIALIZES their full schema into the * next request's tools[] (client-side, provider-agnostic — no Anthropic `tool_reference` beta) and * announces them via the search's own result content (tail of the log; the cache prefix is untouched). * * This module is pure tool/registry logic: prepare-task owns the harness wiring and supplies a * `rematerialize` callback (which calls `harness.setTools` + refreshes the design/31 fingerprint). */ export declare const TOOL_SEARCH_NAME = "ToolSearch"; /** Default keyword-search result cap (design/116 W1-2 = CC ToolSearchTool.ts:28-32 `max_results` default 5). */ export declare const TOOL_SEARCH_DEFAULT_MAX_RESULTS = 5; /** A deferred tool as the search ranks it: stable name + the one-line hint + full description for scoring. */ export interface DeferredToolInfo { name: string; hint: string; description: string; } /** First line of a description, trimmed to a single short hint for a placeholder / announcement. */ export declare function deferHint(description: string, max?: number): string; /** * Neutralize a tool name before it appears in ANY model-facing text (design/36 minor #5). A tool name is * attacker-influenced — a malicious MCP server can return one containing newlines (to inject a fake * ``/instruction line), backticks/quotes (to corrupt the JSON or markdown the model is * told to emit), or angle brackets (to break a delimiter). Strip control chars, CR/LF, `` ` ``, `"`, `<`, `>`. * For a normal name (`[A-Za-z0-9_.-]`, incl. MCP `server__tool`) this is a no-op. * * MUST wrap a tool name at EVERY site where it reaches the model — placeholder text, the search tool's * announcement, anywhere. A bare interpolation is a prompt-injection hole (council BUG, design/36). */ export declare function safeName(name: string): string; /** * Decide which tool names are deferred. Deterministic by default: explicit `ToolSpec.defer === true` * plus all MCP tool names (most numerous / most drift-prone). With `deferMode: "auto"`, ADDITIONALLY * defer the remaining user tools when their combined inlined schema would exceed ~10% of the model's * context window — an explicit opt-in (council minor #6), because an implicit threshold makes the same * TaskSpec yield different tools[] across models/contexts. Explicit `defer` always wins. */ export declare function classifyDeferred(opts: { specs: ReadonlyArray; mcpToolNames: ReadonlyArray; fullTools: ReadonlyArray<{ name: string; description: string; parameters: unknown; }>; deferMode?: "auto"; model: Model; }): Set; /** Build the registry the search ranks over (deferred tools only — non-deferred are already visible). */ export declare function buildDeferredRegistry(deferred: ReadonlySet, tools: ReadonlyArray<{ name: string; description: string; }>): Map; /** A placeholder AgentTool for a deferred tool: visible name + hint, empty params, "search first" body. */ export declare function makePlaceholderTool(info: DeferredToolInfo): AgentTool; /** Keyword score of a query against a deferred tool: name hits weigh more than description/hint hits. */ export declare function scoreToolMatch(query: string, info: DeferredToolInfo): number; /** The argument shapes `tool_search` accepts (design/116 W1-2). CC-canonical = `query` (+ optional * `max_results`); `select` is the legacy pre-design/116 array form — no longer in the model-facing * schema, but old durable checkpoints replay it and mid-flight sessions may still emit it, so the * resolver keeps accepting it. */ export interface ToolSearchArgs { query?: string; max_results?: number; /** Legacy exact-name array (pre-design/116 schema). Kept for durable-checkpoint replay compat. */ select?: string[]; } /** * Resolve a tool_search request to deferred-tool names (design/116 W1-2, CC ToolSearchTool.ts form). * Query forms, checked in order: * ① `select:A,B,C` prefix (case-insensitive prefix, CC ToolSearchTool.ts:363) — comma-split EXACT * name activation against the registry's names; misses are reported in `missing` (never silently * eaten) and the result is NOT truncated by `max_results` (explicit selection gets everything). * ② bare tool name (case-insensitive equality, CC:199-204) — selects that tool directly; handles * models sending a name instead of the select: prefix (seen from subagents/post-compaction). * ③ keyword search — scored matches capped at `max_results` (CC default 5). * The legacy `select` ARRAY (pre-design/116 schema) is still honored first: registry-checked, unknown * names silently dropped (historic behavior, council minor #4) — durable checkpoints replay these * arguments through here and must keep resolving identically. */ export declare function resolveToolSearchDetailed(args: ToolSearchArgs, registry: ReadonlyMap): { matched: string[]; missing: string[]; }; /** Names-only view of {@link resolveToolSearchDetailed} — the deterministic replay core used by * {@link extractDiscoveredToolNames} (and kept as the stable exported signature). */ export declare function resolveToolSearch(args: ToolSearchArgs, registry: ReadonlyMap): string[]; /** * Stateless recompute of the activated-tool set from the message log (design/36 §2.3). The in-process * `active` Set is the primary source within one process, but a RESUMED session (new process) starts with * an empty Set — without this, previously-activated tools would revert to placeholders. Re-derives the * set by replaying every prior `tool_search` call's arguments through `resolveToolSearch` (deterministic). * * design/116 W1-2 note: pre-116 checkpoints whose KEYWORD queries activated up to 25 tools re-derive at * the new default cap (5) — the extra tools revert to placeholders, which is self-healing (the model * re-activates on demand), never a widening. `select`-array and exact-name activations replay unchanged. */ export declare function extractDiscoveredToolNames(messages: ReadonlyArray, registry: ReadonlyMap): string[]; /** * Build the resident `tool_search` tool. On a search that NEWLY activates deferred tools it (1) adds them * to the monotonic `active` Set, (2) calls `rematerialize` (prepare-task swaps placeholders→full schema * via `harness.setTools` and refreshes the design/31 fingerprint), and (3) returns the "now available" * announcement as its OWN result content — the delta lands at the tail of the log, never in the cache prefix. */ export declare function makeToolSearchTool(opts: { registry: ReadonlyMap; active: Set; rematerialize: (active: ReadonlySet) => Promise; }): AgentTool; //# sourceMappingURL=tool-disclosure.d.ts.map