const SERIES_COOLDOWN_DETAIL_KEY = 'virtualRouterSeriesCooldown';
const QUOTA_RECOVERY_DETAIL_KEY = 'virtualRouterQuotaRecovery';
const QUOTA_DEPLETED_DETAIL_KEY = 'virtualRouterQuotaDepleted';
function parseDurationToMs(value) {
    if (!value || typeof value !== 'string') {
        return null;
    }
    const pattern = /(\d+(?:\.\d+)?)(ms|s|m|h)/gi;
    let totalMs = 0;
    let matched = false;
    let match;
    while ((match = pattern.exec(value)) !== null) {
        matched = true;
        const amount = Number.parseFloat(match[1]);
        if (!Number.isFinite(amount)) {
            continue;
        }
        const unit = match[2].toLowerCase();
        if (unit === 'ms') {
            totalMs += amount;
        }
        else if (unit === 'h') {
            totalMs += amount * 3_600_000;
        }
        else if (unit === 'm') {
            totalMs += amount * 60_000;
        }
        else if (unit === 's') {
            totalMs += amount * 1_000;
        }
    }
    if (!matched) {
        const seconds = Number.parseFloat(value);
        if (Number.isFinite(seconds)) {
            totalMs = seconds * 1_000;
            matched = true;
        }
    }
    if (!matched || totalMs <= 0) {
        return null;
    }
    return Math.round(totalMs);
}
function readEnvSchedule(name, fallback) {
    const raw = (process.env[name] || '').trim();
    if (!raw) {
        return fallback;
    }
    const parts = raw.split(',').map((token) => token.trim()).filter(Boolean);
    const parsed = [];
    for (const part of parts) {
        const ms = parseDurationToMs(part);
        if (ms && ms > 0) {
            parsed.push(ms);
        }
    }
    return parsed.length ? parsed : fallback;
}
function readEnvDuration(name, fallbackMs) {
    const raw = (process.env[name] || '').trim();
    if (!raw) {
        return fallbackMs;
    }
    const ms = parseDurationToMs(raw);
    return ms && ms > 0 ? ms : fallbackMs;
}
/**
 * 对没有 quotaResetDelay 的 429 错误，在 VirtualRouter 内部维护一个简单的阶梯退避策略：
 * - 默认：第 1 次 3 秒，第 2 次 10 秒，第 3 次 31 秒，第 4 次及以上 61 秒封顶；
 * - 可通过环境变量 ROUTECODEX_RL_SCHEDULE / RCC_RL_SCHEDULE 调整（例如 "3s,10s,31s,61s"）。
 *
 * 这里的“次数”针对 providerKey 计数，并带有简单的时间窗口：若距离上次 429 超过 24 小时，则重置计数。
 * 该状态仅用于路由决策，不反映在 healthConfig 上，使 Host 与 VirtualRouter 对 429 处理职责清晰分层。
 */
