import type { AgentMessage } from "../internal/harness.js";
import type { Model } from "../internal/llm.js";
import { type ContextWindowOverlay } from "./context-settings.js";
/**
 * Small-window FLOOR fraction of the clearStale/compaction frontier (see {@link contextEditFrontier},
 * which is the single source for both the clearStale budget and the compaction-threshold clamp).
 * CC's fixed-deduction trigger geometry only exists for ≥100k autocompact windows (its configured-
 * window floor is 100000), and `W − 33000` goes degenerate below ~47k — small windows keep the
 * historical fractional trigger instead.
 */
export declare const EDIT_FRACTION = 0.7;
/**
 * CC-parity trigger geometry (verified against CC 2.1.207): the reactive autocompact trigger sits a
 * FIXED DISTANCE below the window, not at a fixed fraction —
 *   effectiveWindow = autocompactWindow − min(maxOutputTokens, 20000)   (CC `Nye`, cap `uMd`)
 *   trigger         = effectiveWindow − 13000                           (CC `dSo`, buffer `rMd`)
 * i.e. trigger = W − 33000 for every model whose max output is ≥ 20k: a 200k window triggers at
 * 167000 (83.5% of nominal), a 1M window at 967000.
 *
 * 1M DIVERGENCE, stated as fact rather than as an equivalence (anchoring re-check 2026-08-23): CC
 * carries a MODEL-DEFAULT autocompact-window table (2.1.223 `j3u` :242918 — `claude-sonnet-5`
 * default 967000, and since 223 a surface dimension: `remote_cowork` / `local-agent` 500000), so a
 * 1M sonnet-5 gets 967000 as its window and 934000 as its trigger WITHOUT anyone configuring
 * anything. We ship no such table: a model declaring only `contextWindow: 1e6` triggers at 967000
 * here, 33000 LATER than CC. `model.autoCompactTokens = 967000` reproduces CC's number — but that
 * is an EMBEDDER action against CC's product default, not the same posture, and the choice not to
 * follow 934000 is deliberate (design/146 §1.3 argues against living on that thin a cushion). The
 * knob itself is real and unchanged: `autoCompactTokens` lowers only this trigger-side window while
 * the guard and physical request budgeting stay on the 1M window — CC's dual-window shape. We take the
 * 20000 cap branch unconditionally (flat 33000), i.e. the `min(maxOutputTokens, 20000)` branch is
 * deliberately NOT ported: the deduction is exact parity only for models with max output ≥ 20k. A
 * model with a smaller max output would deduct less in CC (trigger LATER); we deduct the full
 * 20000 and so trigger EARLIER there — the conservative direction (early compaction is bounded
 * cost; a late trigger risks the window). It also keeps the frontier a pure function of the
 * window — single source for the death-band clamp below.
 */
export declare const CONTEXT_OUTPUT_RESERVE_TOKENS = 20000;
export declare const COMPACTION_TRIGGER_BUFFER_TOKENS = 13000;
/**
 * The clearStale/compaction frontier for a window — SINGLE SOURCE for two coupled points
 * (design/64 §26.4 fix 2): the budget at which the request-layer stale-result clearing engages
 * ({@link editBudget}) AND the ceiling the compaction threshold is clamped to
 * (`sanitizeCompactionSettings`). Deriving both from one function keeps `compaction ≡ clearStale`
 * an invariant: a threshold above the clearing frontier would put every request between the two
 * points on a moving clearing frontier that breaks the prefix cache (~frontier re-prefilled PER
 * REQUEST — the "death band"). `max()` keeps the frontier monotonic in the window; exact
 * crossover (integer domain, pinned by test): the fractional floor strictly wins through
 * W = 109996, the two legs are EQUAL for W = 109997…110000 (floor(0.7W) meets W − 33000 there),
 * and the fixed deduction strictly wins from W = 110001 up.
 */
export declare function contextEditFrontier(window: number): number;
/**
 * The `RunnerDeps.microCompact.machine` vocabulary — ONE clearing machine, switchable:
 *
 *  - `"cc"` (default): the CC 2.1.223 `yId` machine — keep 5, a ≥20000-token minimum-savings gate
 *    (below it the pass is a byte-identical no-op), ONE deep clear of everything beyond the keep
 *    window, the window base counts already-cleared/offloaded occurrences (they hold their keep
 *    slots), and CC's plain marker bytes.
 *  - `"off"`: no proactive frontier clearing; the machine gets its one shot at the blocking point
 *    of the guard chain instead (trace trigger `"blocking"`).
 *
 * WHOLE-MACHINE CROSS-CHECK against the 2.1.250 corpus (design/374 slice 4 — the "the knob number
 * was copied, the machine was not" disease is checked by naming the machine's parts, not the one
 * number). CC's four load-bearing parts, each against its 250 coordinate, all verified present here:
 *   ① the ≥20000-token minimum-savings gate — {@link MIN_CLEAR_SAVINGS_TOKENS} vs `Mnn`
 *      (cli250.js:41714, `BBn`: `if (tokensSaved < Mnn) return null`). CC gates TWICE (execution +
 *      before asking the server to reject); sema has one point because the "ask for the rejection"
 *      half needs a cooperating server (see ④).
 *   ② the nine-member clearable whitelist — {@link COMPACTABLE_TOOLS} vs `eQt` (cli250.js:41714),
 *      re-resolved member-by-member off the 250 corpus, byte-identical (see that constant).
 *   ③ cleared-but-recoverable — CC replaces the content with a POINTER, not a void
 *      (`<persisted-output>Tool result saved to: {path}\n\nUse Read to view</persisted-output>`,
 *      cli250.js:40044), which is what makes a keep window of 5 survivable at all. Ours is the same
 *      construction in this engine's carrier: the full text is offloaded first and the marker
 *      carries the store ref ({@link ContextEditOptions.offload} → {@link clearOneResult}), read
 *      back through ReadToolResult instead of a filesystem path (registered divergence D-9). The
 *      runner wires it on BOTH arms by default — the frontier pass and the MC-R rejection arm share
 *      one persist closure — so the shipped default clears to a pointer, never to a dead end.
 *   ④ the TRIGGER — CC's client machine fires ONLY on the server's `context_hint` 422/424 refusal
 *      (cli250.js:58375; `tengu_time_based_microcompact` is a historical event name, its `trigger`
 *      is the constant `"context_hint"`). Under BYOM there is no such server, so the ask half is
 *      unportable and the refusal half is aligned on wire truth (the MC-R arm classifies the
 *      provider's own context-overflow refusal) — registered divergence D-3. sema additionally
 *      keeps a PROACTIVE frontier trigger CC's client does not have, anchored to Anthropic's
 *      platform context-management policy rather than to CC-CLI — registered divergence D-1, and
 *      re-stated at {@link clearStaleToolResults}'s ANCHOR paragraph.
 *
 * A closed set (`docs/CLOSED-SETS.md`): the knob door quotes it, so a word outside it is refused by
 * name rather than folded to the default.
 */
export declare const MICRO_COMPACT_MACHINES: readonly ["off", "cc"];
export type MicroCompactMachine = (typeof MICRO_COMPACT_MACHINES)[number];
/**
 * How many most-recent CANDIDATE tool results keep their content when the stale-result pass runs:
 * 5 (CC 2.1.223 `uAp`; 2.1.250 `var k = 5` at the call site cli250.js:58375, and 2.1.245 carries
 * the same value — zero drift across 245→250; same value again on CC 88's `timeBasedMCConfig`, i.e.
 * stable across all three corpus generations, and it is a hard-coded constant upstream, not a
 * setting). The #384 adjudication adopted 5 CONDITIONAL on the whole machine coming with it (20k
 * gate + deep clear + re-ordered backstops); the machine carries that condition (§3.5: the gate
 * refuses a no-win clear and the in-turn forced compaction owns the request instead of a
 * message-dropping trim).
 */
