import { getLogger as getLogger$1 } from "../../../../logger.js";
import { emitWebhookEventFireAndForget } from "../../services/WebhookDeliveryService.js";
import { setSpanWithError as setSpanWithError$1, tracer } from "../../utils/tracer.js";
import { getStreamHelper } from "../../stream/stream-registry.js";
import { agentSessionManager } from "../../session/AgentSession.js";
import { hasToolCallWithPrefix, resolveGenerationResponse } from "../agent-types.js";
import { withJsonPostProcessing } from "../../utils/json-postprocessor.js";
import { extractTextFromParts } from "../../utils/message-parts.js";
import { buildStrippedPartsNote, stripIncompatibleOfficeParts } from "../../utils/model-file-support.js";
import { enforceOutputContract, resolveContractToolChoice } from "../output-contract.js";
import { configureModelSettings } from "./model-config.js";
import { handleStreamGeneration } from "../streaming/stream-handler.js";
import { V1_BREAKDOWN_SCHEMA } from "../versions/v1/PromptConfig.js";
import { handlePrepareStepCompression, handleStopWhenConditions } from "./ai-sdk-callbacks.js";
import { attachPromptCaching } from "./caching-actuator.js";
import { setupCompression } from "./compression.js";
import { buildConversationHistory, buildInitialMessages } from "./conversation-history.js";
import { formatFinalResponse } from "./response-formatting.js";
import { buildDataComponentsSchema } from "./schema-builder.js";
import { loadToolsAndPrompts } from "./tool-loading.js";
import { z } from "@hono/zod-openapi";
import { GENERATION_TYPES, SESSION_EVENT_AGENT_GENERATE, SPAN_KEYS, TRANSFER_TOOL_PREFIX } from "@inkeep/agents-core";
import { SpanStatusCode } from "@opentelemetry/api";
import { Output, generateText, streamText } from "ai";