const NO_QUOTA_RATE_LIMIT_SCHEDULE_MS = readEnvSchedule('ROUTECODEX_RL_SCHEDULE', [
    3_000,
    10_000,
    31_000,
    61_000
]);
const rateLimitBackoffByProvider = new Map();
const RATE_LIMIT_RESET_WINDOW_MS = readEnvDuration('ROUTECODEX_RL_RESET_WINDOW', 24 * 60 * 60_000);
function computeRateLimitCooldownMsForProvider(providerKey, now) {
    const prev = rateLimitBackoffByProvider.get(providerKey);
    let nextCount = 1;
    if (prev) {
        const elapsed = now - prev.lastAt;
        if (Number.isFinite(elapsed) && elapsed >= 0 && elapsed < RATE_LIMIT_RESET_WINDOW_MS) {
            nextCount = prev.count + 1;
        }
    }
    const idx = Math.min(nextCount - 1, NO_QUOTA_RATE_LIMIT_SCHEDULE_MS.length - 1);
    const ttl = NO_QUOTA_RATE_LIMIT_SCHEDULE_MS[idx];
    rateLimitBackoffByProvider.set(providerKey, { count: nextCount, lastAt: now });
    return ttl;
}
export function resetRateLimitBackoffForProvider(providerKey) {
    if (!providerKey) {
        return;
    }
    rateLimitBackoffByProvider.delete(providerKey);
}
const antigravityRiskBySignature = new Map();
const ANTIGRAVITY_RISK_RESET_WINDOW_MS = readEnvDuration('ROUTECODEX_ANTIGRAVITY_RISK_RESET_WINDOW', 30 * 60_000);
const ANTIGRAVITY_RISK_COOLDOWN_MS = readEnvDuration('ROUTECODEX_ANTIGRAVITY_RISK_COOLDOWN', 5 * 60_000);
const ANTIGRAVITY_RISK_BAN_MS = readEnvDuration('ROUTECODEX_ANTIGRAVITY_RISK_BAN', 24 * 60 * 60_000);
const ANTIGRAVITY_AUTH_VERIFY_BAN_MS = readEnvDuration('ROUTECODEX_ANTIGRAVITY_AUTH_VERIFY_BAN', 24 * 60 * 60_000);
const ANTIGRAVITY_THOUGHT_SIGNATURE_MISSING_COOLDOWN_MS = readEnvDuration('ROUTECODEX_ANTIGRAVITY_THOUGHT_SIGNATURE_MISSING_COOLDOWN', 5 * 60_000);
function isAntigravityEvent(event) {
    const runtime = event?.runtime;
    if (!runtime || typeof runtime !== 'object') {
        return false;
    }
    const providerId = typeof runtime.providerId === 'string' ? runtime.providerId.trim().toLowerCase() : '';
    if (providerId === 'antigravity') {
        return true;
    }
    const providerKey = typeof runtime.providerKey === 'string' ? runtime.providerKey.trim().toLowerCase() : '';
    return providerKey.startsWith('antigravity.');
}
function isGoogleAccountVerificationRequired(event) {
    const sources = [];
    const message = typeof event.message === 'string' ? event.message : '';
    if (message)
        sources.push(message);
    const details = event.details;
    if (details && typeof details === 'object' && !Array.isArray(details)) {
        const upstreamMessage = details.upstreamMessage;
        if (typeof upstreamMessage === 'string' && upstreamMessage.trim()) {
            sources.push(upstreamMessage);
        }
        const meta = details.meta;
        if (meta && typeof meta === 'object' && !Array.isArray(meta)) {
            const metaUpstream = meta.upstreamMessage;
            if (typeof metaUpstream === 'string' && metaUpstream.trim()) {
                sources.push(metaUpstream);
            }
            const metaMessage = meta.message;
            if (typeof metaMessage === 'string' && metaMessage.trim()) {
                sources.push(metaMessage);
            }
        }
    }
    if (sources.length === 0) {
        return false;
    }
    const lowered = sources.join(' | ').toLowerCase();
    return (lowered.includes('verify your account') ||
        // Antigravity-Manager alignment: 403 validation gating keywords.
        lowered.includes('validation_required') ||
        lowered.includes('validation required') ||
        lowered.includes('validation_url') ||
        lowered.includes('validation url') ||
        lowered.includes('accounts.google.com/signin/continue') ||
        lowered.includes('support.google.com/accounts?p=al_alert'));
}
function resolveAntigravityRuntimeKey(event) {
    const runtime = event.runtime;
    if (!runtime || typeof runtime !== 'object') {
        return null;
    }
    const target = runtime.target && typeof runtime.target === 'object' ? runtime.target : null;
    const byTarget = target && typeof target.runtimeKey === 'string' && target.runtimeKey.trim() ? target.runtimeKey.trim() : '';
    if (byTarget) {
        return byTarget;
    }
    const providerKey = (typeof runtime.providerKey === 'string' && runtime.providerKey.trim() ? runtime.providerKey.trim() : '') ||
        (target && typeof target.providerKey === 'string' && String(target.providerKey).trim()
            ? String(target.providerKey).trim()
            : '');
    if (!providerKey) {
        return null;
    }
    const parts = providerKey.split('.').filter(Boolean);
    if (parts.length < 2) {
        return null;
    }
    return `${parts[0]}.${parts[1]}`;
}
function shouldTriggerAntigravityRiskPolicy(event) {
    const status = typeof event.status === 'number' ? event.status : undefined;
    if (typeof status === 'number' && Number.isFinite(status)) {
        // Focus on "dirty request" / auth / permission class issues. Avoid 429 which already has backoff.
        return status >= 400 && status < 500 && status !== 429;
    }
    // If status is missing, fall back to known HTTP-ish error codes.
    return typeof event.code === 'string' && /^HTTP_4\d\d$/.test(event.code.trim());
}
function computeAntigravityRiskSignature(event) {
    const status = typeof event.status === 'number' && Number.isFinite(event.status) ? String(event.status) : '';
    const code = typeof event.code === 'string' && event.code.trim() ? event.code.trim() : '';
    const stage = typeof event.stage === 'string' && event.stage.trim() ? event.stage.trim() : '';
    const parts = [status, code, stage].filter((p) => p.length > 0);
    return parts.length ? parts.join(':') : 'unknown';
}
function isAntigravityThoughtSignatureMissing(event) {
    const code = typeof event.code === 'string' ? event.code.trim().toLowerCase() : '';
    if (code.includes('thought') && code.includes('signature')) {
        return true;
    }
    const sources = [];
    const message = typeof event.message === 'string' ? event.message : '';
    if (message)
        sources.push(message);
    const details = event.details;
    if (details && typeof details === 'object' && !Array.isArray(details)) {
        const upstreamMessage = details.upstreamMessage;
        if (typeof upstreamMessage === 'string' && upstreamMessage.trim()) {
            sources.push(upstreamMessage);
        }
        const meta = details.meta;
        if (meta && typeof meta === 'object' && !Array.isArray(meta)) {
            const metaUpstream = meta.upstreamMessage;
            if (typeof metaUpstream === 'string' && metaUpstream.trim()) {
                sources.push(metaUpstream);
            }
            const metaMessage = meta.message;
            if (typeof metaMessage === 'string' && metaMessage.trim()) {
                sources.push(metaMessage);
            }
        }
    }
    if (sources.length === 0) {
        return false;
    }
    for (const source of sources) {
        const lowered = source.toLowerCase();
        // Match both "thoughtSignature" and "thought signature" variants, as well as "reasoning signature".
        const mentionsSignature = lowered.includes('thoughtsignature') ||
            lowered.includes('thought signature') ||
            lowered.includes('reasoning_signature') ||
            lowered.includes('reasoning signature');
        if (!mentionsSignature) {
            continue;
        }
        // Match common failure patterns in upstream error messages.
        if (lowered.includes('missing') ||
            lowered.includes('required') ||
            lowered.includes('invalid') ||
            lowered.includes('not provided') ||
            lowered.includes('签名') ||
            lowered.includes('缺少') ||
            lowered.includes('无效')) {
            return true;
        }
    }
    return false;
}
function shouldApplyGeminiSeriesSignatureFreeze(event) {
    const status = typeof event.status === 'number' ? event.status : undefined;
    // Signature missing is a request-shape contract failure; treat as 4xx.
    if (status !== undefined && status >= 400 && status < 500) {
        return true;
    }
    const code = typeof event.code === 'string' ? event.code.trim().toUpperCase() : '';
    return /^HTTP_4\d\d$/.test(code) || code.includes('INVALID_ARGUMENT') || code.includes('FAILED_PRECONDITION');
}
export function applyAntigravityRiskPolicyImpl(event, providerRegistry, healthManager, markProviderCooldown, debug) {
    if (!event) {
        return;
    }
    if (!isAntigravityEvent(event)) {
        return;
    }
    if (!shouldTriggerAntigravityRiskPolicy(event)) {
        return;
    }
    const runtimeKey = resolveAntigravityRuntimeKey(event);
    const verificationRequired = Boolean(runtimeKey) && isGoogleAccountVerificationRequired(event);
    // Account verification errors are per-account and must be handled immediately:
    // - blacklist only the affected runtimeKey (do NOT penalize other Antigravity accounts)
    // - require user to complete OAuth verification to recover
    if (verificationRequired && runtimeKey) {
        const providerKeys = providerRegistry
            .listProviderKeys('antigravity')
            .filter((key) => typeof key === 'string' && key.startsWith(`${runtimeKey}.`));
        if (providerKeys.length === 0) {
            return;
        }
        for (const key of providerKeys) {
            try {
                if (!healthManager.isAvailable(key)) {
                    continue;
                }
                healthManager.tripProvider(key, 'auth_verify', ANTIGRAVITY_AUTH_VERIFY_BAN_MS);
                markProviderCooldown(key, ANTIGRAVITY_AUTH_VERIFY_BAN_MS);
            }
            catch {
                // ignore lookup failures
            }
        }
        debug?.log?.('[virtual-router] antigravity auth verify blacklist', {
            runtimeKey,
            cooldownMs: ANTIGRAVITY_AUTH_VERIFY_BAN_MS,
            affected: providerKeys
        });
        return;
    }
    // Thought signature missing is a deterministic incompatibility for Gemini-family flows that require it.
    // If we keep routing into Antigravity Gemini models without a signature, we create a request storm.
    // Freeze the *Gemini* series (pro/flash) immediately so the router falls back to non-Antigravity providers.
    if (isAntigravityThoughtSignatureMissing(event) && shouldApplyGeminiSeriesSignatureFreeze(event)) {
        const allProviderKeys = providerRegistry.listProviderKeys('antigravity');
        const providerKeys = runtimeKey
            ? allProviderKeys.filter((key) => typeof key === 'string' && key.startsWith(`${runtimeKey}.`))
            : allProviderKeys;
        const affected = [];
        for (const key of providerKeys) {
            try {
                if (!healthManager.isAvailable(key)) {
                    continue;
                }
                const profile = providerRegistry.get(key);
                const modelSeries = resolveModelSeries(profile.modelId);
                if (modelSeries !== 'gemini-pro' && modelSeries !== 'gemini-flash') {
                    continue;
                }
                healthManager.tripProvider(key, 'signature_missing', ANTIGRAVITY_THOUGHT_SIGNATURE_MISSING_COOLDOWN_MS);
                markProviderCooldown(key, ANTIGRAVITY_THOUGHT_SIGNATURE_MISSING_COOLDOWN_MS);
                affected.push(key);
            }
            catch {
                // ignore lookup failures
            }
        }
        if (affected.length) {
            debug?.log?.('[virtual-router] antigravity thoughtSignature missing: freeze gemini series', {
                ...(runtimeKey ? { runtimeKey } : {}),
                cooldownMs: ANTIGRAVITY_THOUGHT_SIGNATURE_MISSING_COOLDOWN_MS,
                affected
            });
        }
        return;
    }
    const signature = computeAntigravityRiskSignature(event);
    if (!signature || signature === 'unknown') {
        return;
    }
    // Antigravity-Manager alignment: account verification errors are per-account (runtimeKey) and should not
    // penalize all other Antigravity accounts. Request-shape/auth policy failures are still treated globally.
    const runtimeScoped = Boolean(runtimeKey) && isGoogleAccountVerificationRequired(event);
    const riskKey = runtimeScoped ? `${signature}|${runtimeKey}` : signature;
    const now = Date.now();
    const prev = antigravityRiskBySignature.get(riskKey);
    let count = 1;
    if (prev) {
        const elapsed = now - prev.lastAt;
        if (Number.isFinite(elapsed) && elapsed >= 0 && elapsed < ANTIGRAVITY_RISK_RESET_WINDOW_MS) {
            count = prev.count + 1;
        }
    }
    antigravityRiskBySignature.set(riskKey, { count, lastAt: now });
    // Escalation ladder (Antigravity account safety):
    // 1) First/second occurrence: normal retry/fallback logic handles per-request behavior.
    // 2) Third occurrence: cooldown Antigravity providerKeys for 5 minutes (scoped for account verification errors).
    // 3) Fourth+ occurrence: effectively remove Antigravity from routing (long ban window; scoped for account verification errors).
    const allProviderKeys = providerRegistry.listProviderKeys('antigravity');
    const providerKeys = runtimeScoped
        ? allProviderKeys.filter((key) => {
            return typeof key === 'string' && runtimeKey ? key.startsWith(`${runtimeKey}.`) : false;
        })
        : allProviderKeys;
    if (providerKeys.length === 0) {
        return;
    }
    if (count === 3) {
        for (const key of providerKeys) {
            try {
                healthManager.tripProvider(key, 'risk_cooldown', ANTIGRAVITY_RISK_COOLDOWN_MS);
                markProviderCooldown(key, ANTIGRAVITY_RISK_COOLDOWN_MS);
            }
            catch {
                // ignore lookup failures
            }
        }
        debug?.log?.('[virtual-router] antigravity risk cooldown', {
            signature,
            ...(runtimeScoped ? { runtimeKey } : {}),
            count,
            cooldownMs: ANTIGRAVITY_RISK_COOLDOWN_MS,
            affected: providerKeys
        });
    }
    else if (count >= 4) {
        const ttl = Math.max(ANTIGRAVITY_RISK_BAN_MS, ANTIGRAVITY_RISK_COOLDOWN_MS);
        for (const key of providerKeys) {
            try {
                healthManager.tripProvider(key, 'risk_blacklist', ttl);
                markProviderCooldown(key, ttl);
            }
            catch {
                // ignore lookup failures
            }
        }
        debug?.log?.('[virtual-router] antigravity risk blacklist', {
            signature,
            ...(runtimeScoped ? { runtimeKey } : {}),
            count,
            cooldownMs: ttl,
            affected: providerKeys
        });
    }
}
export function handleProviderFailureImpl(event, healthManager, healthConfig, markProviderCooldown) {
    if (!event || !event.providerKey) {
        return;
    }
    if (event.affectsHealth === false) {
        return;
    }
    if (event.fatal) {
        healthManager.tripProvider(event.providerKey, event.reason, event.cooldownOverrideMs);
    }
    else if (event.reason === 'rate_limit' && event.statusCode === 429) {
        // 对非致命的 429 错误：
        // - 若 ProviderErrorEvent 已携带显式 cooldownOverrideMs（例如来自 quotaResetDelay），则直接使用；
        // - 否则针对该 providerKey 启用阶梯退避策略（5min → 1h → 6h → 24h），
        //   在冷却期内从路由池中移除该 alias，避免持续命中上游。
        const providerKey = event.providerKey;
        let ttl = event.cooldownOverrideMs;
        if (!ttl || !Number.isFinite(ttl) || ttl <= 0) {
            ttl = computeRateLimitCooldownMsForProvider(providerKey, Date.now());
        }
        healthManager.cooldownProvider(providerKey, event.reason, ttl);
        markProviderCooldown(providerKey, ttl);
    }
    else {
        healthManager.recordFailure(event.providerKey, event.reason);
    }
}
export function mapProviderErrorImpl(event, healthConfig) {
    if (!event || !event.runtime) {
        return null;
    }
    const runtime = event.runtime;
    const providerKey = runtime.providerKey ||
        (runtime.target && typeof runtime.target === 'object'
            ? runtime.target.providerKey
            : undefined);
    if (!providerKey) {
        return null;
    }
    const routeName = runtime.routeName;
    const statusCode = event.status;
    const code = event.code?.toUpperCase() ?? 'ERR_UNKNOWN';
    const stage = event.stage?.toLowerCase() ?? 'unknown';
    const recoverable = event.recoverable === true;
    const providerFamily = runtime.providerFamily &&
        typeof runtime.providerFamily === 'string'
        ? runtime.providerFamily
        : undefined;
    const providerId = runtime.providerId &&
        typeof runtime.providerId === 'string'
        ? runtime.providerId
        : undefined;
    const providerTag = (providerFamily || providerId || '').toLowerCase();
    const isOAuthAuth406 = statusCode === 406 && (providerTag === 'iflow' || providerTag === 'qwen');
    let fatal = !recoverable;
    let reason = deriveReason(code, stage, statusCode);
    let cooldownOverrideMs;
    if (statusCode === 401 || statusCode === 402 || statusCode === 403 || code.includes('AUTH') || isOAuthAuth406) {
        fatal = true;
        cooldownOverrideMs = Math.max(10 * 60_000, healthConfig.fatalCooldownMs ?? 10 * 60_000);
        reason = 'auth';
    }
    else if (statusCode === 429 && !recoverable) {
        fatal = true;
        cooldownOverrideMs = Math.max(10 * 60_000, healthConfig.fatalCooldownMs ?? 10 * 60_000);
        reason = 'rate_limit';
    }
    else if (statusCode && statusCode >= 500) {
        fatal = true;
        cooldownOverrideMs = Math.max(5 * 60_000, healthConfig.fatalCooldownMs ?? 5 * 60_000);
        reason = 'upstream_error';
    }
    else if (stage.includes('compat')) {
        fatal = true;
        cooldownOverrideMs = Math.max(10 * 60_000, healthConfig.fatalCooldownMs ?? 10 * 60_000);
        reason = 'compatibility';
    }
    return {
        providerKey,
        routeName,
        reason,
        fatal,
        statusCode,
        errorCode: code,
        retryable: recoverable,
        affectsHealth: event.affectsHealth !== false,
        cooldownOverrideMs,
        metadata: {
            ...event.runtime,
            stage,
            eventCode: code,
            originalMessage: event.message,
            statusCode
        }
    };
}
export function applySeriesCooldownImpl(event, providerRegistry, healthManager, markProviderCooldown, debug) {
    const seriesDetail = extractSeriesCooldownDetail(event);
    if (!seriesDetail) {
        return;
    }
    const targetKeys = resolveSeriesCooldownTargets(seriesDetail, event, providerRegistry);
    if (targetKeys.length === 0) {
        debug?.log?.('[virtual-router] series cooldown skipped: no targets', {
            providerId: seriesDetail.providerId,
            providerKey: seriesDetail.providerKey,
            series: seriesDetail.series
        });
        return;
    }
    const affected = [];
    for (const providerKey of targetKeys) {
        try {
            const profile = providerRegistry.get(providerKey);
            const modelSeries = resolveModelSeries(profile.modelId);
            if (modelSeries !== seriesDetail.series) {
                continue;
            }
            healthManager.tripProvider(providerKey, 'rate_limit', seriesDetail.cooldownMs);
            markProviderCooldown(providerKey, seriesDetail.cooldownMs);
            affected.push(providerKey);
        }
        catch {
            // ignore lookup failures; invalid keys may show up if config drifted
        }
    }
    if (affected.length) {
        debug?.log?.('[virtual-router] series cooldown', {
            providerId: seriesDetail.providerId,
            providerKey: seriesDetail.providerKey,
            series: seriesDetail.series,
            cooldownMs: seriesDetail.cooldownMs,
            affected
        });
    }
}
function extractQuotaRecoveryDetail(event) {
    if (!event || !event.details || typeof event.details !== 'object') {
        return null;
    }
    const raw = event.details[QUOTA_RECOVERY_DETAIL_KEY];
    if (!raw || typeof raw !== 'object') {
        return null;
    }
    const record = raw;
    const providerKeyRaw = record.providerKey;
    if (typeof providerKeyRaw !== 'string' || !providerKeyRaw.trim()) {
        return null;
    }
    const reason = typeof record.reason === 'string' && record.reason.trim()
        ? record.reason.trim()
        : undefined;
    return {
        providerKey: providerKeyRaw.trim(),
        reason
    };
}
/**
 * 处理来自 Host 侧的配额恢复事件：
 * - 清除指定 providerKey 在健康管理器中的熔断/冷却状态；
 * - 清理对应的速率退避计数；
 * - 调用调用方提供的 clearProviderCooldown 回调移除显式 cooldown TTL。
 *
 * 返回值表示是否已处理（true=已处理且后续应跳过常规错误映射逻辑）。
 */