export declare const CC_DEFAULT_KEEP_RECENT_TOOL_RESULTS = 5;
/** design/374 §3.1 — CC's minimum-savings gate (`vUs` :397773; 2.1.250 `Mnn` cli250.js:41714, same
 *  value): the cc machine refuses to clear at
 *  all unless the structural estimate of what WOULD be cleared reaches this many tokens. Rationale
 *  (independent of "CC does it"): every clear breaks the provider prefix cache once, so a clear that
 *  saves less than the re-prefill it causes is a net loss; the gate prices that trade. */
export declare const MIN_CLEAR_SAVINGS_TOKENS = 20000;
/** Minimum kept results — CC `EUs` :397710 `Math.max(1, keepRecent)`. See
 *  {@link ContextEditOptions.keepRecentToolResults} for why both degenerate ends are unusable. */
export declare const MIN_KEEP_RECENT_TOOL_RESULTS = 1;
export interface ContextEditOptions {
    /** Start clearing once estimated context tokens exceed this. */
    budgetTokens: number;
    /**
     * Fired AT MOST ONCE per pass, only when the pass cleared at least one result: the cleared
     * indexes (into the returned array) and the pass's savings estimate (the structural
     * minimum-savings estimate it gated on). The
     * request-projection ledger (design/374 §3.2.6) records cleared occurrences through this seat —
     * observation only, a throwing callback is swallowed and never fails the request build.
     */
    onCleared?: (pass: {
        clears: ReadonlyArray<{
            index: number;
        }>;
        tokensSavedEstimate: number;
    }) => void;
    /**
     * Always keep the content of this many most-recent CANDIDATE tool results (candidates = results
     * from {@link compactableTools}). Default {@link CC_DEFAULT_KEEP_RECENT_TOOL_RESULTS} = 5.
     *
     * Floored at 1, which IS CC's rule (`EUs` `Math.max(1, keepRecent)`), whose own source note gives
     * the reason: 0 leaves the model with zero working tool context, and in CC's `slice(-0)` spelling
     * it degenerates the other way into keeping everything — two unusable extremes for one value.
     * Ours degenerated identically (a 0 or negative `keep` cleared EVERY candidate). Values that are
     * not a non-negative safe integer are REFUSED rather than floored: a NaN used to make this whole
     * defense silently inert (`slice(0, NaN)` = clear nothing), which is the one outcome a context
     * defense must never reach quietly, and there is no config door upstream to announce at — this
     * option only ever arrives as a direct argument from an embedder.
     */
    keepRecentToolResults?: number;
    /**
     * roadmap #6② (CC contentReplacementState parity): when set, a result's FULL TEXT is offloaded to
     * the store before its content is cleared, and the marker carries the ReadToolResult ref — a
     * cleared result becomes pageable instead of gone-from-view. `persist` must be idempotent per ref
     * (the store's write-once put) because clearing re-runs on every request build. Absent ⇒ the bare
     * marker (pre-#6 behavior; the durable session still holds the full result either way).
     */
    offload?: {
        /** Write-once persist; the returned ref goes into the marker. Sync (in-memory) or async stores both fit. */
        persist: (toolCallId: string, fullText: string) => string;
    };
    /**
     * design/123 D3 (= fable-M5) — ANCHORED trigger: `estimateContextTokens(messages,
     * charsPerToken).tokens` for the SAME message array. When set, the trigger gate reads this
     * usage-anchored value instead of the pure structural sum (which underestimates code by ~25% and
     * CJK by 60–75% — the defense would self-certify and pass over-window requests). The minimum-
     * savings gate stays CC's structural form either way (design/374 D-5). Absent ⇒ the structural
     * sum triggers.
     */
    anchoredTotalTokens?: number;
    /** design/123 D2 — structural coefficient (`Model.charsPerToken`); default 3 (design/374 slice 4;
     *  it was 4 before). Note this does NOT reach the ≥20k gate, which is CC-exactly
     *  hard-coded to 4 — see {@link structuralClearSavings}. */
    charsPerToken?: number;
    /**
     * checklist #54 — which tools' results are clearable. Defaults to {@link COMPACTABLE_TOOLS}
     * (CC microCompact.ts:41-50). A result whose `toolName` is not in the set is NEVER cleared by
     * this pass, no matter how far over budget the request is — expensive subagent reports and
     * registry state are compaction's job, not a blanking marker's.
     */
    compactableTools?: ReadonlySet<string>;
    /** The wire name a result's recorded tool name resolves to on the leg's roster (a result is clearable by the tool the
     *  call ran, not the spelling the model used). Absent ⇒ the recorded name. */
    toolNameOf?: (recorded: string) => string;
    /**
     * design/374 (RB-212 durable half): the PRE-CAP counterpart array of `messages`, position-aligned
     * (the pipeline's aggregate caps replace in place, never drop). When present, the machine
     * composes each clear's marker + offload text from `clearSource[idx]` — the same occurrence
     * bytes the cleared-projection ledger repaints the marker onto at the next session rebuild — so
     * the durable marker discloses the occurrence's REAL media and the page-back ref resolves to its
     * real text rather than to a cap-stripped copy. An entry that is not a tool result, or that carries a different `toolCallId` than the
     * target, is ignored per index (composition falls back to the projection copy — a misaligned
     * source must never mis-attribute content).
     */
    clearSource?: ReadonlyArray<AgentMessage>;
}
/**
 * audit A-1 (view-level heal for HISTORICAL pollution): drop synthetic EMPTY failure assistants —
 * stopReason aborted/error with no tool calls and no non-whitespace text/thinking. Sessions persisted
 * before 1.92.0 may carry one parked between an interrupted tool batch and its reconcile tool_results
 * (`A(tu×3), TR1, TR2, A(aborted,""), TR3`) — an API-invalid sequence on strict providers, and
 * append-only sessions cannot be rewritten. Dropping the junk from the REQUEST VIEW restores a legal
 * transcript (TR3 rejoins its batch). Deterministic ⇒ byte-stable per request ⇒ prefix-cache safe.
 * New sessions never contain these (the loop exits cleanly on abort; the harness skips persisting them).
 * #506 ㋐ did NOT re-open this class: the API-failure record the harness now writes carries `API Error: …`
 * TEXT, so it is not an empty failure assistant and this heal correctly leaves it alone. That record is
 * excluded one layer later, at `convertToLlm`, by its own three-key identity (`isSyntheticApiErrorMessage`).
 */
