import { memo, useCallback, useMemo, useRef, useState, type ReactElement, type RefObject, } from "react"; import { View, Text, Pressable, Keyboard, type PressableStateCallbackType, type StyleProp, type ViewStyle, } from "react-native"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { useShallow } from "zustand/shallow"; import { useStoreWithEqualityFn } from "zustand/traditional"; import { Brain, ChevronDown, ListTodo, Settings2, ShieldAlert, ShieldCheck, ShieldOff, ShieldQuestionMark, Zap, } from "lucide-react-native"; import { getProviderIcon } from "@/components/provider-icons"; import { CombinedModelSelector } from "@/components/combined-model-selector"; import { useSessionStore } from "@/stores/session-store"; import { useProvidersSnapshot } from "@/hooks/use-providers-snapshot"; import { resolveProviderDefinition } from "@/utils/provider-definitions"; import { buildFavoriteModelKey, mergeProviderPreferences, toggleFavoriteModel, useFormPreferences, } from "@/hooks/use-form-preferences"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox"; import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import type { AgentFeature, AgentMode, AgentModelDefinition, AgentProvider, } from "@server/server/agent/agent-sdk-types"; import type { AgentProviderDefinition } from "@server/server/agent/provider-manifest"; import { getModeVisuals, type AgentModeColorTier } from "@server/server/agent/provider-manifest"; import { getFeatureHighlightColor, getFeatureTooltip, getStatusSelectorHint, resolveAgentModelSelection, } from "@/components/agent-status-bar.utils"; import { isWeb as platformIsWeb } from "@/constants/platform"; import { useToast } from "@/contexts/toast-context"; import { toErrorMessage } from "@/utils/error-messages"; interface StatusOption { id: string; label: string; } type StatusSelector = "provider" | "mode" | "model" | "thinking" | `feature-${string}`; interface ControlledAgentStatusBarProps { provider: string; providerOptions?: StatusOption[]; selectedProviderId?: string; onSelectProvider?: (providerId: string) => void; modeOptions?: StatusOption[]; selectedModeId?: string; onSelectMode?: (modeId: string) => void; modelOptions?: StatusOption[]; selectedModelId?: string; onSelectModel?: (modelId: string) => void; onSelectProviderAndModel?: (provider: string, modelId: string) => void; thinkingOptions?: StatusOption[]; selectedThinkingOptionId?: string; onSelectThinkingOption?: (thinkingOptionId: string) => void; disabled?: boolean; isModelLoading?: boolean; providerDefinitions: AgentProviderDefinition[]; allProviderModels?: Map; canSelectModelProvider?: (providerId: string) => boolean; favoriteKeys?: Set; onToggleFavoriteModel?: (provider: string, modelId: string) => void; features?: AgentFeature[]; onSetFeature?: (featureId: string, value: unknown) => void; onDropdownClose?: () => void; onModelSelectorOpen?: () => void; } export interface DraftAgentStatusBarProps { providerDefinitions: AgentProviderDefinition[]; selectedProvider: AgentProvider | null; onSelectProvider: (provider: AgentProvider) => void; modeOptions: AgentMode[]; selectedMode: string; onSelectMode: (modeId: string) => void; models: AgentModelDefinition[]; selectedModel: string; onSelectModel: (modelId: string) => void; isModelLoading: boolean; allProviderModels: Map; isAllModelsLoading: boolean; onSelectProviderAndModel: (provider: AgentProvider, modelId: string) => void; thinkingOptions: NonNullable; selectedThinkingOptionId: string; onSelectThinkingOption: (thinkingOptionId: string) => void; features?: AgentFeature[]; onSetFeature?: (featureId: string, value: unknown) => void; onDropdownClose?: () => void; onModelSelectorOpen?: () => void; disabled?: boolean; } interface AgentStatusBarProps { agentId: string; serverId: string; onDropdownClose?: () => void; } function findOptionLabel( options: StatusOption[] | undefined, selectedId: string | undefined, fallback: string, ) { if (!options || options.length === 0) { return fallback; } const selected = options.find((option) => option.id === selectedId); return selected?.label ?? fallback; } const FEATURE_ICONS: Record = { "list-todo": ListTodo, zap: Zap, }; function getFeatureIcon(icon?: string) { return (icon && FEATURE_ICONS[icon]) || Settings2; } function getFeatureIconColor( featureId: string, enabled: boolean, palette: { blue: { 400: string }; yellow: { 400: string }; }, foregroundMuted: string, ): string { if (!enabled) { return foregroundMuted; } switch (getFeatureHighlightColor(featureId)) { case "blue": return palette.blue[400]; case "yellow": return palette.yellow[400]; default: return foregroundMuted; } } const MODE_ICONS = { ShieldCheck, ShieldAlert, ShieldOff, ShieldQuestionMark, } as const; function alwaysTrue() { return true; } function resolveDisplayModel( isModelLoading: boolean, modelOptions: StatusOption[] | undefined, selectedModelId: string | undefined, ) { if (isModelLoading && (!modelOptions || modelOptions.length === 0)) { return "Loading models..."; } return findOptionLabel(modelOptions, selectedModelId, "Select model"); } function resolveHasAnyControl({ providerOptions, modeOptions, canSelectModel, thinkingOptions, features, }: { providerOptions: StatusOption[] | undefined; modeOptions: StatusOption[] | undefined; canSelectModel: boolean; thinkingOptions: StatusOption[] | undefined; features: AgentFeature[] | undefined; }) { return ( Boolean(providerOptions?.length) || Boolean(modeOptions?.length) || canSelectModel || Boolean(thinkingOptions?.length) || Boolean(features?.length) ); } function toComboboxOptions(options: StatusOption[] | undefined): ComboboxOption[] { return (options ?? []).map((o) => ({ id: o.id, label: o.label })); } function buildFallbackAllProviderModels( provider: string, modelOptions: StatusOption[] | undefined, ): Map { const map = new Map(); if (!modelOptions || modelOptions.length === 0) { return map; } map.set( provider, modelOptions.map((option) => ({ provider: provider, id: option.id, label: option.label, })), ); return map; } function makeBadgePressableStyle( baseStyle: StyleProp, disabledStyle: StyleProp, disabled: boolean, isOpen: boolean, ) { return ({ pressed, hovered }: PressableStateCallbackType) => [ baseStyle, hovered && styles.modeBadgeHovered, (pressed || isOpen) && styles.modeBadgePressed, disabled && disabledStyle, ]; } function makeSheetPressableStyle(isDisabled: boolean) { return ({ pressed }: PressableStateCallbackType) => [ styles.sheetSelect, pressed && styles.sheetSelectPressed, isDisabled && styles.disabledSheetSelect, ]; } function makePrefsButtonStyle({ pressed }: PressableStateCallbackType) { return [styles.prefsButton, pressed && styles.prefsButtonPressed]; } function pickSheetModel({ nextProviderId, modelId, currentProvider, onSelectProviderAndModel, onSelectProvider, onSelectModel, }: { nextProviderId: string; modelId: string; currentProvider: string; onSelectProviderAndModel?: (provider: string, modelId: string) => void; onSelectProvider?: (providerId: string) => void; onSelectModel?: (modelId: string) => void; }) { if (onSelectProviderAndModel) { onSelectProviderAndModel(nextProviderId, modelId); return; } if (nextProviderId !== currentProvider) { onSelectProvider?.(nextProviderId); } onSelectModel?.(modelId); } function pickDesktopModel({ nextProviderId, modelId, currentProvider, onSelectModel, }: { nextProviderId: string; modelId: string; currentProvider: string; onSelectModel?: (modelId: string) => void; }) { if (nextProviderId === currentProvider) { onSelectModel?.(modelId); } } function resolveModeVisualsForProvider( provider: string, selectedModeId: string | undefined, providerDefinitions: AgentProviderDefinition[], palette: Parameters[1], ) { const modeVisuals = selectedModeId ? getModeVisuals(provider, selectedModeId, providerDefinitions) : undefined; const icon = modeVisuals?.icon ? MODE_ICONS[modeVisuals.icon] : null; const color = getModeIconColor(modeVisuals?.colorTier, palette); return { icon, color }; } function resolveProviderIcon(provider: string) { if (provider.trim().length === 0) { return null; } return getProviderIcon(provider); } type AgentStatusBarSlice = { provider: string; cwd: string | null; currentModeId: string | null | undefined; runtimeModelId: string | null; model: string | null | undefined; features: AgentFeature[] | undefined; thinkingOptionId: string | null | undefined; lastUsage: unknown; } | null; function selectAgentStatusBarSlice( state: ReturnType, serverId: string, agentId: string, ): AgentStatusBarSlice { const currentAgent = state.sessions[serverId]?.agents?.get(agentId) ?? null; if (!currentAgent) { return null; } return { provider: currentAgent.provider, cwd: currentAgent.cwd, currentModeId: currentAgent.currentModeId, runtimeModelId: currentAgent.runtimeInfo?.model ?? null, model: currentAgent.model, features: currentAgent.features, thinkingOptionId: currentAgent.thinkingOptionId, lastUsage: currentAgent.lastUsage, }; } function resolveSnapshotSelectedEntry( snapshotEntries: ReturnType["entries"], agentProvider: string | undefined, ) { if (!snapshotEntries || !agentProvider) { return null; } return snapshotEntries.find((e) => e.provider === agentProvider) ?? null; } function buildAgentProviderDefinitions( agentProvider: string | undefined, snapshotEntries: ReturnType["entries"], ): AgentProviderDefinition[] { const definition = agentProvider ? resolveProviderDefinition(agentProvider, snapshotEntries) : undefined; return definition ? [definition] : []; } function buildAgentProviderModels( agentProvider: string | undefined, models: AgentModelDefinition[] | null, ): Map { const map = new Map(); if (agentProvider && models) { map.set(agentProvider, models); } return map; } function compareAvailableModes(a: AgentMode[], b: AgentMode[]): boolean { return a === b || JSON.stringify(a) === JSON.stringify(b); } function resolveAgentDisplayMode( availableModes: AgentMode[], currentModeId: string | null | undefined, ): string { const found = availableModes.find((mode) => mode.id === currentModeId); return found?.label || currentModeId || "default"; } function buildOpenChangeHandler( selector: StatusSelector, setOpenSelector: (next: StatusSelector | null) => void, onDropdownClose?: () => void, ) { return (nextOpen: boolean) => { setOpenSelector(nextOpen ? selector : null); if (!nextOpen) { onDropdownClose?.(); } }; } function SheetModelTriggerView({ selectedModelLabel, style, ProviderIcon, }: { selectedModelLabel: string; style: StyleProp; ProviderIcon: ReturnType | null; }) { const { theme } = useUnistyles(); return ( {ProviderIcon ? ( ) : null} {selectedModelLabel} ); } function getModeIconColor( colorTier: AgentModeColorTier | undefined, palette: { blue: { 500: string }; green: { 500: string }; red: { 500: string }; purple: { 500: string }; }, ): string { switch (colorTier) { case "safe": return palette.green[500]; case "moderate": return palette.blue[500]; case "dangerous": return palette.red[500]; case "planning": return palette.purple[500]; default: return palette.blue[500]; } } function ControlledStatusBar({ provider, providerOptions, selectedProviderId, onSelectProvider, modeOptions, selectedModeId, onSelectMode, modelOptions, selectedModelId, onSelectModel, onSelectProviderAndModel, thinkingOptions, selectedThinkingOptionId, onSelectThinkingOption, disabled = false, isModelLoading = false, providerDefinitions, allProviderModels, canSelectModelProvider, favoriteKeys = new Set(), onToggleFavoriteModel, features, onSetFeature, onDropdownClose, onModelSelectorOpen, }: ControlledAgentStatusBarProps) { const { theme } = useUnistyles(); const [prefsOpen, setPrefsOpen] = useState(false); const [openSelector, setOpenSelector] = useState(null); const providerAnchorRef = useRef(null); const modeAnchorRef = useRef(null); const _modelAnchorRef = useRef(null); const thinkingAnchorRef = useRef(null); const canSelectProvider = Boolean( onSelectProvider && providerOptions && providerOptions.length > 0, ); const canSelectMode = Boolean(onSelectMode && modeOptions && modeOptions.length > 0); const canSelectModel = Boolean(onSelectModel); const canSelectThinking = Boolean( onSelectThinkingOption && thinkingOptions && thinkingOptions.length > 0, ); const displayProvider = findOptionLabel(providerOptions, selectedProviderId, "Provider"); const displayMode = findOptionLabel(modeOptions, selectedModeId, "Default"); const displayModel = resolveDisplayModel(isModelLoading, modelOptions, selectedModelId); const displayThinking = findOptionLabel( thinkingOptions, selectedThinkingOptionId, thinkingOptions?.[0]?.label ?? "Unknown", ); const { icon: ModeIconComponent, color: modeIconColor } = resolveModeVisualsForProvider( provider, selectedModeId, providerDefinitions, theme.colors.palette, ); const ProviderIcon = resolveProviderIcon(provider); const hasAnyControl = resolveHasAnyControl({ providerOptions, modeOptions, canSelectModel, thinkingOptions, features, }); const modelDisabled = disabled; const comboboxProviderOptions = useMemo( () => toComboboxOptions(providerOptions), [providerOptions], ); const comboboxModeOptions = useMemo( () => toComboboxOptions(modeOptions), [modeOptions], ); const fallbackAllProviderModels = useMemo( () => buildFallbackAllProviderModels(provider, modelOptions), [modelOptions, provider], ); const effectiveProviderDefinitions = providerDefinitions; const effectiveAllProviderModels = allProviderModels ?? fallbackAllProviderModels; const canSelectProviderInModelMenu = canSelectModelProvider ?? alwaysTrue; const comboboxThinkingOptions = useMemo( () => toComboboxOptions(thinkingOptions), [thinkingOptions], ); const renderModeOption = useCallback( (args: { option: ComboboxOption; selected: boolean; active: boolean; onPress: () => void }) => ( ), [provider, providerDefinitions, theme.colors.foreground], ); const handleOpenChange = useCallback( (selector: StatusSelector) => buildOpenChangeHandler(selector, setOpenSelector, onDropdownClose), [onDropdownClose], ); const handleProviderPress = useCallback(() => { handleOpenChange("provider")(openSelector !== "provider"); }, [handleOpenChange, openSelector]); const handleThinkingPress = useCallback(() => { handleOpenChange("thinking")(openSelector !== "thinking"); }, [handleOpenChange, openSelector]); const handleModePress = useCallback(() => { handleOpenChange("mode")(openSelector !== "mode"); }, [handleOpenChange, openSelector]); const handleProviderOpenChange = useMemo(() => handleOpenChange("provider"), [handleOpenChange]); const handleThinkingOpenChange = useMemo(() => handleOpenChange("thinking"), [handleOpenChange]); const handleModeOpenChange = useMemo(() => handleOpenChange("mode"), [handleOpenChange]); const handleProviderSelect = useCallback( (id: string) => onSelectProvider?.(id), [onSelectProvider], ); const handleThinkingSelect = useCallback( (id: string) => onSelectThinkingOption?.(id), [onSelectThinkingOption], ); const handleModeSelect = useCallback((id: string) => onSelectMode?.(id), [onSelectMode]); const handleDesktopModelSelect = useCallback( (nextProviderId: string, modelId: string) => { pickDesktopModel({ nextProviderId, modelId, currentProvider: provider, onSelectModel }); }, [onSelectModel, provider], ); const providerPressableStyle = useMemo( () => makeBadgePressableStyle( styles.modeBadge, styles.disabledBadge, disabled || !canSelectProvider, openSelector === "provider", ), [canSelectProvider, disabled, openSelector], ); const thinkingPressableStyle = useMemo( () => makeBadgePressableStyle( styles.modeBadge, styles.disabledBadge, disabled || !canSelectThinking, openSelector === "thinking", ), [canSelectThinking, disabled, openSelector], ); const modePressableStyle = useMemo( () => makeBadgePressableStyle( styles.modeIconBadge, styles.disabledBadge, disabled || !canSelectMode, openSelector === "mode", ), [canSelectMode, disabled, openSelector], ); const handleOpenPrefs = useCallback(() => { Keyboard.dismiss(); setPrefsOpen(true); }, []); const handleClosePrefs = useCallback(() => { setPrefsOpen(false); }, []); const prefsButtonStyle = makePrefsButtonStyle; const handleSheetModelSelect = useCallback( (nextProviderId: string, modelId: string) => { pickSheetModel({ nextProviderId, modelId, currentProvider: provider, onSelectProviderAndModel, onSelectProvider, onSelectModel, }); }, [onSelectModel, onSelectProvider, onSelectProviderAndModel, provider], ); const sheetThinkingPressableStyle = useMemo( () => makeSheetPressableStyle(disabled || !canSelectThinking), [canSelectThinking, disabled], ); const sheetModePressableStyle = useMemo( () => makeSheetPressableStyle(disabled || !canSelectMode), [canSelectMode, disabled], ); const sheetSelectStyle = useMemo( () => [styles.sheetSelect, modelDisabled && styles.disabledSheetSelect], [modelDisabled], ); const renderSheetModelTrigger = useCallback( ({ selectedModelLabel }: { selectedModelLabel: string }) => ( ), [ProviderIcon, sheetSelectStyle], ); if (!hasAnyControl) { return null; } return ( {platformIsWeb ? ( ) : ( )} ); } interface DesktopStatusBarContentProps { provider: string; providerOptions?: StatusOption[]; selectedProviderId?: string; modeOptions?: StatusOption[]; selectedModeId?: string; modelOptions?: StatusOption[]; selectedModelId?: string; thinkingOptions?: StatusOption[]; selectedThinkingOptionId?: string; features?: AgentFeature[]; onSetFeature?: (featureId: string, value: unknown) => void; onToggleFavoriteModel?: (provider: string, modelId: string) => void; onDropdownClose?: () => void; onModelSelectorOpen?: () => void; providerDefinitions: AgentProviderDefinition[]; favoriteKeys: Set; disabled: boolean; isModelLoading: boolean; canSelectProvider: boolean; canSelectMode: boolean; canSelectModel: boolean; canSelectThinking: boolean; canSelectProviderInModelMenu: (providerId: string) => boolean; modelDisabled: boolean; effectiveProviderDefinitions: AgentProviderDefinition[]; effectiveAllProviderModels: Map; comboboxProviderOptions: ComboboxOption[]; comboboxModeOptions: ComboboxOption[]; comboboxThinkingOptions: ComboboxOption[]; displayProvider: string; displayModel: string; displayThinking: string; ModeIconComponent: (typeof MODE_ICONS)[keyof typeof MODE_ICONS] | null; modeIconColor: string; openSelector: StatusSelector | null; providerAnchorRef: RefObject; thinkingAnchorRef: RefObject; modeAnchorRef: RefObject; providerPressableStyle: (state: PressableStateCallbackType) => StyleProp; thinkingPressableStyle: (state: PressableStateCallbackType) => StyleProp; modePressableStyle: (state: PressableStateCallbackType) => StyleProp; handleProviderPress: () => void; handleThinkingPress: () => void; handleModePress: () => void; handleProviderSelect: (id: string) => void; handleThinkingSelect: (id: string) => void; handleModeSelect: (id: string) => void; handleDesktopModelSelect: (providerId: string, modelId: string) => void; handleProviderOpenChange: (open: boolean) => void; handleThinkingOpenChange: (open: boolean) => void; handleModeOpenChange: (open: boolean) => void; handleOpenChange: (selector: StatusSelector) => (nextOpen: boolean) => void; renderModeOption: (args: { option: ComboboxOption; selected: boolean; active: boolean; onPress: () => void; }) => ReactElement; } const DESKTOP_SEARCH_THRESHOLD = 6; function DesktopStatusBarContent(props: DesktopStatusBarContentProps) { const { theme } = useUnistyles(); const { provider, providerOptions, selectedProviderId, modeOptions, selectedModeId, selectedModelId, thinkingOptions, selectedThinkingOptionId, features, onSetFeature, onToggleFavoriteModel, onDropdownClose, onModelSelectorOpen, favoriteKeys, disabled, isModelLoading, canSelectProvider, canSelectMode, canSelectModel, canSelectThinking, canSelectProviderInModelMenu, modelDisabled, effectiveProviderDefinitions, effectiveAllProviderModels, comboboxProviderOptions, comboboxModeOptions, comboboxThinkingOptions, displayProvider, displayModel, displayThinking, ModeIconComponent, modeIconColor, openSelector, providerAnchorRef, thinkingAnchorRef, modeAnchorRef, providerPressableStyle, thinkingPressableStyle, modePressableStyle, handleProviderPress, handleThinkingPress, handleModePress, handleProviderSelect, handleThinkingSelect, handleModeSelect, handleDesktopModelSelect, handleProviderOpenChange, handleThinkingOpenChange, handleModeOpenChange, handleOpenChange, renderModeOption, } = props; return ( <> {providerOptions && providerOptions.length > 0 ? ( <> {displayProvider} DESKTOP_SEARCH_THRESHOLD} open={openSelector === "provider"} onOpenChange={handleProviderOpenChange} anchorRef={providerAnchorRef} desktopPlacement="top-start" /> ) : null} {canSelectModel ? ( {getStatusSelectorHint("model")} ) : null} {thinkingOptions && thinkingOptions.length > 0 ? ( <> {displayThinking} {getStatusSelectorHint("thinking")} DESKTOP_SEARCH_THRESHOLD} open={openSelector === "thinking"} onOpenChange={handleThinkingOpenChange} anchorRef={thinkingAnchorRef} desktopPlacement="top-start" /> ) : null} {modeOptions && modeOptions.length > 0 ? ( <> {ModeIconComponent ? ( ) : ( )} {getStatusSelectorHint("mode")} DESKTOP_SEARCH_THRESHOLD} open={openSelector === "mode"} onOpenChange={handleModeOpenChange} anchorRef={modeAnchorRef} desktopPlacement="top-start" renderOption={renderModeOption} /> ) : null} {features?.map((feature) => ( ))} ); } interface SheetStatusBarContentProps { provider: string; modeOptions?: StatusOption[]; selectedModeId?: string; selectedModelId?: string; thinkingOptions?: StatusOption[]; selectedThinkingOptionId?: string; features?: AgentFeature[]; onSetFeature?: (featureId: string, value: unknown) => void; onSelectMode?: (modeId: string) => void; onSelectThinkingOption?: (thinkingOptionId: string) => void; onToggleFavoriteModel?: (provider: string, modelId: string) => void; onDropdownClose?: () => void; onModelSelectorOpen?: () => void; providerDefinitions: AgentProviderDefinition[]; favoriteKeys: Set; disabled: boolean; isModelLoading: boolean; canSelectMode: boolean; canSelectModel: boolean; canSelectThinking: boolean; canSelectProviderInModelMenu: (providerId: string) => boolean; modelDisabled: boolean; effectiveProviderDefinitions: AgentProviderDefinition[]; effectiveAllProviderModels: Map; displayMode: string; displayModel: string; displayThinking: string; ModeIconComponent: (typeof MODE_ICONS)[keyof typeof MODE_ICONS] | null; modeIconColor: string; openSelector: StatusSelector | null; ProviderIcon: ReturnType | null; prefsOpen: boolean; handleOpenPrefs: () => void; handleClosePrefs: () => void; prefsButtonStyle: (state: PressableStateCallbackType) => StyleProp; sheetThinkingPressableStyle: (state: PressableStateCallbackType) => StyleProp; sheetModePressableStyle: (state: PressableStateCallbackType) => StyleProp; handleSheetModelSelect: (providerId: string, modelId: string) => void; handleThinkingOpenChange: (open: boolean) => void; handleModeOpenChange: (open: boolean) => void; handleOpenChange: (selector: StatusSelector) => (nextOpen: boolean) => void; renderSheetModelTrigger: (args: { selectedModelLabel: string }) => ReactElement; } function SheetStatusBarContent(props: SheetStatusBarContentProps) { const { theme } = useUnistyles(); const { provider, modeOptions, selectedModeId, selectedModelId, thinkingOptions, selectedThinkingOptionId, features, onSetFeature, onSelectMode, onSelectThinkingOption, onToggleFavoriteModel, onDropdownClose, onModelSelectorOpen, providerDefinitions, favoriteKeys, disabled, isModelLoading, canSelectMode, canSelectModel, canSelectThinking, canSelectProviderInModelMenu, modelDisabled, effectiveProviderDefinitions, effectiveAllProviderModels, displayMode, displayModel, displayThinking, ModeIconComponent, modeIconColor, openSelector, ProviderIcon, prefsOpen, handleOpenPrefs, handleClosePrefs, prefsButtonStyle, sheetThinkingPressableStyle, sheetModePressableStyle, handleSheetModelSelect, handleThinkingOpenChange, handleModeOpenChange, handleOpenChange, renderSheetModelTrigger, } = props; return ( <> {ProviderIcon ? ( ) : null} {displayModel} {canSelectModel ? ( ) : null} {thinkingOptions && thinkingOptions.length > 0 ? ( {displayThinking} {thinkingOptions.map((thinking) => ( ))} ) : null} {modeOptions && modeOptions.length > 0 ? ( {ModeIconComponent ? ( ) : null} {displayMode} {modeOptions.map((mode) => ( ))} ) : null} {features?.map((feature) => ( ))} ); } function DesktopFeatureItem({ feature, disabled, openSelector, handleOpenChange, onSetFeature, }: { feature: AgentFeature; disabled: boolean; openSelector: StatusSelector | null; handleOpenChange: (selector: StatusSelector) => (nextOpen: boolean) => void; onSetFeature?: (featureId: string, value: unknown) => void; }) { const { theme } = useUnistyles(); const featureSelector: StatusSelector = `feature-${feature.id}`; const handleFeatureOpenChange = useMemo( () => handleOpenChange(featureSelector), [handleOpenChange, featureSelector], ); const handleTogglePress = useCallback(() => { if (feature.type === "toggle") { onSetFeature?.(feature.id, !feature.value); } }, [feature, onSetFeature]); const handleSelectOption = useCallback( (optionId: string) => { onSetFeature?.(feature.id, optionId); }, [feature.id, onSetFeature], ); const togglePressableStyle = useCallback( ({ pressed, hovered }: PressableStateCallbackType) => [ styles.modeIconBadge, hovered && styles.modeBadgeHovered, pressed && styles.modeBadgePressed, disabled && styles.disabledBadge, ], [disabled], ); const selectPressableStyle = useCallback( ({ pressed, hovered }: PressableStateCallbackType) => [ styles.modeBadge, hovered && styles.modeBadgeHovered, (pressed || openSelector === featureSelector) && styles.modeBadgePressed, disabled && styles.disabledBadge, ], [disabled, openSelector, featureSelector], ); if (feature.type === "toggle") { const FeatureIcon = getFeatureIcon(feature.icon); return ( {getFeatureTooltip(feature)} ); } if (feature.type === "select") { const FeatureIcon = getFeatureIcon(feature.icon); const selectedOption = feature.options.find((o) => o.id === feature.value); return ( {selectedOption?.label ?? feature.label} {getFeatureTooltip(feature)} {feature.options.map((option) => ( ))} ); } return null; } function SheetFeatureItem({ feature, disabled, openSelector, handleOpenChange, onSetFeature, }: { feature: AgentFeature; disabled: boolean; openSelector: StatusSelector | null; handleOpenChange: (selector: StatusSelector) => (nextOpen: boolean) => void; onSetFeature?: (featureId: string, value: unknown) => void; }) { const { theme } = useUnistyles(); const featureSelector: StatusSelector = `feature-${feature.id}`; const handleFeatureOpenChange = useMemo( () => handleOpenChange(featureSelector), [handleOpenChange, featureSelector], ); const handleTogglePress = useCallback(() => { if (feature.type === "toggle") { onSetFeature?.(feature.id, !feature.value); } }, [feature, onSetFeature]); const handleSelectOption = useCallback( (optionId: string) => { onSetFeature?.(feature.id, optionId); }, [feature.id, onSetFeature], ); const togglePressableStyle = useCallback( ({ pressed }: PressableStateCallbackType) => [ styles.sheetSelect, pressed && styles.sheetSelectPressed, disabled && styles.disabledSheetSelect, ], [disabled], ); if (feature.type === "toggle") { const FeatureIcon = getFeatureIcon(feature.icon); return ( {feature.label} {feature.value ? "On" : "Off"} ); } if (feature.type === "select") { const selectedOption = feature.options.find((o) => o.id === feature.value); return ( {selectedOption?.label ?? feature.label} {feature.options.map((option) => ( ))} ); } return null; } function FeatureOptionMenuItem({ option, selected, onSelect, }: { option: { id: string; label: string }; selected: boolean; onSelect: (optionId: string) => void; }) { const handleSelect = useCallback(() => { onSelect(option.id); }, [onSelect, option.id]); return ( {option.label} ); } function ThinkingMenuItem({ thinking, selected, onSelectThinkingOption, }: { thinking: StatusOption; selected: boolean; onSelectThinkingOption?: (thinkingOptionId: string) => void; }) { const handleSelect = useCallback(() => { onSelectThinkingOption?.(thinking.id); }, [onSelectThinkingOption, thinking.id]); return ( {thinking.label} ); } function ModeComboboxOption({ option, selected, active, onPress, provider, providerDefinitions, iconColor, }: { option: ComboboxOption; selected: boolean; active: boolean; onPress: () => void; provider: string; providerDefinitions: AgentProviderDefinition[]; iconColor: string; }) { const visuals = getModeVisuals(provider, option.id, providerDefinitions); const IconComponent = visuals?.icon ? MODE_ICONS[visuals.icon] : ShieldCheck; const leadingSlot = useMemo( () => , [IconComponent, iconColor], ); return ( ); } function ModeMenuItem({ mode, provider, providerDefinitions, selected, onSelectMode, }: { mode: StatusOption; provider: string; providerDefinitions: AgentProviderDefinition[]; selected: boolean; onSelectMode?: (modeId: string) => void; }) { const { theme } = useUnistyles(); const visuals = getModeVisuals(provider, mode.id, providerDefinitions); const Icon = visuals?.icon ? MODE_ICONS[visuals.icon] : ShieldCheck; const handleSelect = useCallback(() => { onSelectMode?.(mode.id); }, [mode.id, onSelectMode]); const leadingIcon = useMemo( () => , [Icon, theme.colors.foreground], ); return ( {mode.label} ); } const EMPTY_MODES: AgentMode[] = []; export const AgentStatusBar = memo(function AgentStatusBar({ agentId, serverId, onDropdownClose, }: AgentStatusBarProps) { const { preferences, updatePreferences } = useFormPreferences(); const agent = useSessionStore( useShallow((state) => selectAgentStatusBarSlice(state, serverId, agentId)), ); const availableModes = useStoreWithEqualityFn( useSessionStore, (state) => state.sessions[serverId]?.agents?.get(agentId)?.availableModes ?? EMPTY_MODES, compareAvailableModes, ); const client = useSessionStore((state) => state.sessions[serverId]?.client ?? null); const toast = useToast(); const { entries: snapshotEntries, isLoading: snapshotIsLoading, refetchIfStale: refetchSnapshotIfStale, } = useProvidersSnapshot(serverId); const snapshotSelectedEntry = useMemo( () => resolveSnapshotSelectedEntry(snapshotEntries, agent?.provider), [snapshotEntries, agent?.provider], ); const models = snapshotSelectedEntry?.models ?? null; const selectedProviderIsLoading = snapshotSelectedEntry?.status === "loading"; const agentProviderDefinitions = useMemo( () => buildAgentProviderDefinitions(agent?.provider, snapshotEntries), [agent?.provider, snapshotEntries], ); const agentProviderModels = useMemo( () => buildAgentProviderModels(agent?.provider, models), [agent?.provider, models], ); const displayMode = resolveAgentDisplayMode(availableModes, agent?.currentModeId); const modelSelection = resolveAgentModelSelection({ models, runtimeModelId: agent?.runtimeModelId, configuredModelId: agent?.model, explicitThinkingOptionId: agent?.thinkingOptionId, }); const modeOptions = useMemo(() => { return availableModes.map((mode) => ({ id: mode.id, label: mode.label, })); }, [availableModes]); const modelOptions = useMemo(() => { return (models ?? []).map((model) => ({ id: model.id, label: model.label })); }, [models]); const favoriteKeys = useMemo( () => new Set( (preferences.favoriteModels ?? []).map((favorite) => buildFavoriteModelKey(favorite)), ), [preferences.favoriteModels], ); const thinkingOptions = useMemo(() => { return (modelSelection.thinkingOptions ?? []).map((option) => ({ id: option.id, label: option.label, })); }, [modelSelection.thinkingOptions]); const agentProvider = agent?.provider; const activeModelId = modelSelection.activeModelId; const handleSelectMode = useCallback( (modeId: string) => { if (!client) { return; } void client.setAgentMode(agentId, modeId).catch((error) => { console.warn("[AgentStatusBar] setAgentMode failed", error); toast.error(toErrorMessage(error)); }); }, [agentId, client, toast], ); const handleSelectModel = useCallback( (modelId: string) => { if (!client || !agentProvider) { return; } void updatePreferences((current) => mergeProviderPreferences({ preferences: current, provider: agentProvider, updates: { model: modelId, }, }), ).catch((error) => { console.warn("[AgentStatusBar] persist model preference failed", error); }); void client.setAgentModel(agentId, modelId).catch((error) => { console.warn("[AgentStatusBar] setAgentModel failed", error); toast.error(toErrorMessage(error)); }); }, [agentId, agentProvider, client, toast, updatePreferences], ); const handleToggleFavoriteModel = useCallback( (provider: string, modelId: string) => { void updatePreferences((current) => toggleFavoriteModel({ preferences: current, provider, modelId }), ).catch((error) => { console.warn("[AgentStatusBar] toggle favorite model failed", error); }); }, [updatePreferences], ); const handleSelectThinkingOption = useCallback( (thinkingOptionId: string) => { if (!client || !agentProvider) { return; } if (activeModelId) { void updatePreferences((current) => mergeProviderPreferences({ preferences: current, provider: agentProvider, updates: { model: activeModelId, thinkingByModel: { [activeModelId]: thinkingOptionId, }, }, }), ).catch((error) => { console.warn("[AgentStatusBar] persist thinking preference failed", error); }); } void client.setAgentThinkingOption(agentId, thinkingOptionId).catch((error) => { console.warn("[AgentStatusBar] setAgentThinkingOption failed", error); toast.error(toErrorMessage(error)); }); }, [activeModelId, agentId, agentProvider, client, toast, updatePreferences], ); const handleSetFeature = useCallback( (featureId: string, value: unknown) => { if (!client || !agentProvider) { return; } void updatePreferences((current) => mergeProviderPreferences({ preferences: current, provider: agentProvider, updates: { featureValues: { [featureId]: value, }, }, }), ).catch((error) => { console.warn("[AgentStatusBar] persist feature preference failed", error); }); void client.setAgentFeature(agentId, featureId, value).catch((error) => { console.warn("[AgentStatusBar] setAgentFeature failed", error); toast.error(toErrorMessage(error)); }); }, [agentId, agentProvider, client, toast, updatePreferences], ); const handleModelSelectorOpen = useCallback(() => { refetchSnapshotIfStale(agentProvider); }, [agentProvider, refetchSnapshotIfStale]); const fallbackModeOptions = useMemo( () => modeOptions.length > 0 ? modeOptions : [{ id: agent?.currentModeId ?? "", label: displayMode }], [agent?.currentModeId, displayMode, modeOptions], ); if (!agent) { return null; } return ( 1 ? thinkingOptions : undefined} selectedThinkingOptionId={modelSelection.selectedThinkingId ?? undefined} onSelectThinkingOption={handleSelectThinkingOption} features={agent.features} onSetFeature={handleSetFeature} isModelLoading={snapshotIsLoading || selectedProviderIsLoading} onModelSelectorOpen={handleModelSelectorOpen} onDropdownClose={onDropdownClose} disabled={!client} /> ); }); export function DraftAgentStatusBar({ providerDefinitions, selectedProvider, onSelectProvider: _onSelectProvider, modeOptions, selectedMode, onSelectMode, models, selectedModel, onSelectModel, isModelLoading: _isModelLoading, allProviderModels, isAllModelsLoading, onSelectProviderAndModel, thinkingOptions, selectedThinkingOptionId, onSelectThinkingOption, features, onSetFeature, onDropdownClose, onModelSelectorOpen, disabled = false, }: DraftAgentStatusBarProps) { const { preferences, updatePreferences } = useFormPreferences(); const mappedModeOptions = useMemo(() => { if (modeOptions.length === 0) { return [{ id: "", label: "Default" }]; } return modeOptions.map((mode) => ({ id: mode.id, label: mode.label, })); }, [modeOptions]); const mappedThinkingOptions = useMemo(() => { return thinkingOptions.map((option) => ({ id: option.id, label: option.label })); }, [thinkingOptions]); const favoriteKeys = useMemo( () => new Set( (preferences.favoriteModels ?? []).map((favorite) => buildFavoriteModelKey(favorite)), ), [preferences.favoriteModels], ); const effectiveSelectedMode = selectedMode || mappedModeOptions[0]?.id || ""; const effectiveSelectedThinkingOption = selectedThinkingOptionId || mappedThinkingOptions[0]?.id || undefined; const hasSelectedProvider = selectedProvider !== null; const modelOptions = useMemo( () => models.map((model) => ({ id: model.id, label: model.label, })), [models], ); const handleToggleFavorite = useCallback( (provider: string, modelId: string) => { void updatePreferences((current) => toggleFavoriteModel({ preferences: current, provider, modelId }), ).catch((error) => { console.warn("[DraftAgentStatusBar] toggle favorite model failed", error); }); }, [updatePreferences], ); if (platformIsWeb) { return ( {selectedProvider ? ( 0 ? mappedThinkingOptions : undefined} selectedThinkingOptionId={effectiveSelectedThinkingOption} onSelectThinkingOption={onSelectThinkingOption} features={features} onSetFeature={onSetFeature} onDropdownClose={onDropdownClose} disabled={disabled} /> ) : null} ); } return ( 0 ? mappedThinkingOptions : undefined} selectedThinkingOptionId={effectiveSelectedThinkingOption} onSelectThinkingOption={onSelectThinkingOption} features={features} onSetFeature={onSetFeature} onModelSelectorOpen={onModelSelectorOpen} disabled={disabled} /> ); } const styles = StyleSheet.create((theme) => ({ container: { flexDirection: "row", alignItems: "flex-end", gap: theme.spacing[1], }, modeBadge: { height: 28, flexDirection: "row", alignItems: "center", backgroundColor: "transparent", gap: theme.spacing[1], paddingHorizontal: theme.spacing[2], borderRadius: theme.borderRadius["2xl"], }, modeIconBadge: { width: 28, height: 28, alignItems: "center", justifyContent: "center", backgroundColor: "transparent", borderRadius: theme.borderRadius.full, }, modeBadgeHovered: { backgroundColor: theme.colors.surface2, }, modeBadgePressed: { backgroundColor: theme.colors.surface0, }, disabledBadge: { opacity: 0.5, }, modeBadgeText: { color: theme.colors.foregroundMuted, fontSize: theme.fontSize.sm, fontWeight: theme.fontWeight.normal, }, tooltipText: { color: theme.colors.foreground, fontSize: theme.fontSize.sm, lineHeight: theme.fontSize.sm * 1.4, }, prefsButton: { height: 28, minWidth: 0, flexShrink: 1, flexDirection: "row", alignItems: "center", gap: theme.spacing[1], paddingHorizontal: theme.spacing[2], borderRadius: theme.borderRadius["2xl"], }, prefsButtonPressed: { backgroundColor: theme.colors.surface0, }, prefsButtonText: { color: theme.colors.foregroundMuted, fontSize: theme.fontSize.sm, fontWeight: theme.fontWeight.normal, flexShrink: 1, }, sheetSection: { gap: theme.spacing[2], }, sheetSelect: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", gap: theme.spacing[3], paddingHorizontal: theme.spacing[4], paddingVertical: theme.spacing[3], borderRadius: theme.borderRadius.lg, borderWidth: 1, borderColor: theme.colors.surface2, backgroundColor: theme.colors.surface0, }, sheetSelectPressed: { backgroundColor: theme.colors.surface2, }, disabledSheetSelect: { opacity: 0.5, }, sheetSelectText: { flex: 1, color: theme.colors.foreground, fontSize: theme.fontSize.base, fontWeight: theme.fontWeight.semibold, }, }));