From 23f65cc90b805d23103d43a8227bf73cc4fc02af Mon Sep 17 00:00:00 2001 From: YiWang24 Date: Mon, 26 Jan 2026 03:38:47 -0500 Subject: [PATCH] feat(agents): skip cooldowned providers during failover When all auth profiles for a provider are in cooldown, the failover mechanism now skips that provider immediately rather than attempting and waiting for the cooldown error. This prevents long delays when multiple OAuth providers fail in sequence. --- src/agents/model-fallback.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/agents/model-fallback.ts b/src/agents/model-fallback.ts index ea338ae60..aca834b89 100644 --- a/src/agents/model-fallback.ts +++ b/src/agents/model-fallback.ts @@ -14,6 +14,9 @@ import { resolveModelRefFromString, } from "./model-selection.js"; import type { FailoverReason } from "./pi-embedded-helpers.js"; +import { loadAuthProfileStore, isProfileInCooldown } from "./auth-profiles/usage.js"; +import { resolveAuthProfileOrder } from "./auth-profiles/order.js"; +import { getAgentAuthStoreDir } from "./agent-dirs.js"; type ModelCandidate = { provider: string; @@ -211,11 +214,34 @@ export async function runWithModelFallback(params: { model: params.model, fallbacksOverride: params.fallbacksOverride, }); + + const authStore = params.cfg + ? loadAuthProfileStore(getAgentAuthStoreDir(params.cfg, "main")) + : null; + const attempts: FallbackAttempt[] = []; let lastError: unknown; for (let i = 0; i < candidates.length; i += 1) { const candidate = candidates[i] as ModelCandidate; + + // Skip candidates that are in cooldown + if (authStore) { + const profileIds = resolveAuthProfileOrder(authStore, candidate.provider); + const isAnyProfileAvailable = profileIds.some((id) => !isProfileInCooldown(authStore, id)); + + if (profileIds.length > 0 && !isAnyProfileAvailable) { + // All profiles for this provider are in cooldown; skip without attempting + attempts.push({ + provider: candidate.provider, + model: candidate.model, + error: `Provider ${candidate.provider} is in cooldown (all profiles unavailable)`, + reason: "auth", // Best effort classification + }); + continue; + } + } + try { const result = await params.run(candidate.provider, candidate.model); return {