mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
Integrated into release/v3.7.0. Thanks @clousky2020 for this massive and important contribution! 🎉 We've translated the deprecation comments to English for consistency, and it is now officially merged into the release branch. Great work on the ModelScope integration and Circuit Breaker!
139 lines
4.2 KiB
TypeScript
139 lines
4.2 KiB
TypeScript
import {
|
|
isAccountDeactivated,
|
|
isCreditsExhausted,
|
|
isDailyQuotaExhausted,
|
|
isOAuthInvalidToken,
|
|
} from "./accountFallback.ts";
|
|
|
|
export function isEmptyContentResponse(responseBody: unknown): boolean {
|
|
if (!responseBody || typeof responseBody !== "object") return false;
|
|
|
|
const body = responseBody as Record<string, unknown>;
|
|
|
|
if (Array.isArray(body.choices)) {
|
|
const firstChoice = body.choices[0] as Record<string, unknown> | undefined;
|
|
if (!firstChoice) return true;
|
|
|
|
const message = firstChoice.message as Record<string, unknown> | undefined;
|
|
const delta = firstChoice.delta as Record<string, unknown> | undefined;
|
|
|
|
const content = message?.content ?? delta?.content;
|
|
const reasoningContent = message?.reasoning_content ?? delta?.reasoning_content;
|
|
const hasToolCalls =
|
|
(Array.isArray(message?.tool_calls) && (message.tool_calls as unknown[]).length > 0) ||
|
|
(Array.isArray(delta?.tool_calls) && (delta.tool_calls as unknown[]).length > 0);
|
|
|
|
const hasContent = content !== null && content !== undefined && content !== "";
|
|
const hasReasoning =
|
|
reasoningContent !== null && reasoningContent !== undefined && reasoningContent !== "";
|
|
|
|
return !hasContent && !hasReasoning && !hasToolCalls;
|
|
}
|
|
|
|
if (Array.isArray(body.content)) {
|
|
return body.content.length === 0;
|
|
}
|
|
|
|
if (typeof body.text === "string") {
|
|
return body.text.trim() === "";
|
|
}
|
|
|
|
if ("content" in body) {
|
|
const content = body.content;
|
|
return content === null || content === undefined || content === "";
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
export const PROVIDER_ERROR_TYPES = {
|
|
RATE_LIMITED: "rate_limited",
|
|
UNAUTHORIZED: "unauthorized",
|
|
ACCOUNT_DEACTIVATED: "account_deactivated",
|
|
FORBIDDEN: "forbidden",
|
|
SERVER_ERROR: "server_error",
|
|
QUOTA_EXHAUSTED: "quota_exhausted",
|
|
PROJECT_ROUTE_ERROR: "project_route_error",
|
|
CONTEXT_OVERFLOW: "context_overflow",
|
|
OAUTH_INVALID_TOKEN: "oauth_invalid_token",
|
|
EMPTY_CONTENT: "empty_content",
|
|
};
|
|
|
|
export const CONTEXT_OVERFLOW_SIGNALS = [
|
|
"context overflow",
|
|
"prompt too large",
|
|
"context window",
|
|
"maximum context",
|
|
"exceeds context",
|
|
"input too long",
|
|
"token limit",
|
|
"too many tokens",
|
|
"context length",
|
|
"exceed.*context",
|
|
"messages exceed",
|
|
];
|
|
|
|
export const CONTEXT_OVERFLOW_REGEX = new RegExp(CONTEXT_OVERFLOW_SIGNALS.join("|"), "i");
|
|
|
|
export function isContextOverflow(errorText: string): boolean {
|
|
return CONTEXT_OVERFLOW_REGEX.test(String(errorText || ""));
|
|
}
|
|
|
|
function responseBodyToString(responseBody: unknown): string {
|
|
if (typeof responseBody === "string") return responseBody;
|
|
if (responseBody !== null && typeof responseBody === "object") {
|
|
try {
|
|
return JSON.stringify(responseBody);
|
|
} catch {
|
|
return "";
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
export function classifyProviderError(statusCode: number, responseBody: unknown): string | null {
|
|
const bodyStr = responseBodyToString(responseBody);
|
|
const creditsExhausted = isCreditsExhausted(bodyStr);
|
|
const accountDeactivated = isAccountDeactivated(bodyStr);
|
|
const oauthInvalid = isOAuthInvalidToken(bodyStr);
|
|
|
|
if (creditsExhausted && [400, 402, 403, 429].includes(statusCode)) {
|
|
return PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED;
|
|
}
|
|
|
|
// 429: Check if it's a daily quota exhaustion (lock until tomorrow) vs regular rate limit
|
|
if (statusCode === 429) {
|
|
if (isDailyQuotaExhausted(bodyStr)) {
|
|
return PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED;
|
|
}
|
|
return PROVIDER_ERROR_TYPES.RATE_LIMITED;
|
|
}
|
|
|
|
if (statusCode === 401) {
|
|
if (oauthInvalid) {
|
|
return PROVIDER_ERROR_TYPES.OAUTH_INVALID_TOKEN;
|
|
}
|
|
return accountDeactivated
|
|
? PROVIDER_ERROR_TYPES.ACCOUNT_DEACTIVATED
|
|
: PROVIDER_ERROR_TYPES.UNAUTHORIZED;
|
|
}
|
|
|
|
if (statusCode === 402) return PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED;
|
|
if (statusCode === 403 && accountDeactivated) {
|
|
return PROVIDER_ERROR_TYPES.ACCOUNT_DEACTIVATED;
|
|
}
|
|
if (statusCode === 403) {
|
|
if (bodyStr.includes("has not been used in project")) {
|
|
return PROVIDER_ERROR_TYPES.PROJECT_ROUTE_ERROR;
|
|
}
|
|
return PROVIDER_ERROR_TYPES.FORBIDDEN;
|
|
}
|
|
if (statusCode >= 500) return PROVIDER_ERROR_TYPES.SERVER_ERROR;
|
|
|
|
if (statusCode === 400 && isContextOverflow(bodyStr)) {
|
|
return PROVIDER_ERROR_TYPES.CONTEXT_OVERFLOW;
|
|
}
|
|
|
|
return null;
|
|
}
|