import { useState, useEffect, useMemo, useCallback } from 'react' import type { ACPClient } from '../acp/client' import type { ModelInfo, SessionModelState } from '../acp/types' export interface UseModelsResult { /** Whether model selection is supported by the current agent */ supportsModelSelection: boolean /** List of available models */ availableModels: ModelInfo[] /** The currently selected model ID */ currentModelId: string | null /** The currently selected model info */ currentModel: ModelInfo | null /** Set the model for the current session */ setModel: (modelId: string) => Promise /** Whether a model change is in progress */ isLoading: boolean } /** * Hook to manage model selection state. * Reference: Zed's AcpModelSelector reads from state.available_models and state.current_model_id * * Uses event-driven updates instead of polling: * - setModelStateChangedHandler: called on session create/disconnect * - setModelChangedHandler: called when model selection changes */ export function useModels(client: ACPClient): UseModelsResult { const [modelState, setModelState] = useState( client.modelState, ) const [isLoading, setIsLoading] = useState(false) // Subscribe to model state changes (session created/destroyed) // This replaces the previous 500ms polling approach useEffect(() => { // Handler for when model state changes (session created or disconnected) const handleModelStateChanged = (state: SessionModelState | null) => { setModelState(state) // Auto-restore previously selected model when a new session is created if (state && state.availableModels.length > 0) { const saved = localStorage.getItem('acp_model_id') if ( saved && saved !== state.currentModelId && state.availableModels.some(m => m.modelId === saved) ) { client.setSessionModel(saved).catch(() => {}) } } } // Handler for when current model changes within a session const handleModelChanged = (modelId: string) => { setModelState(prev => { if (!prev) return null return { ...prev, currentModelId: modelId, } }) setIsLoading(false) } // Register handlers - setModelStateChangedHandler immediately calls with current state client.setModelStateChangedHandler(handleModelStateChanged) client.setModelChangedHandler(handleModelChanged) return () => { // Clear handlers on unmount client.setModelStateChangedHandler(() => {}) client.setModelChangedHandler(() => {}) } }, [client]) const availableModels = useMemo( () => modelState?.availableModels ?? [], [modelState], ) const currentModelId = modelState?.currentModelId ?? null const currentModel = useMemo( () => availableModels.find(m => m.modelId === currentModelId) ?? null, [availableModels, currentModelId], ) const setModel = useCallback( async (modelId: string) => { if (!modelState) { throw new Error('Model selection not supported') } setIsLoading(true) try { await client.setSessionModel(modelId) localStorage.setItem('acp_model_id', modelId) // The model_changed event will update the state } catch (error) { setIsLoading(false) throw error } }, [client, modelState], ) return { supportsModelSelection: modelState !== null && availableModels.length > 0, availableModels, currentModelId, currentModel, setModel, isLoading, } }