@@ -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<LlmModelInfo>,
- cache_configs: collections::HashMap<String, LlmCacheConfiguration>,
- is_authenticated: bool,
- icon_path: Option<SharedString>,
- auth_config: Option<extension::LanguageModelAuthConfig>,
-}
-
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<str> = 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(<dyn fs::Fs>::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<str>, cx: &mut Context<Self>) {
- 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<str>, cx: &mut Context<Self>) {
- 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<str>,
- operation: ExtensionOperation,
- cx: &mut Context<Self>,
- ) {
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<Self>,
) -> Task<Result<()>> {
+ 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<str> = 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<ExtensionManifest>,
- WasmExtension,
- Vec<LlmProviderWithModels>,
- )> = 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<LlmModelInfo> = 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<str> =
- 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<str> =
- 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 {