Misc cleanup + clear language model provider API key editors when API keys are submitted (#38165)

Followup to #38163 along with some other misc cleanups

Release Notes:

- N/A
This commit is contained in:
Michael Sloan
2025-09-15 05:08:38 +00:00
committed by GitHub
parent 98edf1bf0b
commit 634ae72cad
10 changed files with 128 additions and 151 deletions
@@ -1,14 +1,13 @@
use crate::api_key::ApiKeyState;
use crate::ui::InstructionListItem;
use crate::{AllLanguageModelSettings, api_key::ApiKeyState};
use anthropic::{
AnthropicError, AnthropicModelMode, ContentDelta, Event, ResponseContent, ToolResultContent,
ToolResultPart, Usage,
};
use anyhow::Result;
use anyhow::{Result, anyhow};
use collections::{BTreeMap, HashMap};
use editor::{Editor, EditorElement, EditorStyle};
use futures::Stream;
use futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};
use futures::{FutureExt, Stream, StreamExt, future, future::BoxFuture, stream::BoxStream};
use gpui::{AnyView, App, AsyncApp, Context, Entity, FontStyle, Task, TextStyle, WhiteSpace};
use http_client::HttpClient;
use language_model::{
@@ -157,7 +156,7 @@ impl AnthropicLanguageModelProvider {
}
fn settings(cx: &App) -> &AnthropicSettings {
&AllLanguageModelSettings::get_global(cx).anthropic
&crate::AllLanguageModelSettings::get_global(cx).anthropic
}
fn api_url(cx: &App) -> SharedString {
@@ -220,11 +219,7 @@ impl LanguageModelProvider for AnthropicLanguageModelProvider {
}
// Override with available models from settings
for model in AllLanguageModelSettings::get_global(cx)
.anthropic
.available_models
.iter()
{
for model in &AnthropicLanguageModelProvider::settings(cx).available_models {
models.insert(
model.name.clone(),
anthropic::Model::Custom {
@@ -363,16 +358,11 @@ impl AnthropicModel {
> {
let http_client = self.http_client.clone();
let api_key_and_url = self.state.read_with(cx, |state, cx| {
let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {
let api_url = AnthropicLanguageModelProvider::api_url(cx);
let api_key = state.api_key_state.key(&api_url);
(api_key, api_url)
});
let (api_key, api_url) = match api_key_and_url {
Ok(api_key_and_url) => api_key_and_url,
Err(err) => {
return futures::future::ready(Err(err.into())).boxed();
}
(state.api_key_state.key(&api_url), api_url)
}) else {
return future::ready(Err(anyhow!("App state dropped").into())).boxed();
};
let beta_headers = self.model.beta_headers();
@@ -938,6 +928,10 @@ impl ConfigurationView {
return;
}
// url changes can cause the editor to be displayed again
self.api_key_editor
.update(cx, |editor, cx| editor.set_text("", window, cx));
let state = self.state.clone();
cx.spawn_in(window, async move |_, cx| {
state
@@ -2,7 +2,7 @@ use anyhow::{Result, anyhow};
use collections::{BTreeMap, HashMap};
use editor::{Editor, EditorElement, EditorStyle};
use futures::Stream;
use futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};
use futures::{FutureExt, StreamExt, future, future::BoxFuture, stream::BoxStream};
use gpui::{
AnyView, App, AsyncApp, Context, Entity, FontStyle, SharedString, Task, TextStyle, WhiteSpace,
Window,
@@ -26,7 +26,7 @@ use ui::{Icon, IconName, List, prelude::*};
use util::ResultExt;
use zed_env_vars::{EnvVar, env_var};
use crate::{AllLanguageModelSettings, api_key::ApiKeyState, ui::InstructionListItem};
use crate::{api_key::ApiKeyState, ui::InstructionListItem};
const PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new("deepseek");
const PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new("DeepSeek");
@@ -119,7 +119,7 @@ impl DeepSeekLanguageModelProvider {
}
fn settings(cx: &App) -> &DeepSeekSettings {
&AllLanguageModelSettings::get_global(cx).deepseek
&crate::AllLanguageModelSettings::get_global(cx).deepseek
}
fn api_url(cx: &App) -> &str {
@@ -220,16 +220,11 @@ impl DeepSeekLanguageModel {
) -> BoxFuture<'static, Result<BoxStream<'static, Result<deepseek::StreamResponse>>>> {
let http_client = self.http_client.clone();
let api_key_and_url = self.state.read_with(cx, |state, cx| {
let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {
let api_url = DeepSeekLanguageModelProvider::api_url(cx);
let api_key = state.api_key_state.key(api_url);
(api_key, api_url.to_string())
});
let (api_key, api_url) = match api_key_and_url {
Ok(api_key_and_url) => api_key_and_url,
Err(err) => {
return futures::future::ready(Err(err)).boxed();
}
(state.api_key_state.key(api_url), api_url.to_string())
}) else {
return future::ready(Err(anyhow!("App state dropped"))).boxed();
};
let future = self.request_limiter.stream(async move {
+17 -17
View File
@@ -1,8 +1,8 @@
use anyhow::{Context as _, Result};
use anyhow::{Context as _, Result, anyhow};
use collections::BTreeMap;
use credentials_provider::CredentialsProvider;
use editor::{Editor, EditorElement, EditorStyle};
use futures::{FutureExt, Stream, StreamExt, future::BoxFuture};
use futures::{FutureExt, Stream, StreamExt, future, future::BoxFuture};
use google_ai::{
FunctionDeclaration, GenerateContentResponse, GoogleModelMode, Part, SystemInstruction,
ThinkingConfig, UsageMetadata,
@@ -37,8 +37,8 @@ use util::ResultExt;
use zed_env_vars::EnvVar;
use crate::api_key::ApiKey;
use crate::api_key::ApiKeyState;
use crate::ui::InstructionListItem;
use crate::{AllLanguageModelSettings, api_key::ApiKeyState};
const PROVIDER_ID: LanguageModelProviderId = language_model::GOOGLE_PROVIDER_ID;
const PROVIDER_NAME: LanguageModelProviderName = language_model::GOOGLE_PROVIDER_NAME;
@@ -173,8 +173,12 @@ impl GoogleLanguageModelProvider {
})
}
fn settings(cx: &App) -> &GoogleSettings {
&crate::AllLanguageModelSettings::get_global(cx).google
}
fn api_url(cx: &App) -> SharedString {
let api_url = &AllLanguageModelSettings::get_global(cx).google.api_url;
let api_url = &Self::settings(cx).api_url;
if api_url.is_empty() {
google_ai::API_URL.into()
} else {
@@ -223,10 +227,7 @@ impl LanguageModelProvider for GoogleLanguageModelProvider {
}
// Override with available models from settings
for model in &AllLanguageModelSettings::get_global(cx)
.google
.available_models
{
for model in &GoogleLanguageModelProvider::settings(cx).available_models {
models.insert(
model.name.clone(),
google_ai::Model::Custom {
@@ -295,16 +296,11 @@ impl GoogleLanguageModel {
> {
let http_client = self.http_client.clone();
let api_key_and_url = self.state.read_with(cx, |state, cx| {
let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {
let api_url = GoogleLanguageModelProvider::api_url(cx);
let api_key = state.api_key_state.key(&api_url);
(api_key, api_url)
});
let (api_key, api_url) = match api_key_and_url {
Ok(api_key_and_url) => api_key_and_url,
Err(err) => {
return futures::future::ready(Err(err)).boxed();
}
(state.api_key_state.key(&api_url), api_url)
}) else {
return future::ready(Err(anyhow!("App state dropped"))).boxed();
};
async move {
@@ -820,6 +816,10 @@ impl ConfigurationView {
return;
}
// url changes can cause the editor to be displayed again
self.api_key_editor
.update(cx, |editor, cx| editor.set_text("", window, cx));
let state = self.state.clone();
cx.spawn_in(window, async move |_, cx| {
state
+9 -10
View File
@@ -1,7 +1,7 @@
use anyhow::{Result, anyhow};
use collections::BTreeMap;
use editor::{Editor, EditorElement, EditorStyle};
use futures::{FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream};
use futures::{FutureExt, Stream, StreamExt, future, future::BoxFuture, stream::BoxStream};
use gpui::{
AnyView, App, AsyncApp, Context, Entity, FontStyle, SharedString, Task, TextStyle, WhiteSpace,
Window,
@@ -244,16 +244,11 @@ impl MistralLanguageModel {
> {
let http_client = self.http_client.clone();
let api_key_and_url = self.state.read_with(cx, |state, cx| {
let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {
let api_url = MistralLanguageModelProvider::api_url(cx);
let api_key = state.api_key_state.key(&api_url);
(api_key, api_url)
});
let (api_key, api_url) = match api_key_and_url {
Ok(api_key_and_url) => api_key_and_url,
Err(err) => {
return futures::future::ready(Err(err)).boxed();
}
(state.api_key_state.key(&api_url), api_url)
}) else {
return future::ready(Err(anyhow!("App state dropped"))).boxed();
};
let future = self.request_limiter.stream(async move {
@@ -762,6 +757,10 @@ impl ConfigurationView {
return;
}
// url changes can cause the editor to be displayed again
self.api_key_editor
.update(cx, |editor, cx| editor.set_text("", window, cx));
let state = self.state.clone();
cx.spawn_in(window, async move |_, cx| {
state
+14 -21
View File
@@ -1,7 +1,7 @@
use anyhow::{Result, anyhow};
use collections::{BTreeMap, HashMap};
use futures::Stream;
use futures::{FutureExt, StreamExt, future::BoxFuture};
use futures::{FutureExt, StreamExt, future, future::BoxFuture};
use gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};
use http_client::HttpClient;
use language_model::{
@@ -25,7 +25,7 @@ use ui_input::SingleLineInput;
use util::ResultExt;
use zed_env_vars::{EnvVar, env_var};
use crate::{AllLanguageModelSettings, api_key::ApiKeyState, ui::InstructionListItem};
use crate::{api_key::ApiKeyState, ui::InstructionListItem};
const PROVIDER_ID: LanguageModelProviderId = language_model::OPEN_AI_PROVIDER_ID;
const PROVIDER_NAME: LanguageModelProviderName = language_model::OPEN_AI_PROVIDER_NAME;
@@ -113,7 +113,7 @@ impl OpenAiLanguageModelProvider {
}
fn settings(cx: &App) -> &OpenAiSettings {
&AllLanguageModelSettings::get_global(cx).openai
&crate::AllLanguageModelSettings::get_global(cx).openai
}
fn api_url(cx: &App) -> SharedString {
@@ -166,10 +166,7 @@ impl LanguageModelProvider for OpenAiLanguageModelProvider {
}
// Override with available models from settings
for model in &AllLanguageModelSettings::get_global(cx)
.openai
.available_models
{
for model in &OpenAiLanguageModelProvider::settings(cx).available_models {
models.insert(
model.name.clone(),
open_ai::Model::Custom {
@@ -230,16 +227,11 @@ impl OpenAiLanguageModel {
{
let http_client = self.http_client.clone();
let api_key_and_url = self.state.read_with(cx, |state, cx| {
let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {
let api_url = OpenAiLanguageModelProvider::api_url(cx);
let api_key = state.api_key_state.key(&api_url);
(api_key, api_url)
});
let (api_key, api_url) = match api_key_and_url {
Ok(api_key_and_url) => api_key_and_url,
Err(err) => {
return futures::future::ready(Err(err)).boxed();
}
(state.api_key_state.key(&api_url), api_url)
}) else {
return future::ready(Err(anyhow!("App state dropped"))).boxed();
};
let future = self.request_limiter.stream(async move {
@@ -744,6 +736,10 @@ impl ConfigurationView {
return;
}
// url changes can cause the editor to be displayed again
self.api_key_editor
.update(cx, |editor, cx| editor.set_text("", window, cx));
let state = self.state.clone();
cx.spawn_in(window, async move |_, cx| {
state
@@ -754,11 +750,8 @@ impl ConfigurationView {
}
fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.api_key_editor.update(cx, |input, cx| {
input.editor.update(cx, |editor, cx| {
editor.set_text("", window, cx);
});
});
self.api_key_editor
.update(cx, |input, cx| input.set_text("", window, cx));
let state = self.state.clone();
cx.spawn_in(window, async move |_, cx| {
@@ -1,6 +1,6 @@
use anyhow::Result;
use anyhow::{Result, anyhow};
use convert_case::{Case, Casing};
use futures::{FutureExt, StreamExt, future::BoxFuture};
use futures::{FutureExt, StreamExt, future, future::BoxFuture};
use gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};
use http_client::HttpClient;
use language_model::{
@@ -20,8 +20,8 @@ use ui_input::SingleLineInput;
use util::ResultExt;
use zed_env_vars::EnvVar;
use crate::api_key::ApiKeyState;
use crate::provider::open_ai::{OpenAiEventMapper, into_open_ai};
use crate::{AllLanguageModelSettings, api_key::ApiKeyState};
#[derive(Default, Clone, Debug, PartialEq)]
pub struct OpenAiCompatibleSettings {
@@ -98,7 +98,7 @@ impl State {
impl OpenAiCompatibleLanguageModelProvider {
pub fn new(id: Arc<str>, http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {
fn resolve_settings<'a>(id: &'a str, cx: &'a App) -> Option<&'a OpenAiCompatibleSettings> {
AllLanguageModelSettings::get_global(cx)
crate::AllLanguageModelSettings::get_global(cx)
.openai_compatible
.get(id)
}
@@ -239,16 +239,14 @@ impl OpenAiCompatibleLanguageModel {
{
let http_client = self.http_client.clone();
let api_key_and_url = self.state.read_with(cx, |state, _cx| {
let Ok((api_key, api_url)) = self.state.read_with(cx, |state, _cx| {
let api_url = &state.settings.api_url;
let api_key = state.api_key_state.key(api_url);
(api_key, state.settings.api_url.clone())
});
let (api_key, api_url) = match api_key_and_url {
Ok(api_key_and_url) => api_key_and_url,
Err(err) => {
return futures::future::ready(Err(err)).boxed();
}
(
state.api_key_state.key(api_url),
state.settings.api_url.clone(),
)
}) else {
return future::ready(Err(anyhow!("App state dropped"))).boxed();
};
let provider = self.provider_name.clone();
@@ -423,6 +421,10 @@ impl ConfigurationView {
return;
}
// url changes can cause the editor to be displayed again
self.api_key_editor
.update(cx, |input, cx| input.set_text("", window, cx));
let state = self.state.clone();
cx.spawn_in(window, async move |_, cx| {
state
@@ -433,11 +435,8 @@ impl ConfigurationView {
}
fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.api_key_editor.update(cx, |input, cx| {
input.editor.update(cx, |editor, cx| {
editor.set_text("", window, cx);
});
});
self.api_key_editor
.update(cx, |input, cx| input.set_text("", window, cx));
let state = self.state.clone();
cx.spawn_in(window, async move |_, cx| {
@@ -1,7 +1,7 @@
use anyhow::{Result, anyhow};
use collections::HashMap;
use editor::{Editor, EditorElement, EditorStyle};
use futures::{FutureExt, Stream, StreamExt, future::BoxFuture};
use futures::{FutureExt, Stream, StreamExt, future, future::BoxFuture};
use gpui::{
AnyView, App, AsyncApp, Context, Entity, FontStyle, SharedString, Task, TextStyle, WhiteSpace,
};
@@ -27,7 +27,7 @@ use ui::{Icon, IconName, List, Tooltip, prelude::*};
use util::ResultExt;
use zed_env_vars::{EnvVar, env_var};
use crate::{AllLanguageModelSettings, api_key::ApiKeyState, ui::InstructionListItem};
use crate::{api_key::ApiKeyState, ui::InstructionListItem};
const PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new("openrouter");
const PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new("OpenRouter");
@@ -96,7 +96,6 @@ pub struct State {
http_client: Arc<dyn HttpClient>,
available_models: Vec<open_router::Model>,
fetch_models_task: Option<Task<Result<(), LanguageModelCompletionError>>>,
settings: OpenRouterSettings,
}
impl State {
@@ -171,14 +170,17 @@ impl State {
impl OpenRouterLanguageModelProvider {
pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {
let state = cx.new(|cx| {
cx.observe_global::<SettingsStore>(|this: &mut State, cx| {
let current_settings = &AllLanguageModelSettings::get_global(cx).open_router;
let settings_changed = current_settings != &this.settings;
if settings_changed {
this.settings = current_settings.clone();
this.authenticate(cx).detach();
cx.observe_global::<SettingsStore>({
let mut last_settings = OpenRouterLanguageModelProvider::settings(cx).clone();
move |this: &mut State, cx| {
let current_settings = OpenRouterLanguageModelProvider::settings(cx);
let settings_changed = current_settings != &last_settings;
if settings_changed {
last_settings = current_settings.clone();
this.authenticate(cx).detach();
cx.notify();
}
}
cx.notify();
})
.detach();
State {
@@ -186,7 +188,6 @@ impl OpenRouterLanguageModelProvider {
http_client: http_client.clone(),
available_models: Vec::new(),
fetch_models_task: None,
settings: OpenRouterSettings::default(),
}
});
@@ -194,7 +195,7 @@ impl OpenRouterLanguageModelProvider {
}
fn settings(cx: &App) -> &OpenRouterSettings {
&AllLanguageModelSettings::get_global(cx).open_router
&crate::AllLanguageModelSettings::get_global(cx).open_router
}
fn api_url(cx: &App) -> SharedString {
@@ -322,17 +323,11 @@ impl OpenRouterLanguageModel {
>,
> {
let http_client = self.http_client.clone();
let api_key_and_url = self.state.read_with(cx, |state, cx| {
let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {
let api_url = OpenRouterLanguageModelProvider::api_url(cx);
let api_key = state.api_key_state.key(&api_url);
(api_key, api_url)
});
let (api_key, api_url) = match api_key_and_url {
Ok(api_key_and_url) => api_key_and_url,
Err(err) => {
return futures::future::ready(Err(LanguageModelCompletionError::Other(err)))
.boxed();
}
(state.api_key_state.key(&api_url), api_url)
}) else {
return future::ready(Err(anyhow!("App state dropped").into())).boxed();
};
async move {
@@ -795,6 +790,10 @@ impl ConfigurationView {
return;
}
// url changes can cause the editor to be displayed again
self.api_key_editor
.update(cx, |editor, cx| editor.set_text("", window, cx));
let state = self.state.clone();
cx.spawn_in(window, async move |_, cx| {
state
+12 -16
View File
@@ -1,6 +1,6 @@
use anyhow::Result;
use anyhow::{Result, anyhow};
use collections::BTreeMap;
use futures::{FutureExt, StreamExt, future::BoxFuture};
use futures::{FutureExt, StreamExt, future, future::BoxFuture};
use gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};
use http_client::HttpClient;
use language_model::{
@@ -219,16 +219,11 @@ impl VercelLanguageModel {
{
let http_client = self.http_client.clone();
let api_key_and_url = self.state.read_with(cx, |state, cx| {
let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {
let api_url = VercelLanguageModelProvider::api_url(cx);
let api_key = state.api_key_state.key(&api_url);
(api_key, api_url)
});
let (api_key, api_url) = match api_key_and_url {
Ok(api_key_and_url) => api_key_and_url,
Err(err) => {
return futures::future::ready(Err(err)).boxed();
}
(state.api_key_state.key(&api_url), api_url)
}) else {
return future::ready(Err(anyhow!("App state dropped"))).boxed();
};
let future = self.request_limiter.stream(async move {
@@ -429,6 +424,10 @@ impl ConfigurationView {
return;
}
// url changes can cause the editor to be displayed again
self.api_key_editor
.update(cx, |editor, cx| editor.set_text("", window, cx));
let state = self.state.clone();
cx.spawn_in(window, async move |_, cx| {
state
@@ -439,11 +438,8 @@ impl ConfigurationView {
}
fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.api_key_editor.update(cx, |input, cx| {
input.editor.update(cx, |editor, cx| {
editor.set_text("", window, cx);
});
});
self.api_key_editor
.update(cx, |input, cx| input.set_text("", window, cx));
let state = self.state.clone();
cx.spawn_in(window, async move |_, cx| {
+12 -16
View File
@@ -1,6 +1,6 @@
use anyhow::Result;
use anyhow::{Result, anyhow};
use collections::BTreeMap;
use futures::{FutureExt, StreamExt, future::BoxFuture};
use futures::{FutureExt, StreamExt, future, future::BoxFuture};
use gpui::{AnyView, App, AsyncApp, Context, Entity, Task, Window};
use http_client::HttpClient;
use language_model::{
@@ -219,16 +219,11 @@ impl XAiLanguageModel {
{
let http_client = self.http_client.clone();
let api_key_and_url = self.state.read_with(cx, |state, cx| {
let Ok((api_key, api_url)) = self.state.read_with(cx, |state, cx| {
let api_url = XAiLanguageModelProvider::api_url(cx);
let api_key = state.api_key_state.key(&api_url);
(api_key, api_url)
});
let (api_key, api_url) = match api_key_and_url {
Ok(api_key_and_url) => api_key_and_url,
Err(err) => {
return futures::future::ready(Err(err)).boxed();
}
(state.api_key_state.key(&api_url), api_url)
}) else {
return future::ready(Err(anyhow!("App state dropped"))).boxed();
};
let future = self.request_limiter.stream(async move {
@@ -423,6 +418,10 @@ impl ConfigurationView {
return;
}
// url changes can cause the editor to be displayed again
self.api_key_editor
.update(cx, |editor, cx| editor.set_text("", window, cx));
let state = self.state.clone();
cx.spawn_in(window, async move |_, cx| {
state
@@ -433,11 +432,8 @@ impl ConfigurationView {
}
fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.api_key_editor.update(cx, |input, cx| {
input.editor.update(cx, |editor, cx| {
editor.set_text("", window, cx);
});
});
self.api_key_editor
.update(cx, |input, cx| input.set_text("", window, cx));
let state = self.state.clone();
cx.spawn_in(window, async move |_, cx| {
+6
View File
@@ -9,6 +9,7 @@ use component::{example_group, single_example};
use editor::{Editor, EditorElement, EditorStyle};
use gpui::{App, Entity, FocusHandle, Focusable, FontStyle, Hsla, TextStyle};
use settings::Settings;
use std::sync::Arc;
use theme::ThemeSettings;
use ui::prelude::*;
@@ -101,6 +102,11 @@ impl SingleLineInput {
pub fn text(&self, cx: &App) -> String {
self.editor().read(cx).text(cx)
}
pub fn set_text(&self, text: impl Into<Arc<str>>, window: &mut Window, cx: &mut App) {
self.editor()
.update(cx, |editor, cx| editor.set_text(text, window, cx))
}
}
impl Render for SingleLineInput {