diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index e04301b678c..4faf341d28f 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -20,6 +20,7 @@ import { ApiStream } from "../transform/stream" import { getModelParams } from "../transform/model-params" import { filterNonAnthropicBlocks } from "../transform/anthropic-filter" import { resolveToolProtocol } from "../../utils/resolveToolProtocol" +import { handleProviderError } from "./utils/error-handler" import { BaseProvider } from "./base-provider" import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index" diff --git a/src/api/providers/gemini.ts b/src/api/providers/gemini.ts index 2c7b10f2057..4402e3e0177 100644 --- a/src/api/providers/gemini.ts +++ b/src/api/providers/gemini.ts @@ -25,6 +25,7 @@ import { convertAnthropicMessageToGemini } from "../transform/gemini-format" import { t } from "i18next" import type { ApiStream, GroundingSource } from "../transform/stream" import { getModelParams } from "../transform/model-params" +import { handleProviderError } from "./utils/error-handler" import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index" import { BaseProvider } from "./base-provider" diff --git a/src/api/providers/mistral.ts b/src/api/providers/mistral.ts index f5f5534db2f..95739cdcf73 100644 --- a/src/api/providers/mistral.ts +++ b/src/api/providers/mistral.ts @@ -15,6 +15,7 @@ import { ApiHandlerOptions } from "../../shared/api" import { convertToMistralMessages } from "../transform/mistral-format" import { ApiStream } from "../transform/stream" +import { handleProviderError } from "./utils/error-handler" import { BaseProvider } from "./base-provider" import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index" diff --git a/src/api/providers/utils/__tests__/error-handler.spec.ts b/src/api/providers/utils/__tests__/error-handler.spec.ts new file mode 100644 index 00000000000..54971134dff --- /dev/null +++ b/src/api/providers/utils/__tests__/error-handler.spec.ts @@ -0,0 +1,283 @@ +import { handleProviderError, handleOpenAIError } from "../error-handler" + +describe("handleProviderError", () => { + const providerName = "TestProvider" + + describe("HTTP status preservation", () => { + it("should preserve status code from Error with status field", () => { + const error = new Error("API request failed") as any + error.status = 401 + + const result = handleProviderError(error, providerName) + + expect(result).toBeInstanceOf(Error) + expect(result.message).toContain("TestProvider completion error") + expect((result as any).status).toBe(401) + }) + + it("should preserve status code from Error with nested error structure", () => { + const error = new Error("Wrapped error") as any + error.status = 429 + error.errorDetails = [{ "@type": "type.googleapis.com/google.rpc.RetryInfo" }] + + const result = handleProviderError(error, providerName) + + expect((result as any).status).toBe(429) + expect((result as any).errorDetails).toBeDefined() + }) + + it("should preserve status from non-Error exception", () => { + const error = { + status: 500, + message: "Internal server error", + } + + const result = handleProviderError(error, providerName) + + expect(result).toBeInstanceOf(Error) + expect((result as any).status).toBe(500) + }) + + it("should not add status field if original error lacks it", () => { + const error = new Error("Generic error") + + const result = handleProviderError(error, providerName) + + expect(result).toBeInstanceOf(Error) + expect((result as any).status).toBeUndefined() + }) + }) + + describe("errorDetails preservation", () => { + it("should preserve errorDetails array from original error", () => { + const error = new Error("Rate limited") as any + error.status = 429 + error.errorDetails = [{ "@type": "type.googleapis.com/google.rpc.RetryInfo", retryDelay: "5s" }] + + const result = handleProviderError(error, providerName) + + expect((result as any).errorDetails).toEqual(error.errorDetails) + }) + + it("should preserve code field from original error", () => { + const error = new Error("Bad request") as any + error.code = "invalid_request" + + const result = handleProviderError(error, providerName) + + expect((result as any).code).toBe("invalid_request") + }) + + it("should preserve AWS $metadata from original error", () => { + const error = new Error("AWS error") as any + error.$metadata = { httpStatusCode: 403, requestId: "test-123" } + + const result = handleProviderError(error, providerName) + + expect((result as any).$metadata).toEqual(error.$metadata) + }) + }) + + describe("custom message prefix", () => { + it("should use custom message prefix when provided", () => { + const error = new Error("Stream failed") + + const result = handleProviderError(error, providerName, { messagePrefix: "streaming" }) + + expect(result.message).toBe("TestProvider streaming error: Stream failed") + }) + + it("should default to 'completion' prefix when not provided", () => { + const error = new Error("Request failed") + + const result = handleProviderError(error, providerName) + + expect(result.message).toBe("TestProvider completion error: Request failed") + }) + }) + + describe("custom message transformer", () => { + it("should use custom message transformer when provided", () => { + const error = new Error("API error") + + const result = handleProviderError(error, providerName, { + messageTransformer: (msg) => `Custom format: ${msg}`, + }) + + expect(result.message).toBe("Custom format: API error") + }) + + it("should preserve status even with custom transformer", () => { + const error = new Error("Rate limited") as any + error.status = 429 + + const result = handleProviderError(error, providerName, { + messageTransformer: (msg) => `Transformed: ${msg}`, + }) + + expect(result.message).toBe("Transformed: Rate limited") + expect((result as any).status).toBe(429) + }) + }) + + describe("ByteString conversion errors", () => { + it("should return localized message for ByteString conversion errors", () => { + const error = new Error("Cannot convert argument to a ByteString") + + const result = handleProviderError(error, providerName) + + expect(result.message).not.toContain("TestProvider completion error") + // The actual translated message depends on i18n setup + expect(result.message).toBeTruthy() + }) + + it("should preserve status even for ByteString errors", () => { + const error = new Error("Cannot convert argument to a ByteString") as any + error.status = 400 + + const result = handleProviderError(error, providerName) + + // Even though ByteString errors are typically client-side, + // we preserve any status metadata that exists for debugging purposes + expect((result as any).status).toBe(400) + }) + }) + + describe("error message formatting", () => { + it("should wrap error message with provider name prefix", () => { + const error = new Error("Authentication failed") + + const result = handleProviderError(error, providerName) + + expect(result.message).toBe("TestProvider completion error: Authentication failed") + }) + + it("should handle error with nested metadata", () => { + const error = new Error("Network error") as any + error.error = { + metadata: { + raw: "Connection refused", + }, + } + + const result = handleProviderError(error, providerName) + + expect(result.message).toContain("Connection refused") + expect(result.message).toContain("TestProvider completion error") + }) + + it("should handle non-Error exceptions", () => { + const error = { message: "Something went wrong" } + + const result = handleProviderError(error, providerName) + + expect(result).toBeInstanceOf(Error) + expect(result.message).toContain("TestProvider completion error") + expect(result.message).toContain("[object Object]") + }) + + it("should handle string exceptions", () => { + const error = "Connection timeout" + + const result = handleProviderError(error, providerName) + + expect(result).toBeInstanceOf(Error) + expect(result.message).toBe("TestProvider completion error: Connection timeout") + }) + }) + + describe("real-world error scenarios", () => { + it("should handle 401 Unauthorized with status and message", () => { + const error = new Error("Unauthorized") as any + error.status = 401 + + const result = handleProviderError(error, providerName) + + expect(result.message).toContain("Unauthorized") + expect((result as any).status).toBe(401) + }) + + it("should handle 429 Rate Limit with RetryInfo", () => { + const error = new Error("Rate limit exceeded") as any + error.status = 429 + error.errorDetails = [ + { + "@type": "type.googleapis.com/google.rpc.RetryInfo", + retryDelay: "10s", + }, + ] + + const result = handleProviderError(error, providerName) + + expect((result as any).status).toBe(429) + expect((result as any).errorDetails).toBeDefined() + expect((result as any).errorDetails[0].retryDelay).toBe("10s") + }) + + it("should handle 500 Internal Server Error", () => { + const error = new Error("Internal server error") as any + error.status = 500 + + const result = handleProviderError(error, providerName) + + expect((result as any).status).toBe(500) + expect(result.message).toContain("Internal server error") + }) + + it("should handle errors without status gracefully", () => { + const error = new Error("Network connectivity issue") + + const result = handleProviderError(error, providerName) + + expect(result).toBeInstanceOf(Error) + expect((result as any).status).toBeUndefined() + expect(result.message).toContain("Network connectivity issue") + }) + + it("should handle Gemini-specific errors with custom transformer", () => { + const error = new Error("Model not found") as any + error.status = 404 + + const result = handleProviderError(error, "Gemini", { + messageTransformer: (msg) => `Gemini API Error: ${msg}`, + }) + + expect(result.message).toBe("Gemini API Error: Model not found") + expect((result as any).status).toBe(404) + }) + + it("should handle Anthropic SDK errors", () => { + const error = new Error("Invalid API key") as any + error.status = 401 + error.error = { type: "authentication_error" } + + const result = handleProviderError(error, "Anthropic") + + expect((result as any).status).toBe(401) + expect(result.message).toContain("Invalid API key") + }) + }) +}) + +describe("handleOpenAIError (backward compatibility)", () => { + it("should be an alias for handleProviderError with completion prefix", () => { + const error = new Error("API failed") as any + error.status = 500 + + const result = handleOpenAIError(error, "OpenAI") + + expect(result).toBeInstanceOf(Error) + expect(result.message).toContain("OpenAI completion error") + expect((result as any).status).toBe(500) + }) + + it("should preserve backward compatibility for existing callers", () => { + const error = new Error("Authentication failed") as any + error.status = 401 + + const result = handleOpenAIError(error, "Roo Code Cloud") + + expect(result.message).toBe("Roo Code Cloud completion error: Authentication failed") + expect((result as any).status).toBe(401) + }) +}) diff --git a/src/api/providers/utils/__tests__/openai-error-handler.spec.ts b/src/api/providers/utils/__tests__/openai-error-handler.spec.ts new file mode 100644 index 00000000000..740d060ac84 --- /dev/null +++ b/src/api/providers/utils/__tests__/openai-error-handler.spec.ts @@ -0,0 +1,186 @@ +import { handleOpenAIError } from "../openai-error-handler" + +describe("handleOpenAIError", () => { + const providerName = "TestProvider" + + describe("HTTP status preservation", () => { + it("should preserve status code from Error with status field", () => { + const error = new Error("API request failed") as any + error.status = 401 + + const result = handleOpenAIError(error, providerName) + + expect(result).toBeInstanceOf(Error) + expect(result.message).toContain("TestProvider completion error") + expect((result as any).status).toBe(401) + }) + + it("should preserve status code from Error with nested error structure", () => { + const error = new Error("Wrapped error") as any + error.status = 429 + error.errorDetails = [{ "@type": "type.googleapis.com/google.rpc.RetryInfo" }] + + const result = handleOpenAIError(error, providerName) + + expect((result as any).status).toBe(429) + expect((result as any).errorDetails).toBeDefined() + }) + + it("should preserve status from non-Error exception", () => { + const error = { + status: 500, + message: "Internal server error", + } + + const result = handleOpenAIError(error, providerName) + + expect(result).toBeInstanceOf(Error) + expect((result as any).status).toBe(500) + }) + + it("should not add status field if original error lacks it", () => { + const error = new Error("Generic error") + + const result = handleOpenAIError(error, providerName) + + expect(result).toBeInstanceOf(Error) + expect((result as any).status).toBeUndefined() + }) + }) + + describe("errorDetails preservation", () => { + it("should preserve errorDetails array from original error", () => { + const error = new Error("Rate limited") as any + error.status = 429 + error.errorDetails = [{ "@type": "type.googleapis.com/google.rpc.RetryInfo", retryDelay: "5s" }] + + const result = handleOpenAIError(error, providerName) + + expect((result as any).errorDetails).toEqual(error.errorDetails) + }) + + it("should preserve code field from original error", () => { + const error = new Error("Bad request") as any + error.code = "invalid_request" + + const result = handleOpenAIError(error, providerName) + + expect((result as any).code).toBe("invalid_request") + }) + }) + + describe("ByteString conversion errors", () => { + it("should return localized message for ByteString conversion errors", () => { + const error = new Error("Cannot convert argument to a ByteString") + + const result = handleOpenAIError(error, providerName) + + expect(result.message).not.toContain("TestProvider completion error") + // The actual translated message depends on i18n setup + expect(result.message).toBeTruthy() + }) + + it("should preserve status even for ByteString errors", () => { + const error = new Error("Cannot convert argument to a ByteString") as any + error.status = 400 + + const result = handleOpenAIError(error, providerName) + + // Even though ByteString errors are typically client-side, + // we preserve any status metadata that exists for debugging purposes + expect((result as any).status).toBe(400) + }) + }) + + describe("error message formatting", () => { + it("should wrap error message with provider name prefix", () => { + const error = new Error("Authentication failed") + + const result = handleOpenAIError(error, providerName) + + expect(result.message).toBe("TestProvider completion error: Authentication failed") + }) + + it("should handle error with nested metadata", () => { + const error = new Error("Network error") as any + error.error = { + metadata: { + raw: "Connection refused", + }, + } + + const result = handleOpenAIError(error, providerName) + + expect(result.message).toContain("Connection refused") + expect(result.message).toContain("TestProvider completion error") + }) + + it("should handle non-Error exceptions", () => { + const error = { message: "Something went wrong" } + + const result = handleOpenAIError(error, providerName) + + expect(result).toBeInstanceOf(Error) + expect(result.message).toContain("TestProvider completion error") + expect(result.message).toContain("[object Object]") + }) + + it("should handle string exceptions", () => { + const error = "Connection timeout" + + const result = handleOpenAIError(error, providerName) + + expect(result).toBeInstanceOf(Error) + expect(result.message).toBe("TestProvider completion error: Connection timeout") + }) + }) + + describe("real-world error scenarios", () => { + it("should handle 401 Unauthorized with status and message", () => { + const error = new Error("Unauthorized") as any + error.status = 401 + + const result = handleOpenAIError(error, providerName) + + expect(result.message).toContain("Unauthorized") + expect((result as any).status).toBe(401) + }) + + it("should handle 429 Rate Limit with RetryInfo", () => { + const error = new Error("Rate limit exceeded") as any + error.status = 429 + error.errorDetails = [ + { + "@type": "type.googleapis.com/google.rpc.RetryInfo", + retryDelay: "10s", + }, + ] + + const result = handleOpenAIError(error, providerName) + + expect((result as any).status).toBe(429) + expect((result as any).errorDetails).toBeDefined() + expect((result as any).errorDetails[0].retryDelay).toBe("10s") + }) + + it("should handle 500 Internal Server Error", () => { + const error = new Error("Internal server error") as any + error.status = 500 + + const result = handleOpenAIError(error, providerName) + + expect((result as any).status).toBe(500) + expect(result.message).toContain("Internal server error") + }) + + it("should handle errors without status gracefully", () => { + const error = new Error("Network connectivity issue") + + const result = handleOpenAIError(error, providerName) + + expect(result).toBeInstanceOf(Error) + expect((result as any).status).toBeUndefined() + expect(result.message).toContain("Network connectivity issue") + }) + }) +}) diff --git a/src/api/providers/utils/error-handler.ts b/src/api/providers/utils/error-handler.ts new file mode 100644 index 00000000000..2c55b96f9cf --- /dev/null +++ b/src/api/providers/utils/error-handler.ts @@ -0,0 +1,114 @@ +/** + * 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 || "" + + // Log the original error details for debugging + console.error(`[${providerName}] API error:`, { + message: msg, + name: error.name, + stack: error.stack, + status: anyErr.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 + const finalMessage = options?.messageTransformer + ? options.messageTransformer(msg) + : `${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 (anyErr.status !== undefined) { + ;(wrapped as any).status = anyErr.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" }) +} diff --git a/src/api/providers/utils/openai-error-handler.ts b/src/api/providers/utils/openai-error-handler.ts index de8a53a187e..f4bd7d0348f 100644 --- a/src/api/providers/utils/openai-error-handler.ts +++ b/src/api/providers/utils/openai-error-handler.ts @@ -1,9 +1,12 @@ /** * General error handler for OpenAI client errors * Transforms technical errors into user-friendly messages + * + * @deprecated Use handleProviderError from './error-handler' instead + * This file is kept for backward compatibility */ -import i18n from "../../../i18n/setup" +import { handleProviderError } from "./error-handler" /** * Handles OpenAI client errors and transforms them into user-friendly messages @@ -12,26 +15,5 @@ import i18n from "../../../i18n/setup" * @returns The original error or a transformed user-friendly error */ export function handleOpenAIError(error: unknown, providerName: string): Error { - if (error instanceof Error) { - const msg = (error as any)?.error?.metadata?.raw || error.message || "" - - // Log the original error details for debugging - console.error(`[${providerName}] API error:`, { - message: msg, - name: error.name, - stack: error.stack, - }) - - // Invalid character/ByteString conversion error in API key - if (msg.includes("Cannot convert argument to a ByteString")) { - return new Error(i18n.t("common:errors.api.invalidKeyInvalidChars")) - } - - // For other Error instances, wrap with provider-specific prefix - return new Error(`${providerName} completion error: ${msg}`) - } - - // Non-Error: wrap with provider-specific prefix - console.error(`[${providerName}] Non-Error exception:`, error) - return new Error(`${providerName} completion error: ${String(error)}`) + return handleProviderError(error, providerName, { messagePrefix: "completion" }) } diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index e0277350c24..92ea6aa957e 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -3996,10 +3996,11 @@ export class Task extends EventEmitter implements TaskLike { // Build header text; fall back to error message if none provided let headerText if (error.status) { - // This sets the message as just the error code, for which - // ChatRow knows how to handle and use an i18n'd error string - // In development, hardcode headerText to an HTTP status code to check it - headerText = error.status + // Include both status code (for ChatRow parsing) and detailed message (for error details) + // Format: "\n" allows ChatRow to extract status via parseInt(text.substring(0,3)) + // while preserving the full error message in errorDetails for debugging + const errorMessage = error?.message || "Unknown error" + headerText = `${error.status}\n${errorMessage}` } else if (error?.message) { headerText = error.message } else { diff --git a/webview-ui/src/components/chat/ErrorRow.tsx b/webview-ui/src/components/chat/ErrorRow.tsx index 8cb2e2d4f48..30ea05fe96d 100644 --- a/webview-ui/src/components/chat/ErrorRow.tsx +++ b/webview-ui/src/components/chat/ErrorRow.tsx @@ -1,12 +1,12 @@ import React, { useState, useCallback, memo, useMemo } from "react" import { useTranslation } from "react-i18next" import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" -import { BookOpenText, MessageCircleWarning, Info, Copy, Check } from "lucide-react" +import { BookOpenText, MessageCircleWarning, Copy, Check } from "lucide-react" import { useCopyToClipboard } from "@src/utils/clipboard" import { vscode } from "@src/utils/vscode" import CodeBlock from "../common/CodeBlock" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@src/components/ui/dialog" -import { Button, Tooltip, TooltipContent, TooltipTrigger } from "../ui" +import { Button } from "../ui" import { useExtensionState } from "@src/context/ExtensionStateContext" import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel" @@ -242,19 +242,6 @@ export const ErrorRow = memo( : t("chat:apiRequest.errorMessage.docs")} )} - {formattedErrorDetails && ( - - - - - {t("chat:errorDetails.title")} - - )} )} @@ -265,6 +252,14 @@ export const ErrorRow = memo( "my-0 font-light whitespace-pre-wrap break-words text-vscode-descriptionForeground" }> {message} + {formattedErrorDetails && ( + + )}

{additionalContent} diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 4def5b58283..c91dff24ba7 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -138,7 +138,7 @@ "streaming": "Sol·licitud API...", "cancelled": "Sol·licitud API cancel·lada", "streamingFailed": "Transmissió API ha fallat", - "errorTitle": "Error API {{code}}", + "errorTitle": "Error de proveïdor {{code}}", "errorMessage": { "docs": "Documentació", "goToSettings": "Configuració", @@ -264,6 +264,7 @@ "noToolsUsedDetails": "El model ha proporcionat text/raonament però no ha cridat cap de les eines necessàries. Això sol indicar que el model ha entès malament la tasca o té dificultats per determinar quina eina utilitzar. El model ha estat sol·licitat automàticament per tornar-ho a provar amb l'ús adequat de les eines." }, "errorDetails": { + "link": "Details", "title": "Detalls de l'error", "copyToClipboard": "Copiar al porta-retalls", "copied": "Copiat!" diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 1097cdfdc5e..b6f9b2a4927 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -138,7 +138,7 @@ "streaming": "API-Anfrage...", "cancelled": "API-Anfrage abgebrochen", "streamingFailed": "API-Streaming fehlgeschlagen", - "errorTitle": "API-Fehler {{code}}", + "errorTitle": "Anbieter-Fehler {{code}}", "errorMessage": { "docs": "Dokumentation", "goToSettings": "Einstellungen", @@ -264,6 +264,7 @@ "noToolsUsedDetails": "Das Modell hat Text/Überlegungen geliefert, aber keine der erforderlichen Tools aufgerufen. Dies deutet normalerweise darauf hin, dass das Modell die Aufgabe missverstanden hat oder Schwierigkeiten hat zu bestimmen, welches Tool verwendet werden soll. Das Modell wurde automatisch aufgefordert, es erneut mit der richtigen Tool-Verwendung zu versuchen." }, "errorDetails": { + "link": "Details", "title": "Fehlerdetails", "copyToClipboard": "In Zwischenablage kopieren", "copied": "Kopiert!" diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 5c8feb122bf..b1a50fd7531 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -144,7 +144,7 @@ "streaming": "API Request...", "cancelled": "API Request Cancelled", "streamingFailed": "API Streaming Failed", - "errorTitle": "API Error {{code}}", + "errorTitle": "Provider Error {{code}}", "errorMessage": { "docs": "Docs", "goToSettings": "Settings", @@ -291,6 +291,7 @@ "noToolsUsedDetails": "The model provided text/reasoning but did not call any of the required tools. This usually indicates the model misunderstood the task or is having difficulty determining which tool to use. The model has been automatically prompted to retry with proper tool usage." }, "errorDetails": { + "link": "Details", "title": "Error Details", "copyToClipboard": "Copy to Clipboard", "copied": "Copied!" diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index c14939f77be..fdf0a4eb31f 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -138,7 +138,7 @@ "streaming": "Solicitud API...", "cancelled": "Solicitud API cancelada", "streamingFailed": "Transmisión API falló", - "errorTitle": "Error API {{code}}", + "errorTitle": "Error del proveedor {{code}}", "errorMessage": { "docs": "Documentación", "goToSettings": "Configuración", @@ -264,6 +264,7 @@ "noToolsUsedDetails": "El modelo proporcionó texto/razonamiento pero no llamó a ninguna de las herramientas necesarias. Esto generalmente indica que el modelo malinterpretó la tarea o tiene dificultades para determinar qué herramienta usar. El modelo ha sido solicitado automáticamente para reintentar con el uso adecuado de herramientas." }, "errorDetails": { + "link": "Detalles", "title": "Detalles del error", "copyToClipboard": "Copiar al portapapeles", "copied": "¡Copiado!" diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index bbf41a58225..6cac21b8e25 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -138,7 +138,7 @@ "streaming": "Requête API...", "cancelled": "Requête API annulée", "streamingFailed": "Échec du streaming API", - "errorTitle": "Erreur API {{code}}", + "errorTitle": "Erreur du fournisseur {{code}}", "errorMessage": { "docs": "Documentation", "goToSettings": "Paramètres", @@ -264,6 +264,7 @@ "noToolsUsedDetails": "Le modèle a fourni du texte/raisonnement mais n'a appelé aucun des outils requis. Cela indique généralement que le modèle a mal compris la tâche ou a des difficultés à déterminer quel outil utiliser. Le modèle a été automatiquement invité à réessayer avec l'utilisation appropriée des outils." }, "errorDetails": { + "link": "Détails", "title": "Détails de l'erreur", "copyToClipboard": "Copier dans le presse-papiers", "copied": "Copié !" diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index ca3bf0ab50c..4b09782639d 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -138,7 +138,7 @@ "streaming": "API अनुरोध...", "cancelled": "API अनुरोध रद्द किया गया", "streamingFailed": "API स्ट्रीमिंग विफल हुई", - "errorTitle": "API त्रुटि {{code}}", + "errorTitle": "प्रदाता त्रुटि {{code}}", "errorMessage": { "docs": "डॉक्स", "goToSettings": "सेटिंग्स", @@ -264,6 +264,7 @@ "noToolsUsedDetails": "मॉडल ने टेक्स्ट/तर्क प्रदान किया लेकिन किसी भी आवश्यक टूल को कॉल नहीं किया। यह आमतौर पर संकेत देता है कि मॉडल ने कार्य को गलत समझा या किस टूल का उपयोग करना है यह निर्धारित करने में कठिनाई हो रही है। मॉडल को स्वचालित रूप से उचित टूल उपयोग के साथ पुनः प्रयास करने के लिए कहा गया है।" }, "errorDetails": { + "link": "विवरण", "title": "त्रुटि विवरण", "copyToClipboard": "क्लिपबोर्ड पर कॉपी करें", "copied": "कॉपी किया गया!" diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 1caea65b046..c72f1abd024 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -147,7 +147,7 @@ "streaming": "Permintaan API...", "cancelled": "Permintaan API Dibatalkan", "streamingFailed": "Streaming API Gagal", - "errorTitle": "Kesalahan API {{code}}", + "errorTitle": "Kesalahan Penyedia {{code}}", "errorMessage": { "docs": "Dokumentasi", "goToSettings": "Pengaturan", @@ -300,6 +300,7 @@ }, "troubleMessage": "Roo mengalami masalah...", "errorDetails": { + "link": "Details", "title": "Detail Kesalahan", "copyToClipboard": "Salin ke Clipboard", "copied": "Disalin!" diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 615f9281a97..a1a22db25ff 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -141,7 +141,7 @@ "streaming": "Richiesta API...", "cancelled": "Richiesta API annullata", "streamingFailed": "Streaming API fallito", - "errorTitle": "Errore API {{code}}", + "errorTitle": "Errore del fornitore {{code}}", "errorMessage": { "docs": "Documentazione", "goToSettings": "Impostazioni", @@ -264,6 +264,7 @@ "noToolsUsedDetails": "Il modello ha fornito testo/ragionamento ma non ha chiamato nessuno degli strumenti richiesti. Questo di solito indica che il modello ha frainteso l'attività o ha difficoltà a determinare quale strumento utilizzare. Il modello è stato automaticamente sollecitato a riprovare con l'uso appropriato degli strumenti." }, "errorDetails": { + "link": "Dettagli", "title": "Dettagli errore", "copyToClipboard": "Copia negli appunti", "copied": "Copiato!" diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index be701e92fcc..e12c0866b9e 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -138,7 +138,7 @@ "streaming": "APIリクエスト...", "cancelled": "APIリクエストキャンセル", "streamingFailed": "APIストリーミング失敗", - "errorTitle": "APIエラー {{code}}", + "errorTitle": "プロバイダーエラー {{code}}", "errorMessage": { "docs": "ドキュメント", "goToSettings": "設定", @@ -264,6 +264,7 @@ "noToolsUsedDetails": "モデルはテキスト/推論を提供しましたが、必要なツールを呼び出しませんでした。これは通常、モデルがタスクを誤解したか、どのツールを使用するかを決定するのに苦労していることを示しています。モデルは適切なツールの使用で再試行するよう自動的に促されました。" }, "errorDetails": { + "link": "詳細", "title": "エラー詳細", "copyToClipboard": "クリップボードにコピー", "copied": "コピーしました!" diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 676e64f6db1..d268809e826 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -138,7 +138,7 @@ "streaming": "API 요청...", "cancelled": "API 요청 취소됨", "streamingFailed": "API 스트리밍 실패", - "errorTitle": "API 오류 {{code}}", + "errorTitle": "공급자 오류 {{code}}", "errorMessage": { "docs": "문서", "goToSettings": "설정", @@ -264,6 +264,7 @@ "noToolsUsedDetails": "모델이 텍스트/추론을 제공했지만 필요한 도구를 호출하지 않았습니다. 이는 일반적으로 모델이 작업을 잘못 이해했거나 어떤 도구를 사용할지 결정하는 데 어려움을 겪고 있음을 나타냅니다. 모델이 적절한 도구 사용으로 다시 시도하도록 자동으로 요청되었습니다." }, "errorDetails": { + "link": "자세히", "title": "오류 세부 정보", "copyToClipboard": "클립보드에 복사", "copied": "복사됨!" diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 777f7812b61..f92cc414a8f 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -133,7 +133,7 @@ "streaming": "API-verzoek...", "cancelled": "API-verzoek geannuleerd", "streamingFailed": "API-streaming mislukt", - "errorTitle": "API-fout {{code}}", + "errorTitle": "Fout van provider {{code}}", "errorMessage": { "docs": "Documentatie", "goToSettings": "Instellingen", @@ -264,6 +264,7 @@ }, "troubleMessage": "Roo ondervindt problemen...", "errorDetails": { + "link": "Details", "title": "Foutdetails", "copyToClipboard": "Naar klembord kopiëren", "copied": "Gekopieerd!" diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 3971b17bf01..24c5f169932 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -138,7 +138,7 @@ "streaming": "Zapytanie API...", "cancelled": "Zapytanie API anulowane", "streamingFailed": "Strumieniowanie API nie powiodło się", - "errorTitle": "Błąd API {{code}}", + "errorTitle": "Błąd dostawcy {{code}}", "errorMessage": { "docs": "Dokumentacja", "goToSettings": "Ustawienia", @@ -264,6 +264,7 @@ "noToolsUsedDetails": "Model dostarczył tekst/rozumowanie, ale nie wywołał żadnego z wymaganych narzędzi. Zazwyczaj oznacza to, że model źle zrozumiał zadanie lub ma trudności z określeniem, którego narzędzia użyć. Model został automatycznie poproszony o ponowną próbę z odpowiednim użyciem narzędzi." }, "errorDetails": { + "link": "Details", "title": "Szczegóły błędu", "copyToClipboard": "Kopiuj do schowka", "copied": "Skopiowano!" diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 3d9f9a6f4bf..393c22842f2 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -138,7 +138,7 @@ "streaming": "Requisição API...", "cancelled": "Requisição API cancelada", "streamingFailed": "Streaming API falhou", - "errorTitle": "Erro API {{code}}", + "errorTitle": "Erro do provedor {{code}}", "errorMessage": { "docs": "Documentação", "goToSettings": "Configurações", @@ -264,6 +264,7 @@ "noToolsUsedDetails": "O modelo forneceu texto/raciocínio, mas não chamou nenhuma das ferramentas necessárias. Isso geralmente indica que o modelo entendeu mal a tarefa ou está tendo dificuldade em determinar qual ferramenta usar. O modelo foi automaticamente solicitado a tentar novamente com o uso adequado de ferramentas." }, "errorDetails": { + "link": "Details", "title": "Detalhes do erro", "copyToClipboard": "Copiar para área de transferência", "copied": "Copiado!" diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 1f2a70e0051..f40ab34f8bc 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -133,7 +133,7 @@ "streaming": "API-запрос...", "cancelled": "API-запрос отменен", "streamingFailed": "Ошибка потокового API-запроса", - "errorTitle": "Ошибка API {{code}}", + "errorTitle": "Ошибка провайдера {{code}}", "errorMessage": { "docs": "Документация", "goToSettings": "Настройки", @@ -265,6 +265,7 @@ }, "troubleMessage": "У Roo возникли проблемы...", "errorDetails": { + "link": "Подробности", "title": "Детали ошибки", "copyToClipboard": "Скопировать в буфер обмена", "copied": "Скопировано!" diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 1f5d32f73c8..cfd828fc683 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -138,7 +138,7 @@ "streaming": "API İsteği...", "cancelled": "API İsteği İptal Edildi", "streamingFailed": "API Akışı Başarısız", - "errorTitle": "API Hatası {{code}}", + "errorTitle": "Sağlayıcı Hatası {{code}}", "errorMessage": { "docs": "Belgeler", "goToSettings": "Ayarlar", @@ -265,6 +265,7 @@ "noToolsUsedDetails": "Model metin/akıl yürütme sağladı ancak gerekli araçlardan hiçbirini çağırmadı. Bu genellikle modelin görevi yanlış anladığını veya hangi aracı kullanacağını belirlemekte zorlandığını gösterir. Model, uygun araç kullanımıyla yeniden denemesi için otomatik olarak istenmiştir." }, "errorDetails": { + "link": "Details", "title": "Hata Detayları", "copyToClipboard": "Panoya Kopyala", "copied": "Kopyalandı!" diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 5574daddb21..1d05e5d8020 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -138,7 +138,7 @@ "streaming": "Yêu cầu API...", "cancelled": "Yêu cầu API đã hủy", "streamingFailed": "Streaming API thất bại", - "errorTitle": "Lỗi API {{code}}", + "errorTitle": "Lỗi nhà cung cấp {{code}}", "errorMessage": { "docs": "Tài liệu", "goToSettings": "Cài đặt", @@ -265,6 +265,7 @@ "noToolsUsedDetails": "Mô hình đã cung cấp văn bản/lý luận nhưng không gọi bất kỳ công cụ bắt buộc nào. Điều này thường cho thấy mô hình đã hiểu sai tác vụ hoặc đang gặp khó khăn trong việc xác định công cụ nào sẽ sử dụng. Mô hình đã được tự động nhắc thử lại với việc sử dụng công cụ phù hợp." }, "errorDetails": { + "link": "Chi tiết", "title": "Chi tiết lỗi", "copyToClipboard": "Sao chép vào clipboard", "copied": "Đã sao chép!" diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index e409b92c9af..f704e8fe64c 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -138,7 +138,7 @@ "streaming": "API请求...", "cancelled": "API请求已取消", "streamingFailed": "API流式传输失败", - "errorTitle": "API 错误 {{code}}", + "errorTitle": "提供商错误 {{code}}", "errorMessage": { "docs": "文档", "goToSettings": "设置", @@ -265,6 +265,7 @@ "noToolsUsedDetails": "模型提供了文本/推理,但未调用任何必需的工具。这通常表明模型误解了任务,或在确定使用哪个工具时遇到困难。系统已自动提示模型使用正确的工具重试。" }, "errorDetails": { + "link": "详情", "title": "错误详情", "copyToClipboard": "复制到剪贴板", "copied": "已复制!" diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index ceb7088388f..c24bfa79cd5 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -144,7 +144,7 @@ "streaming": "正在處理 API 請求...", "cancelled": "API 請求已取消", "streamingFailed": "API 串流處理失敗", - "errorTitle": "API 錯誤 {{code}}", + "errorTitle": "提供商錯誤 {{code}}", "errorMessage": { "docs": "文件", "goToSettings": "設定", @@ -298,6 +298,7 @@ }, "troubleMessage": "Roo 遇到問題...", "errorDetails": { + "link": "詳情", "title": "錯誤詳細資訊", "copyToClipboard": "複製到剪貼簿", "copied": "已複製!"