import manageDbPool_default from "../../../data/db/manageDbPool.js";
import runDbClient_default from "../../../data/db/runDbClient.js";
import { getLogger as getLogger$1 } from "../../../logger.js";
import { A2AClient } from "../a2a/client.js";
import { DELEGATION_TOOL_BACKOFF_EXPONENT, DELEGATION_TOOL_BACKOFF_INITIAL_INTERVAL_MS, DELEGATION_TOOL_BACKOFF_MAX_ELAPSED_TIME_MS, DELEGATION_TOOL_BACKOFF_MAX_INTERVAL_MS } from "../constants/execution-limits/index.js";
import { toolSessionManager } from "./services/ToolSessionManager.js";
import { saveA2AMessageResponse } from "../data/conversations.js";
import { agentSessionManager } from "../session/AgentSession.js";
import { getUserIdFromContext } from "../types/executionContext.js";
import { getExternalAgentRelationsForTargetSubAgent, getToolsForSubAgent, getTransferRelationsForTargetSubAgent } from "../utils/project.js";
import { ContextResolver } from "../context/ContextResolver.js";
import "../context/index.js";
import { z } from "@hono/zod-openapi";
import { CredentialStuffer, SPAN_KEYS, TemplateEngine, createMessage, generateId, generateServiceToken, getInProcessFetch, getMcpToolById, headers, withRef } from "@inkeep/agents-core";
import { trace } from "@opentelemetry/api";
import { tool } from "ai";

