import runDbClient_default from "../../../data/db/runDbClient.js";
import { getLogger as getLogger$1 } from "../../../logger.js";
import { flushBatchProcessor } from "../../../instrumentation.js";
import { A2AClient } from "../a2a/client.js";
import { extractTransferData, isTransferTask } from "../a2a/types.js";
import { executeTransfer } from "../a2a/transfer.js";
import { AGENT_EXECUTION_MAX_CONSECUTIVE_ERRORS } from "../constants/execution-limits/index.js";
import { tracer } from "../utils/tracer.js";
import { registerStreamHelper, unregisterStreamHelper } from "../stream/stream-registry.js";
import { agentSessionManager } from "../session/AgentSession.js";
import { BufferingStreamHelper } from "../stream/stream-helpers.js";
import { agentInitializingOp, completionOp, errorOp } from "../utils/agent-operations.js";
import { mergeHeadersWithoutOverrides } from "../utils/merge-headers.js";
import { resolveModelConfig } from "../utils/model-resolver.js";
import { triggerConversationEvaluation } from "../../evals/services/conversationEvaluation.js";
import { AGENT_EXECUTION_TRANSFER_COUNT_DEFAULT, createMessage, createTask, generateId, generateServiceToken, getActiveAgentForConversation, getInProcessFetch, getTask, isUniqueConstraintError, setSpanWithError, unwrapError, updateTask } from "@inkeep/agents-core";

