import { getLogger as getLogger$1 } from "../../../logger.js";
import runDbClient_default from "../../../data/db/runDbClient.js";
import { getCompressionConfigForModel, getModelContextWindow } from "../utils/model-context-utils.js";
import { estimateTokens } from "../utils/token-estimator.js";
import { distillConversation } from "../tools/distill-conversation-tool.js";
import { detectOversizedArtifact } from "../utils/artifact-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");
var BaseCompressor = class {
	processedToolCalls = /* @__PURE__ */ new Set();
	cumulativeSummary = null;
	contextWindowSize;
	toolCallInputMap = /* @__PURE__ */ new Map();
	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;
		if (baseModel) this.contextWindowSize = getModelContextWindow(baseModel).contextWindow ?? void 0;
	}
	getHardLimit() {
		return this.config.hardLimit;
	}
	estimateTokens(content) {
		return estimateTokens(typeof content === "string" ? content : JSON.stringify(content));
	}
	calculateContextSize(messages) {
		return messages.reduce((total, msg) => {
			if (Array.isArray(msg.content)) {
				for (const block of msg.content) if (block.type === "text") total += this.estimateTokens(block.text || "");
				else if (block.type === "tool-call") total += this.estimateTokens(JSON.stringify({
					toolCallId: block.toolCallId,
					toolName: block.toolName,
					input: block.input
				}));
				else if (block.type === "tool-result") total += this.estimateTokens(JSON.stringify({
					toolCallId: block.toolCallId,
					toolName: block.toolName,
					output: block.output
				}));
			} else if (typeof msg.content === "string") total += this.estimateTokens(msg.content);
			else if (msg.content) total += this.estimateTokens(JSON.stringify(msg.content));
			return total;
		}, 0);
	}
	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);
		this.toolCallInputMap = /* @__PURE__ */ new Map();
		for (const message of messagesToProcess) if (Array.isArray(message.content)) {
			for (const block of message.content) if (block.type === "tool-call" && block.toolCallId != null) this.toolCallInputMap.set(block.toolCallId, block.input);
		}
		const toolCallIds = this.extractToolCallIds(messagesToProcess);
		const existingArtifacts = await this.findExistingArtifacts([...new Set(toolCallIds)]);
		const toolCallToArtifactMap = {};
		for (const message of messagesToProcess) {
			this.convertDatabaseFormatMessage(message);
			if (Array.isArray(message.content)) {
				for (const block of message.content) if (block.type === "tool-result") {
					const artifactInfo = await this.processToolResult(block, session, existingArtifacts);
					if (artifactInfo) toolCallToArtifactMap[block.toolCallId] = artifactInfo;
				}
			}
		}
		return toolCallToArtifactMap;
	}
	async findExistingArtifacts(toolCallIds) {
		const result = /* @__PURE__ */ new Map();
		if (toolCallIds.length === 0) return result;
		try {
			const artifacts = await getLedgerArtifacts(runDbClient_default)({
				scopes: {
					tenantId: this.tenantId,
					projectId: this.projectId
				},
				toolCallIds
			});
			for (const artifact of artifacts) if (artifact.toolCallId) {
				const dataPart = artifact.parts?.find((p) => p.kind === "data");
				result.set(artifact.toolCallId, {
					artifactId: artifact.artifactId,
					isOversized: artifact.metadata?.isOversized ?? false,
					toolArgs: artifact.metadata?.toolArgs,
					toolName: artifact.metadata?.toolName,
					summaryData: dataPart?.data?.summary ?? dataPart?.data
				});
			}
		} catch (error) {
			logger.warn({
				sessionId: this.sessionId,
				toolCallCount: toolCallIds.length,
				error: error instanceof Error ? error.message : String(error)
			}, "Artifact batch lookup failed — existing artifacts may not be reused");
		}
		return result;
	}
	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) {
				const a2a = message.metadata?.a2a_metadata;
				message.content = [{
					type: "tool-result",
					toolCallId,
					toolName,
					input: a2a?.toolArgs ?? null,
					output: a2a?.toolOutput ?? message.content.text
				}];
			}
		}
	}
	async processToolResult(block, session, existingArtifacts) {
		if (this.shouldSkipToolCall(block.toolName)) {
			this.processedToolCalls.add(block.toolCallId);
			return null;
		}
		if (this.processedToolCalls.has(block.toolCallId)) return null;
		const existing = existingArtifacts.get(block.toolCallId);
		if (existing) return {
			artifactId: existing.artifactId,
			isOversized: existing.isOversized,
			toolArgs: existing.toolArgs,
			summaryData: existing.summaryData
		};
		return await this.createNewArtifact(block, session);
	}
	shouldSkipToolCall(toolName) {
		return toolName === "get_reference_artifact" || toolName === "load_skill" || toolName === "thinking_complete" || toolName?.includes("save_tool_result") || toolName?.startsWith("transfer_to_");
	}
	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 toolCallIds;
	}
	isEmpty(data) {
		if (!data.toolResult) return true;
		if (typeof data.toolResult === "object" && Object.keys(data.toolResult).length === 0) return true;
		return false;
	}
	buildArtifactData(artifactId, block, toolResultData) {
		const toolInput = block.input ?? this.toolCallInputMap.get(block.toolCallId) ?? null;
		const oversized = detectOversizedArtifact(toolResultData, this.contextWindowSize, {
			artifactId,
			toolCallId: block.toolCallId,
			toolName: block.toolName
		});
		const summaryData = {
			toolCallId: block.toolCallId,
			toolName: block.toolName,
			toolInput: this.generatePreview(toolInput),
			resultPreview: this.generatePreview(toolResultData.toolResult),
			note: `Tool result from ${block.toolName} - compressed to save context space`
		};
		if (oversized.isOversized) {
			summaryData._oversizedWarning = oversized.oversizedWarning;
			summaryData._structureInfo = oversized.structureInfo;
		}
		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,
				toolArgs: toolInput ?? null,
				compressionReason: this.getCompressionType(),
				isOversized: oversized.isOversized,
				originalTokenSize: oversized.originalTokenSize,
				contextWindowSize: oversized.contextWindowSize,
				retrievalBlocked: oversized.retrievalBlocked
			},
			summaryData,
			data: toolResultData
		};
	}
	async createNewArtifact(block, session) {
		const artifactId = `compress_${block.toolName || "tool"}_${block.toolCallId || Date.now()}_${randomUUID().slice(0, 8)}`;
		const toolInput = block.input ?? this.toolCallInputMap.get(block.toolCallId) ?? null;
		const toolResultData = {
			toolName: block.toolName,
			toolInput,
			toolResult: this.removeStructureHints(block.output),
			compressedAt: (/* @__PURE__ */ new Date()).toISOString()
		};
		if (this.isEmpty(toolResultData)) return null;
		const artifactData = this.buildArtifactData(artifactId, block, toolResultData);
		session.recordEvent("artifact_saved", this.sessionId, artifactData);
		this.processedToolCalls.add(block.toolCallId);
		return {
			artifactId,
			isOversized: artifactData.metadata.isOversized,
			toolArgs: artifactData.metadata.toolArgs ?? void 0,
			structureInfo: artifactData.summaryData._structureInfo,
			oversizedWarning: artifactData.summaryData._oversizedWarning,
			summaryData: artifactData.summaryData
		};
	}
	formatMessagesForDistillation(messages, toolCallToArtifactMap, maxTotalChars) {
		let nonToolResultChars = 0;
		let numToolResults = 0;
		for (const msg of messages) if (typeof msg.content === "string") nonToolResultChars += msg.content.length;
		else if (Array.isArray(msg.content)) {
			for (const block of msg.content) if (block.type === "text") nonToolResultChars += (block.text || "").length;
			else if (block.type === "tool-call") nonToolResultChars += JSON.stringify(block.input || {}).length + (block.toolName || "").length;
			else if (block.type === "tool-result" && toolCallToArtifactMap?.[block.toolCallId]) numToolResults++;
		} else if (msg.content?.text) nonToolResultChars += msg.content.text.length;
		const perResultLimit = maxTotalChars && numToolResults > 0 ? Math.max(200, Math.floor((maxTotalChars - nonToolResultChars) / numToolResults)) : maxTotalChars;
		return messages.map((msg) => {
			const parts = [];
			if (typeof msg.content === "string") parts.push(msg.content);
			else if (Array.isArray(msg.content)) {
				for (const block of msg.content) if (block.type === "text") parts.push(block.text);
				else if (block.type === "tool-call") parts.push(`[TOOL CALL] ${block.toolName}(${JSON.stringify(block.input)}) [ID: ${block.toolCallId}]`);
				else if (block.type === "tool-result") {
					const artifactInfo = toolCallToArtifactMap?.[block.toolCallId];
					if (artifactInfo) {
						const summary = artifactInfo.summaryData ? JSON.stringify(artifactInfo.summaryData) : "";
						const truncated = perResultLimit && summary.length > perResultLimit ? `${summary.slice(0, perResultLimit)}...` : summary;
						parts.push(`[TOOL RESULT] ${block.toolName} [ARTIFACT: ${artifactInfo.artifactId}]\n${truncated}`);
					} else logger.debug({
						toolCallId: block.toolCallId,
						toolName: block.toolName
					}, "Skipping tool result without artifact mapping in distillation");
				}
			} else if (msg.content?.text) parts.push(msg.content.text);
			return parts.length > 0 ? `${msg.role || "system"}: ${parts.join("\n")}` : "";
		}).filter((line) => line.trim().length > 0).join("\n\n");
	}
	async createConversationSummary(messages, toolCallToArtifactMap) {
		const summary = await distillConversation({
			conversationId: this.conversationId,
			currentSummary: this.cumulativeSummary,
			summarizerModel: this.summarizerModel,
			messageFormatter: (maxChars) => this.formatMessagesForDistillation(messages, toolCallToArtifactMap, maxChars)
		});
		this.cumulativeSummary = summary;
		return summary;
	}
	generatePreview(value, maxChars = 150) {
		if (value == null) return null;
		try {
			const str = typeof value === "string" ? value : JSON.stringify(value);
			return str.length > maxChars ? `${str.slice(0, maxChars).replace(/\s+/g, " ").trim()}...` : str.replace(/\s+/g, " ").trim();
		} catch {
			return null;
		}
	}
	recordCompressionEvent(eventData) {
		const session = agentSessionManager.getSession(this.sessionId);
		if (session) session.recordEvent("compression", this.sessionId, eventData);
	}
	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;
	}
	getCompressionSummary() {
		return this.cumulativeSummary;
	}
	cleanup(options = {}) {
		const { resetSummary = false, keepRecentToolCalls = 0 } = options;
		if (keepRecentToolCalls > 0) this.processedToolCalls = new Set(Array.from(this.processedToolCalls).slice(-keepRecentToolCalls));
		else this.processedToolCalls.clear();
		if (resetSummary) this.cumulativeSummary = null;
	}
	partialCleanup() {
		this.cleanup({ keepRecentToolCalls: 50 });
	}
	fullCleanup() {
		this.cleanup({ resetSummary: true });
	}
	getState() {
		return {
			config: this.config,
			processedToolCalls: Array.from(this.processedToolCalls),
			cumulativeSummary: this.cumulativeSummary
		};
	}
	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": this.calculateContextSize(messages),
			"compression.full_context_size": fullContextSize,
			"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);
				const inputTokens = fullContextSize ?? this.calculateContextSize(messages);
				compressionSpan.setAttributes({
					"compression.result.artifact_count": result.artifactIds.length,
					"compression.result.output_tokens": resultTokens,
					"compression.result.compression_ratio": inputTokens > 0 ? (inputTokens - resultTokens) / inputTokens : 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);
				const inputTokens = fullContextSize ?? this.calculateContextSize(messages);
				compressionSpan.setAttributes({
					"compression.result.artifact_count": fallbackResult.artifactIds.length,
					"compression.result.output_tokens": fallbackTokens,
					"compression.result.compression_ratio": inputTokens > 0 ? (inputTokens - fallbackTokens) / inputTokens : 0,
					"compression.success": true
				});
				compressionSpan.setStatus({ code: SpanStatusCode.OK });
				return fallbackResult;
			} finally {
				compressionSpan.end();
			}
		});
	}
	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
		}, "Simple compression fallback completed");
		return {
			artifactIds: [],
			summary: result
		};
	}
};
function getModelAwareCompressionConfig(modelSettings, targetPercentage) {
	const config = getCompressionConfigForModel(modelSettings, targetPercentage);
	return {
		hardLimit: config.hardLimit,
		safetyBuffer: config.safetyBuffer,
		enabled: config.enabled
	};
}

//#endregion
export { BaseCompressor, getModelAwareCompressionConfig };