import runDbClient_default from "../../../../data/db/runDbClient.js";
import { getLogger as getLogger$1 } from "../../../../logger.js";
import { emitWebhookEventFireAndForget } from "../../services/WebhookDeliveryService.js";
import { SENTINEL_KEY } from "../../constants/artifact-syntax.js";
import { stripInternalFields } from "../../utils/select-filter.js";
import { stripBinaryDataForObservability } from "../../services/blob-storage/artifact-binary-sanitizer.js";
import { agentSessionManager } from "../../session/AgentSession.js";
import { generateToolId } from "../../utils/agent-operations.js";
import { isToolResultDenied } from "../../utils/tool-result.js";
import { buildToolResultForConversationHistory } from "../generation/tool-result-for-conversation-history.js";
import { buildToolResultForModelInput } from "../generation/tool-result-for-model-input.js";
import { getRelationshipIdForTool } from "./tool-utils.js";
import { DELEGATE_TOOL_PREFIX, DURABLE_APPROVAL_ARTIFACT_TYPE, LOAD_SKILL_TOOL, SAVE_TOOL_RESULT_TOOL, SESSION_EVENT_TOOL_CALL, SESSION_EVENT_TOOL_RESULT, TRANSFER_TOOL_PREFIX, createMessage, generateId, parseEmbeddedJson, unwrapError } from "@inkeep/agents-core";
import { trace } from "@opentelemetry/api";