export declare function dropEmptyFailureAssistants(messages: AgentMessage[]): AgentMessage[];
export declare function clearStaleToolResults(messages: AgentMessage[], opts: ContextEditOptions): AgentMessage[];
/** design/374 slice 1b (X5) — the resolved trigger-side window and whether the clamp repaired it. */
export interface TriggerWindowResolution {
    /** The effective trigger-side window: `autoCompactTokens ?? contextTokens ?? contextWindow`,
     *  CLAMPED to the physical window when the declared autocompact window exceeds it. */
    window: number;
    /** True = the declared `autoCompactTokens` was ABOVE the physical window and got clamped down —
     *  a bad deployment value the caller should announce loudly (坏值响亮度默认律). */
    clamped: boolean;
    /** Present on the clamped arm: the declared (refused) value and the physical window it was
     *  clamped to, for the announcement text. */
    declaredAutoCompactTokens?: number;
    physicalWindow?: number;
}
/**
 * design/374 slice 1b (X5, r2-M⑤) — SINGLE SOURCE for the trigger-side window chain
 * (`autoCompactTokens ?? contextTokens ?? contextWindow`), now with the inversion clamp:
 * `autoCompactTokens` is a TRIGGER-side lowering knob (dual-window semantics — the guard trim and
 * physical request budgeting stay on `contextTokens ?? contextWindow`), so a declared value ABOVE
 * the physical window inverts the whole geometry: the frontier (W−33000 of the declared window)
 * lands HIGHER than the guard (physical−23000), every "the frontier machine runs first, the guard
 * only backstops" argument silently reverses (967k declared vs 177k physical is the recorded
 * counterexample), and the misconfiguration announces nothing. Clamp = `min(declared, physical)`,
 * reported via `clamped` so the ONE announce point (prepare) can be loud about it. Every consumer
 * of the chain (editBudget here / maybeCompact's window derivation / the compaction-window-hazard
 * estimate / runtask's prefix-overhead disclosure) reads THIS helper — a fifth copy of the `??`
 * chain is the drift this function exists to end. Non-finite/≤0 declared values keep their historic
 * meaning (each consumer's own unknown-window sentinel), deliberately untouched here.
 */
