diff --git a/src/sse/services/auth.ts b/src/sse/services/auth.ts index 9f30d9c156..1ee4ba227e 100644 --- a/src/sse/services/auth.ts +++ b/src/sse/services/auth.ts @@ -1064,19 +1064,41 @@ export async function getProviderCredentials( }; } - // Quota-aware: prioritize accounts with available quota + // Quota-aware: filter out accounts with exhausted quota const withQuota = policyEligibleConnections.filter((c) => !isAccountQuotaExhausted(c.id)); const exhaustedQuota = policyEligibleConnections.filter((c) => isAccountQuotaExhausted(c.id)); - const orderedConnections = - withQuota.length > 0 ? [...withQuota, ...exhaustedQuota] : policyEligibleConnections; if (exhaustedQuota.length > 0) { - log.debug( + log.info( "AUTH", - `${provider} | quota-aware: ${withQuota.length} with quota, ${exhaustedQuota.length} exhausted` + `${provider} | quota-aware: ${withQuota.length} with quota, skipping ${exhaustedQuota.length} exhausted` ); } + if (withQuota.length === 0 && exhaustedQuota.length > 0) { + // All remaining eligible accounts are exhausted + const earliestResetAt = getEarliestFutureDate( + exhaustedQuota.map((c) => { + const entry = getQuotaCache(c.id); + return entry?.nextResetAt || null; + }) + ); + const earliestResetMs = parseFutureDateMs(earliestResetAt); + const retryAfter = earliestResetMs + ? new Date(earliestResetMs).toISOString() + : new Date(Date.now() + 5 * 60 * 1000).toISOString(); + + return { + allRateLimited: true, + retryAfter, + retryAfterHuman: formatRetryAfter(retryAfter), + lastError: `All ${provider} accounts have exhausted their quota`, + lastErrorCode: 429, + }; + } + + const orderedConnections = withQuota; + const settings = await getSettings(); const strategy = settings.fallbackStrategy || "fill-first";