import { isAccountDeactivated, isCreditsExhausted, isOAuthInvalidToken, } from "./accountFallback.ts"; export function isEmptyContentResponse(responseBody: unknown): boolean { if (!responseBody || typeof responseBody !== "object") return false; const body = responseBody as Record; if (Array.isArray(body.choices)) { const firstChoice = body.choices[0] as Record | undefined; if (!firstChoice) return true; const message = firstChoice.message as Record | undefined; const delta = firstChoice.delta as Record | 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 && (statusCode === 400 || statusCode === 402 || statusCode === 429 || statusCode === 403) ) { return PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED; } if (statusCode === 429) { 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; }