Agent settings

Conrad Irwin and Ben Kunkle created

Co-authored-by: Ben Kunkle <ben@zed.dev>

Change summary

crates/agent/src/agent_profile.rs           |   2 
crates/agent_settings/src/agent_profile.rs  |  97 ++-
crates/agent_settings/src/agent_settings.rs | 553 +++++-----------------
crates/agent_ui/src/agent_panel.rs          |   2 
crates/settings/src/settings_content.rs     |   4 
5 files changed, 205 insertions(+), 453 deletions(-)

Detailed changes

crates/agent/src/agent_profile.rs 🔗

@@ -52,7 +52,7 @@ impl AgentProfile {
         update_settings_file::<AgentSettings>(fs, cx, {
             let id = id.clone();
             move |settings, _cx| {
-                settings.create_profile(id, profile_settings).log_err();
+                profile_settings.save_to_settings(id, settings).log_err();
             }
         });
 

crates/agent_settings/src/agent_profile.rs 🔗

@@ -1,15 +1,17 @@
 use std::sync::Arc;
 
+use anyhow::{Result, bail};
 use collections::IndexMap;
 use convert_case::{Case, Casing as _};
 use fs::Fs;
 use gpui::{App, SharedString};
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use settings::{Settings as _, update_settings_file};
+use settings::{
+    AgentProfileContent, ContextServerPresetContent, Settings as _, SettingsContent,
+    update_settings_file,
+};
 use util::ResultExt as _;
 
-use crate::AgentSettings;
+use crate::{AgentProfileId, AgentSettings};
 
 pub mod builtin_profiles {
     use super::AgentProfileId;
@@ -23,27 +25,6 @@ pub mod builtin_profiles {
     }
 }
 
-#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)]
-pub struct AgentProfileId(pub Arc<str>);
-
-impl AgentProfileId {
-    pub fn as_str(&self) -> &str {
-        &self.0
-    }
-}
-
-impl std::fmt::Display for AgentProfileId {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.0)
-    }
-}
-
-impl Default for AgentProfileId {
-    fn default() -> Self {
-        Self("write".into())
-    }
-}
-
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct AgentProfile {
     id: AgentProfileId,
@@ -87,10 +68,10 @@ impl AgentProfile {
                 .unwrap_or_default(),
         };
 
-        update_settings_file::<AgentSettings>(fs, cx, {
+        update_settings_file(fs, cx, {
             let id = id.clone();
             move |settings, _cx| {
-                settings.create_profile(id, profile_settings).log_err();
+                profile_settings.save_to_settings(id, settings).log_err();
             }
         });
 
@@ -129,9 +110,71 @@ impl AgentProfileSettings {
                 .get(server_id)
                 .is_some_and(|preset| preset.tools.get(tool_name) == Some(&true))
     }
+
+    pub fn save_to_settings(
+        &self,
+        profile_id: AgentProfileId,
+        content: &mut SettingsContent,
+    ) -> Result<()> {
+        let profiles = content
+            .agent
+            .get_or_insert_default()
+            .profiles
+            .get_or_insert_default();
+        if profiles.contains_key(&profile_id.0) {
+            bail!("profile with ID '{profile_id}' already exists");
+        }
+
+        profiles.insert(
+            profile_id.0.clone(),
+            AgentProfileContent {
+                name: self.name.clone().into(),
+                tools: self.tools.clone(),
+                enable_all_context_servers: Some(self.enable_all_context_servers),
+                context_servers: self
+                    .context_servers
+                    .clone()
+                    .into_iter()
+                    .map(|(server_id, preset)| {
+                        (
+                            server_id,
+                            ContextServerPresetContent {
+                                tools: preset.tools,
+                            },
+                        )
+                    })
+                    .collect(),
+            },
+        );
+
+        Ok(())
+    }
+}
+
+impl From<AgentProfileContent> for AgentProfileSettings {
+    fn from(content: AgentProfileContent) -> Self {
+        Self {
+            name: content.name.into(),
+            tools: content.tools,
+            enable_all_context_servers: content.enable_all_context_servers.unwrap_or_default(),
+            context_servers: content
+                .context_servers
+                .into_iter()
+                .map(|(server_id, preset)| (server_id, preset.into()))
+                .collect(),
+        }
+    }
 }
 
 #[derive(Debug, Clone, Default)]
 pub struct ContextServerPreset {
     pub tools: IndexMap<Arc<str>, bool>,
 }
+
+impl From<settings::ContextServerPresetContent> for ContextServerPreset {
+    fn from(content: settings::ContextServerPresetContent) -> Self {
+        Self {
+            tools: content.tools,
+        }
+    }
+}

crates/agent_settings/src/agent_settings.rs 🔗

@@ -2,14 +2,16 @@ mod agent_profile;
 
 use std::sync::Arc;
 
-use anyhow::{Result, bail};
 use collections::IndexMap;
-use gpui::{App, Pixels, SharedString};
+use gpui::{App, Pixels, px};
 use language_model::LanguageModel;
-use schemars::{JsonSchema, json_schema};
+use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
-use std::borrow::Cow;
+use settings::{
+    AgentDockPosition, DefaultAgentView, LanguageModelParameters, LanguageModelSelection,
+    NotifyWhenAgentWaiting, Settings, SettingsContent,
+};
+use util::MergeFrom;
 
 pub use crate::agent_profile::*;
 
@@ -22,32 +24,6 @@ pub fn init(cx: &mut App) {
     AgentSettings::register(cx);
 }
 
-#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum AgentDockPosition {
-    Left,
-    #[default]
-    Right,
-    Bottom,
-}
-
-#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum DefaultView {
-    #[default]
-    Thread,
-    TextThread,
-}
-
-#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-#[serde(rename_all = "snake_case")]
-pub enum NotifyWhenAgentWaiting {
-    #[default]
-    PrimaryScreen,
-    AllScreens,
-    Never,
-}
-
 #[derive(Default, Clone, Debug)]
 pub struct AgentSettings {
     pub enabled: bool,
@@ -60,9 +36,8 @@ pub struct AgentSettings {
     pub commit_message_model: Option<LanguageModelSelection>,
     pub thread_summary_model: Option<LanguageModelSelection>,
     pub inline_alternatives: Vec<LanguageModelSelection>,
-    pub using_outdated_settings_version: bool,
     pub default_profile: AgentProfileId,
-    pub default_view: DefaultView,
+    pub default_view: DefaultAgentView,
     pub profiles: IndexMap<AgentProfileId, AgentProfileSettings>,
     pub always_allow_tool_actions: bool,
     pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
@@ -80,71 +55,20 @@ pub struct AgentSettings {
 impl AgentSettings {
     pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
         let settings = Self::get_global(cx);
-        settings
-            .model_parameters
-            .iter()
-            .rfind(|setting| setting.matches(model))
-            .and_then(|m| m.temperature)
-    }
-
-    pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
-        self.inline_assistant_model = Some(LanguageModelSelection {
-            provider: provider.into(),
-            model,
-        });
-    }
-
-    pub fn set_commit_message_model(&mut self, provider: String, model: String) {
-        self.commit_message_model = Some(LanguageModelSelection {
-            provider: provider.into(),
-            model,
-        });
-    }
-
-    pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
-        self.thread_summary_model = Some(LanguageModelSelection {
-            provider: provider.into(),
-            model,
-        });
-    }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
-pub struct LanguageModelParameters {
-    pub provider: Option<LanguageModelProviderSetting>,
-    pub model: Option<SharedString>,
-    pub temperature: Option<f32>,
-}
-
-impl LanguageModelParameters {
-    pub fn matches(&self, model: &Arc<dyn LanguageModel>) -> bool {
-        if let Some(provider) = &self.provider
-            && provider.0 != model.provider_id().0
-        {
-            return false;
-        }
-        if let Some(setting_model) = &self.model
-            && *setting_model != model.id().0
-        {
-            return false;
+        for setting in settings.model_parameters.iter().rev() {
+            if let Some(provider) = &setting.provider
+                && provider.0 != model.provider_id().0
+            {
+                continue;
+            }
+            if let Some(setting_model) = &setting.model
+                && *setting_model != model.id().0
+            {
+                continue;
+            }
+            return setting.temperature;
         }
-        true
-    }
-}
-
-impl AgentSettingsContent {
-    pub fn set_dock(&mut self, dock: AgentDockPosition) {
-        self.dock = Some(dock);
-    }
-
-    pub fn set_model(&mut self, language_model: Arc<dyn LanguageModel>) {
-        let model = language_model.id().0.to_string();
-        let provider = language_model.provider_id().0.to_string();
-
-        self.default_model = Some(LanguageModelSelection {
-            provider: provider.into(),
-            model,
-        });
+        return None;
     }
 
     pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
@@ -167,159 +91,6 @@ impl AgentSettingsContent {
             model,
         });
     }
-
-    pub fn set_always_allow_tool_actions(&mut self, allow: bool) {
-        self.always_allow_tool_actions = Some(allow);
-    }
-
-    pub fn set_play_sound_when_agent_done(&mut self, allow: bool) {
-        self.play_sound_when_agent_done = Some(allow);
-    }
-
-    pub fn set_single_file_review(&mut self, allow: bool) {
-        self.single_file_review = Some(allow);
-    }
-
-    pub fn set_use_modifier_to_send(&mut self, always_use: bool) {
-        self.use_modifier_to_send = Some(always_use);
-    }
-
-    pub fn set_profile(&mut self, profile_id: AgentProfileId) {
-        self.default_profile = Some(profile_id);
-    }
-
-    pub fn create_profile(
-        &mut self,
-        profile_id: AgentProfileId,
-        profile_settings: AgentProfileSettings,
-    ) -> Result<()> {
-        let profiles = self.profiles.get_or_insert_default();
-        if profiles.contains_key(&profile_id) {
-            bail!("profile with ID '{profile_id}' already exists");
-        }
-
-        profiles.insert(
-            profile_id,
-            AgentProfileContent {
-                name: profile_settings.name.into(),
-                tools: profile_settings.tools,
-                enable_all_context_servers: Some(profile_settings.enable_all_context_servers),
-                context_servers: profile_settings
-                    .context_servers
-                    .into_iter()
-                    .map(|(server_id, preset)| {
-                        (
-                            server_id,
-                            ContextServerPresetContent {
-                                tools: preset.tools,
-                            },
-                        )
-                    })
-                    .collect(),
-            },
-        );
-
-        Ok(())
-    }
-}
-
-#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default, SettingsUi, SettingsKey)]
-#[settings_key(key = "agent", fallback_key = "assistant")]
-pub struct AgentSettingsContent {
-    /// Whether the Agent is enabled.
-    ///
-    /// Default: true
-    enabled: Option<bool>,
-    /// Whether to show the agent panel button in the status bar.
-    ///
-    /// Default: true
-    button: Option<bool>,
-    /// Where to dock the agent panel.
-    ///
-    /// Default: right
-    dock: Option<AgentDockPosition>,
-    /// Default width in pixels when the agent panel is docked to the left or right.
-    ///
-    /// Default: 640
-    default_width: Option<f32>,
-    /// Default height in pixels when the agent panel is docked to the bottom.
-    ///
-    /// Default: 320
-    default_height: Option<f32>,
-    /// The default model to use when creating new chats and for other features when a specific model is not specified.
-    default_model: Option<LanguageModelSelection>,
-    /// Model to use for the inline assistant. Defaults to default_model when not specified.
-    inline_assistant_model: Option<LanguageModelSelection>,
-    /// Model to use for generating git commit messages. Defaults to default_model when not specified.
-    commit_message_model: Option<LanguageModelSelection>,
-    /// Model to use for generating thread summaries. Defaults to default_model when not specified.
-    thread_summary_model: Option<LanguageModelSelection>,
-    /// Additional models with which to generate alternatives when performing inline assists.
-    inline_alternatives: Option<Vec<LanguageModelSelection>>,
-    /// The default profile to use in the Agent.
-    ///
-    /// Default: write
-    default_profile: Option<AgentProfileId>,
-    /// Which view type to show by default in the agent panel.
-    ///
-    /// Default: "thread"
-    default_view: Option<DefaultView>,
-    /// The available agent profiles.
-    pub profiles: Option<IndexMap<AgentProfileId, AgentProfileContent>>,
-    /// Whenever a tool action would normally wait for your confirmation
-    /// that you allow it, always choose to allow it.
-    ///
-    /// This setting has no effect on external agents that support permission modes, such as Claude Code.
-    ///
-    /// Set `agent_servers.claude.default_mode` to `bypassPermissions`, to disable all permission requests when using Claude Code.
-    ///
-    /// Default: false
-    always_allow_tool_actions: Option<bool>,
-    /// Where to show a popup notification when the agent is waiting for user input.
-    ///
-    /// Default: "primary_screen"
-    notify_when_agent_waiting: Option<NotifyWhenAgentWaiting>,
-    /// Whether to play a sound when the agent has either completed its response, or needs user input.
-    ///
-    /// Default: false
-    play_sound_when_agent_done: Option<bool>,
-    /// Whether to stream edits from the agent as they are received.
-    ///
-    /// Default: false
-    stream_edits: Option<bool>,
-    /// Whether to display agent edits in single-file editors in addition to the review multibuffer pane.
-    ///
-    /// Default: true
-    single_file_review: Option<bool>,
-    /// Additional parameters for language model requests. When making a request
-    /// to a model, parameters will be taken from the last entry in this list
-    /// that matches the model's provider and name. In each entry, both provider
-    /// and model are optional, so that you can specify parameters for either
-    /// one.
-    ///
-    /// Default: []
-    #[serde(default)]
-    model_parameters: Vec<LanguageModelParameters>,
-    /// What completion mode to enable for new threads
-    ///
-    /// Default: normal
-    preferred_completion_mode: Option<CompletionMode>,
-    /// Whether to show thumb buttons for feedback in the agent panel.
-    ///
-    /// Default: true
-    enable_feedback: Option<bool>,
-    /// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff.
-    ///
-    /// Default: true
-    expand_edit_card: Option<bool>,
-    /// Whether to have terminal cards in the agent panel expanded, showing the whole command output.
-    ///
-    /// Default: true
-    expand_terminal_card: Option<bool>,
-    /// Whether to always use cmd-enter (or ctrl-enter on Linux or Windows) to send messages in the agent panel.
-    ///
-    /// Default: false
-    use_modifier_to_send: Option<bool>,
 }
 
 #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
