import { getLogger as getLogger$1 } from "../../../logger.js";
import runDbClient_default from "../../../data/db/runDbClient.js";
import { CONVERSATION_ARTIFACTS_LIMIT, CONVERSATION_HISTORY_DEFAULT_LIMIT as CONVERSATION_HISTORY_DEFAULT_LIMIT$1 } from "../constants/execution-limits/index.js";
import { getCompressionConfigForModel } from "../utils/model-context-utils.js";
import { ConversationCompressor } from "../services/ConversationCompressor.js";
import { CONVERSATION_HISTORY_MAX_OUTPUT_TOKENS_DEFAULT, createMessage, generateId, getConversationHistory, getLedgerArtifacts } from "@inkeep/agents-core";

//#region src/domains/run/data/conversations.ts
const logger = getLogger$1("conversations");
const compressionLocks = /* @__PURE__ */ new Map();
/**
* Creates default conversation history configuration
* @param mode - The conversation history mode ('full' | 'scoped' | 'none')
* @returns Default AgentConversationHistoryConfig
*/
function createDefaultConversationHistoryConfig(mode = "full") {
	return {
		mode,
		limit: CONVERSATION_HISTORY_DEFAULT_LIMIT$1,
		includeInternal: true,
		messageTypes: ["chat", "tool-result"],
		maxOutputTokens: CONVERSATION_HISTORY_MAX_OUTPUT_TOKENS_DEFAULT
	};
}
/**
* Extracts text content from A2A Message parts array
* Escapes control characters to ensure proper JSON serialization for Dolt
*/
function extractA2AMessageText(parts) {
	return parts.filter((part) => part.kind === "text" && part.text).map((part) => part.text).join("").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t").replace(/\f/g, "\\f").replace(/\b/g, "\\b");
}
/**
* Saves the result of an A2A client sendMessage call as a conversation message
* @param response - The response from a2aClient.sendMessage()
* @param params - Parameters for saving the message
* @returns The saved message or null if no text content was found
*/
async function saveA2AMessageResponse(response, params) {
	if (response.error) throw new Error(response.error.message);
	let messageText = "";
	if (response.result.kind === "message") messageText = extractA2AMessageText(response.result.parts);
	else if (response.result.kind === "task") {
		if (response.result.artifacts && response.result.artifacts.length > 0) {
			const firstArtifact = response.result.artifacts[0];
			if (firstArtifact.parts) messageText = extractA2AMessageText(firstArtifact.parts);
		}
	} else if (typeof response.result === "string") messageText = response.result;
	if (!messageText || messageText.trim() === "") return null;
	return await createMessage(runDbClient_default)({
		id: generateId(),
		tenantId: params.tenantId,
		projectId: params.projectId,
		conversationId: params.conversationId,
		role: "agent",
		content: { text: messageText },
		visibility: params.visibility,
		messageType: params.messageType,
		fromSubAgentId: params.fromSubAgentId,
		toSubAgentId: params.toSubAgentId,
		fromExternalAgentId: params.fromExternalAgentId,
		toExternalAgentId: params.toExternalAgentId,
		a2aTaskId: params.a2aTaskId,
		a2aSessionId: params.a2aSessionId,
		metadata: params.metadata
	});
}
/**
* Applies filtering based on agent, task, or both criteria
* Returns the filtered messages array
*/
async function getScopedHistory({ tenantId, projectId, conversationId, filters, options }) {
	try {
		const allMessages = await getConversationHistory(runDbClient_default)({
			scopes: {
				tenantId,
				projectId
			},
			conversationId,
			options: {
				...options,
				limit: 1e4,
				includeInternal: true,
				maxOutputTokens: void 0
			}
		});
		const compressionSummaries = allMessages.filter((msg) => msg.messageType === "compression_summary" && msg.metadata?.compressionType === "conversation_history");
		const latestCompressionSummary = compressionSummaries.length > 0 ? compressionSummaries.reduce((latest, current) => new Date(current.createdAt) > new Date(latest.createdAt) ? current : latest) : null;
		let messages;
		if (latestCompressionSummary) {
			const summaryDate = new Date(latestCompressionSummary.createdAt);
			messages = [latestCompressionSummary, ...allMessages.filter((msg) => new Date(msg.createdAt) > summaryDate && msg.messageType !== "compression_summary")];
			logger.debug({
				conversationId,
				latestCompressionSummaryId: latestCompressionSummary.id,
				summaryDate: summaryDate.toISOString(),
				messagesAfterCompression: messages.length - 1,
				totalMessages: allMessages.length
			}, "Retrieved conversation with compression summary");
		} else {
			messages = allMessages;
			logger.debug({
				conversationId,
				totalMessages: messages.length
			}, "Retrieved conversation without compression summary");
		}
		if (!filters || !filters.subAgentId && !filters.taskId && !filters.delegationId && filters.isDelegated === void 0) return messages;
		return messages.filter((msg) => {
			if (msg.role === "user") return true;
			let matchesAgent = true;
			let matchesTask = true;
			let matchesDelegation = true;
			if (filters.subAgentId) matchesAgent = msg.role === "agent" && msg.visibility === "user-facing" || msg.toSubAgentId === filters.subAgentId || msg.fromSubAgentId === filters.subAgentId;
			if (filters.taskId) matchesTask = msg.taskId === filters.taskId || msg.a2aTaskId === filters.taskId;
			if (filters.delegationId !== void 0 || filters.isDelegated !== void 0) {
				if (msg.messageType === "tool-result") {
					const messageDelegationId = msg.metadata?.a2a_metadata?.delegationId;
					const messageIsDelegated = msg.metadata?.a2a_metadata?.isDelegated;
					if (filters.delegationId) matchesDelegation = messageDelegationId === filters.delegationId || !messageDelegationId;
					else if (filters.isDelegated === false) matchesDelegation = !messageIsDelegated;
					else if (filters.isDelegated === true) matchesDelegation = messageIsDelegated === true;
				}
			}
			const conditions = [];
			if (filters.subAgentId) conditions.push(matchesAgent);
			if (filters.taskId) conditions.push(matchesTask);
			if (filters.delegationId !== void 0 || filters.isDelegated !== void 0) conditions.push(matchesDelegation);
			return conditions.length === 0 || conditions.every(Boolean);
		});
	} catch (error) {
		console.error("Failed to fetch scoped messages:", error);
		return [];
	}
}
/**
* Get user-facing conversation history (for client display)
*/
async function getUserFacingHistory(tenantId, projectId, conversationId, limit = CONVERSATION_HISTORY_DEFAULT_LIMIT$1) {
	return await getConversationHistory(runDbClient_default)({
		scopes: {
			tenantId,
			projectId
		},
		conversationId,
		options: {
			limit,
			includeInternal: false,
			messageTypes: ["chat"]
		}
	});
}
/**
* Get full conversation context (for agent processing)
*/
async function getFullConversationContext(tenantId, projectId, conversationId, maxTokens) {
	const defaultConfig = createDefaultConversationHistoryConfig();
	return await getConversationHistory(runDbClient_default)({
		scopes: {
			tenantId,
			projectId
		},
		conversationId,
		options: {
			...defaultConfig,
			limit: 100,
			includeInternal: true,
			maxOutputTokens: maxTokens
		}
	});
}
/**
* Get formatted conversation history for a2a
*/
async function getFormattedConversationHistory({ tenantId, projectId, conversationId, currentMessage, options, filters, sessionId, summarizerModel }) {
	const conversationHistory = await getScopedHistory({
		tenantId,
		projectId,
		conversationId,
		filters,
		options: options ?? createDefaultConversationHistoryConfig()
	});
	let messagesToFormat = conversationHistory;
	if (currentMessage && conversationHistory.length > 0) {
		if (conversationHistory[conversationHistory.length - 1].content.text === currentMessage) messagesToFormat = conversationHistory.slice(0, -1);
	}
	if (!messagesToFormat.length) return "";
	let finalMessagesToFormat = messagesToFormat;
	if (sessionId && summarizerModel) finalMessagesToFormat = await compressConversationIfNeeded(messagesToFormat, {
		conversationId,
		tenantId,
		projectId,
		summarizerModel,
		streamRequestId: sessionId
	});
	return `<conversation_history>\n${finalMessagesToFormat.map((msg) => {
		let roleLabel;
		if (msg.role === "user") roleLabel = "user";
		else if (msg.role === "agent" && (msg.messageType === "a2a-request" || msg.messageType === "a2a-response")) roleLabel = `${msg.fromSubAgentId || msg.fromExternalAgentId || "unknown"} to ${msg.toSubAgentId || msg.toExternalAgentId || "unknown"}`;
		else if (msg.role === "agent" && msg.messageType === "chat") roleLabel = `${msg.fromSubAgentId || "unknown"} to User`;
		else if (msg.role === "assistant" && msg.messageType === "tool-result") roleLabel = `${msg.fromSubAgentId || "unknown"} tool: ${msg.metadata?.a2a_metadata?.toolName || "unknown"}`;
		else roleLabel = msg.role || "system";
		return `${roleLabel}: """${msg.content.text}"""`;
	}).join("\n")}\n</conversation_history>\n`;
}
/**
* Modern conversation history retrieval with compression support
* Replaces getFormattedConversationHistory with built-in compression when needed
*/
async function getConversationHistoryWithCompression({ tenantId, projectId, conversationId, currentMessage, options, filters, summarizerModel, streamRequestId, fullContextSize }) {
	const conversationHistory = await getScopedHistory({
		tenantId,
		projectId,
		conversationId,
		filters,
		options: {
			...options ?? createDefaultConversationHistoryConfig(),
			includeInternal: true,
			maxOutputTokens: void 0
		}
	});
	let messagesToFormat = conversationHistory;
	if (currentMessage && conversationHistory.length > 0) {
		if (conversationHistory[conversationHistory.length - 1].content.text === currentMessage) messagesToFormat = conversationHistory.slice(0, -1);
	}
	if (!messagesToFormat.length) return "";
	if (summarizerModel) {
		const compressionInfo = getCompressionConfigForModel(summarizerModel, .5);
		const estimatedTokens = messagesToFormat.reduce((total, msg) => {
			const text = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
			return total + Math.ceil(text.length / 4);
		}, 0);
		const remaining = compressionInfo.hardLimit - estimatedTokens;
		const compressionNeeded = remaining <= compressionInfo.safetyBuffer;
		const contextWindowUtilization = compressionInfo.modelContextInfo.contextWindow ? (estimatedTokens / compressionInfo.modelContextInfo.contextWindow * 100).toFixed(1) : "unknown";
		logger.info({
			conversationId,
			model: summarizerModel.model,
			modelContextWindow: compressionInfo.modelContextInfo.contextWindow,
			currentTokens: estimatedTokens,
			hardLimit: compressionInfo.hardLimit,
			safetyBuffer: compressionInfo.safetyBuffer,
			remaining,
			compressionNeeded,
			contextWindowUtilization: `${contextWindowUtilization}%`,
			messageCount: messagesToFormat.length,
			source: compressionInfo.source
		}, "Conversation history fetch - model context analysis");
		const compressionSummary = messagesToFormat.find((msg) => msg.messageType === "compression_summary" && msg.metadata?.compressionType === "conversation_history");
		if (compressionSummary) {
			const messagesAfterCompression = messagesToFormat.filter((msg) => new Date(msg.createdAt) > new Date(compressionSummary.createdAt) && msg.messageType !== "compression_summary");
			if (messagesAfterCompression.length >= 10) {
				logger.info({
					conversationId,
					messagesAfterLastCompression: messagesAfterCompression.length,
					lastCompressionDate: compressionSummary.createdAt
				}, "Checking if re-compression needed for new messages");
				const newMessagesCompressed = await compressConversationIfNeeded(messagesAfterCompression, {
					conversationId,
					tenantId,
					projectId,
					summarizerModel,
					streamRequestId,
					fullContextSize
				});
				if (newMessagesCompressed.length === 1 && newMessagesCompressed[0].messageType === "compression_summary") {
					messagesToFormat = [compressionSummary, ...newMessagesCompressed];
					logger.info({
						conversationId,
						totalCompressedMessages: messagesToFormat.length
					}, "Re-compression completed - combined with existing summary");
				} else messagesToFormat = [compressionSummary, ...messagesAfterCompression];
			}
		} else messagesToFormat = await compressConversationIfNeeded(messagesToFormat, {
			conversationId,
			tenantId,
			projectId,
			summarizerModel,
			streamRequestId,
			fullContextSize
		});
		const compressedTokens = messagesToFormat.reduce((total, msg) => {
			const text = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
			return total + Math.ceil(text.length / 4);
		}, 0);
		const compressionSummaryMessages = messagesToFormat.filter((msg) => msg.messageType === "compression_summary");
		if (compressionSummaryMessages.length > 0) logger.info({
			conversationId,
			finalMessages: messagesToFormat.length,
			compressionSummaries: compressionSummaryMessages.length,
			finalTokens: compressedTokens,
			contextWindowUtilization: compressionInfo.modelContextInfo.contextWindow ? `${(compressedTokens / compressionInfo.modelContextInfo.contextWindow * 100).toFixed(1)}%` : "unknown"
		}, "Final conversation history with compression summaries");
	}
	return formatMessagesAsConversationHistory(messagesToFormat);
}
/**
* Apply conversation compression using the BaseCompressor infrastructure
*/
async function compressConversationIfNeeded(messages, params) {
	const { conversationId, tenantId, projectId } = params;
	const lockKey = `${conversationId}_${tenantId}_${projectId}`;
	if (compressionLocks.has(lockKey)) {
		logger.debug({ conversationId }, "Waiting for existing compression to complete");
		await compressionLocks.get(lockKey);
		return messages;
	}
	const compressionPromise = performActualCompression(messages, params);
	compressionLocks.set(lockKey, compressionPromise);
	try {
		return await compressionPromise;
	} finally {
		compressionLocks.delete(lockKey);
	}
}
async function performActualCompression(messages, params) {
	const { conversationId, tenantId, projectId, summarizerModel, streamRequestId } = params;
	const compressor = new ConversationCompressor(streamRequestId || conversationId, conversationId, tenantId, projectId, void 0, summarizerModel);
	if (!compressor.isCompressionNeeded(messages)) return messages;
	logger.info({
		conversationId,
		messageCount: messages.length
	}, "Applying conversation-level compression");
	try {
		const compressionResult = await compressor.safeCompress(messages, params.fullContextSize);
		if (compressionResult.summary) {
			const compressionMessage = await createMessage(runDbClient_default)({
				id: generateId(),
				tenantId,
				projectId,
				conversationId,
				role: "system",
				content: { text: buildCompressionSummaryMessage(compressionResult.summary, compressionResult.artifactIds) },
				visibility: "internal",
				messageType: "compression_summary",
				metadata: { a2a_metadata: {
					compressionType: "conversation_history",
					artifactIds: compressionResult.artifactIds,
					originalMessageCount: messages.length,
					compressedAt: (/* @__PURE__ */ new Date()).toISOString(),
					summaryData: compressionResult.summary
				} }
			});
			logger.debug({
				conversationId,
				originalMessageCount: messages.length,
				artifactCount: compressionResult.artifactIds?.length || 0,
				compressionMessageId: compressionMessage.id
			}, "Conversation compression saved to messages table");
			compressor.fullCleanup();
			return [compressionMessage];
		}
		compressor.fullCleanup();
		return messages;
	} catch (error) {
		logger.error({
			conversationId,
			error: error instanceof Error ? error.message : String(error)
		}, "Conversation compression failed, using original messages");
		compressor.fullCleanup();
		return messages;
	}
}
/**
* Build a summary message for compressed conversation content
*/
function buildCompressionSummaryMessage(summary, artifactIds) {
	const parts = [];
	parts.push("=== CONVERSATION SUMMARY ===");
	parts.push("Previous conversation has been compressed to save context space.");
	parts.push("");
	if (summary.conversation_overview) parts.push(`📋 Overview: ${summary.conversation_overview}`);
	if (summary.user_goals?.primary) {
		parts.push(`🎯 Primary Goal: ${summary.user_goals.primary}`);
		if (summary.user_goals.secondary && summary.user_goals.secondary.length > 0) {
			parts.push(`🎯 Secondary Goals:`);
			for (const goal of summary.user_goals.secondary) parts.push(`  • ${goal}`);
		}
	}
	if (summary.key_outcomes) {
		if (summary.key_outcomes.completed && summary.key_outcomes.completed.length > 0) {
			parts.push(`✅ Completed:`);
			for (const item of summary.key_outcomes.completed) parts.push(`  • ${item}`);
		}
		if (summary.key_outcomes.discoveries && summary.key_outcomes.discoveries.length > 0) {
			parts.push(`💡 Key Discoveries:`);
			for (const discovery of summary.key_outcomes.discoveries) parts.push(`  • ${discovery}`);
		}
		if (summary.key_outcomes.partial && summary.key_outcomes.partial.length > 0) {
			parts.push(`⏳ In Progress:`);
			for (const item of summary.key_outcomes.partial) parts.push(`  • ${item}`);
		}
	}
	if (summary.context_for_continuation) {
		if (summary.context_for_continuation.current_state) parts.push(`📍 Current State: ${summary.context_for_continuation.current_state}`);
		if (summary.context_for_continuation.next_logical_steps && summary.context_for_continuation.next_logical_steps.length > 0) {
			parts.push(`📝 Next Steps:`);
			for (const step of summary.context_for_continuation.next_logical_steps) parts.push(`  • ${step}`);
		}
		if (summary.context_for_continuation.important_context && summary.context_for_continuation.important_context.length > 0) {
			parts.push(`🔑 Key Context:`);
			for (const context of summary.context_for_continuation.important_context) parts.push(`  • ${context}`);
		}
	}
	if (summary.technical_context) {
		if (summary.technical_context.technologies && summary.technical_context.technologies.length > 0) parts.push(`🔧 Technologies: ${summary.technical_context.technologies.join(", ")}`);
		if (summary.technical_context.issues_encountered && summary.technical_context.issues_encountered.length > 0) {
			parts.push(`⚠️ Issues Encountered:`);
			for (const issue of summary.technical_context.issues_encountered) parts.push(`  • ${issue}`);
		}
		if (summary.technical_context.solutions_applied && summary.technical_context.solutions_applied.length > 0) {
			parts.push(`✨ Solutions Applied:`);
			for (const solution of summary.technical_context.solutions_applied) parts.push(`  • ${solution}`);
		}
	}
	if (summary.high_level) parts.push(`📋 Overview: ${summary.high_level}`);
	if (summary.user_intent) parts.push(`🎯 User Goal: ${summary.user_intent}`);
	if (summary.decisions && summary.decisions.length > 0) {
		parts.push(`✅ Key Decisions Made:`);
		for (const decision of summary.decisions) parts.push(`  • ${decision}`);
	}
	if (summary.next_steps && summary.next_steps.length > 0) {
		parts.push(`📝 Planned Next Steps:`);
		for (const step of summary.next_steps) parts.push(`  • ${step}`);
	}
	if (summary.open_questions && summary.open_questions.length > 0) {
		parts.push(`❓ Outstanding Questions:`);
		for (const question of summary.open_questions) parts.push(`  • ${question}`);
	}
	if (summary.conversation_artifacts && summary.conversation_artifacts.length > 0) {
		parts.push(`💾 Research Artifacts: ${summary.conversation_artifacts.length} created from previous work`);
		summary.conversation_artifacts.forEach((artifact) => {
			parts.push(`   [ARTIFACT: ${artifact.id}]`);
			parts.push(`   📋 ${artifact.name || "Research Data"}`);
			if (artifact.content_summary) parts.push(`   📝 ${artifact.content_summary}`);
			if (artifact.tool_name && artifact.tool_name !== "unknown") parts.push(`   🔧 Source: ${artifact.tool_name}`);
			parts.push(`   🔗 Reference: <artifact:ref id="${artifact.id}" tool_call_id="${artifact.tool_call_id}" />`);
			parts.push("");
		});
	} else if (artifactIds && artifactIds.length > 0) {
		parts.push(`💾 Research Artifacts: ${artifactIds.length} created from previous work`);
		artifactIds.forEach((artifactId) => {
			parts.push(`   [ARTIFACT: ${artifactId}]`);
			parts.push(`   🔗 Reference: <artifact:ref id="${artifactId}" />`);
		});
	}
	parts.push("");
	parts.push("=== END SUMMARY ===");
	parts.push("Recent conversation continues below...");
	return parts.join("\n");
}
/**
* Format messages into conversation history string (extracted from legacy method)
*/
function formatMessagesAsConversationHistory(messages) {
	return `<conversation_history>\n${messages.map((msg) => {
		let roleLabel;
		if (msg.role === "user") roleLabel = "user";
		else if (msg.role === "agent" && (msg.messageType === "a2a-request" || msg.messageType === "a2a-response")) roleLabel = `${msg.fromSubAgentId || msg.fromExternalAgentId || "unknown"} to ${msg.toSubAgentId || msg.toExternalAgentId || "unknown"}`;
		else if (msg.role === "agent" && msg.messageType === "chat") roleLabel = `${msg.fromSubAgentId || "unknown"} to User`;
		else if (msg.role === "assistant" && msg.messageType === "tool-result") roleLabel = `${msg.fromSubAgentId || "unknown"} tool: ${msg.metadata?.a2a_metadata?.toolName || "unknown"}`;
		else if (msg.role === "system") roleLabel = "system";
		else roleLabel = msg.role || "system";
		return `${roleLabel}: """${msg.content.text}"""`;
	}).join("\n")}\n</conversation_history>\n`;
}
/**
* Get artifacts that are within the scope of the conversation history
* Only returns artifacts from messages that are actually visible to the LLM
* Uses the same scoping logic as getFormattedConversationHistory
*/
async function getConversationScopedArtifacts(params) {
	const { tenantId, projectId, conversationId, historyConfig } = params;
	if (!conversationId) return [];
	try {
		if (historyConfig.mode === "none") return [];
		const visibleMessages = await getScopedHistory({
			tenantId,
			projectId,
			conversationId,
			options: historyConfig
		});
		if (visibleMessages.length === 0) return [];
		if (visibleMessages.filter((msg) => !(msg.messageType === "system" && msg.content?.text?.includes("Previous conversation history truncated"))).map((msg) => msg.id).length === 0) return [];
		const visibleTaskIds = visibleMessages.map((msg) => msg.taskId).filter((taskId) => Boolean(taskId));
		const referenceArtifacts = [];
		for (const taskId of visibleTaskIds) {
			const artifacts = await getLedgerArtifacts(runDbClient_default)({
				scopes: {
					tenantId,
					projectId
				},
				taskId
			});
			referenceArtifacts.push(...artifacts);
		}
		const ARTIFACT_COUNT_LIMIT = CONVERSATION_ARTIFACTS_LIMIT;
		const limitedArtifacts = referenceArtifacts.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, ARTIFACT_COUNT_LIMIT);
		logger.debug({
			conversationId,
			visibleMessages: visibleMessages.length,
			visibleTasks: visibleTaskIds.length,
			totalArtifacts: referenceArtifacts.length,
			limitedArtifacts: limitedArtifacts.length,
			artifactLimit: ARTIFACT_COUNT_LIMIT,
			historyMode: historyConfig.mode
		}, "Loaded conversation-scoped artifacts with count limit");
		return limitedArtifacts;
	} catch (error) {
		logger.error({
			error: error instanceof Error ? error.message : "Unknown error",
			conversationId
		}, "Failed to get conversation-scoped artifacts");
		return [];
	}
}

//#endregion
export { compressConversationIfNeeded, compressionLocks, createDefaultConversationHistoryConfig, getConversationHistoryWithCompression, getConversationScopedArtifacts, getFormattedConversationHistory, getFullConversationContext, getScopedHistory, getUserFacingHistory, saveA2AMessageResponse };