import type { Message } from '../types/message/index.js'; /** * Why the runtime is asking for a shorter history. * * The two cases want different behaviour and a reducer that cannot tell * them apart has to guess. `'threshold'` is speculative — the estimate says * the window is filling, nothing has failed, and declining costs only some * headroom. `'overflow'` is the provider having already rejected the * prompt: declining there ends the turn. */ export type ContextReductionReason = 'threshold' | 'overflow'; /** Everything a reducer needs to decide, and nothing it cannot act on. */ export interface ContextReduction { /** The live history, oldest first. Treat as read-only and return a new array. */ readonly messages: readonly Message[]; readonly reason: ContextReductionReason; /** Estimated size of `messages`. Derived from the provider's last prompt count when there is one, else measured. */ readonly estimatedTokens: number; /** What it has to fit in. Resolved from the model when the host did not say. */ readonly contextWindowTokens: number; /** The model this turn is calling, for a reducer that varies by model. */ readonly model: string; /** The turn's configured recent-window size, as a starting point. */ readonly keepRecentMessages: number; } /** * Replace the turn's history with a shorter one. * * Returning `undefined` means "I could not shorten this" and is a first-class * answer, not a failure — on `'threshold'` the turn simply continues, and on * `'overflow'` it fails with an irreducible-prompt error instead of retrying * a prompt nothing changed. That is deliberate: a reducer that returned the * input unchanged while reporting success would send the same rejected * prompt again and burn a call to learn the same thing. * * A reducer OWNS reduction for the turn it governs. The built-in structured * pass — LLM-verified summarization, stale tool-result clearing, working * state slots — does not also run, because two mechanisms editing the same * history in one pass cannot both be reasoned about. * * Invariants a reducer is expected to keep, all three enforced by the * built-in one and none of them checkable from here: * * 1. The leading system messages stay. They carry the system prompt and the * working-memory slot; dropping them changes who the agent is. * 2. `tool_use` and its `tool_result` stay together. A split pair is a 400 * from the provider, so a reducer that splits one turns a context problem * into a dead turn. {@link findSafeTrimIndex} is exported for this. * 3. Messages marked `retain` survive. That marker is how a caller says a * fact in the middle of the conversation outranks recency. */ export type ContextReducer = (reduction: ContextReduction) => readonly Message[] | undefined | Promise; export interface SlidingWindowOptions { /** * How many trailing messages to keep. Defaults to the turn's configured * `keepRecentMessages`, so the same knob governs both strategies. */ readonly keepRecentMessages?: number; /** * Fraction of the window to keep when relieving an overflow, applied to * the recent count. Defaults to 0.5. * * An overflow means the ordinary window was already too big, so cutting * to the same size again would shed nothing and the caller would retry * an identical prompt. */ readonly overflowFactor?: number; } /** * Keep the last N turns, drop what precedes them, summarize nothing. * * The honest counterpart to the structured strategy rather than a lesser * version of it. Structured compaction pays a summarization call and * paraphrases the agent's own reasoning to buy context; this pays nothing * and admits the older history is gone. For a long-running agent whose * state lives outside the transcript — a task queue, a file it keeps * editing, a working-memory block the host renders each turn — that is the * better trade, and the paraphrase was only ever cost. * * Until now `strategy: 'sliding-window'` was accepted by the config schema * and then ignored: the runtime asked only whether the strategy was * `'disabled'`, so choosing the cheap non-LLM path silently ran the * expensive LLM one. This is the implementation that name always claimed. */ export declare function createSlidingWindowReducer(options?: SlidingWindowOptions): ContextReducer; //# sourceMappingURL=reducer.d.ts.map