import { type TSchema } from "typebox";
import type { AgentMessage, AgentTool, AgentToolResult, AgentToolUpdateCallback, ToolExecutionMode } from "../../internal/harness-types.js";
/**
 * Deferred-tool dynamic disclosure. 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 fingerprint).
 */
export declare const TOOL_SEARCH_NAME = "ToolSearch";
/** Default keyword-search result cap (CC ToolSearchTool.ts:28-32 `max_results` default 5). */
export declare const TOOL_SEARCH_DEFAULT_MAX_RESULTS = 5;
/**
 * How many times in a row ONE placeholder may answer the IDENTICAL invalid-arguments
 * shape before the lane declares the loop unproductive and stops re-teaching. Three, because the
 * corrective round is worth paying for twice (the first rejection hands over the schema; a second
 * covers a model that needed the restatement) and a third identical failure is evidence that the
 * schema was never what was missing.
 */
export declare const DEFERRED_NO_PROGRESS_LIMIT = 3;
/**
 * The stable machine code the no-progress terminal carries on its tool-result `details.noProgress`
 * (and verbatim in its model-facing text, so a transcript reader and a log reader see one string).
 * Names the CONDITION, not the blame: the caller and the tool's declaration cannot be reconciled on
 * this route — the classic producer is a constrained decoder bound to the placeholder's empty
 * advertised schema.
 */