export function applyQuotaRecoveryImpl(event, healthManager, clearProviderCooldown, debug) {
    const detail = extractQuotaRecoveryDetail(event);
    if (!detail) {
        return false;
    }
    const providerKey = detail.providerKey;
    try {
        healthManager.recordSuccess(providerKey);
        resetRateLimitBackoffForProvider(providerKey);
        clearProviderCooldown(providerKey);
    }
    catch {
        // 恢复失败不得影响主路由流程
    }
    return true;
}
function extractQuotaDepletedDetail(event) {
    if (!event || !event.details || typeof event.details !== 'object') {
        return null;
    }
    const raw = event.details[QUOTA_DEPLETED_DETAIL_KEY];
    if (!raw || typeof raw !== 'object') {
        return null;
    }
    const record = raw;
    const providerKeyRaw = record.providerKey;
    if (typeof providerKeyRaw !== 'string' || !providerKeyRaw.trim()) {
        return null;
    }
    const cooldownMs = typeof record.cooldownMs === 'number' && Number.isFinite(record.cooldownMs) && record.cooldownMs > 0
        ? record.cooldownMs
        : undefined;
    const reason = typeof record.reason === 'string' && record.reason.trim()
        ? record.reason.trim()
        : undefined;
    return {
        providerKey: providerKeyRaw.trim(),
        cooldownMs,
        reason
    };
}
export function applyQuotaDepletedImpl(event, healthManager, markProviderCooldown, debug) {
    const detail = extractQuotaDepletedDetail(event);
    if (!detail) {
        return false;
    }
    const ttl = detail.cooldownMs;
    try {
        healthManager.cooldownProvider(detail.providerKey, 'rate_limit', ttl);
        markProviderCooldown(detail.providerKey, ttl);
        debug?.log?.('[virtual-router] quota depleted', {
            providerKey: detail.providerKey,
            cooldownMs: ttl,
            reason: detail.reason
        });
    }
    catch {
        // ignore failures
    }
    return true;
}
function resolveSeriesCooldownTargets(detail, event, providerRegistry) {
    const candidates = new Set();
    const push = (key) => {
        if (typeof key !== 'string') {
            return;
        }
        const trimmed = key.trim();
        if (!trimmed) {
            return;
        }
        if (providerRegistry.has(trimmed)) {
            candidates.add(trimmed);
        }
    };
    push(detail.providerKey);
    const runtimeKey = (event.runtime?.target && typeof event.runtime.target === 'object'
        ? event.runtime.target.providerKey
        : undefined) || event.runtime?.providerKey;
    push(runtimeKey);
    return Array.from(candidates);
}
function extractSeriesCooldownDetail(event) {
    if (!event || !event.details || typeof event.details !== 'object') {
        return null;
    }
    const raw = event.details[SERIES_COOLDOWN_DETAIL_KEY];
    if (!raw || typeof raw !== 'object') {
        return null;
    }
    const record = raw;
    const providerIdRaw = record.providerId;
    const seriesRaw = record.series;
    const providerKeyRaw = record.providerKey;
    const cooldownRaw = record.cooldownMs;
    if (typeof providerIdRaw !== 'string' || !providerIdRaw.trim()) {
        return null;
    }
    const normalizedSeries = typeof seriesRaw === 'string' ? seriesRaw.trim().toLowerCase() : '';
    if (normalizedSeries !== 'gemini-pro' && normalizedSeries !== 'gemini-flash' && normalizedSeries !== 'claude') {
        return null;
    }
    const cooldownMs = typeof cooldownRaw === 'number'
        ? cooldownRaw
        : typeof cooldownRaw === 'string'
            ? Number.parseFloat(cooldownRaw)
            : Number.NaN;
    if (!Number.isFinite(cooldownMs) || cooldownMs <= 0) {
        return null;
    }
    return {
        providerId: providerIdRaw.trim(),
        ...(typeof providerKeyRaw === 'string' && providerKeyRaw.trim().length
            ? { providerKey: providerKeyRaw.trim() }
            : {}),
        series: normalizedSeries,
        cooldownMs: Math.round(cooldownMs)
    };
}
export function deriveReason(code, stage, statusCode) {
    if (code.includes('RATE') || code.includes('429'))
        return 'rate_limit';
    if (code.includes('AUTH') || statusCode === 401 || statusCode === 403)
        return 'auth';
    if (stage.includes('compat'))
        return 'compatibility';
    if (code.includes('SSE'))
        return 'sse';
    if (code.includes('TIMEOUT') || statusCode === 408 || statusCode === 504)
        return 'timeout';
    if (statusCode && statusCode >= 500)
        return 'upstream_error';
    if (statusCode && statusCode >= 400)
        return 'client_error';
    return 'unknown';
}
function resolveModelSeries(modelId) {
    if (!modelId) {
        return 'default';
    }
    const lower = modelId.toLowerCase();
    if (lower.includes('claude') || lower.includes('opus')) {
        return 'claude';
    }
    if (lower.includes('flash')) {
        return 'gemini-flash';
    }
    if (lower.includes('gemini') || lower.includes('pro')) {
        return 'gemini-pro';
    }
    return 'default';
}
