import { createLogger } from "../../utils/logger/index.js";
import { init_logger } from "../../utils/logger.js";
//#region src/agent/orchestration/loop-guard.ts
init_logger();
const log = createLogger("LoopGuard");
const DEFAULT_CONFIG = {
	softThreshold: 2,
	hideThreshold: 3
};
/**
* Stable fingerprint for tool call comparison.
* Recursively sorts object keys; truncates long string values.
*/
function fingerprint(toolName, params) {
	return `${toolName}::${JSON.stringify(params, (_key, value) => {
		if (typeof value === "object" && value !== null && !Array.isArray(value)) return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)));
		if (typeof value === "string" && value.length > 200) return `${value.slice(0, 80)}…[${value.length}]`;
		return value;
	})}`;
}
/**
* Analyze recent tool calls and produce loop guard result.
*
* Pure function — no side effects, no shared state.
*/
function detectToolLoops(recentCalls, config = {}) {
	const { softThreshold, hideThreshold } = {
		...DEFAULT_CONFIG,
		...config
	};
	if (recentCalls.length === 0) return {
		injection: null,
		hiddenTools: /* @__PURE__ */ new Set()
	};
	const consecutiveGroups = findConsecutiveRepeats(recentCalls);
	const frequencyMap = countFrequency(recentCalls);
	const warnings = [];
	const hiddenTools = /* @__PURE__ */ new Set();
	for (const group of consecutiveGroups) if (group.count >= hideThreshold) {
		hiddenTools.add(group.toolName);
		warnings.push(`Tool '${group.toolName}' has been called ${group.count} times consecutively with identical arguments and is now unavailable. Use a completely different approach.`);
		log.warn({
			tool: group.toolName,
			consecutiveCount: group.count
		}, "Loop guard: hiding tool from LLM");
	} else if (group.count >= softThreshold) {
		warnings.push(`You have called '${group.toolName}' ${group.count} times with the same arguments. This is not producing new information. Do NOT call it again with the same arguments — try a different approach.`);
		log.debug({
			tool: group.toolName,
			consecutiveCount: group.count
		}, "Loop guard: soft warning injected");
	}
	for (const [fp, count] of frequencyMap) {
		const toolName = fp.split("::")[0];
		if (count >= hideThreshold + 1 && !hiddenTools.has(toolName)) {
			if (!consecutiveGroups.some((g) => g.toolName === toolName && g.count >= softThreshold)) warnings.push(`Tool '${toolName}' has been called ${count} times this turn with the same arguments (interleaved). Stop repeating this pattern.`);
		}
	}
	return {
		injection: warnings.length > 0 ? `⚠️ LOOP DETECTION:\n${warnings.join("\n")}\n\nYou MUST take a different approach. Do not repeat the same tool calls.` : null,
		hiddenTools
	};
}
/**
* Find consecutive runs of identical (tool+params) calls from the end of history.
* Returns all groups with count >= 2 (most recent first).
*/
function findConsecutiveRepeats(calls) {
	const groups = [];
	let index = calls.length - 1;
	while (index >= 0) {
		const current = calls[index];
		const fp = fingerprint(current.name, current.params);
		let count = 1;
		while (index - count >= 0) {
			const prev = calls[index - count];
			if (fingerprint(prev.name, prev.params) !== fp) break;
			count++;
		}
		if (count >= 2) groups.push({
			toolName: current.name,
			fingerprint: fp,
			count
		});
		index -= count;
	}
	return groups;
}
/**
* Count how many times each (tool+params) fingerprint appears.
*/
function countFrequency(calls) {
	const map = /* @__PURE__ */ new Map();
	for (const call of calls) {
		const fp = fingerprint(call.name, call.params);
		map.set(fp, (map.get(fp) ?? 0) + 1);
	}
	return map;
}
//#endregion
export { detectToolLoops };

//# sourceMappingURL=loop-guard.js.map