import { getApiKey, init_providers, resolveModel } from "../../providers/index.js";
import { JUDGE_REASON_EN } from "../../i18n/goals-bundle.js";
import { goalUiLocaleOrFallback, judgeFreeformBuiltinMessages, judgeResponseLanguageNote, localizeJudgeReasonText } from "./goal-locale.js";
import { complete } from "@earendil-works/pi-ai";
//#region src/agent/goals/judge.ts
init_providers();
const JUDGE_RESPONSE_SNIPPET_CHARS = 4e3;
/**
* Extract visible text from a pi-ai `AssistantMessage.content` array.
* Handles `TextContent` (`type: 'text'`) and falls back to `ThinkingContent`
* (`type: 'thinking'`) when the text blocks are empty — reasoning models
* (DeepSeek-R1, Qwen-thinking, etc.) may place the entire response inside
* thinking blocks, leaving the text portion blank.
*/
function extractAssistantText(content) {
	if (!Array.isArray(content)) return "";
	let textParts = "";
	let thinkingParts = "";
	for (const block of content) {
		if (!block || typeof block !== "object") continue;
		const typed = block;
		const blockType = typed.type;
		if (blockType === "text" && typeof typed.text === "string") textParts += typed.text;
		else if (blockType === "thinking") {
			const thinking = typeof typed.thinking === "string" ? typed.thinking : typeof typed.text === "string" ? typed.text : "";
			thinkingParts += thinking;
		}
	}
	return textParts.trim() ? textParts : thinkingParts;
}
/**
* Strip markdown code fences (opening AND closing) from raw model output.
* Handles `` ```json ``, `` ``` ``, and trailing `` ``` `` with optional whitespace.
*/
function stripCodeFences(raw) {
	let text = raw.trim();
	const openMatch = text.match(/^`{3,}[^\n]*\n?/);
	if (openMatch) text = text.slice(openMatch[0].length);
	const closeMatch = text.match(/\n?`{3,}\s*$/);
	if (closeMatch) text = text.slice(0, -closeMatch[0].length);
	return text.trim();
}
/** Mirrors `hermes_cli/goals.py` — strict judge, JSON-only reply. */
const JUDGE_SYSTEM_PROMPT = "You are a strict judge evaluating whether an autonomous agent has achieved a user's stated goal. You receive the goal text and the agent's most recent response. Your only job is to decide whether the goal is fully satisfied based on that response.\n\nA goal is DONE only when:\n- The response explicitly confirms the goal was completed, OR\n- The response clearly shows the final deliverable was produced, OR\n- The response explains the goal is unachievable / blocked / needs user input (treat this as DONE with reason describing the block).\n\nOtherwise the goal is NOT done — CONTINUE.\n\nReply ONLY with a single JSON object on one line:\n{\"done\": <true|false>, \"reason\": \"<one-sentence rationale>\"}";
const JUDGE_USER_PROMPT_TEMPLATE = "Goal:\n{goal}\n\nAgent's most recent response:\n{response}\n\nIs the goal satisfied?";
const DEFAULT_JUDGE_TIMEOUT_MS = 6e4;
function truncateGoalText(text, limit) {
	if (!text) return "";
	if (text.length <= limit) return text;
	return text.slice(0, limit) + "… [truncated]";
}
async function resolveGoalJudgeApiKey(model) {
	try {
		return await getApiKey(model.provider);
	} catch {
		return;
	}
}
function getAssistantMessageErrorReason(message) {
	if (!message || typeof message !== "object") return null;
	const record = message;
	const stopReason = record.stopReason;
	const errorMessage = typeof record.errorMessage === "string" ? record.errorMessage.trim() : "";
	if (stopReason === "error") return errorMessage || "Judge model call failed.";
	return null;
}
/** Parse judge JSON — fail-open to **continue** (Hermes semantics). */
function parseJudgeResponseFailOpen(raw) {
	if (!raw?.trim()) return {
		done: false,
		reason: JUDGE_REASON_EN.judge_returned_empty,
		parseFailed: true
	};
	const text = stripCodeFences(raw);
	let data = null;
	try {
		data = JSON.parse(text);
	} catch {
		const start = text.indexOf("{");
		const end = text.lastIndexOf("}");
		if (start !== -1 && end > start) try {
			data = JSON.parse(text.slice(start, end + 1));
		} catch {
			data = null;
		}
	}
	if (!data || typeof data !== "object") return {
		done: false,
		reason: JUDGE_REASON_EN.judge_reply_not_json,
		parseFailed: true
	};
	const doneVal = data.done;
	let done;
	if (typeof doneVal === "string") done = [
		"true",
		"yes",
		"1",
		"done"
	].includes(doneVal.trim().toLowerCase());
	else done = Boolean(doneVal);
	const reason = typeof data.reason === "string" ? data.reason.trim() : "";
	return {
		done,
		reason: reason || JUDGE_REASON_EN.no_reason_provided,
		parseFailed: false
	};
}
/**
* Ask the configured model whether the goal is satisfied.
* Fail-open: any error → `continue` (Hermes — turn budget is the backstop).
*/
async function judgeGoalHermesStyle(goal, lastResponse, judgeModelRef, signal, opts) {
	const locale = goalUiLocaleOrFallback(opts?.uiLocale);
	const b = judgeFreeformBuiltinMessages(locale);
	if (!goal.trim()) return {
		verdict: "skipped",
		reason: b.emptyGoal,
		parseFailed: false
	};
	if (!lastResponse.trim()) return {
		verdict: "continue",
		reason: b.emptyResponse,
		parseFailed: false
	};
	let model;
	try {
		model = resolveModel(judgeModelRef);
	} catch {
		return {
			verdict: "continue",
			reason: b.noModel,
			parseFailed: false
		};
	}
	const userContent = JUDGE_USER_PROMPT_TEMPLATE.replace("{goal}", truncateGoalText(goal, 2e3)).replace("{response}", truncateGoalText(lastResponse, JUDGE_RESPONSE_SNIPPET_CHARS)) + judgeResponseLanguageNote(locale);
	const timeoutMs = typeof opts?.judgeTimeoutMs === "number" && Number.isFinite(opts.judgeTimeoutMs) ? Math.max(5e3, Math.min(12e4, Math.floor(opts.judgeTimeoutMs))) : DEFAULT_JUDGE_TIMEOUT_MS;
	const controller = new AbortController();
	const timer = setTimeout(() => controller.abort(), timeoutMs);
	const merged = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
	try {
		const combinedUser = {
			role: "user",
			content: `${JUDGE_SYSTEM_PROMPT}\n\n${userContent}`,
			timestamp: Date.now()
		};
		const apiKey = await resolveGoalJudgeApiKey(model);
		const result = await complete(model, { messages: [combinedUser] }, {
			apiKey,
			maxTokens: 200,
			temperature: 0,
			signal: merged
		});
		if (getAssistantMessageErrorReason(result)) return {
			verdict: "continue",
			reason: b.callFailed,
			parseFailed: false
		};
		const { done, reason, parseFailed } = parseJudgeResponseFailOpen(extractAssistantText(result.content));
		const reasonOut = localizeJudgeReasonText(reason, locale);
		return {
			verdict: done ? "done" : "continue",
			reason: reasonOut,
			parseFailed
		};
	} catch {
		return {
			verdict: "continue",
			reason: b.callFailed,
			parseFailed: false
		};
	} finally {
		clearTimeout(timer);
	}
}
//#endregion
export { DEFAULT_JUDGE_TIMEOUT_MS, JUDGE_SYSTEM_PROMPT, JUDGE_USER_PROMPT_TEMPLATE, extractAssistantText, getAssistantMessageErrorReason, judgeGoalHermesStyle, parseJudgeResponseFailOpen, resolveGoalJudgeApiKey, stripCodeFences, truncateGoalText };

//# sourceMappingURL=judge.js.map