const ERROR_PAYLOAD_PREFIX_RE = /^(?:error|(?:[a-z][\w-]*\s+)?api\s*error|apierror|openai\s*error|anthropic\s*error|gateway\s*error)(?:\s+\d{3})?[:\s-]+/i; const HTTP_STATUS_PREFIX_RE = /^(?:http\s*)?(\d{3})\s+(.+)$/i; const HTTP_STATUS_CODE_PREFIX_RE = /^(?:http\s*)?(\d{3})(?:\s+([\s\S]+))?$/i; const HTML_ERROR_PREFIX_RE = /^\s*(?:; export type ApiErrorInfo = { httpCode?: string; type?: string; message?: string; requestId?: string; }; function isErrorPayloadObject(payload: unknown): payload is ErrorPayload { if (!payload || typeof payload !== "object" || Array.isArray(payload)) { return false; } const record = payload as ErrorPayload; if (record.type === "error") { return true; } if (typeof record.request_id === "string" || typeof record.requestId === "string") { return true; } if ("error" in record) { const err = record.error; if (err && typeof err === "object" && !Array.isArray(err)) { const errRecord = err as ErrorPayload; if ( typeof errRecord.message === "string" || typeof errRecord.type === "string" || typeof errRecord.code === "string" ) { return true; } } } return false; } export function parseApiErrorPayload(raw?: string): ErrorPayload | null { if (!raw) { return null; } const trimmed = raw.trim(); if (!trimmed) { return null; } const candidates = [trimmed]; if (ERROR_PAYLOAD_PREFIX_RE.test(trimmed)) { candidates.push(trimmed.replace(ERROR_PAYLOAD_PREFIX_RE, "").trim()); } for (const candidate of candidates) { if (!candidate.startsWith("{") || !candidate.endsWith("}")) { continue; } try { const parsed = JSON.parse(candidate) as unknown; if (isErrorPayloadObject(parsed)) { return parsed; } } catch { // ignore parse errors } } return null; } export function extractLeadingHttpStatus(raw: string): { code: number; rest: string } | null { const match = raw.match(HTTP_STATUS_CODE_PREFIX_RE); if (!match) { return null; } const code = Number(match[1]); if (!Number.isFinite(code)) { return null; } return { code, rest: (match[2] ?? "").trim() }; } export function isCloudflareOrHtmlErrorPage(raw: string): boolean { const trimmed = raw.trim(); if (!trimmed) { return false; } const status = extractLeadingHttpStatus(trimmed); if (!status || status.code < 500) { return false; } if (CLOUDFLARE_HTML_ERROR_CODES.has(status.code)) { return true; } return ( status.code < 600 && HTML_ERROR_PREFIX_RE.test(status.rest) && /<\/html>/i.test(status.rest) ); } export function parseApiErrorInfo(raw?: string): ApiErrorInfo | null { if (!raw) { return null; } const trimmed = raw.trim(); if (!trimmed) { return null; } let httpCode: string | undefined; let candidate = trimmed; const httpPrefixMatch = candidate.match(/^(\d{3})\s+(.+)$/s); if (httpPrefixMatch) { httpCode = httpPrefixMatch[1]; candidate = httpPrefixMatch[2].trim(); } const payload = parseApiErrorPayload(candidate); if (!payload) { return null; } const requestId = typeof payload.request_id === "string" ? payload.request_id : typeof payload.requestId === "string" ? payload.requestId : undefined; const topType = typeof payload.type === "string" ? payload.type : undefined; const topMessage = typeof payload.message === "string" ? payload.message : undefined; let errType: string | undefined; let errMessage: string | undefined; if (payload.error && typeof payload.error === "object" && !Array.isArray(payload.error)) { const err = payload.error as Record; if (typeof err.type === "string") { errType = err.type; } if (typeof err.code === "string" && !errType) { errType = err.code; } if (typeof err.message === "string") { errMessage = err.message; } } return { httpCode, type: errType ?? topType, message: errMessage ?? topMessage, requestId, }; } export function formatRawAssistantErrorForUi(raw?: string): string { const trimmed = (raw ?? "").trim(); if (!trimmed) { return "LLM request failed with an unknown error."; } const leadingStatus = extractLeadingHttpStatus(trimmed); if (leadingStatus && isCloudflareOrHtmlErrorPage(trimmed)) { return `The AI service is temporarily unavailable (HTTP ${leadingStatus.code}). Please try again in a moment.`; } const httpMatch = trimmed.match(HTTP_STATUS_PREFIX_RE); if (httpMatch) { const rest = httpMatch[2].trim(); if (!rest.startsWith("{")) { return `HTTP ${httpMatch[1]}: ${rest}`; } } const info = parseApiErrorInfo(trimmed); if (info?.message) { const prefix = info.httpCode ? `HTTP ${info.httpCode}` : "LLM error"; const type = info.type ? ` ${info.type}` : ""; const requestId = info.requestId ? ` (request_id: ${info.requestId})` : ""; return `${prefix}${type}: ${info.message}${requestId}`; } return trimmed.length > 600 ? `${trimmed.slice(0, 600)}…` : trimmed; }