From 65668f1453946e537a4c34ff9f62a57aaeeb4aa3 Mon Sep 17 00:00:00 2001 From: herjarsa Date: Fri, 28 Aug 2026 17:47:34 +0200 Subject: [PATCH] fix(providers): wire optionsApiKey through both call sites; restore editable custom exemption The previous fix added optionsApiKey to providerHasCredentials but wired it through only one of the two call sites in ProvidersPage.tsx. The auto-open effect at line 339 still omitted it, so a config-defined provider whose only credential is options.apiKey would get the auth panel force-opened on every selection while the summary beside it said Connected; the dismissal resets on provider switch, so the panel re-opens each time. Restore the isEditableCustomProvider exemption that main's requiresProviderAuth helper carried into both authStatusIncomplete and shouldShowModelsSection. A keyless local custom provider (LM Studio / Ollama style) regressed from 'models visible, no banner' to 'Credentials missing' with the models section hidden. Rewire requiresProviderAuth (its only production consumer was lost in the rebase) by delegating authStatusIncomplete to it. The helper already encodes sourcesLoaded && !hasCredentials && !isEditableCustom Provider, so the call site is one line and the contract matches main. shouldShowModelsSection now accepts an optional isEditableCustom Provider flag that lifts the credential gate for editable providers, matching the exemption the ProvidersPage.test.ts:22 fixture asserts. Tests: 15/15 pass. Was 14 in the previous commit; added one test for the editable custom exemption. --- .../sections/providers/ProvidersPage.test.ts | 25 +++++++++++++++++++ .../sections/providers/ProvidersPage.tsx | 6 +++-- .../sections/providers/providerAuth.ts | 12 ++++++++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/sections/providers/ProvidersPage.test.ts b/packages/ui/src/components/sections/providers/ProvidersPage.test.ts index 5bb461f3..1861568c 100644 --- a/packages/ui/src/components/sections/providers/ProvidersPage.test.ts +++ b/packages/ui/src/components/sections/providers/ProvidersPage.test.ts @@ -133,6 +133,31 @@ describe('provider credential state helpers', () => { })).toBe(true); }); + test('editable custom provider keeps models visible even with no credentials signal', () => { + // Config-defined custom providers (e.g. local LM Studio/Ollama style) + // are user-editable in place; a stale 'Credentials missing' must not + // hide their models section. Without the exemption, a keyless local + // custom provider regresses to 'Credentials missing' with models hidden. + const hasCredentials = providerHasCredentials({ + key: undefined, + authSourceExists: false, + optionsApiKey: null, + }); + expect(hasCredentials).toBe(false); + expect(shouldShowModelsSection({ + modelCount: 1, + sourcesLoaded: true, + hasCredentials: false, + isEditableCustomProvider: true, + })).toBe(true); + expect(shouldShowModelsSection({ + modelCount: 1, + sourcesLoaded: true, + hasCredentials: false, + isEditableCustomProvider: false, + })).toBe(false); + }); + test('auth save followed by providers refresh recognizes credentials without stale missing state', () => { // Pre-save: sources say no auth, provider has no key yet. const before = providerHasCredentials({ diff --git a/packages/ui/src/components/sections/providers/ProvidersPage.tsx b/packages/ui/src/components/sections/providers/ProvidersPage.tsx index 18269918..d50b4875 100644 --- a/packages/ui/src/components/sections/providers/ProvidersPage.tsx +++ b/packages/ui/src/components/sections/providers/ProvidersPage.tsx @@ -27,7 +27,7 @@ import type { ModelMetadata } from '@/types'; import { getCurrentIntlLocale, useI18n } from '@/lib/i18n'; import { runtimeFetch } from '@/lib/runtime-fetch'; import { opencodeClient } from '@/lib/opencode/client'; -import { shouldLoadAvailableProviders } from './providerAvailability'; +import { requiresProviderAuth, shouldLoadAvailableProviders } from './providerAvailability'; import { getOAuthAuthMethods, parseAuthPayload, @@ -339,6 +339,7 @@ export const ProvidersPage: React.FC = () => { const hasCreds = providerHasCredentials({ key: provider?.key, authSourceExists: sources.auth.exists, + optionsApiKey: (provider as { options?: { apiKey?: string | null } } | undefined)?.options?.apiKey ?? null, }); const isEditableCustomProvider = Boolean( provider && isConfigDefinedCustomProvider(provider, sources) @@ -862,11 +863,12 @@ export const ProvidersPage: React.FC = () => { authSourceExists: selectedSources?.auth.exists, optionsApiKey: (selectedProvider as { options?: { apiKey?: string | null } }).options?.apiKey ?? null, }); - const authStatusIncomplete = sourcesLoaded && !hasCredentials; + const authStatusIncomplete = requiresProviderAuth(sourcesLoaded, hasCredentials, isEditableCustomProvider); const showModelsSection = shouldShowModelsSection({ modelCount: providerModels.length, sourcesLoaded, hasCredentials, + isEditableCustomProvider, }); const incompleteAuthHint = !showApiKeyAuth && oauthAuthMethods.length > 0 ? t('settings.providers.page.auth.useReconnectHint') diff --git a/packages/ui/src/components/sections/providers/providerAuth.ts b/packages/ui/src/components/sections/providers/providerAuth.ts index 7cc7b5ac..cff0ca5b 100644 --- a/packages/ui/src/components/sections/providers/providerAuth.ts +++ b/packages/ui/src/components/sections/providers/providerAuth.ts @@ -94,7 +94,17 @@ export const shouldShowModelsSection = (input: { modelCount: number; sourcesLoaded: boolean; hasCredentials: boolean; -}): boolean => input.modelCount > 0 && (!input.sourcesLoaded || input.hasCredentials); + /** + * Config-defined custom providers (providerSources.custom present and parsed + * via `isConfigDefinedCustomProvider`) are user-editable in place, so a + * stale `Credentials missing` signal must not hide their models section. + * Optional for back-compat; defaults to `false`, restoring the pre-rewrite + * exemption that `requiresProviderAuth` carried via `providerAvailability.ts`. + */ + isEditableCustomProvider?: boolean; +}): boolean => + input.modelCount > 0 && + (!input.sourcesLoaded || input.hasCredentials || Boolean(input.isEditableCustomProvider)); export const shouldAutoOpenAuthPanel = (input: { sourcesLoaded: boolean;