import { useAtomValue } from "@effect/atom-react"; import { connectionStatusText } from "@t3tools/client-runtime/connection"; import { safeErrorLogAttributes } from "@t3tools/client-runtime/errors"; import { isAtomCommandInterrupted, squashAtomCommandFailure, } from "@t3tools/client-runtime/state/runtime"; import { defaultInstanceIdForDriver, type EnvironmentId, PROVIDER_DISPLAY_NAMES, ProviderDriverKind, type ProviderInstanceConfig, type ProviderInstanceId, resolveProviderInstanceEnabled, } from "@t3tools/contracts"; import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; import { getBackgroundActivityPresetSettings, resolveServerBackgroundActivitySettings, } from "@t3tools/shared/backgroundActivitySettings"; import * as Arr from "effect/Array"; import * as Duration from "effect/Duration"; import * as Equal from "effect/Equal"; import * as Result from "effect/Result"; import { ChevronDownIcon, CloudIcon, LaptopIcon, LoaderIcon, MonitorIcon, PlusIcon, RefreshCwIcon, TerminalIcon, } from "lucide-react"; import { useCallback, useMemo, useRef, useState, type ReactNode } from "react"; import { isDesktopLocalConnectionTarget } from "../../connection/desktopLocal"; import { isElectron } from "../../env"; import { usePrimarySessionState } from "../../environments/primary"; import { useEnvironmentSettings, useUpdateEnvironmentSettings } from "../../hooks/useSettings"; import { cn } from "../../lib/utils"; import { resolveAppModelSelectionState } from "../../modelSelection"; import { useEnvironments, usePrimaryEnvironmentId, type EnvironmentPresentation, } from "../../state/environments"; import { EMPTY_SERVER_PROVIDERS, serverEnvironment } from "../../state/server"; import { useEnvironmentSessionState } from "../../state/session"; import { useAtomCommand } from "../../state/use-atom-command"; import { getRelativeTimeState } from "../../timestampFormat"; import { ConnectionStatusDot, connectionPhaseDotClassName, connectionPhasePingClassName, } from "../ConnectionStatusDot"; import { canOneClickUpdateProviderCandidate, collectProviderUpdateCandidates, hasOneClickUpdateProviderCandidate, isProviderUpdateActive, type ProviderUpdateCandidate, } from "../ProviderUpdateLaunchNotification.logic"; import { Button } from "../ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible"; import { NumberField, NumberFieldDecrement, NumberFieldGroup, NumberFieldIncrement, NumberFieldInput, } from "../ui/number-field"; import { ScrollArea } from "../ui/scroll-area"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { stackedThreadToast, toastManager } from "../ui/toast"; import { AddProviderInstanceDialog } from "./AddProviderInstanceDialog"; import { ProviderInstanceCard } from "./ProviderInstanceCard"; import { DRIVER_OPTIONS, getDriverOption } from "./providerDriverMeta"; import { providerSettingsTabClassName } from "./providerSettingsTabs"; import { searchableSetting } from "./settingsSearch"; import { backgroundActivityOverrideSettings, buildProviderInstanceUpdatePatch, durationToSeconds, normalizeIntervalSeconds, PROVIDER_HEALTH_INTERVAL_STEP_SECONDS, } from "./SettingsPanels.logic"; import { PolicyTooltip, SettingResetButton, SettingsPageContainer, SettingsRow, SettingsSection, useRelativeTimeTick, } from "./settingsLayout"; import { buildProviderEnvironmentOptions, classifyProviderEnvironmentAccess, type ProviderEnvironmentAccess, type ProviderOperateAccess, resolvePrimaryOperateAccess, resolveRemoteOperateAccess, resolveSelectedProviderEnvironmentId, } from "./ProviderSettingsPanel.logic"; function withoutProviderInstanceKey( record: Readonly> | undefined, key: ProviderInstanceId, ): Record { const next = { ...record } as Record; delete next[key]; return next; } function withoutProviderInstanceFavorites( favorites: ReadonlyArray<{ readonly provider: ProviderInstanceId; readonly model: string }>, instanceId: ProviderInstanceId, ) { return favorites.filter((favorite) => favorite.provider !== instanceId); } const PROVIDER_SETTINGS = DRIVER_OPTIONS.map((definition) => ({ provider: definition.value, })); function ProviderLastChecked({ lastCheckedAt }: { lastCheckedAt: string | null }) { useRelativeTimeTick(); const lastCheckedRelative = getRelativeTimeState(lastCheckedAt); if (lastCheckedRelative.status === "missing") { return null; } if (lastCheckedRelative.status === "invalid") { return Checked unavailable; } return ( {lastCheckedRelative.suffix ? ( <> Checked {lastCheckedRelative.value}{" "} {lastCheckedRelative.suffix} ) : ( <>Checked {lastCheckedRelative.value} )} ); } function providerEnvironmentIcon(environment: EnvironmentPresentation) { if (environment.entry.target._tag === "PrimaryConnectionTarget") return MonitorIcon; if (environment.entry.target._tag === "RelayConnectionTarget") return CloudIcon; if (environment.entry.target._tag === "SshConnectionTarget") return TerminalIcon; if (isDesktopLocalConnectionTarget(environment.entry.target)) return LaptopIcon; return CloudIcon; } function providerEnvironmentDetail(environment: EnvironmentPresentation): string { if (environment.entry.target._tag === "PrimaryConnectionTarget") return "Primary device"; if (environment.relayManaged) return "T3 Connect"; if (environment.entry.target._tag === "SshConnectionTarget") return "SSH"; if (isDesktopLocalConnectionTarget(environment.entry.target)) return "Local device"; return environment.displayUrl ?? "Remote device"; } function EnvironmentUnavailableRow({ environment, access, deviceTabs, }: { readonly environment: EnvironmentPresentation; readonly access: Exclude; readonly deviceTabs?: ReactNode; }) { const isLoading = access.kind === "loading"; const title = isLoading ? "Loading provider settings" : access.kind === "error" ? "Could not connect to this device" : "Provider settings are unavailable"; const description = isLoading ? access.reason === "permissions" ? "Checking what this session is allowed to change." : `Waiting for ${environment.label}'s configuration.` : connectionStatusText(environment.connection); // No spinner: this state can persist indefinitely for a wedged device, and a // continuously repainting animation would run the whole time. return ( {deviceTabs} ); } export function ProviderSettingsPanel() { const { environments, isReady } = useEnvironments(); const primaryEnvironmentId = usePrimaryEnvironmentId(); const options = useMemo( () => buildProviderEnvironmentOptions(environments, primaryEnvironmentId), [environments, primaryEnvironmentId], ); // Raw user intent; the effective selection is re-derived every render so a // device that drops out of the catalog falls back without erasing the pick — // if it reappears (e.g. after a reconnect) the selection is restored. const [selectedEnvironmentId, setSelectedEnvironmentId] = useState( primaryEnvironmentId, ); const effectiveEnvironmentId = resolveSelectedProviderEnvironmentId( options, selectedEnvironmentId, primaryEnvironmentId, ); const selectedEnvironment = options.find((environment) => environment.environmentId === effectiveEnvironmentId) ?? null; const onlyPrimaryDevice = options.length === 1 && options[0]?.entry.target._tag === "PrimaryConnectionTarget"; const deviceTabs = !onlyPrimaryDevice && options.length > 0 ? (
{options.map((environment) => { const Icon = providerEnvironmentIcon(environment); const selected = environment.environmentId === effectiveEnvironmentId; const detail = providerEnvironmentDetail(environment); const statusText = connectionStatusText(environment.connection); return ( setSelectedEnvironmentId(environment.environmentId)} > {environment.label} {environment.connection.phase !== "connected" ? ( ) : null} {detail}, {statusText} } /> {detail} · {statusText} ); })}
) : null; return ( {options.length === 0 ? ( ) : null} {selectedEnvironment ? ( ) : null} ); } function SelectedEnvironmentProviderSettings({ environment, deviceTabs, }: { readonly environment: EnvironmentPresentation; readonly deviceTabs?: ReactNode; }) { const isPrimary = environment.entry.target._tag === "PrimaryConnectionTarget"; if (isPrimary) { // The desktop app owns its primary server outright; a browser session // checks the scopes its cookie session was granted. if (isElectron) { return ( ); } return ( ); } return ; } function PrimarySessionGatedProviderSettings({ environment, deviceTabs, }: { readonly environment: EnvironmentPresentation; readonly deviceTabs?: ReactNode; }) { const primarySessionState = usePrimarySessionState(); const operateAccess = resolvePrimaryOperateAccess({ isPrimary: true, hasDesktopBridge: false, session: primarySessionState.data, isPending: primarySessionState.isPending, hasError: primarySessionState.error !== null, }); return ( ); } function RemoteSessionGatedProviderSettings({ environment, deviceTabs, }: { readonly environment: EnvironmentPresentation; readonly deviceTabs?: ReactNode; }) { const sessionState = useEnvironmentSessionState(environment.environmentId); const operateAccess = resolveRemoteOperateAccess({ session: sessionState.data, isPending: sessionState.isPending, hasError: sessionState.hasError, }); return ( ); } function AccessGatedProviderSettings({ environment, operateAccess, deviceTabs, }: { readonly environment: EnvironmentPresentation; readonly operateAccess: ProviderOperateAccess; readonly deviceTabs?: ReactNode; }) { const access = classifyProviderEnvironmentAccess({ connectionPhase: environment.connection.phase, hasServerConfig: environment.serverConfig !== null, operateAccess, }); if (access.kind !== "editable" && access.kind !== "read-only") { return ( ); } return ( ); } export function EnvironmentProviderSettings({ environmentId, environmentLabel, readOnly = false, deviceTabs, }: { readonly environmentId: EnvironmentId; readonly environmentLabel: string; readonly deviceTabs?: ReactNode; /** * Grey out and freeze every write control when this session's credential * lacks `orchestration:operate` on the environment. Selecting providers and * opening Advanced still work so the real configuration stays readable; * switches, forms, and the health interval are inert so no write is * offered and then rejected. */ readonly readOnly?: boolean; }) { const settings = useEnvironmentSettings(environmentId); const updateSettings = useUpdateEnvironmentSettings(environmentId); const serverProviders = useAtomValue(serverEnvironment.providersValueAtom(environmentId)) ?? EMPTY_SERVER_PROVIDERS; const refreshServerProviders = useAtomCommand(serverEnvironment.refreshProviders, { reportFailure: false, }); const updateProvider = useAtomCommand(serverEnvironment.updateProvider, { reportFailure: false, }); const [isRefreshingProviders, setIsRefreshingProviders] = useState(false); const [isAddInstanceDialogOpen, setIsAddInstanceDialogOpen] = useState(false); const [selectedInstanceId, setSelectedInstanceId] = useState(null); const [advancedOpen, setAdvancedOpen] = useState(false); const [updatingProviderDrivers, setUpdatingProviderDrivers] = useState< ReadonlySet >(() => new Set()); const refreshingRef = useRef(false); const updatingDriversRef = useRef>(new Set()); const providerUpdateCandidates = useMemo( () => collectProviderUpdateCandidates(serverProviders), [serverProviders], ); const providerUpdateCandidateByInstanceId = useMemo( () => new Map(providerUpdateCandidates.map((candidate) => [candidate.instanceId, candidate])), [providerUpdateCandidates], ); const visibleProviderSettings = PROVIDER_SETTINGS.filter( (providerSettings) => providerSettings.provider !== "cursor" || serverProviders.some( (provider) => provider.instanceId === defaultInstanceIdForDriver(ProviderDriverKind.make("cursor")), ), ); const textGenerationModelSelection = resolveAppModelSelectionState(settings, serverProviders); const textGenInstanceId = textGenerationModelSelection.instanceId; const resolvedBackgroundActivity = resolveServerBackgroundActivitySettings(settings); const providerHealthPreset = getBackgroundActivityPresetSettings( resolvedBackgroundActivity.profile, ).providerHealthRefreshInterval; const providerHealthRefreshIntervalSeconds = durationToSeconds( resolvedBackgroundActivity.providerHealthRefreshInterval, ); const defaultProviderHealthRefreshIntervalSeconds = durationToSeconds(providerHealthPreset); const lastCheckedAt = serverProviders.length > 0 ? serverProviders.reduce( (latest, provider) => (provider.checkedAt > latest ? provider.checkedAt : latest), serverProviders[0]!.checkedAt, ) : null; const refreshProviders = useCallback(() => { if (refreshingRef.current) return; refreshingRef.current = true; setIsRefreshingProviders(true); void (async () => { const result = await refreshServerProviders({ environmentId, input: {}, }); refreshingRef.current = false; setIsRefreshingProviders(false); if (result._tag === "Failure" && !isAtomCommandInterrupted(result)) { console.warn("Failed to refresh providers", { operation: "refresh-providers", environmentId, ...safeErrorLogAttributes(squashAtomCommandFailure(result)), }); } })(); }, [environmentId, refreshServerProviders]); const runProviderUpdate = useCallback( async (candidate: ProviderUpdateCandidate) => { // Ref-based re-entry guard, mirroring refreshProviders: a state updater // may run after this function returns, so it cannot gate the dispatch. if (updatingDriversRef.current.has(candidate.driver)) { return; } updatingDriversRef.current.add(candidate.driver); setUpdatingProviderDrivers((previous) => new Set(previous).add(candidate.driver)); const result = await updateProvider({ environmentId, input: { provider: candidate.driver, instanceId: candidate.instanceId, }, }); if (result._tag === "Failure" && !isAtomCommandInterrupted(result)) { const error = squashAtomCommandFailure(result); toastManager.add( stackedThreadToast({ type: "error", title: `Could not update ${PROVIDER_DISPLAY_NAMES[candidate.driver] ?? candidate.driver}`, description: error instanceof Error ? error.message : "The provider update command could not be started.", }), ); } updatingDriversRef.current.delete(candidate.driver); setUpdatingProviderDrivers((previous) => { if (!previous.has(candidate.driver)) { return previous; } const next = new Set(previous); next.delete(candidate.driver); return next; }); }, [environmentId, updateProvider], ); interface InstanceRow { readonly instanceId: ProviderInstanceId; readonly instance: ProviderInstanceConfig; readonly driver: ProviderDriverKind; readonly isDefault: boolean; readonly isDirty?: boolean; } const instancesByDriver = new Map< ProviderDriverKind, Array<[ProviderInstanceId, ProviderInstanceConfig]> >(); for (const [rawId, instance] of Object.entries(settings.providerInstances ?? {})) { const driver = instance.driver; const list = instancesByDriver.get(driver) ?? []; list.push([rawId as ProviderInstanceId, instance]); instancesByDriver.set(driver, list); } const defaultSlotIdsBySource = new Set( visibleProviderSettings.map((providerSettings) => String(defaultInstanceIdForDriver(providerSettings.provider)), ), ); const rows: InstanceRow[] = []; const visibleDriverKinds = new Set( visibleProviderSettings.map((providerSettings) => providerSettings.provider), ); for (const providerSettings of visibleProviderSettings) { type LegacyProviderSettings = (typeof settings.providers)[keyof typeof settings.providers]; const legacyProviders = settings.providers as Record; const defaultLegacyProviders = DEFAULT_UNIFIED_SETTINGS.providers as Record< string, LegacyProviderSettings >; const driver = providerSettings.provider; const defaultInstanceId = defaultInstanceIdForDriver(driver); const explicitInstance = settings.providerInstances?.[defaultInstanceId]; // A remote device may run a server version whose settings predate this // driver, so the legacy mirror can be absent. Without either an explicit // instance or a legacy blob there is nothing to render for the slot. const legacyConfig = legacyProviders[providerSettings.provider]; const defaultLegacyConfig = defaultLegacyProviders[providerSettings.provider]; // The envelope is the single enabled flag: keep the legacy in-config // flag out of the synthesized blob, or an explicit `enabled: false` // would keep winning over the envelope and the Switch could never // turn a default-off provider on. const synthesizedInstance = (): ProviderInstanceConfig | undefined => { if (legacyConfig === undefined) { return undefined; } const { enabled: legacyEnabled, ...legacyConfigRest } = legacyConfig; return { driver, enabled: legacyEnabled, config: legacyConfigRest, } satisfies ProviderInstanceConfig; }; const effectiveInstance: ProviderInstanceConfig | undefined = explicitInstance ?? synthesizedInstance(); // Only the default slot depends on the legacy blob; custom instances for // the driver must still render even when the slot has nothing to show. if (effectiveInstance !== undefined) { const isDirty = explicitInstance !== undefined || !Equal.equals(legacyConfig, defaultLegacyConfig); rows.push({ instanceId: defaultInstanceId, instance: effectiveInstance, driver, isDefault: true, isDirty, }); } for (const [id, instance] of instancesByDriver.get(providerSettings.provider) ?? []) { if (id === defaultInstanceId) continue; rows.push({ instanceId: id, instance, driver: instance.driver, isDefault: false }); } } for (const [driver, list] of instancesByDriver) { if (visibleDriverKinds.has(driver)) continue; for (const [id, instance] of list) { rows.push({ instanceId: id, instance, driver: instance.driver, isDefault: defaultSlotIdsBySource.has(String(id)), }); } } const selectedRow = rows.find((row) => row.instanceId === selectedInstanceId) ?? rows[0] ?? null; const updateProviderInstance = ( row: InstanceRow, next: ProviderInstanceConfig, options?: { readonly textGenerationModelSelection?: Parameters< typeof buildProviderInstanceUpdatePatch >[0]["textGenerationModelSelection"]; }, ) => { updateSettings( buildProviderInstanceUpdatePatch({ settings, instanceId: row.instanceId, instance: next, driver: row.driver, isDefault: row.isDefault, textGenerationModelSelection: options?.textGenerationModelSelection, }), ); }; const deleteProviderInstance = (id: ProviderInstanceId) => { updateSettings({ providerInstances: withoutProviderInstanceKey(settings.providerInstances, id), }); }; const updateProviderModelPreferences = ( instanceId: ProviderInstanceId, next: { readonly hiddenModels: ReadonlyArray; readonly modelOrder: ReadonlyArray; }, ) => { const hiddenModels = [...new Set(next.hiddenModels.filter((slug) => slug.trim().length > 0))]; const modelOrder = [...new Set(next.modelOrder.filter((slug) => slug.trim().length > 0))]; const rest = withoutProviderInstanceKey(settings.providerModelPreferences, instanceId); updateSettings({ providerModelPreferences: hiddenModels.length === 0 && modelOrder.length === 0 ? rest : { ...rest, [instanceId]: { hiddenModels, modelOrder, }, }, }); }; const updateProviderFavoriteModels = ( instanceId: ProviderInstanceId, nextFavoriteModels: ReadonlyArray, ) => { const favoriteModels = [ ...new Set( Arr.filterMap(nextFavoriteModels, (slug) => { const trimmedSlug = slug.trim(); return trimmedSlug.length > 0 ? Result.succeed(trimmedSlug) : Result.failVoid; }), ), ]; updateSettings({ favorites: [ ...withoutProviderInstanceFavorites(settings.favorites ?? [], instanceId), ...favoriteModels.map((model) => ({ provider: instanceId, model })), ], }); }; const resetDefaultInstance = (driverKind: ProviderDriverKind) => { type LegacyProviderSettings = (typeof settings.providers)[keyof typeof settings.providers]; const defaultLegacyProviders = DEFAULT_UNIFIED_SETTINGS.providers as Record< string, LegacyProviderSettings | undefined >; const defaultInstanceId = defaultInstanceIdForDriver(driverKind); const defaultLegacyProvider = defaultLegacyProviders[driverKind]; if (defaultLegacyProvider === undefined) return; updateSettings({ providers: { ...settings.providers, [driverKind]: defaultLegacyProvider, } as typeof settings.providers, providerInstances: withoutProviderInstanceKey(settings.providerInstances, defaultInstanceId), }); }; const renderProviderInstance = (row: InstanceRow, mode: "list" | "editor") => { const driverOption = getDriverOption(row.driver); const liveProvider = serverProviders.find( (candidate) => candidate.instanceId === row.instanceId, ); const updateCandidate = liveProvider ? providerUpdateCandidateByInstanceId.get(liveProvider.instanceId) : undefined; const isDriverUpdateRunning = updateCandidate !== undefined && (updatingProviderDrivers.has(updateCandidate.driver) || serverProviders.some( (provider) => provider.driver === updateCandidate.driver && isProviderUpdateActive(provider), )); const showInlineUpdateButton = updateCandidate !== undefined && hasOneClickUpdateProviderCandidate(updateCandidate, serverProviders); const canRunInlineUpdate = updateCandidate !== undefined && canOneClickUpdateProviderCandidate(updateCandidate, serverProviders) && !updatingProviderDrivers.has(updateCandidate.driver); const modelPreferences = settings.providerModelPreferences?.[row.instanceId] ?? { hiddenModels: [], modelOrder: [], }; const favoriteModels = Arr.filterMap(settings.favorites ?? [], (favorite) => favorite.provider === row.instanceId ? Result.succeed(favorite.model) : Result.failVoid, ); const resetLabel = driverOption?.label ?? String(row.driver); return ( setSelectedInstanceId(row.instanceId) : undefined} readOnly={readOnly} onUpdate={(next) => { const wasEnabled = resolveProviderInstanceEnabled(row.instance); const isDisabling = next.enabled === false && wasEnabled; const shouldClearTextGen = isDisabling && textGenInstanceId === row.instanceId; updateProviderInstance( row, next, shouldClearTextGen ? { textGenerationModelSelection: DEFAULT_UNIFIED_SETTINGS.textGenerationModelSelection, } : undefined, ); }} onDelete={ mode === "editor" && !row.isDefault ? () => deleteProviderInstance(row.instanceId) : undefined } headerAction={ mode === "editor" && row.isDefault && row.isDirty ? ( resetDefaultInstance(row.driver)} /> ) : null } hiddenModels={modelPreferences.hiddenModels} favoriteModels={favoriteModels} modelOrder={modelPreferences.modelOrder} onHiddenModelsChange={(hiddenModels) => updateProviderModelPreferences(row.instanceId, { ...modelPreferences, hiddenModels, }) } onFavoriteModelsChange={(next) => updateProviderFavoriteModels(row.instanceId, next)} onModelOrderChange={(modelOrder) => updateProviderModelPreferences(row.instanceId, { ...modelPreferences, modelOrder, }) } onRunUpdate={ mode === "editor" && showInlineUpdateButton && updateCandidate ? () => { if (canRunInlineUpdate) void runProviderUpdate(updateCandidate); } : undefined } isUpdating={mode === "editor" && showInlineUpdateButton ? isDriverUpdateRunning : undefined} /> ); }; return ( <> {/* The 11px size must sit on this flex item, not just the span inside: the item's line box is struck from its own font size, and an inherited 16px strut hangs the smaller text below the vertical center of the row. */} {!readOnly ? ( <> void refreshProviders()} aria-label="Refresh provider status" > {isRefreshingProviders ? ( ) : ( )} } /> Refresh provider status setIsAddInstanceDialogOpen(true)} aria-label="Add provider" > } /> Add provider ) : null} } > {deviceTabs} {readOnly ? ( ) : null}
{rows.map((row) => (
{renderProviderInstance(row, "list")}
))}
{selectedRow ? ( renderProviderInstance(selectedRow, "editor") ) : (
No providers configured.
)}
Advanced
Health check interval This interval is configured here, then the shared Background activity policy decides whether provider probes may run when the timer fires. Custom intervals appear as Advanced in General settings. } description="Refresh availability, versions, auth state, and models in the background. 0 seconds turns background checks off." resetAction={ providerHealthRefreshIntervalSeconds !== defaultProviderHealthRefreshIntervalSeconds ? ( updateSettings( backgroundActivityOverrideSettings( settings.backgroundActivity, resolvedBackgroundActivity, { providerHealthRefreshInterval: undefined }, ), ) } /> ) : null } control={
updateSettings( backgroundActivityOverrideSettings( settings.backgroundActivity, resolvedBackgroundActivity, { providerHealthRefreshInterval: Duration.seconds( normalizeIntervalSeconds(value), ), }, ), ) } > seconds
} />
{isAddInstanceDialogOpen ? ( ) : null} ); }