import { getLogger as getLogger$1 } from "../../../logger.js";
import runDbClient_default from "../../../data/db/runDbClient.js";
import { distillConversation } from "../tools/distill-conversation-tool.js";
import { getCompressionConfigForModel } from "../utils/model-context-utils.js";
import { tracer } from "../utils/tracer.js";
import { agentSessionManager } from "./AgentSession.js";
import { getLedgerArtifacts } from "@inkeep/agents-core";
import { SpanStatusCode } from "@opentelemetry/api";
import { randomUUID } from "node:crypto";

//#region src/domains/run/services/BaseCompressor.ts
const logger = getLogger$1("BaseCompressor");
/**
* Base compressor class containing shared functionality for all compression types
*/
var BaseCompressor = class {
	processedToolCalls = /* @__PURE__ */ new Set();
	cumulativeSummary = null;
	constructor(sessionId, conversationId, tenantId, projectId, config, summarizerModel, baseModel) {
		this.sessionId = sessionId;
		this.conversationId = conversationId;
		this.tenantId = tenantId;
		this.projectId = projectId;
		this.config = config;
		this.summarizerModel = summarizerModel;
		this.baseModel = baseModel;
	}
	/**
	* Get the hard limit for compression decisions
	*/
	getHardLimit() {
		return this.config.hardLimit;
	}
	/**
	* Estimate tokens (4 chars = 1 token)
	*/
	estimateTokens(content) {
		const text = typeof content === "string" ? content : JSON.stringify(content);
		return Math.ceil(text.length / 4);
	}
	/**
	* Calculate total context size for messages
	*/
	calculateContextSize(messages) {
		return messages.reduce((total, msg) => {
			let msgTokens = 0;
			if (Array.isArray(msg.content)) {
				for (const block of msg.content) if (block.type === "text") msgTokens += this.estimateTokens(block.text || "");
				else if (block.type === "tool-call") msgTokens += this.estimateTokens(JSON.stringify({
					toolCallId: block.toolCallId,
					toolName: block.toolName,
					input: block.input
				}));
				else if (block.type === "tool-result") msgTokens += this.estimateTokens(JSON.stringify({
					toolCallId: block.toolCallId,
					toolName: block.toolName,
					output: block.output
				}));
			} else if (typeof msg.content === "string") msgTokens += this.estimateTokens(msg.content);
			else if (msg.content) msgTokens += this.estimateTokens(JSON.stringify(msg.content));
			return total + msgTokens;
		}, 0);
	}
	/**
	* Save tool results as artifacts
	*/
	async saveToolResultsAsArtifacts(messages, startIndex = 0) {
		const session = agentSessionManager.getSession(this.sessionId);
		if (!session) throw new Error(`No session found: ${this.sessionId}`);
		const messagesToProcess = messages.slice(startIndex);
		const toolCallIds = this.extractToolCallIds(messagesToProcess);
		const existingArtifacts = await this.batchFindExistingArtifacts(toolCallIds);
		const toolCallToArtifactMap = {};
		for (const message of messagesToProcess) {
			this.convertDatabaseFormatMessage(message);
			if (Array.isArray(message.content)) {
				const messageArtifacts = await this.processMessageToolResults(message, session, existingArtifacts);
				Object.assign(toolCallToArtifactMap, messageArtifacts);
			}
		}
		return toolCallToArtifactMap;
	}
	/**
	* Extract all tool call IDs from messages for batch lookup
	*/
	extractToolCallIds(messages) {
		const toolCallIds = [];
		for (const message of messages) {
			if (message.messageType === "tool-result" && !Array.isArray(message.content)) {
				const toolCallId = message.metadata?.a2a_metadata?.toolCallId;
				if (toolCallId && !this.shouldSkipToolCall(message.metadata?.a2a_metadata?.toolName)) toolCallIds.push(toolCallId);
			}
			if (Array.isArray(message.content)) {
				for (const block of message.content) if (block.type === "tool-result" && !this.shouldSkipToolCall(block.toolName)) toolCallIds.push(block.toolCallId);
			}
		}
		return [...new Set(toolCallIds)];
	}
	/**
	* Batch lookup existing artifacts for multiple tool call IDs (solves N+1 query problem)
	*/
	async batchFindExistingArtifacts(toolCallIds) {
		const existingArtifacts = /* @__PURE__ */ new Map();
		if (toolCallIds.length === 0) return existingArtifacts;
		try {
			const artifacts = await this.queryExistingArtifactsBatch(toolCallIds);
			for (const artifact of artifacts) if (artifact.toolCallId) existingArtifacts.set(artifact.toolCallId, artifact.artifactId);
			logger.debug({
				sessionId: this.sessionId,
				toolCallIds: toolCallIds.length,
				foundArtifacts: existingArtifacts.size
			}, "Batched artifact lookup completed");
		} catch (error) {
			logger.debug({
				sessionId: this.sessionId,
				error: error instanceof Error ? error.message : String(error)
			}, "Batch artifact lookup failed, will create new artifacts");
		}
		return existingArtifacts;
	}
	/**
	* Query database for existing artifacts using enhanced getLedgerArtifacts with batch support
	*/
	async queryExistingArtifactsBatch(toolCallIds) {
		if (toolCallIds.length === 0) return [];
		try {
			return (await getLedgerArtifacts(runDbClient_default)({
				scopes: {
					tenantId: this.tenantId,
					projectId: this.projectId
				},
				toolCallIds
			})).map((artifact) => ({
				artifactId: artifact.artifactId,
				toolCallId: artifact.toolCallId
			}));
		} catch (error) {
			logger.debug({
				sessionId: this.sessionId,
				toolCallIds: toolCallIds.length,
				error: error instanceof Error ? error.message : String(error)
			}, "Batch artifact lookup failed");
			return [];
		}
	}
	/**
	* Convert database format tool-result messages to Vercel AI SDK format
	*/
	convertDatabaseFormatMessage(message) {
		if (message.messageType === "tool-result" && !Array.isArray(message.content) && message.content?.text) {
			const toolName = message.metadata?.a2a_metadata?.toolName;
			const toolCallId = message.metadata?.a2a_metadata?.toolCallId;
			if (this.shouldSkipToolCall(toolName)) return;
			if (toolName && toolCallId) message.content = [{
				type: "tool-result",
				toolCallId,
				toolName,
				output: message.content.text
			}];
		}
	}
	/**
	* Process all tool results in a message
	*/
	async processMessageToolResults(message, session, existingArtifacts) {
		const toolCallToArtifactMap = {};
		for (const block of message.content) if (block.type === "tool-result") {
			const artifactId = await this.processToolResult(block, message, session, existingArtifacts);
			if (artifactId) toolCallToArtifactMap[block.toolCallId] = artifactId;
		}
		return toolCallToArtifactMap;
	}
	/**
	* Process a single tool result block
	*/
	async processToolResult(block, message, session, existingArtifacts) {
		if (this.shouldSkipToolCall(block.toolName)) {
			logger.debug({
				toolCallId: block.toolCallId,
				toolName: block.toolName
			}, "Skipping special tool - not creating artifacts");
			this.processedToolCalls.add(block.toolCallId);
			return null;
		}
		if (this.processedToolCalls.has(block.toolCallId)) {
			logger.debug({
				toolCallId: block.toolCallId,
				toolName: block.toolName
			}, "Skipping already processed tool call");
			return null;
		}
		const existingArtifactId = existingArtifacts.get(block.toolCallId);
		if (existingArtifactId) {
			logger.debug({
				toolCallId: block.toolCallId,
				existingArtifactId,
				toolName: block.toolName
			}, "Reusing existing artifact from batch lookup");
			return existingArtifactId;
		}
		return await this.createNewArtifact(block, message, session);
	}
	/**
	* Check if a tool should be skipped
	*/
	shouldSkipToolCall(toolName) {
		return toolName === "get_reference_artifact" || toolName === "thinking_complete" || toolName?.includes("save_tool_result") || toolName?.startsWith("transfer_to_");
	}
	/**
	* Create a new artifact for a tool call
	*/
	async createNewArtifact(block, message, session) {
		const artifactId = `compress_${block.toolName || "tool"}_${block.toolCallId || Date.now()}_${randomUUID().slice(0, 8)}`;
		const toolInput = this.findToolInput(message, block.toolCallId);
		const toolResultData = {
			toolName: block.toolName,
			toolInput,
			toolResult: this.removeStructureHints(block.output),
			compressedAt: (/* @__PURE__ */ new Date()).toISOString()
		};
		if (this.isEmpty(toolResultData)) {
			logger.debug({
				toolName: block.toolName,
				toolCallId: block.toolCallId
			}, "Skipping empty tool result");
			return null;
		}
		const artifactData = this.buildArtifactData(artifactId, block, toolResultData);
		if (!this.validateArtifactData(artifactData)) {
			logger.debug({
				artifactId,
				toolName: block.toolName,
				toolCallId: block.toolCallId
			}, "Skipping empty compression artifact");
			return null;
		}
		session.recordEvent("artifact_saved", this.sessionId, artifactData);
		this.processedToolCalls.add(block.toolCallId);
		logger.debug({
			artifactId,
			toolName: block.toolName,
			toolCallId: block.toolCallId
		}, "Created new compression artifact");
		return artifactId;
	}
	/**
	* Find tool input for a given tool call ID
	*/
	findToolInput(message, toolCallId) {
		if (!Array.isArray(message.content)) return null;
		return message.content.find((b) => b.type === "tool-call" && b.toolCallId === toolCallId)?.input || null;
	}
	/**
	* Build artifact data structure
	*/
	buildArtifactData(artifactId, block, toolResultData) {
		return {
			artifactId,
			taskId: `task_${this.conversationId}-${this.sessionId}`,
			toolCallId: block.toolCallId,
			artifactType: "tool_result",
			pendingGeneration: true,
			tenantId: this.tenantId,
			projectId: this.projectId,
			contextId: this.conversationId,
			subAgentId: this.sessionId,
			metadata: {
				toolCallId: block.toolCallId,
				toolName: block.toolName,
				compressionReason: this.getCompressionType()
			},
			summaryData: {
				toolCallId: block.toolCallId,
				toolName: block.toolName,
				resultPreview: this.generateResultPreview(toolResultData.toolResult),
				note: `Tool result from ${block.toolName} - compressed to save context space`
			},
			data: toolResultData
		};
	}
	/**
	* Validate artifact data has meaningful content
	*/
	validateArtifactData(artifactData) {
		const fullData = artifactData.data;
		return fullData && typeof fullData === "object" && Object.keys(fullData).length > 0 && fullData.toolResult && (typeof fullData.toolResult !== "object" || Object.keys(fullData.toolResult).length > 0);
	}
	/**
	* Create conversation summary using distillConversation
	*/
	async createConversationSummary(messages, toolCallToArtifactMap) {
		const summary = await distillConversation({
			messages,
			conversationId: this.conversationId,
			currentSummary: this.cumulativeSummary,
			summarizerModel: this.summarizerModel,
			toolCallToArtifactMap
		});
		this.cumulativeSummary = summary;
		return summary;
	}
	/**
	* Record compression event in session
	*/
	/**
	* Generate a preview of the tool result for the artifact summary
	*/
	generateResultPreview(toolResult) {
		try {
			if (!toolResult) return "No result data";
			let preview;
			if (typeof toolResult === "string") preview = toolResult;
			else if (typeof toolResult === "object") preview = JSON.stringify(toolResult);
			else preview = String(toolResult);
			return preview.slice(0, 150).replace(/\s+/g, " ").trim() + (preview.length > 150 ? "..." : "");
		} catch {
			return "Preview unavailable";
		}
	}
	recordCompressionEvent(eventData) {
		const session = agentSessionManager.getSession(this.sessionId);
		if (session) session.recordEvent("compression", this.sessionId, eventData);
	}
	/**
	* Check if tool result data is effectively empty
	*/
	isEmpty(toolResultData) {
		if (!toolResultData || typeof toolResultData !== "object") return true;
		const { toolResult } = toolResultData;
		if (!toolResult) return true;
		if (typeof toolResult === "object" && !Array.isArray(toolResult)) {
			const keys = Object.keys(toolResult);
			if (keys.length === 0) return true;
			return keys.every((key) => {
				const value = toolResult[key];
				if (value === null || value === void 0 || value === "") return true;
				if (Array.isArray(value) && value.length === 0) return true;
				if (typeof value === "object" && Object.keys(value).length === 0) return true;
				return false;
			});
		}
		if (Array.isArray(toolResult) && toolResult.length === 0) return true;
		if (typeof toolResult === "string" && toolResult.trim() === "") return true;
		return false;
	}
	/**
	* Recursively remove _structureHints from an object
	*/
	removeStructureHints(obj) {
		if (obj === null || obj === void 0) return obj;
		if (Array.isArray(obj)) return obj.map((item) => this.removeStructureHints(item));
		if (typeof obj === "object") {
			const cleaned = {};
			for (const [key, value] of Object.entries(obj)) if (key !== "_structureHints") cleaned[key] = this.removeStructureHints(value);
			return cleaned;
		}
		return obj;
	}
	/**
	* Get current compression summary
	*/
	getCompressionSummary() {
		return this.cumulativeSummary;
	}
	/**
	* Clean up memory by clearing processed tool calls and optionally resetting summary
	* Call this at the end of agent generation or after compression cycles
	*/
	cleanup(options = {}) {
		const { resetSummary = false, keepRecentToolCalls = 0 } = options;
		if (keepRecentToolCalls > 0) {
			const recentCalls = Array.from(this.processedToolCalls).slice(-keepRecentToolCalls);
			this.processedToolCalls = new Set(recentCalls);
		} else this.processedToolCalls.clear();
		if (resetSummary) this.cumulativeSummary = null;
		logger.debug({
			sessionId: this.sessionId,
			conversationId: this.conversationId,
			processedToolCallsSize: this.processedToolCalls.size,
			summaryReset: resetSummary
		}, "BaseCompressor cleanup completed");
	}
	/**
	* Partial cleanup that preserves recent state for ongoing conversations
	*/
	partialCleanup() {
		this.cleanup({ keepRecentToolCalls: 50 });
	}
	/**
	* Full cleanup that resets all state - use when conversation/session ends
	*/
	fullCleanup() {
		this.cleanup({ resetSummary: true });
	}
	/**
	* Get current state for debugging
	*/
	getState() {
		return {
			config: this.config,
			processedToolCalls: Array.from(this.processedToolCalls),
			cumulativeSummary: this.cumulativeSummary
		};
	}
	/**
	* Safe compression wrapper with fallback handling
	*/
	async safeCompress(messages, fullContextSize) {
		return await tracer.startActiveSpan("compressor.safe_compress", { attributes: {
			"compression.type": this.getCompressionType(),
			"compression.session_id": this.sessionId,
			"compression.message_count": messages.length,
			"compression.input_tokens": fullContextSize ?? this.calculateContextSize(messages),
			"compression.hard_limit": this.getHardLimit(),
			"compression.safety_buffer": this.config.safetyBuffer
		} }, async (compressionSpan) => {
			try {
				const result = await this.compress(messages);
				const resultTokens = Array.isArray(result.summary) ? this.calculateContextSize(result.summary) : this.estimateTokens(result.summary);
				compressionSpan.setAttributes({
					"compression.result.artifact_count": result.artifactIds.length,
					"compression.result.output_tokens": resultTokens,
					"compression.result.compression_ratio": (fullContextSize ?? this.calculateContextSize(messages)) > 0 ? ((fullContextSize ?? this.calculateContextSize(messages)) - resultTokens) / (fullContextSize ?? this.calculateContextSize(messages)) : 0,
					"compression.success": true,
					"compression.result.summary": result.summary?.high_level || ""
				});
				compressionSpan.setStatus({ code: SpanStatusCode.OK });
				return result;
			} catch (error) {
				logger.error({
					sessionId: this.sessionId,
					conversationId: this.conversationId,
					error: error instanceof Error ? error.message : String(error),
					stack: error instanceof Error ? error.stack : void 0
				}, "Compression failed, using simple fallback");
				compressionSpan.setAttributes({ "compression.error": error instanceof Error ? error.message : String(error) });
				const fallbackResult = await this.simpleCompressionFallback(messages);
				const fallbackTokens = Array.isArray(fallbackResult.summary) ? this.calculateContextSize(fallbackResult.summary) : this.estimateTokens(fallbackResult.summary);
				compressionSpan.setAttributes({
					"compression.result.artifact_count": fallbackResult.artifactIds.length,
					"compression.result.output_tokens": fallbackTokens,
					"compression.result.compression_ratio": (fullContextSize ?? this.calculateContextSize(messages)) > 0 ? ((fullContextSize ?? this.calculateContextSize(messages)) - fallbackTokens) / (fullContextSize ?? this.calculateContextSize(messages)) : 0,
					"compression.success": true
				});
				compressionSpan.setStatus({ code: SpanStatusCode.OK });
				return fallbackResult;
			} finally {
				compressionSpan.end();
			}
		});
	}
	/**
	* Simple compression fallback using the same logic as Agent.simpleCompression
	* Returns the compressed messages, not just a summary
	*/
	async simpleCompressionFallback(messages) {
		if (messages.length === 0) return {
			artifactIds: [],
			summary: []
		};
		const targetTokens = Math.floor(this.getHardLimit() * .5);
		let totalTokens = this.calculateContextSize(messages);
		if (totalTokens <= targetTokens) return {
			artifactIds: [],
			summary: messages
		};
		const result = [...messages];
		while (totalTokens > targetTokens && result.length > 1) {
			const dropped = result.shift();
			if (dropped) totalTokens -= this.estimateTokens(dropped);
		}
		logger.info({
			sessionId: this.sessionId,
			conversationId: this.conversationId,
			originalCount: messages.length,
			compressedCount: result.length,
			compressionType: "simple_fallback"
		}, "Simple compression fallback completed");
		return {
			artifactIds: [],
			summary: result
		};
	}
};
/**
* Get model-aware compression config for any model
* @param modelSettings - Model settings to get context window for
* @param targetPercentage - Target percentage of context window (e.g., 0.5 for conversation, undefined for aggressive)
*/
function getModelAwareCompressionConfig(modelSettings, targetPercentage) {
	const config = getCompressionConfigForModel(modelSettings, targetPercentage);
	return {
		hardLimit: config.hardLimit,
		safetyBuffer: config.safetyBuffer,
		enabled: config.enabled
	};
}

//#endregion
export { BaseCompressor, getModelAwareCompressionConfig };