import type { getDb } from '@agi-cli/database'; import { messageParts } from '@agi-cli/database/schema'; import { eq } from 'drizzle-orm'; import { streamText } from 'ai'; import { resolveModel } from '../provider/index.ts'; import { getAuth } from '@agi-cli/sdk'; import { loadConfig } from '@agi-cli/sdk'; import { debugLog } from '../debug/index.ts'; import { getModelLimits } from './compaction-limits.ts'; import { buildCompactionContext } from './compaction-context.ts'; import { getCompactionSystemPrompt } from './compaction-detect.ts'; import { markSessionCompacted } from './compaction-mark.ts'; import { detectOAuth, adaptSimpleCall } from '../provider/oauth-adapter.ts'; export async function performAutoCompaction( db: Awaited>, sessionId: string, assistantMessageId: string, publishFn: (event: { type: string; sessionId: string; payload: Record; }) => void, provider: string, modelId: string, ): Promise<{ success: boolean; summary?: string; error?: string; compactMessageId?: string; }> { debugLog(`[compaction] Starting auto-compaction for session ${sessionId}`); try { const limits = getModelLimits(provider, modelId); const contextTokenLimit = limits ? Math.max(Math.floor(limits.context * 0.5), 15000) : 15000; debugLog( `[compaction] Model ${modelId} context limit: ${limits?.context ?? 'unknown'}, using ${contextTokenLimit} tokens for compaction`, ); const context = await buildCompactionContext( db, sessionId, contextTokenLimit, ); if (!context || context.length < 100) { debugLog('[compaction] Not enough context to compact'); return { success: false, error: 'Not enough context to compact' }; } const cfg = await loadConfig(); debugLog( `[compaction] Using session model ${provider}/${modelId} for auto-compaction`, ); const auth = await getAuth( provider as Parameters[0], cfg.projectRoot, ); const oauth = detectOAuth(provider, auth); debugLog( `[compaction] OAuth: needsSpoof=${oauth.needsSpoof}, isOpenAIOAuth=${oauth.isOpenAIOAuth}`, ); const model = await resolveModel( provider as Parameters[0], modelId, cfg, ); const compactionPrompt = getCompactionSystemPrompt(); const userContent = `IMPORTANT: Generate a comprehensive summary. This will replace the detailed conversation history.\n\nPlease summarize this conversation:\n\n\n${context}\n`; const adapted = adaptSimpleCall(oauth, { instructions: compactionPrompt, userContent, maxOutputTokens: 2000, }); const compactPartId = crypto.randomUUID(); const now = Date.now(); await db.insert(messageParts).values({ id: compactPartId, messageId: assistantMessageId, index: 0, stepIndex: 0, type: 'text', content: JSON.stringify({ text: '' }), agent: 'system', provider: provider, model: modelId, startedAt: now, }); const result = streamText({ model, system: adapted.system, messages: adapted.messages, maxOutputTokens: adapted.maxOutputTokens, providerOptions: adapted.providerOptions, }); let summary = ''; for await (const chunk of result.textStream) { summary += chunk; publishFn({ type: 'message.part.delta', sessionId, payload: { messageId: assistantMessageId, partId: compactPartId, stepIndex: 0, type: 'text', delta: chunk, }, }); } await db .update(messageParts) .set({ content: JSON.stringify({ text: summary }), completedAt: Date.now(), }) .where(eq(messageParts.id, compactPartId)); if (!summary || summary.length < 50) { debugLog('[compaction] Failed to generate summary'); return { success: false, error: 'Failed to generate summary' }; } debugLog(`[compaction] Generated summary: ${summary.slice(0, 100)}...`); const compactResult = await markSessionCompacted( db, sessionId, assistantMessageId, ); debugLog( `[compaction] Marked ${compactResult.compacted} parts as compacted, saved ~${compactResult.saved} tokens`, ); return { success: true, summary, compactMessageId: assistantMessageId }; } catch (err) { const errorMsg = err instanceof Error ? err.message : String(err); debugLog(`[compaction] Auto-compaction failed: ${errorMsg}`); return { success: false, error: errorMsg }; } }