import type { Model } from "../../internal/llm.js";
import type { ToolSpec } from "../types.js";
import type { ToolFingerprintInput } from "../cache-break-detector.js";
import { type ResolvedToolSearch, type ToolSearchSetting } from "../tool-search-setting.js";
/**
 * The deferral GATE: which mounted tool names ship as placeholders on a request. Pure
 * registry logic over names and sizes — no harness, no I/O. `tool-disclosure.ts` owns the placeholder
 * and ToolSearch mechanics that act on the set this module decides; `prepare-defer-classify.ts` is the
 * phase that assembles the inputs from the mounted face.
 */
/** The deferral gate's DEFAULT threshold: with no `toolSearch` seat, the candidate set defers when its inlined size reaches this fraction of the model's
 *  context window (that set measured, never the rest of the face). Derived from the seat's own default percent — one default, two units. */
export declare const DEFER_THRESHOLD_FRACTION: number;
/** The typed refusal a `deferMode` outside its declared domain (`"auto"` or absent) raises at the classifier —
 *  a bad value never selects an arm silently. */
export declare const DEFER_MODE_INVALID_CODE = "config.defer_mode_invalid";
/** The deferral gate's verdict — the two deferral lists, what the gate measured, and whether it opened. */
export interface DeferralGate {
    /** The caller's LITERAL list: `TaskSpec.deferTools` names mounted on this face, minus the inline pins.
     *  Deferred on every request, whatever the gate says — an intentional deferral is not a size question. */
    explicit: Set<string>;
    /** The engine's SELF-SELECTED candidates: `ToolSpec.defer === true`, every remote-protocol name, built-ins
     *  that declare their own `defer`, and — under `deferMode: "auto"` — every caller spec; minus the inline
     *  pins and minus `explicit`. Deferred iff the gate opens. */
    gated: Set<string>;
    /** Σ inlined chars (name + description + serialized schema) over `gated` — the bytes the gate could reclaim. */
    gatedChars: number;
    /** `floor(contextTokens × fraction) × CHARS_PER_TOKEN`; the char reading of the token threshold. `0` under `toolSearch: true` and `Infinity` under
     *  `false` — the two boolean arms ARE this number. */
    thresholdChars: number;
    /** Whether `gated` is deferred on this request: `gatedChars ≥ thresholdChars`. */
    open: boolean;
    /** The lowered seat this verdict was measured under ({@link ToolSearchSetting}); always present, since an absent seat resolves to the default `auto`. */
    toolSearch: ResolvedToolSearch;
}
/**
 * Deferral has TWO rules, and only two.
 *
 * ① THE CALLER'S LIST IS LITERAL. `TaskSpec.deferTools` (arriving as `callerDeferNames`, pre-filtered to
 *    mounted wire names, built-ins included) is deferred on every request; `alwaysLoad` (`ToolSpec.alwaysLoad`,
 *    `alwaysLoadNames`) is inlined on every request and is judged FIRST — a pinned name is in neither list
 *    and is never measured. Neither depends on the gate: they are the deployment's stated intent.
 *
 * ② THE ENGINE'S OWN CANDIDATES SIT BEHIND ONE THRESHOLD GATE. The candidates are declared: explicit
 *    `ToolSpec.defer === true` on a caller spec, every REMOTE-PROTOCOL tool name (MCP servers AND A2A peers —
 *    the caller unions them), and `builtinDeferNames` (mounted BUILT-INS that declare their own `defer`; a
 *    built-in ToolSpec never reaches `specs`, so its declaration arrives as a mounted name). `deferMode:
 *    "auto"` WIDENS the candidate set to every caller spec — undeclared caller tools included — and nothing
 *    else: it is not a second gate, and engine built-ins the deployment did not name are candidates under no
 *    setting. The gate decides the set all or nothing: deferred iff `gatedChars ≥ thresholdChars`, where the
 *    threshold is the DEPLOYMENT'S FRACTION of the model's context window (`contextTokens ?? contextWindow`,
 *    in chars at {@link CHARS_PER_TOKEN}) — {@link ToolSearchSetting} lowered by {@link resolveToolSearch},
 *    which is {@link DEFER_THRESHOLD_FRACTION} when no seat is wired. The measure is the candidate set ONLY —
 *    the bytes the gate could reclaim — never the caller's list, the pins, or non-candidates: a request whose
 *    weight sits elsewhere gains nothing from a ToolSearch round-trip, so nothing about it argues for one. A
 *    window of zero reads as a zero threshold (every candidate byte is over budget), so the candidates defer.
 *
 * Invariants: `explicit ∩ gated = ∅`; `deferred = explicit ∪ (open ? gated : ∅)`; a pinned name is in no
 * list; the same inputs always yield the same verdict (a pure function of the request).
 */
export declare function deferralGate(opts: {
    specs: ReadonlyArray<ToolSpec>;
    /** Every namespaced tool a remote protocol materialized for this task (MCP servers AND A2A peers —
     *  the caller unions them). The remote-roster shape (many tools, schemas the deployment does not
     *  control, drift between tasks) is the same for every protocol, so every protocol's names are
     *  candidates by being remote. Only an inline pin (or the caller's own list) takes a name out. */
    protocolToolNames: ReadonlyArray<string>;
    /** The MOUNTED face — the tools whose full definitions the request would carry. The measure reads
     *  each candidate's inlined size from here; a candidate absent from it weighs nothing. */
    fullTools: ReadonlyArray<ToolFingerprintInput>;
    /** `"auto"` widens the candidate set to every caller spec (see above). Any other value refuses. */
    deferMode?: "auto";
    /** The deployment's gate policy for rule ② ({@link ToolSearchSetting}); absent ⇒ the default `auto` at
     *  {@link TOOL_SEARCH_DEFAULT_THRESHOLD_PERCENT}, byte-for-byte the prior behavior. */
    toolSearch?: ToolSearchSetting;
    model: Model;
    /** Rule ① — `TaskSpec.deferTools`: ALREADY-MOUNTED wire names (built-ins included; `ToolSpec.defer`
     *  only reaches caller specs) the caller defers on every request. Caller pre-filters to mounted names. */
    callerDeferNames?: ReadonlyArray<string>;
    /** Rule ② — mounted BUILT-INS that declare their own `defer` (the engine's declaration, not the
     *  caller's), so they are candidates. Caller pre-filters to mounted names. */
    builtinDeferNames?: ReadonlyArray<string>;
    /** Rule ① — inline-pin names (`TaskSpec.alwaysLoadTools` + the MCP tools whose server declared
     *  `_meta["anthropic/alwaysLoad"]`, caller-collected; A2A has no such per-skill declaration, so the
     *  task-level list is a peer's only inline-keep channel): exempt from BOTH lists.
     *  `ToolSpec.alwaysLoad` rides the specs directly and needs no entry here. */
    alwaysLoadNames?: ReadonlyArray<string>;
}): DeferralGate;
/**
 * Decide which tool names are deferred: the caller's literal list, plus the gated candidates when the gate
 * opens. THIS CONTRACT (with {@link deferralGate}) IS THE SINGLE AUTHORITY on the deferral sources and the
 * gate; the `TaskSpec` / `RunnerDeps` JSDoc points here.
 */
export declare function classifyDeferred(opts: Parameters<typeof deferralGate>[0]): Set<string>;
/** The SAME fold, with the verdict it folded — a disclosure that reports the policy must read the one the gate measured under, never a later re-resolution. */
export declare function classifyDeferredWithGate(opts: Parameters<typeof deferralGate>[0]): {
    deferred: Set<string>;
    gate: DeferralGate;
};
