export function resolveProfileUnusableUntil(
stats: Pick<ProfileUsageStats, "cooldownUntil" | "disabledUntil">,
): number | null { const values = [stats.cooldownUntil, stats.disabledUntil]
.filter((value): value is number => typeof value === "number")
.filter((value) => Number.isFinite(value) && value > 0); if (values.length === 0) { returnnull;
} return Math.max(...values);
}
export function isActiveUnusableWindow(until: number | undefined, now: number): boolean { returntypeof until === "number" && Number.isFinite(until) && until > 0 && now < until;
}
/** *Checkifaprofileiscurrentlyincooldown(duetoratelimits,overload,orothertransientfailures).
*/
export function isProfileInCooldown(
store: AuthProfileStore,
profileId: string,
now?: number,
forModel?: string,
): boolean { if (isAuthCooldownBypassedForProvider(store.profiles[profileId]?.provider)) { returnfalse;
} const stats = store.usageStats?.[profileId]; if (!stats) { returnfalse;
} const ts = now ?? Date.now(); // Model-aware bypass: if the cooldown was caused by a rate_limit on a // specific model and the caller is requesting a *different* model, allow it. // We still honour any active billing/auth disable (`disabledUntil`) — those // are profile-wide and must not be short-circuited by model scoping. if (shouldBypassModelScopedCooldown(stats, ts, forModel)) { returnfalse;
} const unusableUntil = resolveProfileUnusableUntil(stats); return unusableUntil ? ts < unusableUntil : false;
}
/** *Returnthesoonest`unusableUntil`timestamp(msepoch)amongthegiven *profiles,or`null`whennoprofilehasarecordedcooldown.Note:the *returnedtimestampmaybeinthepastifthecooldownhasalreadyexpired.
*/
export function getSoonestCooldownExpiry(
store: AuthProfileStore,
profileIds: string[],
options?: { now?: number; forModel?: string },
): number | null { const ts = options?.now ?? Date.now();
let soonest: number | null = null;
let latestMatchingModelCooldown: number | null = null; for (const id of profileIds) { const stats = store.usageStats?.[id]; if (!stats) { continue;
} if (shouldBypassModelScopedCooldown(stats, ts, options?.forModel)) { continue;
} const until = resolveProfileUnusableUntil(stats); if (typeof until !== "number" || !Number.isFinite(until) || until <= 0) { continue;
} const matchingModelScopedCooldown =
options?.forModel &&
stats.cooldownReason === "rate_limit" &&
stats.cooldownModel === options.forModel &&
!isActiveUnusableWindow(stats.disabledUntil, ts); if (matchingModelScopedCooldown) {
latestMatchingModelCooldown =
latestMatchingModelCooldown === null ? until : Math.max(latestMatchingModelCooldown, until); continue;
} if (soonest === null || until < soonest) {
soonest = until;
}
} if (soonest === null) { return latestMatchingModelCooldown;
} if (latestMatchingModelCooldown === null) { return soonest;
} return Math.min(soonest, latestMatchingModelCooldown);
}
// Reset error counters when ALL cooldowns have expired so the profile gets // a fair retry window. Preserves lastFailureAt for the failureWindowMs // decay check in computeNextProfileUsageStats. if (profileMutated && !resolveProfileUnusableUntil(stats)) {
stats.errorCount = 0;
stats.failureCounts = undefined;
}