AUDIT / src /core /errors /index.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
6.75 kB
/**
* src/core/errors/index.ts — Error taxonomy professionale
*
* Ogni errore AI/network ha un codice, un messaggio user-facing,
* e una severity. Usare sempre questi invece di string generici.
*
* Checklist 1.3 — error taxonomy
*/
// ─── Error codes ────────────────────────────────────────────────────────────
export enum ErrorCode {
NETWORK_ERROR = "NETWORK_ERROR",
RATE_LIMIT = "RATE_LIMIT",
INVALID_KEY = "INVALID_KEY",
MODEL_UNAVAILABLE = "MODEL_UNAVAILABLE",
EXECUTION_FAILED = "EXECUTION_FAILED",
MEMORY_ERROR = "MEMORY_ERROR",
OFFLINE_MODE = "OFFLINE_MODE",
TIMEOUT = "TIMEOUT",
QUOTA_EXCEEDED = "QUOTA_EXCEEDED",
PROVIDER_SWITCHING = "PROVIDER_SWITCHING",
UNKNOWN_ERROR = "UNKNOWN_ERROR",
}
export type ErrorSeverity = "info" | "warn" | "error" | "fatal";
// ─── Structured app error ───────────────────────────────────────────────────
export interface AppError {
code: ErrorCode;
message: string; // user-facing (breve)
detail?: string; // tecnico / debug
provider?: string; // es. "Groq", "Gemini"
severity: ErrorSeverity;
retryable: boolean;
timestamp: number;
}
// ─── Factory ─────────────────────────────────────────────────────────────────
export function createError(
code: ErrorCode,
opts: Partial<Omit<AppError, "code" | "timestamp">> = {},
): AppError {
return {
code,
message: opts.message ?? ERROR_MESSAGES[code],
detail: opts.detail,
provider: opts.provider,
severity: opts.severity ?? ERROR_SEVERITY[code],
retryable: opts.retryable ?? ERROR_RETRYABLE[code],
timestamp: Date.now(),
};
}
// ─── Default user-facing messages ────────────────────────────────────────────
const ERROR_MESSAGES: Record<ErrorCode, string> = {
[ErrorCode.NETWORK_ERROR]: "Errore di rete — controlla la connessione",
[ErrorCode.RATE_LIMIT]: "Limite richieste raggiunto — ritento automaticamente",
[ErrorCode.INVALID_KEY]: "Chiave API non valida — verifica nelle Impostazioni",
[ErrorCode.MODEL_UNAVAILABLE]: "Modello non disponibile — passo al prossimo",
[ErrorCode.EXECUTION_FAILED]: "Esecuzione fallita",
[ErrorCode.MEMORY_ERROR]: "Errore di memoria — prova a liberare spazio",
[ErrorCode.OFFLINE_MODE]: "Modalità offline — funzionalità limitate",
[ErrorCode.TIMEOUT]: "Timeout — la risposta ha impiegato troppo",
[ErrorCode.QUOTA_EXCEEDED]: "Quota esaurita — cambio provider",
[ErrorCode.PROVIDER_SWITCHING]: "Provider non disponibile — cambio in corso",
[ErrorCode.UNKNOWN_ERROR]: "Errore sconosciuto",
};
// ─── Default severity ────────────────────────────────────────────────────────
const ERROR_SEVERITY: Record<ErrorCode, ErrorSeverity> = {
[ErrorCode.NETWORK_ERROR]: "warn",
[ErrorCode.RATE_LIMIT]: "warn",
[ErrorCode.INVALID_KEY]: "error",
[ErrorCode.MODEL_UNAVAILABLE]: "warn",
[ErrorCode.EXECUTION_FAILED]: "error",
[ErrorCode.MEMORY_ERROR]: "error",
[ErrorCode.OFFLINE_MODE]: "info",
[ErrorCode.TIMEOUT]: "warn",
[ErrorCode.QUOTA_EXCEEDED]: "warn",
[ErrorCode.PROVIDER_SWITCHING]: "info",
[ErrorCode.UNKNOWN_ERROR]: "error",
};
// ─── Default retryable ───────────────────────────────────────────────────────
const ERROR_RETRYABLE: Record<ErrorCode, boolean> = {
[ErrorCode.NETWORK_ERROR]: true,
[ErrorCode.RATE_LIMIT]: true,
[ErrorCode.INVALID_KEY]: false,
[ErrorCode.MODEL_UNAVAILABLE]: true,
[ErrorCode.EXECUTION_FAILED]: false,
[ErrorCode.MEMORY_ERROR]: false,
[ErrorCode.OFFLINE_MODE]: false,
[ErrorCode.TIMEOUT]: true,
[ErrorCode.QUOTA_EXCEEDED]: true,
[ErrorCode.PROVIDER_SWITCHING]: true,
[ErrorCode.UNKNOWN_ERROR]: false,
};
// ─── Classifier: HTTP status + body → ErrorCode ──────────────────────────────
export function classifyHttpError(status: number, body = ""): ErrorCode {
const b = body.toLowerCase();
if (status === 401 || status === 403) return ErrorCode.INVALID_KEY;
if (status === 429 || status === 402) return ErrorCode.RATE_LIMIT;
if (status === 404) return ErrorCode.MODEL_UNAVAILABLE;
if (status === 408) return ErrorCode.TIMEOUT;
if (b.includes("quota") || b.includes("exceeded")) return ErrorCode.QUOTA_EXCEEDED;
if (b.includes("rate limit")) return ErrorCode.RATE_LIMIT;
if (b.includes("model not found")) return ErrorCode.MODEL_UNAVAILABLE;
if (status >= 500) return ErrorCode.NETWORK_ERROR;
return ErrorCode.UNKNOWN_ERROR;
}
// ─── Classifier: catch(err) → ErrorCode ─────────────────────────────────────
export function classifyJsError(err: unknown): ErrorCode {
if (!err) return ErrorCode.UNKNOWN_ERROR;
const e = err as { name?: string; message?: string; status?: number; body?: string };
if (e.name === "AbortError") return ErrorCode.TIMEOUT;
if (e.message?.includes("completion-timeout")) return ErrorCode.TIMEOUT;
if (e.message?.toLowerCase().includes("network")) return ErrorCode.NETWORK_ERROR;
if (e.message?.toLowerCase().includes("offline")) return ErrorCode.OFFLINE_MODE;
if (e.status && e.body !== undefined) return classifyHttpError(e.status, e.body);
return ErrorCode.UNKNOWN_ERROR;
}
// ─── Format for display ──────────────────────────────────────────────────────
export function formatErrorBanner(err: AppError): string {
const prefix = err.provider ? `${err.provider}: ` : "";
return `${prefix}${err.message}`;
}