//#region src/domains/run/agents/tools/tool-wrapper.ts
const logger = getLogger$1("Agent");
function chunkString(s, size = 16) {
	const out = [];
	for (let i = 0; i < s.length; i += size) out.push(s.slice(i, i + size));
	return out;
}
function sanitizeToolsForAISDK(tools) {
	const sanitizedTools = {};
	for (const [originalKey, toolDef] of Object.entries(tools)) {
		let sanitizedKey = originalKey.replace(/[^a-zA-Z0-9_-]/g, "_");
		sanitizedKey = sanitizedKey.replace(/_+/g, "_");
		sanitizedKey = sanitizedKey.replace(/^_+|_+$/g, "");
		if (!sanitizedKey || sanitizedKey.length === 0) sanitizedKey = "unnamed_tool";
		if (sanitizedKey.length > 100) sanitizedKey = sanitizedKey.substring(0, 100);
		let sanitizedId = (toolDef.id || originalKey).replace(/[^a-zA-Z0-9_.-]/g, "_");
		sanitizedId = sanitizedId.replace(/_+/g, "_");
		sanitizedId = sanitizedId.replace(/^_+|_+$/g, "");
		if (sanitizedId.length > 128) sanitizedId = sanitizedId.substring(0, 128);
		sanitizedTools[sanitizedKey] = {
			...toolDef,
			id: sanitizedId
		};
	}
	return sanitizedTools;
}
function wrapToolWithStreaming(ctx, toolName, toolDefinition, streamRequestId, toolType, options) {
	if (!toolDefinition || typeof toolDefinition !== "object" || typeof toolDefinition.execute !== "function") return toolDefinition;
	const relationshipId = getRelationshipIdForTool(ctx, toolName, toolType);
	const originalExecute = toolDefinition.execute;
	return {
		...toolDefinition,
		toModelOutput: ({ output }) => buildToolResultForModelInput(output),
		execute: async (args, context$1) => {
			const startTime = Date.now();
			const toolCallId = context$1?.toolCallId || generateToolId();
			const streamHelper = ctx.isDelegatedAgent ? void 0 : ctx.streamHelper;
			const activeSpan = trace.getActiveSpan();
			if (activeSpan) {
				const attributes = {
					"conversation.id": ctx.conversationId,
					"tool.purpose": toolDefinition.description || "No description provided",
					"ai.toolType": toolType || "unknown",
					"subAgent.name": ctx.config.name || "unknown",
					"subAgent.id": ctx.config.id || "unknown",
					"agent.id": ctx.config.agentId || "unknown"
				};
				if (options?.mcpServerId) attributes["ai.toolCall.mcpServerId"] = options.mcpServerId;
				if (options?.mcpServerName) attributes["ai.toolCall.mcpServerName"] = options.mcpServerName;
				activeSpan.setAttributes(attributes);
			}
			const isInternalTool = toolName.includes(SAVE_TOOL_RESULT_TOOL) || toolName.startsWith(TRANSFER_TOOL_PREFIX);
			const hideToolFromUiStream = isInternalTool || toolName.startsWith(DELEGATE_TOOL_PREFIX) || toolName === LOAD_SKILL_TOOL || toolName === "get_reference_artifact";
			const hideToolFromTraceEvents = isInternalTool || toolName.startsWith(DELEGATE_TOOL_PREFIX) || toolName === LOAD_SKILL_TOOL;
			const isDurableDelegation = !!ctx.durableWorkflowRunId && toolName.startsWith(DELEGATE_TOOL_PREFIX);
			const hideToolFromConversationHistory = hideToolFromTraceEvents && !isDurableDelegation;
			const needsApproval = options?.needsApproval || false;
			const isPreApproved = !!(ctx.durableWorkflowRunId ? ctx.approvedToolCalls?.[toolCallId] : void 0);
			if (streamRequestId && streamHelper && !hideToolFromUiStream && !isPreApproved) {
				const inputText = JSON.stringify(args ?? {});
				await streamHelper.writeToolInputStart({
					toolCallId,
					toolName
				});
				for (const part of chunkString(inputText, 16)) await streamHelper.writeToolInputDelta({
					toolCallId,
					inputTextDelta: part
				});
				await streamHelper.writeToolInputAvailable({
					toolCallId,
					toolName,
					input: args ?? {},
					providerMetadata: context$1?.providerMetadata
				});
			}
			if (streamRequestId && !hideToolFromTraceEvents) {
				const toolCallData = {
					toolName,
					input: args,
					toolCallId,
					relationshipId,
					inDelegatedAgent: ctx.isDelegatedAgent
				};
				if (needsApproval) {
					toolCallData.needsApproval = true;
					toolCallData.conversationId = ctx.conversationId;
				}
				await agentSessionManager.recordEvent(streamRequestId, SESSION_EVENT_TOOL_CALL, ctx.config.id, toolCallData);
			}
			try {
				const artifactParser = streamRequestId ? agentSessionManager.getArtifactParser(streamRequestId) : null;
				const parsedArgsForResolution = artifactParser ? parseEmbeddedJson(args) : args;
				const resolvedArgs = artifactParser ? await artifactParser.resolveArgs(parsedArgsForResolution) : args;
				const aiToolDef = toolDefinition;
				const validationSchema = aiToolDef.baseInputSchema ?? aiToolDef.parameters;
				const resolvedChanged = JSON.stringify(parsedArgsForResolution) !== JSON.stringify(resolvedArgs);
				if (artifactParser && validationSchema?.safeParse && resolvedChanged) {
					const validation = validationSchema.safeParse(resolvedArgs);
					if (!validation.success) {
						const mismatchDetails = resolvedArgs && typeof resolvedArgs === "object" && !Array.isArray(resolvedArgs) ? Object.entries(resolvedArgs).map(([key, val]) => {
							return `"${key}" resolved to ${val === null ? "null" : Array.isArray(val) ? "array" : typeof val}`;
						}).join(", ") : `resolved to ${Array.isArray(resolvedArgs) ? "array" : typeof resolvedArgs}`;
						throw new Error(`Tool chaining ${SENTINEL_KEY.SELECT} resolved to the wrong type for '${toolName}'. ${mismatchDetails}. ${validation.error.message}. Your ${SENTINEL_KEY.SELECT} expression likely returns an object or array where the tool expects a primitive (string/number/boolean). Drill deeper in your ${SENTINEL_KEY.SELECT} path — e.g. add ".text", ".name", or ".id" to extract the specific field. Check _structureHints.terminalPaths in the source tool result for leaf fields.`);
					}
				}
				const result = await originalExecute(resolvedArgs, context$1);
				const duration = Date.now() - startTime;
				if (ctx.durableWorkflowRunId && result && typeof result === "object") {
					const taskResult = result?.result;
					const findApprovalRequired = (parts) => {
						if (!Array.isArray(parts)) return void 0;
						for (const part of parts) if (part?.kind === "data") {
							const data = part.data;
							if (data?.type === DURABLE_APPROVAL_ARTIFACT_TYPE) return data;
						}
					};
					const findApprovalInArtifacts = (artifacts) => {
						if (!Array.isArray(artifacts)) return void 0;
						for (const artifact of artifacts) {
							const found = findApprovalRequired(artifact?.parts);
							if (found) return found;
						}
					};
					const approvalDataRaw = findApprovalRequired(taskResult?.parts) ?? findApprovalInArtifacts(taskResult?.artifacts);
					if (approvalDataRaw) {
						const approvalData = approvalDataRaw;
						const delegatedToolCallId = approvalData.toolCallId;
						const delegatedToolName = approvalData.toolName;
						if (typeof delegatedToolCallId !== "string" || !delegatedToolCallId) {
							logger.error({
								approvalData,
								parentToolName: toolName
							}, "Malformed durable-approval-required artifact: invalid toolCallId");
							return result;
						}
						if (typeof delegatedToolName !== "string" || !delegatedToolName) {
							logger.error({
								approvalData,
								parentToolName: toolName
							}, "Malformed durable-approval-required artifact: invalid toolName");
							return result;
						}
						ctx.pendingDurableApproval = {
							toolCallId,
							toolName,
							args: resolvedArgs,
							delegatedApproval: {
								toolCallId: delegatedToolCallId,
								toolName: delegatedToolName,
								args: approvalData.args,
								subAgentId: toolName.replace(DELEGATE_TOOL_PREFIX, "")
							}
						};
						return result;
					}
				}
				if (ctx.pendingDurableApproval) return result;
				const toolResultConversationId = ctx.conversationId;
				if (streamRequestId && !hideToolFromConversationHistory && toolResultConversationId) try {
					const session = agentSessionManager.getSession(streamRequestId);
					const messageId = generateId();
					const taskId = session ? `task_${toolResultConversationId}-${session.messageId}` : `tool_result_${messageId}`;
					const messageContent = await buildToolResultForConversationHistory(ctx, toolName, args, result, toolCallId, toolResultConversationId, messageId, taskId, { skipArtifactCreation: options?.skipArtifactCreation });
					await createMessage(runDbClient_default)({
						scopes: {
							tenantId: ctx.config.tenantId,
							projectId: ctx.config.projectId
						},
						data: {
							id: messageId,
							conversationId: toolResultConversationId,
							taskId,
							role: "assistant",
							content: messageContent,
							visibility: "internal",
							messageType: "tool-result",
							fromSubAgentId: ctx.config.id,
							metadata: { a2a_metadata: {
								toolName,
								toolCallId,
								toolArgs: args,
								toolOutput: stripBinaryDataForObservability(result),
								timestamp: Date.now(),
								delegationId: ctx.delegationId,
								isDelegated: ctx.isDelegatedAgent
							} }
						}
					});
				} catch (error) {
					logger.warn({
						error,
						toolName,
						toolCallId,
						conversationId: toolResultConversationId
					}, "Failed to store tool result in conversation history");
				}
				if (streamRequestId && !hideToolFromTraceEvents) agentSessionManager.recordEvent(streamRequestId, SESSION_EVENT_TOOL_RESULT, ctx.config.id, {
					toolName,
					output: stripBinaryDataForObservability(stripInternalFields(result)),
					toolCallId,
					duration,
					relationshipId,
					needsApproval,
					inDelegatedAgent: ctx.isDelegatedAgent
				});
				const isDeniedResult = isToolResultDenied(result);
				if (streamRequestId && streamHelper && !hideToolFromUiStream) if (isDeniedResult) await streamHelper.writeToolOutputDenied({ toolCallId });
				else await streamHelper.writeToolOutputAvailable({
					toolCallId,
					output: result
				});
				if (isDeniedResult) return result.reason ?? "Tool call was denied by the user.";
				return result;
			} catch (error) {
				const duration = Date.now() - startTime;
				const rootCause = unwrapError(error);
				const errorMessage = rootCause.message;
				if (streamRequestId && !hideToolFromTraceEvents) agentSessionManager.recordEvent(streamRequestId, SESSION_EVENT_TOOL_RESULT, ctx.config.id, {
					toolName,
					output: null,
					toolCallId,
					duration,
					error: errorMessage,
					relationshipId,
					needsApproval,
					inDelegatedAgent: ctx.isDelegatedAgent
				});
				if (streamRequestId && streamHelper && !hideToolFromUiStream) await streamHelper.writeToolOutputError({
					toolCallId,
					errorText: errorMessage
				});
				if (!hideToolFromTraceEvents) {
					const { resolvedRef } = ctx.executionContext;
					if (resolvedRef && ctx.conversationId) emitWebhookEventFireAndForget({
						tenantId: ctx.executionContext.tenantId,
						projectId: ctx.executionContext.projectId,
						agentId: ctx.config.agentId,
						resolvedRef,
						eventType: "conversation.tool.error",
						data: {
							conversation: { id: ctx.conversationId },
							tool: {
								id: relationshipId,
								name: toolName
							},
							mcpServer: options?.mcpServerName ? {
								id: options.mcpServerId,
								name: options.mcpServerName
							} : void 0,
							reason: errorMessage
						}
					}, "tool-error");
				}
				throw rootCause;
			}
		}
	};
}

//#endregion
export { sanitizeToolsForAISDK, wrapToolWithStreaming };