import { R as resolveUserPath } from "./subsystem-B57zAnh3.js";
import { C as modelKey, D as resolveConfiguredModelRef, Q as DEFAULT_PROVIDER, Z as DEFAULT_MODEL, b as buildModelAliasIndex, d as ensureAuthProfileStore, i as isProfileInCooldown, k as resolveModelRefFromString, n as resolveAuthProfileOrder, r as getSoonestCooldownExpiry, w as normalizeModelRef, y as buildConfiguredAllowlistKeys } from "./auth-profiles-vy16iW9a.js";
import { W as isLikelyContextOverflowError, p as classifyFailoverReason } from "./pi-embedded-helpers-14yhAK8Z.js";
import { i as traceGatewayEvent } from "./chat-trace-YVp5bEfe.js";
import path from "node:path";

//#region src/agents/identity/workspace-dir.ts
function normalizeWorkspaceDir(workspaceDir) {
	const trimmed = workspaceDir?.trim();
	if (!trimmed) return null;
	const expanded = trimmed.startsWith("~") ? resolveUserPath(trimmed) : trimmed;
	const resolved = path.resolve(expanded);
	if (resolved === path.parse(resolved).root) return null;
	return resolved;
}
function resolveWorkspaceRoot(workspaceDir) {
	return normalizeWorkspaceDir(workspaceDir) ?? process.cwd();
}

//#endregion
//#region src/agents/api-infra/failover-error.ts
const TIMEOUT_HINT_RE = /timeout|timed out|deadline exceeded|context deadline exceeded|stop reason:\s*abort|reason:\s*abort|unhandled stop reason:\s*abort/i;
const ABORT_TIMEOUT_RE = /request was aborted|request aborted/i;
var FailoverError = class extends Error {
	constructor(message, params) {
		super(message, { cause: params.cause });
		this.name = "FailoverError";
		this.reason = params.reason;
		this.provider = params.provider;
		this.model = params.model;
		this.profileId = params.profileId;
		this.status = params.status;
		this.code = params.code;
	}
};
function isFailoverError(err) {
	return err instanceof FailoverError;
}
function resolveFailoverStatus(reason) {
	switch (reason) {
		case "billing": return 402;
		case "rate_limit": return 429;
		case "auth": return 401;
		case "timeout": return 408;
		case "format": return 400;
		default: return;
	}
}
function getStatusCode(err) {
	if (!err || typeof err !== "object") return;
	const candidate = err.status ?? err.statusCode;
	if (typeof candidate === "number") return candidate;
	if (typeof candidate === "string" && /^\d+$/.test(candidate)) return Number(candidate);
}
function getErrorName(err) {
	if (!err || typeof err !== "object") return "";
	return "name" in err ? String(err.name) : "";
}
function getErrorCode(err) {
	if (!err || typeof err !== "object") return;
	const candidate = err.code;
	if (typeof candidate !== "string") return;
	const trimmed = candidate.trim();
	return trimmed ? trimmed : void 0;
}
function getErrorMessage(err) {
	if (err instanceof Error) return err.message;
	if (typeof err === "string") return err;
	if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") return String(err);
	if (typeof err === "symbol") return err.description ?? "";
	if (err && typeof err === "object") {
		const message = err.message;
		if (typeof message === "string") return message;
	}
	return "";
}
function hasTimeoutHint(err) {
	if (!err) return false;
	if (getErrorName(err) === "TimeoutError") return true;
	const message = getErrorMessage(err);
	return Boolean(message && TIMEOUT_HINT_RE.test(message));
}
function isTimeoutError(err) {
	if (hasTimeoutHint(err)) return true;
	if (!err || typeof err !== "object") return false;
	if (getErrorName(err) !== "AbortError") return false;
	const message = getErrorMessage(err);
	if (message && ABORT_TIMEOUT_RE.test(message)) return true;
	const cause = "cause" in err ? err.cause : void 0;
	const reason = "reason" in err ? err.reason : void 0;
	return hasTimeoutHint(cause) || hasTimeoutHint(reason);
}
function resolveFailoverReasonFromError(err) {
	if (isFailoverError(err)) return err.reason;
	const status = getStatusCode(err);
	if (status === 402) return "billing";
	if (status === 429) return "rate_limit";
	if (status === 401 || status === 403) return "auth";
	if (status === 408) return "timeout";
	if (status === 400) return "format";
	const code = (getErrorCode(err) ?? "").toUpperCase();
	if ([
		"ETIMEDOUT",
		"ESOCKETTIMEDOUT",
		"ECONNRESET",
		"ECONNABORTED"
	].includes(code)) return "timeout";
	if (isTimeoutError(err)) return "timeout";
	const message = getErrorMessage(err);
	if (!message) return null;
	return classifyFailoverReason(message);
}
function describeFailoverError(err) {
	if (isFailoverError(err)) return {
		message: err.message,
		reason: err.reason,
		status: err.status,
		code: err.code
	};
	return {
		message: getErrorMessage(err) || String(err),
		reason: resolveFailoverReasonFromError(err) ?? void 0,
		status: getStatusCode(err),
		code: getErrorCode(err)
	};
}
function coerceToFailoverError(err, context) {
	if (isFailoverError(err)) return err;
	const reason = resolveFailoverReasonFromError(err);
	if (!reason) return null;
	const message = getErrorMessage(err) || String(err);
	const status = getStatusCode(err) ?? resolveFailoverStatus(reason);
	const code = getErrorCode(err);
	return new FailoverError(message, {
		reason,
		provider: context?.provider,
		model: context?.model,
		profileId: context?.profileId,
		status,
		code,
		cause: err instanceof Error ? err : void 0
	});
}