//#region src/domains/run/handlers/executionHandler.ts
const logger = getLogger$1("ExecutionHandler");
function getResponsePartKind(part) {
	return part.kind ?? part.type;
}
var ExecutionHandler = class {
	MAX_ERRORS = AGENT_EXECUTION_MAX_CONSECUTIVE_ERRORS;
	/**
	* performs exeuction loop
	*
	* Do up to limit of MAX_ITERATIONS
	*
	* 1. lookup active agent for thread
	* 2. Send A2A message to selected agent
	* 3. Parse A2A message response
	* 4. Handle transfer messages (if any)
	* 5. Handle completion messages (if any)
	* 6. If no valid response or transfer, return error
	* @param params
	* @returns
	*/
	async execute(params) {
		const { executionContext, conversationId, userMessage, messageParts, initialAgentId, requestId, sseHelper, emitOperations, forwardedHeaders } = params;
		const { tenantId, projectId, project, agentId, baseUrl, resolvedRef } = executionContext;
		registerStreamHelper(requestId, sseHelper);
		agentSessionManager.createSession(requestId, executionContext, conversationId);
		if (emitOperations) agentSessionManager.enableEmitOperations(requestId);
		logger.info({
			sessionId: requestId,
			agentId,
			conversationId,
			emitOperations
		}, "Created AgentSession for message execution");
		const agent = project.agents[agentId];
		try {
			let summarizerModel;
			let baseModel;
			try {
				if (agent?.defaultSubAgentId) {
					const resolvedModels = await resolveModelConfig(executionContext, agent.subAgents[agent.defaultSubAgentId]);
					summarizerModel = resolvedModels.summarizer;
					baseModel = resolvedModels.base;
				} else {
					summarizerModel = agent.models?.summarizer;
					baseModel = agent.models?.base;
				}
			} catch (modelError) {
				logger.warn({
					error: modelError instanceof Error ? modelError.message : "Unknown error",
					agentId
				}, "Failed to resolve models, using agent-level config");
				summarizerModel = agent.models?.summarizer;
				baseModel = agent.models?.base;
			}
			const statusConfig = agent?.statusUpdates && agent.statusUpdates.enabled !== false ? agent.statusUpdates : { enabled: false };
			agentSessionManager.initializeStatusUpdates(requestId, statusConfig, summarizerModel, baseModel);
		} catch (error) {
			logger.error({
				error: error instanceof Error ? error.message : "Unknown error",
				stack: error instanceof Error ? error.stack : void 0
			}, "Failed to initialize session configuration, continuing with defaults");
		}
		let currentAgentId = initialAgentId;
		let iterations = 0;
		let errorCount = 0;
		let task = null;
		let fromSubAgentId;
		try {
			await sseHelper.writeOperation(agentInitializingOp(requestId, agentId));
			const taskId = `task_${conversationId}-${requestId}`;
			logger.info({
				taskId,
				currentAgentId,
				conversationId,
				requestId
			}, "Attempting to create or reuse existing task");
			try {
				task = await createTask(runDbClient_default)({
					id: taskId,
					tenantId,
					projectId,
					agentId,
					subAgentId: currentAgentId,
					contextId: conversationId,
					status: "pending",
					ref: resolvedRef,
					metadata: {
						conversation_id: conversationId,
						message_id: requestId,
						stream_request_id: requestId,
						created_at: (/* @__PURE__ */ new Date()).toISOString(),
						updated_at: (/* @__PURE__ */ new Date()).toISOString(),
						root_sub_agent_id: initialAgentId,
						sub_agent_id: currentAgentId
					}
				});
				logger.info({
					taskId,
					createdTaskMetadata: Array.isArray(task) ? task[0]?.metadata : task?.metadata
				}, "Task created with metadata");
			} catch (error) {
				if (isUniqueConstraintError(error)) {
					logger.info({
						taskId,
						error: error.message
					}, "Task already exists, fetching existing task");
					const existingTask = await getTask(runDbClient_default)({
						id: taskId,
						scopes: {
							tenantId,
							projectId
						}
					});
					if (existingTask) {
						task = existingTask;
						logger.info({
							taskId,
							existingTask
						}, "Successfully reused existing task from race condition");
					} else {
						logger.error({
							taskId,
							error
						}, "Task constraint failed but task not found");
						throw error;
					}
				} else {
					logger.error({
						taskId,
						error
					}, "Failed to create task due to non-constraint error");
					throw error;
				}
			}
			logger.debug({
				timestamp: /* @__PURE__ */ new Date(),
				executionType: "create_initial_task",
				conversationId,
				agentId,
				requestId,
				currentAgentId,
				taskId: Array.isArray(task) ? task[0]?.id : task?.id,
				userMessage: userMessage.substring(0, 100)
			}, "ExecutionHandler: Initial task created");
			if (Array.isArray(task)) task = task[0];
			let currentMessage = userMessage;
			const maxTransfers = agent?.stopWhen?.transferCountIs ?? AGENT_EXECUTION_TRANSFER_COUNT_DEFAULT;
			while (iterations < maxTransfers) {
				iterations++;
				logger.info({
					iterations,
					currentAgentId,
					agentId,
					conversationId,
					fromSubAgentId
				}, `Execution loop iteration ${iterations} with agent ${currentAgentId}, transfer from: ${fromSubAgentId || "none"}`);
				const activeAgent = await getActiveAgentForConversation(runDbClient_default)({
					scopes: {
						tenantId,
						projectId
					},
					conversationId
				});
				logger.info({ activeAgent }, "activeAgent");
				if (activeAgent && activeAgent.activeSubAgentId !== currentAgentId) {
					currentAgentId = activeAgent.activeSubAgentId;
					logger.info({ currentAgentId }, `Updated current agent to: ${currentAgentId}`);
				}
				const agentBaseUrl = `${baseUrl}/run/agents`;
				const initiatedBy = executionContext.metadata?.initiatedBy;
				const authToken = await generateServiceToken({
					tenantId,
					projectId,
					originAgentId: agentId,
					targetAgentId: currentAgentId,
					initiatedBy
				});
				const runAsUserId = initiatedBy?.type === "user" && initiatedBy.id && initiatedBy.id !== "system" && !initiatedBy.id.startsWith("apikey:") ? initiatedBy.id : void 0;
				const appId = executionContext.metadata?.appId;
				const a2aClient = new A2AClient(agentBaseUrl, {
					headers: mergeHeadersWithoutOverrides({
						Authorization: `Bearer ${authToken}`,
						"x-inkeep-tenant-id": tenantId,
						"x-inkeep-project-id": projectId,
						"x-inkeep-agent-id": agentId,
						"x-inkeep-sub-agent-id": currentAgentId,
						...runAsUserId ? { "x-inkeep-run-as-user-id": runAsUserId } : {},
						...appId ? { "x-inkeep-app-id": appId } : {}
					}, forwardedHeaders || {}),
					fetchFn: getInProcessFetch()
				});
				let messageResponse = null;
				const messageMetadata = {
					stream_request_id: requestId,
					forwardedHeaders
				};
				if (fromSubAgentId) messageMetadata.fromSubAgentId = fromSubAgentId;
				if (params.durableWorkflowRunId) {
					messageMetadata.durable_workflow_run_id = params.durableWorkflowRunId;
					messageMetadata.approved_tool_calls = JSON.stringify(params.approvedToolCalls ?? {});
				}
				const partsToSend = iterations === 1 && messageParts && messageParts.length > 0 ? messageParts : [{
					kind: "text",
					text: currentMessage
				}];
				messageResponse = await a2aClient.sendMessage({
					message: {
						role: "user",
						parts: partsToSend,
						messageId: `${requestId}-iter-${iterations}`,
						kind: "message",
						contextId: conversationId,
						metadata: messageMetadata
					},
					configuration: {
						acceptedOutputModes: ["text", "text/plain"],
						blocking: false
					}
				});
				if (!messageResponse?.result) {
					errorCount++;
					logger.error({
						currentAgentId,
						iterations,
						errorCount,
						hasError: !!messageResponse?.error,
						errorDetails: messageResponse?.error,
						fullResponse: messageResponse
					}, `No response from agent ${currentAgentId} on iteration ${iterations} (error ${errorCount}/${this.MAX_ERRORS})`);
					if (errorCount >= this.MAX_ERRORS) {
						const errorMessage = `Maximum error limit (${this.MAX_ERRORS}) reached`;
						logger.error({
							maxErrors: this.MAX_ERRORS,
							errorCount
						}, errorMessage);
						return tracer.startActiveSpan("execution_handler.execute", {}, async (span) => {
							try {
								span.setAttributes({
									"ai.response.content": `Hmm.. It seems I might be having some issues right now. Please clear the chat and try again.`,
									"ai.response.timestamp": (/* @__PURE__ */ new Date()).toISOString(),
									"subAgent.name": agent?.subAgents[currentAgentId]?.name,
									"subAgent.id": currentAgentId
								});
								setSpanWithError(span, new Error(errorMessage));
								await sseHelper.writeOperation(errorOp(errorMessage, currentAgentId || "system"));
								if (task) await updateTask(runDbClient_default)({
									taskId: task.id,
									scopes: {
										tenantId,
										projectId
									},
									data: {
										status: "failed",
										metadata: {
											...task.metadata,
											failed_at: (/* @__PURE__ */ new Date()).toISOString(),
											error: errorMessage
										}
									}
								});
								await agentSessionManager.endSession(requestId);
								unregisterStreamHelper(requestId);
								return {
									success: false,
									error: errorMessage,
									iterations
								};
							} finally {
								span.end();
								await new Promise((resolve) => setImmediate(resolve));
								await flushBatchProcessor();
							}
						});
					}
					continue;
				}
				const firstArtifactData = messageResponse.result?.artifacts?.[0]?.parts?.[0]?.data;
				if (firstArtifactData?.type === "durable-approval-required") return {
					success: true,
					iterations,
					pendingApproval: {
						toolCallId: firstArtifactData.toolCallId ?? "",
						toolName: firstArtifactData.toolName ?? "",
						args: firstArtifactData.args
					}
				};
				if (isTransferTask(messageResponse.result)) {
					const transferData = extractTransferData(messageResponse.result);
					if (!transferData) {
						logger.error({ result: messageResponse.result }, "Transfer detected but no transfer data found");
						continue;
					}
					const { targetSubAgentId, fromSubAgentId: transferFromAgent } = transferData;
					const firstArtifact = messageResponse.result.artifacts[0];
					const transferReason = firstArtifact?.parts[1]?.kind === "text" ? firstArtifact.parts[1].text : "Transfer initiated";
					logger.info({
						targetSubAgentId,
						transferReason,
						transferFromAgent
					}, "Transfer response");
					await createMessage(runDbClient_default)({
						scopes: {
							tenantId,
							projectId
						},
						data: {
							id: generateId(),
							conversationId,
							role: "agent",
							content: {
								text: transferReason,
								parts: [{
									kind: "text",
									text: transferReason
								}]
							},
							visibility: "user-facing",
							messageType: "chat",
							fromSubAgentId: currentAgentId,
							taskId: task.id
						}
					});
					currentMessage = currentMessage + "\n\nPlease continue this conversation seamlessly. The previous response in conversation history was from another internal agent, but you must continue as if YOU made that response. All responses must appear as one unified agent - do not repeat what was already communicated.";
					const { success, targetSubAgentId: newAgentId } = await executeTransfer({
						projectId,
						tenantId,
						threadId: conversationId,
						agentId,
						targetSubAgentId,
						ref: resolvedRef
					});
					if (success) {
						fromSubAgentId = currentAgentId;
						currentAgentId = newAgentId;
						logger.info({
							transferFrom: fromSubAgentId,
							transferTo: currentAgentId,
							reason: transferReason
						}, "Transfer executed, tracking fromSubAgentId for next iteration");
					}
					continue;
				}
				let responseParts = [];
				if (messageResponse.result.streamedContent?.parts) {
					responseParts = messageResponse.result.streamedContent.parts;
					logger.info({ partsCount: responseParts.length }, "Using streamed content for conversation history");
				} else {
					responseParts = messageResponse.result.artifacts?.flatMap((artifact) => artifact.parts || []) || [];
					logger.info({ partsCount: responseParts.length }, "Using artifacts for conversation history (fallback)");
				}
				if (responseParts && responseParts.length > 0) {
					const agentSessionData = agentSessionManager.getSession(requestId);
					if (agentSessionData) {
						const sessionSummary = agentSessionData.getSummary();
						logger.info(sessionSummary, "AgentSession data after completion");
					}
					let textContent = "";
					for (const part of responseParts) if (getResponsePartKind(part) === "text" && part.text) textContent += part.text;
					return tracer.startActiveSpan("execution_handler.execute", {}, async (span) => {
						try {
							span.setAttributes({
								"ai.response.content": textContent || "No response content",
								"ai.response.timestamp": (/* @__PURE__ */ new Date()).toISOString(),
								"subAgent.name": agent?.subAgents[currentAgentId]?.name,
								"subAgent.id": currentAgentId
							});
							const messageId = params.responseMessageId || generateId();
							await createMessage(runDbClient_default)({
								scopes: {
									tenantId,
									projectId
								},
								data: {
									id: messageId,
									conversationId,
									role: "agent",
									content: {
										text: textContent || void 0,
										parts: responseParts.map((part) => {
											const k = getResponsePartKind(part);
											if (k === "text") return {
												kind: "text",
												text: part.text
											};
											return {
												kind: "data",
												text: void 0,
												data: k === "data" ? JSON.stringify(part.data) : void 0
											};
										})
									},
									visibility: "user-facing",
									messageType: "chat",
									fromSubAgentId: currentAgentId,
									taskId: task.id
								}
							});
							const updateTaskStart = Date.now();
							await updateTask(runDbClient_default)({
								taskId: task.id,
								scopes: {
									tenantId,
									projectId
								},
								data: {
									status: "completed",
									metadata: {
										...task.metadata,
										completed_at: /* @__PURE__ */ new Date(),
										response: {
											text: textContent,
											parts: responseParts,
											hasText: !!textContent,
											hasData: responseParts.some((p) => getResponsePartKind(p) === "data")
										}
									}
								}
							});
							const updateTaskEnd = Date.now();
							logger.info({ duration: updateTaskEnd - updateTaskStart }, "Completed updateTask operation");
							await sseHelper.writeOperation(completionOp(currentAgentId, iterations));
							await sseHelper.complete();
							logger.info({}, "Ending AgentSession and cleaning up");
							await agentSessionManager.endSession(requestId);
							logger.info({}, "Cleaning up streamHelper");
							unregisterStreamHelper(requestId);
							let response;
							if (sseHelper instanceof BufferingStreamHelper) response = sseHelper.getCapturedResponse().text || "No response content";
							logger.info({}, "ExecutionHandler returning success");
							if (!params.datasetRunId) triggerConversationEvaluation({
								tenantId,
								projectId,
								conversationId,
								resolvedRef
							}).catch((error) => {
								logger.error({
									error,
									conversationId,
									tenantId,
									projectId,
									resolvedRef
								}, "Failed to trigger conversation evaluation (non-blocking)");
							});
							return {
								success: true,
								iterations,
								response
							};
						} catch (error) {
							setSpanWithError(span, error instanceof Error ? error : new Error(String(error)));
							throw error;
						} finally {
							span.end();
							await new Promise((resolve) => setImmediate(resolve));
							await flushBatchProcessor();
						}
					});
				}
				errorCount++;
				logger.warn({
					iterations,
					errorCount
				}, `No valid response or transfer on iteration ${iterations} (error ${errorCount}/${this.MAX_ERRORS})`);
				if (errorCount >= this.MAX_ERRORS) {
					const errorMessage = `Maximum error limit (${this.MAX_ERRORS}) reached`;
					logger.error({
						maxErrors: this.MAX_ERRORS,
						errorCount
					}, errorMessage);
					return tracer.startActiveSpan("execution_handler.execute", {}, async (span) => {
						try {
							span.setAttributes({
								"ai.response.content": "Hmm.. It seems I might be having some issues right now. Please clear the chat and try again.",
								"ai.response.timestamp": (/* @__PURE__ */ new Date()).toISOString(),
								"subAgent.name": agent?.subAgents[currentAgentId]?.name,
								"subAgent.id": currentAgentId
							});
							setSpanWithError(span, new Error(errorMessage));
							await sseHelper.writeOperation(errorOp(errorMessage, currentAgentId || "system"));
							if (task) await updateTask(runDbClient_default)({
								taskId: task.id,
								scopes: {
									tenantId,
									projectId
								},
								data: {
									status: "failed",
									metadata: {
										...task.metadata,
										failed_at: /* @__PURE__ */ new Date(),
										error: errorMessage
									}
								}
							});
							await agentSessionManager.endSession(requestId);
							unregisterStreamHelper(requestId);
							if (!params.datasetRunId) triggerConversationEvaluation({
								tenantId,
								projectId,
								conversationId,
								resolvedRef
							}).catch((evalError) => {
								logger.error({
									error: evalError,
									conversationId,
									tenantId,
									projectId
								}, "Failed to trigger conversation evaluation (non-blocking)");
							});
							return {
								success: false,
								error: errorMessage,
								iterations
							};
						} finally {
							span.end();
							await new Promise((resolve) => setImmediate(resolve));
							await flushBatchProcessor();
						}
					});
				}
			}
			const maxTransfersErrorMessage = `Maximum transfer limit (${maxTransfers}) reached without completion`;
			logger.error({
				maxTransfers,
				iterations
			}, maxTransfersErrorMessage);
			return tracer.startActiveSpan("execution_handler.execute", {}, async (span) => {
				try {
					span.setAttributes({
						"ai.response.content": "Hmm.. It seems I might be having some issues right now. Please clear the chat and try again.",
						"ai.response.timestamp": (/* @__PURE__ */ new Date()).toISOString(),
						"subAgent.name": agent?.subAgents[currentAgentId]?.name,
						"subAgent.id": currentAgentId
					});
					setSpanWithError(span, new Error(maxTransfersErrorMessage));
					await sseHelper.writeOperation(errorOp(maxTransfersErrorMessage, currentAgentId || "system"));
					if (task) await updateTask(runDbClient_default)({
						taskId: task.id,
						scopes: {
							tenantId,
							projectId
						},
						data: {
							status: "failed",
							metadata: {
								...task.metadata,
								failed_at: /* @__PURE__ */ new Date(),
								error: maxTransfersErrorMessage
							}
						}
					});
					await agentSessionManager.endSession(requestId);
					unregisterStreamHelper(requestId);
					return {
						success: false,
						error: maxTransfersErrorMessage,
						iterations
					};
				} finally {
					span.end();
					await new Promise((resolve) => setImmediate(resolve));
					await flushBatchProcessor();
				}
			});
		} catch (error) {
			const rootCause = unwrapError(error);
			const errorMessage = rootCause.message;
			const errorStack = rootCause.stack;
			logger.error({
				errorMessage,
				errorStack
			}, "Error in execution handler");
			return tracer.startActiveSpan("execution_handler.execute", {}, async (span) => {
				try {
					span.setAttributes({
						"ai.response.content": "Hmm.. It seems I might be having some issues right now. Please clear the chat and try again.",
						"ai.response.timestamp": (/* @__PURE__ */ new Date()).toISOString(),
						"subAgent.name": agent?.subAgents[currentAgentId]?.name,
						"subAgent.id": currentAgentId
					});
					setSpanWithError(span, rootCause);
					await sseHelper.writeOperation(errorOp(`Execution error: ${errorMessage}`, currentAgentId || "system"));
					if (task) await updateTask(runDbClient_default)({
						taskId: task.id,
						scopes: {
							tenantId,
							projectId
						},
						data: {
							status: "failed",
							metadata: {
								...task.metadata,
								failed_at: /* @__PURE__ */ new Date(),
								error: errorMessage
							}
						}
					});
					await agentSessionManager.endSession(requestId);
					unregisterStreamHelper(requestId);
					return {
						success: false,
						error: errorMessage,
						iterations
					};
				} finally {
					span.end();
					await new Promise((resolve) => setImmediate(resolve));
					await flushBatchProcessor();
				}
			});
		}
	}
};

//#endregion
export { ExecutionHandler };