Skip to repository content747 lines · 25.8 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T02:34:04.233Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
registry.rs
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