From 7ae8f81d742c1eee3faed5b2d4558e32a77fea1b Mon Sep 17 00:00:00 2001
From: Umesh Yadav <23421535+imumesh18@users.noreply.github.com>
Date: Mon, 8 Sep 2025 10:27:16 +0530
Subject: [PATCH] language_models: Clear cached credentials when OpenAI and
OpenAI Compatible provider `api_url` change (#37610)
Closes #37093
Also check this: #37099.
So currently in zed for both OpenAI and OpenAI Compatible provider when
the url is changed from settings the api_key stored in the provider
state is not cleared and it is still used. But if you restart zed the
api_key is cleared. Currently zed uses the api_url to store and fetch
the api key from credential provider. The behaviour is not changed
overall, it's just that we have made it consistent it with the zed
restart logic where it re-authenticates and fetches the api_key again. I
have attached the video below to show case before and after of this.
So all in all the problem was we were not re-authenticating the in case
api_url change while zed is still running. Now we trigger a
re-authentication and clear the state in case authentication fails.
OpenAI Compatible Provider:
| Before | After |
|--------|--------|
| | |
OpenAI Provider:
| Before | After |
|--------|--------|
| | |
Release Notes:
- Fixed OpenAI and OpenAI Compatible provide API keys being persisted
when changing the API URL setting. Authentication is now properly
revalidated when settings change.
---------
Signed-off-by: Umesh Yadav
---
.../language_models/src/provider/open_ai.rs | 48 ++++++++++++++++---
.../src/provider/open_ai_compatible.rs | 36 ++++++++++----
2 files changed, 69 insertions(+), 15 deletions(-)
diff --git a/crates/language_models/src/provider/open_ai.rs b/crates/language_models/src/provider/open_ai.rs
index cfd4303351..fca1cf977c 100644
--- a/crates/language_models/src/provider/open_ai.rs
+++ b/crates/language_models/src/provider/open_ai.rs
@@ -56,13 +56,13 @@ pub struct OpenAiLanguageModelProvider {
pub struct State {
api_key: Option,
api_key_from_env: bool,
+ last_api_url: String,
_subscription: Subscription,
}
const OPENAI_API_KEY_VAR: &str = "OPENAI_API_KEY";
impl State {
- //
fn is_authenticated(&self) -> bool {
self.api_key.is_some()
}
@@ -104,11 +104,7 @@ impl State {
})
}
- fn authenticate(&self, cx: &mut Context) -> Task> {
- if self.is_authenticated() {
- return Task::ready(Ok(()));
- }
-
+ fn get_api_key(&self, cx: &mut Context) -> Task> {
let credentials_provider = ::global(cx);
let api_url = AllLanguageModelSettings::get_global(cx)
.openai
@@ -136,14 +132,52 @@ impl State {
Ok(())
})
}
+
+ fn authenticate(&self, cx: &mut Context) -> Task> {
+ if self.is_authenticated() {
+ return Task::ready(Ok(()));
+ }
+
+ self.get_api_key(cx)
+ }
}
impl OpenAiLanguageModelProvider {
pub fn new(http_client: Arc, cx: &mut App) -> Self {
+ let initial_api_url = AllLanguageModelSettings::get_global(cx)
+ .openai
+ .api_url
+ .clone();
+
let state = cx.new(|cx| State {
api_key: None,
api_key_from_env: false,
- _subscription: cx.observe_global::(|_this: &mut State, cx| {
+ last_api_url: initial_api_url.clone(),
+ _subscription: cx.observe_global::(|this: &mut State, cx| {
+ let current_api_url = AllLanguageModelSettings::get_global(cx)
+ .openai
+ .api_url
+ .clone();
+
+ if this.last_api_url != current_api_url {
+ this.last_api_url = current_api_url;
+ if !this.api_key_from_env {
+ this.api_key = None;
+ let spawn_task = cx.spawn(async move |handle, cx| {
+ if let Ok(task) = handle.update(cx, |this, cx| this.get_api_key(cx)) {
+ if let Err(_) = task.await {
+ handle
+ .update(cx, |this, _| {
+ this.api_key = None;
+ this.api_key_from_env = false;
+ })
+ .ok();
+ }
+ }
+ });
+ spawn_task.detach();
+ }
+ }
cx.notify();
}),
});
diff --git a/crates/language_models/src/provider/open_ai_compatible.rs b/crates/language_models/src/provider/open_ai_compatible.rs
index 789eb00a57..4ebb11a07b 100644
--- a/crates/language_models/src/provider/open_ai_compatible.rs
+++ b/crates/language_models/src/provider/open_ai_compatible.rs
@@ -113,11 +113,7 @@ impl State {
})
}
- fn authenticate(&self, cx: &mut Context) -> Task> {
- if self.is_authenticated() {
- return Task::ready(Ok(()));
- }
-
+ fn get_api_key(&self, cx: &mut Context) -> Task> {
let credentials_provider = ::global(cx);
let env_var_name = self.env_var_name.clone();
let api_url = self.settings.api_url.clone();
@@ -143,6 +139,14 @@ impl State {
Ok(())
})
}
+
+ fn authenticate(&self, cx: &mut Context) -> Task> {
+ if self.is_authenticated() {
+ return Task::ready(Ok(()));
+ }
+
+ self.get_api_key(cx)
+ }
}
impl OpenAiCompatibleLanguageModelProvider {
@@ -160,11 +164,27 @@ impl OpenAiCompatibleLanguageModelProvider {
api_key: None,
api_key_from_env: false,
_subscription: cx.observe_global::(|this: &mut State, cx| {
- let Some(settings) = resolve_settings(&this.id, cx) else {
+ let Some(settings) = resolve_settings(&this.id, cx).cloned() else {
return;
};
- if &this.settings != settings {
- this.settings = settings.clone();
+ if &this.settings != &settings {
+ if settings.api_url != this.settings.api_url && !this.api_key_from_env {
+ let spawn_task = cx.spawn(async move |handle, cx| {
+ if let Ok(task) = handle.update(cx, |this, cx| this.get_api_key(cx)) {
+ if let Err(_) = task.await {
+ handle
+ .update(cx, |this, _| {
+ this.api_key = None;
+ this.api_key_from_env = false;
+ })
+ .ok();
+ }
+ }
+ });
+ spawn_task.detach();
+ }
+
+ this.settings = settings;
cx.notify();
}
}),