Spaces:
Running
Running
File size: 6,745 Bytes
cc11e77 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | /**
* 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}`;
}
|