mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Fixes issue where providers like DeepSeek show "Unknown API error" instead of user-friendly messages for HTTP status codes like 402 (insufficient balance/payment required). Changes: - Modify handleProviderError to prefix error messages with HTTP status code (e.g., "402 - DeepSeek completion error: ...") when status >= 400 - This allows the UI (ChatRow.tsx) to parse the status code and display appropriate user-friendly messages from the localization files - Preserves existing behavior for custom messageTransformers and non-HTTP errors The UI already has localized messages for common HTTP status codes (400, 401, 402, 403, 429, 500) but they were not being shown because the error message format did not allow the UI to extract the status code. Closes #10016
124 lines
4.3 KiB
TypeScript
124 lines
4.3 KiB
TypeScript
/**
|
|
* General error handler for API provider errors
|
|
* Transforms technical errors into user-friendly messages while preserving metadata
|
|
*
|
|
* This utility ensures consistent error handling across all API providers:
|
|
* - Preserves HTTP status codes for UI-aware error display
|
|
* - Maintains error details for retry logic (e.g., RetryInfo for 429 errors)
|
|
* - Provides consistent error message formatting
|
|
* - Enables telemetry and debugging with complete error context
|
|
*/
|
|
|
|
import i18n from "../../../i18n/setup"
|
|
|
|
/**
|
|
* Handles API provider errors and transforms them into user-friendly messages
|
|
* while preserving important metadata for retry logic and UI display.
|
|
*
|
|
* @param error - The error to handle
|
|
* @param providerName - The name of the provider for context in error messages
|
|
* @param options - Optional configuration for error handling
|
|
* @returns A wrapped Error with preserved metadata (status, errorDetails, code)
|
|
*
|
|
* @example
|
|
* // Basic usage
|
|
* try {
|
|
* await apiClient.createMessage(...)
|
|
* } catch (error) {
|
|
* throw handleProviderError(error, "OpenAI")
|
|
* }
|
|
*
|
|
* @example
|
|
* // With custom message prefix
|
|
* catch (error) {
|
|
* throw handleProviderError(error, "Anthropic", { messagePrefix: "streaming" })
|
|
* }
|
|
*/
|
|
export function handleProviderError(
|
|
error: unknown,
|
|
providerName: string,
|
|
options?: {
|
|
/** Custom message prefix (default: "completion") */
|
|
messagePrefix?: string
|
|
/** Custom message transformer */
|
|
messageTransformer?: (msg: string) => string
|
|
},
|
|
): Error {
|
|
const messagePrefix = options?.messagePrefix || "completion"
|
|
|
|
if (error instanceof Error) {
|
|
const anyErr = error as any
|
|
const msg = anyErr?.error?.metadata?.raw || error.message || ""
|
|
const status = anyErr.status
|
|
|
|
// Log the original error details for debugging
|
|
console.error(`[${providerName}] API error:`, {
|
|
message: msg,
|
|
name: error.name,
|
|
stack: error.stack,
|
|
status,
|
|
})
|
|
|
|
let wrapped: Error
|
|
|
|
// Special case: Invalid character/ByteString conversion error in API key
|
|
// This is specific to OpenAI-compatible SDKs
|
|
if (msg.includes("Cannot convert argument to a ByteString")) {
|
|
wrapped = new Error(i18n.t("common:errors.api.invalidKeyInvalidChars"))
|
|
} else {
|
|
// Apply custom transformer if provided, otherwise use default format
|
|
// IMPORTANT: When HTTP status is available, prefix the message with the status code
|
|
// This allows the UI (ChatRow.tsx) to parse the status and show user-friendly error messages
|
|
// for known status codes (e.g., 402 = insufficient balance, 429 = rate limit)
|
|
let finalMessage: string
|
|
if (options?.messageTransformer) {
|
|
finalMessage = options.messageTransformer(msg)
|
|
} else if (typeof status === "number" && status >= 400) {
|
|
// Prefix with status code for UI parsing (e.g., "402 - DeepSeek completion error: ...")
|
|
finalMessage = `${status} - ${providerName} ${messagePrefix} error: ${msg}`
|
|
} else {
|
|
finalMessage = `${providerName} ${messagePrefix} error: ${msg}`
|
|
}
|
|
wrapped = new Error(finalMessage)
|
|
}
|
|
|
|
// Preserve HTTP status and structured details for retry/backoff + UI
|
|
// These fields are used by Task.backoffAndAnnounce() and ChatRow/ErrorRow
|
|
// to provide status-aware error messages and handling
|
|
if (status !== undefined) {
|
|
;(wrapped as any).status = status
|
|
}
|
|
if (anyErr.errorDetails !== undefined) {
|
|
;(wrapped as any).errorDetails = anyErr.errorDetails
|
|
}
|
|
if (anyErr.code !== undefined) {
|
|
;(wrapped as any).code = anyErr.code
|
|
}
|
|
// Preserve AWS-specific metadata if present (for Bedrock)
|
|
if (anyErr.$metadata !== undefined) {
|
|
;(wrapped as any).$metadata = anyErr.$metadata
|
|
}
|
|
|
|
return wrapped
|
|
}
|
|
|
|
// Non-Error: wrap with provider-specific prefix
|
|
console.error(`[${providerName}] Non-Error exception:`, error)
|
|
const wrapped = new Error(`${providerName} ${messagePrefix} error: ${String(error)}`)
|
|
|
|
// Also try to preserve status for non-Error exceptions (e.g., plain objects with status)
|
|
const anyErr = error as any
|
|
if (typeof anyErr?.status === "number") {
|
|
;(wrapped as any).status = anyErr.status
|
|
}
|
|
|
|
return wrapped
|
|
}
|
|
|
|
/**
|
|
* Specialized handler for OpenAI-compatible providers
|
|
* Re-exports with OpenAI-specific defaults for backward compatibility
|
|
*/
|
|
export function handleOpenAIError(error: unknown, providerName: string): Error {
|
|
return handleProviderError(error, providerName, { messagePrefix: "completion" })
|
|
}
|