export declare function resolveTriggerWindow(model: Model, overlay?: ContextWindowOverlay): TriggerWindowResolution;
/** The token budget at which context-editing begins for a model (below the harder guard trim).
 *  = {@link contextEditFrontier} of the model's AUTOCOMPACT window (CC-parity fixed deduction with
 *  a 0.7 small-window floor) — the same point the compaction threshold is clamped to. Dual-window
 *  semantics: `autoCompactTokens` (when declared) feeds this trigger-side geometry only; the guard
 *  trim (`guardBudget`) stays on the physical window — which is why the window is read through
 *  {@link resolveTriggerWindow}: an autocompact window declared ABOVE the physical one would put
 *  this frontier above the guard (inverted geometry), so it is clamped there (slice 1b, X5). */
export declare function editBudget(model: Model, overlay?: ContextWindowOverlay): number;
/** Content fingerprint of an occurrence — a sha-256 digest over the FULL canonical content form
 *  (per block: type + complete text bytes, and for non-text blocks the mime type + payload length
 *  + a payload head), replay's mis-paint guard (adversarial rounds 1+2): ordinal mapping alone
 *  cannot see an equal-size cut-and-append churn inside a same-(id,timestamp) group — the mapped
 *  slot may hold a DIFFERENT occurrence, and painting it would clear content that was never
 *  cleared. r2 rejected the first cut's {chars, head-64} pair (same-prefix/same-length tails and
 *  media-only churn collide); the digest covers whole text bytes and block structure. A non-marker
 *  replay target must match or the entry prunes (full text returns — always the safe direction).
 *  Content-identical occurrences remain indistinguishable by construction and paint harmlessly. */
export type OccurrenceFingerprint = string;
export declare function fingerprintToolResultOccurrence(m: AgentMessage): OccurrenceFingerprint;
/** One recorded clear: the exact marker bytes to re-apply, the occurrence-group size at record
 *  time (the shrink-mapping input — see the lifecycle note above), and the original content's
 *  {@link OccurrenceFingerprint} (the mis-paint guard). */
export interface ClearedOccurrence {
    marker: string;
    groupCount: number;
    fp: OccurrenceFingerprint;
}
/** The per-task ledger. Keys are OPAQUE occurrence coordinates minted by
 *  {@link replayClearedProjection} / {@link OccurrenceIndex} — derived per view, never persisted. */
export interface ClearedProjectionLedger {
    entries: Map<string, ClearedOccurrence>;
}
export declare function createClearedProjectionLedger(): ClearedProjectionLedger;
/** One minted occurrence coordinate: the opaque ledger key, the group size at mint, the
 *  INDEXED-view fingerprint, and (when the index can hand it back) the indexed-view occurrence
 *  MESSAGE itself — the durable-composition seat (RB-212 durable half): a ledger-bound clear
 *  composes its marker + offload text from this occurrence, never from a cap-transformed copy, so
 *  the recorded marker discloses the occurrence's real media and the page-back ref resolves to its
 *  real text (the same bytes the replay repaints the marker onto). */
