diff --git a/Cargo.lock b/Cargo.lock index c707c3349d..0ae7925341 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6963,6 +6963,7 @@ dependencies = [ "feature_flags", "gpui", "language_model", + "log", "picker", "proto", "ui", diff --git a/crates/language_model_selector/Cargo.toml b/crates/language_model_selector/Cargo.toml index 41c24248ab..e4b8b7256e 100644 --- a/crates/language_model_selector/Cargo.toml +++ b/crates/language_model_selector/Cargo.toml @@ -15,6 +15,7 @@ path = "src/language_model_selector.rs" feature_flags.workspace = true gpui.workspace = true language_model.workspace = true +log.workspace = true picker.workspace = true proto.workspace = true ui.workspace = true diff --git a/crates/language_model_selector/src/language_model_selector.rs b/crates/language_model_selector/src/language_model_selector.rs index 5a7641db45..cd03a6b1f5 100644 --- a/crates/language_model_selector/src/language_model_selector.rs +++ b/crates/language_model_selector/src/language_model_selector.rs @@ -5,7 +5,9 @@ use gpui::{ Action, AnyElement, AnyView, App, Corner, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity, }; -use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry}; +use language_model::{ + AuthenticateError, LanguageModel, LanguageModelAvailability, LanguageModelRegistry, +}; use picker::{Picker, PickerDelegate}; use proto::Plan; use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger}; @@ -20,6 +22,7 @@ pub struct LanguageModelSelector { /// The task used to update the picker's matches when there is a change to /// the language model registry. update_matches_task: Option>, + _authenticate_all_providers_task: Task<()>, _subscriptions: Vec, } @@ -50,6 +53,7 @@ impl LanguageModelSelector { LanguageModelSelector { picker, update_matches_task: None, + _authenticate_all_providers_task: Self::authenticate_all_providers(cx), _subscriptions: vec![cx.subscribe_in( &LanguageModelRegistry::global(cx), window, @@ -80,6 +84,56 @@ impl LanguageModelSelector { } } + /// Authenticates all providers in the [`LanguageModelRegistry`]. + /// + /// We do this so that we can populate the language selector with all of the + /// models from the configured providers. + fn authenticate_all_providers(cx: &mut App) -> Task<()> { + let authenticate_all_providers = LanguageModelRegistry::global(cx) + .read(cx) + .providers() + .iter() + .map(|provider| (provider.id(), provider.name(), provider.authenticate(cx))) + .collect::>(); + + cx.spawn(|_cx| async move { + for (provider_id, provider_name, authenticate_task) in authenticate_all_providers { + if let Err(err) = authenticate_task.await { + if matches!(err, AuthenticateError::CredentialsNotFound) { + // Since we're authenticating these providers in the + // background for the purposes of populating the + // language selector, we don't care about providers + // where the credentials are not found. + } else { + // Some providers have noisy failure states that we + // don't want to spam the logs with every time the + // language model selector is initialized. + // + // Ideally these should have more clear failure modes + // that we know are safe to ignore here, like what we do + // with `CredentialsNotFound` above. + match provider_id.0.as_ref() { + "lmstudio" | "ollama" => { + // LM Studio and Ollama both make fetch requests to the local APIs to determine if they are "authenticated". + // + // These fail noisily, so we don't log them. + } + "copilot_chat" => { + // Copilot Chat returns an error if Copilot is not enabled, so we don't log those errors. + } + _ => { + log::error!( + "Failed to authenticate provider: {}: {err}", + provider_name.0 + ); + } + } + } + } + } + }) + } + fn all_models(cx: &App) -> Vec { LanguageModelRegistry::global(cx) .read(cx)