import { readAgentMessageContent } from "./agent-message-access.js";
import { formatSummaryAsText, generateStructuredSummary } from "./summary-generator.js";
import { complete } from "@earendil-works/pi-ai";
//#region src/agent/memory/compaction.ts
const DEFAULT_CONFIG = {
	enabled: true,
	mode: "abstractive",
	reserveTokens: 8e3,
	triggerThreshold: .8,
	minMessagesBeforeCompact: 10,
	keepRecentMessages: 10,
	summaryMaxTokens: 500,
	evictionWindow: .2,
	retentionWindow: 6,
	preserveReasoning: true,
	accumulateUsage: true
};
function estimateTokens(text) {
	return Math.ceil(text.length / 4);
}
function estimateMessageTokens(msg) {
	let text = "";
	const raw = readAgentMessageContent(msg);
	if (typeof raw === "string") text = raw;
	else if (Array.isArray(raw)) text = raw.filter((c) => c.type === "text").map((c) => c.text || "").join("\n");
	return estimateTokens(text) + 10;
}
function extractLastReasoning(messages) {
	for (let i = messages.length - 1; i >= 0; i--) {
		const msg = messages[i];
		if (msg.role === "assistant") {
			const rd = msg.reasoning_details;
			if (rd && (rd.thinking || rd.signature)) return rd;
		}
	}
	return null;
}
function injectReasoningIntoFirstAssistant(messages, reasoning) {
	for (const msg of messages) if (msg.role === "assistant") {
		const existing = msg.reasoning_details;
		if (!existing || !existing.thinking && !existing.signature) msg.reasoning_details = reasoning;
		break;
	}
}
function accumulateUsage(messages) {
	let totalInput = 0;
	let totalOutput = 0;
	let totalCost = 0;
	let hasUsage = false;
	for (const msg of messages) {
		const usage = msg.usage;
		if (usage) {
			hasUsage = true;
			totalInput += usage.input || 0;
			totalOutput += usage.output || 0;
			totalCost += usage.cost || 0;
		}
	}
	if (!hasUsage) return void 0;
	return {
		input: totalInput,
		output: totalOutput,
		total: totalInput + totalOutput,
		cost: totalCost > 0 ? totalCost : void 0
	};
}
function filterDroppableMessages(messages) {
	return messages.filter((msg) => !msg.droppable);
}
function findNthTurnFromEnd(messages, n) {
	if (n <= 0) return messages.length;
	let turnsFound = 0;
	let lastRole = null;
	for (let i = messages.length - 1; i >= 0; i--) {
		const msg = messages[i];
		if (msg.role === "user" && lastRole !== "user") {
			turnsFound++;
			if (turnsFound === n) return i;
		}
		lastRole = msg.role;
	}
	return 0;
}
function calculateCompactionRange(messages, config) {
	const totalMessages = messages.length;
	if (totalMessages < config.minMessagesBeforeCompact) return null;
	const evictionEnd = Math.floor(totalMessages * config.evictionWindow);
	const retentionStart = findNthTurnFromEnd(messages, config.retentionWindow);
	const retentionEnd = retentionStart > 0 ? retentionStart - 1 : 0;
	const compactionEnd = Math.min(evictionEnd, retentionEnd);
	if (compactionEnd <= 1) return null;
	return {
		start: 0,
		end: compactionEnd
	};
}
var SessionCompactor = class {
	config;
	constructor(config, model) {
		this.model = model;
		this.config = {
			...DEFAULT_CONFIG,
			...config
		};
	}
	needsCompaction(messages, contextWindow) {
		if (!this.config.enabled) return {
			needed: false,
			reason: "disabled"
		};
		if (messages.length < this.config.minMessagesBeforeCompact) return {
			needed: false,
			reason: "not_enough_messages"
		};
		const usagePercent = this.estimateTotalTokens(messages) / contextWindow;
		if (usagePercent > this.config.triggerThreshold) return {
			needed: true,
			reason: "threshold_exceeded",
			usagePercent
		};
		return {
			needed: false,
			reason: "within_threshold"
		};
	}
	async compact(messages, instructions, force) {
		const effectiveMessages = filterDroppableMessages(messages);
		const minRequired = this.config.minMessagesBeforeCompact;
		if (!force && effectiveMessages.length < minRequired) return {
			summary: "",
			firstKeptIndex: 0,
			tokensBefore: this.estimateTotalTokens(effectiveMessages),
			tokensAfter: this.estimateTotalTokens(effectiveMessages),
			compacted: false
		};
		if (force && effectiveMessages.length < 2) return {
			summary: "",
			firstKeptIndex: 0,
			tokensBefore: this.estimateTotalTokens(effectiveMessages),
			tokensAfter: this.estimateTotalTokens(effectiveMessages),
			compacted: false
		};
		const range = calculateCompactionRange(effectiveMessages, this.config);
		let messagesToSummarize;
		let keptMessages;
		if (range) {
			messagesToSummarize = effectiveMessages.slice(0, range.end);
			keptMessages = effectiveMessages.slice(range.end);
		} else if (force) {
			const keep = Math.min(this.config.keepRecentMessages, Math.max(1, effectiveMessages.length - 1));
			messagesToSummarize = effectiveMessages.slice(0, effectiveMessages.length - keep);
			keptMessages = effectiveMessages.slice(-keep);
		} else {
			const keepRecent = this.config.keepRecentMessages;
			messagesToSummarize = effectiveMessages.slice(0, -keepRecent);
			keptMessages = effectiveMessages.slice(-keepRecent);
		}
		if (messagesToSummarize.length === 0) return {
			summary: "",
			firstKeptIndex: 0,
			tokensBefore: this.estimateTotalTokens(effectiveMessages),
			tokensAfter: this.estimateTotalTokens(effectiveMessages),
			compacted: false
		};
		let preservedReasoning = null;
		if (this.config.preserveReasoning) {
			preservedReasoning = extractLastReasoning(messagesToSummarize);
			if (preservedReasoning) injectReasoningIntoFirstAssistant(keptMessages, preservedReasoning);
		}
		const summary = await this.generateSummary(messagesToSummarize, instructions);
		const structuredSummary = this.config.mode === "structured" ? generateStructuredSummary(messagesToSummarize) : void 0;
		const tokensBefore = this.estimateTotalTokens(effectiveMessages);
		const tokensAfter = estimateTokens(summary) + 20 + this.estimateTotalTokens(keptMessages);
		let compactedUsage;
		if (this.config.accumulateUsage) compactedUsage = accumulateUsage(messagesToSummarize);
		return {
			summary,
			firstKeptIndex: keptMessages.length > 0 ? effectiveMessages.length - keptMessages.length : 0,
			tokensBefore,
			tokensAfter,
			compacted: true,
			structuredSummary,
			compactedUsage
		};
	}
	async generateSummary(messages, instructions) {
		if (this.model && (this.config.mode === "abstractive" || this.config.mode === "structured")) try {
			if (this.config.mode === "structured") return formatSummaryAsText(generateStructuredSummary(messages), true);
			else return await this.llmAbstractiveSummary(messages, instructions);
		} catch (err) {
			console.warn("[Compactor] LLM summarization failed, falling back to extractive", err);
		}
		return this.extractiveSummary(messages);
	}
	async llmAbstractiveSummary(messages, instructions) {
		if (!this.model) throw new Error("Model not available");
		const conversation = this.formatMessages(messages);
		const prompt = `Summarize the following conversation in 2-3 concise sentences. Focus on:
1. What the user was trying to accomplish
2. Key decisions, outcomes, or solutions
3. Any important context that should be preserved
${instructions?.trim() ? `\nAdditional focus from the user:\n${instructions.trim()}\n` : ""}
Conversation:
${conversation}

Summary:`;
		const controller = new AbortController();
		const timeoutId = setTimeout(() => controller.abort(), 3e4);
		try {
			const summaryMessage = {
				role: "user",
				content: prompt,
				timestamp: Date.now()
			};
			const result = await complete(this.model, { messages: [summaryMessage] }, {
				maxTokens: this.config.summaryMaxTokens,
				temperature: .3,
				signal: controller.signal
			});
			return (Array.isArray(result.content) ? result.content.filter((c) => c.type === "text").map((c) => c.text).join("") : "").trim();
		} catch (err) {
			if (err instanceof Error && err.name === "AbortError") throw new Error("LLM summarization timed out after 30 seconds");
			throw err;
		} finally {
			clearTimeout(timeoutId);
		}
	}
	formatMessages(messages) {
		return messages.map((m) => {
			const role = m.role;
			const raw = readAgentMessageContent(m);
			return `[${role}]: ${typeof raw === "string" ? raw : raw.filter((c) => c.type === "text").map((c) => c.text || "").join("\n")}`;
		}).join("\n\n");
	}
	extractiveSummary(messages) {
		return `Previous conversation covered: ${messages.filter((m) => m.role === "user").slice(-5).map((m) => {
			const raw = readAgentMessageContent(m);
			if (typeof raw === "string") return raw;
			if (Array.isArray(raw)) return raw.filter((c) => c.type === "text").map((c) => c.text || "").join("\n");
			return "";
		}).filter((t) => t.length > 0).join("; ").slice(0, 300)}...`;
	}
	estimateTotalTokens(messages) {
		return messages.reduce((sum, msg) => sum + estimateMessageTokens(msg), 0);
	}
	applyCompaction(messages, result) {
		if (!result.compacted || !result.summary) return messages;
		const summaryMessage = {
			role: "user",
			content: [{
				type: "text",
				text: `[Previous conversation summary]: ${result.summary}`
			}],
			timestamp: Date.now()
		};
		if (result.compactedUsage) summaryMessage.usage = result.compactedUsage;
		return [summaryMessage, ...messages.slice(result.firstKeptIndex)];
	}
};
//#endregion
export { SessionCompactor, accumulateUsage };

//# sourceMappingURL=compaction.js.map