/** * Context compaction for long sessions. * * Pure functions for compaction logic. The session manager handles I/O, * and after compaction the session is reloaded. */ import type { AgentMessage, StreamFn, ThinkingLevel } from "../../../agent-core/index.js"; import type { Model, Usage } from "../../../ai/index.js"; import { type SessionEntry } from "../session-manager.js"; import { type FileOperations } from "./utils.js"; import { type ConversationCompactionPolicy, type ManualCompactionPolicy } from "./policy.js"; /** Details stored in CompactionEntry.details for file tracking */ export interface CompactionDetails { readFiles: string[]; modifiedFiles: string[]; } export type CompactionSkipReason = "non_shrinking"; export interface CompactionTokenSourceBucket { type: "role" | "tool" | "source"; name: string; tokens: number; } /** Result from compact() - SessionManager adds uuid/parentUuid when saving */ export interface CompactionResult { summary: string; firstKeptEntryId: string; tokensBefore: number; /** Estimated tokens in the raw span replaced by the compaction summary. */ tokensCompacted?: number; /** Estimated net tokens removed by replacing raw span tokens with summary tokens. */ tokensRemoved?: number; /** Estimated tokens added back as the compaction summary. */ summaryTokens?: number; /** Estimated percentage of compacted raw tokens removed by the summary. */ reductionPercent?: number; /** 1-based compaction ordinal within the current session branch. */ compactionNumber?: number; /** Largest estimated token buckets from the span being compacted. */ largestTokenSources?: CompactionTokenSourceBucket[]; /** True when the compaction was intentionally skipped and no history was pruned. */ skipped?: boolean; /** Machine-readable reason for a skipped compaction. */ skipReason?: CompactionSkipReason; /** Human-readable skipped/failure message for UI and persisted history. */ message?: string; /** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */ details?: T; } export type CompactionSettings = ConversationCompactionPolicy; export declare const DEFAULT_COMPACTION_SETTINGS: CompactionSettings; export declare const MANUAL_COMPACTION_KEEP_RECENT_TOKENS: number; /** * Manual/model-visible compaction is an explicit phase-boundary action. Keep a * smaller recent suffix than automatic threshold compaction so a ~20k-token * default session does not keep the whole eligible branch and compact nothing. */ export declare function getManualCompactionSettings(settings: CompactionSettings, manualPolicy?: ManualCompactionPolicy): CompactionSettings; /** * Calculate total context tokens from usage. * Uses the native totalTokens field when available, falls back to computing from components. */ export declare function calculateContextTokens(usage: Usage): number; /** * Find the last non-aborted assistant message usage from session entries. */ export declare function getLastAssistantUsage(entries: SessionEntry[]): Usage | undefined; export interface ContextUsageEstimate { tokens: number; usageTokens: number; trailingTokens: number; lastUsageIndex: number | null; } /** * Estimate context tokens from messages, using the last assistant usage when available. * If there are messages after the last usage, estimate their tokens with estimateTokens. */ export declare function estimateContextTokens(messages: AgentMessage[]): ContextUsageEstimate; /** * Check if compaction should trigger based on context usage. */ export declare function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean; /** * Estimate token count for a message using chars/4 heuristic. * This is conservative (overestimates tokens). */ export declare function estimateTokens(message: AgentMessage): number; /** * Find the user message (or bashExecution) that starts the turn containing the given entry index. * Returns -1 if no turn start found before the index. * BashExecutionMessage is treated like a user message for turn boundaries. */ export declare function findTurnStartIndex(entries: SessionEntry[], entryIndex: number, startIndex: number): number; export interface CutPointResult { /** Index of first entry to keep */ firstKeptEntryIndex: number; /** Index of user message that starts the turn being split, or -1 if not splitting */ turnStartIndex: number; /** Whether this cut splits a turn (cut point is not a user message) */ isSplitTurn: boolean; } export interface FindCutPointOptions { /** * Explicit phase-boundary compaction should prefer the newest safe boundary * over retaining a large just-finished turn as the recent raw suffix. */ phaseBoundary?: boolean; /** * When provided, use this entry id as a fixed cut point instead of walking * backwards from the newest entry. This lets request-time adapters freeze a * previously-computed cut without regenerating the summary. */ anchorEntryId?: string; /** * When true, ensure the most recent user message is never folded into the * compaction summary — the cut point is pulled back to (or before) the * latest user message so it survives verbatim. This protects the current * task/brief from being reduced to a truncated excerpt mid-run. */ pinLatestUserMessage?: boolean; } /** * Find the cut point in session entries that keeps approximately `keepRecentTokens`. * * Algorithm: Walk backwards from newest, accumulating estimated message sizes. * Stop when we've accumulated >= keepRecentTokens. Cut at that point. * * Can cut at user OR assistant messages (never tool results). When cutting at an * assistant message with tool calls, its tool results come after and will be kept. * * Returns CutPointResult with: * - firstKeptEntryIndex: the entry index to start keeping from * - turnStartIndex: if cutting mid-turn, the user message that started that turn * - isSplitTurn: whether we're cutting in the middle of a turn * * Only considers entries between `startIndex` and `endIndex` (exclusive). */ export declare function findCutPoint(entries: SessionEntry[], startIndex: number, endIndex: number, keepRecentTokens: number, options?: FindCutPointOptions): CutPointResult; /** * Generate a summary of the conversation using the LLM. * If previousSummary is provided, uses the update prompt to merge. * * @deprecated The active coding-agent session path uses deterministic DCP-lite * (`compactDcpLite`) plus `session_before_compact` hook details. This legacy * LLM summarizer remains exported for compatibility with older callers only. */ export declare function generateSummary(currentMessages: AgentMessage[], model: Model, reserveTokens: number, apiKey: string | undefined, headers?: Record, signal?: AbortSignal, customInstructions?: string, previousSummary?: string, thinkingLevel?: ThinkingLevel, streamFn?: StreamFn): Promise; export interface CompactionPreparation { /** UUID of first entry to keep */ firstKeptEntryId: string; /** Messages that will be summarized and discarded */ messagesToSummarize: AgentMessage[]; /** Messages that will be turned into turn prefix summary (if splitting) */ turnPrefixMessages: AgentMessage[]; /** Whether this is a split turn (cut point in middle of turn) */ isSplitTurn: boolean; tokensBefore: number; /** Summary from previous compaction, for iterative update */ previousSummary?: string; /** File operations extracted from messagesToSummarize */ fileOps: FileOperations; /** Compaction settions from settings.jsonl */ settings: CompactionSettings; /** 1-based compaction ordinal within the current session branch. */ compactionNumber: number; } export interface PrepareCompactionOptions { /** Prefer a caller-provided phase boundary over the usual recent-token suffix. */ phaseBoundary?: boolean; /** * Use a fixed entry id as the cut point. The caller owns the anchor and is * responsible for validating that it still belongs to the current branch. */ anchorEntryId?: string; /** * Protect the most recent user message from being folded into the compaction * summary. Useful for isolated agent loops (subagents) where the latest user * message carries the task/brief and must survive verbatim mid-run. */ pinLatestUserMessage?: boolean; } export declare function prepareCompaction(pathEntries: SessionEntry[], settings: CompactionSettings, options?: PrepareCompactionOptions): CompactionPreparation | undefined; export declare function estimateTextTokens(text: string): number; export declare function estimateCompactionTokenMetrics(preparation: Pick & Partial>, summary: string): Pick; /** * Deduplicate repeated tool calls in a message array by keeping only the * most recent occurrence of each (tool, args) pair. * * Strategy: * - **Idempotent-read tools** (read, grep, glob): same (name, args) → keep last only. * These tools return the same content for the same arguments. * - **Output-dependent tools** (bash): same (name, args, output) → keep last only. * Two bash calls with same command but different output are NOT duplicates. * - **Mutating tools** (edit, write): never deduplicated. Chronological ordering * of mutations matters for correctness. * - **Unknown tools** (extensions, MCP, custom): never deduplicated. Conservative * by default — only tools in the known sets above participate. * * Deduplication removes both the ToolCall block from the assistant message * and the corresponding ToolResultMessage from the array. * * Recalculated each time compaction runs — prompt cache is only impacted * alongside compression, not on every turn. */ export declare function deduplicateToolCalls(messages: AgentMessage[]): AgentMessage[]; /** * Generate summaries for compaction using prepared data. * Returns CompactionResult - SessionManager adds uuid/parentUuid when saving. * * @param preparation - Pre-calculated preparation from prepareCompaction() * @param customInstructions - Optional custom focus for the summary * @deprecated The active AgentSession compaction flow uses deterministic * DCP-lite (`compactDcpLite`) through `_buildDcpCompactionResult`. Keep this * exported legacy LLM fallback for SDK/harness compatibility only. */ export declare function compact(preparation: CompactionPreparation, model: Model, apiKey: string | undefined, headers?: Record, customInstructions?: string, signal?: AbortSignal, thinkingLevel?: ThinkingLevel, streamFn?: StreamFn): Promise; //# sourceMappingURL=compaction.d.ts.map