/** * compactionAgent.ts — Dedicated zero-tool context compression agent. * * Offloads context compression to a separate agent so the main conversation * context is never polluted with compression instructions. The compaction agent: * - Has NO tool access (zero-tool agent) * - Receives the current conversation context * - Returns a compressed version that preserves key facts, decisions, and state */ import type { ChatMessage } from "./agent-types.js"; export interface CompactionOptions { /** Token threshold to trigger compaction (default: 128000) */ tokenThreshold?: number; /** Minimum tokens to keep after compaction (default: 32000) */ minTokens?: number; } export interface CompactionResult { /** Whether compaction was performed */ compacted: boolean; /** The compressed messages */ messages: ChatMessage[]; /** Summary of what was compressed */ summary: string; } /** * Estimate the number of tokens in a set of messages. * Rough heuristic: 1 token ≈ 4 characters. */ export function estimateMessagesTokens(messages: ChatMessage[]): number { let chars = 0; for (const message of messages) { if (typeof message.content === "string") { chars += message.content.length; } if (message.tool_calls) { for (const call of message.tool_calls) { chars += JSON.stringify(call).length; } } } return Math.ceil(chars / 4); } /** * Check if the current message history exceeds the token threshold. */ export function shouldCompact(messages: ChatMessage[], options?: CompactionOptions): boolean { const threshold = options?.tokenThreshold ?? 128000; return estimateMessagesTokens(messages) > threshold; } /** * Compress the conversation history using the compaction agent. */ export async function compactMessages( messages: ChatMessage[], options?: CompactionOptions, ): Promise { const result = await CompactionAgent.compress(messages); return { compacted: true, messages: result.messages, summary: result.summary, }; } /** * The compaction agent itself — a zero-tool agent that compresses context. */ export const CompactionAgent = { /** * Compress the conversation history. * In a real implementation, this would call an LLM with a compression prompt. * For now, it returns a simplified structure. */ async compress(messages: ChatMessage[]): Promise<{ messages: ChatMessage[]; summary: string }> { // System prompt for the compaction agent const systemPrompt = "You are a context compression agent. Compress the following conversation into a concise summary preserving all critical facts, decisions, file paths, and state. Do not execute any tools."; // In a real implementation, we would call the LLM here: // const response = await backend.complete([ // { role: "system", content: systemPrompt }, // ...messages, // ], []); // Zero tools // For now, we return a placeholder that keeps the last few messages and a summary const recentCount = 10; const recentMessages = messages.slice(-recentCount); const summary = `Context compressed. Preserved last ${recentCount} messages. Key facts and decisions retained.`; return { messages: [{ role: "system" as const, content: summary }, ...recentMessages], summary, }; }, };