agent-ecosystem/src/renderer/components/runtime/providerConnectionUi.ts
777genius e9cebe64ff feat: improve provider status startup hydration
Keep connected provider details visible while refreshes are in flight, restore reusable provider status UI, and separate fast startup summaries from heavier provider hydration. Replace the fixed 30s startup wait with an idle-aware scheduler with a 30s safety cap and cover the Electron timer binding crash.
2026-05-24 00:23:04 +03:00

536 lines
15 KiB
TypeScript

import { CLI_PROVIDER_STATUS_DEFERRED_MESSAGE } from '@shared/types/cliInstaller';
import type { CliProviderAuthMode, CliProviderStatus } from '@shared/types';
const CODEX_NATIVE_LABEL = 'Codex native';
const ANTHROPIC_SUBSCRIPTION_LABEL = 'Anthropic subscription';
const AUTH_MODE_LABELS: Record<CliProviderAuthMode, string> = {
auto: 'Auto',
oauth: 'Subscription / OAuth',
chatgpt: 'ChatGPT account',
api_key: 'API key',
};
export function formatProviderAuthModeLabel(authMode: CliProviderAuthMode | null): string | null {
return authMode ? AUTH_MODE_LABELS[authMode] : null;
}
export function formatProviderAuthModeLabelForProvider(
providerId: CliProviderStatus['providerId'],
authMode: CliProviderAuthMode | null
): string | null {
if (!authMode) {
return null;
}
if (providerId === 'anthropic' && authMode === 'oauth') {
return ANTHROPIC_SUBSCRIPTION_LABEL;
}
return formatProviderAuthModeLabel(authMode);
}
export function formatProviderAuthMethodLabel(authMethod: string | null): string {
switch (authMethod) {
case 'api_key':
return 'API key';
case 'api_key_helper':
return 'API key helper';
case 'oauth_token':
return 'OAuth';
case 'claude.ai':
return 'Claude subscription';
case 'cli_oauth_personal':
return 'Gemini CLI';
case 'gemini_adc_authorized_user':
return 'Google account';
case 'gemini_adc_service_account':
return 'service account';
default:
return authMethod ? authMethod.replaceAll('_', ' ') : 'Not connected';
}
}
export function formatProviderAuthMethodLabelForProvider(
providerId: CliProviderStatus['providerId'],
authMethod: string | null
): string {
if (providerId === 'anthropic' && (authMethod === 'oauth_token' || authMethod === 'claude.ai')) {
return ANTHROPIC_SUBSCRIPTION_LABEL;
}
return formatProviderAuthMethodLabel(authMethod);
}
function isCodexNativeLane(provider: CliProviderStatus): boolean {
return (
provider.providerId === 'codex' &&
(provider.resolvedBackendId === 'codex-native' || provider.selectedBackendId === 'codex-native')
);
}
function getSelectedRuntimeBackendOption(
provider: CliProviderStatus
): NonNullable<CliProviderStatus['availableBackends']>[number] | null {
const options = provider.availableBackends ?? [];
if (options.length === 0) {
return null;
}
const selectedBackendId = provider.selectedBackendId ?? null;
const resolvedBackendId = provider.resolvedBackendId ?? null;
return (
options.find((option) => option.id === selectedBackendId) ??
options.find((option) => option.id === resolvedBackendId) ??
null
);
}
export function isProviderInventoryOnlyFallback(provider: CliProviderStatus): boolean {
return (
provider.supported === false &&
provider.authenticated === false &&
provider.authMethod === null &&
provider.verificationState === 'unknown' &&
provider.models.length > 0 &&
provider.backend == null &&
(provider.availableBackends?.length ?? 0) === 0 &&
provider.capabilities.teamLaunch === false
);
}
export function isOpenCodeCatalogHydrating(
provider:
| Pick<
CliProviderStatus,
| 'providerId'
| 'models'
| 'modelCatalog'
| 'modelCatalogRefreshState'
| 'runtimeCapabilities'
>
| null
| undefined
): boolean {
if (provider?.providerId !== 'opencode') {
return false;
}
if (provider.modelCatalog?.providerId === 'opencode') {
return false;
}
if (provider.modelCatalogRefreshState === 'error') {
return false;
}
return (
provider.modelCatalogRefreshState === 'loading' ||
provider.runtimeCapabilities?.modelCatalog?.dynamic === true
);
}
function hasKnownProviderStatus(
provider: Pick<
CliProviderStatus,
| 'authenticated'
| 'supported'
| 'statusMessage'
| 'models'
| 'backend'
| 'availableBackends'
| 'connection'
| 'modelCatalog'
>
): boolean {
const statusMessage = provider.statusMessage?.trim() ?? '';
return (
provider.authenticated ||
provider.supported ||
provider.models.length > 0 ||
provider.backend != null ||
(provider.availableBackends?.length ?? 0) > 0 ||
provider.connection != null ||
provider.modelCatalog != null ||
(statusMessage.length > 0 &&
statusMessage !== 'Checking...' &&
statusMessage !== CLI_PROVIDER_STATUS_DEFERRED_MESSAGE)
);
}
export function shouldShowProviderStatusSkeleton(
provider: CliProviderStatus,
providerLoading: boolean
): boolean {
const isPlaceholder =
!provider.authenticated &&
(provider.statusMessage === 'Checking...' ||
provider.statusMessage === CLI_PROVIDER_STATUS_DEFERRED_MESSAGE) &&
provider.models.length === 0 &&
provider.backend == null;
return isPlaceholder || (providerLoading && !hasKnownProviderStatus(provider));
}
export function isConnectionManagedRuntimeProvider(provider: CliProviderStatus): boolean {
return provider.providerId === 'codex';
}
function getCodexCurrentRuntimeLabel(): string {
return CODEX_NATIVE_LABEL;
}
function getCodexApiKeyAvailabilitySummary(provider: CliProviderStatus): string | null {
if (provider.providerId !== 'codex' || !provider.connection?.apiKeyConfigured) {
return null;
}
if (provider.connection.apiKeySource === 'stored') {
return 'Saved API key available in Manage';
}
return provider.connection.apiKeySourceLabel ?? 'API key is configured';
}
function isAnthropicApiKeyModeReady(provider: CliProviderStatus): boolean {
return (
provider.providerId === 'anthropic' &&
provider.connection?.configuredAuthMode === 'api_key' &&
provider.connection.apiKeyConfigured === true &&
provider.authenticated === true &&
(provider.authMethod === 'api_key' || provider.authMethod === 'api_key_helper') &&
provider.verificationState === 'verified'
);
}
function isAnthropicApiKeyModeMissingCredential(provider: CliProviderStatus): boolean {
return (
provider.providerId === 'anthropic' &&
provider.connection?.configuredAuthMode === 'api_key' &&
provider.connection.apiKeyConfigured !== true
);
}
function getCodexMissingManagedAccountStatus(provider: CliProviderStatus): string | null {
if (provider.providerId !== 'codex') {
return null;
}
const codexConnection = provider.connection?.codex;
if (!codexConnection || codexConnection.managedAccount?.type === 'chatgpt') {
return null;
}
if (provider.connection?.configuredAuthMode !== 'chatgpt') {
return null;
}
if (codexConnection.requiresOpenaiAuth) {
if (codexConnection.localActiveChatgptAccountPresent) {
return 'Codex has a locally selected ChatGPT account, but the current session needs reconnect.';
}
return codexConnection.localAccountArtifactsPresent
? 'Codex CLI reports no active ChatGPT login. Local Codex account data exists, but no active managed session is selected.'
: 'Codex CLI reports no active ChatGPT login';
}
return (
codexConnection.launchIssueMessage ??
'Connect a ChatGPT account to use your Codex subscription.'
);
}
export function getProviderCurrentRuntimeSummary(provider: CliProviderStatus): string | null {
if (provider.providerId !== 'codex' || !isConnectionManagedRuntimeProvider(provider)) {
return null;
}
const prefix = provider.authenticated ? 'Current runtime' : 'Selected runtime';
return `${prefix}: ${getCodexCurrentRuntimeLabel()}`;
}
export function formatProviderStatusText(provider: CliProviderStatus): string {
if (isProviderInventoryOnlyFallback(provider)) {
return 'Checking...';
}
const selectedBackendOption = getSelectedRuntimeBackendOption(provider);
if (provider.providerId === 'codex') {
if (provider.connection?.codex?.login.status === 'starting') {
return 'Starting ChatGPT login...';
}
if (provider.connection?.codex?.login.status === 'pending') {
return 'Waiting for ChatGPT account login...';
}
if (
provider.connection?.codex?.login.status === 'failed' &&
provider.connection.codex.login.error
) {
return provider.connection.codex.login.error;
}
if (
provider.connection?.codex?.appServerState === 'degraded' &&
provider.connection.codex.effectiveAuthMode === 'chatgpt' &&
provider.connection.codex.launchAllowed
) {
return (
provider.connection.codex.launchIssueMessage ??
'ChatGPT account detected - account verification is currently degraded.'
);
}
if (provider.connection?.codex?.launchAllowed) {
if (provider.connection.codex.effectiveAuthMode === 'chatgpt') {
return 'ChatGPT account ready';
}
if (provider.connection.codex.effectiveAuthMode === 'api_key') {
return 'API key ready';
}
}
const missingManagedAccountStatus = getCodexMissingManagedAccountStatus(provider);
if (missingManagedAccountStatus) {
return missingManagedAccountStatus;
}
if (provider.connection?.codex?.launchIssueMessage) {
return provider.connection.codex.launchIssueMessage;
}
if (selectedBackendOption?.statusMessage) {
return selectedBackendOption.statusMessage;
}
return (
provider.statusMessage ?? (provider.authenticated ? 'Codex native ready' : 'Not connected')
);
}
if (
isCodexNativeLane(provider) &&
selectedBackendOption?.state &&
selectedBackendOption.state !== 'ready'
) {
return (
selectedBackendOption.statusMessage ?? provider.statusMessage ?? 'Codex native unavailable'
);
}
if (
isCodexNativeLane(provider) &&
selectedBackendOption?.audience === 'internal' &&
selectedBackendOption.statusMessage
) {
return selectedBackendOption.statusMessage;
}
if (!provider.supported) {
return provider.statusMessage ?? 'Unavailable in current runtime';
}
if (isAnthropicApiKeyModeReady(provider)) {
return 'Connected via API key';
}
if (
provider.providerId === 'anthropic' &&
provider.connection?.configuredAuthMode === 'api_key' &&
provider.connection.apiKeyConfigured === true
) {
const statusMessage = provider.statusMessage?.trim();
if (statusMessage && !/^connected\b/i.test(statusMessage)) {
return statusMessage;
}
return 'API key configured, but not verified yet';
}
if (isAnthropicApiKeyModeMissingCredential(provider)) {
return 'API key mode selected, but no API key is configured';
}
if (provider.authenticated) {
return `Connected via ${formatProviderAuthMethodLabelForProvider(
provider.providerId,
provider.authMethod
)}`;
}
if (provider.verificationState === 'offline') {
return provider.statusMessage ?? 'Unable to verify';
}
return provider.statusMessage ?? 'Not connected';
}
export function getProviderConnectionModeSummary(provider: CliProviderStatus): string | null {
if (provider.providerId !== 'anthropic' && provider.providerId !== 'codex') {
return null;
}
if (provider.providerId === 'anthropic') {
if (provider.authenticated) {
return null;
}
if (provider.connection?.configuredAuthMode === 'auto') {
return null;
}
}
if (provider.providerId === 'codex' && provider.connection?.configuredAuthMode === 'auto') {
return null;
}
const authModeLabel = formatProviderAuthModeLabelForProvider(
provider.providerId,
provider.connection?.configuredAuthMode ?? null
);
if (!authModeLabel) {
return null;
}
return provider.providerId === 'codex'
? `Selected auth: ${authModeLabel}`
: `Preferred auth: ${authModeLabel}`;
}
export function getProviderCredentialSummary(provider: CliProviderStatus): string | null {
if (!provider.connection?.apiKeyConfigured) {
return null;
}
if (isAnthropicApiKeyModeReady(provider)) {
return provider.connection?.apiKeySourceLabel ?? 'API key is configured';
}
if (
provider.providerId === 'anthropic' &&
provider.connection.apiKeySource === 'stored' &&
provider.connection.configuredAuthMode === 'auto'
) {
return 'Saved API key available in Manage';
}
if (
provider.providerId === 'anthropic' &&
provider.authMethod !== 'api_key' &&
provider.authMethod !== 'api_key_helper'
) {
return provider.connection.apiKeySource === 'stored'
? 'API key also configured in Manage'
: (provider.connection.apiKeySourceLabel ?? 'API key is configured');
}
if (provider.authMethod !== 'api_key' && provider.providerId === 'gemini') {
return provider.connection.apiKeySource === 'stored'
? 'API key is configured in Manage'
: (provider.connection.apiKeySourceLabel ?? 'API key is configured');
}
if (provider.providerId === 'codex') {
const apiKeyAvailabilitySummary = getCodexApiKeyAvailabilitySummary(provider);
if (!apiKeyAvailabilitySummary) {
return null;
}
if (
provider.connection.codex?.managedAccount?.type === 'chatgpt' ||
provider.connection.codex?.effectiveAuthMode === 'chatgpt'
) {
return provider.connection.apiKeySource === 'stored'
? 'API key also available in Manage as fallback'
: `${apiKeyAvailabilitySummary} - available as fallback`;
}
if (provider.connection.configuredAuthMode === 'chatgpt') {
return provider.connection.apiKeySource === 'stored'
? 'Saved API key available in Manage if you switch to API key mode'
: `${apiKeyAvailabilitySummary} - available if you switch to API key mode`;
}
if (provider.connection.configuredAuthMode === 'auto') {
return `${apiKeyAvailabilitySummary} - Auto will use this until ChatGPT is connected`;
}
return apiKeyAvailabilitySummary;
}
return provider.connection.apiKeySourceLabel ?? null;
}
export function getProviderDisconnectAction(provider: CliProviderStatus): {
label: string;
confirmLabel: string;
title: string;
message: string;
} | null {
if (!provider.authenticated) {
return null;
}
if (provider.providerId === 'anthropic') {
if (provider.authMethod !== 'oauth_token' && provider.authMethod !== 'claude.ai') {
return null;
}
return {
label: 'Disconnect',
confirmLabel: 'Disconnect',
title: 'Disconnect Anthropic subscription?',
message: provider.connection?.apiKeyConfigured
? 'This removes the local Anthropic subscription session from the Claude CLI runtime. Saved API keys in Manage stay available.'
: 'This removes the local Anthropic subscription session from the Claude CLI runtime.',
};
}
if (provider.providerId === 'gemini' && provider.authMethod === 'cli_oauth_personal') {
return {
label: 'Disconnect',
confirmLabel: 'Disconnect',
title: 'Disconnect Gemini CLI?',
message:
'This clears the local Gemini CLI session metadata. External ADC credentials and saved API keys are not removed.',
};
}
return null;
}
export function getProviderConnectLabel(provider: CliProviderStatus): string {
if (provider.providerId === 'anthropic') {
return 'Connect Anthropic';
}
if (provider.providerId === 'codex') {
return 'Connect ChatGPT';
}
if (provider.providerId === 'gemini') {
return 'Open Login';
}
return 'Connect';
}
export function shouldShowProviderConnectAction(provider: CliProviderStatus): boolean {
if (provider.providerId === 'codex') {
return false;
}
if (!provider.canLoginFromUi || provider.authenticated) {
return false;
}
if (provider.connection?.configuredAuthMode === 'api_key') {
return false;
}
return true;
}