import type { Context } from '../core/context.js'; import type { EventBus } from '../kernel/events.js'; import type { MiddlewareHandler } from '../kernel/pipeline.js'; import type { SessionEventBridge } from '../storage/session-event-bridge.js'; import type { Compactor, CompactReport } from '../types/compactor.js'; import type { ContextWindowAggressiveOn, ContextWindowPolicy } from '../types/context-window.js'; export interface ContextWindowBudgetSnapshot { maxContext: number; inputTokens: number; availableInputTokens: number; remainingInputTokens: number; reservedOutputTokens: number; reservedSafetyTokens: number; load: number; overflowTokens: number; } type CompactionFailureMode = 'throw' | 'throw_on_hard' | 'continue'; interface AutoCompactionOptions { aggressiveOn?: ContextWindowAggressiveOn | undefined; events?: EventBus | undefined; failureMode?: CompactionFailureMode | undefined; policyProvider?: (ctx: Context) => Pick | null | undefined; /** Optional bridge for writing compaction events into the persistent session log. */ sessionBridge?: SessionEventBridge | undefined; /** * Optional callback fired after successful compaction with the report. * Receives the CompactReport so callers can feed the collapsed digest * to memory consolidation, logging, or other side effects. */ onCompact?: ((report: CompactReport) => void) | undefined; } /** * Pipeline middleware that monitors context token load and automatically * triggers compaction when the warn/soft/hard thresholds are crossed. * Runs before the next agent iteration. * * Uses `estimateRequestTokens` for accurate full-request token counting: * messages + systemPrompt + toolDefs. This replaces the previous pattern * of applying an OVERHEAD_FACTOR to a messages-only estimate. */ export declare class AutoCompactionMiddleware { readonly name = "AutoCompaction"; private readonly compactor; /** Deprecated. Kept for backward compat with tests that pass simpleEstimator. */ private readonly _estimator?; private readonly warnThreshold; private readonly softThreshold; private readonly hardThreshold; /** Writable so model-switch can update the denominator without re-registering the middleware. */ private _maxContext; /** * Runtime on/off gate. The middleware is always installed in the pipeline so * auto-compaction can be toggled live from the TUI `/settings` picker; when * disabled the handler is a pass-through. Defaults to enabled. */ private _enabled; private readonly aggressiveOn; private readonly events?; private readonly failureMode; private readonly policyProvider?; private readonly sessionBridge?; private readonly onCompact?; /** * Once a compaction attempt reduces nothing (preserveK protects everything, * no oversized tool_results remain to elide), retrying on every iteration * just spams `compaction.fired` events without making progress. We remember * the no-op and skip until either the pressure level escalates or context * has grown by at least this many tokens since the failed attempt. */ private static readonly NOOP_RETRY_DELTA_TOKENS; /** A tiny positive delta is operationally still a no-op at large context sizes. */ private static readonly MIN_EFFECTIVE_REDUCTION_TOKENS; private static readonly MIN_EFFECTIVE_REDUCTION_RATIO; /** * Calibrated-load floor above which the upper-bound send guard runs. Below * this, even the maximum density under-count factor (2.5×) cannot push the * real token count past the window, so the extra scan is pure waste. Equals * 1 / 2.5 = 0.4. */ private static readonly GUARD_GATE_LOAD; /** Tracks the most recent no-op attempt so we can avoid re-firing per turn. */ private lastNoopAttempt; /** * Cached token estimate from the last handler() invocation. When the * message count and tool count haven't changed since the last estimate * (autonomous idle loops), we skip the expensive O(n) token estimation * and reuse this value. Reset to -1 when the context changes. */ private _cachedTokens; private _cachedMsgCount; private _cachedToolCount; private _cachedRevision; private _cachedSystemRef; private _cachedToolsRef; /** * @param compactor Compactor to use for compaction. * @param maxContext Provider's max context window in tokens. * @param _estimator Deprecated parameter kept for backward compatibility. * The middleware now uses `estimateRequestTokens` internally * for accurate full-request token counting (messages + * systemPrompt + toolDefs). * @param thresholds Threshold fractions (0-1) of maxContext. * @param opts Optional behavior. By default, failures at the * hard threshold throw AGENT_CONTEXT_OVERFLOW so * the agent does not continue into a likely * provider context overflow. Warn/soft failures * still emit compaction.failed and continue. */ constructor(compactor: Compactor, maxContext: number, /** * Optional custom estimator. When omitted (pass `undefined`), the * middleware uses its internal `estimateRequestTokensCalibrated` default — * the right choice for subagents, which want the same per-(provider,model) * calibration as the leader without threading an estimator through. */ _estimator: ((ctx: Context) => number) | undefined, thresholds: { warn: number; soft: number; hard: number; }, optsOrAggressiveOn?: AutoCompactionOptions | ContextWindowAggressiveOn, events?: EventBus | undefined); /** Allow callers (e.g. model-switch in WebUI) to update the context window * denominator when the active model changes. */ setMaxContext(maxContext: number): void; /** Whether auto-compaction is currently active. */ get enabled(): boolean; /** Toggle auto-compaction on a live session (TUI `/settings`). When disabled * the middleware passes every iteration straight through without estimating * tokens or compacting. */ setEnabled(enabled: boolean): void; handler(): MiddlewareHandler; /** * H1: try to read a pre-computed token total from `ctx.lastRequestTokens` * (set by the agent loop's pre-flight or its restash in emitContextPct). * Returns the uncalibrated total when the stash is valid for the current * context shape (positive number, and the message count it was computed * at matches the current one — otherwise tool results have been appended * since and the value is stale). Returns null when missing or stale so * the caller falls back to a fresh walk. */ private tryStashedTokens; /** * Returns true when the previous compaction at the same or higher pressure * level reduced nothing and context has not grown materially since. Prevents * a stuck preserveK window from spamming compaction events every iteration. */ private shouldSkipNoopRetry; private recordAttempt; private compact; /** * Last-resort trim that makes the request structurally fit the window. * Converts the calibrated context budget into a raw message-token budget * (the estimator scale `enforceHardBudget` works in) by subtracting the * system-prompt + tool-definition overhead, then trims message content until * it fits. Targets 95% of the hard line so re-estimation jitter does not * leave it exactly on the boundary. Returns the trim result, or null when * nothing needed trimming. */ private emergencyTrim; /** Preserve-window size from the active policy, defaulting to 6 recent pairs. */ private resolvePreserveK; } export {}; //# sourceMappingURL=auto-compaction-middleware.d.ts.map