TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1import { PolyProviderError, type PolyErrorCode, type ProviderId } from "./types";2import { redactSecrets } from "@/lib/crypto/keys";34export function isRetryableStatus(status?: number): boolean {5 return status === 408 || status === 409 || status === 429 || (status !== undefined && status >= 500 && status <= 599);6}78export function codeFromStatus(status: number | undefined): PolyErrorCode {9 switch (status) {10 case 401:11 return "INVALID_API_KEY";12 case 402:13 return "INSUFFICIENT_CREDITS";14 case 403:15 return "PERMISSION_DENIED";16 case 404:17 return "MODEL_NOT_FOUND";18 case 408:19 return "REQUEST_TIMEOUT";20 case 413:21 return "CONTEXT_TOO_LONG";22 case 422:23 case 400:24 return "INVALID_PARAMETER";25 case 429:26 return "RATE_LIMITED";27 case 500:28 case 502:29 case 503:30 case 504:31 case 529:32 return "PROVIDER_UNAVAILABLE";33 default:34 return "UNKNOWN_PROVIDER_ERROR";35 }36}3738/** Refine a 400-ish error using the provider's message text (all providers put the reason in prose). */39export function refineByMessage(code: PolyErrorCode, message: string): PolyErrorCode {40 const m = message.toLowerCase();41 if (/context.length|context_length|too many tokens|maximum context|prompt is too long|exceeds the (model'?s )?(maximum )?(context|token)|input token count|token limit|max_tokens.*(too large|exceed)/.test(m)) return "CONTEXT_TOO_LONG";42 if (/(insufficient|exceeded your current) (quota|credits|balance)|billing|credit balance is too low|payment/.test(m)) return "INSUFFICIENT_CREDITS";43 if (/(model|deployment).*(not found|does not exist|not exist|unknown|unsupported model)|not found for model|no such model/.test(m)) return "MODEL_NOT_FOUND";44 if (/(invalid|incorrect) (api key|x-api-key|authentication)|api key (not valid|invalid|expired)|bad credentials|unauthenticated|authentication_error/.test(m)) return "INVALID_API_KEY";45 if (/rate.?limit|too many requests|resource.?exhausted|overloaded|quota exceeded/.test(m)) return "RATE_LIMITED";46 if (/safety|content (policy|filter|management)|blocked|prohibited|refus|harm_category/.test(m)) return "CONTENT_REJECTED";47 if (/timed? ?out|deadline exceeded/.test(m)) return "REQUEST_TIMEOUT";48 return code;49}5051export function parseRetryAfter(headers?: Headers | Record<string, string | string[] | undefined> | null): number | undefined {52 if (!headers) return undefined;53 const get = (k: string): string | undefined => {54 if (headers instanceof Headers) return headers.get(k) ?? undefined;55 const v = headers[k] ?? headers[k.toLowerCase()];56 return Array.isArray(v) ? v[0] : v;57 };58 const ra = get("retry-after");59 if (ra) {60 const secs = Number(ra);61 if (!Number.isNaN(secs)) return Math.min(secs * 1000, 60_000);62 const date = Date.parse(ra);63 if (!Number.isNaN(date)) return Math.max(0, Math.min(date - Date.now(), 60_000));64 }65 const raMs = get("retry-after-ms");66 if (raMs && !Number.isNaN(Number(raMs))) return Math.min(Number(raMs), 60_000);67 return undefined;68}6970/** Human-readable, non-technical explanations shown in the UI. */71export const ERROR_MESSAGES: Record<PolyErrorCode, string> = {72 INVALID_API_KEY: "The API key for this provider was rejected. Check it in Settings → Providers.",73 PERMISSION_DENIED: "This key isn't allowed to use that model or feature.",74 RATE_LIMITED: "The provider is rate-limiting your key. Wait a moment and try again.",75 MODEL_NOT_FOUND: "This model isn't available for your account or was renamed. Refresh the model list.",76 CONTEXT_TOO_LONG: "The conversation is longer than this model's context window. Start a new chat or pick a long-context model.",77 INSUFFICIENT_CREDITS: "Your provider account has no remaining credit or quota.",78 REQUEST_TIMEOUT: "The provider took too long to respond.",79 PROVIDER_UNAVAILABLE: "The provider is temporarily unavailable. Try again shortly.",80 INVALID_PARAMETER: "A setting isn't accepted by this model. Reset the model configuration and retry.",81 CONTENT_REJECTED: "The provider declined to answer this request (content policy).",82 NETWORK_ERROR: "PolyLLM couldn't reach the provider.",83 CANCELLED: "Generation stopped.",84 UNKNOWN_PROVIDER_ERROR: "The provider returned an unexpected error.",85};8687/**88 * Generic normalizer used by adapters as a fallback: works on SDK error objects89 * exposing `status`, `message`, `headers`, `error.code/type`. Provider-specific90 * adapters call this then refine.91 */92export function normalizeGenericError(provider: ProviderId, error: unknown): PolyProviderError {93 if (error instanceof PolyProviderError) return error;94 const e = error as { status?: number; statusCode?: number; message?: string; name?: string; code?: string; headers?: Headers | Record<string, string>; error?: { code?: string; type?: string; message?: string; status?: string } } | undefined;95 const status = e?.status ?? e?.statusCode;96 const rawMessage = e?.error?.message ?? e?.message ?? String(error);97 const message = redactSecrets(rawMessage).slice(0, 600);9899 if (e?.name === "AbortError" || /aborted|abort/i.test(e?.name ?? "") || (e?.code === "ABORT_ERR")) {100 return new PolyProviderError({ code: "CANCELLED", message: "Cancelled", provider, retryable: false, cause: error });101 }102 if (e?.name === "APIConnectionTimeoutError" || /timeout/i.test(e?.name ?? "")) {103 return new PolyProviderError({ code: "REQUEST_TIMEOUT", message, provider, retryable: true, cause: error });104 }105 if (e?.name === "APIConnectionError" || e?.code === "ECONNRESET" || e?.code === "ENOTFOUND" || e?.code === "ECONNREFUSED" || e?.code === "UND_ERR_SOCKET" || /fetch failed/i.test(message)) {106 return new PolyProviderError({ code: "NETWORK_ERROR", message, provider, retryable: true, cause: error });107 }108109 let code = codeFromStatus(status);110 code = refineByMessage(code, message);111 const retryAfterMs = parseRetryAfter(e?.headers ?? null);112 return new PolyProviderError({113 code,114 message: code === "INVALID_API_KEY" ? "Invalid API key" : message,115 provider,116 status,117 retryable: isRetryableStatus(status) && code !== "INSUFFICIENT_CREDITS" && code !== "INVALID_API_KEY",118 retryAfterMs,119 providerCode: e?.error?.code ?? e?.error?.type ?? e?.error?.status ?? e?.code,120 cause: error,121 });122}123