import type { ProviderHealth } from "../types.js"; export function initialHealth(concurrency = 3): ProviderHealth { return { state: "healthy", recent429: 0, recent5xx: 0, p50LatencyMs: 0, p95LatencyMs: 0, concurrency }; } export function recordProviderResponse(current: ProviderHealth, status: number, latencyMs: number, maxConcurrency = Infinity, now = new Date()): ProviderHealth { const ceiling = Number.isFinite(maxConcurrency) ? Math.max(1, Math.floor(maxConcurrency)) : Infinity; const concurrency = Math.min(current.concurrency, ceiling); const throttles = status === 429 ? current.recent429 + 1 : Math.max(0, current.recent429 - 1); const errors = status >= 500 ? current.recent5xx + 1 : Math.max(0, current.recent5xx - 1); const samples = [current.p50LatencyMs, current.p95LatencyMs, latencyMs].filter((value) => value > 0).sort((a, b) => a - b); if (current.cooldownUntil && Date.parse(current.cooldownUntil) > now.getTime()) return { ...current, state: "cooldown", recent429: throttles, recent5xx: errors, concurrency, p50LatencyMs: samples[Math.floor(samples.length / 2)] ?? 0, p95LatencyMs: samples.at(-1) ?? 0 }; if (status === 429) { return { ...current, state: throttles > 1 ? "cooldown" : "throttled", recent429: throttles, recent5xx: errors, concurrency: Math.max(1, Math.floor(concurrency / 2)), cooldownUntil: throttles > 1 ? new Date(now.getTime() + 30_000).toISOString() : undefined, p50LatencyMs: samples[Math.floor(samples.length / 2)] ?? 0, p95LatencyMs: samples.at(-1) ?? 0 }; } const degraded = errors > 0; return { ...current, state: degraded ? "degraded" : "healthy", recent429: throttles, recent5xx: errors, concurrency: !degraded && status < 400 ? Math.min(ceiling, concurrency + 1) : concurrency, p50LatencyMs: samples[Math.floor(samples.length / 2)] ?? 0, p95LatencyMs: samples.at(-1) ?? 0, cooldownUntil: undefined }; }