Specify env vars for the builtin extensions

Richard Feldman created

Change summary

crates/extension_host/src/extension_host.rs | 79 +++++++++++++++++++++++
extensions/anthropic/extension.toml         |  3 
extensions/copilot_chat/extension.toml      |  3 
extensions/google-ai/extension.toml         |  3 
extensions/open_router/extension.toml       |  3 
extensions/openai/extension.toml            |  3 
6 files changed, 94 insertions(+)

Detailed changes

crates/extension_host/src/extension_host.rs 🔗

@@ -80,6 +80,80 @@ pub use extension_settings::ExtensionSettings;
 pub const RELOAD_DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
 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.
+const LEGACY_LLM_EXTENSION_IDS: &[&str] = &[
+    "anthropic",
+    "copilot_chat",
+    "google-ai",
+    "open_router",
+    "openai",
+];
+
+/// Migrates legacy LLM provider extensions by auto-enabling env var reading
+/// if the env var is currently present in the environment.
+fn migrate_legacy_llm_provider_env_var(manifest: &ExtensionManifest, cx: &mut App) {
+    // Only apply migration to known legacy LLM extensions
+    if !LEGACY_LLM_EXTENSION_IDS.contains(&manifest.id.as_ref()) {
+        return;
+    }
+
+    // Check each provider in the manifest
+    for (provider_id, provider_entry) in &manifest.language_model_providers {
+        let Some(auth_config) = &provider_entry.auth else {
+            continue;
+        };
+        let Some(env_var_name) = &auth_config.env_var else {
+            continue;
+        };
+
+        // 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);
+
+        if !env_var_is_set {
+            continue;
+        }
+
+        let full_provider_id: Arc<str> = format!("{}:{}", manifest.id, provider_id).into();
+
+        // Check if already in settings
+        let already_allowed = ExtensionSettings::get_global(cx)
+            .allowed_env_var_providers
+            .contains(full_provider_id.as_ref());
+
+        if already_allowed {
+            continue;
+        }
+
+        // Auto-enable env var reading for this provider
+        log::info!(
+            "Migrating legacy LLM provider {}: auto-enabling {} env var reading",
+            full_provider_id,
+            env_var_name
+        );
+
+        settings::update_settings_file(<dyn fs::Fs>::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 !providers
+                    .iter()
+                    .any(|id| id.as_ref() == full_provider_id.as_ref())
+                {
+                    providers.push(full_provider_id);
+                }
+            }
+        });
+    }
+}
+
 /// The current extension [`SchemaVersion`] supported by Zed.
 const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(1);
 
@@ -781,6 +855,11 @@ impl ExtensionStore {
 
             if let ExtensionOperation::Install = operation {
                 this.update(cx, |this, cx| {
+                    // Check for legacy LLM provider migration
+                    if let Some(manifest) = this.extension_manifest_for_id(&extension_id) {
+                        migrate_legacy_llm_provider_env_var(&manifest, cx);
+                    }
+
                     cx.emit(Event::ExtensionInstalled(extension_id.clone()));
                     if let Some(events) = ExtensionEvents::try_global(cx)
                         && let Some(manifest) = this.extension_manifest_for_id(&extension_id)

extensions/anthropic/extension.toml 🔗

@@ -8,3 +8,6 @@ repository = "https://github.com/zed-industries/zed"
 
 [language_model_providers.anthropic]
 name = "Anthropic"
+
+[language_model_providers.anthropic.auth]
+env_var = "ANTHROPIC_API_KEY"

extensions/copilot_chat/extension.toml 🔗

@@ -8,3 +8,6 @@ repository = "https://github.com/zed-industries/zed"
 
 [language_model_providers.copilot_chat]
 name = "Copilot Chat"
+
+[language_model_providers.copilot_chat.auth]
+env_var = "GH_COPILOT_TOKEN"

extensions/google-ai/extension.toml 🔗

@@ -8,3 +8,6 @@ repository = "https://github.com/zed-industries/zed"
 
 [language_model_providers.google-ai]
 name = "Google AI"
+
+[language_model_providers.google-ai.auth]
+env_var = "GEMINI_API_KEY"

extensions/open_router/extension.toml 🔗

@@ -8,3 +8,6 @@ repository = "https://github.com/zed-industries/zed"
 
 [language_model_providers.open_router]
 name = "OpenRouter"
+
+[language_model_providers.open_router.auth]
+env_var = "OPENROUTER_API_KEY"

extensions/openai/extension.toml 🔗

@@ -8,3 +8,6 @@ repository = "https://github.com/zed-industries/zed"
 
 [language_model_providers.openai]
 name = "OpenAI"
+
+[language_model_providers.openai.auth]
+env_var = "OPENAI_API_KEY"