fix(providers): address bot blockers for stale Credentials missing state
- shouldAutoOpenAuthPanel: re-introduce isEditableCustomProvider exemption (was dropped when requiresProviderAuth was replaced). Custom providers are editable directly in the form and must not be force-opened into the auth panel on a stale sources snapshot. - handleSaveCustomProvider + handleDisconnectProvider: route through applyConfigReloadOrRecordDeferred so an externally managed OpenCode that throws requiresManualRestart records deferred-restart guidance instead of toasting a misleading 'mutation failed' for a write that already persisted. - handleOAuthConnected: call markAuthWriteSucceeded so the page does not stick on a stale 'Credentials missing' summary while the providers refresh lands (OAuth previously only updated the deferred-restart payload). - Sources effect deps: add settingsDirectory back so a directory switch while the provider id is unchanged refetches the source snapshot (was swapped for providerSourcesRevision in the prior rebase). - Cleanup: drop dead oauthCodes state and copyTextToClipboard import; restore the requiresOpenCodeRestartAfterOAuth import alignment. 13/13 ProvidersPage.test.ts still green.
This commit is contained in:
@@ -23,7 +23,6 @@ import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore';
|
||||
import type { ConfigChangeScope } from '@/lib/configSync';
|
||||
import { recordDeferredOpenCodeRestart } from '@/lib/opencode/deferredRestart';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { copyTextToClipboard } from '@/lib/clipboard';
|
||||
import type { ModelMetadata } from '@/types';
|
||||
import { getCurrentIntlLocale, useI18n } from '@/lib/i18n';
|
||||
import { runtimeFetch } from '@/lib/runtime-fetch';
|
||||
@@ -32,8 +31,8 @@ import { shouldLoadAvailableProviders } from './providerAvailability';
|
||||
import {
|
||||
getOAuthAuthMethods,
|
||||
parseAuthPayload,
|
||||
requiresOpenCodeRestartAfterOAuth,
|
||||
providerHasCredentials,
|
||||
requiresOpenCodeRestartAfterOAuth,
|
||||
shouldAutoOpenAuthPanel,
|
||||
shouldShowApiKeyAuth,
|
||||
shouldShowModelsSection,
|
||||
@@ -167,7 +166,6 @@ export const ProvidersPage: React.FC = () => {
|
||||
const [authMethodsByProvider, setAuthMethodsByProvider] = React.useState<Record<string, AuthMethod[]>>({});
|
||||
const [authLoading, setAuthLoading] = React.useState(false);
|
||||
const [apiKeyInputs, setApiKeyInputs] = React.useState<Record<string, string>>({});
|
||||
const [oauthCodes, setOauthCodes] = React.useState<Record<string, string>>({});
|
||||
const [authBusyKey, setAuthBusyKey] = React.useState<string | null>(null);
|
||||
const [modelQuery, setModelQuery] = React.useState('');
|
||||
const [availableProviders, setAvailableProviders] = React.useState<ProviderOption[]>([]);
|
||||
@@ -342,11 +340,15 @@ export const ProvidersPage: React.FC = () => {
|
||||
key: provider?.key,
|
||||
authSourceExists: sources.auth.exists,
|
||||
});
|
||||
const isEditableCustomProvider = Boolean(
|
||||
provider && isConfigDefinedCustomProvider(provider, sources)
|
||||
);
|
||||
if (
|
||||
shouldAutoOpenAuthPanel({
|
||||
sourcesLoaded: true,
|
||||
hasCredentials: hasCreds,
|
||||
userDismissed: authPanelDismissedForId === selectedProviderId,
|
||||
isEditableCustomProvider,
|
||||
})
|
||||
) {
|
||||
setShowAuthPanel(true);
|
||||
@@ -394,7 +396,7 @@ export const ProvidersPage: React.FC = () => {
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [selectedProviderId, providerSourcesRevision, t]);
|
||||
}, [selectedProviderId, providerSourcesRevision, settingsDirectory, t]);
|
||||
|
||||
const refreshProviderSources = React.useCallback(() => {
|
||||
setProviderSourcesRevision((revision) => revision + 1);
|
||||
@@ -534,7 +536,10 @@ export const ProvidersPage: React.FC = () => {
|
||||
setEditingCustomScope(null);
|
||||
setCustomAuthFailureHint(null);
|
||||
setLastCustomPersistId(null);
|
||||
await reloadOpenCodeConfiguration({ scopes: ['providers'], mode: 'active' });
|
||||
// Mutation succeeded; route through the helper so an externally managed
|
||||
// OpenCode does not produce a misleading "save failed" toast for a write
|
||||
// that already persisted.
|
||||
await applyConfigReloadOrRecordDeferred('providers', plan.providerID);
|
||||
markAuthWriteSucceeded(plan.providerID);
|
||||
} catch (error) {
|
||||
console.error('Failed to save custom provider:', error);
|
||||
@@ -556,6 +561,9 @@ export const ProvidersPage: React.FC = () => {
|
||||
if (requiresOpenCodeRestartAfterOAuth(providerId)) {
|
||||
recordDeferredOpenCodeRestart('providers', { id: providerId });
|
||||
}
|
||||
// Optimistic mark + sources refetch so the page does not stick on a stale
|
||||
// "Credentials missing" summary while the providers refresh lands.
|
||||
markAuthWriteSucceeded(providerId);
|
||||
};
|
||||
|
||||
const handleDisconnectProvider = async (providerId: string) => {
|
||||
@@ -579,7 +587,10 @@ export const ProvidersPage: React.FC = () => {
|
||||
toast.success(t('settings.providers.page.toast.providerDisconnected'));
|
||||
// Only accumulate when the server actually deferred a restart (e.g. auth removed).
|
||||
// removed:false payloads must not create a phantom pending Apply & Restart.
|
||||
await reloadOpenCodeConfiguration({ scopes: ["providers"], mode: "active" });
|
||||
// Use the helper so an externally managed OpenCode that requires a manual
|
||||
// restart records the deferred-restart guidance instead of toasting a
|
||||
// misleading "disconnect failed" for a write that already persisted.
|
||||
await applyConfigReloadOrRecordDeferred('providers', providerId);
|
||||
setAuthPanelDismissedForId(null);
|
||||
refreshProviderSources();
|
||||
} catch (error) {
|
||||
|
||||
@@ -89,4 +89,17 @@ export const shouldAutoOpenAuthPanel = (input: {
|
||||
sourcesLoaded: boolean;
|
||||
hasCredentials: boolean;
|
||||
userDismissed: boolean;
|
||||
}): boolean => input.sourcesLoaded && !input.hasCredentials && !input.userDismissed;
|
||||
/**
|
||||
* Config-defined custom providers (providerSources.custom present and parsed
|
||||
* via `isConfigDefinedCustomProvider`) do not auto-open the auth panel: the
|
||||
* provider is editable directly in the form, and a stale `Credentials
|
||||
* missing` summary would be misleading. Optional for back-compat; defaults to
|
||||
* `false`, restoring the pre-rewrite exemption that `requiresProviderAuth`
|
||||
* carried via `providerAvailability.ts`.
|
||||
*/
|
||||
isEditableCustomProvider?: boolean;
|
||||
}): boolean =>
|
||||
input.sourcesLoaded &&
|
||||
!input.hasCredentials &&
|
||||
!input.userDismissed &&
|
||||
!input.isEditableCustomProvider;
|
||||
|
||||
Reference in New Issue
Block a user