From cc7799af38b6e2da510e0d834d70f95fe7c8b30a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 14 Dec 2025 20:35:01 -0500 Subject: [PATCH] Allow multiple API keys --- crates/extension/src/extension_manifest.rs | 5 +- crates/extension_host/src/extension_host.rs | 69 ++-- .../extension_host/src/extension_settings.rs | 10 +- .../src/wasm_host/llm_provider.rs | 341 +++++++++--------- .../src/wasm_host/wit/since_v0_8_0.rs | 77 ++-- .../src/settings_content/extension.rs | 6 +- extensions/anthropic/extension.toml | 2 +- extensions/google-ai/extension.toml | 2 +- extensions/openai/extension.toml | 2 +- extensions/openrouter/extension.toml | 2 +- 10 files changed, 262 insertions(+), 254 deletions(-) diff --git a/crates/extension/src/extension_manifest.rs b/crates/extension/src/extension_manifest.rs index f61686e7e80c25646ae597b025e5eafae821a8b8..60b755e39457a6be99bf37c28e1074388365d3e5 100644 --- a/crates/extension/src/extension_manifest.rs +++ b/crates/extension/src/extension_manifest.rs @@ -330,9 +330,10 @@ pub struct LanguageModelManifestEntry { pub struct LanguageModelAuthConfig { /// Human-readable name for the credential shown in the UI input field (e.g. "API Key", "Access Token"). pub credential_label: Option, - /// Environment variable name for the API key (if env var auth supported). + /// Environment variable names for the API key (if env var auth supported). + /// Multiple env vars can be specified; they will be checked in order. #[serde(default)] - pub env_var: Option, + pub env_vars: Option>, /// OAuth configuration for web-based authentication flows. #[serde(default)] pub oauth: Option, diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index 95fde0cfc0ab0d3626305694ae86271d850ed9d6..d9824588b440f3ec1a32bd740936a58dd59e7239 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -99,7 +99,7 @@ const LEGACY_LLM_EXTENSION_IDS: &[&str] = &[ /// Migrates legacy LLM provider extensions by auto-enabling env var reading /// if the env var is currently present in the environment. /// -/// This is idempotent: if the provider is already in `allowed_env_var_providers`, +/// This is idempotent: if the env var is already in `allowed_env_vars`, /// we skip. This means if a user explicitly removes it, it will be re-added on /// next launch if the env var is still set - but that's predictable behavior. fn migrate_legacy_llm_provider_env_var(manifest: &ExtensionManifest, cx: &mut App) { @@ -113,48 +113,51 @@ fn migrate_legacy_llm_provider_env_var(manifest: &ExtensionManifest, cx: &mut Ap let Some(auth_config) = &provider_entry.auth else { continue; }; - let Some(env_var_name) = &auth_config.env_var else { + let Some(env_vars) = &auth_config.env_vars else { continue; }; - let full_provider_id: Arc = format!("{}:{}", manifest.id, provider_id).into(); + let full_provider_id = format!("{}:{}", manifest.id, provider_id); - // Check if the env var is present and non-empty - let env_var_is_set = std::env::var(env_var_name) - .map(|v| !v.is_empty()) - .unwrap_or(false); + // For each env var, check if it's set and enable it if so + for env_var_name in env_vars { + let env_var_is_set = std::env::var(env_var_name) + .map(|v| !v.is_empty()) + .unwrap_or(false); - // If env var isn't set, no need to do anything - if !env_var_is_set { - continue; - } + if !env_var_is_set { + continue; + } - // Check if already enabled in settings - let already_enabled = ExtensionSettings::get_global(cx) - .allowed_env_var_providers - .contains(full_provider_id.as_ref()); + let settings_key: Arc = format!("{}:{}", full_provider_id, env_var_name).into(); - if already_enabled { - continue; - } + // Check if already enabled in settings + let already_enabled = ExtensionSettings::get_global(cx) + .allowed_env_vars + .contains(settings_key.as_ref()); - // Enable env var reading since the env var is set - settings::update_settings_file(::global(cx), cx, { - let full_provider_id = full_provider_id.clone(); - move |settings, _| { - let providers = settings - .extension - .allowed_env_var_providers - .get_or_insert_with(Vec::new); + if already_enabled { + continue; + } - if !providers - .iter() - .any(|id| id.as_ref() == full_provider_id.as_ref()) - { - providers.push(full_provider_id); + // Enable env var reading since the env var is set + settings::update_settings_file(::global(cx), cx, { + let settings_key = settings_key.clone(); + move |settings, _| { + let allowed = settings + .extension + .allowed_env_vars + .get_or_insert_with(Vec::new); + + if !allowed + .iter() + .any(|id| id.as_ref() == settings_key.as_ref()) + { + allowed.push(settings_key); + } } - } - }); + }); + } } } diff --git a/crates/extension_host/src/extension_settings.rs b/crates/extension_host/src/extension_settings.rs index 3322ea4068cc08ef8f5257e670ad8cb7088b57b7..a988804cd5556ce1a9b95318ebaddcabd732b20b 100644 --- a/crates/extension_host/src/extension_settings.rs +++ b/crates/extension_host/src/extension_settings.rs @@ -17,9 +17,9 @@ pub struct ExtensionSettings { pub auto_update_extensions: HashMap, bool>, pub granted_capabilities: Vec, /// The extension language model providers that are allowed to read API keys - /// from environment variables. Each entry is a provider ID in the format - /// "extension_id:provider_id". - pub allowed_env_var_providers: HashSet>, + /// from environment variables. Each entry is in the format + /// "extension_id:provider_id:ENV_VAR_NAME". + pub allowed_env_vars: HashSet>, } impl ExtensionSettings { @@ -64,9 +64,9 @@ impl Settings for ExtensionSettings { } }) .collect(), - allowed_env_var_providers: content + allowed_env_vars: content .extension - .allowed_env_var_providers + .allowed_env_vars .clone() .unwrap_or_default() .into_iter() diff --git a/crates/extension_host/src/wasm_host/llm_provider.rs b/crates/extension_host/src/wasm_host/llm_provider.rs index 15a3434448a271efb9e0e832ec18d1ddca1cb32e..ab134b14d3c21262d6b8f9f17667c9bd9de6f3df 100644 --- a/crates/extension_host/src/wasm_host/llm_provider.rs +++ b/crates/extension_host/src/wasm_host/llm_provider.rs @@ -1,5 +1,6 @@ use crate::ExtensionSettings; use crate::wasm_host::WasmExtension; +use collections::HashSet; use crate::wasm_host::wit::{ LlmCompletionEvent, LlmCompletionRequest, LlmImageData, LlmMessageContent, LlmMessageRole, @@ -46,8 +47,10 @@ pub struct ExtensionLanguageModelProvider { pub struct ExtensionLlmProviderState { is_authenticated: bool, available_models: Vec, - env_var_allowed: bool, - api_key_from_env: bool, + /// Set of env var names that are allowed to be read for this provider. + allowed_env_vars: HashSet, + /// If authenticated via env var, which one was used. + env_var_name_used: Option, } impl EventEmitter<()> for ExtensionLlmProviderState {} @@ -63,31 +66,40 @@ impl ExtensionLanguageModelProvider { cx: &mut App, ) -> Self { let provider_id_string = format!("{}:{}", extension.manifest.id, provider_info.id); - let env_var_allowed = ExtensionSettings::get_global(cx) - .allowed_env_var_providers - .contains(provider_id_string.as_str()); - - let (is_authenticated, api_key_from_env) = - if env_var_allowed && auth_config.as_ref().is_some_and(|c| c.env_var.is_some()) { - let env_var_name = auth_config.as_ref().unwrap().env_var.as_ref().unwrap(); - if let Ok(value) = std::env::var(env_var_name) { - if !value.is_empty() { - (true, true) - } else { - (is_authenticated, false) - } - } else { - (is_authenticated, false) + + // Build set of allowed env vars for this provider + let settings = ExtensionSettings::get_global(cx); + let mut allowed_env_vars = HashSet::default(); + if let Some(env_vars) = auth_config.as_ref().and_then(|c| c.env_vars.as_ref()) { + for env_var_name in env_vars { + let key = format!("{}:{}", provider_id_string, env_var_name); + if settings.allowed_env_vars.contains(key.as_str()) { + allowed_env_vars.insert(env_var_name.clone()); } - } else { - (is_authenticated, false) - }; + } + } + + // Check if any allowed env var is set + let env_var_name_used = allowed_env_vars.iter().find_map(|env_var_name| { + if let Ok(value) = std::env::var(env_var_name) { + if !value.is_empty() { + return Some(env_var_name.clone()); + } + } + None + }); + + let is_authenticated = if env_var_name_used.is_some() { + true + } else { + is_authenticated + }; let state = cx.new(|_| ExtensionLlmProviderState { is_authenticated, available_models: models, - env_var_allowed, - api_key_from_env, + allowed_env_vars, + env_var_name_used, }); Self { @@ -184,18 +196,19 @@ impl LanguageModelProvider for ExtensionLanguageModelProvider { return true; } - // Also check env var dynamically (in case migration happened after provider creation) + // Also check env var dynamically (in case settings changed after provider creation) if let Some(ref auth_config) = self.auth_config { - if let Some(ref env_var_name) = auth_config.env_var { + if let Some(ref env_vars) = auth_config.env_vars { let provider_id_string = self.provider_id_string(); - let env_var_allowed = ExtensionSettings::get_global(cx) - .allowed_env_var_providers - .contains(provider_id_string.as_str()); - - if env_var_allowed { - if let Ok(value) = std::env::var(env_var_name) { - if !value.is_empty() { - return true; + let settings = ExtensionSettings::get_global(cx); + + for env_var_name in env_vars { + let key = format!("{}:{}", provider_id_string, env_var_name); + if settings.allowed_env_vars.contains(key.as_str()) { + if let Ok(value) = std::env::var(env_var_name) { + if !value.is_empty() { + return true; + } } } } @@ -409,11 +422,11 @@ impl ExtensionProviderConfigurationView { let state = self.state.clone(); // Check if we should use env var (already set in state during provider construction) - let api_key_from_env = self.state.read(cx).api_key_from_env; + let using_env_var = self.state.read(cx).env_var_name_used.is_some(); cx.spawn(async move |this, cx| { // If using env var, we're already authenticated - if api_key_from_env { + if using_env_var { this.update(cx, |this, cx| { this.loading_credentials = false; cx.notify(); @@ -448,76 +461,72 @@ impl ExtensionProviderConfigurationView { .detach(); } - fn toggle_env_var_permission(&mut self, cx: &mut Context) { - let full_provider_id: Arc = self.full_provider_id.clone().into(); - let env_var_name = match &self.auth_config { - Some(config) => config.env_var.clone(), - None => return, - }; + fn toggle_env_var_permission(&mut self, env_var_name: String, cx: &mut Context) { + let full_provider_id = self.full_provider_id.clone(); + let settings_key: Arc = format!("{}:{}", full_provider_id, env_var_name).into(); let state = self.state.clone(); - let currently_allowed = self.state.read(cx).env_var_allowed; + let currently_allowed = self.state.read(cx).allowed_env_vars.contains(&env_var_name); // Update settings file - settings::update_settings_file(::global(cx), cx, move |settings, _| { - let providers = settings - .extension - .allowed_env_var_providers - .get_or_insert_with(Vec::new); - - if currently_allowed { - providers.retain(|id| id.as_ref() != full_provider_id.as_ref()); - } else { - if !providers - .iter() - .any(|id| id.as_ref() == full_provider_id.as_ref()) - { - providers.push(full_provider_id.clone()); + settings::update_settings_file(::global(cx), cx, { + let settings_key = settings_key.clone(); + move |settings, _| { + let allowed = settings + .extension + .allowed_env_vars + .get_or_insert_with(Vec::new); + + if currently_allowed { + allowed.retain(|id| id.as_ref() != settings_key.as_ref()); + } else { + if !allowed + .iter() + .any(|id| id.as_ref() == settings_key.as_ref()) + { + allowed.push(settings_key.clone()); + } } } }); // Update local state let new_allowed = !currently_allowed; - let new_from_env = if new_allowed { - if let Some(var_name) = &env_var_name { - if let Ok(value) = std::env::var(var_name) { - !value.is_empty() - } else { - false - } - } else { - false - } - } else { - false - }; + let env_var_name_clone = env_var_name.clone(); state.update(cx, |state, cx| { - state.env_var_allowed = new_allowed; - state.api_key_from_env = new_from_env; - if new_from_env { - state.is_authenticated = true; + if new_allowed { + state.allowed_env_vars.insert(env_var_name_clone.clone()); + // Check if this env var is set and update env_var_name_used + if let Ok(value) = std::env::var(&env_var_name_clone) { + if !value.is_empty() && state.env_var_name_used.is_none() { + state.env_var_name_used = Some(env_var_name_clone); + state.is_authenticated = true; + } + } + } else { + state.allowed_env_vars.remove(&env_var_name_clone); + // If this was the env var being used, clear it and find another + if state.env_var_name_used.as_ref() == Some(&env_var_name_clone) { + state.env_var_name_used = state.allowed_env_vars.iter().find_map(|var| { + if let Ok(value) = std::env::var(var) { + if !value.is_empty() { + return Some(var.clone()); + } + } + None + }); + if state.env_var_name_used.is_none() { + // No env var auth available, need to check keychain + state.is_authenticated = false; + } + } } cx.notify(); }); - // If env var is being enabled, clear any stored keychain credentials - // so there's only one source of truth for the API key - if new_allowed { - let credential_key = self.credential_key.clone(); - let credentials_provider = ::global(cx); - cx.spawn(async move |_this, cx| { - credentials_provider - .delete_credentials(&credential_key, cx) - .await - .log_err(); - }) - .detach(); - } - - // If env var is being disabled, reload credentials from keychain - if !new_allowed { + // If all env vars are being disabled, reload credentials from keychain + if !new_allowed && self.state.read(cx).allowed_env_vars.is_empty() { self.reload_keychain_credentials(cx); } @@ -760,8 +769,8 @@ impl gpui::Render for ExtensionProviderConfigurationView { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let is_loading = self.loading_settings || self.loading_credentials; let is_authenticated = self.is_authenticated(cx); - let env_var_allowed = self.state.read(cx).env_var_allowed; - let api_key_from_env = self.state.read(cx).api_key_from_env; + let allowed_env_vars = self.state.read(cx).allowed_env_vars.clone(); + let env_var_name_used = self.state.read(cx).env_var_name_used.clone(); let has_oauth = self.has_oauth_config(); let has_api_key = self.has_api_key_config(); @@ -780,95 +789,83 @@ impl gpui::Render for ExtensionProviderConfigurationView { content = content.child(MarkdownElement::new(markdown.clone(), style)); } - // Render env var checkbox if the extension specifies an env var + // Render env var checkboxes - one for each env var the extension declares if let Some(auth_config) = &self.auth_config { - if let Some(env_var_name) = &auth_config.env_var { - let env_var_name = env_var_name.clone(); - let checkbox_label = - format!("Read API key from {} environment variable", env_var_name); - - content = content.child( - h_flex() - .gap_2() - .child( - ui::Checkbox::new("env-var-permission", env_var_allowed.into()) - .on_click(cx.listener(|this, _, _window, cx| { - this.toggle_env_var_permission(cx); - })), - ) - .child(Label::new(checkbox_label).size(LabelSize::Small)), - ); + if let Some(env_vars) = &auth_config.env_vars { + for env_var_name in env_vars { + let is_allowed = allowed_env_vars.contains(env_var_name); + let checkbox_label = + format!("Read API key from {} environment variable", env_var_name); + let env_var_for_click = env_var_name.clone(); - // Show status if env var is allowed - if env_var_allowed { - if api_key_from_env { - let tooltip_label = format!( - "To reset this API key, unset the {} environment variable.", - env_var_name - ); - content = content.child( - h_flex() - .mt_0p5() - .p_1() - .justify_between() - .rounded_md() - .border_1() - .border_color(cx.theme().colors().border) - .bg(cx.theme().colors().background) - .child( - h_flex() - .flex_1() - .min_w_0() - .gap_1() - .child( - ui::Icon::new(ui::IconName::Check) - .color(Color::Success), - ) - .child( - Label::new(format!( - "API key set in {} environment variable", - env_var_name - )) - .truncate(), - ), - ) - .child( - ui::Button::new("reset-key", "Reset Key") - .label_size(LabelSize::Small) - .icon(ui::IconName::Undo) - .icon_size(ui::IconSize::Small) - .icon_color(Color::Muted) - .icon_position(ui::IconPosition::Start) - .disabled(true) - .tooltip(ui::Tooltip::text(tooltip_label)), - ), - ); - return content.into_any_element(); - } else { - content = content.child( - h_flex() - .gap_2() - .child( - ui::Icon::new(ui::IconName::Warning) - .color(Color::Warning) - .size(ui::IconSize::Small), + content = content.child( + h_flex() + .gap_2() + .child( + ui::Checkbox::new( + SharedString::from(format!("env-var-{}", env_var_name)), + is_allowed.into(), ) - .child( - Label::new(format!( - "{} is not set or empty. You can set it and restart Zed, or use another authentication method below.", - env_var_name - )) - .color(Color::Warning) - .size(LabelSize::Small), - ), - ); - } + .on_click(cx.listener( + move |this, _, _window, cx| { + this.toggle_env_var_permission( + env_var_for_click.clone(), + cx, + ); + }, + )), + ) + .child(Label::new(checkbox_label).size(LabelSize::Small)), + ); + } + + // Show status if any env var is being used + if let Some(used_var) = &env_var_name_used { + let tooltip_label = format!( + "To reset this API key, unset the {} environment variable.", + used_var + ); + content = content.child( + h_flex() + .mt_0p5() + .p_1() + .justify_between() + .rounded_md() + .border_1() + .border_color(cx.theme().colors().border) + .bg(cx.theme().colors().background) + .child( + h_flex() + .flex_1() + .min_w_0() + .gap_1() + .child(ui::Icon::new(ui::IconName::Check).color(Color::Success)) + .child( + Label::new(format!( + "API key set in {} environment variable", + used_var + )) + .truncate(), + ), + ) + .child( + ui::Button::new("reset-key", "Reset Key") + .label_size(LabelSize::Small) + .icon(ui::IconName::Undo) + .icon_size(ui::IconSize::Small) + .icon_color(Color::Muted) + .icon_position(ui::IconPosition::Start) + .disabled(true) + .tooltip(ui::Tooltip::text(tooltip_label)), + ), + ); + return content.into_any_element(); } } } // If authenticated, show success state with sign out option - if is_authenticated && !api_key_from_env { + if is_authenticated && env_var_name_used.is_none() { let reset_label = if has_oauth && !has_api_key { "Sign Out" } else { @@ -915,7 +912,7 @@ impl gpui::Render for ExtensionProviderConfigurationView { } // Not authenticated - show available auth options - if !api_key_from_env { + if env_var_name_used.is_none() { // Render OAuth sign-in button if configured if has_oauth { let oauth_config = self.oauth_config(); diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs index a95ad0a8ac6bac08bfa3ad1c0e53e72e64cdef62..9182b8c2994b81fe4b530a99cf6344a2fdedfa6b 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs @@ -1115,38 +1115,44 @@ impl llm_provider::Host for WasmState { async fn get_credential(&mut self, provider_id: String) -> wasmtime::Result> { let extension_id = self.manifest.id.clone(); - // Check if this provider has an env var configured and if the user has allowed it - let env_var_name = self + // Check if this provider has env vars configured and if the user has allowed any of them + let env_vars = self .manifest .language_model_providers .get(&Arc::::from(provider_id.as_str())) .and_then(|entry| entry.auth.as_ref()) - .and_then(|auth| auth.env_var.clone()); - - if let Some(env_var_name) = env_var_name { - let full_provider_id: Arc = format!("{}:{}", extension_id, provider_id).into(); - // Read settings dynamically to get current allowed_env_var_providers - let is_allowed = self - .on_main_thread({ - let full_provider_id = full_provider_id.clone(); - move |cx| { - async move { - cx.update(|cx| { - crate::extension_settings::ExtensionSettings::get_global(cx) - .allowed_env_var_providers - .contains(&full_provider_id) - }) + .and_then(|auth| auth.env_vars.clone()); + + if let Some(env_vars) = env_vars { + let full_provider_id = format!("{}:{}", extension_id, provider_id); + + // Check each env var to see if it's allowed and set + for env_var_name in &env_vars { + let settings_key: Arc = + format!("{}:{}", full_provider_id, env_var_name).into(); + + let is_allowed = self + .on_main_thread({ + let settings_key = settings_key.clone(); + move |cx| { + async move { + cx.update(|cx| { + crate::extension_settings::ExtensionSettings::get_global(cx) + .allowed_env_vars + .contains(&settings_key) + }) + } + .boxed_local() } - .boxed_local() - } - }) - .await - .unwrap_or(false); + }) + .await + .unwrap_or(false); - if is_allowed { - if let Ok(value) = env::var(&env_var_name) { - if !value.is_empty() { - return Ok(Some(value)); + if is_allowed { + if let Ok(value) = env::var(env_var_name) { + if !value.is_empty() { + return Ok(Some(value)); + } } } } @@ -1220,9 +1226,11 @@ impl llm_provider::Host for WasmState { let mut allowed_provider_id: Option> = None; for (provider_id, provider_entry) in &self.manifest.language_model_providers { if let Some(auth_config) = &provider_entry.auth { - if auth_config.env_var.as_deref() == Some(&name) { - allowed_provider_id = Some(provider_id.clone()); - break; + if let Some(env_vars) = &auth_config.env_vars { + if env_vars.iter().any(|v| v == &name) { + allowed_provider_id = Some(provider_id.clone()); + break; + } } } } @@ -1237,18 +1245,17 @@ impl llm_provider::Host for WasmState { return Ok(None); }; - // Check if the user has allowed this provider to read env vars - // Read settings dynamically to get current allowed_env_var_providers - let full_provider_id: Arc = format!("{}:{}", extension_id, provider_id).into(); + // Check if the user has allowed this specific env var + let settings_key: Arc = format!("{}:{}:{}", extension_id, provider_id, name).into(); let is_allowed = self .on_main_thread({ - let full_provider_id = full_provider_id.clone(); + let settings_key = settings_key.clone(); move |cx| { async move { cx.update(|cx| { crate::extension_settings::ExtensionSettings::get_global(cx) - .allowed_env_var_providers - .contains(&full_provider_id) + .allowed_env_vars + .contains(&settings_key) }) } .boxed_local() diff --git a/crates/settings/src/settings_content/extension.rs b/crates/settings/src/settings_content/extension.rs index 64df163f4ec961cf6bfc469c18ac0f8884c39f0b..283c4d3d0c68a04c783f89b469aafa364102747c 100644 --- a/crates/settings/src/settings_content/extension.rs +++ b/crates/settings/src/settings_content/extension.rs @@ -21,11 +21,11 @@ pub struct ExtensionSettingsContent { /// The capabilities granted to extensions. pub granted_extension_capabilities: Option>, /// Extension language model providers that are allowed to read API keys from - /// environment variables. Each entry is a provider ID in the format - /// "extension_id:provider_id" (e.g., "openai:openai"). + /// environment variables. Each entry is in the format + /// "extension_id:provider_id:ENV_VAR_NAME" (e.g., "google-ai:google-ai:GEMINI_API_KEY"). /// /// Default: [] - pub allowed_env_var_providers: Option>>, + pub allowed_env_vars: Option>>, } /// A capability for an extension. diff --git a/extensions/anthropic/extension.toml b/extensions/anthropic/extension.toml index c37b8aca34f6cfcf2d70715eb24383077e207366..3e8b2b32e3f0c38ba7eafded3f9aa756288df693 100644 --- a/extensions/anthropic/extension.toml +++ b/extensions/anthropic/extension.toml @@ -10,4 +10,4 @@ repository = "https://github.com/zed-industries/zed" name = "Anthropic" [language_model_providers.anthropic.auth] -env_var = "ANTHROPIC_API_KEY" \ No newline at end of file +env_vars = ["ANTHROPIC_API_KEY"] diff --git a/extensions/google-ai/extension.toml b/extensions/google-ai/extension.toml index 1b1cb382a7835d79adf93af225b19ced9a5f551c..aebe57f396f39e971f8d7647299ccad75448cce9 100644 --- a/extensions/google-ai/extension.toml +++ b/extensions/google-ai/extension.toml @@ -10,4 +10,4 @@ repository = "https://github.com/zed-industries/zed" name = "Google AI" [language_model_providers.google-ai.auth] -env_var = "GEMINI_API_KEY" \ No newline at end of file +env_vars = ["GEMINI_API_KEY", "GOOGLE_AI_API_KEY"] diff --git a/extensions/openai/extension.toml b/extensions/openai/extension.toml index 94788688716f1d8a6534684b0cb39febf98585ef..dd185853699198d27ea1a9f55bedd86b74ce7b4c 100644 --- a/extensions/openai/extension.toml +++ b/extensions/openai/extension.toml @@ -10,4 +10,4 @@ repository = "https://github.com/zed-industries/zed" name = "OpenAI" [language_model_providers.openai.auth] -env_var = "OPENAI_API_KEY" \ No newline at end of file +env_vars = ["OPENAI_API_KEY"] diff --git a/extensions/openrouter/extension.toml b/extensions/openrouter/extension.toml index 610f2b9624586cb10c0afd374bc32c59f2e4d0fc..d77bcbc4576f5b89e729111bcce00b30e4c69a99 100644 --- a/extensions/openrouter/extension.toml +++ b/extensions/openrouter/extension.toml @@ -10,4 +10,4 @@ repository = "https://github.com/zed-industries/zed" name = "OpenRouter" [language_model_providers.openrouter.auth] -env_var = "OPENROUTER_API_KEY" \ No newline at end of file +env_vars = ["OPENROUTER_API_KEY"]