diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index ff4cb5985763a0d250c2abcadf621e862311efd3..09e8259771668346c237c1cc05e6074ca3b37797 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -1,11 +1,6 @@ -mod anthropic_migration; mod capability_granter; -mod copilot_migration; pub mod extension_settings; -mod google_ai_migration; pub mod headless_host; -mod open_router_migration; -mod openai_migration; pub mod wasm_host; #[cfg(test)] @@ -17,14 +12,13 @@ use async_tar::Archive; use client::ExtensionProvides; use client::{Client, ExtensionMetadata, GetExtensionsResponse, proto, telemetry::Telemetry}; use collections::{BTreeMap, BTreeSet, HashSet, btree_map}; - pub use extension::ExtensionManifest; use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder}; use extension::{ ExtensionContextServerProxy, ExtensionDebugAdapterProviderProxy, ExtensionEvents, - ExtensionGrammarProxy, ExtensionHostProxy, ExtensionLanguageModelProviderProxy, - ExtensionLanguageProxy, ExtensionLanguageServerProxy, ExtensionSlashCommandProxy, - ExtensionSnippetProxy, ExtensionThemeProxy, + ExtensionGrammarProxy, ExtensionHostProxy, ExtensionLanguageProxy, + ExtensionLanguageServerProxy, ExtensionSlashCommandProxy, ExtensionSnippetProxy, + ExtensionThemeProxy, }; use fs::{Fs, RemoveOptions}; use futures::future::join_all; @@ -38,8 +32,8 @@ use futures::{ select_biased, }; use gpui::{ - App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Global, SharedString, Task, - WeakEntity, actions, + App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Global, Task, WeakEntity, + actions, }; use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; use language::{ @@ -59,28 +53,15 @@ use std::{ cmp::Ordering, path::{self, Path, PathBuf}, sync::Arc, - time::Duration, + time::{Duration, Instant}, }; use url::Url; use util::{ResultExt, paths::RemotePathBuf}; -use wasm_host::llm_provider::ExtensionLanguageModelProvider; use wasm_host::{ WasmExtension, WasmHost, - wit::{ - LlmCacheConfiguration, LlmModelInfo, LlmProviderInfo, is_supported_wasm_api_version, - wasm_api_version_range, - }, + wit::{is_supported_wasm_api_version, wasm_api_version_range}, }; -struct LlmProviderWithModels { - provider_info: LlmProviderInfo, - models: Vec, - cache_configs: collections::HashMap, - is_authenticated: bool, - icon_path: Option, - auth_config: Option, -} - pub use extension::{ ExtensionLibraryKind, GrammarManifestEntry, OldExtensionManifest, SchemaVersion, }; @@ -89,82 +70,6 @@ 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 on first install. -pub const LEGACY_LLM_EXTENSION_IDS: &[&str] = &[ - "anthropic", - "copilot-chat", - "google-ai", - "openrouter", - "openai", -]; - -/// 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 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) { - // 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_vars) = &auth_config.env_vars else { - continue; - }; - - let full_provider_id = format!("{}:{}", manifest.id, provider_id); - - // 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_is_set { - continue; - } - - let settings_key: Arc = format!("{}:{}", full_provider_id, env_var_name).into(); - - // Check if already enabled in settings - let already_enabled = ExtensionSettings::get_global(cx) - .allowed_env_var_providers - .contains(settings_key.as_ref()); - - if already_enabled { - continue; - } - - // 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_var_providers - .get_or_insert_with(Vec::new); - - if !allowed - .iter() - .any(|id| id.as_ref() == settings_key.as_ref()) - { - allowed.push(settings_key); - } - } - }); - } - } -} - /// The current extension [`SchemaVersion`] supported by Zed. const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(1); @@ -226,8 +131,6 @@ pub struct ExtensionStore { pub enum ExtensionOperation { Upgrade, Install, - /// Auto-install from settings - triggers legacy LLM provider migrations - AutoInstall, Remove, } @@ -710,60 +613,8 @@ impl ExtensionStore { cx.spawn(async move |this, cx| { for extension_id in extensions_to_install { - // When enabled, this checks if an extension exists locally in the repo's extensions/ - // directory and installs it as a dev extension instead of fetching from the registry. - // This is useful for testing auto-installed extensions before they've been published. - // Set to `true` only during local development/testing of new auto-install extensions. - #[cfg(debug_assertions)] - const DEBUG_ALLOW_UNPUBLISHED_AUTO_EXTENSIONS: bool = false; - - #[cfg(debug_assertions)] - if DEBUG_ALLOW_UNPUBLISHED_AUTO_EXTENSIONS { - let local_extension_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent() - .unwrap() - .parent() - .unwrap() - .join("extensions") - .join(extension_id.as_ref()); - - if local_extension_path.exists() { - // Force-remove existing extension directory if it exists and isn't a symlink - // This handles the case where the extension was previously installed from the registry - if let Some(installed_dir) = this - .update(cx, |this, _cx| this.installed_dir.clone()) - .ok() - { - let existing_path = installed_dir.join(extension_id.as_ref()); - if existing_path.exists() { - let metadata = std::fs::symlink_metadata(&existing_path); - let is_symlink = metadata.map(|m| m.is_symlink()).unwrap_or(false); - if !is_symlink { - if let Err(e) = std::fs::remove_dir_all(&existing_path) { - log::error!( - "Failed to remove existing extension directory {:?}: {}", - existing_path, - e - ); - } - } - } - } - - if let Some(task) = this - .update(cx, |this, cx| { - this.install_dev_extension(local_extension_path, cx) - }) - .ok() - { - task.await.log_err(); - } - continue; - } - } - this.update(cx, |this, cx| { - this.auto_install_latest_extension(extension_id.clone(), cx); + this.install_latest_extension(extension_id.clone(), cx); }) .ok(); } @@ -918,10 +769,7 @@ impl ExtensionStore { this.update(cx, |this, cx| this.reload(Some(extension_id.clone()), cx))? .await; - if matches!( - operation, - ExtensionOperation::Install | ExtensionOperation::AutoInstall - ) { + if let ExtensionOperation::Install = operation { this.update(cx, |this, cx| { cx.emit(Event::ExtensionInstalled(extension_id.clone())); if let Some(events) = ExtensionEvents::try_global(cx) @@ -931,27 +779,6 @@ impl ExtensionStore { this.emit(extension::Event::ExtensionInstalled(manifest.clone()), cx) }); } - - // Run legacy LLM provider migrations only for auto-installed extensions - if matches!(operation, ExtensionOperation::AutoInstall) { - if let Some(manifest) = this.extension_manifest_for_id(&extension_id) { - migrate_legacy_llm_provider_env_var(&manifest, cx); - } - copilot_migration::migrate_copilot_credentials_if_needed(&extension_id, cx); - anthropic_migration::migrate_anthropic_credentials_if_needed( - &extension_id, - cx, - ); - google_ai_migration::migrate_google_ai_credentials_if_needed( - &extension_id, - cx, - ); - openai_migration::migrate_openai_credentials_if_needed(&extension_id, cx); - open_router_migration::migrate_open_router_credentials_if_needed( - &extension_id, - cx, - ); - } }) .ok(); } @@ -961,24 +788,8 @@ impl ExtensionStore { } pub fn install_latest_extension(&mut self, extension_id: Arc, cx: &mut Context) { - self.install_latest_extension_with_operation(extension_id, ExtensionOperation::Install, cx); - } - - /// Auto-install an extension, triggering legacy LLM provider migrations. - fn auto_install_latest_extension(&mut self, extension_id: Arc, cx: &mut Context) { - self.install_latest_extension_with_operation( - extension_id, - ExtensionOperation::AutoInstall, - cx, - ); - } + log::info!("installing extension {extension_id} latest version"); - fn install_latest_extension_with_operation( - &mut self, - extension_id: Arc, - operation: ExtensionOperation, - cx: &mut Context, - ) { let schema_versions = schema_version_range(); let wasm_api_versions = wasm_api_version_range(ReleaseChannel::global(cx)); @@ -1001,8 +812,13 @@ impl ExtensionStore { return; }; - self.install_or_upgrade_extension_at_endpoint(extension_id, url, operation, cx) - .detach_and_log_err(cx); + self.install_or_upgrade_extension_at_endpoint( + extension_id, + url, + ExtensionOperation::Install, + cx, + ) + .detach_and_log_err(cx); } pub fn upgrade_extension( @@ -1021,6 +837,7 @@ impl ExtensionStore { operation: ExtensionOperation, cx: &mut Context, ) -> Task> { + log::info!("installing extension {extension_id} {version}"); let Some(url) = self .http_client .build_zed_api_url( @@ -1196,37 +1013,9 @@ impl ExtensionStore { } } - fs.create_symlink(output_path, extension_source_path.clone()) + fs.create_symlink(output_path, extension_source_path) .await?; - // Re-load manifest and run migrations 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); - // Also run credential migrations for dev extensions - copilot_migration::migrate_copilot_credentials_if_needed( - manifest_for_migration.id.as_ref(), - cx, - ); - anthropic_migration::migrate_anthropic_credentials_if_needed( - manifest_for_migration.id.as_ref(), - cx, - ); - google_ai_migration::migrate_google_ai_credentials_if_needed( - manifest_for_migration.id.as_ref(), - cx, - ); - openai_migration::migrate_openai_credentials_if_needed( - manifest_for_migration.id.as_ref(), - cx, - ); - open_router_migration::migrate_open_router_credentials_if_needed( - manifest_for_migration.id.as_ref(), - cx, - ); - })?; - this.update(cx, |this, cx| this.reload(None, cx))?.await; this.update(cx, |this, cx| { cx.emit(Event::ExtensionInstalled(extension_id.clone())); @@ -1345,6 +1134,18 @@ impl ExtensionStore { return Task::ready(()); } + let reload_count = extensions_to_unload + .iter() + .filter(|id| extensions_to_load.contains(id)) + .count(); + + log::info!( + "extensions updated. loading {}, reloading {}, unloading {}", + extensions_to_load.len() - reload_count, + reload_count, + extensions_to_unload.len() - reload_count + ); + let extension_ids = extensions_to_load .iter() .filter_map(|id| { @@ -1419,11 +1220,6 @@ impl ExtensionStore { for command_name in extension.manifest.slash_commands.keys() { self.proxy.unregister_slash_command(command_name.clone()); } - for provider_id in extension.manifest.language_model_providers.keys() { - let full_provider_id: Arc = format!("{}:{}", extension_id, provider_id).into(); - self.proxy - .unregister_language_model_provider(full_provider_id, cx); - } } self.wasm_extensions @@ -1562,11 +1358,7 @@ impl ExtensionStore { }) .await; - let mut wasm_extensions: Vec<( - Arc, - WasmExtension, - Vec, - )> = Vec::new(); + let mut wasm_extensions = Vec::new(); for extension in extension_entries { if extension.manifest.lib.kind.is_none() { continue; @@ -1584,149 +1376,7 @@ impl ExtensionStore { match wasm_extension { Ok(wasm_extension) => { - // Query for LLM providers if the manifest declares any - let mut llm_providers_with_models = Vec::new(); - if !extension.manifest.language_model_providers.is_empty() { - let providers_result = wasm_extension - .call(|ext, store| { - async move { ext.call_llm_providers(store).await }.boxed() - }) - .await; - - if let Ok(Ok(providers)) = providers_result { - for provider_info in providers { - let models_result = wasm_extension - .call({ - let provider_id = provider_info.id.clone(); - |ext, store| { - async move { - ext.call_llm_provider_models(store, &provider_id) - .await - } - .boxed() - } - }) - .await; - - let models: Vec = match models_result { - Ok(Ok(Ok(models))) => models, - Ok(Ok(Err(e))) => { - log::error!( - "Failed to get models for LLM provider {} in extension {}: {}", - provider_info.id, - extension.manifest.id, - e - ); - Vec::new() - } - Ok(Err(e)) => { - log::error!( - "Wasm error calling llm_provider_models for {} in extension {}: {:?}", - provider_info.id, - extension.manifest.id, - e - ); - Vec::new() - } - Err(e) => { - log::error!( - "Extension call failed for llm_provider_models {} in extension {}: {:?}", - provider_info.id, - extension.manifest.id, - e - ); - Vec::new() - } - }; - - // Query cache configurations for each model - let mut cache_configs = collections::HashMap::default(); - for model in &models { - let cache_config_result = wasm_extension - .call({ - let provider_id = provider_info.id.clone(); - let model_id = model.id.clone(); - |ext, store| { - async move { - ext.call_llm_cache_configuration( - store, - &provider_id, - &model_id, - ) - .await - } - .boxed() - } - }) - .await; - - if let Ok(Ok(Some(config))) = cache_config_result { - cache_configs.insert(model.id.clone(), config); - } - } - - // Query initial authentication state - let is_authenticated = wasm_extension - .call({ - let provider_id = provider_info.id.clone(); - |ext, store| { - async move { - ext.call_llm_provider_is_authenticated( - store, - &provider_id, - ) - .await - } - .boxed() - } - }) - .await - .unwrap_or(Ok(false)) - .unwrap_or(false); - - // Resolve icon path if provided - let icon_path = provider_info.icon.as_ref().map(|icon| { - let icon_file_path = extension_path.join(icon); - // Canonicalize to resolve symlinks (dev extensions are symlinked) - let absolute_icon_path = icon_file_path - .canonicalize() - .unwrap_or(icon_file_path) - .to_string_lossy() - .to_string(); - SharedString::from(absolute_icon_path) - }); - - let provider_id_arc: Arc = - provider_info.id.as_str().into(); - let auth_config = extension - .manifest - .language_model_providers - .get(&provider_id_arc) - .and_then(|entry| entry.auth.clone()); - - llm_providers_with_models.push(LlmProviderWithModels { - provider_info, - models, - cache_configs, - is_authenticated, - icon_path, - auth_config, - }); - } - } else { - log::error!( - "Failed to get LLM providers from extension {}: {:?}", - extension.manifest.id, - providers_result - ); - } - } - - wasm_extensions.push(( - extension.manifest.clone(), - wasm_extension, - llm_providers_with_models, - )) + wasm_extensions.push((extension.manifest.clone(), wasm_extension)) } Err(e) => { log::error!( @@ -1745,7 +1395,7 @@ impl ExtensionStore { this.update(cx, |this, cx| { this.reload_complete_senders.clear(); - for (manifest, wasm_extension, llm_providers_with_models) in &wasm_extensions { + for (manifest, wasm_extension) in &wasm_extensions { let extension = Arc::new(wasm_extension.clone()); for (language_server_id, language_server_config) in &manifest.language_servers { @@ -1799,42 +1449,9 @@ impl ExtensionStore { this.proxy .register_debug_locator(extension.clone(), debug_adapter.clone()); } - - // Register LLM providers - for llm_provider in llm_providers_with_models { - let provider_id: Arc = - format!("{}:{}", manifest.id, llm_provider.provider_info.id).into(); - let wasm_ext = extension.as_ref().clone(); - let pinfo = llm_provider.provider_info.clone(); - let mods = llm_provider.models.clone(); - let cache_cfgs = llm_provider.cache_configs.clone(); - let auth = llm_provider.is_authenticated; - let icon = llm_provider.icon_path.clone(); - let auth_config = llm_provider.auth_config.clone(); - - this.proxy.register_language_model_provider( - provider_id.clone(), - Box::new(move |cx: &mut App| { - let provider = Arc::new(ExtensionLanguageModelProvider::new( - wasm_ext, pinfo, mods, cache_cfgs, auth, icon, auth_config, cx, - )); - language_model::LanguageModelRegistry::global(cx).update( - cx, - |registry, cx| { - registry.register_provider(provider, cx); - }, - ); - }), - cx, - ); - } } - let wasm_extensions_without_llm: Vec<_> = wasm_extensions - .into_iter() - .map(|(manifest, ext, _)| (manifest, ext)) - .collect(); - this.wasm_extensions.extend(wasm_extensions_without_llm); + this.wasm_extensions.extend(wasm_extensions); this.proxy.set_extensions_loaded(); this.proxy.reload_current_theme(cx); this.proxy.reload_current_icon_theme(cx); @@ -1856,6 +1473,7 @@ impl ExtensionStore { let index_path = self.index_path.clone(); let proxy = self.proxy.clone(); cx.background_spawn(async move { + let start_time = Instant::now(); let mut index = ExtensionIndex::default(); fs.create_dir(&work_dir).await.log_err(); @@ -1893,6 +1511,7 @@ impl ExtensionStore { .log_err(); } + log::info!("rebuilt extension index in {:?}", start_time.elapsed()); index }) } @@ -2166,6 +1785,11 @@ impl ExtensionStore { })?, path_style, ); + log::info!( + "Uploading extension {} to {:?}", + missing_extension.clone().id, + dest_dir + ); client .update(cx, |client, cx| { @@ -2173,6 +1797,11 @@ impl ExtensionStore { })? .await?; + log::info!( + "Finished uploading extension {}", + missing_extension.clone().id + ); + let result = client .update(cx, |client, _cx| { client.proto_client().request(proto::InstallExtension {