@@ -73,9 +73,10 @@ final class AIModelsManager: AIModelsManagerProtocol {
7373 modelsByProviderId = . init( llmModelByProvider. values. flatMap ( \. self) . reduce ( into: [ : ] ) { acc, model in
7474 acc [ model. id] = model
7575 } )
76- providerModelsByModelId = . init ( llmModelByProvider. values. flatMap ( \. self) . reduce ( into: [ : ] ) { acc, model in
76+ let providerModelsByModelIdDict = llmModelByProvider. values. flatMap ( \. self) . reduce ( into: [ : ] ) { acc, model in
7777 acc [ model. modelInfo. id, default: [ ] ] . append ( model)
78- } )
78+ }
79+ providerModelsByModelId = . init( providerModelsByModelIdDict)
7980 let modelByModelId = PublishedDictionary < AIModelID , AIModel > ( llmModelByProvider. values. flatMap ( \. self)
8081 . reduce ( into: [ : ] ) { acc, model in
8182 acc [ model. modelInfo. id] = model. modelInfo
@@ -86,6 +87,15 @@ final class AIModelsManager: AIModelsManagerProtocol {
8687 settings: settingsService. value ( for: \. llmProviderSettings) ,
8788 models: llmModelByProvider) )
8889
90+ // Initialize activeModels with the current state
91+ let configuredProviders = Set ( settingsService. value ( for: \. llmProviderSettings) . keys)
92+ let initialActiveModels = Self . filterActiveModels (
93+ models: modelByModelId. sortedValues,
94+ enabledModels: settingsService. value ( for: \. enabledModels) ,
95+ providerModelsByModelId: providerModelsByModelIdDict,
96+ configuredProviders: configuredProviders)
97+ _activeModels = . init( initialActiveModels)
98+
8999 observerChangesToSettings ( )
90100 }
91101
@@ -98,14 +108,7 @@ final class AIModelsManager: AIModelsManagerProtocol {
98108 }
99109
100110 var activeModels : ReadonlyCurrentValueSubject < [ AIModel ] , Never > {
101- ReadonlyCurrentValueSubject < [ AIModel ] , Never > (
102- filterActiveModels ( models. currentValue) ,
103- publisher: models. map { @Sendable [ weak self] models in
104- guard let self else { return [ ] }
105- return filterActiveModels ( models)
106- }
107- . removeDuplicates ( )
108- . eraseToAnyPublisher ( ) )
111+ _activeModels. readonly ( )
109112 }
110113
111114 func provider( for model: AIModel ) -> ReadonlyCurrentValueSubject < AIProvider ? , Never > {
@@ -146,6 +149,7 @@ final class AIModelsManager: AIModelsManagerProtocol {
146149 }
147150
148151 private let _availableModels : CurrentValueSubject < [ AIModel ] , Never >
152+ private let _activeModels : CurrentValueSubject < [ AIModel ] , Never >
149153
150154 private let localServer : LocalServer
151155 private let settingsService : SettingsService
@@ -229,6 +233,43 @@ final class AIModelsManager: AIModelsManagerProtocol {
229233 }
230234 }
231235
236+ /// Return the list of models that are active.
237+ /// - Parameters:
238+ /// - models: The list of models to choose from, typically all known models
239+ /// - enabledModels: The models that have been enabled by the user.
240+ /// - providerModelsByModelId: The list of known providers for each model
241+ /// - configuredProviders: The providers that have been configured by the user.
242+ private static func filterActiveModels(
243+ models: [ AIModel ] ,
244+ enabledModels: [ AIModelID ] ,
245+ providerModelsByModelId: [ AIModelID : [ AIProviderModel ] ] ,
246+ configuredProviders: Set < AIProvider > )
247+ -> [ AIModel ]
248+ {
249+ let enabledModels = Set ( enabledModels)
250+ return models. filter { model in
251+ // Get the list of providers that support the model
252+ guard let providerModels = providerModelsByModelId [ model. id] , !providerModels. isEmpty else {
253+ return false
254+ }
255+
256+ // Check if at least one of the providers for this model is configured
257+ let hasConfiguredProvider = providerModels. contains { configuredProviders. contains ( $0. provider) }
258+ guard hasConfiguredProvider else {
259+ return false
260+ }
261+
262+ // Check if it should be active
263+ let isEnabled = enabledModels. contains ( model. id)
264+ // Check if the model is from an external agent
265+ let isExternalAgent = providerModels. contains { providerModel in
266+ providerModel. provider. isExternalAgent
267+ }
268+
269+ return isEnabled || isExternalAgent
270+ }
271+ }
272+
232273 private func fetchAndSaveModelsAvailable(
233274 for provider: AIProvider ,
234275 settings: AIProviderSettings )
@@ -305,14 +346,32 @@ final class AIModelsManager: AIModelsManagerProtocol {
305346 guard let self else { return }
306347 await updateModels ( from: previous, to: llmProviderSettings)
307348 _availableModels. send ( Self . availableModels ( settings: llmProviderSettings, models: llmModelByProvider. wrappedValue) )
349+ updateActiveModels ( )
308350 }
309351 } . store ( in: & cancellables)
352+
310353 settingsService. liveValue ( for: \. enabledModels) . sink { @Sendable [ weak self] _ in
311354 guard let self else { return }
312- mutableModels. send ( mutableModels. value) // This will trigger a new filtering of active models
355+ updateActiveModels ( )
356+ } . store ( in: & cancellables)
357+
358+ // Update activeModels when the models list changes
359+ mutableModels. sink { @Sendable [ weak self] _ in
360+ guard let self else { return }
361+ updateActiveModels ( )
313362 } . store ( in: & cancellables)
314363 }
315364
365+ private func updateActiveModels( ) {
366+ let configuredProviders = Set ( settingsService. value ( for: \. llmProviderSettings) . keys)
367+ let activeModels = Self . filterActiveModels (
368+ models: modelByModelId. sortedValues,
369+ enabledModels: settingsService. value ( for: \. enabledModels) ,
370+ providerModelsByModelId: providerModelsByModelId. wrappedValue,
371+ configuredProviders: configuredProviders)
372+ _activeModels. send ( activeModels)
373+ }
374+
316375 private func updateModels(
317376 from previous: [ AIProvider : AIProviderSettings ] ? ,
318377 to current: [ AIProvider : AIProviderSettings ] ? )
@@ -326,11 +385,13 @@ final class AIModelsManager: AIModelsManagerProtocol {
326385 {
327386 // Remove providers that are no longer present
328387 let removedProviders = ( previous ?? [ : ] ) . keys. filter { current ? [ $0] == nil }
329- let modelInfos = inLock { state in
388+ let ( modelInfos, modelsToPersist ) = inLock { state in
330389 for provider in removedProviders {
331390 Self . remove ( provider: provider, from: & state)
332391 }
333- return state. modelByModelId. sortedValues
392+ return (
393+ state. modelByModelId. sortedValues,
394+ state. llmModelByProvider. wrappedValue. reduce ( into: [ : ] ) { $0 [ $1. key] = $1. value } )
334395 }
335396 mutableModels. send ( modelInfos)
336397
@@ -350,12 +411,13 @@ final class AIModelsManager: AIModelsManagerProtocol {
350411 await group. waitForAll ( )
351412 }
352413
353- do {
354- try Self . persist (
355- models: llmModelByProvider. wrappedValue. reduce ( into: [ : ] , { $0 [ $1. key] = $1. value } ) ,
356- fileManager: fileManager)
357- } catch {
358- defaultLogger. error ( " Failed to persist models " , error)
414+ // Only persist if we removed providers (fetched models handle their own persistence)
415+ if !removedProviders. isEmpty {
416+ do {
417+ try Self . persist ( models: modelsToPersist, fileManager: fileManager)
418+ } catch {
419+ defaultLogger. error ( " Failed to persist models " , error)
420+ }
359421 }
360422 }
361423 // Ensure that those updates are serial, since they rely on the change between two states.
@@ -364,15 +426,6 @@ final class AIModelsManager: AIModelsManagerProtocol {
364426 } . value
365427 }
366428
367- private func filterActiveModels( _ models: [ AIModel ] ) -> [ AIModel ] {
368- models. filter { model in
369- settingsService. value ( for: \. enabledModels) . contains ( model. id) ||
370- // The model that represent an external agent should always be considered active.
371- // To disable it, the user can disable the provider instead.
372- providerModelsByModelId [ model. id] ? . first? . provider. isExternalAgent == true
373- }
374- }
375-
376429}
377430
378431extension PublishedDictionary < AIModelID , AIModel > {
0 commit comments