@@ -340,202 +111,136 @@ impl From<CompletionMode> for cloud_llm_client::CompletionMode {
     }
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
-pub struct LanguageModelSelection {
-    pub provider: LanguageModelProviderSetting,
-    pub model: String,
+impl From<settings::CompletionMode> for CompletionMode {
+    fn from(value: settings::CompletionMode) -> Self {
+        match value {
+            settings::CompletionMode::Normal => CompletionMode::Normal,
+            settings::CompletionMode::Burn => CompletionMode::Burn,
+        }
+    }
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-pub struct LanguageModelProviderSetting(pub String);
-
-impl JsonSchema for LanguageModelProviderSetting {
-    fn schema_name() -> Cow<'static, str> {
-        "LanguageModelProviderSetting".into()
-    }
+#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)]
+pub struct AgentProfileId(pub Arc<str>);
 
-    fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
-        json_schema!({
-            "enum": [
-                "amazon-bedrock",
-                "anthropic",
-                "copilot_chat",
-                "deepseek",
-                "google",
-                "lmstudio",
-                "mistral",
-                "ollama",
-                "openai",
-                "openrouter",
-                "vercel",
-                "x_ai",
-                "zed.dev"
-            ]
-        })
+impl AgentProfileId {
+    pub fn as_str(&self) -> &str {
+        &self.0
     }
 }
 
-impl From<String> for LanguageModelProviderSetting {
-    fn from(provider: String) -> Self {
-        Self(provider)
+impl std::fmt::Display for AgentProfileId {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
     }
 }
 
-impl From<&str> for LanguageModelProviderSetting {
-    fn from(provider: &str) -> Self {
-        Self(provider.to_string())
+impl Default for AgentProfileId {
+    fn default() -> Self {
+        Self("write".into())
     }
 }
 
-#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]
-pub struct AgentProfileContent {
-    pub name: Arc<str>,
-    #[serde(default)]
-    pub tools: IndexMap<Arc<str>, bool>,
-    /// Whether all context servers are enabled by default.
-    pub enable_all_context_servers: Option<bool>,
-    #[serde(default)]
-    pub context_servers: IndexMap<Arc<str>, ContextServerPresetContent>,
-}
-
-#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)]
-pub struct ContextServerPresetContent {
-    pub tools: IndexMap<Arc<str>, bool>,
-}
-
 impl Settings for AgentSettings {
-    const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
-
-    type FileContent = AgentSettingsContent;
-
-    fn load(
-        sources: SettingsSources<Self::FileContent>,
-        _: &mut gpui::App,
-    ) -> anyhow::Result<Self> {
-        let mut settings = AgentSettings::default();
+    // todo!() test preserved keys logic
+    fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
+        let agent = content.agent.clone().unwrap();
+        Self {
+            enabled: agent.enabled.unwrap(),
+            button: agent.button.unwrap(),
+            dock: agent.dock.unwrap(),
+            default_width: px(agent.default_width.unwrap()),
+            default_height: px(agent.default_height.unwrap()),
+            default_model: Some(agent.default_model.unwrap()),
+            inline_assistant_model: agent.inline_assistant_model,
+            commit_message_model: agent.commit_message_model,
+            thread_summary_model: agent.thread_summary_model,
+            inline_alternatives: agent.inline_alternatives.unwrap_or_default(),
+            default_profile: AgentProfileId(agent.default_profile.unwrap()),
+            default_view: agent.default_view.unwrap(),
+            profiles: agent
+                .profiles
+                .unwrap()
+                .into_iter()
+                .map(|(key, val)| (AgentProfileId(key), val.into()))
+                .collect(),
+            always_allow_tool_actions: agent.always_allow_tool_actions.unwrap(),
+            notify_when_agent_waiting: agent.notify_when_agent_waiting.unwrap(),
+            play_sound_when_agent_done: agent.play_sound_when_agent_done.unwrap(),
+            stream_edits: agent.stream_edits.unwrap(),
+            single_file_review: agent.single_file_review.unwrap(),
+            model_parameters: agent.model_parameters,
+            preferred_completion_mode: agent.preferred_completion_mode.unwrap().into(),
+            enable_feedback: agent.enable_feedback.unwrap(),
+            expand_edit_card: agent.expand_edit_card.unwrap(),
+            expand_terminal_card: agent.expand_terminal_card.unwrap(),
+            use_modifier_to_send: agent.use_modifier_to_send.unwrap(),
+        }
+    }
 
-        for value in sources.defaults_and_customizations() {
-            merge(&mut settings.enabled, value.enabled);
-            merge(&mut settings.button, value.button);
-            merge(&mut settings.dock, value.dock);
-            merge(
-                &mut settings.default_width,
-                value.default_width.map(Into::into),
-            );
-            merge(
-                &mut settings.default_height,
-                value.default_height.map(Into::into),
-            );
-            settings.default_model = value
-                .default_model
-                .clone()
-                .or(settings.default_model.take());
-            settings.inline_assistant_model = value
-                .inline_assistant_model
-                .clone()
-                .or(settings.inline_assistant_model.take());
-            settings.commit_message_model = value
-                .clone()
-                .commit_message_model
-                .or(settings.commit_message_model.take());
-            settings.thread_summary_model = value
-                .clone()
-                .thread_summary_model
-                .or(settings.thread_summary_model.take());
-            merge(
-                &mut settings.inline_alternatives,
-                value.inline_alternatives.clone(),
-            );
-            merge(
-                &mut settings.notify_when_agent_waiting,
-                value.notify_when_agent_waiting,
-            );
-            merge(
-                &mut settings.play_sound_when_agent_done,
-                value.play_sound_when_agent_done,
-            );
-            merge(&mut settings.stream_edits, value.stream_edits);
-            merge(&mut settings.single_file_review, value.single_file_review);
-            merge(&mut settings.default_profile, value.default_profile.clone());
-            merge(&mut settings.default_view, value.default_view);
-            merge(
-                &mut settings.preferred_completion_mode,
-                value.preferred_completion_mode,
-            );
-            merge(&mut settings.enable_feedback, value.enable_feedback);
-            merge(&mut settings.expand_edit_card, value.expand_edit_card);
-            merge(
-                &mut settings.expand_terminal_card,
-                value.expand_terminal_card,
-            );
-            merge(
-                &mut settings.use_modifier_to_send,
-                value.use_modifier_to_send,
+    fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) {
+        let Some(value) = &content.agent else { return };
+        self.enabled.merge_from(&value.enabled);
+        self.button.merge_from(&value.button);
+        self.dock.merge_from(&value.dock);
+        self.default_width
+            .merge_from(&value.default_width.map(Into::into));
+        self.default_height
+            .merge_from(&value.default_height.map(Into::into));
+        self.default_model = value.default_model.clone().or(self.default_model.take());
+
+        self.inline_assistant_model = value
+            .inline_assistant_model
+            .clone()
+            .or(self.inline_assistant_model.take());
+        self.commit_message_model = value
+            .clone()
+            .commit_message_model
+            .or(self.commit_message_model.take());
+        self.thread_summary_model = value
+            .clone()
+            .thread_summary_model
+            .or(self.thread_summary_model.take());
+        self.inline_alternatives
+            .merge_from(&value.inline_alternatives.clone());
+        self.notify_when_agent_waiting
+            .merge_from(&value.notify_when_agent_waiting);
+        self.play_sound_when_agent_done
+            .merge_from(&value.play_sound_when_agent_done);
+        self.stream_edits.merge_from(&value.stream_edits);
+        self.single_file_review
+            .merge_from(&value.single_file_review);
+        self.default_profile
+            .merge_from(&value.default_profile.clone().map(AgentProfileId));
+        self.default_view.merge_from(&value.default_view);
+        self.preferred_completion_mode
+            .merge_from(&value.preferred_completion_mode.map(Into::into));
+        self.enable_feedback.merge_from(&value.enable_feedback);
+        self.expand_edit_card.merge_from(&value.expand_edit_card);
+        self.expand_terminal_card
+            .merge_from(&value.expand_terminal_card);
+        self.use_modifier_to_send
+            .merge_from(&value.use_modifier_to_send);
+
+        self.model_parameters
+            .extend_from_slice(&value.model_parameters);
+
+        if let Some(profiles) = value.profiles.as_ref() {
+            self.profiles.extend(
+                profiles
+                    .into_iter()
+                    .map(|(id, profile)| (AgentProfileId(id.clone()), profile.clone().into())),
             );
-
-            settings
-                .model_parameters
-                .extend_from_slice(&value.model_parameters);
-
-            if let Some(profiles) = value.profiles.as_ref() {
-                settings
-                    .profiles
-                    .extend(profiles.into_iter().map(|(id, profile)| {
-                        (
-                            id.clone(),
-                            AgentProfileSettings {
-                                name: profile.name.clone().into(),
-                                tools: profile.tools.clone(),
-                                enable_all_context_servers: profile
-                                    .enable_all_context_servers
-                                    .unwrap_or_default(),
-                                context_servers: profile
-                                    .context_servers
-                                    .iter()
-                                    .map(|(context_server_id, preset)| {
-                                        (
-                                            context_server_id.clone(),
-                                            ContextServerPreset {
-                                                tools: preset.tools.clone(),
-                                            },
-                                        )
-                                    })
-                                    .collect(),
-                            },
-                        )
-                    }));
-            }
         }
-
-        debug_assert!(
-            !sources.default.always_allow_tool_actions.unwrap_or(false),
-            "For security, agent.always_allow_tool_actions should always be false in default.json. If it's true, that is a bug that should be fixed!"
-        );
-
-        // For security reasons, only trust the user's global settings for whether to always allow tool actions.
-        // If this could be overridden locally, an attacker could (e.g. by committing to source control and
-        // convincing you to switch branches) modify your project-local settings to disable the agent's safety checks.
-        settings.always_allow_tool_actions = sources
-            .user
-            .and_then(|setting| setting.always_allow_tool_actions)
-            .unwrap_or(false);
-
-        Ok(settings)
     }
 
-    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
+    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
         if let Some(b) = vscode
             .read_value("chat.agent.enabled")
             .and_then(|b| b.as_bool())
         {
-            current.enabled = Some(b);
-            current.button = Some(b);
+            current.agent.get_or_insert_default().enabled = Some(b);
+            current.agent.get_or_insert_default().button = Some(b);
         }
     }
 }
-
-fn merge<T>(target: &mut T, value: Option<T>) {
-    if let Some(value) = value {
-        *target = value;
-    }
-}

crates/agent_ui/src/agent_panel.rs 🔗

@@ -33,7 +33,7 @@ use agent::{
     history_store::{HistoryEntryId, HistoryStore},
     thread_store::{TextThreadStore, ThreadStore},
 };
-use agent_settings::{AgentDockPosition, AgentSettings, DefaultView};
+use agent_settings::{AgentDockPosition, AgentSettings, DefaultAgentView};
 use ai_onboarding::AgentPanelOnboarding;
 use anyhow::{Result, anyhow};
 use assistant_context::{AssistantContext, ContextEvent, ContextSummary};

crates/settings/src/settings_content.rs 🔗

@@ -1,6 +1,8 @@
+mod agent;
 mod language;
 mod terminal;
 mod theme;
+pub use agent::*;
 pub use language::*;
 pub use terminal::*;
 pub use theme::*;
@@ -23,6 +25,8 @@ pub struct SettingsContent {
     #[serde(flatten)]
     pub theme: ThemeSettingsContent,
 
+    pub agent: Option<AgentSettingsContent>,
+
     /// Configuration of audio in Zed.
     pub audio: Option<AudioSettingsContent>,
     pub auto_update: Option<bool>,