//#endregion
//#region src/providers/model-catalog.ts
/**
* Known model context windows. Key format: lowercase model id (without provider prefix).
* For Ollama models, use the tag-qualified name (e.g. "qwen3:1.7b").
*/
const MODEL_CONTEXT_WINDOWS = {
	"claude-sonnet-4": 2e5,
	"claude-opus-4": 2e5,
	"claude-haiku-3.5": 2e5,
	"claude-3.5-sonnet": 2e5,
	"claude-3-opus": 2e5,
	"claude-3-haiku": 2e5,
	"gpt-5.2": 2e5,
	"gpt-4o": 128e3,
	"gpt-4o-mini": 128e3,
	"gpt-4-turbo": 128e3,
	"gpt-4": 8192,
	o1: 2e5,
	"o1-mini": 128e3,
	o3: 2e5,
	"o3-mini": 2e5,
	"o4-mini": 2e5,
	"gemini-3.1-pro": 2e6,
	"gemini-2.5-pro": 1e6,
	"gemini-2.5-flash-lite": 1e6,
	"gemini-2.5-flash": 1e6,
	"gemini-2.0-flash": 1e6,
	"gemini-1.5-pro": 2e6,
	"gemini-1.5-flash": 1e6,
	"mixtral-8x7b": 32768,
	"mistral-large": 128e3,
	codestral: 32e3,
	"pixtral-large": 128e3,
	"deepseek-chat": 64e3,
	"deepseek-coder": 16384,
	"deepseek-r1": 64e3,
	"grok-2": 131072,
	"grok-3": 131072,
	"llama4-maverick": 1e6,
	"meta-llama/llama-4-maverick-17b-128e-instruct": 1e6,
	"meta-llama/llama-4-maverick-17b-128e-instruct-fp8": 1e6,
	"qwen3:1.7b": 4096,
	"qwen3:8b": 32768,
	"qwen3:30b": 32768,
	"gemma3:2b": 8192,
	"gemma3:12b": 8192,
	"gemma3:27b": 8192,
	"llama3:8b": 8192,
	"llama3:70b": 8192,
	"phi-4": 16384,
	"phi4": 16384,
	"phi4:14b": 16384,
	"deepseek-r1:8b": 32768,
	"deepseek-r1:14b": 32768
};
/**
* Look up the context window size for a model.
*
* Tries exact match first, then prefix matching (e.g. "claude-sonnet-4-20250514"
* matches "claude-sonnet-4"). Returns undefined for unknown models.
*/
function getModelContextWindow(modelId) {
	const lower = modelId.toLowerCase();
	const exact = MODEL_CONTEXT_WINDOWS[lower];
	if (exact !== void 0) return exact;
	for (const [key, value] of Object.entries(MODEL_CONTEXT_WINDOWS)) if (lower.startsWith(key)) return value;
}
/**
* Check if a model's context window can accommodate the estimated token count.
*
* Uses a 1.1x safety margin — the model is considered viable if its context
* window is at least 110% of the estimated tokens.
*
* Returns true for unknown models (fail-open: don't skip what we can't measure).
*/
function modelFitsContext(modelId, estimatedTokens) {
	const contextWindow = getModelContextWindow(modelId);
	if (contextWindow === void 0) return true;
	return contextWindow >= estimatedTokens * 1.1;
}
/**
* Models known to NOT support tool/function calling.
*
* Key format: lowercase model-id prefix (without provider prefix).
* Prefix matching is used — "minimax" matches "minimax/abab6.5-chat", etc.
*
* Only list models where tool calling is confirmed broken or unsupported.
* Unknown models are treated as tool-capable (fail-open).
*
* Sources:
* - Minimax removed from ClawRouter auto-routing chains due to tool schema
*   incompatibilities (ClawRouter v0.10.0, commit 0255524)
*/
const TOOL_INCOMPATIBLE_PREFIXES = new Set(["minimax"]);
/**
* Check if a model supports tool/function calling.
*
* Returns false only for models explicitly listed as tool-incompatible.
* Returns true for unknown models (fail-open: don't skip what we can't verify).
*/
function modelSupportsToolCalling(modelId) {
	const lower = modelId.toLowerCase();
	for (const prefix of TOOL_INCOMPATIBLE_PREFIXES) if (lower === prefix || lower.startsWith(`${prefix}/`) || lower.startsWith(`${prefix}-`)) return false;
	return true;
}

