export interface CompactionMessage { role: string; content?: any; toolName?: string; toolCallId?: string; isError?: boolean; } export interface SummarizerRequest { prompt: string; signal?: AbortSignal; } export interface SummarizerResponse { text: string; usage?: unknown; } export interface HistoryCompactionInput { messages: CompactionMessage[]; firstKeptEntryId: string; tokensBefore: number; signal?: AbortSignal; } export interface HistoryCompactionResult { summary: string; firstKeptEntryId: string; tokensBefore: number; usage?: unknown; } export interface HistoryCompactor { compact(input: HistoryCompactionInput): Promise; } export interface HistoryCompactorDependencies { summarize(request: SummarizerRequest): Promise; } function serializeContent(content: unknown): string { if (typeof content === "string") return content; if (!Array.isArray(content)) return JSON.stringify(content); return content .map((block) => { if (block?.type === "text") return String(block.text ?? ""); if (block?.type === "thinking") return `[thinking] ${String(block.thinking ?? "")}`; if (block?.type === "toolCall") { return `[tool call] ${String(block.name ?? "unknown")} ${JSON.stringify(block.arguments ?? {})}`; } if (block?.type === "image") return "[image omitted]"; return JSON.stringify(block); }) .join("\n"); } function serializeConversation(messages: CompactionMessage[]): string { return messages .map((message) => { const label = message.role === "toolResult" ? `Tool result (${message.toolName ?? "unknown"}${message.isError ? ", error" : ""})` : message.role; return `[${label}]\n${serializeContent(message.content)}`; }) .join("\n\n"); } const SUMMARY_CONTRACT = `Write a continuation summary with these exact sections: ## Current objective ## User constraints and preferences ## Decisions and rationale ## Completed work ## Current implementation state ## Files read and modified ## Commands and tests with outcomes ## Failures and approaches not to repeat ## Unresolved questions ## Next concrete steps ## Artifact and recovery pointers Keep errors, failed approaches, file names, user promises, and unresolved work. Do not call tools. Return only the summary.`; export function createHistoryCompactor( dependencies: HistoryCompactorDependencies, ): HistoryCompactor { return { async compact(input) { const conversation = serializeConversation(input.messages); let response: SummarizerResponse; try { response = await dependencies.summarize({ prompt: `${SUMMARY_CONTRACT}\n\n\n${conversation}\n`, signal: input.signal, }); } catch { return undefined; } const summary = response.text.trim(); if (!summary) return undefined; return { summary, firstKeptEntryId: input.firstKeptEntryId, tokensBefore: input.tokensBefore, ...(response.usage === undefined ? {} : { usage: response.usage }), }; }, }; }