export function isRetryableError(error: unknown, status?: number): boolean { if (status && status >= 500) return true; if (status === 429) return true; if (status === 408) return true; if (error instanceof TypeError) return true; if (error instanceof Error && error.name === "AbortError") return false; return false; } /** Normalize FastAPI / generic JSON error bodies into a single string for `Error.message`. */ export function formatHttpErrorMessage(status: number, body: unknown): string { const fallback = `HTTP ${status}`; if (body == null) return fallback; if (typeof body === "string") { const t = body.trim(); return t.length > 0 ? t : fallback; } if (typeof body !== "object" || Array.isArray(body)) { return fallback; } const b = body as Record; const detail = b["detail"]; if (typeof detail === "string") { const t = detail.trim(); return t.length > 0 ? t : fallback; } if (Array.isArray(detail)) { const parts = detail.map((item) => { if (typeof item === "string") return item.trim(); if (item && typeof item === "object" && !Array.isArray(item)) { const o = item as Record; const msg = typeof o["msg"] === "string" ? o["msg"].trim() : typeof o["message"] === "string" ? (o["message"] as string).trim() : ""; if (msg) { const locRaw = o["loc"]; const loc = Array.isArray(locRaw) && locRaw.length > 0 ? locRaw .filter( (x): x is string | number => typeof x === "string" || typeof x === "number", ) .join(".") : ""; return loc ? `${loc}: ${msg}` : msg; } } try { return JSON.stringify(item); } catch { return String(item); } }); const joined = parts.filter((p) => p.length > 0).join("; "); return joined.length > 0 ? joined : fallback; } if (detail && typeof detail === "object") { try { return JSON.stringify(detail); } catch { return fallback; } } const nested = b["error"]; if (nested && typeof nested === "object" && !Array.isArray(nested)) { const msg = (nested as Record)["message"]; if (typeof msg === "string" && msg.trim()) return msg.trim(); } if (typeof b["message"] === "string" && b["message"].trim()) { return (b["message"] as string).trim(); } return fallback; }