export interface OccurrenceKey {
    key: string;
    groupCount: number;
    fp: OccurrenceFingerprint;
    occurrence?: AgentMessage;
}
/** Occurrence coordinates for ONE request view (minted fresh per view — see the ledger note). */
export interface OccurrenceIndex {
    /** Key the tool-result occurrence at this index of the indexed array. `fp` is the occurrence's
     *  fingerprint IN THE INDEXED (pre-cap) view — the same bytes the next session rebuild presents,
     *  which is what the replay guard compares against (r2: fingerprinting the post-cap projection
     *  instead made an MC-R clear of a cap-transformed result prune ITSELF on the retry build — an
     *  advertised clear that never reached the wire). */
    keyAt(index: number): OccurrenceKey | undefined;
    /** Key a tool-result by OBJECT identity against the indexed view (the MC-R seat: the rejected
     *  projection reuses the view's message objects for everything the caps did not replace); falls
     *  back to the unambiguous single-member (id, timestamp) group for cap-replaced copies, and
     *  answers undefined — "not clearable via the ledger" — for anything ambiguous. The returned
     *  `fp` (and `occurrence`) are ALWAYS of the indexed-view occurrence (never of a cap-replaced
     *  copy the caller holds). */
    keyOf(m: AgentMessage): OccurrenceKey | undefined;
}
/** The MC-R planner's verdict — either the entries to record (with the pass's gated savings) or a
 *  typed decline the caller reports on the `context.mc_null` observation face. */
export type RejectionClearPlan = {
    cleared: Array<{
        key: string;
        groupCount: number;
        marker: string;
        fp: OccurrenceFingerprint;
    }>;
    tokensSavedEstimate: number;
} | {
    declined: "no_candidates" | "below_min_savings";
};
/**
 * design/374 §3.2 (slice 2) — plan the MC-R rejection clear over the REJECTED projection: the same
 * machine (keep 5 default, window base counts cleared occurrences, five-form exclusion, Sby
 * savings, ≥20k gate) with the TRIGGER replaced by the rejection itself — the provider already said
 * the input does not fit, so there is no budget coordinate to compare against (an enabled MC-R runs
 * even with the frontier machine `"off"`).
 * `keyOf` threads occurrence identity (the ledger is the ONLY way a rejection-arm clear survives
 * the session rebuild — §1.2's adoption chain discards the in-memory view); a candidate the index
 * cannot key is skipped from BOTH the clear set and the savings (never promise savings the ledger
 * cannot deliver). Marker composition (offload persist included) runs only on the accept path —
 * a declined plan has zero side effects.
 */
export declare function planRejectionClears(messages: AgentMessage[], opts: {
    keyOf: OccurrenceIndex["keyOf"];
    offload?: ContextEditOptions["offload"];
    compactableTools?: ReadonlySet<string>;
    toolNameOf?: (recorded: string) => string;
    keepRecentToolResults?: number;
}): RejectionClearPlan;
/**
 * Replay the ledger onto a freshly rebuilt request view (design/374 §3.2.6, r2-M⑩: positioned
 * AFTER the heal, BEFORE the aggregate caps — so every estimator, cap and trigger downstream
 * accounts the REPLAYED view, and a replayed marker never re-enters the cap store or re-emits a
 * cap frame). Also the ledger's intersection point: entries that no longer resolve are pruned, and
 * surviving entries are RE-KEYED to the current view's coordinates. Returns the SAME array instance
 * when nothing changed (reference inequality is the pipeline's lossy-change signal), plus the
 * view's occurrence index for the record/MC-R seats.
 */
export declare function replayClearedProjection(messages: AgentMessage[], ledger: ClearedProjectionLedger): {
    messages: AgentMessage[];
    index: OccurrenceIndex;
};
