Make mercury and sweep non experimental (#48227)

Ben Kunkle created

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

crates/agent_ui/src/agent_ui.rs                         |   2 
crates/edit_prediction_ui/src/edit_prediction_button.rs |  26 -
crates/migrator/src/migrations.rs                       |   6 
crates/migrator/src/migrations/m_2026_02_03/settings.rs |  44 +++
crates/migrator/src/migrator.rs                         | 122 +++++++++++
crates/settings_content/src/language.rs                 |  32 -
crates/zed/src/zed/edit_prediction_registry.rs          |  40 +-
7 files changed, 212 insertions(+), 60 deletions(-)

Detailed changes

crates/agent_ui/src/agent_ui.rs 🔗

@@ -401,6 +401,8 @@ fn update_command_palette_filter(cx: &mut App) {
                 }
                 EditPredictionProvider::Zed
                 | EditPredictionProvider::Codestral
+                | EditPredictionProvider::Sweep
+                | EditPredictionProvider::Mercury
                 | EditPredictionProvider::Experimental(_) => {
                     filter.show_namespace("edit_prediction");
                     filter.hide_namespace("copilot");

crates/edit_prediction_ui/src/edit_prediction_button.rs 🔗

@@ -25,10 +25,7 @@ use language::{
 use project::{DisableAiSettings, Project};
 use regex::Regex;
 use settings::{
-    EXPERIMENTAL_MERCURY_EDIT_PREDICTION_PROVIDER_NAME,
-    EXPERIMENTAL_SWEEP_EDIT_PREDICTION_PROVIDER_NAME,
-    EXPERIMENTAL_ZETA2_EDIT_PREDICTION_PROVIDER_NAME, Settings, SettingsStore,
-    update_settings_file,
+    EXPERIMENTAL_ZETA2_EDIT_PREDICTION_PROVIDER_NAME, Settings, SettingsStore, update_settings_file,
 };
 use std::{
     sync::{Arc, LazyLock},
@@ -306,7 +303,10 @@ impl Render for EditPredictionButton {
                         .with_handle(self.popover_menu_handle.clone()),
                 )
             }
-            provider @ (EditPredictionProvider::Experimental(_) | EditPredictionProvider::Zed) => {
+            provider @ (EditPredictionProvider::Experimental(_)
+            | EditPredictionProvider::Zed
+            | EditPredictionProvider::Sweep
+            | EditPredictionProvider::Mercury) => {
                 let enabled = self.editor_enabled.unwrap_or(true);
                 let icons = self
                     .edit_prediction_provider
@@ -321,9 +321,7 @@ impl Render for EditPredictionButton {
                 let mut missing_token = false;
 
                 match provider {
-                    EditPredictionProvider::Experimental(
-                        EXPERIMENTAL_SWEEP_EDIT_PREDICTION_PROVIDER_NAME,
-                    ) => {
+                    EditPredictionProvider::Sweep => {
                         missing_token = edit_prediction::EditPredictionStore::try_global(cx)
                             .is_some_and(|ep_store| !ep_store.read(cx).has_sweep_api_token(cx));
                         ep_icon = if enabled { icons.base } else { icons.disabled };
@@ -333,9 +331,7 @@ impl Render for EditPredictionButton {
                             "Powered by Sweep"
                         };
                     }
-                    EditPredictionProvider::Experimental(
-                        EXPERIMENTAL_MERCURY_EDIT_PREDICTION_PROVIDER_NAME,
-                    ) => {
+                    EditPredictionProvider::Mercury => {
                         ep_icon = if enabled { icons.base } else { icons.disabled };
                         missing_token = edit_prediction::EditPredictionStore::try_global(cx)
                             .is_some_and(|ep_store| !ep_store.read(cx).has_mercury_api_token(cx));
@@ -1327,9 +1323,7 @@ pub fn get_available_providers(cx: &mut App) -> Vec<EditPredictionProvider> {
             .read(cx)
             .has_key()
     {
-        providers.push(EditPredictionProvider::Experimental(
-            EXPERIMENTAL_SWEEP_EDIT_PREDICTION_PROVIDER_NAME,
-        ));
+        providers.push(EditPredictionProvider::Sweep);
     }
 
     if cx.has_flag::<MercuryFeatureFlag>()
@@ -1337,9 +1331,7 @@ pub fn get_available_providers(cx: &mut App) -> Vec<EditPredictionProvider> {
             .read(cx)
             .has_key()
     {
-        providers.push(EditPredictionProvider::Experimental(
-            EXPERIMENTAL_MERCURY_EDIT_PREDICTION_PROVIDER_NAME,
-        ));
+        providers.push(EditPredictionProvider::Mercury);
     }
 
     providers

crates/migrator/src/migrations.rs 🔗

@@ -171,3 +171,9 @@ pub(crate) mod m_2026_02_02 {
 
     pub(crate) use settings::move_edit_prediction_provider_to_edit_predictions;
 }
+
+pub(crate) mod m_2026_02_03 {
+    mod settings;
+
+    pub(crate) use settings::migrate_experimental_sweep_mercury;
+}

crates/migrator/src/migrations/m_2026_02_03/settings.rs 🔗

@@ -0,0 +1,44 @@
+use anyhow::Result;
+use serde_json::Value;
+
+pub fn migrate_experimental_sweep_mercury(value: &mut Value) -> Result<()> {
+    let Some(obj) = value.as_object_mut() else {
+        return Ok(());
+    };
+
+    if let Some(edit_predictions) = obj.get_mut("edit_predictions") {
+        if let Some(edit_predictions_obj) = edit_predictions.as_object_mut() {
+            migrate_provider_field(edit_predictions_obj, "provider");
+        }
+    }
+
+    if let Some(features) = obj.get_mut("features") {
+        if let Some(features_obj) = features.as_object_mut() {
+            migrate_provider_field(features_obj, "edit_prediction_provider");
+        }
+    }
+
+    Ok(())
+}
+
+fn migrate_provider_field(obj: &mut serde_json::Map<String, Value>, field_name: &str) {
+    let Some(provider) = obj.get(field_name) else {
+        return;
+    };
+
+    let Some(provider_obj) = provider.as_object() else {
+        return;
+    };
+
+    let Some(experimental_name) = provider_obj.get("experimental") else {
+        return;
+    };
+
+    let Some(name) = experimental_name.as_str() else {
+        return;
+    };
+
+    if name == "sweep" || name == "mercury" {
+        obj.insert(field_name.to_string(), Value::String(name.to_string()));
+    }
+}

crates/migrator/src/migrator.rs 🔗

@@ -235,6 +235,7 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> {
         MigrationType::Json(
             migrations::m_2026_02_02::move_edit_prediction_provider_to_edit_predictions,
         ),
+        MigrationType::Json(migrations::m_2026_02_03::migrate_experimental_sweep_mercury),
     ];
     run_migrations(text, migrations)
 }
@@ -2470,4 +2471,125 @@ mod tests {
             None,
         );
     }
+
+    #[test]
+    fn test_migrate_experimental_sweep_mercury() {
+        assert_migrate_settings_with_migrations(
+            &[MigrationType::Json(
+                migrations::m_2026_02_03::migrate_experimental_sweep_mercury,
+            )],
+            &r#"{ }"#.unindent(),
+            None,
+        );
+
+        assert_migrate_settings_with_migrations(
+            &[MigrationType::Json(
+                migrations::m_2026_02_03::migrate_experimental_sweep_mercury,
+            )],
+            &r#"
+            {
+                "edit_predictions": {
+                    "provider": {
+                        "experimental": "sweep"
+                    }
+                }
+            }
+            "#
+            .unindent(),
+            Some(
+                &r#"
+                {
+                    "edit_predictions": {
+                        "provider": "sweep"
+                    }
+                }
+                "#
+                .unindent(),
+            ),
+        );
+
+        assert_migrate_settings_with_migrations(
+            &[MigrationType::Json(
+                migrations::m_2026_02_03::migrate_experimental_sweep_mercury,
+            )],
+            &r#"
+            {
+                "edit_predictions": {
+                    "provider": {
+                        "experimental": "mercury"
+                    }
+                }
+            }
+            "#
+            .unindent(),
+            Some(
+                &r#"
+                {
+                    "edit_predictions": {
+                        "provider": "mercury"
+                    }
+                }
+                "#
+                .unindent(),
+            ),
+        );
+
+        assert_migrate_settings_with_migrations(
+            &[MigrationType::Json(
+                migrations::m_2026_02_03::migrate_experimental_sweep_mercury,
+            )],
+            &r#"
+            {
+                "features": {
+                    "edit_prediction_provider": {
+                        "experimental": "sweep"
+                    }
+                }
+            }
+            "#
+            .unindent(),
+            Some(
+                &r#"
+                {
+                    "features": {
+                        "edit_prediction_provider": "sweep"
+                    }
+                }
+                "#
+                .unindent(),
+            ),
+        );
+
+        assert_migrate_settings_with_migrations(
+            &[MigrationType::Json(
+                migrations::m_2026_02_03::migrate_experimental_sweep_mercury,
+            )],
+            &r#"
+            {
+                "edit_predictions": {
+                    "provider": "zed"
+                }
+            }
+            "#
+            .unindent(),
+            None,
+        );
+
+        assert_migrate_settings_with_migrations(
+            &[MigrationType::Json(
+                migrations::m_2026_02_03::migrate_experimental_sweep_mercury,
+            )],
+            &r#"
+            {
+                "edit_predictions": {
+                    "provider": {
+                        "experimental": "zeta2"
+                    }
+                }
+            }
+            "#
+            .unindent(),
+            None,
+        );
+    }
 }

crates/settings_content/src/language.rs 🔗

@@ -84,12 +84,12 @@ pub enum EditPredictionProvider {
     Supermaven,
     Zed,
     Codestral,
+    Sweep,
+    Mercury,
     Experimental(&'static str),
 }
 
-pub const EXPERIMENTAL_SWEEP_EDIT_PREDICTION_PROVIDER_NAME: &str = "sweep";
 pub const EXPERIMENTAL_ZETA2_EDIT_PREDICTION_PROVIDER_NAME: &str = "zeta2";
-pub const EXPERIMENTAL_MERCURY_EDIT_PREDICTION_PROVIDER_NAME: &str = "mercury";
 
 impl<'de> Deserialize<'de> for EditPredictionProvider {
     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@@ -104,6 +104,8 @@ impl<'de> Deserialize<'de> for EditPredictionProvider {
             Supermaven,
             Zed,
             Codestral,
+            Sweep,
+            Mercury,
             Experimental(String),
         }
 
@@ -113,20 +115,8 @@ impl<'de> Deserialize<'de> for EditPredictionProvider {
             Content::Supermaven => EditPredictionProvider::Supermaven,
             Content::Zed => EditPredictionProvider::Zed,
             Content::Codestral => EditPredictionProvider::Codestral,
-            Content::Experimental(name)
-                if name == EXPERIMENTAL_SWEEP_EDIT_PREDICTION_PROVIDER_NAME =>
-            {
-                EditPredictionProvider::Experimental(
-                    EXPERIMENTAL_SWEEP_EDIT_PREDICTION_PROVIDER_NAME,
-                )
-            }
-            Content::Experimental(name)
-                if name == EXPERIMENTAL_MERCURY_EDIT_PREDICTION_PROVIDER_NAME =>
-            {
-                EditPredictionProvider::Experimental(
-                    EXPERIMENTAL_MERCURY_EDIT_PREDICTION_PROVIDER_NAME,
-                )
-            }
+            Content::Sweep => EditPredictionProvider::Sweep,
+            Content::Mercury => EditPredictionProvider::Mercury,
             Content::Experimental(name)
                 if name == EXPERIMENTAL_ZETA2_EDIT_PREDICTION_PROVIDER_NAME =>
             {
@@ -152,6 +142,8 @@ impl EditPredictionProvider {
             | EditPredictionProvider::Copilot
             | EditPredictionProvider::Supermaven
             | EditPredictionProvider::Codestral
+            | EditPredictionProvider::Sweep
+            | EditPredictionProvider::Mercury
             | EditPredictionProvider::Experimental(_) => false,
         }
     }
@@ -162,12 +154,8 @@ impl EditPredictionProvider {
             EditPredictionProvider::Copilot => Some("GitHub Copilot"),
             EditPredictionProvider::Supermaven => Some("Supermaven"),
             EditPredictionProvider::Codestral => Some("Codestral"),
-            EditPredictionProvider::Experimental(
-                EXPERIMENTAL_SWEEP_EDIT_PREDICTION_PROVIDER_NAME,
-            ) => Some("Sweep"),
-            EditPredictionProvider::Experimental(
-                EXPERIMENTAL_MERCURY_EDIT_PREDICTION_PROVIDER_NAME,
-            ) => Some("Mercury"),
+            EditPredictionProvider::Sweep => Some("Sweep"),
+            EditPredictionProvider::Mercury => Some("Mercury"),
             EditPredictionProvider::Experimental(
                 EXPERIMENTAL_ZETA2_EDIT_PREDICTION_PROVIDER_NAME,
             ) => Some("Zeta2"),

crates/zed/src/zed/edit_prediction_registry.rs 🔗

@@ -10,11 +10,7 @@ use feature_flags::FeatureFlagAppExt;
 use gpui::{AnyWindowHandle, App, AppContext as _, Context, Entity, WeakEntity};
 use language::language_settings::{EditPredictionProvider, all_language_settings};
 use language_models::MistralLanguageModelProvider;
-use settings::{
-    EXPERIMENTAL_MERCURY_EDIT_PREDICTION_PROVIDER_NAME,
-    EXPERIMENTAL_SWEEP_EDIT_PREDICTION_PROVIDER_NAME,
-    EXPERIMENTAL_ZETA2_EDIT_PREDICTION_PROVIDER_NAME, SettingsStore,
-};
+use settings::{EXPERIMENTAL_ZETA2_EDIT_PREDICTION_PROVIDER_NAME, SettingsStore};
 use std::{cell::RefCell, rc::Rc, sync::Arc};
 use supermaven::{Supermaven, SupermavenEditPredictionDelegate};
 use ui::Window;
@@ -195,7 +191,10 @@ fn assign_edit_prediction_provider(
             let provider = cx.new(|_| CodestralEditPredictionDelegate::new(http_client));
             editor.set_edit_prediction_provider(Some(provider), window, cx);
         }
-        value @ (EditPredictionProvider::Experimental(_) | EditPredictionProvider::Zed) => {
+        value @ (EditPredictionProvider::Experimental(_)
+        | EditPredictionProvider::Zed
+        | EditPredictionProvider::Sweep
+        | EditPredictionProvider::Mercury) => {
             let ep_store = edit_prediction::EditPredictionStore::global(client, &user_store, cx);
 
             if let Some(project) = editor.project()
@@ -203,28 +202,27 @@ fn assign_edit_prediction_provider(
                 && buffer.read(cx).file().is_some()
             {
                 let has_model = ep_store.update(cx, |ep_store, cx| {
-                    let model = if let EditPredictionProvider::Experimental(name) = value {
-                        if name == EXPERIMENTAL_SWEEP_EDIT_PREDICTION_PROVIDER_NAME
-                            && cx.has_flag::<SweepFeatureFlag>()
-                        {
+                    let model = match value {
+                        EditPredictionProvider::Sweep if cx.has_flag::<SweepFeatureFlag>() => {
                             edit_prediction::EditPredictionModel::Sweep
-                        } else if name == EXPERIMENTAL_ZETA2_EDIT_PREDICTION_PROVIDER_NAME
-                            && cx.has_flag::<Zeta2FeatureFlag>()
+                        }
+                        EditPredictionProvider::Mercury if cx.has_flag::<MercuryFeatureFlag>() => {
+                            edit_prediction::EditPredictionModel::Mercury
+                        }
+                        EditPredictionProvider::Experimental(name)
+                            if name == EXPERIMENTAL_ZETA2_EDIT_PREDICTION_PROVIDER_NAME
+                                && cx.has_flag::<Zeta2FeatureFlag>() =>
                         {
                             edit_prediction::EditPredictionModel::Zeta2 {
                                 version: Default::default(),
                             }
-                        } else if name == EXPERIMENTAL_MERCURY_EDIT_PREDICTION_PROVIDER_NAME
-                            && cx.has_flag::<MercuryFeatureFlag>()
+                        }
+                        EditPredictionProvider::Zed
+                            if user_store.read(cx).current_user().is_some() =>
                         {
-                            edit_prediction::EditPredictionModel::Mercury
-                        } else {
-                            return false;
+                            edit_prediction::EditPredictionModel::Zeta1
                         }
-                    } else if user_store.read(cx).current_user().is_some() {
-                        edit_prediction::EditPredictionModel::Zeta1
-                    } else {
-                        return false;
+                        _ => return false,
                     };
 
                     ep_store.set_edit_prediction_model(model);