From 372aaecdb4249a76f5445c6385a22b2f8c90768d Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 18 Feb 2025 20:40:07 -0500 Subject: [PATCH] language_model_selector: Authenticate all providers up front (#25123) This PR fixes an issue where configured language model providers would not show up unless the configuration view was opened. The problem was that we were filtering unauthenticated language model providers out of the language model selector, but would only authenticate the active provider when the selector loaded. Authenticating the rest of the providers was deferred until the configuration view was opened for the first time. Closes https://github.com/zed-industries/zed/issues/21821. Release Notes: - Fixed an issue where configured languages models were not showing up in the language model selector until the configuration view was opened for the first time. --- Cargo.lock | 1 + crates/language_model_selector/Cargo.toml | 1 + .../src/language_model_selector.rs | 56 ++++++++++++++++++- 3 files changed, 57 insertions(+), 1 deletion(-) 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)