From 9cccc8d667afcde5148aac9af76801b8fe5bd408 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 3 Aug 2026 12:47:12 +0000 Subject: [PATCH] Load provider OAuth methods on reconnect, not only add mode. Reconnect previously showed only the API key field because provider.auth() ran solely in add-provider mode. Also keep the original OpenCode method index when listing OAuth options. Co-authored-by: Serhii Dziupin --- .../sections/providers/ProvidersPage.test.ts | 11 +++- .../sections/providers/ProvidersPage.tsx | 58 ++++++++++--------- .../providers/providerAvailability.ts | 4 ++ 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/packages/ui/src/components/sections/providers/ProvidersPage.test.ts b/packages/ui/src/components/sections/providers/ProvidersPage.test.ts index e45fb969..fb9f258a 100644 --- a/packages/ui/src/components/sections/providers/ProvidersPage.test.ts +++ b/packages/ui/src/components/sections/providers/ProvidersPage.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from 'bun:test'; -import { shouldLoadAvailableProviders } from './providerAvailability'; +import { shouldLoadAvailableProviders, shouldLoadProviderAuthMethods } from './providerAvailability'; describe('ProvidersPage available provider loading', () => { test('loads available providers only in add-provider mode', () => { @@ -7,3 +7,12 @@ describe('ProvidersPage available provider loading', () => { expect(shouldLoadAvailableProviders(true)).toBe(true); }); }); + +describe('ProvidersPage auth method loading', () => { + test('loads auth methods for add mode and reconnect panel', () => { + expect(shouldLoadProviderAuthMethods(false, false)).toBe(false); + expect(shouldLoadProviderAuthMethods(true, false)).toBe(true); + expect(shouldLoadProviderAuthMethods(false, true)).toBe(true); + expect(shouldLoadProviderAuthMethods(true, true)).toBe(true); + }); +}); diff --git a/packages/ui/src/components/sections/providers/ProvidersPage.tsx b/packages/ui/src/components/sections/providers/ProvidersPage.tsx index 150b4c03..d28f5f29 100644 --- a/packages/ui/src/components/sections/providers/ProvidersPage.tsx +++ b/packages/ui/src/components/sections/providers/ProvidersPage.tsx @@ -25,7 +25,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 { shouldLoadAvailableProviders, shouldLoadProviderAuthMethods } from './providerAvailability'; const formatCompactNumber = (value: number) => new Intl.NumberFormat(getCurrentIntlLocale(), { notation: 'compact', @@ -86,6 +86,12 @@ const normalizeAuthType = (method: AuthMethod) => { return raw.toLowerCase(); }; +/** OAuth methods with the original provider.auth() method index OpenCode expects. */ +const listOAuthMethods = (methods: AuthMethod[]): Array<{ method: AuthMethod; methodIndex: number }> => + methods + .map((method, methodIndex) => ({ method, methodIndex })) + .filter(({ method }) => normalizeAuthType(method) === 'oauth'); + const parseAuthPayload = (payload: unknown): Record => { if (!isRecord(payload)) { return {}; @@ -181,7 +187,7 @@ export const ProvidersPage: React.FC = () => { }, [providers, selectedProviderId, setSelectedProvider]); React.useEffect(() => { - if (!isAddMode) { + if (!shouldLoadProviderAuthMethods(isAddMode, showAuthPanel)) { return; } @@ -212,7 +218,7 @@ export const ProvidersPage: React.FC = () => { return () => { isMounted = false; }; - }, [isAddMode, t]); + }, [isAddMode, showAuthPanel, t]); React.useEffect(() => { if (!shouldLoadAvailableProviders(isAddMode)) { @@ -650,9 +656,7 @@ export const ProvidersPage: React.FC = () => { {(() => { const candidateAuthMethods = authMethodsByProvider[candidateProviderId] ?? []; - const candidateOAuthMethods = candidateAuthMethods.filter( - (method) => normalizeAuthType(method) === 'oauth' - ); + const candidateOAuthMethods = listOAuthMethods(candidateAuthMethods); if (candidateOAuthMethods.length === 0) { return null; @@ -660,14 +664,14 @@ export const ProvidersPage: React.FC = () => { return (
- {candidateOAuthMethods.map((method, index) => { - const methodLabel = method.label || method.name || t('settings.providers.page.auth.oauthMethodFallback', { index: String(index + 1) }); - const codeKey = `${candidateProviderId}:${index}`; + {candidateOAuthMethods.map(({ method, methodIndex }) => { + const methodLabel = method.label || method.name || t('settings.providers.page.auth.oauthMethodFallback', { index: String(methodIndex + 1) }); + const codeKey = `${candidateProviderId}:${methodIndex}`; const isPending = - pendingOAuth?.providerId === candidateProviderId && pendingOAuth?.methodIndex === index; + pendingOAuth?.providerId === candidateProviderId && pendingOAuth?.methodIndex === methodIndex; return ( -
+
{methodLabel}
@@ -681,8 +685,8 @@ export const ProvidersPage: React.FC = () => { variant="outline" size="xs" className="!font-normal" - onClick={() => handleOAuthStart(candidateProviderId, index)} - disabled={authBusyKey === `oauth:${candidateProviderId}:${index}`} + onClick={() => handleOAuthStart(candidateProviderId, methodIndex)} + disabled={authBusyKey === `oauth:${candidateProviderId}:${methodIndex}`} > {t('settings.providers.page.actions.connect')} @@ -727,10 +731,10 @@ export const ProvidersPage: React.FC = () => {
)} @@ -762,7 +766,7 @@ export const ProvidersPage: React.FC = () => { const providerModels = Array.isArray(selectedProvider.models) ? selectedProvider.models : []; const providerAuthMethods = authMethodsByProvider[selectedProvider.id] ?? []; - const oauthAuthMethods = providerAuthMethods.filter((method) => normalizeAuthType(method) === 'oauth'); + const oauthAuthMethods = listOAuthMethods(providerAuthMethods); const filteredModels = providerModels.filter((model) => { const name = typeof model?.name === 'string' ? model.name : ''; @@ -835,14 +839,14 @@ export const ProvidersPage: React.FC = () => { {oauthAuthMethods.length > 0 && (
- {oauthAuthMethods.map((method, index) => { - const methodLabel = method.label || method.name || t('settings.providers.page.auth.oauthMethodFallback', { index: String(index + 1) }); - const codeKey = `${selectedProvider.id}:${index}`; + {oauthAuthMethods.map(({ method, methodIndex }) => { + const methodLabel = method.label || method.name || t('settings.providers.page.auth.oauthMethodFallback', { index: String(methodIndex + 1) }); + const codeKey = `${selectedProvider.id}:${methodIndex}`; const isPending = - pendingOAuth?.providerId === selectedProvider.id && pendingOAuth?.methodIndex === index; + pendingOAuth?.providerId === selectedProvider.id && pendingOAuth?.methodIndex === methodIndex; return ( -
+
{methodLabel}
@@ -856,8 +860,8 @@ export const ProvidersPage: React.FC = () => { variant="outline" size="xs" className="!font-normal" - onClick={() => handleOAuthStart(selectedProvider.id, index)} - disabled={authBusyKey === `oauth:${selectedProvider.id}:${index}`} + onClick={() => handleOAuthStart(selectedProvider.id, methodIndex)} + disabled={authBusyKey === `oauth:${selectedProvider.id}:${methodIndex}`} > {t('settings.providers.page.actions.connect')} @@ -902,10 +906,10 @@ export const ProvidersPage: React.FC = () => {
)} diff --git a/packages/ui/src/components/sections/providers/providerAvailability.ts b/packages/ui/src/components/sections/providers/providerAvailability.ts index ea6f0386..c98db78c 100644 --- a/packages/ui/src/components/sections/providers/providerAvailability.ts +++ b/packages/ui/src/components/sections/providers/providerAvailability.ts @@ -1 +1,5 @@ export const shouldLoadAvailableProviders = (isAddMode: boolean): boolean => isAddMode; + +/** Auth methods are needed when adding a provider or reconnecting an existing one. */ +export const shouldLoadProviderAuthMethods = (isAddMode: boolean, showAuthPanel: boolean): boolean => + isAddMode || showAuthPanel;