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 <makeittech@users.noreply.github.com>
This commit is contained in:
co-authored by
Serhii Dziupin
parent
395e08c683
commit
9cccc8d667
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test } from 'bun:test';
|
import { describe, expect, test } from 'bun:test';
|
||||||
import { shouldLoadAvailableProviders } from './providerAvailability';
|
import { shouldLoadAvailableProviders, shouldLoadProviderAuthMethods } from './providerAvailability';
|
||||||
|
|
||||||
describe('ProvidersPage available provider loading', () => {
|
describe('ProvidersPage available provider loading', () => {
|
||||||
test('loads available providers only in add-provider mode', () => {
|
test('loads available providers only in add-provider mode', () => {
|
||||||
@@ -7,3 +7,12 @@ describe('ProvidersPage available provider loading', () => {
|
|||||||
expect(shouldLoadAvailableProviders(true)).toBe(true);
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import type { ModelMetadata } from '@/types';
|
|||||||
import { getCurrentIntlLocale, useI18n } from '@/lib/i18n';
|
import { getCurrentIntlLocale, useI18n } from '@/lib/i18n';
|
||||||
import { runtimeFetch } from '@/lib/runtime-fetch';
|
import { runtimeFetch } from '@/lib/runtime-fetch';
|
||||||
import { opencodeClient } from '@/lib/opencode/client';
|
import { opencodeClient } from '@/lib/opencode/client';
|
||||||
import { shouldLoadAvailableProviders } from './providerAvailability';
|
import { shouldLoadAvailableProviders, shouldLoadProviderAuthMethods } from './providerAvailability';
|
||||||
|
|
||||||
const formatCompactNumber = (value: number) => new Intl.NumberFormat(getCurrentIntlLocale(), {
|
const formatCompactNumber = (value: number) => new Intl.NumberFormat(getCurrentIntlLocale(), {
|
||||||
notation: 'compact',
|
notation: 'compact',
|
||||||
@@ -86,6 +86,12 @@ const normalizeAuthType = (method: AuthMethod) => {
|
|||||||
return raw.toLowerCase();
|
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<string, AuthMethod[]> => {
|
const parseAuthPayload = (payload: unknown): Record<string, AuthMethod[]> => {
|
||||||
if (!isRecord(payload)) {
|
if (!isRecord(payload)) {
|
||||||
return {};
|
return {};
|
||||||
@@ -181,7 +187,7 @@ export const ProvidersPage: React.FC = () => {
|
|||||||
}, [providers, selectedProviderId, setSelectedProvider]);
|
}, [providers, selectedProviderId, setSelectedProvider]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!isAddMode) {
|
if (!shouldLoadProviderAuthMethods(isAddMode, showAuthPanel)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +218,7 @@ export const ProvidersPage: React.FC = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
isMounted = false;
|
isMounted = false;
|
||||||
};
|
};
|
||||||
}, [isAddMode, t]);
|
}, [isAddMode, showAuthPanel, t]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!shouldLoadAvailableProviders(isAddMode)) {
|
if (!shouldLoadAvailableProviders(isAddMode)) {
|
||||||
@@ -650,9 +656,7 @@ export const ProvidersPage: React.FC = () => {
|
|||||||
|
|
||||||
{(() => {
|
{(() => {
|
||||||
const candidateAuthMethods = authMethodsByProvider[candidateProviderId] ?? [];
|
const candidateAuthMethods = authMethodsByProvider[candidateProviderId] ?? [];
|
||||||
const candidateOAuthMethods = candidateAuthMethods.filter(
|
const candidateOAuthMethods = listOAuthMethods(candidateAuthMethods);
|
||||||
(method) => normalizeAuthType(method) === 'oauth'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (candidateOAuthMethods.length === 0) {
|
if (candidateOAuthMethods.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -660,14 +664,14 @@ export const ProvidersPage: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 border-t border-[var(--surface-subtle)] pt-2">
|
<div className="space-y-4 border-t border-[var(--surface-subtle)] pt-2">
|
||||||
{candidateOAuthMethods.map((method, index) => {
|
{candidateOAuthMethods.map(({ method, methodIndex }) => {
|
||||||
const methodLabel = method.label || method.name || t('settings.providers.page.auth.oauthMethodFallback', { index: String(index + 1) });
|
const methodLabel = method.label || method.name || t('settings.providers.page.auth.oauthMethodFallback', { index: String(methodIndex + 1) });
|
||||||
const codeKey = `${candidateProviderId}:${index}`;
|
const codeKey = `${candidateProviderId}:${methodIndex}`;
|
||||||
const isPending =
|
const isPending =
|
||||||
pendingOAuth?.providerId === candidateProviderId && pendingOAuth?.methodIndex === index;
|
pendingOAuth?.providerId === candidateProviderId && pendingOAuth?.methodIndex === methodIndex;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={`${candidateProviderId}-${methodLabel}`} className="space-y-3">
|
<div key={`${candidateProviderId}-${methodLabel}-${methodIndex}`} className="space-y-3">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div>
|
<div>
|
||||||
<div className="typography-ui-label text-foreground">{methodLabel}</div>
|
<div className="typography-ui-label text-foreground">{methodLabel}</div>
|
||||||
@@ -681,8 +685,8 @@ export const ProvidersPage: React.FC = () => {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="xs"
|
size="xs"
|
||||||
className="!font-normal"
|
className="!font-normal"
|
||||||
onClick={() => handleOAuthStart(candidateProviderId, index)}
|
onClick={() => handleOAuthStart(candidateProviderId, methodIndex)}
|
||||||
disabled={authBusyKey === `oauth:${candidateProviderId}:${index}`}
|
disabled={authBusyKey === `oauth:${candidateProviderId}:${methodIndex}`}
|
||||||
>
|
>
|
||||||
{t('settings.providers.page.actions.connect')}
|
{t('settings.providers.page.actions.connect')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -727,10 +731,10 @@ export const ProvidersPage: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
className="!font-normal"
|
className="!font-normal"
|
||||||
onClick={() => handleOAuthComplete(candidateProviderId, index)}
|
onClick={() => handleOAuthComplete(candidateProviderId, methodIndex)}
|
||||||
disabled={authBusyKey === `oauth-complete:${candidateProviderId}:${index}`}
|
disabled={authBusyKey === `oauth-complete:${candidateProviderId}:${methodIndex}`}
|
||||||
>
|
>
|
||||||
{authBusyKey === `oauth-complete:${candidateProviderId}:${index}` ? t('settings.providers.page.actions.saving') : t('settings.providers.page.actions.complete')}
|
{authBusyKey === `oauth-complete:${candidateProviderId}:${methodIndex}` ? t('settings.providers.page.actions.saving') : t('settings.providers.page.actions.complete')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -762,7 +766,7 @@ export const ProvidersPage: React.FC = () => {
|
|||||||
|
|
||||||
const providerModels = Array.isArray(selectedProvider.models) ? selectedProvider.models : [];
|
const providerModels = Array.isArray(selectedProvider.models) ? selectedProvider.models : [];
|
||||||
const providerAuthMethods = authMethodsByProvider[selectedProvider.id] ?? [];
|
const providerAuthMethods = authMethodsByProvider[selectedProvider.id] ?? [];
|
||||||
const oauthAuthMethods = providerAuthMethods.filter((method) => normalizeAuthType(method) === 'oauth');
|
const oauthAuthMethods = listOAuthMethods(providerAuthMethods);
|
||||||
|
|
||||||
const filteredModels = providerModels.filter((model) => {
|
const filteredModels = providerModels.filter((model) => {
|
||||||
const name = typeof model?.name === 'string' ? model.name : '';
|
const name = typeof model?.name === 'string' ? model.name : '';
|
||||||
@@ -835,14 +839,14 @@ export const ProvidersPage: React.FC = () => {
|
|||||||
|
|
||||||
{oauthAuthMethods.length > 0 && (
|
{oauthAuthMethods.length > 0 && (
|
||||||
<div className="space-y-4 border-t border-[var(--surface-subtle)] pt-2">
|
<div className="space-y-4 border-t border-[var(--surface-subtle)] pt-2">
|
||||||
{oauthAuthMethods.map((method, index) => {
|
{oauthAuthMethods.map(({ method, methodIndex }) => {
|
||||||
const methodLabel = method.label || method.name || t('settings.providers.page.auth.oauthMethodFallback', { index: String(index + 1) });
|
const methodLabel = method.label || method.name || t('settings.providers.page.auth.oauthMethodFallback', { index: String(methodIndex + 1) });
|
||||||
const codeKey = `${selectedProvider.id}:${index}`;
|
const codeKey = `${selectedProvider.id}:${methodIndex}`;
|
||||||
const isPending =
|
const isPending =
|
||||||
pendingOAuth?.providerId === selectedProvider.id && pendingOAuth?.methodIndex === index;
|
pendingOAuth?.providerId === selectedProvider.id && pendingOAuth?.methodIndex === methodIndex;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={`${selectedProvider.id}-${methodLabel}`} className="space-y-3">
|
<div key={`${selectedProvider.id}-${methodLabel}-${methodIndex}`} className="space-y-3">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div>
|
<div>
|
||||||
<div className="typography-ui-label text-foreground">{methodLabel}</div>
|
<div className="typography-ui-label text-foreground">{methodLabel}</div>
|
||||||
@@ -856,8 +860,8 @@ export const ProvidersPage: React.FC = () => {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="xs"
|
size="xs"
|
||||||
className="!font-normal"
|
className="!font-normal"
|
||||||
onClick={() => handleOAuthStart(selectedProvider.id, index)}
|
onClick={() => handleOAuthStart(selectedProvider.id, methodIndex)}
|
||||||
disabled={authBusyKey === `oauth:${selectedProvider.id}:${index}`}
|
disabled={authBusyKey === `oauth:${selectedProvider.id}:${methodIndex}`}
|
||||||
>
|
>
|
||||||
{t('settings.providers.page.actions.connect')}
|
{t('settings.providers.page.actions.connect')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -902,10 +906,10 @@ export const ProvidersPage: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
className="!font-normal"
|
className="!font-normal"
|
||||||
onClick={() => handleOAuthComplete(selectedProvider.id, index)}
|
onClick={() => handleOAuthComplete(selectedProvider.id, methodIndex)}
|
||||||
disabled={authBusyKey === `oauth-complete:${selectedProvider.id}:${index}`}
|
disabled={authBusyKey === `oauth-complete:${selectedProvider.id}:${methodIndex}`}
|
||||||
>
|
>
|
||||||
{authBusyKey === `oauth-complete:${selectedProvider.id}:${index}` ? t('settings.providers.page.actions.saving') : t('settings.providers.page.actions.complete')}
|
{authBusyKey === `oauth-complete:${selectedProvider.id}:${methodIndex}` ? t('settings.providers.page.actions.saving') : t('settings.providers.page.actions.complete')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
export const shouldLoadAvailableProviders = (isAddMode: boolean): boolean => isAddMode;
|
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;
|
||||||
|
|||||||
Reference in New Issue
Block a user