From 939ab58b8d918de7d8d2c5ca8d0ea5888041a815 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 11 May 2026 22:17:46 -0400 Subject: [PATCH] fix(desktop): suppress generic provider warning in onboarding Hide the red setup notice when the message is the generic missing-provider guidance, since onboarding already presents provider auth actions. Centralize provider-setup matching across desktop hooks and add coverage for the matcher. --- .../app/session/hooks/use-message-stream.ts | 6 ++--- .../app/session/hooks/use-prompt-actions.ts | 5 ++-- .../components/desktop-onboarding-overlay.tsx | 4 ++- .../src/lib/provider-setup-errors.test.ts | 25 +++++++++++++++++++ apps/desktop/src/lib/provider-setup-errors.ts | 12 +++++++++ 5 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 apps/desktop/src/lib/provider-setup-errors.test.ts create mode 100644 apps/desktop/src/lib/provider-setup-errors.ts diff --git a/apps/desktop/src/app/session/hooks/use-message-stream.ts b/apps/desktop/src/app/session/hooks/use-message-stream.ts index b747d1d7a42..7bd1b956e37 100644 --- a/apps/desktop/src/app/session/hooks/use-message-stream.ts +++ b/apps/desktop/src/app/session/hooks/use-message-stream.ts @@ -15,6 +15,7 @@ import { } from '@/lib/chat-messages' import { coerceGatewayText, coerceThinkingText, normalizePersonalityValue } from '@/lib/chat-runtime' import { triggerHaptic } from '@/lib/haptics' +import { isProviderSetupErrorMessage } from '@/lib/provider-setup-errors' import { setClarifyRequest } from '@/store/clarify' import { notify } from '@/store/notifications' import { requestDesktopOnboarding } from '@/store/onboarding' @@ -35,9 +36,6 @@ import type { RpcEvent } from '@/types/hermes' import type { ClientSessionState } from '../../types' -const PROVIDER_SETUP_ERROR_RE = - /No inference provider configured|no_provider_configured|OPENROUTER_API_KEY|OPENAI_API_KEY|ANTHROPIC_API_KEY|set an API key/i - interface MessageStreamOptions { activeSessionIdRef: MutableRefObject hydrateFromStoredSession: ( @@ -598,7 +596,7 @@ export function useMessageStream({ } } else if (event.type === 'error') { const errorMessage = payload?.message || 'Hermes reported an error' - const looksLikeProviderSetup = PROVIDER_SETUP_ERROR_RE.test(errorMessage) + const looksLikeProviderSetup = isProviderSetupErrorMessage(errorMessage) if (looksLikeProviderSetup) { requestDesktopOnboarding(errorMessage) diff --git a/apps/desktop/src/app/session/hooks/use-prompt-actions.ts b/apps/desktop/src/app/session/hooks/use-prompt-actions.ts index af6d8dd1ccb..bee5f78f09e 100644 --- a/apps/desktop/src/app/session/hooks/use-prompt-actions.ts +++ b/apps/desktop/src/app/session/hooks/use-prompt-actions.ts @@ -18,6 +18,7 @@ import { isDesktopSlashCommand } from '@/lib/desktop-slash-commands' import { triggerHaptic } from '@/lib/haptics' +import { isProviderSetupErrorMessage } from '@/lib/provider-setup-errors' import { $composerAttachments, addComposerAttachment, @@ -49,9 +50,7 @@ function blobToDataUrl(blob: Blob): Promise { function isProviderSetupError(error: unknown) { const message = error instanceof Error ? error.message : String(error) - return /No inference provider configured|OPENROUTER_API_KEY|OPENAI_API_KEY|ANTHROPIC_API_KEY|set an API key/i.test( - message - ) + return isProviderSetupErrorMessage(message) } interface PromptActionsOptions { diff --git a/apps/desktop/src/components/desktop-onboarding-overlay.tsx b/apps/desktop/src/components/desktop-onboarding-overlay.tsx index 3c9455f1172..fce4fec0c40 100644 --- a/apps/desktop/src/components/desktop-onboarding-overlay.tsx +++ b/apps/desktop/src/components/desktop-onboarding-overlay.tsx @@ -5,6 +5,7 @@ import { ModelPickerDialog } from '@/components/model-picker' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Check, ChevronLeft, ChevronRight, ExternalLink, KeyRound, Loader2, Sparkles } from '@/lib/icons' +import { isProviderSetupErrorMessage } from '@/lib/provider-setup-errors' import { cn } from '@/lib/utils' import { $desktopBoot, type DesktopBootState } from '@/store/boot' import { @@ -137,7 +138,8 @@ export function DesktopOnboardingOverlay({ enabled, onCompleted, requestGateway } const { flow } = onboarding - const reason = onboarding.reason?.trim() || null + const rawReason = onboarding.reason?.trim() || null + const reason = rawReason && !isProviderSetupErrorMessage(rawReason) ? rawReason : null const ready = enabled && onboarding.configured === false const showPicker = flow.status === 'idle' || flow.status === 'success' diff --git a/apps/desktop/src/lib/provider-setup-errors.test.ts b/apps/desktop/src/lib/provider-setup-errors.test.ts new file mode 100644 index 00000000000..62f99e3a2df --- /dev/null +++ b/apps/desktop/src/lib/provider-setup-errors.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest' + +import { isProviderSetupErrorMessage } from './provider-setup-errors' + +describe('isProviderSetupErrorMessage', () => { + it('matches generic missing-provider copy', () => { + expect(isProviderSetupErrorMessage('No inference provider configured. Run `hermes model` to choose one.')).toBe(true) + expect(isProviderSetupErrorMessage('No inference provider is configured.')).toBe(true) + expect(isProviderSetupErrorMessage('set an API key (OPENROUTER_API_KEY) in ~/.hermes/.env')).toBe(true) + }) + + it('does not match non-provider runtime failures', () => { + expect( + isProviderSetupErrorMessage( + 'Selected runtime is not available. setup.status reports configured credentials.' + ) + ).toBe(false) + }) + + it('returns false for empty input', () => { + expect(isProviderSetupErrorMessage('')).toBe(false) + expect(isProviderSetupErrorMessage(null)).toBe(false) + expect(isProviderSetupErrorMessage(undefined)).toBe(false) + }) +}) diff --git a/apps/desktop/src/lib/provider-setup-errors.ts b/apps/desktop/src/lib/provider-setup-errors.ts new file mode 100644 index 00000000000..e1c666ac0b7 --- /dev/null +++ b/apps/desktop/src/lib/provider-setup-errors.ts @@ -0,0 +1,12 @@ +const PROVIDER_SETUP_ERROR_RE = + /No inference provider(?: is)? configured|no_provider_configured|OPENROUTER_API_KEY|OPENAI_API_KEY|ANTHROPIC_API_KEY|set an API key/i + +export function isProviderSetupErrorMessage(message: null | string | undefined): boolean { + const text = message?.trim() + + if (!text) { + return false + } + + return PROVIDER_SETUP_ERROR_RE.test(text) +}