mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-18 05:02:15 +03:00
fix(sse): let :free OpenRouter models bypass connection-wide credits_exhausted lock (#10445)
* fix(sse): let :free OpenRouter models bypass connection-wide credits_exhausted lock A 402 from one paid OpenRouter model correctly locks the whole connection as credits_exhausted for an hour (intentional, per #6842), but that lock was also blocking every :free model on the same connection even though OpenRouter bills free models separately from account credits. Reconstructed clean against release/v3.8.50 by the maintainer: the author's original branch predated a large auth.ts import refactor; the same delta was re-applied onto the current tip and the TDD test still passes. TDD: tests/unit/openrouter-free-model-credits-exhausted.test.ts reproduces the bug (fails before the fix, passes after) and covers the three guard cases above. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test(mutation): register openrouter-free-model-credits-exhausted in stryker tap.testFiles The new unit test covers src/sse/services/auth.ts, which is one of the 31 stryker-mutated modules — per check-mutation-test-coverage every covering test must be listed in tap.testFiles or its mutant kills stop counting. Registered the file so the blocking mutation-test-coverage gate passes. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: killmonger2317-coder <282069920+killmonger2317-coder@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
f466ea91c9
commit
d33e62af9c
@@ -81,6 +81,7 @@ import {
|
||||
WEB_COOKIE_PROVIDERS,
|
||||
} from "@/shared/constants/providers";
|
||||
import { isModelExcludedByConnection } from "@/domain/connectionModelRules";
|
||||
import { isFreeModel } from "@/shared/utils/freeModels";
|
||||
import {
|
||||
applySessionAffinityPin,
|
||||
formatSessionKeyForLog,
|
||||
@@ -340,6 +341,31 @@ function isTerminalConnectionStatus(connection: ProviderConnectionView): boolean
|
||||
return status === "credits_exhausted" || status === "banned" || status === "expired";
|
||||
}
|
||||
|
||||
// OpenRouter's paid balance and its `:free`-suffixed models are billed
|
||||
// separately — a 402 from a paid model call correctly locks the whole
|
||||
// connection as credits_exhausted (see openrouter-quota-6842.test.ts), but
|
||||
// that lock must not also block :free model requests on the same
|
||||
// connection, or combo failover to the user's configured free models never
|
||||
// fires. Scoped to provider === "openrouter" + status === credits_exhausted
|
||||
// only; every other terminal status (banned, expired) and every other
|
||||
// provider keep the unconditional exclusion.
|
||||
function isTerminalConnectionStatusForModel(
|
||||
connection: ProviderConnectionView,
|
||||
provider: string,
|
||||
requestedModel: string | null
|
||||
): boolean {
|
||||
if (!isTerminalConnectionStatus(connection)) return false;
|
||||
if (
|
||||
provider === "openrouter" &&
|
||||
normalizeStatus(connection.testStatus) === "credits_exhausted" &&
|
||||
requestedModel &&
|
||||
isFreeModel("openrouter", { id: requestedModel })
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// #8200: cookie-auth providers (perplexity-web, grok-web, ...) use a rotating browser
|
||||
// session, not a static API key — a 401 means "session needs a refresh", not "dead".
|
||||
function isRecoverableCookieAuth401(
|
||||
@@ -1239,7 +1265,7 @@ export async function getProviderCredentials(
|
||||
connectionFilterStatus.set(c.id, "rateLimited");
|
||||
return false;
|
||||
}
|
||||
if (isTerminalConnectionStatus(c)) {
|
||||
if (isTerminalConnectionStatusForModel(c, provider, requestedModel)) {
|
||||
connectionFilterStatus.set(c.id, "terminalStatus");
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user