From 95c5a75ca39cc0217c0926397e9732a57cf4b33f Mon Sep 17 00:00:00 2001 From: singledog957 <125362381+singledog957@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:52:18 +0800 Subject: [PATCH] feat: Show detailed error messages instead of generic 'Internal server error' (#144) (#154) * feat: Show detailed error messages instead of generic 'Internal server error' (#144) * refactor: simplify error handling logic per feedback * refactor: imported AI SDK error handler * fix: remove unused import and expand sensitive data filter - Remove unused NoSuchModelError import - Add 'secret', 'password', 'credential' to sensitive data filter --------- Co-authored-by: dayuan.jiang --- app/api/chat/route.ts | 70 ++++++++++++++++++++++++++++++++++++--- components/chat-panel.tsx | 14 ++++++-- package-lock.json | 24 +++++++++++++- 3 files changed, 100 insertions(+), 8 deletions(-) diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 6df3b0f..ff073cf 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -1,7 +1,9 @@ import { + APICallError, convertToModelMessages, createUIMessageStream, createUIMessageStreamResponse, + LoadAPIKeyError, stepCountIs, streamText, } from "ai" @@ -451,16 +453,74 @@ IMPORTANT: Keep edits concise: }) } +// Helper to categorize errors and return appropriate response +function handleError(error: unknown): Response { + console.error("Error in chat route:", error) + + const isDev = process.env.NODE_ENV === "development" + + // Check for specific AI SDK error types + if (APICallError.isInstance(error)) { + return Response.json( + { + error: error.message, + ...(isDev && { + details: error.responseBody, + stack: error.stack, + }), + }, + { status: error.statusCode || 500 }, + ) + } + + if (LoadAPIKeyError.isInstance(error)) { + return Response.json( + { + error: "Authentication failed. Please check your API key.", + ...(isDev && { + stack: error.stack, + }), + }, + { status: 401 }, + ) + } + + // Fallback for other errors with safety filter + const message = + error instanceof Error ? error.message : "An unexpected error occurred" + const status = (error as any)?.statusCode || (error as any)?.status || 500 + + // Prevent leaking API keys, tokens, or other sensitive data + const lowerMessage = message.toLowerCase() + const safeMessage = + lowerMessage.includes("key") || + lowerMessage.includes("token") || + lowerMessage.includes("sig") || + lowerMessage.includes("signature") || + lowerMessage.includes("secret") || + lowerMessage.includes("password") || + lowerMessage.includes("credential") + ? "Authentication failed. Please check your credentials." + : message + + return Response.json( + { + error: safeMessage, + ...(isDev && { + details: message, + stack: error instanceof Error ? error.stack : undefined, + }), + }, + { status }, + ) +} + // Wrap handler with error handling async function safeHandler(req: Request): Promise { try { return await handleChatRequest(req) } catch (error) { - console.error("Error in chat route:", error) - return Response.json( - { error: "Internal server error" }, - { status: 500 }, - ) + return handleError(error) } } diff --git a/components/chat-panel.tsx b/components/chat-panel.tsx index 52e85b3..7377c22 100644 --- a/components/chat-panel.tsx +++ b/components/chat-panel.tsx @@ -462,13 +462,23 @@ Please retry with an adjusted search pattern or use display_diagram if retries a console.error("Chat error:", error) } + // Translate technical errors into user-friendly messages + // The server now handles detailed error messages, so we can display them directly. + // But we still handle connection/network errors that happen before reaching the server. + let friendlyMessage = error.message + + // Simple check for network errors if message is generic + if (friendlyMessage === "Failed to fetch") { + friendlyMessage = "Network error. Please check your connection." + } + // Add system message for error so it can be cleared setMessages((currentMessages) => { const errorMessage = { id: `error-${Date.now()}`, role: "system" as const, - content: error.message, - parts: [{ type: "text" as const, text: error.message }], + content: friendlyMessage, + parts: [{ type: "text" as const, text: friendlyMessage }], } return [...currentMessages, errorMessage] }) diff --git a/package-lock.json b/package-lock.json index 162c312..a76538e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1120,6 +1120,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1606,6 +1607,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -1628,6 +1630,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -2735,6 +2738,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -2768,6 +2772,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -2783,6 +2788,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.208.0.tgz", "integrity": "sha512-jbzDw1q+BkwKFq9yxhjAJ9rjKldbt5AgIy1gmEIJjEV/WRxQ3B6HcLVkwbjJ3RcMif86BDNKR846KJ0tY0aOJA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-exporter-base": "0.208.0", @@ -2888,6 +2894,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", @@ -5294,6 +5301,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.0.tgz", "integrity": "sha512-UaicktuQI+9UKyA4njtDOGBD/67t8YEBt2xdfqu8+gP9hqPUPsiXlNPcpS2gVdjmis5GKPG3fCxbQLVgxsQZ8w==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -5304,6 +5312,7 @@ "integrity": "sha512-jFf/woGTVTjUJsl2O7hcopJ1r0upqoq/vIOoCj0yLh3RIXxWcljlpuZ+vEBRXsymD1jhfeJrlyTy/S1UW+4y1w==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -5360,6 +5369,7 @@ "integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", @@ -5908,6 +5918,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5939,6 +5950,7 @@ "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.107.tgz", "integrity": "sha512-laZlS9ZC/DZfSaxPgrBqI4mM+kxRvTPBBQfa74ceBFskkunZKEsaGVFNEs4cfyGa3nCCCl1WO/fjxixp4V8Zag==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@ai-sdk/gateway": "2.0.18", "@ai-sdk/provider": "2.0.0", @@ -6371,6 +6383,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -7192,6 +7205,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7377,6 +7391,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -10430,6 +10445,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-16.0.7.tgz", "integrity": "sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "16.0.7", "@swc/helpers": "0.5.15", @@ -11013,6 +11029,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11022,6 +11039,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -12059,7 +12077,8 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.2.tgz", "integrity": "sha512-VCsK+fitIbQF7JlxXaibFhxrPq4E2hDcG8apzHUdWFMCQWD8uLdlHg4iSkZ53cgLCCcZ+FZK7vG8VjvLcnBgKw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tailwindcss-animate": { "version": "1.0.7", @@ -12146,6 +12165,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -12370,6 +12390,7 @@ "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12980,6 +13001,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }