Initial WallMuse project
This commit is contained in:
137
packages/provider-adapters/src/errors.ts
Normal file
137
packages/provider-adapters/src/errors.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import type { ProviderCode, ProviderError, ProviderErrorCategory } from "./types";
|
||||
|
||||
type ErrorLike = {
|
||||
name?: string;
|
||||
message?: string;
|
||||
status?: number;
|
||||
statusCode?: number;
|
||||
code?: string;
|
||||
response?: {
|
||||
status?: number;
|
||||
data?: unknown;
|
||||
};
|
||||
cause?: unknown;
|
||||
};
|
||||
|
||||
export class NormalizedProviderError extends Error {
|
||||
readonly providerError: ProviderError;
|
||||
|
||||
constructor(providerError: ProviderError) {
|
||||
super(providerError.message);
|
||||
this.name = "NormalizedProviderError";
|
||||
this.providerError = providerError;
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeProviderError(provider: ProviderCode | string, error: unknown): ProviderError {
|
||||
if (error instanceof NormalizedProviderError) {
|
||||
return error.providerError;
|
||||
}
|
||||
|
||||
const statusCode = getStatusCode(error);
|
||||
const providerCode = getProviderErrorCode(error, statusCode);
|
||||
const message = getProviderErrorMessage(error);
|
||||
const category = categorizeProviderError(statusCode, providerCode, message);
|
||||
|
||||
return {
|
||||
provider,
|
||||
category,
|
||||
code: providerCode,
|
||||
message,
|
||||
retryable: isRetryable(category, statusCode),
|
||||
...(statusCode !== undefined ? { statusCode } : {}),
|
||||
raw: error
|
||||
};
|
||||
}
|
||||
|
||||
export function providerHttpError(provider: ProviderCode | string, statusCode: number, body: unknown): ProviderError {
|
||||
const code = getBodyErrorCode(body) ?? `HTTP_${statusCode}`;
|
||||
const message = getBodyErrorMessage(body) ?? `Provider request failed with HTTP ${statusCode}`;
|
||||
const category = categorizeProviderError(statusCode, code, message);
|
||||
|
||||
return {
|
||||
provider,
|
||||
category,
|
||||
code,
|
||||
message,
|
||||
statusCode,
|
||||
retryable: isRetryable(category, statusCode),
|
||||
raw: body
|
||||
};
|
||||
}
|
||||
|
||||
function getStatusCode(error: unknown): number | undefined {
|
||||
const value = error as ErrorLike;
|
||||
return value?.statusCode ?? value?.status ?? value?.response?.status;
|
||||
}
|
||||
|
||||
function getProviderErrorCode(error: unknown, statusCode?: number): string {
|
||||
const value = error as ErrorLike;
|
||||
const fromBody = getBodyErrorCode(value?.response?.data) ?? getBodyErrorCode(error);
|
||||
return fromBody ?? value?.code ?? (statusCode ? `HTTP_${statusCode}` : "PROVIDER_ERROR");
|
||||
}
|
||||
|
||||
function getProviderErrorMessage(error: unknown): string {
|
||||
const value = error as ErrorLike;
|
||||
return getBodyErrorMessage(value?.response?.data) ?? getBodyErrorMessage(error) ?? value?.message ?? "Provider request failed";
|
||||
}
|
||||
|
||||
function getBodyErrorCode(body: unknown): string | undefined {
|
||||
if (!body || typeof body !== "object") return undefined;
|
||||
const record = body as Record<string, unknown>;
|
||||
const nested = record.error && typeof record.error === "object" ? (record.error as Record<string, unknown>) : undefined;
|
||||
const code = nested?.code ?? nested?.type ?? record.code ?? record.error_code;
|
||||
return typeof code === "string" ? code : undefined;
|
||||
}
|
||||
|
||||
function getBodyErrorMessage(body: unknown): string | undefined {
|
||||
if (!body || typeof body !== "object") return undefined;
|
||||
const record = body as Record<string, unknown>;
|
||||
const nested = record.error && typeof record.error === "object" ? (record.error as Record<string, unknown>) : undefined;
|
||||
const message = nested?.message ?? record.message ?? record.error_msg ?? record.detail;
|
||||
return typeof message === "string" ? message : undefined;
|
||||
}
|
||||
|
||||
function categorizeProviderError(statusCode: number | undefined, code: string, message: string): ProviderErrorCategory {
|
||||
const normalized = `${code} ${message}`.toLowerCase();
|
||||
|
||||
if (statusCode === 401 || normalized.includes("invalid api key") || normalized.includes("unauthorized")) {
|
||||
return "authentication";
|
||||
}
|
||||
if (statusCode === 403 || normalized.includes("permission") || normalized.includes("forbidden")) {
|
||||
return normalized.includes("policy") || normalized.includes("safety") ? "content_policy" : "permission";
|
||||
}
|
||||
if (statusCode === 408 || normalized.includes("timeout") || normalized.includes("aborted")) {
|
||||
return "timeout";
|
||||
}
|
||||
if (statusCode === 429 || normalized.includes("rate limit") || normalized.includes("too many request")) {
|
||||
return "rate_limit";
|
||||
}
|
||||
if (normalized.includes("quota") || normalized.includes("insufficient") || normalized.includes("balance")) {
|
||||
return "quota";
|
||||
}
|
||||
if (normalized.includes("content policy") || normalized.includes("safety") || normalized.includes("sensitive")) {
|
||||
return "content_policy";
|
||||
}
|
||||
if (statusCode === 400 || statusCode === 422) {
|
||||
return "bad_request";
|
||||
}
|
||||
if (statusCode === 404) {
|
||||
return "not_found";
|
||||
}
|
||||
if (statusCode && statusCode >= 500) {
|
||||
return "provider_unavailable";
|
||||
}
|
||||
if (normalized.includes("fetch failed") || normalized.includes("econn") || normalized.includes("network")) {
|
||||
return "network";
|
||||
}
|
||||
|
||||
return statusCode ? "provider_error" : "unknown";
|
||||
}
|
||||
|
||||
function isRetryable(category: ProviderErrorCategory, statusCode?: number): boolean {
|
||||
if (category === "rate_limit" || category === "timeout" || category === "network" || category === "provider_unavailable") {
|
||||
return true;
|
||||
}
|
||||
return typeof statusCode === "number" && statusCode >= 500;
|
||||
}
|
||||
Reference in New Issue
Block a user