//#endregion
//#region src/agents/models/model-fallback.ts
/**
* Fallback abort check. Only treats explicit AbortError names as user aborts.
* Message-based checks (e.g., "aborted") can mask timeouts and skip fallback.
*/
function isFallbackAbortError(err) {
	if (!err || typeof err !== "object") return false;
	if (isFailoverError(err)) return false;
	return ("name" in err ? String(err.name) : "") === "AbortError";
}
function shouldRethrowAbort(err) {
	return isFallbackAbortError(err) && !isTimeoutError(err);
}
function createModelCandidateCollector(allowlist) {
	const seen = /* @__PURE__ */ new Set();
	const candidates = [];
	const addCandidate = (candidate, enforceAllowlist) => {
		if (!candidate.provider || !candidate.model) return;
		const key = modelKey(candidate.provider, candidate.model);
		if (seen.has(key)) return;
		if (enforceAllowlist && allowlist && !allowlist.has(key)) return;
		seen.add(key);
		candidates.push(candidate);
	};
	return {
		candidates,
		addCandidate
	};
}
function throwFallbackFailureSummary(params) {
	if (params.attempts.length <= 1 && params.lastError) throw params.lastError;
	const summary = params.attempts.length > 0 ? params.attempts.map(params.formatAttempt).join(" | ") : "unknown";
	throw new Error(`All ${params.label} failed (${params.attempts.length || params.candidates.length}): ${summary}`, { cause: params.lastError instanceof Error ? params.lastError : void 0 });
}
function resolveImageFallbackCandidates(params) {
	const aliasIndex = buildModelAliasIndex({
		cfg: params.cfg ?? {},
		defaultProvider: params.defaultProvider
	});
	const { candidates, addCandidate } = createModelCandidateCollector(buildConfiguredAllowlistKeys({
		cfg: params.cfg,
		defaultProvider: params.defaultProvider
	}));
	const addRaw = (raw, enforceAllowlist) => {
		const resolved = resolveModelRefFromString({
			raw: String(raw ?? ""),
			defaultProvider: params.defaultProvider,
			aliasIndex
		});
		if (!resolved) return;
		addCandidate(resolved.ref, enforceAllowlist);
	};
	if (params.modelOverride?.trim()) addRaw(params.modelOverride, false);
	else {
		const imageModel = params.cfg?.agents?.defaults?.imageModel;
		const primary = typeof imageModel === "string" ? imageModel.trim() : imageModel?.primary;
		if (primary?.trim()) addRaw(primary, false);
	}
	const imageFallbacks = (() => {
		const imageModel = params.cfg?.agents?.defaults?.imageModel;
		if (imageModel && typeof imageModel === "object") return imageModel.fallbacks ?? [];
		return [];
	})();
	for (const raw of imageFallbacks) addRaw(raw, true);
	return candidates;
}
function resolveFallbackCandidates(params) {
	const primary = params.cfg ? resolveConfiguredModelRef({
		cfg: params.cfg,
		defaultProvider: DEFAULT_PROVIDER,
		defaultModel: DEFAULT_MODEL
	}) : null;
	const defaultProvider = primary?.provider ?? DEFAULT_PROVIDER;
	const defaultModel = primary?.model ?? DEFAULT_MODEL;
	const normalizedPrimary = normalizeModelRef(String(params.provider ?? "").trim() || defaultProvider, String(params.model ?? "").trim() || defaultModel);
	const aliasIndex = buildModelAliasIndex({
		cfg: params.cfg ?? {},
		defaultProvider
	});
	const { candidates, addCandidate } = createModelCandidateCollector(buildConfiguredAllowlistKeys({
		cfg: params.cfg,
		defaultProvider
	}));
	addCandidate(normalizedPrimary, false);
	const modelFallbacks = (() => {
		if (params.fallbacksOverride !== void 0) return params.fallbacksOverride;
		const model = params.cfg?.agents?.defaults?.model;
		if (model && typeof model === "object") return model.fallbacks ?? [];
		return [];
	})();
	for (const raw of modelFallbacks) {
		const resolved = resolveModelRefFromString({
			raw: String(raw ?? ""),
			defaultProvider,
			aliasIndex
		});
		if (!resolved) continue;
		addCandidate(resolved.ref, true);
	}
	if (params.fallbacksOverride === void 0 && primary?.provider && primary.model) addCandidate({
		provider: primary.provider,
		model: primary.model
	}, false);
	if (params.estimatedContextTokens && params.estimatedContextTokens > 0) {
		const filtered = candidates.filter((c) => modelFitsContext(c.model, params.estimatedContextTokens));
		if (filtered.length > 0) return filtered;
	}
	if (params.hasTools) {
		const toolCapable = candidates.filter((c) => modelSupportsToolCalling(c.model));
		if (toolCapable.length > 0) return toolCapable;
	}
	return candidates;
}
const lastProbeAttempt = /* @__PURE__ */ new Map();
const MIN_PROBE_INTERVAL_MS = 3e4;
const PROBE_MARGIN_MS = 120 * 1e3;
const PROBE_SCOPE_DELIMITER = "::";
function resolveProbeThrottleKey(provider, agentDir) {
	const scope = String(agentDir ?? "").trim();
	return scope ? `${scope}${PROBE_SCOPE_DELIMITER}${provider}` : provider;
}
function shouldProbePrimaryDuringCooldown(params) {
	if (!params.isPrimary || !params.hasFallbackCandidates) return false;
	const lastProbe = lastProbeAttempt.get(params.throttleKey) ?? 0;
	if (params.now - lastProbe < MIN_PROBE_INTERVAL_MS) return false;
	const soonest = getSoonestCooldownExpiry(params.authStore, params.profileIds);
	if (soonest === null || !Number.isFinite(soonest)) return true;
	return params.now >= soonest - PROBE_MARGIN_MS;
}
async function runWithModelFallback(params) {
	const candidates = resolveFallbackCandidates({
		cfg: params.cfg,
		provider: params.provider,
		model: params.model,
		fallbacksOverride: params.fallbacksOverride,
		estimatedContextTokens: params.estimatedContextTokens,
		hasTools: params.hasTools
	});
	const authStore = params.cfg ? ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false }) : null;
	const attempts = [];
	let lastError;
	const hasFallbackCandidates = candidates.length > 1;
	for (let i = 0; i < candidates.length; i += 1) {
		const candidate = candidates[i];
		if (authStore) {
			const profileIds = resolveAuthProfileOrder({
				cfg: params.cfg,
				store: authStore,
				provider: candidate.provider
			});
			const isAnyProfileAvailable = profileIds.some((id) => !isProfileInCooldown(authStore, id));
			if (profileIds.length > 0 && !isAnyProfileAvailable) {
				const now = Date.now();
				const probeThrottleKey = resolveProbeThrottleKey(candidate.provider, params.agentDir);
				if (!shouldProbePrimaryDuringCooldown({
					isPrimary: i === 0,
					hasFallbackCandidates,
					now,
					throttleKey: probeThrottleKey,
					authStore,
					profileIds
				})) {
					attempts.push({
						provider: candidate.provider,
						model: candidate.model,
						error: `Provider ${candidate.provider} is in cooldown (all profiles unavailable)`,
						reason: "rate_limit"
					});
					continue;
				}
				lastProbeAttempt.set(probeThrottleKey, now);
			}
		}
		try {
			return {
				result: await params.run(candidate.provider, candidate.model),
				provider: candidate.provider,
				model: candidate.model,
				attempts
			};
		} catch (err) {
			if (shouldRethrowAbort(err)) throw err;
			const errMessage = err instanceof Error ? err.message : String(err);
			if (isLikelyContextOverflowError(errMessage)) {
				traceGatewayEvent({
					traceId: `fb${String(i).padStart(6, "0")}`,
					level: "WARN",
					stage: "CONTEXT_OVERFLOW",
					data: {
						provider: candidate.provider,
						model: candidate.model,
						error: errMessage.slice(0, 200)
					}
				});
				throw err;
			}
			const normalized = coerceToFailoverError(err, {
				provider: candidate.provider,
				model: candidate.model
			}) ?? err;
			if (!isFailoverError(normalized)) throw err;
			lastError = normalized;
			const described = describeFailoverError(normalized);
			attempts.push({
				provider: candidate.provider,
				model: candidate.model,
				error: described.message,
				reason: described.reason,
				status: described.status,
				code: described.code
			});
			traceGatewayEvent({
				traceId: `fb${String(i).padStart(6, "0")}`,
				level: "WARN",
				stage: "MODEL_FALLBACK",
				data: {
					failed: `${candidate.provider}/${candidate.model}`,
					error: described.message,
					reason: described.reason,
					attempt: i + 1,
					total: candidates.length
				}
			});
			await params.onError?.({
				provider: candidate.provider,
				model: candidate.model,
				error: normalized,
				attempt: i + 1,
				total: candidates.length
			});
		}
	}
	traceGatewayEvent({
		traceId: `fb${String(candidates.length).padStart(6, "0")}`,
		level: "ERROR",
		stage: "MODEL_FALLBACK_EXHAUSTED",
		data: {
			attempted: attempts.length,
			candidates: candidates.map((c) => `${c.provider}/${c.model}`).join(", ")
		}
	});
	throwFallbackFailureSummary({
		attempts,
		candidates,
		lastError,
		label: "models",
		formatAttempt: (attempt) => `${attempt.provider}/${attempt.model}: ${attempt.error}${attempt.reason ? ` (${attempt.reason})` : ""}`
	});
}
async function runWithImageModelFallback(params) {
	const candidates = resolveImageFallbackCandidates({
		cfg: params.cfg,
		defaultProvider: DEFAULT_PROVIDER,
		modelOverride: params.modelOverride
	});
	if (candidates.length === 0) throw new Error("No image model configured. Set agents.defaults.imageModel.primary or agents.defaults.imageModel.fallbacks.");
	const attempts = [];
	let lastError;
	for (let i = 0; i < candidates.length; i += 1) {
		const candidate = candidates[i];
		try {
			return {
				result: await params.run(candidate.provider, candidate.model),
				provider: candidate.provider,
				model: candidate.model,
				attempts
			};
		} catch (err) {
			if (shouldRethrowAbort(err)) throw err;
			lastError = err;
			attempts.push({
				provider: candidate.provider,
				model: candidate.model,
				error: err instanceof Error ? err.message : String(err)
			});
			await params.onError?.({
				provider: candidate.provider,
				model: candidate.model,
				error: err,
				attempt: i + 1,
				total: candidates.length
			});
		}
	}
	throwFallbackFailureSummary({
		attempts,
		candidates,
		lastError,
		label: "image models",
		formatAttempt: (attempt) => `${attempt.provider}/${attempt.model}: ${attempt.error}`
	});
}

//#endregion
export { resolveFailoverReasonFromError as a, resolveWorkspaceRoot as c, isTimeoutError as i, runWithModelFallback as n, resolveFailoverStatus as o, FailoverError as r, normalizeWorkspaceDir as s, runWithImageModelFallback as t };