diff --git a/apps/desktop/src/i18n/en.ts b/apps/desktop/src/i18n/en.ts index a1f7724d6c60..8e602867f902 100644 --- a/apps/desktop/src/i18n/en.ts +++ b/apps/desktop/src/i18n/en.ts @@ -132,6 +132,7 @@ export const en: Translations = { errors: { elevenLabsNeedsKey: 'ElevenLabs STT needs ELEVENLABS_API_KEY.', elevenLabsRejectedKey: 'ElevenLabs rejected the API key (401).', + gatewayAuthFailed: 'Gateway authentication failed — check your API_SERVER_KEY.', methodNotAllowed: 'The desktop backend rejected that request (405 Method Not Allowed). Try restarting Hermes Desktop.', microphonePermission: 'Microphone permission was denied.', diff --git a/apps/desktop/src/i18n/ja.ts b/apps/desktop/src/i18n/ja.ts index 6d4bd9a89c52..d7dd7ec56510 100644 --- a/apps/desktop/src/i18n/ja.ts +++ b/apps/desktop/src/i18n/ja.ts @@ -133,6 +133,7 @@ export const ja = defineLocale({ errors: { elevenLabsNeedsKey: 'ElevenLabs STT には ELEVENLABS_API_KEY が必要です。', elevenLabsRejectedKey: 'ElevenLabs が API キーを拒否しました (401)。', + gatewayAuthFailed: 'ゲートウェイ認証に失敗しました — API_SERVER_KEY を確認してください。', methodNotAllowed: 'デスクトップバックエンドがそのリクエストを拒否しました (405 Method Not Allowed)。Hermes Desktop を再起動してください。', microphonePermission: 'マイクのアクセス許可が拒否されました。', diff --git a/apps/desktop/src/i18n/types.ts b/apps/desktop/src/i18n/types.ts index f71770813e00..65a93e3dda80 100644 --- a/apps/desktop/src/i18n/types.ts +++ b/apps/desktop/src/i18n/types.ts @@ -173,6 +173,7 @@ export interface Translations { errors: { elevenLabsNeedsKey: string elevenLabsRejectedKey: string + gatewayAuthFailed: string methodNotAllowed: string microphonePermission: string openaiRejectedApiKey: string diff --git a/apps/desktop/src/i18n/zh-hant.ts b/apps/desktop/src/i18n/zh-hant.ts index 919039f5504e..5229164c0a7a 100644 --- a/apps/desktop/src/i18n/zh-hant.ts +++ b/apps/desktop/src/i18n/zh-hant.ts @@ -129,6 +129,7 @@ export const zhHant = defineLocale({ errors: { elevenLabsNeedsKey: 'ElevenLabs STT 需要 ELEVENLABS_API_KEY。', elevenLabsRejectedKey: 'ElevenLabs 拒絕了該 API 金鑰 (401)。', + gatewayAuthFailed: '閘道認證失敗 — 請檢查你的 API_SERVER_KEY。', methodNotAllowed: '桌面後端拒絕了該請求 (405 Method Not Allowed)。請嘗試重新啟動 Hermes Desktop。', microphonePermission: '麥克風權限已被拒絕。', openaiRejectedApiKey: 'OpenAI 拒絕了該 API 金鑰。', diff --git a/apps/desktop/src/i18n/zh.ts b/apps/desktop/src/i18n/zh.ts index 53cf3a93c2bf..297d5156f2a2 100644 --- a/apps/desktop/src/i18n/zh.ts +++ b/apps/desktop/src/i18n/zh.ts @@ -129,6 +129,7 @@ export const zh: Translations = { errors: { elevenLabsNeedsKey: 'ElevenLabs STT 需要 ELEVENLABS_API_KEY。', elevenLabsRejectedKey: 'ElevenLabs 拒绝了该 API key (401)。', + gatewayAuthFailed: '网关认证失败 — 请检查你的 API_SERVER_KEY。', methodNotAllowed: '桌面后端拒绝了该请求 (405 Method Not Allowed)。请尝试重启 Hermes Desktop。', microphonePermission: '麦克风权限已被拒绝。', openaiRejectedApiKey: 'OpenAI 拒绝了该 API key。', diff --git a/apps/desktop/src/store/notifications.test.ts b/apps/desktop/src/store/notifications.test.ts new file mode 100644 index 000000000000..49cb0b1024f6 --- /dev/null +++ b/apps/desktop/src/store/notifications.test.ts @@ -0,0 +1,34 @@ +import { beforeEach, expect, test } from 'vitest' + +import { $notifications, clearNotifications, notifyError } from './notifications' + +beforeEach(() => { + clearNotifications() +}) + +function lastMessage(): string { + return $notifications.get()[0]?.message ?? '' +} + +// Regression for #39365: a gateway auth 401 (bad API_SERVER_KEY) must not be +// summarized as a provider (OpenAI/OpenRouter) API key problem. +test('gateway_auth_failed error is summarized as gateway auth, not provider key', () => { + notifyError( + new Error( + '401 {"error": {"message": "Invalid gateway API key (API_SERVER_KEY)", "type": "gateway_auth_error", "code": "gateway_auth_failed"}}' + ), + 'Request failed' + ) + + expect(lastMessage()).toContain('API_SERVER_KEY') + expect(lastMessage()).not.toMatch(/OpenAI/i) +}) + +test('provider invalid_api_key error still maps to the OpenAI summary', () => { + notifyError( + new Error('401 {"error": {"message": "Incorrect API key provided", "code": "invalid_api_key"}}'), + 'Request failed' + ) + + expect(lastMessage()).toMatch(/OpenAI rejected the API key/i) +}) diff --git a/apps/desktop/src/store/notifications.ts b/apps/desktop/src/store/notifications.ts index 97bfc31d541d..b0f8bb752c47 100644 --- a/apps/desktop/src/store/notifications.ts +++ b/apps/desktop/src/store/notifications.ts @@ -78,11 +78,11 @@ function cleanErrorText(value: string) { const ERROR_SUMMARIES: { test: (msg: string) => boolean; summarize: (msg: string) => string }[] = [ { - test: msg => /['"']code['"']\s*:\s*['"']gateway_auth_failed['"']/i.test(msg), - summarize: () => 'Gateway authentication failed — check your API_SERVER_KEY.' + test: msg => /['"]code['"]\s*:\s*['"]gateway_auth_failed['"]/i.test(msg), + summarize: () => translateNow('notifications.errors.gatewayAuthFailed') }, { - test: msg => /incorrect api key provided/i.test(msg) || /['"']code['"']\s*:\s*['"']invalid_api_key['"']/i.test(msg), + test: msg => /incorrect api key provided/i.test(msg) || /['"]code['"]\s*:\s*['"]invalid_api_key['"]/i.test(msg), summarize: msg => { const status = msg.match(/(?:error code|status(?:Code)?)[^\d]*(\d{3})/i)?.[1]