export declare const DEFERRED_SCHEMA_INCOMPATIBLE_CODE = "tool.deferred_schema_incompatible";
/** 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;
    /** The real tool's aliases — a placeholder stands for the tool under EVERY name it answers to, so dispatch by
     *  alias resolves to the same slot before and after activation (and the roster's name→row resolution agrees). */
    aliases?: readonly 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. A tool name is
 * externally influenced — an untrusted MCP server can return one containing newlines (to inject a fake
 * `<system-reminder>`/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.
 */
export declare function safeName(name: string): string;
/** Build the registry the search ranks over (deferred tools only — non-deferred are already visible). */
export declare function buildDeferredRegistry(deferred: ReadonlySet<string>, tools: ReadonlyArray<{
    name: string;
    description: string;
    aliases?: readonly string[];
}>): Map<string, DeferredToolInfo>;
/**
 * The direct-call lane a placeholder may carry. The upstream defer gate is SHAPE
 * validation, not activation state: a call whose arguments validate against the REAL tool's
 * declared schema runs the real tool directly (activation is only about whether the model has
 * been SHOWN the schema, never an execution gate). Governance cannot be laundered through this
 * lane: policy/effect/irreversibility gates key on the tool NAME maps built from the real
 * declarations, and the engine's approval pipeline runs before any execute — the placeholder
 * object on the harness list plays no part in adjudication.
 */
export interface PlaceholderDirectCall {
    /**
     * Resolve the real tool AT CALL TIME, never from a prepare-time snapshot. The mounted
     * roster is a LIVE array: an MCP refresh splices a server's name domain out and pushes freshly
     * materialized tool objects back in, so a Map captured when the placeholder was built would hand
     * this lane the WITHDRAWN object (stale schema shape gate, stale transport client) long after the
     * roster moved on. Returns `undefined` when the name no longer resolves — a withdrawn name has no
     * direct lane, and the caller falls back to the teaching rejection.
     */
    resolveReal: () => PlaceholderDirectTarget | undefined;
    /**
     * The scheduling declaration this placeholder carries while it stands in for the real
     * tool. Mirrors the REAL tool's own `executionMode` (see the mirror rationale in
     * {@link createPlaceholderTool}); absent ⇒ the engine's fail-closed default, exactly as it would
     * be for the real tool once activated.
     */
    executionMode?: ToolExecutionMode;
    /**
     * Does THIS name sit on the static face right now, i.e. will it never be swapped for
     * the real tool's declaration? The lane's model-facing rejection text reads it to say where the
     * schema it hands back actually lives (this result only, versus this result AND every later
     * request's tools block). Absent ⇒ the swap posture.
     *
   * A FUNCTION, evaluated per call, for the same reason `resolveReal` is: the answer is not
     * a prepare-time constant. The run-wide strategy is, but the per-tool exemption on top of it reads
     * the LIVE roster — an MCP refresh can replace a tool with a declaration that no longer fits the
     * result-text carrier (or one that now does), and a boolean captured when the placeholder was built
     * would then describe the wrong carrier.
     */
    staticFace?: () => boolean;
    /**
     * Activation side effects (active-set add + rematerialize + boundary delta announcement).
     * Idempotent; awaited BEFORE the invoke so the next boundary already shows the full schema.
     * MUST be atomic: a thrown rematerialize has to leave the active set as it found it.
     *
     * Returns the announcement text this activation has to CARRY, or `undefined` when it has
     * none (already active, or nothing rides on this name). The ToolSearch lane announces on its own
     * result content; a direct call makes no ToolSearch call at all, so a face whose only carrier is
     * that result — today the agent-type roster of a deferred delegation tool — had no way to reach
     * the model on this leg. The text is appended to THIS call's result (see
     * {@link createPlaceholderTool}), the same channel discipline: tail of the log, cache prefix
     * untouched, emitted only after the rematerialize succeeded.
     */
    activate: () => Promise<string | undefined>;
}
/** The live view of the real tool behind a placeholder, resolved per call (see {@link PlaceholderDirectCall.resolveReal}). */
export interface PlaceholderDirectTarget {
    /** The REAL tool's declared parameter schema — the shape gate for a direct call. */
    parameters: TSchema;
    /** Invoke the real tool; the result passes through VERBATIM (structured details intact).
     *  `onUpdate` is part of the contract: the engine hands every execute a progress sink,
     *  and dropping it here silently mutes streaming progress (MCP `notifications/progress`) for any
     *  tool reached through this lane, while the SAME tool streams normally once activated. */
    invoke: (toolCallId: string, params: unknown, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<unknown>) => Promise<AgentToolResult<unknown>>;
}
/**
 * Can the STATIC face carry this declaration at all? Static's only in-context schema
 * carrier is result TEXT, so a declaration that cannot be serialized, or that the model-facing error
 * bound would truncate into non-JSON, has no carrier there. Such a tool is exempted per-tool from the
 * static face (it materializes into the tools block on activation, the swap way) rather than being
 * announced as callable with a schema the model never actually receives. `undefined` (a name the live
 * roster no longer resolves) is not renderable either.
 */
export declare function staticSchemaRenderable(schema: TSchema | undefined): boolean;
/** A placeholder AgentTool for a deferred tool: visible name + hint, empty params. Without `direct`,
 *  every call is the teaching rejection; with it, a schema-valid call runs the real tool. */
export declare function createPlaceholderTool(info: DeferredToolInfo, direct?: PlaceholderDirectCall): 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 (CC-canonical): `query` (+ optional
 *  `max_results`). The pre-keyword-search `select` ARRAY arm is retired —
 *  durable-replay compat was already sacrificed by a prior ruling (replay resolution included);
 *  the CC-canonical `select:A,B,C` string prefix inside `query` is the live selection form. */
export interface ToolSearchArgs {
    query?: string;
    max_results?: number;
}
/**
 * Resolve a tool_search request to deferred-tool names (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 pre-keyword-search `select` ARRAY arm is retired (see {@link ToolSearchArgs}) — a stale
 * replayed array argument now resolves like an empty query (nothing matched, honestly).
 */
export declare function resolveToolSearchDetailed(args: ToolSearchArgs, registry: ReadonlyMap<string, DeferredToolInfo>): {
    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, DeferredToolInfo>): string[];
/**
 * Stateless recompute of the activated-tool set from the message log. 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. Two carriers,
 * both deterministic and both replayed here:
 *
 *  ① every prior `tool_search` call's arguments, re-resolved through `resolveToolSearch` — unless that
 *    call's OWN result carries the error flag (a denied/blocked search never reached execute
 *    and staged nothing; a call with no result at all keeps the historic credit);
 *  ② A SUCCESSFUL call on a deferred name, which is an activation in its own right: the
 *    direct lane runs `activate()` BEFORE it invokes the real tool, so a result that came back
 *    without an error flag can only have come from a call that passed the shape gate and activated.
 *    No new carrier had to be minted for this: the toolCall part and its result are already in the
 *    transcript. Before this arm, a direct activation left NO trace at all — the next leg reverted the
 *    tool to a placeholder (losing one turn's schema) and, for the roster-bearing delegation tool, kept
 *    its listing face deferred so the boundary producer stayed silent as well.
 *
 * ② is deliberately keyed on the ERROR FLAG rather than on re-running the shape gate: the error flag is
 * the only signal that distinguishes "the lane ran" from "the call never reached it". Every non-execution
 * outcome the engine can produce for a placeholder call — the teaching rejection (a throw), a policy
 * denial or hook block, an abort, invalid arguments — is flagged as an error, and none of them activated
 * anything. It is an UNDER-approximation in one direction only: an activation whose real tool then failed
 * (or threw) is not counted, so that name reverts to a placeholder and the model re-activates on demand —
 * self-healing, exactly like the keyword-cap note below, never a widening. The shape-INVALID
 * direct call (ruled 2026-08-03) activates too and is flagged as an error, so it lands in that same
 * under-approximated class by construction: within the process the in-memory `active` set carries it, and
 * a replay that loses it costs the corrected call — which succeeds, and is itself carrier ②. The one way to
 * self-declare a positive is a deployment `tool_result` hook that rewrites a rejection's error flag to false,
 * which is the deployment editing its own transcript.
 *
 * 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. Exact-name and `select:`-prefix activations replay unchanged.
 *
 * The retired `{select:[…]}` ARRAY no longer replays: it resolves to nothing here, so those names revert
 * to placeholders on the same self-healing path. This is the READ half of a deliberate split — the write
 * lane (`createToolSearchTool.execute`) REFUSES the dead key loudly, because there a message can still
 * change what the caller does; here it cannot, and erroring would only turn an old transcript into a
 * resume that cannot start. Recompute is total: no shape of persisted arguments makes it throw.
 */
export declare function extractDiscoveredToolNames(messages: ReadonlyArray<AgentMessage>, registry: ReadonlyMap<string, DeferredToolInfo>): 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 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 createToolSearchTool(opts: {
    registry: ReadonlyMap<string, DeferredToolInfo>;
    active: Set<string>;
    rematerialize: (active: ReadonlySet<string>) => Promise<void>;
    /** Listing ride-along on the deferred-tool channel: when an activation newly
     *  materializes a listing-bearing tool (the deferred delegation tool whose description points at an
     *  agent-types listing that the deferred leg's boundary producer never ships), the caller returns
     *  the listing text here and it rides THIS search's own result content — tail of the log, cache
     *  prefix untouched, same channel the activation announcement already uses. */
    listingRide?: (newlyActivated: readonly string[]) => string | undefined;
    /** The names callable DIRECTLY right now (mounted non-deferred + activated),
     *  computed at call time (setTools swaps mutate the surface). Lets the miss note tell the model
     *  "Workflow is already available — call it" instead of a generic not-found that reads as absence
     *  (observed: the model treated the miss as unavailability and only recovered by guessing). */
    mountedNames?: () => ReadonlySet<string>;
    /** Whether the placeholders on this task carry the direct-call lane
     *  (`TaskSpec.deferSelfResolve` not disabled). Selects which of the two teaching postures below is
     *  true for this task; defaults to the direct-lane posture, matching the spec field's own default. */
    directCallEnabled?: boolean;
    /** Run this tool's activation critical section on the CALLER's serializer instead of a
     *  private one. The `active` set and `rematerialize` are shared with the direct-call lane,
     *  which activates from a placeholder's own execute; both lanes must queue on ONE section or a
     *  concurrent batch lets the direct lane stage a name while this section is still pending (and
     *  vice versa, so a rollback here cannot be observed as "already active" there). Absent ⇒ a private
     *  chain, which still gives the standalone factory its own same-instance guarantee. */
    /**
     * Is this deferred name still on the LIVE roster? The registry is a prepare-time snapshot, but the
     * roster is not: an MCP refresh splices a server's name domain out, and a name that was deferred at
     * prepare can be gone. Without this test an exact `select:` still resolves through the stale
     * registry, the activation section adds the name to `active`, the rematerialize "succeeds" while
     * mounting nothing, and the result announces a tool that does not exist — an uncallable name the
     * model has been told is now available. Absent ⇒ every registry name is assumed live (the standalone
     * factory has no roster to consult), which is the prior behavior.
     */
    isMounted?: (name: string) => boolean;
    serializeActivation?: <T>(section: () => Promise<T>) => Promise<T>;
    /** Called at the TAIL of this lane's activation section, after the rollback window has closed — the one point at which the shared active set is committed
     *  for this call. The disclosure census rides it, because a publisher reading that set from OUTSIDE the section (an MCP refresh's rematerialize, which runs
     *  unserialized and after an await) can observe a name a racing activation staged and then rolled back, and announce an activation that never happened.
     *  Absent ⇒ nothing is published. */
    onActivationCommitted?: () => void;
    /** Resolve a deferred name's REAL declared
     *  parameter schema, read from the live roster at call time. Presence switches this face to the
     *  static posture: activation never swaps the placeholder in the tools block, so the result text
     *  BELOW is the schema's only in-context carrier — each matched tool's schema is inlined into the
     *  result (newly-activated AND already-active alike, so a post-compaction re-select recovers it),
     *  and every "full schemas are loaded" claim about the tools block is dropped as false. Absent ⇒
     *  the swap posture (activation rewrites the tools block; prior wording, unchanged). */
    staticSchemaFor?: (name: string) => TSchema | undefined;
}): AgentTool;