//#region src/domains/run/agents/generation/generate.ts
const logger = getLogger$1("Agent");
function setupGenerationContext(ctx, runtimeContext) {
	const contextId = runtimeContext?.contextId || "default";
	const taskId = runtimeContext?.metadata?.taskId || "unknown";
	const streamRequestId = runtimeContext?.metadata?.streamRequestId;
	const sessionId = streamRequestId || "fallback-session";
	ctx.streamRequestId = streamRequestId;
	ctx.streamHelper = streamRequestId ? getStreamHelper(streamRequestId) : void 0;
	if (streamRequestId && ctx.artifactComponents.length > 0) agentSessionManager.updateArtifactComponents(streamRequestId, ctx.artifactComponents);
	const conversationId = runtimeContext?.metadata?.conversationId;
	if (conversationId) ctx.conversationId = conversationId;
	return {
		contextId,
		taskId,
		streamRequestId: streamRequestId ?? "",
		sessionId
	};
}
function buildBaseGenerationConfig(ctx, modelSettings, messages, sanitizedTools, compressor, originalMessageCount, timeoutMs, toolChoice = "auto", phase) {
	return {
		...modelSettings,
		toolChoice,
		messages,
		tools: sanitizedTools,
		prepareStep: async ({ messages: stepMessages, steps }) => {
			return await handlePrepareStepCompression(stepMessages, steps, compressor, originalMessageCount);
		},
		stopWhen: async ({ steps }) => {
			return await handleStopWhenConditions(ctx, steps);
		},
		experimental_telemetry: buildTelemetryConfig(ctx, phase),
		abortSignal: AbortSignal.timeout(timeoutMs)
	};
}
function buildTelemetryConfig(ctx, phase) {
	return {
		isEnabled: true,
		functionId: ctx.config.id,
		recordInputs: true,
		recordOutputs: true,
		metadata: {
			...phase && { phase },
			tenantId: ctx.config.tenantId,
			projectId: ctx.config.projectId,
			agentId: ctx.config.agentId,
			subAgentId: ctx.config.id,
			subAgentName: ctx.config.name,
			generationType: GENERATION_TYPES.SUB_AGENT_GENERATION,
			...ctx.conversationId && { conversationId: ctx.conversationId }
		}
	};
}
function computeGenerationType(parts, hasObject) {
	if ((parts || []).some((p) => p?.kind === "text") && hasObject) return "mixed_generation";
	if (hasObject) return "object_generation";
	return "text_generation";
}
function buildStructuredSuccessText(response) {
	const prelude = response.text?.trim();
	const json = JSON.stringify(response.output, null, 2);
	if (!prelude) return json;
	if (preludeEqualsOutput(prelude, response.output)) return json;
	return `${prelude}\n\n${json}`;
}
function preludeEqualsOutput(prelude, output) {
	try {
		return canonicalJsonString(JSON.parse(prelude)) === canonicalJsonString(output);
	} catch {
		return false;
	}
}
function canonicalJsonString(value) {
	return JSON.stringify(sortObjectKeysDeep(value));
}
function sortObjectKeysDeep(value) {
	if (value === null || typeof value !== "object") return value;
	if (Array.isArray(value)) return value.map(sortObjectKeysDeep);
	return Object.keys(value).sort().reduce((acc, key) => {
		acc[key] = sortObjectKeysDeep(value[key]);
		return acc;
	}, {});
}
function selectStructuredFallbackText(response) {
	if (response.text) return response.text;
	return response.steps?.map((s) => s?.text).filter((t) => Boolean(t)).join("\n\n") || "";
}
function mapPartsToEventParts(parts) {
	return (parts || []).map((part) => {
		if (part.kind === "text") return {
			type: "text",
			content: part.text ?? ""
		};
		if (part.kind === "data") {
			const data = part.data;
			return {
				type: Boolean(data?.artifactId && data?.toolCallId) ? "data_artifact" : "data_component",
				data: part.data
			};
		}
		logger.warn({
			kind: part.kind,
			op: "mapPartsToEventParts"
		}, "unknown part kind — mapping to empty text part");
		return {
			type: "text",
			content: ""
		};
	});
}
function resolveTextResponseAndWarn({ response, hasStructuredOutput, hasTransferToolCall, logger: log, warnContext }) {
	if (hasStructuredOutput && response.output) return buildStructuredSuccessText(response);
	if (hasTransferToolCall) return response.steps?.[response.steps.length - 1]?.text || "";
	if (hasStructuredOutput) {
		log.warn(warnContext, "Structured output expected but not produced; surfacing fallback text");
		return selectStructuredFallbackText(response);
	}
	return response.text || "";
}
function emitGenerationError(ctx, reason) {
	const { resolvedRef } = ctx.executionContext;
	if (resolvedRef && ctx.conversationId) emitWebhookEventFireAndForget({
		tenantId: ctx.executionContext.tenantId,
		projectId: ctx.executionContext.projectId,
		agentId: ctx.config.agentId,
		resolvedRef,
		eventType: "conversation.generation.error",
		data: {
			conversation: { id: ctx.conversationId },
			reason
		}
	}, "generation-error");
}
function handleGenerationError(ctx, error, span) {
	if (ctx.currentCompressor) ctx.currentCompressor.fullCleanup();
	ctx.currentCompressor = null;
	const errorToThrow = error instanceof Error ? error : new Error(String(error));
	logger.error({
		errorMessage: errorToThrow.message,
		errorStack: errorToThrow.stack,
		errorName: errorToThrow.name
	}, "Generation error in Agent");
	setSpanWithError$1(span, errorToThrow);
	span.end();
	emitGenerationError(ctx, errorToThrow.message);
	throw errorToThrow;
}
async function runGenerate(ctx, userParts, runtimeContext, options) {
	const textParts = extractTextFromParts(userParts);
	const dataParts = userParts.filter((part) => part.kind === "data" && part.data != null);
	const userMessage = `${textParts}${dataParts.length > 0 ? dataParts.map((part) => {
		const metadata = part.metadata;
		return `\n\n<structured_data${metadata?.source ? ` (source: ${metadata.source})` : ""}>\n${JSON.stringify(part.data, null, 2)}\n</structured_data>`;
	}).join("") : ""}`;
	const fileParts = userParts.filter((part) => part.kind === "file");
	const conversationIdForSpan = runtimeContext?.metadata?.conversationId;
	return tracer.startActiveSpan("agent.generate", { attributes: {
		"subAgent.id": ctx.config.id,
		"subAgent.name": ctx.config.name,
		"tenant.id": ctx.config.tenantId,
		"project.id": ctx.config.projectId,
		"agent.id": ctx.config.agentId,
		"agent.name": ctx.config.agentName,
		...conversationIdForSpan ? { "conversation.id": conversationIdForSpan } : {}
	} }, async (span) => {
		const { contextId, taskId, streamRequestId, sessionId } = setupGenerationContext(ctx, runtimeContext);
		try {
			const { systemPrompt, sanitizedTools, contextBreakdown: initialContextBreakdown, artifactsMessage } = await loadToolsAndPrompts(ctx, sessionId, streamRequestId || void 0, runtimeContext);
			const { conversationHistory, contextBreakdown } = await buildConversationHistory(ctx, contextId, taskId, userMessage, streamRequestId || void 0, initialContextBreakdown);
			const breakdownAttributes = {};
			for (const componentDef of V1_BREAKDOWN_SCHEMA) breakdownAttributes[componentDef.spanAttribute] = contextBreakdown.components[componentDef.key] ?? 0;
			breakdownAttributes["context.breakdown.total_tokens"] = contextBreakdown.total;
			span.setAttributes(breakdownAttributes);
			const { primaryModelSettings, modelSettings, hasStructuredOutput, hasContractEnforcement, timeoutMs } = configureModelSettings(ctx);
			const contractToolChoice = resolveContractToolChoice({
				resolvedAllowText: ctx.resolvedAllowText,
				hasStructuredOutput,
				hasArtifactComponents: Boolean(ctx.config.artifactComponents?.length)
			});
			const { compatible: compatibleFileParts, stripped } = stripIncompatibleOfficeParts(fileParts, primaryModelSettings.model ?? "");
			let effectiveUserMessage = userMessage;
			if (stripped.length > 0) {
				effectiveUserMessage = `${userMessage}\n\n${buildStrippedPartsNote(stripped)}`;
				logger.warn({
					agentId: ctx.config.agentId,
					modelId: primaryModelSettings.model,
					strippedParts: stripped
				}, "Stripped incompatible office document parts before generation");
				span.setAttribute("input.stripped_file_count", stripped.length);
			}
			const inlinePdfFileCount = compatibleFileParts.filter((part) => part.file.mimeType?.toLowerCase().startsWith("application/pdf") === true).length;
			span.setAttributes({
				"input.file_count": compatibleFileParts.length,
				"input.pdf_file_count": inlinePdfFileCount
			});
			let response;
			const toolsForLlm = options?.schemaOnlyTools ? Object.fromEntries(Object.entries(sanitizedTools).map(([k, v]) => [k, {
				...v,
				execute: void 0
			}])) : sanitizedTools;
			const messages = await buildInitialMessages(systemPrompt, conversationHistory, effectiveUserMessage, compatibleFileParts, artifactsMessage);
			const { originalMessageCount, compressor } = setupCompression(ctx, messages, sessionId, contextId, primaryModelSettings);
			const streamConfig = {
				...modelSettings,
				toolChoice: contractToolChoice
			};
			const shouldStream = ctx.isDelegatedAgent ? void 0 : ctx.streamHelper;
			let dataComponentsSchema = null;
			if (hasStructuredOutput) try {
				dataComponentsSchema = buildDataComponentsSchema(ctx);
			} catch (err) {
				logger.error({ err }, "Failed to build data components schema — continuing without structured output");
			}
			const baseConfig = buildBaseGenerationConfig(ctx, streamConfig, messages, toolsForLlm, compressor, originalMessageCount, timeoutMs, contractToolChoice, dataComponentsSchema ? "structured_generation" : void 0);
			const cachedGenerationConfig = attachPromptCaching(dataComponentsSchema ? {
				...baseConfig,
				output: Output.object({ schema: z.object({ dataComponents: z.array(dataComponentsSchema) }) }),
				providerOptions: {
					...baseConfig.providerOptions,
					anthropic: {
						...baseConfig.providerOptions?.anthropic ?? {},
						structuredOutputMode: "jsonTool"
					}
				}
			} : baseConfig, primaryModelSettings);
			const nonStreamingConfig = withJsonPostProcessing(cachedGenerationConfig);
			logger.info({
				hasStructuredOutput,
				shouldStream
			}, "Starting generation");
			let rawResponse;
			if (shouldStream) rawResponse = await handleStreamGeneration(ctx, streamText(cachedGenerationConfig), sessionId, contextId, !!dataComponentsSchema);
			else rawResponse = await generateText(nonStreamingConfig);
			logger.info({
				hasOutput: !!rawResponse.output,
				dataComponentsCount: rawResponse.output?.dataComponents?.length ?? 0,
				finishReason: rawResponse.finishReason
			}, "Generation completed");
			response = await resolveGenerationResponse(rawResponse);
			if (hasStructuredOutput && response.output) {
				response.object = response.output;
				logger.info({
					dataComponentsCount: response.output?.dataComponents?.length || 0,
					dataComponentNames: response.output?.dataComponents?.map((dc) => dc.name) || []
				}, "Processing response with data components");
			}
			const hasTransferToolCall = hasToolCallWithPrefix(TRANSFER_TOOL_PREFIX)(response);
			const textResponse = resolveTextResponseAndWarn({
				response,
				hasStructuredOutput,
				hasTransferToolCall,
				logger,
				warnContext: {
					agentId: ctx.config.agentId,
					conversationId: conversationIdForSpan,
					finishReason: response.finishReason
				}
			});
			enforceOutputContract({
				ctx,
				response,
				hasStructuredOutput,
				hasContractEnforcement,
				textResponse,
				span,
				logger
			});
			const actualInputTokens = response.totalUsage?.inputTokens ?? response.usage?.inputTokens;
			if (actualInputTokens != null) span.setAttribute(SPAN_KEYS.CONTEXT_BREAKDOWN_ACTUAL_INPUT_TOKENS, actualInputTokens);
			if (response.finishReason === "other") {
				const timeoutError = /* @__PURE__ */ new Error(`Generation terminated by timeout/abort signal`);
				logger.warn({
					finishReason: response.finishReason,
					conversationId: conversationIdForSpan
				}, "Generation terminated by timeout/abort signal");
				span.setAttributes({ [SPAN_KEYS.GENERATION_TIMEOUT_MS]: timeoutMs });
				setSpanWithError$1(span, timeoutError);
				emitGenerationError(ctx, "Generation terminated by timeout/abort signal");
			} else span.setStatus({ code: SpanStatusCode.OK });
			span.end();
			const formattedResponse = await formatFinalResponse(ctx, response, textResponse, sessionId, contextId);
			if (streamRequestId) {
				const generationType = computeGenerationType(formattedResponse.formattedContent?.parts, !!response.object);
				agentSessionManager.recordEvent(streamRequestId, SESSION_EVENT_AGENT_GENERATE, ctx.config.id, {
					parts: mapPartsToEventParts(formattedResponse.formattedContent?.parts),
					generationType
				});
			}
			if (compressor) compressor.fullCleanup();
			ctx.currentCompressor = null;
			return formattedResponse;
		} catch (error) {
			handleGenerationError(ctx, error, span);
		}
	});
}

//#endregion
export { buildBaseGenerationConfig, buildStructuredSuccessText, buildTelemetryConfig, computeGenerationType, handleGenerationError, mapPartsToEventParts, resolveTextResponseAndWarn, runGenerate, selectStructuredFallbackText, setupGenerationContext };