//#region src/domains/run/agents/relationTools.ts
const logger = getLogger$1("relationships Tools");
const A2A_RETRY_STATUS_CODES = [
	"429",
	"500",
	"502",
	"503",
	"504"
];
z.object({
	conversationId: z.string(),
	threadId: z.string(),
	streamRequestId: z.string().optional(),
	streamBaseUrl: z.string().optional(),
	apiKey: z.string().optional()
});
function getDelegationMetadata(params) {
	const { isInternal, callingAgentId, delegationId, metadata } = params;
	const { apiKey: _apiKey, ...safeMetadata } = metadata;
	return {
		...isInternal ? metadata : safeMetadata,
		isDelegation: true,
		delegationId,
		...isInternal ? { fromSubAgentId: callingAgentId } : { fromExternalAgentId: callingAgentId }
	};
}
const generateTransferToolDescription = (config) => {
	let toolsSection = "";
	let transferSection = "";
	if (config.transferRelations && config.transferRelations.length > 0) transferSection = `

Can Transfer To:
${config.transferRelations.map((transfer) => `  - ${transfer.name || transfer.id}: ${transfer.description || "No description available"}`).join("\n")}`;
	let delegateSection = "";
	if (config.delegateRelations && config.delegateRelations.length > 0) delegateSection = `

Can Delegate To:
${config.delegateRelations.map((delegate) => `  - ${delegate.config.name || delegate.config.id}: ${delegate.config.description || "No description available"} (${delegate.type})`).join("\n")}`;
	if (config.tools && config.tools.length > 0) toolsSection = `

Available Tools & Capabilities:
${config.tools.map((tool$1) => {
		const toolsList = tool$1.availableTools?.map((t) => `  - ${t.name}: ${t.description || "No description available"}`).join("\n") || "";
		return `MCP Server: ${tool$1.name}\n${toolsList}`;
	}).join("\n\n")}`;
	return `🚨 CRITICAL TRANSFER PROTOCOL 🚨

This tool immediately transfers conversation control to agent ${config.id}. 

⚠️ MANDATORY BEHAVIOR:
1. DO NOT write any response to the user
2. DO NOT explain what you're doing  
3. DO NOT provide partial answers
4. ONLY call this tool and STOP

Agent Information:
- ID: ${config.id}
- Name: ${config.name ?? "No name provided"}
- Description: ${config.description ?? "No description provided"}${toolsSection}${transferSection}${delegateSection}

🔄 Use when: The user's request is better handled by this specialized agent.

⛔ VIOLATION WARNING: Any text generation before/after this tool call will create a disjointed user experience. The receiving agent will provide the complete response.`;
};
const generateDelegateToolDescription = (delegateRelation) => {
	const config = delegateRelation.config;
	let toolsSection = "";
	let transferSection = "";
	let delegateSection = "";
	if (delegateRelation.type === "internal" && "tools" in config) {
		const agentConfig = config;
		if (agentConfig.tools && agentConfig.tools.length > 0) toolsSection = `

Available Tools & Capabilities:
${agentConfig.tools.map((tool$1) => {
			const toolsList = tool$1.availableTools?.map((t) => `  - ${t.name}: ${t.description || "No description available"}`).join("\n") || "";
			return `MCP Server: ${tool$1.name}\n${toolsList}`;
		}).join("\n\n")}`;
		if (agentConfig.transferRelations && agentConfig.transferRelations.length > 0) transferSection = `

Can Transfer To:
${agentConfig.transferRelations.map((transfer) => `  - ${transfer.name || transfer.id}: ${transfer.description || "No description available"}`).join("\n")}`;
		if (agentConfig.delegateRelations && agentConfig.delegateRelations.length > 0) delegateSection = `

Can Delegate To:
${agentConfig.delegateRelations.map((delegate) => `  - ${delegate.config.name || delegate.config.id}: ${delegate.config.description || "No description available"} (${delegate.type})`).join("\n")}`;
	}
	return `Delegate a specific task to another agent and wait for their response.

Agent Information:
- ID: ${config.id}
- Name: ${config.name}
- Description: ${config.description || "No description provided"}
- Type: ${delegateRelation.type}${toolsSection}${transferSection}${delegateSection}

Delegate a specific task to agent ${config.id} when it can do relevant work. The delegated agent will return results that you can incorporate into your response to the user.

NOTE: Unlike transfers, delegation returns control back to you with the delegated agent's results.`;
};
const createTransferToAgentTool = ({ transferConfig, callingAgentId, streamRequestId }) => {
	return tool({
		description: generateTransferToolDescription(transferConfig),
		inputSchema: z.object({}),
		execute: async () => {
			const activeSpan = trace.getActiveSpan();
			if (activeSpan) activeSpan.setAttributes({
				[SPAN_KEYS.TRANSFER_FROM_SUB_AGENT_ID]: callingAgentId,
				[SPAN_KEYS.TRANSFER_TO_SUB_AGENT_ID]: transferConfig.id ?? "unknown"
			});
			logger.info({
				transferTo: transferConfig.id ?? "unknown",
				fromSubAgent: callingAgentId
			}, "invoked transferToAgentTool");
			if (streamRequestId) agentSessionManager.recordEvent(streamRequestId, "transfer", callingAgentId, {
				fromSubAgent: callingAgentId,
				targetSubAgent: transferConfig.id ?? "unknown",
				reason: `Transfer to ${transferConfig.name || transferConfig.id}`
			});
			const transferResult = {
				type: "transfer",
				targetSubAgentId: transferConfig.id ?? "unknown",
				fromSubAgentId: callingAgentId
			};
			logger.info({
				transferResult,
				transferResultKeys: Object.keys(transferResult)
			}, "[DEBUG] Transfer tool returning");
			return transferResult;
		}
	});
};
function createDelegateToAgentTool({ delegateConfig, callingAgentId, executionContext, contextId, metadata, sessionId, credentialStoreRegistry, agentRunContext }) {
	const { tenantId, projectId, agentId, project } = executionContext;
	return tool({
		description: generateDelegateToolDescription(delegateConfig),
		inputSchema: z.object({ message: z.string() }),
		execute: async (input, context$1) => {
			const delegationId = `del_${generateId()}`;
			const activeSpan = trace.getActiveSpan();
			if (activeSpan) activeSpan.setAttributes({
				[SPAN_KEYS.DELEGATION_FROM_SUB_AGENT_ID]: callingAgentId,
				[SPAN_KEYS.DELEGATION_TO_SUB_AGENT_ID]: delegateConfig.config.id ?? "unknown",
				[SPAN_KEYS.DELEGATION_ID]: delegationId,
				[SPAN_KEYS.DELEGATION_TYPE]: delegateConfig.type
			});
			if (metadata.streamRequestId) agentSessionManager.recordEvent(metadata.streamRequestId, "delegation_sent", callingAgentId, {
				delegationId,
				fromSubAgent: callingAgentId,
				targetSubAgent: delegateConfig.config.id,
				taskDescription: input.message
			});
			const isInternal = delegateConfig.type === "internal";
			const isExternal = delegateConfig.type === "external";
			const isTeam = delegateConfig.type === "team";
			let resolvedHeaders = {};
			if (isExternal) {
				if ((delegateConfig.config.credentialReferenceId || delegateConfig.config.headers) && credentialStoreRegistry) {
					const credentialStuffer = new CredentialStuffer(credentialStoreRegistry, new ContextResolver(executionContext, credentialStoreRegistry));
					const credentialContext = {
						tenantId,
						projectId,
						conversationId: metadata.conversationId,
						contextConfigId: contextId,
						metadata
					};
					let storeReference;
					if (delegateConfig.config.credentialReferenceId) {
						const id = delegateConfig.config.credentialReferenceId;
						const credentialReference = project.credentialReferences?.[id];
						if (credentialReference) storeReference = {
							credentialStoreId: credentialReference.credentialStoreId,
							retrievalParams: credentialReference.retrievalParams || {}
						};
					}
					resolvedHeaders = await credentialStuffer.getCredentialHeaders({
						context: credentialContext,
						storeReference,
						headers: delegateConfig.config.headers || void 0
					});
				}
			} else if (isTeam) {
				const context$2 = await new ContextResolver(executionContext, credentialStoreRegistry).resolveHeaders(metadata.conversationId, contextId);
				for (const [key, value] of Object.entries(headers)) resolvedHeaders[key] = TemplateEngine.render(value, context$2, { strict: true });
				resolvedHeaders.Authorization = `Bearer ${await generateServiceToken({
					tenantId,
					projectId,
					originAgentId: agentId,
					targetAgentId: delegateConfig.config.id,
					initiatedBy: executionContext.metadata?.initiatedBy,
					appId: executionContext.metadata?.appId
				})}`;
			} else resolvedHeaders = {
				Authorization: `Bearer ${await generateServiceToken({
					tenantId,
					projectId,
					originAgentId: agentId,
					targetAgentId: delegateConfig.config.id,
					initiatedBy: executionContext.metadata?.initiatedBy,
					appId: executionContext.metadata?.appId
				})}`,
				"x-inkeep-tenant-id": tenantId,
				"x-inkeep-project-id": projectId,
				"x-inkeep-agent-id": agentId,
				"x-inkeep-sub-agent-id": delegateConfig.config.id
			};
			const a2aClient = new A2AClient(delegateConfig.config.baseUrl, {
				headers: resolvedHeaders,
				retryConfig: {
					strategy: "backoff",
					retryConnectionErrors: true,
					statusCodes: [...A2A_RETRY_STATUS_CODES],
					backoff: {
						initialInterval: DELEGATION_TOOL_BACKOFF_INITIAL_INTERVAL_MS,
						maxInterval: DELEGATION_TOOL_BACKOFF_MAX_INTERVAL_MS,
						exponent: DELEGATION_TOOL_BACKOFF_EXPONENT,
						maxElapsedTime: DELEGATION_TOOL_BACKOFF_MAX_ELAPSED_TIME_MS
					}
				},
				...isInternal || isTeam ? { fetchFn: getInProcessFetch() } : {}
			});
			const delegationMeta = {
				...getDelegationMetadata({
					isInternal,
					callingAgentId,
					delegationId,
					metadata
				}),
				...agentRunContext?.durableWorkflowRunId ? { durable_workflow_run_id: agentRunContext.durableWorkflowRunId } : {},
				...agentRunContext?.delegatedToolApproval ? { approved_tool_calls: JSON.stringify({ [agentRunContext.delegatedToolApproval.toolName]: [{
					approved: agentRunContext.delegatedToolApproval.approved,
					reason: agentRunContext.delegatedToolApproval.reason,
					originalToolCallId: agentRunContext.delegatedToolApproval.toolCallId
				}] }) } : {}
			};
			const messageToSend = {
				role: "agent",
				parts: [{
					text: input.message,
					kind: "text"
				}],
				messageId: generateId(),
				kind: "message",
				contextId,
				metadata: delegationMeta
			};
			logger.info({ messageToSend }, "messageToSend");
			await createMessage(runDbClient_default)({
				scopes: {
					tenantId,
					projectId
				},
				data: {
					id: generateId(),
					conversationId: contextId,
					role: "agent",
					content: { text: input.message },
					visibility: isInternal ? "internal" : "external",
					messageType: "a2a-request",
					fromSubAgentId: callingAgentId,
					...isInternal ? { toSubAgentId: delegateConfig.config.id } : { toExternalAgentId: delegateConfig.config.id }
				}
			});
			logger.info({ messageToSend }, "Created message in database");
			const response = await a2aClient.sendMessage({ message: messageToSend });
			if (response.error) throw new Error(response.error.message);
			await saveA2AMessageResponse(response, {
				tenantId,
				projectId,
				conversationId: contextId,
				messageType: "a2a-response",
				visibility: isInternal ? "internal" : "external",
				toSubAgentId: callingAgentId,
				...isInternal ? { fromSubAgentId: delegateConfig.config.id } : { fromExternalAgentId: delegateConfig.config.id }
			});
			if (sessionId && context$1?.toolCallId) {
				const toolResult = {
					toolCallId: context$1.toolCallId,
					toolName: `delegate_to_${delegateConfig.config.id}`,
					args: input,
					result: response.result,
					timestamp: Date.now()
				};
				toolSessionManager.recordToolResult(sessionId, toolResult);
			}
			if (metadata.streamRequestId) agentSessionManager.recordEvent(metadata.streamRequestId, "delegation_returned", callingAgentId, {
				delegationId,
				fromSubAgent: delegateConfig.config.id,
				targetSubAgent: callingAgentId,
				result: response.result
			});
			return {
				toolCallId: context$1?.toolCallId,
				result: response.result
			};
		}
	});
}
/**
* Build a transfer relation config for an internal relation.
* Fetches tools, transfer relations, and external agent relations for the target sub-agent.
*/
async function buildTransferRelationConfig(params, credentialStoreRegistry) {
	const { relation, executionContext, baseUrl, apiKey } = params;
	const { tenantId, projectId, project, agentId } = executionContext;
	const agent = executionContext.project.agents[agentId];
	const targetSubAgent = agent.subAgents?.[relation.id];
	if (!targetSubAgent) throw new Error(`Target sub-agent not found: ${relation.id}`);
	const targetToolsForSubAgent = getToolsForSubAgent({
		agent,
		project,
		subAgent: targetSubAgent
	});
	const targetAgentTools = await withRef(manageDbPool_default, executionContext.resolvedRef, async (db) => {
		return await Promise.all(targetToolsForSubAgent.map(async (item) => {
			const mcpTool = await getMcpToolById(db)({
				scopes: {
					tenantId,
					projectId
				},
				toolId: item.tool.id,
				credentialStoreRegistry,
				userId: getUserIdFromContext(executionContext)
			});
			if (!mcpTool) throw new Error(`Tool not found: ${item.tool.id}`);
			if (item.relationshipId) mcpTool.relationshipId = item.relationshipId;
			if (item.selectedTools && item.selectedTools.length > 0) {
				const selectedToolsSet = new Set(item.selectedTools);
				mcpTool.availableTools = mcpTool.availableTools?.filter((tool$1) => selectedToolsSet.has(tool$1.name)) || [];
			}
			return mcpTool;
		}));
	}) ?? [];
	const targetTransferRelations = getTransferRelationsForTargetSubAgent({
		agent,
		subAgentId: relation.id
	});
	const targetExternalAgentRelations = getExternalAgentRelationsForTargetSubAgent({
		agent,
		project,
		subAgentId: relation.id
	});
	const targetTransferRelationsConfig = targetTransferRelations.map((rel) => ({
		baseUrl,
		apiKey,
		id: rel.id,
		tenantId,
		projectId,
		agentId,
		name: rel.name,
		description: rel.description || void 0,
		prompt: "",
		delegateRelations: [],
		subAgentRelations: [],
		transferRelations: [],
		project
	}));
	const targetDelegateRelationsConfig = targetExternalAgentRelations.map((rel) => ({
		type: "external",
		config: {
			relationId: rel.relationId || `external-${rel.externalAgent.id}`,
			id: rel.externalAgent.id,
			name: rel.externalAgent.name,
			description: rel.externalAgent.description || "",
			ref: executionContext.resolvedRef,
			baseUrl: rel.externalAgent.baseUrl,
			headers: rel.headers || void 0,
			credentialReferenceId: rel.externalAgent.credentialReferenceId,
			relationType: "delegate"
		}
	}));
	return {
		baseUrl,
		apiKey,
		id: relation.id,
		tenantId,
		projectId,
		agentId,
		name: relation.name,
		description: relation.description || void 0,
		prompt: "",
		delegateRelations: targetDelegateRelationsConfig,
		subAgentRelations: [],
		transferRelations: targetTransferRelationsConfig,
		tools: targetAgentTools
	};
}

//#endregion
export { buildTransferRelationConfig, createDelegateToAgentTool, createTransferToAgentTool };