diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index d9824588b440f3ec1a32bd740936a58dd59e7239..d344c4db41c0258cfea131ed996b02b9454a8954 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -88,7 +88,7 @@ const FS_WATCH_LATENCY: Duration = Duration::from_millis(100); /// Extension IDs that are being migrated from hardcoded LLM providers. /// For backwards compatibility, if the user has the corresponding env var set, /// we automatically enable env var reading for these extensions on first install. -const LEGACY_LLM_EXTENSION_IDS: &[&str] = &[ +pub const LEGACY_LLM_EXTENSION_IDS: &[&str] = &[ "anthropic", "copilot-chat", "google-ai", @@ -133,7 +133,7 @@ fn migrate_legacy_llm_provider_env_var(manifest: &ExtensionManifest, cx: &mut Ap // Check if already enabled in settings let already_enabled = ExtensionSettings::get_global(cx) - .allowed_env_vars + .allowed_env_var_providers .contains(settings_key.as_ref()); if already_enabled { @@ -146,7 +146,7 @@ fn migrate_legacy_llm_provider_env_var(manifest: &ExtensionManifest, cx: &mut Ap move |settings, _| { let allowed = settings .extension - .allowed_env_vars + .allowed_env_var_providers .get_or_insert_with(Vec::new); if !allowed @@ -1192,9 +1192,16 @@ impl ExtensionStore { } } - fs.create_symlink(output_path, extension_source_path) + fs.create_symlink(output_path, extension_source_path.clone()) .await?; + // Re-load manifest and run migration before reload so settings are updated before providers are registered + let manifest_for_migration = + ExtensionManifest::load(fs.clone(), &extension_source_path).await?; + this.update(cx, |_this, cx| { + migrate_legacy_llm_provider_env_var(&manifest_for_migration, cx); + })?; + this.update(cx, |this, cx| this.reload(None, cx))?.await; this.update(cx, |this, cx| { cx.emit(Event::ExtensionInstalled(extension_id.clone())); diff --git a/crates/extension_host/src/extension_settings.rs b/crates/extension_host/src/extension_settings.rs index a988804cd5556ce1a9b95318ebaddcabd732b20b..665761bf77ac681a5511d8a98862566d28946f5c 100644 --- a/crates/extension_host/src/extension_settings.rs +++ b/crates/extension_host/src/extension_settings.rs @@ -19,7 +19,7 @@ pub struct ExtensionSettings { /// The extension language model providers that are allowed to read API keys /// from environment variables. Each entry is in the format /// "extension_id:provider_id:ENV_VAR_NAME". - pub allowed_env_vars: HashSet>, + pub allowed_env_var_providers: HashSet>, } impl ExtensionSettings { @@ -64,9 +64,9 @@ impl Settings for ExtensionSettings { } }) .collect(), - allowed_env_vars: content + allowed_env_var_providers: content .extension - .allowed_env_vars + .allowed_env_var_providers .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 ab134b14d3c21262d6b8f9f17667c9bd9de6f3df..b28fd472dfb0e2cf61a4c8b524a6e0e15f23118c 100644 --- a/crates/extension_host/src/wasm_host/llm_provider.rs +++ b/crates/extension_host/src/wasm_host/llm_provider.rs @@ -1,4 +1,5 @@ use crate::ExtensionSettings; +use crate::LEGACY_LLM_EXTENSION_IDS; use crate::wasm_host::WasmExtension; use collections::HashSet; @@ -69,11 +70,20 @@ impl ExtensionLanguageModelProvider { // Build set of allowed env vars for this provider let settings = ExtensionSettings::get_global(cx); + let is_legacy_extension = + LEGACY_LLM_EXTENSION_IDS.contains(&extension.manifest.id.as_ref()); + 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()) { + // For legacy extensions, auto-allow if env var is set (migration will persist this) + let env_var_is_set = std::env::var(env_var_name) + .map(|v| !v.is_empty()) + .unwrap_or(false); + if settings.allowed_env_var_providers.contains(key.as_str()) + || (is_legacy_extension && env_var_is_set) + { allowed_env_vars.insert(env_var_name.clone()); } } @@ -201,10 +211,18 @@ impl LanguageModelProvider for ExtensionLanguageModelProvider { if let Some(ref env_vars) = auth_config.env_vars { let provider_id_string = self.provider_id_string(); let settings = ExtensionSettings::get_global(cx); + let is_legacy_extension = + LEGACY_LLM_EXTENSION_IDS.contains(&self.extension.manifest.id.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()) { + // For legacy extensions, auto-allow if env var is set + let env_var_is_set = std::env::var(env_var_name) + .map(|v| !v.is_empty()) + .unwrap_or(false); + if settings.allowed_env_var_providers.contains(key.as_str()) + || (is_legacy_extension && env_var_is_set) + { if let Ok(value) = std::env::var(env_var_name) { if !value.is_empty() { return true; @@ -474,7 +492,7 @@ impl ExtensionProviderConfigurationView { move |settings, _| { let allowed = settings .extension - .allowed_env_vars + .allowed_env_var_providers .get_or_insert_with(Vec::new); if currently_allowed { 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 9182b8c2994b81fe4b530a99cf6344a2fdedfa6b..bc1c6832814ea034d8c290f1786af0cc984505a6 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 @@ -1114,6 +1114,7 @@ impl ExtensionImports for WasmState { impl llm_provider::Host for WasmState { async fn get_credential(&mut self, provider_id: String) -> wasmtime::Result> { let extension_id = self.manifest.id.clone(); + let is_legacy_extension = crate::LEGACY_LLM_EXTENSION_IDS.contains(&extension_id.as_ref()); // Check if this provider has env vars configured and if the user has allowed any of them let env_vars = self @@ -1131,6 +1132,11 @@ impl llm_provider::Host for WasmState { let settings_key: Arc = format!("{}:{}", full_provider_id, env_var_name).into(); + // For legacy extensions, auto-allow if env var is set + let env_var_is_set = env::var(env_var_name) + .map(|v| !v.is_empty()) + .unwrap_or(false); + let is_allowed = self .on_main_thread({ let settings_key = settings_key.clone(); @@ -1138,7 +1144,7 @@ impl llm_provider::Host for WasmState { async move { cx.update(|cx| { crate::extension_settings::ExtensionSettings::get_global(cx) - .allowed_env_vars + .allowed_env_var_providers .contains(&settings_key) }) } @@ -1148,7 +1154,7 @@ impl llm_provider::Host for WasmState { .await .unwrap_or(false); - if is_allowed { + if is_allowed || (is_legacy_extension && env_var_is_set) { if let Ok(value) = env::var(env_var_name) { if !value.is_empty() { return Ok(Some(value)); @@ -1247,6 +1253,11 @@ impl llm_provider::Host for WasmState { // Check if the user has allowed this specific env var let settings_key: Arc = format!("{}:{}:{}", extension_id, provider_id, name).into(); + let is_legacy_extension = crate::LEGACY_LLM_EXTENSION_IDS.contains(&extension_id.as_ref()); + + // For legacy extensions, auto-allow if env var is set + let env_var_is_set = env::var(&name).map(|v| !v.is_empty()).unwrap_or(false); + let is_allowed = self .on_main_thread({ let settings_key = settings_key.clone(); @@ -1254,7 +1265,7 @@ impl llm_provider::Host for WasmState { async move { cx.update(|cx| { crate::extension_settings::ExtensionSettings::get_global(cx) - .allowed_env_vars + .allowed_env_var_providers .contains(&settings_key) }) } @@ -1264,7 +1275,7 @@ impl llm_provider::Host for WasmState { .await .unwrap_or(false); - if !is_allowed { + if !is_allowed && !(is_legacy_extension && env_var_is_set) { log::debug!( "Extension {} provider {} is not allowed to read env var {}", extension_id, diff --git a/crates/settings/src/settings_content/extension.rs b/crates/settings/src/settings_content/extension.rs index 283c4d3d0c68a04c783f89b469aafa364102747c..61afe705a5576b6c3eca718bfd5281ae9818b135 100644 --- a/crates/settings/src/settings_content/extension.rs +++ b/crates/settings/src/settings_content/extension.rs @@ -25,7 +25,7 @@ pub struct ExtensionSettingsContent { /// "extension_id:provider_id:ENV_VAR_NAME" (e.g., "google-ai:google-ai:GEMINI_API_KEY"). /// /// Default: [] - pub allowed_env_vars: Option>>, + pub allowed_env_var_providers: Option>>, } /// A capability for an extension.