mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-22 06:42:19 +03:00
Validado em lote numa worktree combinada com os 10 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check-file-size` OK e **241/242** nos 29 arquivos de teste que os PRs tocam. A única "falha" não é falha: `tests/unit/autoCombo/strict-zero-cost-filter.test.ts` é um teste em estilo Vitest que eu incluí por engano na invocação do runner nativo do Node — ele quebra no import (`@vitest/runner`), não numa asserção. Ao investigar, descobri que esse arquivo não roda em nenhum dos dois runners hoje (o glob do `test:unit` não lista `autoCombo` e o `include` do Vitest só pega `.tsx` nessa pasta); é um problema pré-existente do repositório, sem relação com esta leva, e vou registrá-lo separadamente. O #12636 conflitava apenas na lista de testes do `@omniroute/opencode-plugin/package.json`, de forma aditiva: o tip já tinha `models-fetcher.test.ts` (do #12607, irmão desta mesma leva) e o #12636 acrescenta `telemetry.test.ts`. Fiz a união dos dois lados (25 arquivos contra 24 de cada) em vez de escolher um, o que teria removido um arquivo da suíte do plugin em silêncio. Obrigado, @RaviTharuma.
94 lines
3.6 KiB
TypeScript
94 lines
3.6 KiB
TypeScript
import { PROVIDER_ERROR_TYPES } from "@omniroute/open-sse/services/errorClassifier.ts";
|
|
import { isCreditsExhausted } from "@omniroute/open-sse/services/accountFallback.ts";
|
|
import { resolveProviderId, WEB_COOKIE_PROVIDERS } from "@/shared/constants/providers";
|
|
|
|
// #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".
|
|
export function isRecoverableCookieAuth401(
|
|
provider: string | null,
|
|
providerErrorType: string | null
|
|
): boolean {
|
|
return (
|
|
providerErrorType !== PROVIDER_ERROR_TYPES.ACCOUNT_DEACTIVATED &&
|
|
provider != null &&
|
|
resolveProviderId(provider) in WEB_COOKIE_PROVIDERS
|
|
);
|
|
}
|
|
// #12242 (402 variant of #3027): a bare 402 on a passthrough/gateway
|
|
// provider that multiplexes many models behind one credential
|
|
// (kilo-gateway, ollama-cloud, etc.) is a PER-MODEL billing signal, not
|
|
// proof the credential itself is dead — free models on the same connection
|
|
// remain perfectly usable. Only terminalize the whole connection for a 402
|
|
// when the provider is NOT a per-model-quota provider; the caller lets it
|
|
// fall through to the per-model lockout branch instead.
|
|
// `result.creditsExhausted` is a provider's own explicit classification
|
|
// (independent of HTTP status) and stays unconditionally terminal — it is
|
|
// not scoped by this check.
|
|
export function isConnectionWideCreditsExhausted(
|
|
status: number,
|
|
result: { permanent?: boolean; creditsExhausted?: boolean },
|
|
isPerModelQuotaProvider: boolean
|
|
): boolean {
|
|
return result.creditsExhausted || (status === 402 && !isPerModelQuotaProvider);
|
|
}
|
|
|
|
/** Credits-depleted bodies park; renewing billing-cycle quota does not. */
|
|
export function shouldParkCreditsExhausted(
|
|
status: number,
|
|
result: { permanent?: boolean; creditsExhausted?: boolean },
|
|
isPerModelQuotaProvider: boolean,
|
|
errorText: string
|
|
): boolean {
|
|
return (
|
|
isConnectionWideCreditsExhausted(status, result, isPerModelQuotaProvider) ||
|
|
(!isPerModelQuotaProvider && isCreditsExhausted(errorText))
|
|
);
|
|
}
|
|
|
|
function isNonTerminalProviderError(providerErrorType: string | null): boolean {
|
|
return (
|
|
providerErrorType === PROVIDER_ERROR_TYPES.PROJECT_ROUTE_ERROR ||
|
|
providerErrorType === PROVIDER_ERROR_TYPES.GEO_BLOCKED ||
|
|
providerErrorType === PROVIDER_ERROR_TYPES.OAUTH_INVALID_TOKEN ||
|
|
// #1010: Cloudflare fingerprint rejection is the CDN refusing the CLIENT's
|
|
// signature, not the account's credentials — never a terminal account state.
|
|
providerErrorType === PROVIDER_ERROR_TYPES.FINGERPRINT_REJECTION
|
|
);
|
|
}
|
|
|
|
function isExpiredAuthFailure(
|
|
status: number,
|
|
providerErrorType: string | null,
|
|
provider: string | null
|
|
): boolean {
|
|
return (
|
|
(providerErrorType === PROVIDER_ERROR_TYPES.ACCOUNT_DEACTIVATED ||
|
|
providerErrorType === PROVIDER_ERROR_TYPES.UNAUTHORIZED ||
|
|
status === 401) &&
|
|
!isRecoverableCookieAuth401(provider, providerErrorType)
|
|
);
|
|
}
|
|
|
|
export function resolveTerminalConnectionStatus(
|
|
status: number,
|
|
result: { permanent?: boolean; creditsExhausted?: boolean },
|
|
providerErrorType: string | null = null,
|
|
provider: string | null = null,
|
|
isPerModelQuotaProvider = false,
|
|
errorText: string = ""
|
|
): string | null {
|
|
if (shouldParkCreditsExhausted(status, result, isPerModelQuotaProvider, errorText)) {
|
|
return "credits_exhausted";
|
|
}
|
|
if (isNonTerminalProviderError(providerErrorType)) {
|
|
return null;
|
|
}
|
|
if (result.permanent || providerErrorType === PROVIDER_ERROR_TYPES.FORBIDDEN) {
|
|
return "banned";
|
|
}
|
|
if (isExpiredAuthFailure(status, providerErrorType, provider)) {
|
|
return "expired";
|
|
}
|
|
return null;
|
|
}
|