import { createLogger } from "../../utils/logger/index.js";
import { init_logger } from "../../utils/logger.js";
import { resolveAgentTurnTimeoutMs, runAgentTurnWithTimeout } from "../orchestration/run-agent-turn-with-timeout.js";
import { wrapStreamFnForXopcExtensions } from "./xopc-stream-bridge.js";
import { acquireEmbeddedSessionRunner, resolveEmbeddedTranscriptInputs } from "./session-runner.js";
import { getAssistantTurnErrorMessage, isAssistantTurnAborted, isAssistantTurnFailed, maybeRetryTurnAfterTransientLlmFailure } from "../orchestration/llm-turn-retry.js";
import { abortEmbeddedRun, queueEmbeddedSteer, registerEmbeddedRun, unregisterEmbeddedRun } from "./runs.js";
import { lastAssistantPlainText, subscribeEmbeddedSessionEvents } from "./subscribe-session.js";
import { detectToolLoops } from "../orchestration/loop-guard.js";
//#region src/agent/embedded/run-turn.ts
init_logger();
const log = createLogger("EmbeddedRun");
const LOG_PREVIEW_MAX_CHARS = 300;
function truncateForLog(value, maxChars = LOG_PREVIEW_MAX_CHARS) {
	return value.length > maxChars ? `${value.slice(0, maxChars)}…` : value;
}
function extractTextFromContent(content) {
	if (typeof content === "string") return content;
	if (!Array.isArray(content)) return "";
	return content.filter((block) => {
		return !!block && typeof block === "object" && block.type === "text";
	}).map((block) => block.text).join("");
}
function extractRecentToolCalls(messages) {
	const calls = [];
	for (const message of messages) {
		if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
		for (const block of message.content) if (block && typeof block === "object" && block.type === "toolCall") {
			const toolCall = block;
			calls.push({
				name: toolCall.name,
				params: toolCall.arguments
			});
		}
	}
	return calls;
}
function getLastUserMessagePreview(messages) {
	for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex--) {
		const message = messages[messageIndex];
		if (message?.role !== "user") continue;
		const text = extractTextFromContent(message.content).trim();
		return text ? truncateForLog(text) : void 0;
	}
}
function requireEmbeddedModel(model, modelRef) {
	if (!model?.id || !model?.provider) throw new Error(`Invalid model for embedded run: ${modelRef}`);
	return model;
}
function userMessageToPromptText(message) {
	const content = message.content;
	if (typeof content === "string") return content;
	if (Array.isArray(content)) return content.filter((block) => {
		return !!block && typeof block === "object" && block.type === "text";
	}).map((block) => block.text).join("");
	return "";
}
function userMessageToPromptImages(message) {
	const content = message.content;
	if (!Array.isArray(content)) return [];
	const images = [];
	for (const block of content) {
		if (!block || typeof block !== "object") continue;
		const typedBlock = block;
		if (typedBlock.type !== "image" || typeof typedBlock.data !== "string" || typedBlock.data.length === 0) continue;
		images.push({
			type: "image",
			data: typedBlock.data,
			mimeType: typeof typedBlock.mimeType === "string" ? typedBlock.mimeType : "image/png"
		});
	}
	return images;
}
async function runXopcEmbeddedTurn(params) {
	const { sessionKey, runId, userMessage, model, tools, systemPrompt, thinkingLevel, workspaceDir, sessionStore, onEvent } = params;
	const timeoutMs = params.timeoutMs || resolveAgentTurnTimeoutMs();
	const resolvedModel = requireEmbeddedModel(model, params.modelRef);
	const transcript = await resolveEmbeddedTranscriptInputs(sessionStore, sessionKey);
	let runner;
	let unsubscribe;
	try {
		runner = await acquireEmbeddedSessionRunner({
			sessionKey,
			sessionId: transcript.sessionId,
			sessionFile: transcript.sessionFile,
			sessionsDir: transcript.sessionsDir,
			hadSessionFile: transcript.hadSessionFile,
			workspaceDir,
			model: resolvedModel,
			modelRef: params.modelRef,
			tools,
			systemPrompt,
			thinkingLevel: thinkingLevel ?? "medium"
		});
		const { session, reused } = runner;
		const streamFnWithXopcExtensions = wrapStreamFnForXopcExtensions(session.agent.streamFn);
		const loggingStreamFn = (streamModel, context, options) => {
			const loopGuard = detectToolLoops(extractRecentToolCalls(context.messages));
			let effectiveContext = context;
			if (loopGuard.injection || loopGuard.hiddenTools.size > 0) {
				const messages = loopGuard.injection ? [...context.messages, {
					role: "user",
					content: loopGuard.injection,
					timestamp: Date.now()
				}] : context.messages;
				const contextTools = loopGuard.hiddenTools.size > 0 && context.tools ? context.tools.filter((t) => !loopGuard.hiddenTools.has(t.name)) : context.tools;
				effectiveContext = {
					...context,
					messages,
					tools: contextTools
				};
			}
			log.debug({
				sessionKey,
				runId,
				reusedRunner: reused,
				modelRef: `${streamModel.provider}/${streamModel.id}`,
				systemPromptLength: effectiveContext.systemPrompt?.length ?? 0,
				messageCount: effectiveContext.messages.length,
				toolCount: effectiveContext.tools?.length ?? 0,
				lastUserMessagePreview: getLastUserMessagePreview(effectiveContext.messages),
				loopWarningInjected: !!loopGuard.injection,
				hiddenToolCount: loopGuard.hiddenTools.size
			}, "Sending messages to AI");
			return streamFnWithXopcExtensions(streamModel, effectiveContext, options);
		};
		session.agent.streamFn = loggingStreamFn;
		if (onEvent) unsubscribe = subscribeEmbeddedSessionEvents(session, onEvent);
		const handle = {
			sessionKey,
			sessionId: transcript.sessionId,
			runId,
			session,
			abort: async () => {
				await session.abort();
			}
		};
		registerEmbeddedRun(handle);
		const abortListener = () => {
			session.abort();
		};
		if (params.abortSignal) {
			if (params.abortSignal.aborted) {
				await session.abort();
				return {
					ok: false,
					errorMessage: "aborted"
				};
			}
			params.abortSignal.addEventListener("abort", abortListener, { once: true });
		}
		try {
			await runAgentTurnWithTimeout(session.agent, async () => {
				const text = userMessageToPromptText(userMessage);
				const images = [...params.images ?? [], ...userMessageToPromptImages(userMessage)];
				await session.prompt(text, images.length > 0 ? { images } : void 0);
				await session.agent.waitForIdle();
				await maybeRetryTurnAfterTransientLlmFailure(session.agent, {
					sessionKey,
					log
				});
			}, timeoutMs);
			if (isAssistantTurnAborted(session.agent)) return {
				ok: true,
				lastAssistantText: lastAssistantPlainText(session)
			};
			if (isAssistantTurnFailed(session.agent)) return {
				ok: false,
				errorMessage: getAssistantTurnErrorMessage(session.agent) ?? "Assistant turn failed",
				lastAssistantText: lastAssistantPlainText(session)
			};
			return {
				ok: true,
				lastAssistantText: lastAssistantPlainText(session)
			};
		} finally {
			params.abortSignal?.removeEventListener("abort", abortListener);
			unregisterEmbeddedRun(handle);
		}
	} catch (err) {
		const em = err instanceof Error ? err.message : String(err);
		log.error({
			err,
			sessionKey,
			runId
		}, `Embedded run failed: ${em}`);
		onEvent?.({
			type: "error",
			content: em
		});
		return {
			ok: false,
			errorMessage: em
		};
	} finally {
		unsubscribe?.();
		try {
			runner?.piSm.flushPendingToolResults?.();
		} catch {}
		runner?.release();
	}
}
//#endregion
export { abortEmbeddedRun, queueEmbeddedSteer, runXopcEmbeddedTurn };

//# sourceMappingURL=run-turn.js.map