Spaces:
Paused
Paused
| import type { Context, Next } from "hono"; | |
| import type { StatusCode } from "hono/utils/http-status"; | |
| import type { OpenAIErrorBody } from "../types/openai.js"; | |
| import type { AnthropicErrorBody, AnthropicErrorType } from "../types/anthropic.js"; | |
| import { GEMINI_STATUS_MAP } from "../types/gemini.js"; | |
| function makeOpenAIError( | |
| message: string, | |
| type: string, | |
| code: string | null, | |
| ): OpenAIErrorBody { | |
| return { | |
| error: { | |
| message, | |
| type, | |
| param: null, | |
| code, | |
| }, | |
| }; | |
| } | |
| function makeAnthropicError( | |
| message: string, | |
| errorType: AnthropicErrorType, | |
| ): AnthropicErrorBody { | |
| return { type: "error", error: { type: errorType, message } }; | |
| } | |
| interface GeminiErrorBody { | |
| error: { code: number; message: string; status: string }; | |
| } | |
| function makeGeminiError( | |
| code: number, | |
| message: string, | |
| status: string, | |
| ): GeminiErrorBody { | |
| return { error: { code, message, status } }; | |
| } | |
| export async function errorHandler(c: Context, next: Next): Promise<void> { | |
| try { | |
| await next(); | |
| } catch (err: unknown) { | |
| const message = err instanceof Error ? err.message : "Internal server error"; | |
| console.error("[ErrorHandler]", err instanceof Error ? (err.stack ?? message) : message); | |
| const status = (err as { status?: number }).status; | |
| const path = c.req.path; | |
| // Malformed JSON request body should be treated as a client error. | |
| if (err instanceof SyntaxError && message.toLowerCase().includes("json")) { | |
| c.status(400); | |
| return c.json( | |
| makeOpenAIError( | |
| "Malformed JSON request body", | |
| "invalid_request_error", | |
| "invalid_json", | |
| ), | |
| ) as never; | |
| } | |
| // Anthropic Messages API errors | |
| if (path.startsWith("/v1/messages")) { | |
| if (status === 401) { | |
| c.status(401); | |
| return c.json( | |
| makeAnthropicError( | |
| "Invalid or expired token. Please re-authenticate.", | |
| "authentication_error", | |
| ), | |
| ) as never; | |
| } | |
| if (status === 429) { | |
| c.status(429); | |
| return c.json( | |
| makeAnthropicError( | |
| "Rate limit exceeded. Please try again later.", | |
| "rate_limit_error", | |
| ), | |
| ) as never; | |
| } | |
| if (status && status >= 500) { | |
| c.status(502); | |
| return c.json( | |
| makeAnthropicError(`Upstream server error: ${message}`, "api_error"), | |
| ) as never; | |
| } | |
| c.status(500); | |
| return c.json(makeAnthropicError(message, "api_error")) as never; | |
| } | |
| // Gemini API errors | |
| if (path.startsWith("/v1beta/")) { | |
| const code = status ?? 500; | |
| const geminiStatus = GEMINI_STATUS_MAP[code] ?? "INTERNAL"; | |
| c.status((code >= 400 && code < 600 ? code : 500) as StatusCode); | |
| return c.json(makeGeminiError(code, message, geminiStatus)) as never; | |
| } | |
| // Default: OpenAI-format errors | |
| if (status === 401) { | |
| c.status(401); | |
| return c.json( | |
| makeOpenAIError( | |
| "Invalid or expired ChatGPT token. Please re-authenticate.", | |
| "invalid_request_error", | |
| "invalid_api_key", | |
| ), | |
| ) as never; | |
| } | |
| if (status === 429) { | |
| c.status(429); | |
| return c.json( | |
| makeOpenAIError( | |
| "Rate limit exceeded. Please try again later.", | |
| "rate_limit_error", | |
| "rate_limit_exceeded", | |
| ), | |
| ) as never; | |
| } | |
| if (status && status >= 500) { | |
| c.status(502); | |
| return c.json( | |
| makeOpenAIError( | |
| `Upstream server error: ${message}`, | |
| "server_error", | |
| "server_error", | |
| ), | |
| ) as never; | |
| } | |
| c.status(500); | |
| return c.json( | |
| makeOpenAIError(message, "server_error", "internal_error"), | |
| ) as never; | |
| } | |
| } | |