import { useCallback, useMemo, useRef, useState } from 'react' import { View, Text, Platform, Pressable } from 'react-native' import { StyleSheet, useUnistyles } from 'react-native-unistyles' import { Bot, Brain, ChevronDown, ShieldAlert, ShieldCheck, ShieldOff, SlidersHorizontal, } from 'lucide-react-native' import { ClaudeIcon } from '@/components/icons/claude-icon' import { CodexIcon } from '@/components/icons/codex-icon' import { useQuery } from '@tanstack/react-query' import { useSessionStore } from '@/stores/session-store' 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 type { AgentMode, AgentModelDefinition, AgentProvider, } from '@server/server/agent/agent-sdk-types' import type { AgentProviderDefinition } from '@server/server/agent/provider-manifest' import { getModeVisuals, type AgentModeColorTier, type AgentModeIcon, } from '@server/server/agent/provider-manifest' import { normalizeModelId, resolveAgentModelSelection } from '@/components/agent-status-bar.utils' type StatusOption = { id: string label: string } type 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 thinkingOptions?: StatusOption[] selectedThinkingOptionId?: string onSelectThinkingOption?: (thinkingOptionId: string) => void disabled?: boolean isModelLoading?: boolean } export interface DraftAgentStatusBarProps { providerDefinitions: AgentProviderDefinition[] selectedProvider: AgentProvider onSelectProvider: (provider: AgentProvider) => void modeOptions: AgentMode[] selectedMode: string onSelectMode: (modeId: string) => void models: AgentModelDefinition[] selectedModel: string onSelectModel: (modelId: string) => void isModelLoading: boolean thinkingOptions: NonNullable selectedThinkingOptionId: string onSelectThinkingOption: (thinkingOptionId: string) => void disabled?: boolean } interface AgentStatusBarProps { agentId: string serverId: string } 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 MODE_ICONS = { ShieldCheck, ShieldAlert, ShieldOff, } as const const PROVIDER_ICONS: Record = { claude: ClaudeIcon as unknown as typeof Bot, codex: CodexIcon as unknown as typeof Bot, } function getProviderIcon(provider: string): typeof Bot { return PROVIDER_ICONS[provider] ?? Bot } function getModeIconColor( colorTier: AgentModeColorTier | undefined, palette: { green: { 500: string }; amber: { 500: string }; red: { 500: string }; purple: { 500: string } } ): string { switch (colorTier) { case 'safe': return palette.green[500] case 'moderate': return palette.amber[500] case 'dangerous': return palette.red[500] case 'readonly': return palette.purple[500] default: return palette.green[500] } } function ControlledStatusBar({ provider, providerOptions, selectedProviderId, onSelectProvider, modeOptions, selectedModeId, onSelectMode, modelOptions, selectedModelId, onSelectModel, thinkingOptions, selectedThinkingOptionId, onSelectThinkingOption, disabled = false, isModelLoading = false, }: ControlledAgentStatusBarProps) { const { theme } = useUnistyles() const isWeb = Platform.OS === 'web' const [prefsOpen, setPrefsOpen] = useState(false) const [openSelector, setOpenSelector] = useState<'provider' | 'mode' | 'model' | 'thinking' | null>(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 = isModelLoading && (!modelOptions || modelOptions.length === 0) ? 'Loading models...' : findOptionLabel(modelOptions, selectedModelId, 'Auto') const displayThinking = findOptionLabel(thinkingOptions, selectedThinkingOptionId, 'auto') const modeVisuals = selectedModeId ? getModeVisuals(provider, selectedModeId) : undefined const ModeIconComponent = modeVisuals?.icon ? MODE_ICONS[modeVisuals.icon] : null const modeIconColor = getModeIconColor(modeVisuals?.colorTier, theme.colors.palette) const ProviderIcon = getProviderIcon(provider) const hasAnyControl = Boolean(providerOptions?.length) || Boolean(modeOptions?.length) || canSelectModel || Boolean(thinkingOptions?.length) if (!hasAnyControl) { return null } const modelDisabled = disabled || isModelLoading || !modelOptions || modelOptions.length === 0 const SEARCH_THRESHOLD = 6 const comboboxProviderOptions = useMemo( () => (providerOptions ?? []).map((o) => ({ id: o.id, label: o.label })), [providerOptions] ) const comboboxModeOptions = useMemo( () => (modeOptions ?? []).map((o) => ({ id: o.id, label: o.label })), [modeOptions] ) const comboboxModelOptions = useMemo( () => (modelOptions ?? []).map((o) => ({ id: o.id, label: o.label })), [modelOptions] ) const comboboxThinkingOptions = useMemo( () => (thinkingOptions ?? []).map((o) => ({ id: o.id, label: o.label })), [thinkingOptions] ) const renderModeOption = useCallback( ({ option, selected, active, onPress }: { option: ComboboxOption; selected: boolean; active: boolean; onPress: () => void }) => { const visuals = getModeVisuals(provider, option.id) const IconComponent = visuals?.icon ? MODE_ICONS[visuals.icon] : ShieldCheck return ( } /> ) }, [provider, theme.colors.foreground] ) const handleOpenChange = useCallback( (selector: 'provider' | 'mode' | 'model' | 'thinking') => (nextOpen: boolean) => { setOpenSelector(nextOpen ? selector : null) }, [] ) return ( {isWeb ? ( <> {providerOptions && providerOptions.length > 0 ? ( <> setOpenSelector(openSelector === 'provider' ? null : 'provider')} style={({ pressed, hovered }) => [ styles.modeBadge, hovered && styles.modeBadgeHovered, (pressed || openSelector === 'provider') && styles.modeBadgePressed, (disabled || !canSelectProvider) && styles.disabledBadge, ]} accessibilityRole="button" accessibilityLabel="Select agent provider" testID="agent-provider-selector" > {displayProvider} onSelectProvider?.(id)} searchable={comboboxProviderOptions.length > SEARCH_THRESHOLD} open={openSelector === 'provider'} onOpenChange={handleOpenChange('provider')} anchorRef={providerAnchorRef} desktopPlacement="top-start" /> ) : null} {modeOptions && modeOptions.length > 0 ? ( <> setOpenSelector(openSelector === 'mode' ? null : 'mode')} style={({ pressed, hovered }) => [ styles.modeIconBadge, hovered && styles.modeBadgeHovered, (pressed || openSelector === 'mode') && styles.modeBadgePressed, (disabled || !canSelectMode) && styles.disabledBadge, ]} accessibilityRole="button" accessibilityLabel={`Select agent mode (${displayMode})`} testID="agent-mode-selector" > {ModeIconComponent ? ( ) : ( )} onSelectMode?.(id)} searchable={comboboxModeOptions.length > SEARCH_THRESHOLD} open={openSelector === 'mode'} onOpenChange={handleOpenChange('mode')} anchorRef={modeAnchorRef} desktopPlacement="top-start" renderOption={renderModeOption} /> ) : null} setOpenSelector(openSelector === 'model' ? null : 'model')} style={({ pressed, hovered }) => [ styles.modeBadge, hovered && styles.modeBadgeHovered, (pressed || openSelector === 'model') && styles.modeBadgePressed, modelDisabled && styles.disabledBadge, ]} accessibilityRole="button" accessibilityLabel="Select agent model" testID="agent-model-selector" > {displayModel} onSelectModel?.(id)} searchable={comboboxModelOptions.length > SEARCH_THRESHOLD} open={openSelector === 'model'} onOpenChange={handleOpenChange('model')} anchorRef={modelAnchorRef} desktopPlacement="top-start" /> {thinkingOptions && thinkingOptions.length > 0 ? ( <> setOpenSelector(openSelector === 'thinking' ? null : 'thinking')} style={({ pressed, hovered }) => [ styles.modeBadge, hovered && styles.modeBadgeHovered, (pressed || openSelector === 'thinking') && styles.modeBadgePressed, (disabled || !canSelectThinking) && styles.disabledBadge, ]} accessibilityRole="button" accessibilityLabel={`Select thinking option (${displayThinking})`} testID="agent-thinking-selector" > {displayThinking} onSelectThinkingOption?.(id)} searchable={comboboxThinkingOptions.length > SEARCH_THRESHOLD} open={openSelector === 'thinking'} onOpenChange={handleOpenChange('thinking')} anchorRef={thinkingAnchorRef} desktopPlacement="top-start" /> ) : null} ) : ( <> setPrefsOpen(true)} style={({ pressed }) => [ styles.prefsButton, pressed && styles.prefsButtonPressed, ]} accessibilityRole="button" accessibilityLabel="Agent preferences" testID="agent-preferences-button" > setPrefsOpen(false)} testID="agent-preferences-sheet" > {providerOptions && providerOptions.length > 0 ? ( [ styles.sheetSelect, pressed && styles.sheetSelectPressed, (disabled || !canSelectProvider) && styles.disabledSheetSelect, ]} accessibilityRole="button" accessibilityLabel="Select agent provider" testID="agent-preferences-provider" > {displayProvider} {providerOptions.map((provider) => ( onSelectProvider?.(provider.id)} > {provider.label} ))} ) : null} {modeOptions && modeOptions.length > 0 ? ( [ styles.sheetSelect, pressed && styles.sheetSelectPressed, (disabled || !canSelectMode) && styles.disabledSheetSelect, ]} accessibilityRole="button" accessibilityLabel="Select agent mode" testID="agent-preferences-mode" > {ModeIconComponent ? ( ) : null} {displayMode} {modeOptions.map((mode) => { const visuals = getModeVisuals(provider, mode.id) const Icon = visuals?.icon ? MODE_ICONS[visuals.icon] : ShieldCheck return ( onSelectMode?.(mode.id)} leading={} > {mode.label} ) })} ) : null} [ styles.sheetSelect, pressed && styles.sheetSelectPressed, modelDisabled && styles.disabledSheetSelect, ]} accessibilityRole="button" accessibilityLabel="Select agent model" testID="agent-preferences-model" > {displayModel} {(modelOptions ?? []).map((model) => ( onSelectModel?.(model.id)} > {model.label} ))} {thinkingOptions && thinkingOptions.length > 0 ? ( [ styles.sheetSelect, pressed && styles.sheetSelectPressed, (disabled || !canSelectThinking) && styles.disabledSheetSelect, ]} accessibilityRole="button" accessibilityLabel="Select thinking option" testID="agent-preferences-thinking" > {displayThinking} {thinkingOptions.map((thinking) => ( onSelectThinkingOption?.(thinking.id)} > {thinking.label} ))} ) : null} )} ) } export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) { const agent = useSessionStore((state) => state.sessions[serverId]?.agents?.get(agentId)) const client = useSessionStore((state) => state.sessions[serverId]?.client ?? null) const modelsQuery = useQuery({ queryKey: [ 'providerModels', serverId, agent?.provider ?? '__missing_provider__', agent?.cwd ?? '__missing_cwd__', ], enabled: Boolean(client && agent?.provider), staleTime: 5 * 60 * 1000, queryFn: async () => { if (!client || !agent) { throw new Error('Daemon client unavailable') } const payload = await client.listProviderModels(agent.provider, { cwd: agent.cwd }) if (payload.error) { throw new Error(payload.error) } return payload.models ?? [] }, }) const models = modelsQuery.data ?? null const displayMode = agent?.availableModes?.find((mode) => mode.id === agent.currentModeId)?.label || agent?.currentModeId || 'default' const modelSelection = resolveAgentModelSelection({ models, runtimeModelId: agent?.runtimeInfo?.model, configuredModelId: agent?.model, explicitThinkingOptionId: agent?.thinkingOptionId, }) const modeOptions = useMemo(() => { return (agent?.availableModes ?? []).map((mode) => ({ id: mode.id, label: mode.label, })) }, [agent?.availableModes]) const modelOptions = useMemo(() => { return (models ?? []).map((model) => ({ id: model.id, label: model.label })) }, [models]) const thinkingOptions = useMemo(() => { return (modelSelection.thinkingOptions ?? []).map((option) => ({ id: option.id, label: option.label, })) }, [modelSelection.thinkingOptions]) if (!agent) { return null } return ( 0 ? modeOptions : [{ id: agent.currentModeId ?? '', label: displayMode }] } selectedModeId={agent.currentModeId ?? undefined} onSelectMode={(modeId) => { if (!client) { return } void client.setAgentMode(agentId, modeId).catch((error) => { console.warn('[AgentStatusBar] setAgentMode failed', error) }) }} modelOptions={modelOptions} selectedModelId={modelSelection.activeModelId ?? undefined} onSelectModel={(modelId) => { if (!client) { return } void client.setAgentModel(agentId, modelId).catch((error) => { console.warn('[AgentStatusBar] setAgentModel failed', error) }) }} thinkingOptions={thinkingOptions.length > 1 ? thinkingOptions : undefined} selectedThinkingOptionId={modelSelection.selectedThinkingId ?? undefined} onSelectThinkingOption={(thinkingOptionId) => { if (!client) { return } void client.setAgentThinkingOption(agentId, thinkingOptionId).catch((error) => { console.warn('[AgentStatusBar] setAgentThinkingOption failed', error) }) }} isModelLoading={modelsQuery.isPending || modelsQuery.isFetching} disabled={!client} /> ) } export function DraftAgentStatusBar({ providerDefinitions, selectedProvider, onSelectProvider, modeOptions, selectedMode, onSelectMode, models, selectedModel, onSelectModel, isModelLoading, thinkingOptions, selectedThinkingOptionId, onSelectThinkingOption, disabled = false, }: DraftAgentStatusBarProps) { const providerOptions = useMemo(() => { return providerDefinitions.map((definition) => ({ id: definition.id, label: definition.label, })) }, [providerDefinitions]) const mappedModeOptions = useMemo(() => { if (modeOptions.length === 0) { return [{ id: '', label: 'Default' }] } return modeOptions.map((mode) => ({ id: mode.id, label: mode.label, })) }, [modeOptions]) const modelOptions = useMemo(() => { const options: StatusOption[] = [{ id: '', label: 'Auto' }] for (const model of models) { options.push({ id: model.id, label: model.label }) } return options }, [models]) const mappedThinkingOptions = useMemo(() => { return thinkingOptions.map((option) => ({ id: option.id, label: option.label })) }, [thinkingOptions]) const effectiveSelectedMode = selectedMode || mappedModeOptions[0]?.id || '' const effectiveSelectedThinkingOption = selectedThinkingOptionId || mappedThinkingOptions[0]?.id || undefined return ( onSelectProvider(providerId as AgentProvider)} modeOptions={mappedModeOptions} selectedModeId={effectiveSelectedMode} onSelectMode={onSelectMode} modelOptions={modelOptions} selectedModelId={selectedModel} onSelectModel={onSelectModel} isModelLoading={isModelLoading} thinkingOptions={mappedThinkingOptions.length > 0 ? mappedThinkingOptions : undefined} selectedThinkingOptionId={effectiveSelectedThinkingOption} onSelectThinkingOption={onSelectThinkingOption} 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, }, prefsButton: { width: 28, height: 28, borderRadius: theme.borderRadius.full, alignItems: 'center', justifyContent: 'center', }, prefsButtonPressed: { backgroundColor: theme.colors.surface0, }, 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, }, }))