import { execCli } from '@main/utils/childProcess'; import { resolveInteractiveShellEnvBestEffort } from '@main/utils/shellEnv'; import { CLI_PROVIDER_STATUS_UNAVAILABLE_MESSAGE } from '@shared/types/cliInstaller'; import { createLogger } from '@shared/utils/logger'; import { createDefaultCliExtensionCapabilities, createLegacyRuntimeFallbackCliExtensionCapabilities, } from '@shared/utils/providerExtensionCapabilities'; import { mkdtemp, readFile, rm } from 'fs/promises'; import { tmpdir } from 'os'; import path from 'path'; import { resolveGeminiRuntimeAuth } from './geminiRuntimeAuth'; import { buildProviderAwareCliEnv } from './providerAwareCliEnv'; import { providerConnectionService } from './ProviderConnectionService'; import type { CliProviderId, CliProviderReasoningEffort, CliProviderStatus, CliProviderSubscriptionRateLimitSnapshot, OpenCodeModelRouteMetadata, } from '@shared/types'; const logger = createLogger('ClaudeMultimodelBridgeService'); const PROVIDER_STATUS_TIMEOUT_MS = 90_000; const PROVIDER_STATUS_SUMMARY_TIMEOUT_MS = 30_000; const LEGACY_FALLBACK_PROVIDER_STATUS_SUMMARY_TIMEOUT_MS = 5_000; const OPENCODE_FALLBACK_PROVIDER_STATUS_SUMMARY_TIMEOUT_MS = 12_000; const LEGACY_PROVIDER_AUTH_TIMEOUT_MS = 15_000; const PROVIDER_MODELS_TIMEOUT_MS = 25_000; const PROVIDER_STATUS_MAX_BUFFER_BYTES = 8 * 1024 * 1024; const PROVIDER_MODELS_MAX_BUFFER_BYTES = 8 * 1024 * 1024; interface RuntimeExtensionCapabilityResponse { status?: 'supported' | 'read-only' | 'unsupported'; ownership?: 'shared' | 'provider-scoped'; reason?: string | null; } interface RuntimeExtensionCapabilitiesResponse { plugins?: RuntimeExtensionCapabilityResponse; mcp?: RuntimeExtensionCapabilityResponse; skills?: RuntimeExtensionCapabilityResponse; apiKeys?: RuntimeExtensionCapabilityResponse; } interface RuntimeProviderCapabilitiesResponse { modelCatalog?: { dynamic?: boolean; source?: | 'anthropic-models-api' | 'anthropic-compatible-api' | 'app-server' | 'static-fallback' | 'runtime'; }; reasoningEffort?: { supported?: boolean; values?: string[]; configPassthrough?: boolean; }; fastMode?: { supported?: boolean; available?: boolean; reason?: string | null; source?: 'runtime'; }; } interface RuntimeSubscriptionRateLimitWindowResponse { usedPercent?: number; windowDurationMins?: number | null; resetsAt?: number | null; } interface RuntimeSubscriptionRateLimitSnapshotResponse { primary?: RuntimeSubscriptionRateLimitWindowResponse | null; secondary?: RuntimeSubscriptionRateLimitWindowResponse | null; } interface RuntimeProviderModelCatalogItemResponse { id?: string; launchModel?: string; displayName?: string; hidden?: boolean; supportedReasoningEfforts?: string[]; defaultReasoningEffort?: string | null; supportsFastMode?: boolean; inputModalities?: string[]; supportsPersonality?: boolean; isDefault?: boolean; upgrade?: boolean; source?: 'anthropic-models-api' | 'anthropic-compatible-api' | 'app-server' | 'static-fallback'; badgeLabel?: string | null; statusMessage?: string | null; metadata?: Record | null; } interface RuntimeProviderModelCatalogResponse { schemaVersion?: number; providerId?: CliProviderId; source?: 'anthropic-models-api' | 'anthropic-compatible-api' | 'app-server' | 'static-fallback'; status?: 'ready' | 'stale' | 'degraded' | 'unavailable'; fetchedAt?: string; staleAt?: string; defaultModelId?: string | null; defaultLaunchModel?: string | null; models?: RuntimeProviderModelCatalogItemResponse[]; diagnostics?: { configReadState?: 'ready' | 'unsupported' | 'failed' | 'skipped'; appServerState?: 'healthy' | 'degraded' | 'runtime-missing' | 'incompatible'; message?: string | null; code?: string | null; }; } interface ProviderStatusPayloadResponse { supported?: boolean; authenticated?: boolean; authMethod?: string | null; verificationState?: 'verified' | 'unknown' | 'offline' | 'error'; canLoginFromUi?: boolean; statusMessage?: string | null; detailMessage?: string | null; capabilities?: { teamLaunch?: boolean; oneShot?: boolean; extensions?: RuntimeExtensionCapabilitiesResponse; }; backend?: { kind?: string; label?: string; endpointLabel?: string | null; projectId?: string | null; authMethodDetail?: string | null; } | null; runtimeCapabilities?: RuntimeProviderCapabilitiesResponse; subscriptionRateLimits?: RuntimeSubscriptionRateLimitSnapshotResponse | null; } interface ProviderStatusCommandResponse { schemaVersion?: number; provider?: string; status?: ProviderStatusPayloadResponse; providers?: Record; } interface ProviderModelsCommandResponse { schemaVersion?: number; providers?: Record< string, { models?: (string | { id?: string; label?: string; description?: string })[]; } >; } interface UnifiedRuntimeStatusResponse { schemaVersion?: number; providers?: Record< string, { supported?: boolean; authenticated?: boolean; authMethod?: string | null; verificationState?: 'verified' | 'unknown' | 'offline' | 'error'; canLoginFromUi?: boolean; statusMessage?: string | null; detailMessage?: string | null; selectedBackendId?: string | null; resolvedBackendId?: string | null; availableBackends?: { id?: string; label?: string; description?: string; selectable?: boolean; recommended?: boolean; available?: boolean; state?: | 'ready' | 'locked' | 'disabled' | 'authentication-required' | 'runtime-missing' | 'degraded'; audience?: 'general' | 'internal'; statusMessage?: string | null; detailMessage?: string | null; }[]; externalRuntimeDiagnostics?: { id?: string; label?: string; detected?: boolean; statusMessage?: string | null; detailMessage?: string | null; }[]; models?: (string | { id?: string; label?: string; description?: string })[]; modelCatalog?: RuntimeProviderModelCatalogResponse | null; capabilities?: { teamLaunch?: boolean; oneShot?: boolean; extensions?: RuntimeExtensionCapabilitiesResponse; }; backend?: { kind?: string; label?: string; endpointLabel?: string | null; projectId?: string | null; authMethodDetail?: string | null; } | null; runtimeCapabilities?: RuntimeProviderCapabilitiesResponse; subscriptionRateLimits?: RuntimeSubscriptionRateLimitSnapshotResponse | null; } >; } interface OpenCodeRuntimeVerifyResponse { schemaVersion?: number; providerId?: 'opencode'; snapshot?: { detected?: boolean; hostHealthy?: boolean; probeError?: string | null; diagnostics?: string[]; host?: { version?: string | null; resolvedConfigFingerprint?: string | null; } | null; profile?: { profileRootKey?: string; projectBehaviorFingerprint?: string; managedConfigFingerprint?: string; } | null; config?: { default_agent?: string; share?: string | null; snapshot?: boolean; autoupdate?: boolean | string; } | null; } | null; } export interface OpenCodeRuntimeTranscriptResponse { schemaVersion?: number; providerId?: 'opencode'; transcript?: { sessionId?: string; durableState?: string; staleReason?: string | null; messageCount?: number; toolCallCount?: number; errorCount?: number; latestAssistantText?: string | null; latestAssistantPreview?: string | null; messages?: unknown[]; diagnostics?: string[]; logProjection?: { sessionId?: string; durableState?: string; sourceMessageCount?: number; projectedMessageCount?: number; syntheticMessageCount?: number; toolCallCount?: number; errorCount?: number; diagnostics?: string[]; messages?: OpenCodeRuntimeTranscriptLogMessage[]; } | null; } | null; } export type OpenCodeRuntimeTranscriptLogContentBlock = | { type: 'text'; text: string; } | { type: 'thinking'; thinking: string; signature: string; } | { type: 'tool_use'; id: string; name: string; input: Record; } | { type: 'tool_result'; tool_use_id: string; content: string | OpenCodeRuntimeTranscriptLogContentBlock[]; is_error?: boolean; }; export interface OpenCodeRuntimeTranscriptLogToolCall { id: string; name: string; input: Record; isTask: boolean; taskDescription?: string; taskSubagentType?: string; } export interface OpenCodeRuntimeTranscriptLogToolResult { toolUseId: string; content: string | OpenCodeRuntimeTranscriptLogContentBlock[]; isError: boolean; } export interface OpenCodeRuntimeTranscriptLogMessage { uuid: string; parentUuid: string | null; type: 'assistant' | 'user' | 'system'; timestamp: string; role?: string; content: OpenCodeRuntimeTranscriptLogContentBlock[] | string; model?: string; agentName?: string; isMeta: boolean; sessionId: string; toolCalls: OpenCodeRuntimeTranscriptLogToolCall[]; toolResults: OpenCodeRuntimeTranscriptLogToolResult[]; sourceToolUseID?: string; sourceToolAssistantUUID?: string; subtype?: string; level?: string; } const ORDERED_PROVIDER_IDS: CliProviderId[] = ['anthropic', 'codex', 'gemini', 'opencode']; const DEFAULT_PROVIDER_STATUS_IDS: CliProviderId[] = ['anthropic', 'codex', 'opencode']; function getProviderDisplayName(providerId: CliProviderId): string { switch (providerId) { case 'anthropic': return 'Anthropic'; case 'codex': return 'Codex'; case 'gemini': return 'Gemini'; case 'opencode': return 'OpenCode (200+ models)'; } } function extractJsonObject(raw: string): T { const trimmed = raw.trim(); try { return JSON.parse(trimmed) as T; } catch { const start = trimmed.indexOf('{'); const end = trimmed.lastIndexOf('}'); if (start >= 0 && end > start) { return JSON.parse(trimmed.slice(start, end + 1)) as T; } throw new Error('No JSON object found in CLI output'); } } function createDefaultProviderStatus(providerId: CliProviderId): CliProviderStatus { return { providerId, displayName: getProviderDisplayName(providerId), supported: false, authenticated: false, authMethod: null, verificationState: 'unknown', modelVerificationState: 'idle', modelCatalogRefreshState: 'idle', statusMessage: null, detailMessage: null, models: [], modelAvailability: [], canLoginFromUi: providerId !== 'opencode', capabilities: { teamLaunch: false, oneShot: false, extensions: createLegacyRuntimeFallbackCliExtensionCapabilities(), }, selectedBackendId: null, resolvedBackendId: null, availableBackends: [], externalRuntimeDiagnostics: [], backend: null, connection: null, modelCatalog: null, runtimeCapabilities: null, subscriptionRateLimits: null, }; } function createPendingProviderStatus(providerId: CliProviderId): CliProviderStatus { return { ...createDefaultProviderStatus(providerId), statusMessage: 'Checking...', }; } function createRuntimeStatusErrorProviderStatus( providerId: CliProviderId, error: unknown ): CliProviderStatus { const message = error instanceof Error ? error.message : String(error); const lower = message.toLowerCase(); const detailMessage = providerId === 'opencode' && (lower.includes('timed out') || lower.includes('timeout')) ? [ 'OpenCode runtime status did not return before the desktop timeout.', 'This means the Agent Teams runtime process did not produce provider-status JSON in time, not necessarily that OpenCode auth is missing.', 'Likely causes include slow or hung OpenCode CLI startup, provider/model inventory, local OpenCode plugins, cache/profile corruption, stale bundled runtime, or Windows security software delaying child processes.', `Raw timeout detail: ${message}`, ].join(' ') : message; return { ...createDefaultProviderStatus(providerId), verificationState: 'error', statusMessage: CLI_PROVIDER_STATUS_UNAVAILABLE_MESSAGE, detailMessage, }; } function mapRuntimeExtensionCapabilities( providerId: CliProviderId, capabilities?: RuntimeExtensionCapabilitiesResponse ): CliProviderStatus['capabilities']['extensions'] { const defaults = capabilities ? createDefaultCliExtensionCapabilities() : createLegacyRuntimeFallbackCliExtensionCapabilities(); const pluginStatus = providerId === 'opencode' ? 'unsupported' : (capabilities?.plugins?.status ?? defaults.plugins.status); const pluginReason = providerId === 'opencode' ? (capabilities?.plugins?.reason ?? 'OpenCode does not support plugin management from Agent Teams.') : (capabilities?.plugins?.reason ?? defaults.plugins.reason); return { plugins: { ...defaults.plugins, status: pluginStatus, ownership: capabilities?.plugins?.ownership ?? defaults.plugins.ownership, reason: pluginReason, }, mcp: { ...defaults.mcp, status: capabilities?.mcp?.status ?? defaults.mcp.status, ownership: capabilities?.mcp?.ownership ?? defaults.mcp.ownership, reason: capabilities?.mcp?.reason ?? defaults.mcp.reason, }, skills: { ...defaults.skills, status: capabilities?.skills?.status ?? defaults.skills.status, ownership: capabilities?.skills?.ownership ?? defaults.skills.ownership, reason: capabilities?.skills?.reason ?? defaults.skills.reason, }, apiKeys: { ...defaults.apiKeys, status: capabilities?.apiKeys?.status ?? defaults.apiKeys.status, ownership: capabilities?.apiKeys?.ownership ?? defaults.apiKeys.ownership, reason: capabilities?.apiKeys?.reason ?? defaults.apiKeys.reason, }, }; } function extractModelIds( models: (string | { id?: string; label?: string; description?: string })[] | undefined ): string[] { if (!models) { return []; } return models.flatMap((model) => { if (typeof model === 'string') { return [model]; } if (typeof model?.id === 'string' && model.id.trim().length > 0) { return [model.id.trim()]; } return []; }); } function normalizeRuntimeReasoningEffort( value: string | null | undefined ): CliProviderReasoningEffort | null { return value === 'none' || value === 'minimal' || value === 'low' || value === 'medium' || value === 'high' || value === 'xhigh' || value === 'max' ? value : null; } function collectRuntimeReasoningEfforts(values?: string[]): CliProviderReasoningEffort[] { return ( values?.flatMap((value) => { const normalized = normalizeRuntimeReasoningEffort(value); return normalized ? [normalized] : []; }) ?? [] ); } const OPENCODE_ACCESS_KINDS = new Set([ 'no_model', 'unknown_model', 'credentialed', 'builtin_free', 'configured_authless', 'verified', 'not_authenticated', 'execution_failed', ]); const OPENCODE_ROUTE_KINDS = new Set([ 'connected_provider', 'builtin_free', 'configured_local', 'catalog_provider', ]); const OPENCODE_PROOF_STATES = new Set(['not_required', 'needs_probe', 'verified', 'failed']); function asStringOrNull(value: unknown): string | null { return typeof value === 'string' ? value : null; } function mapOpenCodeModelRouteMetadata(value: unknown): OpenCodeModelRouteMetadata | null { if (!value || typeof value !== 'object' || Array.isArray(value)) { return null; } const record = value as Record; const accessKind = record.accessKind; const routeKind = record.routeKind; const proofState = record.proofState; if ( typeof accessKind !== 'string' || typeof routeKind !== 'string' || typeof proofState !== 'string' || !OPENCODE_ACCESS_KINDS.has(accessKind) || !OPENCODE_ROUTE_KINDS.has(routeKind) || !OPENCODE_PROOF_STATES.has(proofState) ) { return null; } return { providerId: asStringOrNull(record.providerId), modelId: asStringOrNull(record.modelId), sourceLabel: asStringOrNull(record.sourceLabel), accessKind: accessKind as OpenCodeModelRouteMetadata['accessKind'], routeKind: routeKind as OpenCodeModelRouteMetadata['routeKind'], proofState: proofState as OpenCodeModelRouteMetadata['proofState'], requiresExecutionProof: record.requiresExecutionProof === true, reason: asStringOrNull(record.reason), }; } function mapRuntimeProviderModelMetadata( metadata?: Record | null ): NonNullable['models'][number]['metadata'] { if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) { return null; } const context = metadata.context; const opencode = mapOpenCodeModelRouteMetadata(metadata.opencode); return { cost: metadata.cost ?? null, context: typeof context === 'number' && Number.isFinite(context) ? context : null, limits: metadata.limits ?? null, free: metadata.free === true, ...(opencode ? { opencode } : {}), }; } function mapRuntimeProviderModelCatalog( providerId: CliProviderId, modelCatalog?: RuntimeProviderModelCatalogResponse | null ): CliProviderStatus['modelCatalog'] { if (modelCatalog?.providerId !== providerId) { return null; } const fetchedAt = modelCatalog.fetchedAt?.trim(); const staleAt = modelCatalog.staleAt?.trim(); const source = modelCatalog.source; const status = modelCatalog.status; if ( modelCatalog.schemaVersion !== 1 || !fetchedAt || !staleAt || (source !== 'anthropic-models-api' && source !== 'anthropic-compatible-api' && source !== 'app-server' && source !== 'static-fallback') || (status !== 'ready' && status !== 'stale' && status !== 'degraded' && status !== 'unavailable') ) { return null; } const models: NonNullable['models'] = modelCatalog.models?.flatMap((model) => { const id = model.id?.trim(); const launchModel = model.launchModel?.trim(); const displayName = model.displayName?.trim(); if (!id || !launchModel || !displayName) { return []; } const supportedReasoningEfforts = collectRuntimeReasoningEfforts( model.supportedReasoningEfforts ); const defaultReasoningEffort = normalizeRuntimeReasoningEffort( model.defaultReasoningEffort ?? null ); const itemSource = model.source === 'anthropic-models-api' || model.source === 'anthropic-compatible-api' || model.source === 'app-server' || model.source === 'static-fallback' ? model.source : source; return [ { id, launchModel, displayName, hidden: model.hidden === true, supportedReasoningEfforts, defaultReasoningEffort, supportsFastMode: model.supportsFastMode === true, inputModalities: model.inputModalities?.filter((value) => value.trim().length > 0) ?? [], supportsPersonality: model.supportsPersonality === true, isDefault: model.isDefault === true, upgrade: model.upgrade === true, source: itemSource, badgeLabel: model.badgeLabel ?? null, statusMessage: model.statusMessage ?? null, metadata: mapRuntimeProviderModelMetadata(model.metadata), }, ]; }) ?? []; return { schemaVersion: 1, providerId, source, status, fetchedAt, staleAt, defaultModelId: modelCatalog.defaultModelId ?? null, defaultLaunchModel: modelCatalog.defaultLaunchModel ?? null, models, diagnostics: { configReadState: modelCatalog.diagnostics?.configReadState ?? 'skipped', appServerState: modelCatalog.diagnostics?.appServerState ?? 'degraded', message: modelCatalog.diagnostics?.message ?? null, code: modelCatalog.diagnostics?.code ?? null, }, }; } function getRuntimeModelCatalogRefreshState( runtimeStatus: NonNullable[string] | undefined, modelCatalog: CliProviderStatus['modelCatalog'] ): NonNullable { if (modelCatalog) { return 'ready'; } return runtimeStatus?.runtimeCapabilities?.modelCatalog?.dynamic === true ? 'loading' : 'idle'; } function mapRuntimeSubscriptionRateLimitWindow( window: RuntimeSubscriptionRateLimitWindowResponse | null | undefined ): NonNullable | null { if (!window || typeof window.usedPercent !== 'number' || !Number.isFinite(window.usedPercent)) { return null; } return { usedPercent: Math.max(0, Math.min(100, window.usedPercent)), windowDurationMins: typeof window.windowDurationMins === 'number' && Number.isFinite(window.windowDurationMins) ? window.windowDurationMins : null, resetsAt: typeof window.resetsAt === 'number' && Number.isFinite(window.resetsAt) ? window.resetsAt : null, }; } function mapRuntimeSubscriptionRateLimits( providerId: CliProviderId, authMethod: string | null | undefined, rateLimits: RuntimeSubscriptionRateLimitSnapshotResponse | null | undefined ): CliProviderSubscriptionRateLimitSnapshot | null { if ( providerId !== 'anthropic' || (authMethod !== 'claude.ai' && authMethod !== 'oauth_token') || !rateLimits ) { return null; } const primary = mapRuntimeSubscriptionRateLimitWindow(rateLimits.primary); const secondary = mapRuntimeSubscriptionRateLimitWindow(rateLimits.secondary); return primary || secondary ? { primary, secondary } : null; } function mergeRuntimeCapabilitiesForCatalogHydration( live: CliProviderStatus['runtimeCapabilities'], hydrated: CliProviderStatus['runtimeCapabilities'] ): CliProviderStatus['runtimeCapabilities'] { if (!hydrated) { return live ?? null; } if (!live) { return hydrated; } return { ...live, modelCatalog: hydrated.modelCatalog ?? live.modelCatalog, reasoningEffort: hydrated.reasoningEffort ?? live.reasoningEffort, fastMode: hydrated.fastMode ?? live.fastMode, }; } function shouldPromoteHydratedAuthState( liveProvider: CliProviderStatus, hydratedProvider: CliProviderStatus ): boolean { return ( liveProvider.providerId === 'opencode' && liveProvider.authenticated !== true && hydratedProvider.authenticated === true ); } function mergeProviderCatalogFields( liveProvider: CliProviderStatus, hydratedProvider: CliProviderStatus ): CliProviderStatus { const modelCatalog = hydratedProvider.modelCatalog ?? liveProvider.modelCatalog ?? null; const promoteHydratedAuthState = shouldPromoteHydratedAuthState(liveProvider, hydratedProvider); return { ...liveProvider, authenticated: promoteHydratedAuthState ? hydratedProvider.authenticated : liveProvider.authenticated, authMethod: promoteHydratedAuthState ? hydratedProvider.authMethod : liveProvider.authMethod, verificationState: promoteHydratedAuthState ? hydratedProvider.verificationState : liveProvider.verificationState, capabilities: promoteHydratedAuthState ? hydratedProvider.capabilities : liveProvider.capabilities, statusMessage: promoteHydratedAuthState ? hydratedProvider.statusMessage : liveProvider.statusMessage, detailMessage: promoteHydratedAuthState ? hydratedProvider.detailMessage : liveProvider.detailMessage, backend: promoteHydratedAuthState ? hydratedProvider.backend : liveProvider.backend, models: hydratedProvider.models.length > 0 ? hydratedProvider.models : liveProvider.models, modelCatalog, modelCatalogRefreshState: modelCatalog ? 'ready' : hydratedProvider.modelCatalogRefreshState === 'error' ? 'error' : liveProvider.modelCatalogRefreshState, runtimeCapabilities: mergeRuntimeCapabilitiesForCatalogHydration( liveProvider.runtimeCapabilities, hydratedProvider.runtimeCapabilities ), subscriptionRateLimits: hydratedProvider.subscriptionRateLimits ?? liveProvider.subscriptionRateLimits ?? null, }; } export class ClaudeMultimodelBridgeService { private providerStatusHydrationGeneration = 0; private readonly providerStatusHydrationGenerations = new Map(); private readonly providerStatusHydrationInFlight = new Map< CliProviderId, { readonly generation: number; readonly promise: Promise } >(); private beginProviderStatusHydration(providerIds: readonly CliProviderId[]): number { const generation = ++this.providerStatusHydrationGeneration; for (const providerId of providerIds) { this.providerStatusHydrationGenerations.set(providerId, generation); } return generation; } private isProviderStatusHydrationCurrent(providerId: CliProviderId, generation: number): boolean { return this.providerStatusHydrationGenerations.get(providerId) === generation; } private getProviderCatalogHydration( binaryPath: string, providerId: CliProviderId, generation: number ): Promise { const inFlight = this.providerStatusHydrationInFlight.get(providerId); if (inFlight) { if (inFlight.generation === generation) { return inFlight.promise; } return inFlight.promise .catch(() => undefined) .then(() => { if (!this.isProviderStatusHydrationCurrent(providerId, generation)) { return null; } return this.getProviderCatalogHydration(binaryPath, providerId, generation); }); } const request = this.getProviderStatusFromScopedRuntimeStatus(binaryPath, providerId).finally( () => { if (this.providerStatusHydrationInFlight.get(providerId)?.promise === request) { this.providerStatusHydrationInFlight.delete(providerId); } } ); this.providerStatusHydrationInFlight.set(providerId, { generation, promise: request }); return request; } private async buildCliEnv( binaryPath: string ): Promise>> { return buildProviderAwareCliEnv({ binaryPath, allowStoredApiKeyDecryption: false, allowedStoredApiKeyEnvVarNames: ['ANTHROPIC_AUTH_TOKEN'], }); } private async buildProviderCliEnv( binaryPath: string, providerId: CliProviderId ): Promise>> { return buildProviderAwareCliEnv({ binaryPath, providerId, allowStoredApiKeyDecryption: false, allowedStoredApiKeyEnvVarNames: providerId === 'anthropic' ? ['ANTHROPIC_AUTH_TOKEN'] : undefined, }); } private isRuntimeStatusCompatibilityError(error: unknown): boolean { const message = error instanceof Error ? error.message : String(error); const lower = message.toLowerCase(); return ( lower.includes('unknown command') || lower.includes('unknown option') || lower.includes('no such command') || lower.includes('did you mean') ); } private isUnifiedRuntimeUnsupported(error: unknown): boolean { const message = error instanceof Error ? error.message : String(error); const lower = message.toLowerCase(); return this.isRuntimeStatusCompatibilityError(error) || lower.includes('runtime status'); } private isRuntimeStatusTimeoutError(error: unknown): boolean { const message = error instanceof Error ? error.message : String(error); const lower = message.toLowerCase(); return lower.includes('timed out') || lower.includes('timeout'); } private shouldUseLegacyProviderTimeoutFallback(providerId: CliProviderId): boolean { return providerId === 'anthropic' || providerId === 'codex' || providerId === 'opencode'; } private getProviderStatusRuntimeTimeout( providerId: CliProviderId, options: { summary?: boolean; timeoutMs?: number } ): number { if (options.summary && this.shouldUseLegacyProviderTimeoutFallback(providerId)) { const fallbackTimeout = providerId === 'opencode' ? OPENCODE_FALLBACK_PROVIDER_STATUS_SUMMARY_TIMEOUT_MS : LEGACY_FALLBACK_PROVIDER_STATUS_SUMMARY_TIMEOUT_MS; return Math.min(options.timeoutMs ?? PROVIDER_STATUS_SUMMARY_TIMEOUT_MS, fallbackTimeout); } return ( options.timeoutMs ?? (options.summary ? PROVIDER_STATUS_SUMMARY_TIMEOUT_MS : PROVIDER_STATUS_TIMEOUT_MS) ); } private getLegacyProviderStatusPayload( providerId: CliProviderId, parsed: ProviderStatusCommandResponse ): ProviderStatusPayloadResponse | undefined { if (parsed.providers?.[providerId]) { return parsed.providers[providerId]; } return parsed.provider === providerId ? parsed.status : undefined; } private mergeLegacyProviderStatusPayload( provider: CliProviderStatus, runtimeStatus: ProviderStatusPayloadResponse | undefined ): CliProviderStatus { if (!runtimeStatus) { return provider; } return { ...provider, supported: runtimeStatus.supported === true, authenticated: runtimeStatus.authenticated === true, authMethod: runtimeStatus.authMethod ?? null, verificationState: runtimeStatus.verificationState ?? 'unknown', statusMessage: runtimeStatus.statusMessage ?? null, detailMessage: runtimeStatus.detailMessage ?? null, canLoginFromUi: runtimeStatus.canLoginFromUi !== false, capabilities: { teamLaunch: runtimeStatus.capabilities?.teamLaunch === true, oneShot: runtimeStatus.capabilities?.oneShot === true, extensions: mapRuntimeExtensionCapabilities( provider.providerId, runtimeStatus.capabilities?.extensions ), }, backend: runtimeStatus.backend?.kind ? { kind: runtimeStatus.backend.kind, label: runtimeStatus.backend.label ?? runtimeStatus.backend.kind, endpointLabel: runtimeStatus.backend.endpointLabel ?? null, projectId: runtimeStatus.backend.projectId ?? null, authMethodDetail: runtimeStatus.backend.authMethodDetail ?? null, } : null, }; } private async getProviderStatusFromLegacyProbes( binaryPath: string, providerId: CliProviderId ): Promise { const { env, connectionIssues } = await this.buildProviderCliEnv(binaryPath, providerId); let provider = createDefaultProviderStatus(providerId); let fulfilledProbeCount = 0; const authStatusPromise = providerId === 'anthropic' || providerId === 'codex' ? execCli(binaryPath, ['auth', 'status', '--json', '--provider', providerId], { timeout: LEGACY_PROVIDER_AUTH_TIMEOUT_MS, maxBuffer: PROVIDER_STATUS_MAX_BUFFER_BYTES, env, }) : Promise.resolve(null); const modelListPromise = execCli( binaryPath, ['model', 'list', '--json', '--provider', providerId], { timeout: PROVIDER_MODELS_TIMEOUT_MS, maxBuffer: PROVIDER_MODELS_MAX_BUFFER_BYTES, env, } ); const [authStatusResult, modelListResult] = await Promise.allSettled([ authStatusPromise, modelListPromise, ]); if (authStatusResult.status === 'fulfilled' && authStatusResult.value) { const parsed = extractJsonObject( authStatusResult.value.stdout ); provider = this.mergeLegacyProviderStatusPayload( provider, this.getLegacyProviderStatusPayload(providerId, parsed) ); fulfilledProbeCount += 1; } else if (authStatusResult.status === 'rejected') { logger.warn( `Legacy provider auth status unavailable for ${providerId}: ${ authStatusResult.reason instanceof Error ? authStatusResult.reason.message : String(authStatusResult.reason) }` ); } if (modelListResult.status === 'fulfilled') { const parsed = extractJsonObject(modelListResult.value.stdout); const runtimeModels = extractModelIds(parsed.providers?.[providerId]?.models); if (runtimeModels.length > 0) { provider = { ...provider, models: runtimeModels, }; } fulfilledProbeCount += 1; } else { logger.warn( `Legacy provider models unavailable for ${providerId}: ${ modelListResult.reason instanceof Error ? modelListResult.reason.message : String(modelListResult.reason) }` ); } if (fulfilledProbeCount === 0) { throw new Error(`Legacy provider probes unavailable for ${providerId}`); } return providerConnectionService.enrichProviderStatus( this.applyConnectionIssue(provider, connectionIssues) ); } private async getProviderStatusFromLegacyProbesOrError( binaryPath: string, providerId: CliProviderId, originalError: unknown ): Promise { try { return await this.getProviderStatusFromLegacyProbes(binaryPath, providerId); } catch (fallbackError) { logger.warn( `Legacy provider probes unavailable for ${providerId}: ${ fallbackError instanceof Error ? fallbackError.message : String(fallbackError) }` ); return createRuntimeStatusErrorProviderStatus(providerId, originalError); } } private mapRuntimeProviderStatus( providerId: CliProviderId, runtimeStatus: NonNullable[string] | undefined ): CliProviderStatus { const provider = createDefaultProviderStatus(providerId); if (!runtimeStatus) { return provider; } const modelCatalog = mapRuntimeProviderModelCatalog(providerId, runtimeStatus.modelCatalog); return { ...provider, supported: runtimeStatus.supported === true, authenticated: runtimeStatus.authenticated === true, authMethod: runtimeStatus.authMethod ?? null, verificationState: runtimeStatus.verificationState ?? 'unknown', statusMessage: runtimeStatus.statusMessage ?? null, detailMessage: runtimeStatus.detailMessage ?? null, canLoginFromUi: runtimeStatus.canLoginFromUi !== false, capabilities: { teamLaunch: runtimeStatus.capabilities?.teamLaunch === true, oneShot: runtimeStatus.capabilities?.oneShot === true, extensions: mapRuntimeExtensionCapabilities( providerId, runtimeStatus.capabilities?.extensions ), }, selectedBackendId: runtimeStatus.selectedBackendId ?? null, resolvedBackendId: runtimeStatus.resolvedBackendId ?? null, availableBackends: runtimeStatus.availableBackends?.map((backend) => ({ id: backend.id ?? 'unknown', label: backend.label ?? backend.id ?? 'Unknown', description: backend.description ?? '', selectable: backend.selectable !== false, recommended: backend.recommended === true, available: backend.available === true, state: backend.state ?? undefined, audience: backend.audience ?? undefined, statusMessage: backend.statusMessage ?? null, detailMessage: backend.detailMessage ?? null, })) ?? [], externalRuntimeDiagnostics: runtimeStatus.externalRuntimeDiagnostics?.map((diagnostic) => ({ id: diagnostic.id ?? 'unknown', label: diagnostic.label ?? diagnostic.id ?? 'Unknown', detected: diagnostic.detected === true, statusMessage: diagnostic.statusMessage ?? null, detailMessage: diagnostic.detailMessage ?? null, })) ?? [], models: extractModelIds(runtimeStatus.models), modelCatalog, modelCatalogRefreshState: getRuntimeModelCatalogRefreshState(runtimeStatus, modelCatalog), subscriptionRateLimits: mapRuntimeSubscriptionRateLimits( providerId, runtimeStatus.authMethod, runtimeStatus.subscriptionRateLimits ), backend: runtimeStatus.backend?.kind ? { kind: runtimeStatus.backend.kind, label: runtimeStatus.backend.label ?? runtimeStatus.backend.kind, endpointLabel: runtimeStatus.backend.endpointLabel ?? null, projectId: runtimeStatus.backend.projectId ?? null, authMethodDetail: runtimeStatus.backend.authMethodDetail ?? null, } : null, runtimeCapabilities: runtimeStatus.runtimeCapabilities ? { modelCatalog: runtimeStatus.runtimeCapabilities.modelCatalog ? { dynamic: runtimeStatus.runtimeCapabilities.modelCatalog.dynamic === true, source: runtimeStatus.runtimeCapabilities.modelCatalog.source, } : undefined, reasoningEffort: runtimeStatus.runtimeCapabilities.reasoningEffort ? { supported: runtimeStatus.runtimeCapabilities.reasoningEffort.supported === true, values: collectRuntimeReasoningEfforts( runtimeStatus.runtimeCapabilities.reasoningEffort.values ), configPassthrough: runtimeStatus.runtimeCapabilities.reasoningEffort.configPassthrough === true, } : undefined, fastMode: runtimeStatus.runtimeCapabilities.fastMode ? { supported: runtimeStatus.runtimeCapabilities.fastMode.supported === true, available: runtimeStatus.runtimeCapabilities.fastMode.available === true, reason: runtimeStatus.runtimeCapabilities.fastMode.reason ?? null, source: 'runtime', } : undefined, } : null, }; } private applyConnectionIssue( provider: CliProviderStatus, connectionIssues: Partial> ): CliProviderStatus { const issue = connectionIssues[provider.providerId]; if (!issue) { return provider; } return { ...provider, authenticated: false, authMethod: null, verificationState: 'error', statusMessage: issue, detailMessage: null, backend: null, }; } private applyConnectionIssues( providers: CliProviderStatus[], connectionIssues: Partial> ): CliProviderStatus[] { return providers.map((provider) => this.applyConnectionIssue(provider, connectionIssues)); } private buildProviderStatusesSnapshot( providers: Map, providerIds: readonly CliProviderId[] = ORDERED_PROVIDER_IDS ): CliProviderStatus[] { return providerIds.map( (providerId) => providers.get(providerId) ?? createPendingProviderStatus(providerId) ); } private async getProviderStatusFromRuntimeStatusCommand( binaryPath: string, providerId: CliProviderId, env: NodeJS.ProcessEnv, connectionIssues: Partial>, options: { summary?: boolean; timeoutMs?: number } = {} ): Promise { const args = ['runtime', 'status', '--json', '--provider', providerId]; if (options.summary) { args.push('--summary'); } const timeout = this.getProviderStatusRuntimeTimeout(providerId, options); const { stdout } = await execCli(binaryPath, args, { timeout, maxBuffer: PROVIDER_STATUS_MAX_BUFFER_BYTES, env, }); const parsed = extractJsonObject(stdout); return providerConnectionService.enrichProviderStatus( this.applyConnectionIssue( this.mapRuntimeProviderStatus(providerId, parsed.providers?.[providerId]), connectionIssues ), { hydrateModelCatalog: options.summary !== true } ); } private async getProviderStatusFromScopedRuntimeStatus( binaryPath: string, providerId: CliProviderId, options: { summary?: boolean; timeoutMs?: number } = {} ): Promise { const { env, connectionIssues } = await this.buildProviderCliEnv(binaryPath, providerId); return this.getProviderStatusFromRuntimeStatusCommand( binaryPath, providerId, env, connectionIssues, options ); } private async getProviderStatusesFromScopedRuntimeStatus( binaryPath: string, onUpdate?: (providers: CliProviderStatus[]) => void, options: { summary?: boolean; timeoutMs?: number; providerIds?: readonly CliProviderId[] } = {} ): Promise { const providerIds = options.providerIds ?? ORDERED_PROVIDER_IDS; const providers = new Map( providerIds.map((providerId) => [providerId, createPendingProviderStatus(providerId)]) ); const failures: { providerId: CliProviderId; error: unknown }[] = []; await Promise.all( providerIds.map(async (providerId) => { try { providers.set( providerId, await this.getProviderStatusFromScopedRuntimeStatus(binaryPath, providerId, options) ); onUpdate?.(this.buildProviderStatusesSnapshot(providers, providerIds)); } catch (error) { failures.push({ providerId, error }); } }) ); failures.sort((a, b) => providerIds.indexOf(a.providerId) - providerIds.indexOf(b.providerId)); if (failures.length === 0) { return this.buildProviderStatusesSnapshot(providers, providerIds); } if (failures.length === providerIds.length) { if (failures.every(({ error }) => this.isRuntimeStatusTimeoutError(error))) { logger.warn( `Provider-scoped runtime status timed out for ${failures .map(({ providerId }) => providerId) .join(', ')}; falling back to scoped legacy provider probes` ); const fallbackProviders = await Promise.all( failures.map(async ({ providerId, error }) => ({ providerId, provider: this.shouldUseLegacyProviderTimeoutFallback(providerId) ? await this.getProviderStatusFromLegacyProbesOrError(binaryPath, providerId, error) : createRuntimeStatusErrorProviderStatus(providerId, error), })) ); for (const { providerId, provider } of fallbackProviders) { providers.set(providerId, provider); } onUpdate?.(this.buildProviderStatusesSnapshot(providers, providerIds)); return this.buildProviderStatusesSnapshot(providers, providerIds); } return null; } logger.warn( `Provider-scoped runtime status failed for ${failures .map(({ providerId }) => providerId) .join(', ')}; using partial provider statuses` ); const fallbackProviders = await Promise.all( failures.map(async ({ providerId, error }) => ({ providerId, provider: this.isRuntimeStatusTimeoutError(error) && this.shouldUseLegacyProviderTimeoutFallback(providerId) ? await this.getProviderStatusFromLegacyProbesOrError(binaryPath, providerId, error) : createRuntimeStatusErrorProviderStatus(providerId, error), })) ); for (const { providerId, provider } of fallbackProviders) { providers.set(providerId, provider); } onUpdate?.(this.buildProviderStatusesSnapshot(providers, providerIds)); return this.buildProviderStatusesSnapshot(providers, providerIds); } private hydrateProviderCatalogs( binaryPath: string, liveProviders: CliProviderStatus[], generation: number, onUpdate?: (providers: CliProviderStatus[]) => void ): void { if (!onUpdate) { return; } const providers = new Map( liveProviders.map((provider) => [provider.providerId, provider]) ); const providerIds = liveProviders.map((provider) => provider.providerId); for (const liveProvider of liveProviders) { if (liveProvider.runtimeCapabilities?.modelCatalog?.dynamic !== true) { continue; } void this.getProviderCatalogHydration(binaryPath, liveProvider.providerId, generation) .then((hydratedProvider) => { if (!hydratedProvider) { return; } if (!this.isProviderStatusHydrationCurrent(liveProvider.providerId, generation)) { return; } const currentProvider = providers.get(liveProvider.providerId); if (!currentProvider) { return; } providers.set( liveProvider.providerId, mergeProviderCatalogFields(currentProvider, hydratedProvider) ); onUpdate(this.buildProviderStatusesSnapshot(providers, providerIds)); }) .catch((error) => { if (!this.isProviderStatusHydrationCurrent(liveProvider.providerId, generation)) { return; } const currentProvider = providers.get(liveProvider.providerId); if (!currentProvider) { return; } providers.set(liveProvider.providerId, { ...currentProvider, modelCatalogRefreshState: 'error', }); logger.warn( `Provider catalog hydration failed for ${liveProvider.providerId}: ${ error instanceof Error ? error.message : String(error) }` ); onUpdate(this.buildProviderStatusesSnapshot(providers, providerIds)); }); } } private async getOpenCodeVerifySnapshot( binaryPath: string ): Promise { const { env } = await this.buildCliEnv(binaryPath); const { stdout } = await execCli( binaryPath, ['runtime', 'verify', '--json', '--provider', 'opencode'], { timeout: PROVIDER_STATUS_TIMEOUT_MS, maxBuffer: PROVIDER_STATUS_MAX_BUFFER_BYTES, env, } ); const parsed = extractJsonObject(stdout); return parsed.providerId === 'opencode' ? (parsed.snapshot ?? null) : null; } private mergeOpenCodeVerification( provider: CliProviderStatus, snapshot: OpenCodeRuntimeVerifyResponse['snapshot'] ): CliProviderStatus { if (!snapshot) { return provider; } const diagnostics = snapshot.diagnostics ?? []; const diagnosticsSummary = diagnostics.slice(0, 2).join(' - '); const liveIssuesPresent = snapshot.detected === false || snapshot.hostHealthy !== true || Boolean(snapshot.probeError) || diagnostics.length > 0; const detailParts = [ provider.detailMessage ?? null, snapshot.host?.resolvedConfigFingerprint ? `live ${snapshot.host.resolvedConfigFingerprint.slice(0, 12)}` : null, snapshot.profile?.managedConfigFingerprint ? `managed ${snapshot.profile.managedConfigFingerprint.slice(0, 12)}` : null, snapshot.profile?.projectBehaviorFingerprint ? `behavior ${snapshot.profile.projectBehaviorFingerprint.slice(0, 12)}` : null, diagnosticsSummary || null, ].filter((value): value is string => Boolean(value)); const nextDiagnostics = [ ...(provider.externalRuntimeDiagnostics ?? []), { id: 'opencode-live-host', label: 'OpenCode live host', detected: snapshot.hostHealthy === true, statusMessage: snapshot.hostHealthy === true ? 'Healthy' : 'Unavailable', detailMessage: snapshot.probeError ?? null, }, { id: 'opencode-managed-runtime', label: 'OpenCode managed runtime', detected: !liveIssuesPresent, statusMessage: liveIssuesPresent ? 'Live verification found runtime drift' : 'Managed runtime verified', detailMessage: diagnosticsSummary || null, }, ]; return { ...provider, verificationState: liveIssuesPresent ? 'error' : 'verified', statusMessage: liveIssuesPresent ? (snapshot.probeError ?? diagnostics[0] ?? 'OpenCode live verification found runtime drift') : provider.statusMessage, detailMessage: detailParts.length > 0 ? detailParts.join(' - ') : provider.detailMessage, externalRuntimeDiagnostics: nextDiagnostics, backend: provider.backend ? { ...provider.backend, authMethodDetail: snapshot.config?.default_agent === 'teammate' ? 'managed teammate agent' : (provider.backend.authMethodDetail ?? null), } : provider.backend, }; } async getProviderStatus( binaryPath: string, providerId: CliProviderId, onCatalogUpdate?: (provider: CliProviderStatus) => void ): Promise { await resolveInteractiveShellEnvBestEffort({ timeoutMs: 1_500, fallbackEnv: process.env, background: false, }); try { const generation = this.beginProviderStatusHydration([providerId]); const provider = await this.getProviderStatusFromScopedRuntimeStatus(binaryPath, providerId, { summary: true, }); if (provider.runtimeCapabilities?.modelCatalog?.dynamic === true && onCatalogUpdate) { void this.getProviderCatalogHydration(binaryPath, provider.providerId, generation) .then((hydratedProvider) => { if (!hydratedProvider) { return; } if (!this.isProviderStatusHydrationCurrent(provider.providerId, generation)) { return; } onCatalogUpdate(mergeProviderCatalogFields(provider, hydratedProvider)); }) .catch((error) => { if (!this.isProviderStatusHydrationCurrent(provider.providerId, generation)) { return; } logger.warn( `Provider catalog hydration failed for ${provider.providerId}: ${ error instanceof Error ? error.message : String(error) }` ); onCatalogUpdate({ ...provider, modelCatalogRefreshState: 'error', }); }); } return provider; } catch (error) { if (providerId === 'gemini' && this.isRuntimeStatusCompatibilityError(error)) { return this.buildGeminiStatus(binaryPath); } if (this.isRuntimeStatusCompatibilityError(error)) { logger.warn( `Provider-scoped summary runtime status unavailable for ${providerId}, falling back to full probe: ${ error instanceof Error ? error.message : String(error) }` ); try { return await this.getProviderStatusFromScopedRuntimeStatus(binaryPath, providerId); } catch (fullError) { if ( this.isRuntimeStatusTimeoutError(fullError) && this.shouldUseLegacyProviderTimeoutFallback(providerId) ) { logger.warn( `Provider-scoped full runtime status timed out for ${providerId}, falling back to scoped legacy probes: ${ fullError instanceof Error ? fullError.message : String(fullError) }` ); return this.getProviderStatusFromLegacyProbesOrError(binaryPath, providerId, fullError); } logger.warn( `Provider-scoped full runtime status unavailable for ${providerId}, returning scoped error: ${ fullError instanceof Error ? fullError.message : String(fullError) }` ); return createRuntimeStatusErrorProviderStatus(providerId, fullError); } } logger.warn( `Provider-scoped summary runtime status unavailable for ${providerId}: ${ error instanceof Error ? error.message : String(error) }` ); if ( this.isRuntimeStatusTimeoutError(error) && this.shouldUseLegacyProviderTimeoutFallback(providerId) ) { logger.warn( `Provider-scoped summary runtime status timed out for ${providerId}, falling back to scoped legacy probes: ${ error instanceof Error ? error.message : String(error) }` ); return this.getProviderStatusFromLegacyProbesOrError(binaryPath, providerId, error); } return createRuntimeStatusErrorProviderStatus(providerId, error); } } async verifyProviderStatus( binaryPath: string, providerId: CliProviderId ): Promise { const provider = await this.getProviderStatus(binaryPath, providerId); if (providerId !== 'opencode') { return provider; } try { const snapshot = await this.getOpenCodeVerifySnapshot(binaryPath); return this.mergeOpenCodeVerification(provider, snapshot); } catch (error) { logger.warn( `OpenCode live verification unavailable: ${ error instanceof Error ? error.message : String(error) }` ); return { ...provider, verificationState: 'error', statusMessage: 'OpenCode live verification failed', detailMessage: error instanceof Error ? error.message : String(error), }; } } async getOpenCodeTranscript( binaryPath: string, params: { teamId: string; memberName: string; limit?: number; laneId?: string; sessionId?: string; timeoutMs?: number; } ): Promise { const { env } = await this.buildCliEnv(binaryPath); const args = [ 'runtime', 'transcript', '--json', '--provider', 'opencode', '--team', params.teamId, '--member', params.memberName, '--projection-only', ]; if (typeof params.limit === 'number') { args.push('--limit', String(params.limit)); } if (typeof params.laneId === 'string' && params.laneId.trim().length > 0) { args.push('--lane', params.laneId.trim()); } if (typeof params.sessionId === 'string' && params.sessionId.trim().length > 0) { args.push('--session-id', params.sessionId.trim()); } const outputDir = await mkdtemp(path.join(tmpdir(), 'opencode-transcript-')); const outputPath = path.join(outputDir, 'transcript.json'); try { await execCli(binaryPath, [...args, '--output', outputPath], { timeout: params.timeoutMs ?? PROVIDER_STATUS_TIMEOUT_MS, env, }); const parsed = extractJsonObject( await readFile(outputPath, 'utf8') ); return parsed.providerId === 'opencode' ? (parsed.transcript ?? null) : null; } finally { await rm(outputDir, { recursive: true, force: true }).catch(() => undefined); } } async verifyOpenCodeModels( _binaryPath: string, provider: CliProviderStatus ): Promise { return { ...provider, modelVerificationState: 'idle', modelAvailability: [], }; } private async buildGeminiStatus(binaryPath: string): Promise { const provider = createDefaultProviderStatus('gemini'); const { env } = await this.buildProviderCliEnv(binaryPath, 'gemini'); try { const { stdout } = await execCli( binaryPath, ['model', 'list', '--json', '--provider', 'all'], { timeout: PROVIDER_MODELS_TIMEOUT_MS, maxBuffer: PROVIDER_MODELS_MAX_BUFFER_BYTES, env, } ); const parsed = extractJsonObject(stdout); const models = extractModelIds(parsed.providers?.gemini?.models); if (models.length > 0) { provider.supported = true; provider.models = models; provider.capabilities = { teamLaunch: true, oneShot: true, extensions: createDefaultCliExtensionCapabilities(), }; } } catch (error) { logger.warn( `Gemini model list unavailable: ${error instanceof Error ? error.message : String(error)}` ); } const authState = await resolveGeminiRuntimeAuth(env); if (authState.authenticated) { provider.authenticated = true; provider.authMethod = authState.authMethod === 'adc_authorized_user' || authState.authMethod === 'adc_service_account' ? `gemini_${authState.authMethod}` : authState.authMethod; provider.verificationState = 'verified'; provider.statusMessage = null; if (authState.authMethod === 'cli_oauth_personal') { provider.backend = { kind: 'cli', label: 'Gemini CLI', endpointLabel: 'Code Assist (cloudcode-pa.googleapis.com/v1internal)', projectId: authState.projectId, authMethodDetail: authState.authMethod, }; } return provider; } provider.statusMessage = authState.statusMessage ?? 'Set GEMINI_API_KEY or Google ADC to use Gemini.'; return provider; } async getProviderStatuses( binaryPath: string, onUpdate?: (providers: CliProviderStatus[]) => void ): Promise { await resolveInteractiveShellEnvBestEffort({ timeoutMs: 1_500, fallbackEnv: process.env, background: false, }); try { const generation = this.beginProviderStatusHydration(DEFAULT_PROVIDER_STATUS_IDS); const providers = await this.getProviderStatusesFromScopedRuntimeStatus( binaryPath, onUpdate, { summary: true, timeoutMs: PROVIDER_STATUS_SUMMARY_TIMEOUT_MS, providerIds: DEFAULT_PROVIDER_STATUS_IDS, } ); if (providers) { this.hydrateProviderCatalogs(binaryPath, providers, generation, onUpdate); return providers; } } catch (error) { logger.warn( `Provider-scoped summary runtime status unavailable, falling back to full probe: ${ error instanceof Error ? error.message : String(error) }` ); } try { const providers = await this.getProviderStatusesFromScopedRuntimeStatus( binaryPath, onUpdate, { providerIds: DEFAULT_PROVIDER_STATUS_IDS } ); if (providers) { return providers; } } catch (error) { logger.warn( `Provider-scoped full runtime status unavailable, falling back to legacy probes: ${ error instanceof Error ? error.message : String(error) }` ); } const { env, connectionIssues } = await this.buildCliEnv(binaryPath); try { const { stdout } = await execCli(binaryPath, ['runtime', 'status', '--json'], { timeout: PROVIDER_STATUS_TIMEOUT_MS, maxBuffer: PROVIDER_STATUS_MAX_BUFFER_BYTES, env, }); const parsed = extractJsonObject(stdout); const providers = await providerConnectionService.enrichProviderStatuses( this.applyConnectionIssues( DEFAULT_PROVIDER_STATUS_IDS.map((providerId) => this.mapRuntimeProviderStatus(providerId, parsed.providers?.[providerId]) ), connectionIssues ) ); onUpdate?.(providers); return providers; } catch (error) { if (!this.isUnifiedRuntimeUnsupported(error)) { logger.warn( `Unified runtime status unavailable, falling back to legacy probes: ${ error instanceof Error ? error.message : String(error) }` ); } } const [statusResult, modelsResult] = await Promise.allSettled([ execCli(binaryPath, ['auth', 'status', '--json', '--provider', 'all'], { timeout: PROVIDER_STATUS_TIMEOUT_MS, maxBuffer: PROVIDER_STATUS_MAX_BUFFER_BYTES, env, }), execCli(binaryPath, ['model', 'list', '--json', '--provider', 'all'], { timeout: PROVIDER_MODELS_TIMEOUT_MS, maxBuffer: PROVIDER_MODELS_MAX_BUFFER_BYTES, env, }), ]); const providers = new Map( DEFAULT_PROVIDER_STATUS_IDS.map((providerId) => [ providerId, createDefaultProviderStatus(providerId), ]) ); if (statusResult.status === 'fulfilled') { try { const parsed = extractJsonObject(statusResult.value.stdout); for (const providerId of DEFAULT_PROVIDER_STATUS_IDS) { const runtimeStatus = parsed.providers?.[providerId]; if (!runtimeStatus) continue; providers.set(providerId, { ...providers.get(providerId)!, supported: runtimeStatus.supported === true, authenticated: runtimeStatus.authenticated === true, authMethod: runtimeStatus.authMethod ?? null, verificationState: runtimeStatus.verificationState ?? 'unknown', statusMessage: runtimeStatus.statusMessage ?? null, detailMessage: runtimeStatus.detailMessage ?? null, canLoginFromUi: runtimeStatus.canLoginFromUi !== false, capabilities: { teamLaunch: runtimeStatus.capabilities?.teamLaunch === true, oneShot: runtimeStatus.capabilities?.oneShot === true, extensions: mapRuntimeExtensionCapabilities( providerId, runtimeStatus.capabilities?.extensions ), }, backend: runtimeStatus.backend?.kind ? { kind: runtimeStatus.backend.kind, label: runtimeStatus.backend.label ?? runtimeStatus.backend.kind, endpointLabel: runtimeStatus.backend.endpointLabel ?? null, projectId: runtimeStatus.backend.projectId ?? null, authMethodDetail: runtimeStatus.backend.authMethodDetail ?? null, } : null, }); onUpdate?.(DEFAULT_PROVIDER_STATUS_IDS.map((id) => providers.get(id)!)); } } catch (error) { logger.warn( `Failed to parse provider auth status JSON: ${ error instanceof Error ? error.message : String(error) }` ); } } else { const message = statusResult.reason instanceof Error ? statusResult.reason.message : String(statusResult.reason); logger.warn(`Provider auth status unavailable: ${message}`); for (const providerId of DEFAULT_PROVIDER_STATUS_IDS) { providers.set(providerId, { ...providers.get(providerId)!, statusMessage: 'Provider status not supported by current claude-multimodel build', }); onUpdate?.(DEFAULT_PROVIDER_STATUS_IDS.map((id) => providers.get(id)!)); } } if (modelsResult.status === 'fulfilled') { try { const parsed = extractJsonObject(modelsResult.value.stdout); for (const providerId of DEFAULT_PROVIDER_STATUS_IDS) { const runtimeModels = extractModelIds(parsed.providers?.[providerId]?.models); if (runtimeModels.length === 0) continue; providers.set(providerId, { ...providers.get(providerId)!, models: runtimeModels, }); onUpdate?.(DEFAULT_PROVIDER_STATUS_IDS.map((id) => providers.get(id)!)); } } catch (error) { logger.warn( `Failed to parse provider models JSON: ${ error instanceof Error ? error.message : String(error) }` ); } } const enrichedProviders = await providerConnectionService.enrichProviderStatuses( this.applyConnectionIssues( DEFAULT_PROVIDER_STATUS_IDS.map((providerId) => providers.get(providerId)!), connectionIssues ) ); onUpdate?.(enrichedProviders); return enrichedProviders; } }