File
Blob: src/worker/lib/ai/client-error.ts
| 1 | import type { AiErrorCode } from "@/shared/ai"; |
| 2 | import { AiBackendError, AiMisconfiguredError } from "@/worker/lib/ai/types"; |
| 3 | |
| 4 | const SAFE_MESSAGES: Record<AiErrorCode, string> = { |
| 5 | ai_misconfigured: "AI backend unavailable", |
| 6 | ai_not_entitled: "AI action not permitted on this page", |
| 7 | ai_chat_failed: "Upstream AI service failed", |
| 8 | ai_chat_no_stream: "Upstream AI service did not return a stream", |
| 9 | ai_summarize_failed: "Upstream AI service failed", |
| 10 | ai_summarize_empty: "Upstream AI service returned an empty response", |
| 11 | ai_backend_failed: "Upstream AI service failed", |
| 12 | ai_failed: "AI request failed", |
| 13 | page_empty: "Page has no body text yet", |
| 14 | rate_limited: "Rate limit exceeded", |
| 15 | unauthorized: "Authentication required", |
| 16 | not_found: "Not found", |
| 17 | request_failed: "AI request failed", |
| 18 | validation_error: "Invalid request", |
| 19 | }; |
| 20 | |
| 21 | // Routes must use this when sending error text to clients. Raw upstream bodies |
| 22 | // can include prompts or provider details, so they never leave the worker |
| 23 | // boundary; only a fixed code-keyed string does. `errorContext(err)` continues |
| 24 | // to capture whatever the underlying Error carries for server logs. |
| 25 | export function clientAiErrorMessage(err: unknown): { code: AiErrorCode; message: string } { |
| 26 | const code = aiErrorCode(err); |
| 27 | return { code, message: SAFE_MESSAGES[code] ?? SAFE_MESSAGES.ai_failed }; |
| 28 | } |
| 29 | |
| 30 | function aiErrorCode(err: unknown): AiErrorCode { |
| 31 | if (err instanceof AiBackendError) return err.code; |
| 32 | if (err instanceof AiMisconfiguredError) return err.code; |
| 33 | return "ai_failed"; |
| 34 | } |