Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:34:04.233Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

registry.rs

747 lines · 25.8 KB · rust
1use crate::{
2    LanguageModel, LanguageModelId, LanguageModelProvider, LanguageModelProviderId,
3    LanguageModelProviderState, ZED_CLOUD_PROVIDER_ID,
4};
5use collections::{BTreeMap, HashSet};
6use gpui::{App, Context, Entity, EventEmitter, Global, prelude::*};
7use std::{str::FromStr, sync::Arc};
8use thiserror::Error;
9
10/// Function type for checking if a built-in provider should be hidden.
11/// Returns Some(extension_id) if the provider should be hidden when that extension is installed.
12pub type BuiltinProviderHidingFn = Box<dyn Fn(&str) -> Option<&'static str> + Send + Sync>;
13
14pub fn init(cx: &mut App) {
15    let registry = cx.new(|_cx| LanguageModelRegistry::default());
16    cx.set_global(GlobalLanguageModelRegistry(registry));
17}
18
19struct GlobalLanguageModelRegistry(Entity<LanguageModelRegistry>);
20
21impl Global for GlobalLanguageModelRegistry {}
22
23#[derive(Error)]
24pub enum ConfigurationError {
25    #[error("Configure at least one LLM provider to start using the panel.")]
26    NoProvider,
27    #[error("LLM provider is not configured or does not support the configured model.")]
28    ModelNotFound,
29    #[error("{} LLM provider is not configured.", .0.name().0)]
30    ProviderNotAuthenticated(Arc<dyn LanguageModelProvider>),
31}
32
33impl std::fmt::Debug for ConfigurationError {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            Self::NoProvider => write!(f, "NoProvider"),
37            Self::ModelNotFound => write!(f, "ModelNotFound"),
38            Self::ProviderNotAuthenticated(provider) => {
39                write!(f, "ProviderNotAuthenticated({})", provider.id())
40            }
41        }
42    }
43}
44
45#[derive(Default)]
46pub struct LanguageModelRegistry {
47    /// True if the user has *NO* default model configured in settings
48    should_use_fallback: bool,
49    default_model: Option<ConfiguredModel>,
50    /// This model is automatically configured by a user's environment after
51    /// authenticating all providers. It's only used when `default_model` is not set.
52    available_fallback_model: Option<ConfiguredModel>,
53    inline_assistant_model: Option<ConfiguredModel>,
54    commit_message_model: Option<ConfiguredModel>,
55    thread_summary_model: Option<ConfiguredModel>,
56    compaction_model: Option<ConfiguredModel>,
57    providers: BTreeMap<LanguageModelProviderId, Arc<dyn LanguageModelProvider>>,
58    inline_alternatives: Vec<Arc<dyn LanguageModel>>,
59    /// Set of installed extension IDs that provide language models.
60    /// Used to determine which built-in providers should be hidden.
61    installed_llm_extension_ids: HashSet<Arc<str>>,
62    /// Function to check if a built-in provider should be hidden by an extension.
63    builtin_provider_hiding_fn: Option<BuiltinProviderHidingFn>,
64}
65
66#[derive(Debug)]
67pub struct SelectedModel {
68    pub provider: LanguageModelProviderId,
69    pub model: LanguageModelId,
70}
71
72impl FromStr for SelectedModel {
73    type Err = String;
74
75    /// Parse string identifiers like `provider_id/model_id` into a `SelectedModel`
76    fn from_str(id: &str) -> Result<SelectedModel, Self::Err> {
77        let Some((provider_id, model_id)) = id.split_once('/') else {
78            return Err(format!(
79                "Invalid model identifier format: `{}`. Expected `provider_id/model_id`",
80                id
81            ));
82        };
83
84        if provider_id.is_empty() || model_id.is_empty() {
85            return Err(format!("Provider and model ids can't be empty: `{}`", id));
86        }
87
88        Ok(SelectedModel {
89            provider: LanguageModelProviderId(provider_id.to_string().into()),
90            model: LanguageModelId(model_id.to_string().into()),
91        })
92    }
93}
94
95#[derive(Clone)]
96pub struct ConfiguredModel {
97    pub provider: Arc<dyn LanguageModelProvider>,
98    pub model: Arc<dyn LanguageModel>,
99}
100
101impl ConfiguredModel {
102    pub fn is_same_as(&self, other: &ConfiguredModel) -> bool {
103        self.model.id() == other.model.id() && self.provider.id() == other.provider.id()
104    }
105
106    pub fn is_provided_by_zed(&self) -> bool {
107        self.provider.id() == ZED_CLOUD_PROVIDER_ID
108    }
109}
110
111pub enum Event {
112    DefaultModelChanged,
113    InlineAssistantModelChanged,
114    CommitMessageModelChanged,
115    CompactionModelChanged,
116    ThreadSummaryModelChanged,
117    ProviderStateChanged(LanguageModelProviderId),
118    AddedProvider(LanguageModelProviderId),
119    RemovedProvider(LanguageModelProviderId),
120    /// Emitted when provider visibility changes due to extension install/uninstall.
121    ProvidersChanged,
122}
123
124impl EventEmitter<Event> for LanguageModelRegistry {}
125
126impl LanguageModelRegistry {
127    pub fn global(cx: &App) -> Entity<Self> {
128        cx.global::<GlobalLanguageModelRegistry>().0.clone()
129    }
130
131    pub fn read_global(cx: &App) -> &Self {
132        cx.global::<GlobalLanguageModelRegistry>().0.read(cx)
133    }
134
135    #[cfg(any(test, feature = "test-support"))]
136    pub fn test(cx: &mut App) -> Arc<crate::fake_provider::FakeLanguageModelProvider> {
137        let fake_provider = Arc::new(crate::fake_provider::FakeLanguageModelProvider::default());
138        let registry = cx.new(|cx| {
139            let mut registry = Self::default();
140            registry.register_provider(fake_provider.clone(), cx);
141            let model = fake_provider.provided_models(cx)[0].clone();
142            let configured_model = ConfiguredModel {
143                provider: fake_provider.clone(),
144                model,
145            };
146            registry.set_default_model(Some(configured_model), cx);
147            registry
148        });
149        cx.set_global(GlobalLanguageModelRegistry(registry));
150        fake_provider
151    }
152
153    #[cfg(any(test, feature = "test-support"))]
154    pub fn fake_model(&self) -> Arc<dyn LanguageModel> {
155        self.default_model.as_ref().unwrap().model.clone()
156    }
157
158    pub fn set_should_use_fallback(&mut self, value: bool) {
159        self.should_use_fallback = value;
160    }
161
162    pub fn register_provider<T: LanguageModelProvider + LanguageModelProviderState>(
163        &mut self,
164        provider: Arc<T>,
165        cx: &mut Context<Self>,
166    ) {
167        let id = provider.id();
168
169        let subscription = provider.subscribe(cx, {
170            let id = id.clone();
171            move |_, cx| {
172                cx.emit(Event::ProviderStateChanged(id.clone()));
173            }
174        });
175        if let Some(subscription) = subscription {
176            subscription.detach();
177        }
178
179        self.providers.insert(id.clone(), provider);
180        cx.emit(Event::AddedProvider(id));
181    }
182
183    pub fn unregister_provider(&mut self, id: LanguageModelProviderId, cx: &mut Context<Self>) {
184        if self.providers.remove(&id).is_some() {
185            cx.emit(Event::RemovedProvider(id));
186        }
187    }
188
189    pub fn providers(&self) -> Vec<Arc<dyn LanguageModelProvider>> {
190        let zed_provider_id = LanguageModelProviderId("zed.dev".into());
191        let mut providers = Vec::with_capacity(self.providers.len());
192        if let Some(provider) = self.providers.get(&zed_provider_id) {
193            providers.push(provider.clone());
194        }
195        providers.extend(self.providers.values().filter_map(|p| {
196            if p.id() != zed_provider_id {
197                Some(p.clone())
198            } else {
199                None
200            }
201        }));
202        providers
203    }
204
205    /// Returns providers, filtering out hidden built-in providers.
206    pub fn visible_providers(&self) -> Vec<Arc<dyn LanguageModelProvider>> {
207        self.providers()
208            .into_iter()
209            .filter(|p| !self.should_hide_provider(&p.id()))
210            .collect()
211    }
212
213    /// Sets the function used to check if a built-in provider should be hidden.
214    pub fn set_builtin_provider_hiding_fn(&mut self, hiding_fn: BuiltinProviderHidingFn) {
215        self.builtin_provider_hiding_fn = Some(hiding_fn);
216    }
217
218    /// Called when an extension is installed/loaded.
219    /// If the extension provides language models, track it so we can hide the corresponding built-in.
220    pub fn extension_installed(&mut self, extension_id: Arc<str>, cx: &mut Context<Self>) {
221        if self.installed_llm_extension_ids.insert(extension_id) {
222            cx.emit(Event::ProvidersChanged);
223            cx.notify();
224        }
225    }
226
227    /// Called when an extension is uninstalled/unloaded.
228    pub fn extension_uninstalled(&mut self, extension_id: &str, cx: &mut Context<Self>) {
229        if self.installed_llm_extension_ids.remove(extension_id) {
230            cx.emit(Event::ProvidersChanged);
231            cx.notify();
232        }
233    }
234
235    /// Sync the set of installed LLM extension IDs.
236    pub fn sync_installed_llm_extensions(
237        &mut self,
238        extension_ids: HashSet<Arc<str>>,
239        cx: &mut Context<Self>,
240    ) {
241        if extension_ids != self.installed_llm_extension_ids {
242            self.installed_llm_extension_ids = extension_ids;
243            cx.emit(Event::ProvidersChanged);
244            cx.notify();
245        }
246    }
247
248    /// Returns true if a provider should be hidden from the UI.
249    /// Built-in providers are hidden when their corresponding extension is installed.
250    pub fn should_hide_provider(&self, provider_id: &LanguageModelProviderId) -> bool {
251        if let Some(ref hiding_fn) = self.builtin_provider_hiding_fn {
252            if let Some(extension_id) = hiding_fn(&provider_id.0) {
253                return self.installed_llm_extension_ids.contains(extension_id);
254            }
255        }
256        false
257    }
258
259    pub fn configuration_error(
260        &self,
261        model: Option<ConfiguredModel>,
262        cx: &App,
263    ) -> Option<ConfigurationError> {
264        let Some(model) = model else {
265            if !self.has_authenticated_provider(cx) {
266                return Some(ConfigurationError::NoProvider);
267            }
268            return Some(ConfigurationError::ModelNotFound);
269        };
270
271        if !model.provider.is_authenticated(cx) {
272            return Some(ConfigurationError::ProviderNotAuthenticated(model.provider));
273        }
274
275        None
276    }
277
278    /// Returns `true` if at least one provider that is authenticated.
279    pub fn has_authenticated_provider(&self, cx: &App) -> bool {
280        self.providers.values().any(|p| p.is_authenticated(cx))
281    }
282
283    pub fn available_models<'a>(
284        &'a self,
285        cx: &'a App,
286    ) -> impl Iterator<Item = Arc<dyn LanguageModel>> + 'a {
287        self.providers
288            .values()
289            .filter(|provider| provider.is_authenticated(cx))
290            .flat_map(|provider| provider.provided_models(cx))
291    }
292
293    pub fn provider(&self, id: &LanguageModelProviderId) -> Option<Arc<dyn LanguageModelProvider>> {
294        self.providers.get(id).cloned()
295    }
296
297    pub fn select_default_model(&mut self, model: Option<&SelectedModel>, cx: &mut Context<Self>) {
298        let configured_model = model.and_then(|model| self.select_model(model, cx));
299        self.set_default_model(configured_model, cx);
300    }
301
302    pub fn select_inline_assistant_model(
303        &mut self,
304        model: Option<&SelectedModel>,
305        cx: &mut Context<Self>,
306    ) {
307        let configured_model = model.and_then(|model| self.select_model(model, cx));
308        self.set_inline_assistant_model(configured_model, cx);
309    }
310
311    pub fn select_commit_message_model(
312        &mut self,
313        model: Option<&SelectedModel>,
314        cx: &mut Context<Self>,
315    ) {
316        let configured_model = model.and_then(|model| self.select_model(model, cx));
317        self.set_commit_message_model(configured_model, cx);
318    }
319
320    pub fn select_thread_summary_model(
321        &mut self,
322        model: Option<&SelectedModel>,
323        cx: &mut Context<Self>,
324    ) {
325        let configured_model = model.and_then(|model| self.select_model(model, cx));
326        self.set_thread_summary_model(configured_model, cx);
327    }
328
329    pub fn select_compaction_model(
330        &mut self,
331        model: Option<&SelectedModel>,
332        cx: &mut Context<Self>,
333    ) {
334        let configured_model = model.and_then(|model| self.select_model(model, cx));
335        self.set_compaction_model(configured_model, cx);
336    }
337
338    /// Selects and sets the inline alternatives for language models based on
339    /// provider name and id.
340    pub fn select_inline_alternative_models(
341        &mut self,
342        alternatives: impl IntoIterator<Item = SelectedModel>,
343        cx: &mut Context<Self>,
344    ) {
345        self.inline_alternatives = alternatives
346            .into_iter()
347            .flat_map(|alternative| {
348                self.select_model(&alternative, cx)
349                    .map(|configured_model| configured_model.model)
350            })
351            .collect::<Vec<_>>();
352    }
353
354    pub fn select_model(
355        &mut self,
356        selected_model: &SelectedModel,
357        cx: &mut Context<Self>,
358    ) -> Option<ConfiguredModel> {
359        let provider = self.provider(&selected_model.provider)?;
360        let model = provider
361            .provided_models(cx)
362            .iter()
363            .find(|model| model.id() == selected_model.model)?
364            .clone();
365        Some(ConfiguredModel { provider, model })
366    }
367
368    pub fn set_default_model(&mut self, model: Option<ConfiguredModel>, cx: &mut Context<Self>) {
369        match (self.default_model(), model.as_ref()) {
370            (Some(old), Some(new)) if old.is_same_as(new) => {}
371            (None, None) => {}
372            _ => cx.emit(Event::DefaultModelChanged),
373        }
374        self.default_model = model;
375    }
376
377    pub fn refresh_fallback_model(&mut self, cx: &mut Context<Self>) {
378        // If the fallback model was already set or we don't want to use it, do nothing
379        if !self.should_use_fallback || self.available_fallback_model.is_some() {
380            return;
381        }
382
383        let fallback_model = self
384            .providers()
385            .iter()
386            .filter(|provider| provider.is_authenticated(cx))
387            .find_map(|provider| {
388                let model = provider
389                    .default_model(cx)
390                    .or_else(|| provider.recommended_models(cx).first().cloned())?;
391                Some(ConfiguredModel {
392                    provider: provider.clone(),
393                    model,
394                })
395            });
396
397        self.set_fallback_model(fallback_model, cx);
398    }
399
400    fn set_fallback_model(&mut self, model: Option<ConfiguredModel>, cx: &mut Context<Self>) {
401        if self.default_model.is_none() {
402            match (self.available_fallback_model.as_ref(), model.as_ref()) {
403                (Some(old), Some(new)) if old.is_same_as(new) => {}
404                (None, None) => {}
405                _ => cx.emit(Event::DefaultModelChanged),
406            }
407        }
408        self.available_fallback_model = model;
409    }
410
411    pub fn set_inline_assistant_model(
412        &mut self,
413        model: Option<ConfiguredModel>,
414        cx: &mut Context<Self>,
415    ) {
416        match (self.inline_assistant_model.as_ref(), model.as_ref()) {
417            (Some(old), Some(new)) if old.is_same_as(new) => {}
418            (None, None) => {}
419            _ => cx.emit(Event::InlineAssistantModelChanged),
420        }
421        self.inline_assistant_model = model;
422    }
423
424    pub fn set_commit_message_model(
425        &mut self,
426        model: Option<ConfiguredModel>,
427        cx: &mut Context<Self>,
428    ) {
429        match (self.commit_message_model.as_ref(), model.as_ref()) {
430            (Some(old), Some(new)) if old.is_same_as(new) => {}
431            (None, None) => {}
432            _ => cx.emit(Event::CommitMessageModelChanged),
433        }
434        self.commit_message_model = model;
435    }
436
437    pub fn set_thread_summary_model(
438        &mut self,
439        model: Option<ConfiguredModel>,
440        cx: &mut Context<Self>,
441    ) {
442        match (self.thread_summary_model.as_ref(), model.as_ref()) {
443            (Some(old), Some(new)) if old.is_same_as(new) => {}
444            (None, None) => {}
445            _ => cx.emit(Event::ThreadSummaryModelChanged),
446        }
447        self.thread_summary_model = model;
448    }
449
450    pub fn set_compaction_model(&mut self, model: Option<ConfiguredModel>, cx: &mut Context<Self>) {
451        match (self.compaction_model.as_ref(), model.as_ref()) {
452            (Some(old), Some(new)) if old.is_same_as(new) => {}
453            (None, None) => {}
454            _ => cx.emit(Event::CompactionModelChanged),
455        }
456        self.compaction_model = model;
457    }
458
459    pub fn default_model(&self) -> Option<ConfiguredModel> {
460        #[cfg(debug_assertions)]
461        if std::env::var("ZED_SIMULATE_NO_LLM_PROVIDER").is_ok() {
462            return None;
463        }
464
465        self.default_model.clone().or_else(|| {
466            if self.should_use_fallback {
467                self.available_fallback_model.clone()
468            } else {
469                None
470            }
471        })
472    }
473
474    pub fn default_fast_model(&self, cx: &App) -> Option<ConfiguredModel> {
475        let configured = self.default_model()?;
476        let fast_model = configured.provider.default_fast_model(cx)?;
477        Some(ConfiguredModel {
478            provider: configured.provider,
479            model: fast_model,
480        })
481    }
482
483    pub fn inline_assistant_model(&self) -> Option<ConfiguredModel> {
484        #[cfg(debug_assertions)]
485        if std::env::var("ZED_SIMULATE_NO_LLM_PROVIDER").is_ok() {
486            return None;
487        }
488
489        self.inline_assistant_model
490            .clone()
491            .or_else(|| self.default_model.clone())
492    }
493
494    pub fn commit_message_model(&self, cx: &App) -> Option<ConfiguredModel> {
495        #[cfg(debug_assertions)]
496        if std::env::var("ZED_SIMULATE_NO_LLM_PROVIDER").is_ok() {
497            return None;
498        }
499
500        self.commit_message_model
501            .clone()
502            .or_else(|| self.default_fast_model(cx))
503            .or_else(|| self.default_model())
504    }
505
506    pub fn thread_summary_model(&self, cx: &App) -> Option<ConfiguredModel> {
507        #[cfg(debug_assertions)]
508        if std::env::var("ZED_SIMULATE_NO_LLM_PROVIDER").is_ok() {
509            return None;
510        }
511
512        self.thread_summary_model
513            .clone()
514            .or_else(|| self.default_fast_model(cx))
515            .or_else(|| self.default_model())
516    }
517
518    /// Returns the configured compaction model without falling back through
519    /// `default_fast_model`/`default_model`. Callers that want a fallback to
520    /// the thread's primary model should handle `None` themselves.
521    pub fn compaction_model(&self) -> Option<ConfiguredModel> {
522        #[cfg(debug_assertions)]
523        if std::env::var("ZED_SIMULATE_NO_LLM_PROVIDER").is_ok() {
524            return None;
525        }
526
527        self.compaction_model.clone()
528    }
529
530    /// The models to use for inline assists. Returns the union of the active
531    /// model and all inline alternatives. When there are multiple models, the
532    /// user will be able to cycle through results.
533    pub fn inline_alternative_models(&self) -> &[Arc<dyn LanguageModel>] {
534        &self.inline_alternatives
535    }
536}
537
538#[cfg(test)]
539mod tests {
540    use super::*;
541    use crate::fake_provider::FakeLanguageModelProvider;
542
543    #[test]
544    fn selected_model_allows_slashes_in_model_id() {
545        let selected = SelectedModel::from_str("custom-provider/organization/model-name")
546            .expect("model identifier should parse");
547
548        assert_eq!(
549            selected.provider,
550            LanguageModelProviderId("custom-provider".into())
551        );
552        assert_eq!(
553            selected.model,
554            LanguageModelId("organization/model-name".into())
555        );
556    }
557
558    #[test]
559    fn selected_model_rejects_missing_separator_or_empty_parts() {
560        assert!(SelectedModel::from_str("custom-provider").is_err());
561        assert!(SelectedModel::from_str("/organization/model-name").is_err());
562        assert!(SelectedModel::from_str("custom-provider/").is_err());
563    }
564
565    #[gpui::test]
566    fn test_register_providers(cx: &mut App) {
567        let registry = cx.new(|_| LanguageModelRegistry::default());
568
569        let provider = Arc::new(FakeLanguageModelProvider::default());
570        registry.update(cx, |registry, cx| {
571            registry.register_provider(provider.clone(), cx);
572        });
573
574        let providers = registry.read(cx).providers();
575        assert_eq!(providers.len(), 1);
576        assert_eq!(providers[0].id(), provider.id());
577
578        registry.update(cx, |registry, cx| {
579            registry.unregister_provider(provider.id(), cx);
580        });
581
582        let providers = registry.read(cx).providers();
583        assert!(providers.is_empty());
584    }
585
586    #[gpui::test]
587    fn test_provider_hiding_on_extension_install(cx: &mut App) {
588        let registry = cx.new(|_| LanguageModelRegistry::default());
589
590        let provider = Arc::new(FakeLanguageModelProvider::default());
591        let provider_id = provider.id();
592
593        registry.update(cx, |registry, cx| {
594            registry.register_provider(provider.clone(), cx);
595
596            registry.set_builtin_provider_hiding_fn(Box::new(|id| {
597                if id == "fake" {
598                    Some("fake-extension")
599                } else {
600                    None
601                }
602            }));
603        });
604
605        let visible = registry.read(cx).visible_providers();
606        assert_eq!(visible.len(), 1);
607        assert_eq!(visible[0].id(), provider_id);
608
609        registry.update(cx, |registry, cx| {
610            registry.extension_installed("fake-extension".into(), cx);
611        });
612
613        let visible = registry.read(cx).visible_providers();
614        assert!(visible.is_empty());
615
616        let all = registry.read(cx).providers();
617        assert_eq!(all.len(), 1);
618    }
619
620    #[gpui::test]
621    fn test_provider_unhiding_on_extension_uninstall(cx: &mut App) {
622        let registry = cx.new(|_| LanguageModelRegistry::default());
623
624        let provider = Arc::new(FakeLanguageModelProvider::default());
625        let provider_id = provider.id();
626
627        registry.update(cx, |registry, cx| {
628            registry.register_provider(provider.clone(), cx);
629
630            registry.set_builtin_provider_hiding_fn(Box::new(|id| {
631                if id == "fake" {
632                    Some("fake-extension")
633                } else {
634                    None
635                }
636            }));
637
638            registry.extension_installed("fake-extension".into(), cx);
639        });
640
641        let visible = registry.read(cx).visible_providers();
642        assert!(visible.is_empty());
643
644        registry.update(cx, |registry, cx| {
645            registry.extension_uninstalled("fake-extension", cx);
646        });
647
648        let visible = registry.read(cx).visible_providers();
649        assert_eq!(visible.len(), 1);
650        assert_eq!(visible[0].id(), provider_id);
651    }
652
653    #[gpui::test]
654    fn test_should_hide_provider(cx: &mut App) {
655        let registry = cx.new(|_| LanguageModelRegistry::default());
656
657        registry.update(cx, |registry, cx| {
658            registry.set_builtin_provider_hiding_fn(Box::new(|id| {
659                if id == "anthropic" {
660                    Some("anthropic")
661                } else if id == "openai" {
662                    Some("openai")
663                } else {
664                    None
665                }
666            }));
667
668            registry.extension_installed("anthropic".into(), cx);
669        });
670
671        let registry_read = registry.read(cx);
672
673        assert!(registry_read.should_hide_provider(&LanguageModelProviderId("anthropic".into())));
674
675        assert!(!registry_read.should_hide_provider(&LanguageModelProviderId("openai".into())));
676
677        assert!(!registry_read.should_hide_provider(&LanguageModelProviderId("unknown".into())));
678    }
679
680    #[gpui::test]
681    async fn test_configure_fallback_model(cx: &mut gpui::TestAppContext) {
682        let registry = cx.new(|_| LanguageModelRegistry::default());
683
684        let provider = Arc::new(FakeLanguageModelProvider::default());
685        registry.update(cx, |registry, cx| {
686            registry.register_provider(provider.clone(), cx);
687        });
688
689        cx.update(|cx| provider.authenticate(cx)).await.unwrap();
690
691        registry.update(cx, |registry, cx| {
692            let provider = registry.provider(&provider.id()).unwrap();
693            let model = provider.default_model(cx).unwrap();
694
695            registry.set_fallback_model(
696                Some(ConfiguredModel {
697                    provider: provider.clone(),
698                    model: model.clone(),
699                }),
700                cx,
701            );
702
703            assert!(registry.default_model().is_none());
704
705            registry.set_should_use_fallback(true);
706
707            let default_model = registry.default_model().unwrap();
708            assert_eq!(default_model.model.id(), model.id());
709            assert_eq!(default_model.provider.id(), provider.id());
710        });
711    }
712
713    #[gpui::test]
714    fn test_sync_installed_llm_extensions(cx: &mut App) {
715        let registry = cx.new(|_| LanguageModelRegistry::default());
716
717        let provider = Arc::new(FakeLanguageModelProvider::default());
718
719        registry.update(cx, |registry, cx| {
720            registry.register_provider(provider.clone(), cx);
721
722            registry.set_builtin_provider_hiding_fn(Box::new(|id| {
723                if id == "fake" {
724                    Some("fake-extension")
725                } else {
726                    None
727                }
728            }));
729        });
730
731        let mut extension_ids = HashSet::default();
732        extension_ids.insert(Arc::from("fake-extension"));
733
734        registry.update(cx, |registry, cx| {
735            registry.sync_installed_llm_extensions(extension_ids, cx);
736        });
737
738        assert!(registry.read(cx).visible_providers().is_empty());
739
740        registry.update(cx, |registry, cx| {
741            registry.sync_installed_llm_extensions(HashSet::default(), cx);
742        });
743
744        assert_eq!(registry.read(cx).visible_providers().len(), 1);
745    }
746}
747
Served at tenant.openagents/omega Member data and write actions are omitted.