Add setting to disable all AI features (#34896)

Richard Feldman created

https://github.com/user-attachments/assets/674bba41-40ac-4a98-99e4-0b47f9097b6a


Release Notes:

- Added setting to disable all AI features

Change summary

Cargo.lock                                                      |  2 
assets/settings/default.json                                    |  4 
crates/agent_ui/Cargo.toml                                      |  1 
crates/agent_ui/src/agent_panel.rs                              |  8 
crates/agent_ui/src/agent_ui.rs                                 | 65 ++
crates/agent_ui/src/inline_assistant.rs                         | 44 +
crates/assistant_tools/Cargo.toml                               |  1 
crates/assistant_tools/src/assistant_tools.rs                   |  3 
crates/client/src/client.rs                                     | 28 +
crates/copilot/src/copilot.rs                                   | 46 +
crates/git_ui/Cargo.toml                                        |  1 
crates/git_ui/src/commit_modal.rs                               | 14 
crates/git_ui/src/git_panel.rs                                  | 19 
crates/inline_completion_button/src/inline_completion_button.rs |  7 
crates/welcome/src/welcome.rs                                   | 36 
crates/workspace/src/dock.rs                                    | 13 
crates/zed/src/main.rs                                          |  1 
crates/zed/src/zed/quick_action_bar.rs                          | 26 +
crates/zeta/src/init.rs                                         | 57 ++
19 files changed, 308 insertions(+), 68 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -210,6 +210,7 @@ dependencies = [
  "chrono",
  "client",
  "collections",
+ "command_palette_hooks",
  "component",
  "context_server",
  "db",
@@ -6360,6 +6361,7 @@ dependencies = [
  "buffer_diff",
  "call",
  "chrono",
+ "client",
  "collections",
  "command_palette_hooks",
  "component",

assets/settings/default.json 🔗

@@ -1076,6 +1076,10 @@
     // Send anonymized usage data like what languages you're using Zed with.
     "metrics": true
   },
+  // Whether to disable all AI features in Zed.
+  //
+  // Default: false
+  "disable_ai": false,
   // Automatically update Zed. This setting may be ignored on Linux if
   // installed through a package manager.
   "auto_update": true,

crates/agent_ui/Cargo.toml 🔗

@@ -32,6 +32,7 @@ buffer_diff.workspace = true
 chrono.workspace = true
 client.workspace = true
 collections.workspace = true
+command_palette_hooks.workspace = true
 component.workspace = true
 context_server.workspace = true
 db.workspace = true

crates/agent_ui/src/agent_panel.rs 🔗

@@ -43,7 +43,7 @@ use anyhow::{Result, anyhow};
 use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
 use assistant_slash_command::SlashCommandWorkingSet;
 use assistant_tool::ToolWorkingSet;
-use client::{UserStore, zed_urls};
+use client::{DisableAiSettings, UserStore, zed_urls};
 use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
 use feature_flags::{self, FeatureFlagAppExt};
 use fs::Fs;
@@ -744,6 +744,7 @@ impl AgentPanel {
         if workspace
             .panel::<Self>(cx)
             .is_some_and(|panel| panel.read(cx).enabled(cx))
+            && !DisableAiSettings::get_global(cx).disable_ai
         {
             workspace.toggle_panel_focus::<Self>(window, cx);
         }
@@ -1665,7 +1666,10 @@ impl Panel for AgentPanel {
     }
 
     fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
-        (self.enabled(cx) && AgentSettings::get_global(cx).button).then_some(IconName::ZedAssistant)
+        (self.enabled(cx)
+            && AgentSettings::get_global(cx).button
+            && !DisableAiSettings::get_global(cx).disable_ai)
+            .then_some(IconName::ZedAssistant)
     }
 
     fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {

crates/agent_ui/src/agent_ui.rs 🔗

@@ -31,7 +31,8 @@ use std::sync::Arc;
 use agent::{Thread, ThreadId};
 use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
 use assistant_slash_command::SlashCommandRegistry;
-use client::Client;
+use client::{Client, DisableAiSettings};
+use command_palette_hooks::CommandPaletteFilter;
 use feature_flags::FeatureFlagAppExt as _;
 use fs::Fs;
 use gpui::{Action, App, Entity, actions};
@@ -43,6 +44,7 @@ use prompt_store::PromptBuilder;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use settings::{Settings as _, SettingsStore};
+use std::any::TypeId;
 
 pub use crate::active_thread::ActiveThread;
 use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
@@ -52,6 +54,7 @@ use crate::slash_command_settings::SlashCommandSettings;
 pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
 pub use text_thread_editor::{AgentPanelDelegate, TextThreadEditor};
 pub use ui::preview::{all_agent_previews, get_agent_preview};
+use zed_actions;
 
 actions!(
     agent,
@@ -241,6 +244,66 @@ pub fn init(
     })
     .detach();
     cx.observe_new(ManageProfilesModal::register).detach();
+
+    // Update command palette filter based on AI settings
+    update_command_palette_filter(cx);
+
+    // Watch for settings changes
+    cx.observe_global::<SettingsStore>(|app_cx| {
+        // When settings change, update the command palette filter
+        update_command_palette_filter(app_cx);
+    })
+    .detach();
+}
+
+fn update_command_palette_filter(cx: &mut App) {
+    let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
+    CommandPaletteFilter::update_global(cx, |filter, _| {
+        if disable_ai {
+            filter.hide_namespace("agent");
+            filter.hide_namespace("assistant");
+            filter.hide_namespace("zed_predict_onboarding");
+            filter.hide_namespace("edit_prediction");
+
+            use editor::actions::{
+                AcceptEditPrediction, AcceptPartialEditPrediction, NextEditPrediction,
+                PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
+            };
+            let edit_prediction_actions = [
+                TypeId::of::<AcceptEditPrediction>(),
+                TypeId::of::<AcceptPartialEditPrediction>(),
+                TypeId::of::<ShowEditPrediction>(),
+                TypeId::of::<NextEditPrediction>(),
+                TypeId::of::<PreviousEditPrediction>(),
+                TypeId::of::<ToggleEditPrediction>(),
+            ];
+            filter.hide_action_types(&edit_prediction_actions);
+            filter.hide_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
+        } else {
+            filter.show_namespace("agent");
+            filter.show_namespace("assistant");
+            filter.show_namespace("zed_predict_onboarding");
+
+            filter.show_namespace("edit_prediction");
+
+            use editor::actions::{
+                AcceptEditPrediction, AcceptPartialEditPrediction, NextEditPrediction,
+                PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
+            };
+            let edit_prediction_actions = [
+                TypeId::of::<AcceptEditPrediction>(),
+                TypeId::of::<AcceptPartialEditPrediction>(),
+                TypeId::of::<ShowEditPrediction>(),
+                TypeId::of::<NextEditPrediction>(),
+                TypeId::of::<PreviousEditPrediction>(),
+                TypeId::of::<ToggleEditPrediction>(),
+            ];
+            filter.show_action_types(edit_prediction_actions.iter());
+
+            filter
+                .show_action_types([TypeId::of::<zed_actions::OpenZedPredictOnboarding>()].iter());
+        }
+    });
 }
 
 fn init_language_model_settings(cx: &mut App) {

crates/agent_ui/src/inline_assistant.rs 🔗

@@ -16,7 +16,7 @@ use agent::{
 };
 use agent_settings::AgentSettings;
 use anyhow::{Context as _, Result};
-use client::telemetry::Telemetry;
+use client::{DisableAiSettings, telemetry::Telemetry};
 use collections::{HashMap, HashSet, VecDeque, hash_map};
 use editor::SelectionEffects;
 use editor::{
@@ -57,6 +57,17 @@ pub fn init(
     cx: &mut App,
 ) {
     cx.set_global(InlineAssistant::new(fs, prompt_builder, telemetry));
+
+    cx.observe_global::<SettingsStore>(|cx| {
+        if DisableAiSettings::get_global(cx).disable_ai {
+            // Hide any active inline assist UI when AI is disabled
+            InlineAssistant::update_global(cx, |assistant, cx| {
+                assistant.cancel_all_active_completions(cx);
+            });
+        }
+    })
+    .detach();
+
     cx.observe_new(|_workspace: &mut Workspace, window, cx| {
         let Some(window) = window else {
             return;
@@ -141,6 +152,26 @@ impl InlineAssistant {
         .detach();
     }
 
+    /// Hides all active inline assists when AI is disabled
+    pub fn cancel_all_active_completions(&mut self, cx: &mut App) {
+        // Cancel all active completions in editors
+        for (editor_handle, _) in self.assists_by_editor.iter() {
+            if let Some(editor) = editor_handle.upgrade() {
+                let windows = cx.windows();
+                if !windows.is_empty() {
+                    let window = windows[0];
+                    let _ = window.update(cx, |_, window, cx| {
+                        editor.update(cx, |editor, cx| {
+                            if editor.has_active_inline_completion() {
+                                editor.cancel(&Default::default(), window, cx);
+                            }
+                        });
+                    });
+                }
+            }
+        }
+    }
+
     fn handle_workspace_event(
         &mut self,
         workspace: Entity<Workspace>,
@@ -176,7 +207,7 @@ impl InlineAssistant {
         window: &mut Window,
         cx: &mut App,
     ) {
-        let is_assistant2_enabled = true;
+        let is_assistant2_enabled = !DisableAiSettings::get_global(cx).disable_ai;
 
         if let Some(editor) = item.act_as::<Editor>(cx) {
             editor.update(cx, |editor, cx| {
@@ -199,6 +230,13 @@ impl InlineAssistant {
                         cx,
                     );
 
+                    if DisableAiSettings::get_global(cx).disable_ai {
+                        // Cancel any active completions
+                        if editor.has_active_inline_completion() {
+                            editor.cancel(&Default::default(), window, cx);
+                        }
+                    }
+
                     // Remove the Assistant1 code action provider, as it still might be registered.
                     editor.remove_code_action_provider("assistant".into(), window, cx);
                 } else {
@@ -219,7 +257,7 @@ impl InlineAssistant {
         cx: &mut Context<Workspace>,
     ) {
         let settings = AgentSettings::get_global(cx);
-        if !settings.enabled {
+        if !settings.enabled || DisableAiSettings::get_global(cx).disable_ai {
             return;
         }
 

crates/assistant_tools/Cargo.toml 🔗

@@ -20,6 +20,7 @@ anyhow.workspace = true
 assistant_tool.workspace = true
 buffer_diff.workspace = true
 chrono.workspace = true
+client.workspace = true
 collections.workspace = true
 component.workspace = true
 derive_more.workspace = true

crates/assistant_tools/src/assistant_tools.rs 🔗

@@ -20,14 +20,13 @@ mod thinking_tool;
 mod ui;
 mod web_search_tool;
 
-use std::sync::Arc;
-
 use assistant_tool::ToolRegistry;
 use copy_path_tool::CopyPathTool;
 use gpui::{App, Entity};
 use http_client::HttpClientWithUrl;
 use language_model::LanguageModelRegistry;
 use move_path_tool::MovePathTool;
+use std::sync::Arc;
 use web_search_tool::WebSearchTool;
 
 pub(crate) use templates::*;

crates/client/src/client.rs 🔗

@@ -151,6 +151,7 @@ impl Settings for ProxySettings {
 
 pub fn init_settings(cx: &mut App) {
     TelemetrySettings::register(cx);
+    DisableAiSettings::register(cx);
     ClientSettings::register(cx);
     ProxySettings::register(cx);
 }
@@ -548,6 +549,33 @@ impl settings::Settings for TelemetrySettings {
     }
 }
 
+/// Whether to disable all AI features in Zed.
+///
+/// Default: false
+#[derive(Copy, Clone, Debug)]
+pub struct DisableAiSettings {
+    pub disable_ai: bool,
+}
+
+impl settings::Settings for DisableAiSettings {
+    const KEY: Option<&'static str> = Some("disable_ai");
+
+    type FileContent = Option<bool>;
+
+    fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
+        Ok(Self {
+            disable_ai: sources
+                .user
+                .or(sources.server)
+                .copied()
+                .flatten()
+                .unwrap_or(sources.default.ok_or_else(Self::missing_default)?),
+        })
+    }
+
+    fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
+}
+
 impl Client {
     pub fn new(
         clock: Arc<dyn SystemClock>,

crates/copilot/src/copilot.rs 🔗

@@ -6,6 +6,7 @@ mod sign_in;
 use crate::sign_in::initiate_sign_in_within_workspace;
 use ::fs::Fs;
 use anyhow::{Context as _, Result, anyhow};
+use client::DisableAiSettings;
 use collections::{HashMap, HashSet};
 use command_palette_hooks::CommandPaletteFilter;
 use futures::{Future, FutureExt, TryFutureExt, channel::oneshot, future::Shared};
@@ -25,6 +26,7 @@ use node_runtime::NodeRuntime;
 use parking_lot::Mutex;
 use request::StatusNotification;
 use serde_json::json;
+use settings::Settings;
 use settings::SettingsStore;
 use sign_in::{reinstall_and_sign_in_within_workspace, sign_out_within_workspace};
 use std::collections::hash_map::Entry;
@@ -93,26 +95,34 @@ pub fn init(
         let copilot_auth_action_types = [TypeId::of::<SignOut>()];
         let copilot_no_auth_action_types = [TypeId::of::<SignIn>()];
         let status = handle.read(cx).status();
+
+        let is_ai_disabled = DisableAiSettings::get_global(cx).disable_ai;
         let filter = CommandPaletteFilter::global_mut(cx);
 
-        match status {
-            Status::Disabled => {
-                filter.hide_action_types(&copilot_action_types);
-                filter.hide_action_types(&copilot_auth_action_types);
-                filter.hide_action_types(&copilot_no_auth_action_types);
-            }
-            Status::Authorized => {
-                filter.hide_action_types(&copilot_no_auth_action_types);
-                filter.show_action_types(
-                    copilot_action_types
-                        .iter()
-                        .chain(&copilot_auth_action_types),
-                );
-            }
-            _ => {
-                filter.hide_action_types(&copilot_action_types);
-                filter.hide_action_types(&copilot_auth_action_types);
-                filter.show_action_types(copilot_no_auth_action_types.iter());
+        if is_ai_disabled {
+            filter.hide_action_types(&copilot_action_types);
+            filter.hide_action_types(&copilot_auth_action_types);
+            filter.hide_action_types(&copilot_no_auth_action_types);
+        } else {
+            match status {
+                Status::Disabled => {
+                    filter.hide_action_types(&copilot_action_types);
+                    filter.hide_action_types(&copilot_auth_action_types);
+                    filter.hide_action_types(&copilot_no_auth_action_types);
+                }
+                Status::Authorized => {
+                    filter.hide_action_types(&copilot_no_auth_action_types);
+                    filter.show_action_types(
+                        copilot_action_types
+                            .iter()
+                            .chain(&copilot_auth_action_types),
+                    );
+                }
+                _ => {
+                    filter.hide_action_types(&copilot_action_types);
+                    filter.hide_action_types(&copilot_auth_action_types);
+                    filter.show_action_types(copilot_no_auth_action_types.iter());
+                }
             }
         }
     })

crates/git_ui/Cargo.toml 🔗

@@ -23,6 +23,7 @@ askpass.workspace = true
 buffer_diff.workspace = true
 call.workspace = true
 chrono.workspace = true
+client.workspace = true
 collections.workspace = true
 command_palette_hooks.workspace = true
 component.workspace = true

crates/git_ui/src/commit_modal.rs 🔗

@@ -1,8 +1,10 @@
 use crate::branch_picker::{self, BranchList};
 use crate::git_panel::{GitPanel, commit_message_editor};
+use client::DisableAiSettings;
 use git::repository::CommitOptions;
 use git::{Amend, Commit, GenerateCommitMessage, Signoff};
 use panel::{panel_button, panel_editor_style};
+use settings::Settings;
 use ui::{
     ContextMenu, KeybindingHint, PopoverMenu, PopoverMenuHandle, SplitButton, Tooltip, prelude::*,
 };
@@ -569,11 +571,13 @@ impl Render for CommitModal {
             .on_action(cx.listener(Self::dismiss))
             .on_action(cx.listener(Self::commit))
             .on_action(cx.listener(Self::amend))
-            .on_action(cx.listener(|this, _: &GenerateCommitMessage, _, cx| {
-                this.git_panel.update(cx, |panel, cx| {
-                    panel.generate_commit_message(cx);
-                })
-            }))
+            .when(!DisableAiSettings::get_global(cx).disable_ai, |this| {
+                this.on_action(cx.listener(|this, _: &GenerateCommitMessage, _, cx| {
+                    this.git_panel.update(cx, |panel, cx| {
+                        panel.generate_commit_message(cx);
+                    })
+                }))
+            })
             .on_action(
                 cx.listener(|this, _: &zed_actions::git::Branch, window, cx| {
                     this.toggle_branch_selector(window, cx);

crates/git_ui/src/git_panel.rs 🔗

@@ -12,6 +12,7 @@ use crate::{
 use agent_settings::AgentSettings;
 use anyhow::Context as _;
 use askpass::AskPassDelegate;
+use client::DisableAiSettings;
 use db::kvp::KEY_VALUE_STORE;
 use editor::{
     Editor, EditorElement, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar,
@@ -53,7 +54,7 @@ use project::{
     git_store::{GitStoreEvent, Repository},
 };
 use serde::{Deserialize, Serialize};
-use settings::{Settings as _, SettingsStore};
+use settings::{Settings, SettingsStore};
 use std::future::Future;
 use std::ops::Range;
 use std::path::{Path, PathBuf};
@@ -464,9 +465,14 @@ impl GitPanel {
             };
 
             let mut assistant_enabled = AgentSettings::get_global(cx).enabled;
+            let mut was_ai_disabled = DisableAiSettings::get_global(cx).disable_ai;
             let _settings_subscription = cx.observe_global::<SettingsStore>(move |_, cx| {
-                if assistant_enabled != AgentSettings::get_global(cx).enabled {
+                let is_ai_disabled = DisableAiSettings::get_global(cx).disable_ai;
+                if assistant_enabled != AgentSettings::get_global(cx).enabled
+                    || was_ai_disabled != is_ai_disabled
+                {
                     assistant_enabled = AgentSettings::get_global(cx).enabled;
+                    was_ai_disabled = is_ai_disabled;
                     cx.notify();
                 }
             });
@@ -1806,7 +1812,7 @@ impl GitPanel {
 
     /// Generates a commit message using an LLM.
     pub fn generate_commit_message(&mut self, cx: &mut Context<Self>) {
-        if !self.can_commit() {
+        if !self.can_commit() || DisableAiSettings::get_global(cx).disable_ai {
             return;
         }
 
@@ -4305,8 +4311,10 @@ impl GitPanel {
 }
 
 fn current_language_model(cx: &Context<'_, GitPanel>) -> Option<Arc<dyn LanguageModel>> {
-    agent_settings::AgentSettings::get_global(cx)
-        .enabled
+    let is_enabled = agent_settings::AgentSettings::get_global(cx).enabled
+        && !DisableAiSettings::get_global(cx).disable_ai;
+
+    is_enabled
         .then(|| {
             let ConfiguredModel { provider, model } =
                 LanguageModelRegistry::read_global(cx).commit_message_model()?;
@@ -5037,6 +5045,7 @@ mod tests {
             language::init(cx);
             editor::init(cx);
             Project::init_settings(cx);
+            client::DisableAiSettings::register(cx);
             crate::init(cx);
         });
     }

crates/inline_completion_button/src/inline_completion_button.rs 🔗

@@ -1,5 +1,5 @@
 use anyhow::Result;
-use client::{UserStore, zed_urls};
+use client::{DisableAiSettings, UserStore, zed_urls};
 use copilot::{Copilot, Status};
 use editor::{
     Editor, SelectionEffects,
@@ -72,6 +72,11 @@ enum SupermavenButtonStatus {
 
 impl Render for InlineCompletionButton {
     fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        // Return empty div if AI is disabled
+        if DisableAiSettings::get_global(cx).disable_ai {
+            return div();
+        }
+
         let all_language_settings = all_language_settings(None, cx);
 
         match all_language_settings.edit_predictions.provider {

crates/welcome/src/welcome.rs 🔗

@@ -1,4 +1,4 @@
-use client::{TelemetrySettings, telemetry::Telemetry};
+use client::{DisableAiSettings, TelemetrySettings, telemetry::Telemetry};
 use db::kvp::KEY_VALUE_STORE;
 use gpui::{
     Action, App, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement,
@@ -174,23 +174,25 @@ impl Render for WelcomePage {
                                                     .ok();
                                             })),
                                     )
-                                    .child(
-                                        Button::new(
-                                            "try-zed-edit-prediction",
-                                            edit_prediction_label,
+                                    .when(!DisableAiSettings::get_global(cx).disable_ai, |parent| {
+                                        parent.child(
+                                            Button::new(
+                                                "edit_prediction_onboarding",
+                                                edit_prediction_label,
+                                            )
+                                            .disabled(edit_prediction_provider_is_zed)
+                                            .icon(IconName::ZedPredict)
+                                            .icon_size(IconSize::XSmall)
+                                            .icon_color(Color::Muted)
+                                            .icon_position(IconPosition::Start)
+                                            .on_click(
+                                                cx.listener(|_, _, window, cx| {
+                                                    telemetry::event!("Welcome Screen Try Edit Prediction clicked");
+                                                    window.dispatch_action(zed_actions::OpenZedPredictOnboarding.boxed_clone(), cx);
+                                                }),
+                                            ),
                                         )
-                                        .disabled(edit_prediction_provider_is_zed)
-                                        .icon(IconName::ZedPredict)
-                                        .icon_size(IconSize::XSmall)
-                                        .icon_color(Color::Muted)
-                                        .icon_position(IconPosition::Start)
-                                        .on_click(
-                                            cx.listener(|_, _, window, cx| {
-                                                telemetry::event!("Welcome Screen Try Edit Prediction clicked");
-                                                window.dispatch_action(zed_actions::OpenZedPredictOnboarding.boxed_clone(), cx);
-                                            }),
-                                        ),
-                                    )
+                                    })
                                     .child(
                                         Button::new("edit settings", "Edit Settings")
                                             .icon(IconName::Settings)

crates/workspace/src/dock.rs 🔗

@@ -242,6 +242,7 @@ struct PanelEntry {
 
 pub struct PanelButtons {
     dock: Entity<Dock>,
+    _settings_subscription: Subscription,
 }
 
 impl Dock {
@@ -373,6 +374,12 @@ impl Dock {
             })
     }
 
+    pub fn first_enabled_panel_idx_excluding(&self, exclude_name: &str, cx: &App) -> Option<usize> {
+        self.panel_entries.iter().position(|entry| {
+            entry.panel.persistent_name() != exclude_name && entry.panel.enabled(cx)
+        })
+    }
+
     fn active_panel_entry(&self) -> Option<&PanelEntry> {
         self.active_panel_index
             .and_then(|index| self.panel_entries.get(index))
@@ -833,7 +840,11 @@ impl Render for Dock {
 impl PanelButtons {
     pub fn new(dock: Entity<Dock>, cx: &mut Context<Self>) -> Self {
         cx.observe(&dock, |_, _, cx| cx.notify()).detach();
-        Self { dock }
+        let settings_subscription = cx.observe_global::<SettingsStore>(|_, cx| cx.notify());
+        Self {
+            dock,
+            _settings_subscription: settings_subscription,
+        }
     }
 }
 

crates/zed/src/main.rs 🔗

@@ -554,6 +554,7 @@ pub fn main() {
         supermaven::init(app_state.client.clone(), cx);
         language_model::init(app_state.client.clone(), cx);
         language_models::init(app_state.user_store.clone(), app_state.client.clone(), cx);
+        agent_settings::init(cx);
         agent_servers::init(cx);
         web_search::init(cx);
         web_search_providers::init(app_state.client.clone(), cx);

crates/zed/src/zed/quick_action_bar.rs 🔗

@@ -2,6 +2,7 @@ mod preview;
 mod repl_menu;
 
 use agent_settings::AgentSettings;
+use client::DisableAiSettings;
 use editor::actions::{
     AddSelectionAbove, AddSelectionBelow, CodeActionSource, DuplicateLineDown, GoToDiagnostic,
     GoToHunk, GoToPreviousDiagnostic, GoToPreviousHunk, MoveLineDown, MoveLineUp, SelectAll,
@@ -32,6 +33,7 @@ const MAX_CODE_ACTION_MENU_LINES: u32 = 16;
 
 pub struct QuickActionBar {
     _inlay_hints_enabled_subscription: Option<Subscription>,
+    _ai_settings_subscription: Subscription,
     active_item: Option<Box<dyn ItemHandle>>,
     buffer_search_bar: Entity<BufferSearchBar>,
     show: bool,
@@ -46,8 +48,28 @@ impl QuickActionBar {
         workspace: &Workspace,
         cx: &mut Context<Self>,
     ) -> Self {
+        let mut was_ai_disabled = DisableAiSettings::get_global(cx).disable_ai;
+        let mut was_agent_enabled = AgentSettings::get_global(cx).enabled;
+        let mut was_agent_button = AgentSettings::get_global(cx).button;
+
+        let ai_settings_subscription = cx.observe_global::<SettingsStore>(move |_, cx| {
+            let is_ai_disabled = DisableAiSettings::get_global(cx).disable_ai;
+            let agent_settings = AgentSettings::get_global(cx);
+
+            if was_ai_disabled != is_ai_disabled
+                || was_agent_enabled != agent_settings.enabled
+                || was_agent_button != agent_settings.button
+            {
+                was_ai_disabled = is_ai_disabled;
+                was_agent_enabled = agent_settings.enabled;
+                was_agent_button = agent_settings.button;
+                cx.notify();
+            }
+        });
+
         let mut this = Self {
             _inlay_hints_enabled_subscription: None,
+            _ai_settings_subscription: ai_settings_subscription,
             active_item: None,
             buffer_search_bar,
             show: true,
@@ -575,7 +597,9 @@ impl Render for QuickActionBar {
             .children(self.render_preview_button(self.workspace.clone(), cx))
             .children(search_button)
             .when(
-                AgentSettings::get_global(cx).enabled && AgentSettings::get_global(cx).button,
+                AgentSettings::get_global(cx).enabled
+                    && AgentSettings::get_global(cx).button
+                    && !DisableAiSettings::get_global(cx).disable_ai,
                 |bar| bar.child(assistant_button),
             )
             .children(code_actions_dropdown)

crates/zeta/src/init.rs 🔗

@@ -1,10 +1,11 @@
 use std::any::{Any, TypeId};
 
+use client::DisableAiSettings;
 use command_palette_hooks::CommandPaletteFilter;
 use feature_flags::{FeatureFlagAppExt as _, PredictEditsRateCompletionsFeatureFlag};
 use gpui::actions;
 use language::language_settings::{AllLanguageSettings, EditPredictionProvider};
-use settings::update_settings_file;
+use settings::{Settings, SettingsStore, update_settings_file};
 use ui::App;
 use workspace::Workspace;
 
@@ -21,6 +22,8 @@ actions!(
 );
 
 pub fn init(cx: &mut App) {
+    feature_gate_predict_edits_actions(cx);
+
     cx.observe_new(move |workspace: &mut Workspace, _, _cx| {
         workspace.register_action(|workspace, _: &RateCompletions, window, cx| {
             if cx.has_flag::<PredictEditsRateCompletionsFeatureFlag>() {
@@ -53,27 +56,57 @@ pub fn init(cx: &mut App) {
         });
     })
     .detach();
-
-    feature_gate_predict_edits_rating_actions(cx);
 }
 
-fn feature_gate_predict_edits_rating_actions(cx: &mut App) {
+fn feature_gate_predict_edits_actions(cx: &mut App) {
     let rate_completion_action_types = [TypeId::of::<RateCompletions>()];
+    let reset_onboarding_action_types = [TypeId::of::<ResetOnboarding>()];
+    let zeta_all_action_types = [
+        TypeId::of::<RateCompletions>(),
+        TypeId::of::<ResetOnboarding>(),
+        zed_actions::OpenZedPredictOnboarding.type_id(),
+        TypeId::of::<crate::ClearHistory>(),
+        TypeId::of::<crate::ThumbsUpActiveCompletion>(),
+        TypeId::of::<crate::ThumbsDownActiveCompletion>(),
+        TypeId::of::<crate::NextEdit>(),
+        TypeId::of::<crate::PreviousEdit>(),
+    ];
 
     CommandPaletteFilter::update_global(cx, |filter, _cx| {
         filter.hide_action_types(&rate_completion_action_types);
+        filter.hide_action_types(&reset_onboarding_action_types);
         filter.hide_action_types(&[zed_actions::OpenZedPredictOnboarding.type_id()]);
     });
 
+    cx.observe_global::<SettingsStore>(move |cx| {
+        let is_ai_disabled = DisableAiSettings::get_global(cx).disable_ai;
+        let has_feature_flag = cx.has_flag::<PredictEditsRateCompletionsFeatureFlag>();
+
+        CommandPaletteFilter::update_global(cx, |filter, _cx| {
+            if is_ai_disabled {
+                filter.hide_action_types(&zeta_all_action_types);
+            } else {
+                if has_feature_flag {
+                    filter.show_action_types(rate_completion_action_types.iter());
+                } else {
+                    filter.hide_action_types(&rate_completion_action_types);
+                }
+            }
+        });
+    })
+    .detach();
+
     cx.observe_flag::<PredictEditsRateCompletionsFeatureFlag, _>(move |is_enabled, cx| {
-        if is_enabled {
-            CommandPaletteFilter::update_global(cx, |filter, _cx| {
-                filter.show_action_types(rate_completion_action_types.iter());
-            });
-        } else {
-            CommandPaletteFilter::update_global(cx, |filter, _cx| {
-                filter.hide_action_types(&rate_completion_action_types);
-            });
+        if !DisableAiSettings::get_global(cx).disable_ai {
+            if is_enabled {
+                CommandPaletteFilter::update_global(cx, |filter, _cx| {
+                    filter.show_action_types(rate_completion_action_types.iter());
+                });
+            } else {
+                CommandPaletteFilter::update_global(cx, |filter, _cx| {
+                    filter.hide_action_types(&rate_completion_action_types);
+                });
+            }
         }
     })
     .detach();