agent_settings.rs

   1mod agent_profile;
   2
   3use std::sync::Arc;
   4
   5use ::open_ai::Model as OpenAiModel;
   6use anthropic::Model as AnthropicModel;
   7use anyhow::{Result, bail};
   8use collections::IndexMap;
   9use deepseek::Model as DeepseekModel;
  10use gpui::{App, Pixels, SharedString};
  11use language_model::LanguageModel;
  12use lmstudio::Model as LmStudioModel;
  13use mistral::Model as MistralModel;
  14use ollama::Model as OllamaModel;
  15use schemars::{JsonSchema, schema::Schema};
  16use serde::{Deserialize, Serialize};
  17use settings::{Settings, SettingsSources};
  18
  19pub use crate::agent_profile::*;
  20
  21pub fn init(cx: &mut App) {
  22    AgentSettings::register(cx);
  23}
  24
  25#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
  26#[serde(rename_all = "snake_case")]
  27pub enum AgentDockPosition {
  28    Left,
  29    #[default]
  30    Right,
  31    Bottom,
  32}
  33
  34#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
  35#[serde(rename_all = "snake_case")]
  36pub enum DefaultView {
  37    #[default]
  38    Thread,
  39    TextThread,
  40}
  41
  42#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
  43#[serde(rename_all = "snake_case")]
  44pub enum NotifyWhenAgentWaiting {
  45    #[default]
  46    PrimaryScreen,
  47    AllScreens,
  48    Never,
  49}
  50
  51#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
  52#[serde(tag = "name", rename_all = "snake_case")]
  53#[schemars(deny_unknown_fields)]
  54pub enum AgentProviderContentV1 {
  55    #[serde(rename = "zed.dev")]
  56    ZedDotDev { default_model: Option<String> },
  57    #[serde(rename = "openai")]
  58    OpenAi {
  59        default_model: Option<OpenAiModel>,
  60        api_url: Option<String>,
  61        available_models: Option<Vec<OpenAiModel>>,
  62    },
  63    #[serde(rename = "anthropic")]
  64    Anthropic {
  65        default_model: Option<AnthropicModel>,
  66        api_url: Option<String>,
  67    },
  68    #[serde(rename = "ollama")]
  69    Ollama {
  70        default_model: Option<OllamaModel>,
  71        api_url: Option<String>,
  72    },
  73    #[serde(rename = "lmstudio")]
  74    LmStudio {
  75        default_model: Option<LmStudioModel>,
  76        api_url: Option<String>,
  77    },
  78    #[serde(rename = "deepseek")]
  79    DeepSeek {
  80        default_model: Option<DeepseekModel>,
  81        api_url: Option<String>,
  82    },
  83    #[serde(rename = "mistral")]
  84    Mistral {
  85        default_model: Option<MistralModel>,
  86        api_url: Option<String>,
  87    },
  88}
  89
  90#[derive(Default, Clone, Debug)]
  91pub struct AgentSettings {
  92    pub enabled: bool,
  93    pub button: bool,
  94    pub dock: AgentDockPosition,
  95    pub default_width: Pixels,
  96    pub default_height: Pixels,
  97    pub default_model: LanguageModelSelection,
  98    pub inline_assistant_model: Option<LanguageModelSelection>,
  99    pub commit_message_model: Option<LanguageModelSelection>,
 100    pub thread_summary_model: Option<LanguageModelSelection>,
 101    pub inline_alternatives: Vec<LanguageModelSelection>,
 102    pub using_outdated_settings_version: bool,
 103    pub default_profile: AgentProfileId,
 104    pub default_view: DefaultView,
 105    pub profiles: IndexMap<AgentProfileId, AgentProfileSettings>,
 106    pub always_allow_tool_actions: bool,
 107    pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
 108    pub play_sound_when_agent_done: bool,
 109    pub stream_edits: bool,
 110    pub single_file_review: bool,
 111    pub model_parameters: Vec<LanguageModelParameters>,
 112    pub preferred_completion_mode: CompletionMode,
 113    pub enable_feedback: bool,
 114}
 115
 116impl AgentSettings {
 117    pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
 118        let settings = Self::get_global(cx);
 119        settings
 120            .model_parameters
 121            .iter()
 122            .rfind(|setting| setting.matches(model))
 123            .and_then(|m| m.temperature)
 124    }
 125
 126    pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
 127        self.inline_assistant_model = Some(LanguageModelSelection {
 128            provider: provider.into(),
 129            model,
 130        });
 131    }
 132
 133    pub fn set_commit_message_model(&mut self, provider: String, model: String) {
 134        self.commit_message_model = Some(LanguageModelSelection {
 135            provider: provider.into(),
 136            model,
 137        });
 138    }
 139
 140    pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
 141        self.thread_summary_model = Some(LanguageModelSelection {
 142            provider: provider.into(),
 143            model,
 144        });
 145    }
 146}
 147
 148#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
 149pub struct LanguageModelParameters {
 150    pub provider: Option<LanguageModelProviderSetting>,
 151    pub model: Option<SharedString>,
 152    pub temperature: Option<f32>,
 153}
 154
 155impl LanguageModelParameters {
 156    pub fn matches(&self, model: &Arc<dyn LanguageModel>) -> bool {
 157        if let Some(provider) = &self.provider {
 158            if provider.0 != model.provider_id().0 {
 159                return false;
 160            }
 161        }
 162        if let Some(setting_model) = &self.model {
 163            if *setting_model != model.id().0 {
 164                return false;
 165            }
 166        }
 167        true
 168    }
 169}
 170
 171/// Agent panel settings
 172#[derive(Clone, Serialize, Deserialize, Debug, Default)]
 173pub struct AgentSettingsContent {
 174    #[serde(flatten)]
 175    pub inner: Option<AgentSettingsContentInner>,
 176}
 177
 178#[derive(Clone, Serialize, Deserialize, Debug)]
 179#[serde(untagged)]
 180pub enum AgentSettingsContentInner {
 181    Versioned(Box<VersionedAgentSettingsContent>),
 182    Legacy(LegacyAgentSettingsContent),
 183}
 184
 185impl AgentSettingsContentInner {
 186    fn for_v2(content: AgentSettingsContentV2) -> Self {
 187        AgentSettingsContentInner::Versioned(Box::new(VersionedAgentSettingsContent::V2(content)))
 188    }
 189}
 190
 191impl JsonSchema for AgentSettingsContent {
 192    fn schema_name() -> String {
 193        VersionedAgentSettingsContent::schema_name()
 194    }
 195
 196    fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> Schema {
 197        VersionedAgentSettingsContent::json_schema(r#gen)
 198    }
 199
 200    fn is_referenceable() -> bool {
 201        VersionedAgentSettingsContent::is_referenceable()
 202    }
 203}
 204
 205impl AgentSettingsContent {
 206    pub fn is_version_outdated(&self) -> bool {
 207        match &self.inner {
 208            Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
 209                VersionedAgentSettingsContent::V1(_) => true,
 210                VersionedAgentSettingsContent::V2(_) => false,
 211            },
 212            Some(AgentSettingsContentInner::Legacy(_)) => true,
 213            None => false,
 214        }
 215    }
 216
 217    fn upgrade(&self) -> AgentSettingsContentV2 {
 218        match &self.inner {
 219            Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
 220                VersionedAgentSettingsContent::V1(ref settings) => AgentSettingsContentV2 {
 221                    enabled: settings.enabled,
 222                    button: settings.button,
 223                    dock: settings.dock,
 224                    default_width: settings.default_width,
 225                    default_height: settings.default_width,
 226                    default_model: settings
 227                        .provider
 228                        .clone()
 229                        .and_then(|provider| match provider {
 230                            AgentProviderContentV1::ZedDotDev { default_model } => default_model
 231                                .map(|model| LanguageModelSelection {
 232                                    provider: "zed.dev".into(),
 233                                    model,
 234                                }),
 235                            AgentProviderContentV1::OpenAi { default_model, .. } => default_model
 236                                .map(|model| LanguageModelSelection {
 237                                    provider: "openai".into(),
 238                                    model: model.id().to_string(),
 239                                }),
 240                            AgentProviderContentV1::Anthropic { default_model, .. } => {
 241                                default_model.map(|model| LanguageModelSelection {
 242                                    provider: "anthropic".into(),
 243                                    model: model.id().to_string(),
 244                                })
 245                            }
 246                            AgentProviderContentV1::Ollama { default_model, .. } => default_model
 247                                .map(|model| LanguageModelSelection {
 248                                    provider: "ollama".into(),
 249                                    model: model.id().to_string(),
 250                                }),
 251                            AgentProviderContentV1::LmStudio { default_model, .. } => default_model
 252                                .map(|model| LanguageModelSelection {
 253                                    provider: "lmstudio".into(),
 254                                    model: model.id().to_string(),
 255                                }),
 256                            AgentProviderContentV1::DeepSeek { default_model, .. } => default_model
 257                                .map(|model| LanguageModelSelection {
 258                                    provider: "deepseek".into(),
 259                                    model: model.id().to_string(),
 260                                }),
 261                            AgentProviderContentV1::Mistral { default_model, .. } => default_model
 262                                .map(|model| LanguageModelSelection {
 263                                    provider: "mistral".into(),
 264                                    model: model.id().to_string(),
 265                                }),
 266                        }),
 267                    inline_assistant_model: None,
 268                    commit_message_model: None,
 269                    thread_summary_model: None,
 270                    inline_alternatives: None,
 271                    default_profile: None,
 272                    default_view: None,
 273                    profiles: None,
 274                    always_allow_tool_actions: None,
 275                    notify_when_agent_waiting: None,
 276                    stream_edits: None,
 277                    single_file_review: None,
 278                    model_parameters: Vec::new(),
 279                    preferred_completion_mode: None,
 280                    enable_feedback: None,
 281                    play_sound_when_agent_done: None,
 282                },
 283                VersionedAgentSettingsContent::V2(ref settings) => settings.clone(),
 284            },
 285            Some(AgentSettingsContentInner::Legacy(settings)) => AgentSettingsContentV2 {
 286                enabled: None,
 287                button: settings.button,
 288                dock: settings.dock,
 289                default_width: settings.default_width,
 290                default_height: settings.default_height,
 291                default_model: Some(LanguageModelSelection {
 292                    provider: "openai".into(),
 293                    model: settings
 294                        .default_open_ai_model
 295                        .clone()
 296                        .unwrap_or_default()
 297                        .id()
 298                        .to_string(),
 299                }),
 300                inline_assistant_model: None,
 301                commit_message_model: None,
 302                thread_summary_model: None,
 303                inline_alternatives: None,
 304                default_profile: None,
 305                default_view: None,
 306                profiles: None,
 307                always_allow_tool_actions: None,
 308                notify_when_agent_waiting: None,
 309                stream_edits: None,
 310                single_file_review: None,
 311                model_parameters: Vec::new(),
 312                preferred_completion_mode: None,
 313                enable_feedback: None,
 314                play_sound_when_agent_done: None,
 315            },
 316            None => AgentSettingsContentV2::default(),
 317        }
 318    }
 319
 320    pub fn set_dock(&mut self, dock: AgentDockPosition) {
 321        match &mut self.inner {
 322            Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
 323                VersionedAgentSettingsContent::V1(ref mut settings) => {
 324                    settings.dock = Some(dock);
 325                }
 326                VersionedAgentSettingsContent::V2(ref mut settings) => {
 327                    settings.dock = Some(dock);
 328                }
 329            },
 330            Some(AgentSettingsContentInner::Legacy(settings)) => {
 331                settings.dock = Some(dock);
 332            }
 333            None => {
 334                self.inner = Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
 335                    dock: Some(dock),
 336                    ..Default::default()
 337                }))
 338            }
 339        }
 340    }
 341
 342    pub fn set_model(&mut self, language_model: Arc<dyn LanguageModel>) {
 343        let model = language_model.id().0.to_string();
 344        let provider = language_model.provider_id().0.to_string();
 345
 346        match &mut self.inner {
 347            Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
 348                VersionedAgentSettingsContent::V1(ref mut settings) => match provider.as_ref() {
 349                    "zed.dev" => {
 350                        log::warn!("attempted to set zed.dev model on outdated settings");
 351                    }
 352                    "anthropic" => {
 353                        let api_url = match &settings.provider {
 354                            Some(AgentProviderContentV1::Anthropic { api_url, .. }) => {
 355                                api_url.clone()
 356                            }
 357                            _ => None,
 358                        };
 359                        settings.provider = Some(AgentProviderContentV1::Anthropic {
 360                            default_model: AnthropicModel::from_id(&model).ok(),
 361                            api_url,
 362                        });
 363                    }
 364                    "ollama" => {
 365                        let api_url = match &settings.provider {
 366                            Some(AgentProviderContentV1::Ollama { api_url, .. }) => api_url.clone(),
 367                            _ => None,
 368                        };
 369                        settings.provider = Some(AgentProviderContentV1::Ollama {
 370                            default_model: Some(ollama::Model::new(
 371                                &model,
 372                                None,
 373                                None,
 374                                Some(language_model.supports_tools()),
 375                                Some(language_model.supports_images()),
 376                                None,
 377                            )),
 378                            api_url,
 379                        });
 380                    }
 381                    "lmstudio" => {
 382                        let api_url = match &settings.provider {
 383                            Some(AgentProviderContentV1::LmStudio { api_url, .. }) => {
 384                                api_url.clone()
 385                            }
 386                            _ => None,
 387                        };
 388                        settings.provider = Some(AgentProviderContentV1::LmStudio {
 389                            default_model: Some(lmstudio::Model::new(
 390                                &model, None, None, false, false,
 391                            )),
 392                            api_url,
 393                        });
 394                    }
 395                    "openai" => {
 396                        let (api_url, available_models) = match &settings.provider {
 397                            Some(AgentProviderContentV1::OpenAi {
 398                                api_url,
 399                                available_models,
 400                                ..
 401                            }) => (api_url.clone(), available_models.clone()),
 402                            _ => (None, None),
 403                        };
 404                        settings.provider = Some(AgentProviderContentV1::OpenAi {
 405                            default_model: OpenAiModel::from_id(&model).ok(),
 406                            api_url,
 407                            available_models,
 408                        });
 409                    }
 410                    "deepseek" => {
 411                        let api_url = match &settings.provider {
 412                            Some(AgentProviderContentV1::DeepSeek { api_url, .. }) => {
 413                                api_url.clone()
 414                            }
 415                            _ => None,
 416                        };
 417                        settings.provider = Some(AgentProviderContentV1::DeepSeek {
 418                            default_model: DeepseekModel::from_id(&model).ok(),
 419                            api_url,
 420                        });
 421                    }
 422                    _ => {}
 423                },
 424                VersionedAgentSettingsContent::V2(ref mut settings) => {
 425                    settings.default_model = Some(LanguageModelSelection {
 426                        provider: provider.into(),
 427                        model,
 428                    });
 429                }
 430            },
 431            Some(AgentSettingsContentInner::Legacy(settings)) => {
 432                if let Ok(model) = OpenAiModel::from_id(&language_model.id().0) {
 433                    settings.default_open_ai_model = Some(model);
 434                }
 435            }
 436            None => {
 437                self.inner = Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
 438                    default_model: Some(LanguageModelSelection {
 439                        provider: provider.into(),
 440                        model,
 441                    }),
 442                    ..Default::default()
 443                }));
 444            }
 445        }
 446    }
 447
 448    pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
 449        self.v2_setting(|setting| {
 450            setting.inline_assistant_model = Some(LanguageModelSelection {
 451                provider: provider.into(),
 452                model,
 453            });
 454            Ok(())
 455        })
 456        .ok();
 457    }
 458
 459    pub fn set_commit_message_model(&mut self, provider: String, model: String) {
 460        self.v2_setting(|setting| {
 461            setting.commit_message_model = Some(LanguageModelSelection {
 462                provider: provider.into(),
 463                model,
 464            });
 465            Ok(())
 466        })
 467        .ok();
 468    }
 469
 470    pub fn v2_setting(
 471        &mut self,
 472        f: impl FnOnce(&mut AgentSettingsContentV2) -> anyhow::Result<()>,
 473    ) -> anyhow::Result<()> {
 474        match self.inner.get_or_insert_with(|| {
 475            AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
 476                ..Default::default()
 477            })
 478        }) {
 479            AgentSettingsContentInner::Versioned(boxed) => {
 480                if let VersionedAgentSettingsContent::V2(ref mut settings) = **boxed {
 481                    f(settings)
 482                } else {
 483                    Ok(())
 484                }
 485            }
 486            _ => Ok(()),
 487        }
 488    }
 489
 490    pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
 491        self.v2_setting(|setting| {
 492            setting.thread_summary_model = Some(LanguageModelSelection {
 493                provider: provider.into(),
 494                model,
 495            });
 496            Ok(())
 497        })
 498        .ok();
 499    }
 500
 501    pub fn set_always_allow_tool_actions(&mut self, allow: bool) {
 502        self.v2_setting(|setting| {
 503            setting.always_allow_tool_actions = Some(allow);
 504            Ok(())
 505        })
 506        .ok();
 507    }
 508
 509    pub fn set_play_sound_when_agent_done(&mut self, allow: bool) {
 510        self.v2_setting(|setting| {
 511            setting.play_sound_when_agent_done = Some(allow);
 512            Ok(())
 513        })
 514        .ok();
 515    }
 516
 517    pub fn set_single_file_review(&mut self, allow: bool) {
 518        self.v2_setting(|setting| {
 519            setting.single_file_review = Some(allow);
 520            Ok(())
 521        })
 522        .ok();
 523    }
 524
 525    pub fn set_profile(&mut self, profile_id: AgentProfileId) {
 526        self.v2_setting(|setting| {
 527            setting.default_profile = Some(profile_id);
 528            Ok(())
 529        })
 530        .ok();
 531    }
 532
 533    pub fn create_profile(
 534        &mut self,
 535        profile_id: AgentProfileId,
 536        profile_settings: AgentProfileSettings,
 537    ) -> Result<()> {
 538        self.v2_setting(|settings| {
 539            let profiles = settings.profiles.get_or_insert_default();
 540            if profiles.contains_key(&profile_id) {
 541                bail!("profile with ID '{profile_id}' already exists");
 542            }
 543
 544            profiles.insert(
 545                profile_id,
 546                AgentProfileContent {
 547                    name: profile_settings.name.into(),
 548                    tools: profile_settings.tools,
 549                    enable_all_context_servers: Some(profile_settings.enable_all_context_servers),
 550                    context_servers: profile_settings
 551                        .context_servers
 552                        .into_iter()
 553                        .map(|(server_id, preset)| {
 554                            (
 555                                server_id,
 556                                ContextServerPresetContent {
 557                                    tools: preset.tools,
 558                                },
 559                            )
 560                        })
 561                        .collect(),
 562                },
 563            );
 564
 565            Ok(())
 566        })
 567    }
 568}
 569
 570#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
 571#[serde(tag = "version")]
 572#[schemars(deny_unknown_fields)]
 573pub enum VersionedAgentSettingsContent {
 574    #[serde(rename = "1")]
 575    V1(AgentSettingsContentV1),
 576    #[serde(rename = "2")]
 577    V2(AgentSettingsContentV2),
 578}
 579
 580impl Default for VersionedAgentSettingsContent {
 581    fn default() -> Self {
 582        Self::V2(AgentSettingsContentV2 {
 583            enabled: None,
 584            button: None,
 585            dock: None,
 586            default_width: None,
 587            default_height: None,
 588            default_model: None,
 589            inline_assistant_model: None,
 590            commit_message_model: None,
 591            thread_summary_model: None,
 592            inline_alternatives: None,
 593            default_profile: None,
 594            default_view: None,
 595            profiles: None,
 596            always_allow_tool_actions: None,
 597            notify_when_agent_waiting: None,
 598            stream_edits: None,
 599            single_file_review: None,
 600            model_parameters: Vec::new(),
 601            preferred_completion_mode: None,
 602            enable_feedback: None,
 603            play_sound_when_agent_done: None,
 604        })
 605    }
 606}
 607
 608#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
 609#[schemars(deny_unknown_fields)]
 610pub struct AgentSettingsContentV2 {
 611    /// Whether the Agent is enabled.
 612    ///
 613    /// Default: true
 614    enabled: Option<bool>,
 615    /// Whether to show the agent panel button in the status bar.
 616    ///
 617    /// Default: true
 618    button: Option<bool>,
 619    /// Where to dock the agent panel.
 620    ///
 621    /// Default: right
 622    dock: Option<AgentDockPosition>,
 623    /// Default width in pixels when the agent panel is docked to the left or right.
 624    ///
 625    /// Default: 640
 626    default_width: Option<f32>,
 627    /// Default height in pixels when the agent panel is docked to the bottom.
 628    ///
 629    /// Default: 320
 630    default_height: Option<f32>,
 631    /// The default model to use when creating new chats and for other features when a specific model is not specified.
 632    default_model: Option<LanguageModelSelection>,
 633    /// Model to use for the inline assistant. Defaults to default_model when not specified.
 634    inline_assistant_model: Option<LanguageModelSelection>,
 635    /// Model to use for generating git commit messages. Defaults to default_model when not specified.
 636    commit_message_model: Option<LanguageModelSelection>,
 637    /// Model to use for generating thread summaries. Defaults to default_model when not specified.
 638    thread_summary_model: Option<LanguageModelSelection>,
 639    /// Additional models with which to generate alternatives when performing inline assists.
 640    inline_alternatives: Option<Vec<LanguageModelSelection>>,
 641    /// The default profile to use in the Agent.
 642    ///
 643    /// Default: write
 644    default_profile: Option<AgentProfileId>,
 645    /// Which view type to show by default in the agent panel.
 646    ///
 647    /// Default: "thread"
 648    default_view: Option<DefaultView>,
 649    /// The available agent profiles.
 650    pub profiles: Option<IndexMap<AgentProfileId, AgentProfileContent>>,
 651    /// Whenever a tool action would normally wait for your confirmation
 652    /// that you allow it, always choose to allow it.
 653    ///
 654    /// Default: false
 655    always_allow_tool_actions: Option<bool>,
 656    /// Where to show a popup notification when the agent is waiting for user input.
 657    ///
 658    /// Default: "primary_screen"
 659    notify_when_agent_waiting: Option<NotifyWhenAgentWaiting>,
 660    /// Whether to play a sound when the agent has either completed its response, or needs user input.
 661    ///
 662    /// Default: false
 663    play_sound_when_agent_done: Option<bool>,
 664    /// Whether to stream edits from the agent as they are received.
 665    ///
 666    /// Default: false
 667    stream_edits: Option<bool>,
 668    /// Whether to display agent edits in single-file editors in addition to the review multibuffer pane.
 669    ///
 670    /// Default: true
 671    single_file_review: Option<bool>,
 672    /// Additional parameters for language model requests. When making a request
 673    /// to a model, parameters will be taken from the last entry in this list
 674    /// that matches the model's provider and name. In each entry, both provider
 675    /// and model are optional, so that you can specify parameters for either
 676    /// one.
 677    ///
 678    /// Default: []
 679    #[serde(default)]
 680    model_parameters: Vec<LanguageModelParameters>,
 681    /// What completion mode to enable for new threads
 682    ///
 683    /// Default: normal
 684    preferred_completion_mode: Option<CompletionMode>,
 685    /// Whether to show thumb buttons for feedback in the agent panel.
 686    ///
 687    /// Default: true
 688    enable_feedback: Option<bool>,
 689}
 690
 691#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
 692#[serde(rename_all = "snake_case")]
 693pub enum CompletionMode {
 694    #[default]
 695    Normal,
 696    #[serde(alias = "max")]
 697    Burn,
 698}
 699
 700impl From<CompletionMode> for zed_llm_client::CompletionMode {
 701    fn from(value: CompletionMode) -> Self {
 702        match value {
 703            CompletionMode::Normal => zed_llm_client::CompletionMode::Normal,
 704            CompletionMode::Burn => zed_llm_client::CompletionMode::Max,
 705        }
 706    }
 707}
 708
 709#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
 710pub struct LanguageModelSelection {
 711    pub provider: LanguageModelProviderSetting,
 712    pub model: String,
 713}
 714
 715#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 716pub struct LanguageModelProviderSetting(pub String);
 717
 718impl JsonSchema for LanguageModelProviderSetting {
 719    fn schema_name() -> String {
 720        "LanguageModelProviderSetting".into()
 721    }
 722
 723    fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> Schema {
 724        schemars::schema::SchemaObject {
 725            enum_values: Some(vec![
 726                "anthropic".into(),
 727                "amazon-bedrock".into(),
 728                "google".into(),
 729                "lmstudio".into(),
 730                "ollama".into(),
 731                "openai".into(),
 732                "zed.dev".into(),
 733                "copilot_chat".into(),
 734                "deepseek".into(),
 735                "openrouter".into(),
 736                "mistral".into(),
 737            ]),
 738            ..Default::default()
 739        }
 740        .into()
 741    }
 742}
 743
 744impl From<String> for LanguageModelProviderSetting {
 745    fn from(provider: String) -> Self {
 746        Self(provider)
 747    }
 748}
 749
 750impl From<&str> for LanguageModelProviderSetting {
 751    fn from(provider: &str) -> Self {
 752        Self(provider.to_string())
 753    }
 754}
 755
 756impl Default for LanguageModelSelection {
 757    fn default() -> Self {
 758        Self {
 759            provider: LanguageModelProviderSetting("openai".to_string()),
 760            model: "gpt-4".to_string(),
 761        }
 762    }
 763}
 764
 765#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]
 766pub struct AgentProfileContent {
 767    pub name: Arc<str>,
 768    #[serde(default)]
 769    pub tools: IndexMap<Arc<str>, bool>,
 770    /// Whether all context servers are enabled by default.
 771    pub enable_all_context_servers: Option<bool>,
 772    #[serde(default)]
 773    pub context_servers: IndexMap<Arc<str>, ContextServerPresetContent>,
 774}
 775
 776#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)]
 777pub struct ContextServerPresetContent {
 778    pub tools: IndexMap<Arc<str>, bool>,
 779}
 780
 781#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
 782#[schemars(deny_unknown_fields)]
 783pub struct AgentSettingsContentV1 {
 784    /// Whether the Agent is enabled.
 785    ///
 786    /// Default: true
 787    enabled: Option<bool>,
 788    /// Whether to show the Agent panel button in the status bar.
 789    ///
 790    /// Default: true
 791    button: Option<bool>,
 792    /// Where to dock the Agent.
 793    ///
 794    /// Default: right
 795    dock: Option<AgentDockPosition>,
 796    /// Default width in pixels when the Agent is docked to the left or right.
 797    ///
 798    /// Default: 640
 799    default_width: Option<f32>,
 800    /// Default height in pixels when the Agent is docked to the bottom.
 801    ///
 802    /// Default: 320
 803    default_height: Option<f32>,
 804    /// The provider of the Agent service.
 805    ///
 806    /// This can be "openai", "anthropic", "ollama", "lmstudio", "deepseek", "zed.dev"
 807    /// each with their respective default models and configurations.
 808    provider: Option<AgentProviderContentV1>,
 809}
 810
 811#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
 812#[schemars(deny_unknown_fields)]
 813pub struct LegacyAgentSettingsContent {
 814    /// Whether to show the Agent panel button in the status bar.
 815    ///
 816    /// Default: true
 817    pub button: Option<bool>,
 818    /// Where to dock the Agent.
 819    ///
 820    /// Default: right
 821    pub dock: Option<AgentDockPosition>,
 822    /// Default width in pixels when the Agent is docked to the left or right.
 823    ///
 824    /// Default: 640
 825    pub default_width: Option<f32>,
 826    /// Default height in pixels when the Agent is docked to the bottom.
 827    ///
 828    /// Default: 320
 829    pub default_height: Option<f32>,
 830    /// The default OpenAI model to use when creating new chats.
 831    ///
 832    /// Default: gpt-4-1106-preview
 833    pub default_open_ai_model: Option<OpenAiModel>,
 834    /// OpenAI API base URL to use when creating new chats.
 835    ///
 836    /// Default: <https://api.openai.com/v1>
 837    pub openai_api_url: Option<String>,
 838}
 839
 840impl Settings for AgentSettings {
 841    const KEY: Option<&'static str> = Some("agent");
 842
 843    const FALLBACK_KEY: Option<&'static str> = Some("assistant");
 844
 845    const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
 846
 847    type FileContent = AgentSettingsContent;
 848
 849    fn load(
 850        sources: SettingsSources<Self::FileContent>,
 851        _: &mut gpui::App,
 852    ) -> anyhow::Result<Self> {
 853        let mut settings = AgentSettings::default();
 854
 855        for value in sources.defaults_and_customizations() {
 856            if value.is_version_outdated() {
 857                settings.using_outdated_settings_version = true;
 858            }
 859
 860            let value = value.upgrade();
 861            merge(&mut settings.enabled, value.enabled);
 862            merge(&mut settings.button, value.button);
 863            merge(&mut settings.dock, value.dock);
 864            merge(
 865                &mut settings.default_width,
 866                value.default_width.map(Into::into),
 867            );
 868            merge(
 869                &mut settings.default_height,
 870                value.default_height.map(Into::into),
 871            );
 872            merge(&mut settings.default_model, value.default_model);
 873            settings.inline_assistant_model = value
 874                .inline_assistant_model
 875                .or(settings.inline_assistant_model.take());
 876            settings.commit_message_model = value
 877                .commit_message_model
 878                .or(settings.commit_message_model.take());
 879            settings.thread_summary_model = value
 880                .thread_summary_model
 881                .or(settings.thread_summary_model.take());
 882            merge(&mut settings.inline_alternatives, value.inline_alternatives);
 883            merge(
 884                &mut settings.always_allow_tool_actions,
 885                value.always_allow_tool_actions,
 886            );
 887            merge(
 888                &mut settings.notify_when_agent_waiting,
 889                value.notify_when_agent_waiting,
 890            );
 891            merge(
 892                &mut settings.play_sound_when_agent_done,
 893                value.play_sound_when_agent_done,
 894            );
 895            merge(&mut settings.stream_edits, value.stream_edits);
 896            merge(&mut settings.single_file_review, value.single_file_review);
 897            merge(&mut settings.default_profile, value.default_profile);
 898            merge(&mut settings.default_view, value.default_view);
 899            merge(
 900                &mut settings.preferred_completion_mode,
 901                value.preferred_completion_mode,
 902            );
 903            merge(&mut settings.enable_feedback, value.enable_feedback);
 904
 905            settings
 906                .model_parameters
 907                .extend_from_slice(&value.model_parameters);
 908
 909            if let Some(profiles) = value.profiles {
 910                settings
 911                    .profiles
 912                    .extend(profiles.into_iter().map(|(id, profile)| {
 913                        (
 914                            id,
 915                            AgentProfileSettings {
 916                                name: profile.name.into(),
 917                                tools: profile.tools,
 918                                enable_all_context_servers: profile
 919                                    .enable_all_context_servers
 920                                    .unwrap_or_default(),
 921                                context_servers: profile
 922                                    .context_servers
 923                                    .into_iter()
 924                                    .map(|(context_server_id, preset)| {
 925                                        (
 926                                            context_server_id,
 927                                            ContextServerPreset {
 928                                                tools: preset.tools.clone(),
 929                                            },
 930                                        )
 931                                    })
 932                                    .collect(),
 933                            },
 934                        )
 935                    }));
 936            }
 937        }
 938
 939        Ok(settings)
 940    }
 941
 942    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
 943        if let Some(b) = vscode
 944            .read_value("chat.agent.enabled")
 945            .and_then(|b| b.as_bool())
 946        {
 947            match &mut current.inner {
 948                Some(AgentSettingsContentInner::Versioned(versioned)) => match versioned.as_mut() {
 949                    VersionedAgentSettingsContent::V1(setting) => {
 950                        setting.enabled = Some(b);
 951                        setting.button = Some(b);
 952                    }
 953
 954                    VersionedAgentSettingsContent::V2(setting) => {
 955                        setting.enabled = Some(b);
 956                        setting.button = Some(b);
 957                    }
 958                },
 959                Some(AgentSettingsContentInner::Legacy(setting)) => setting.button = Some(b),
 960                None => {
 961                    current.inner =
 962                        Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
 963                            enabled: Some(b),
 964                            button: Some(b),
 965                            ..Default::default()
 966                        }));
 967                }
 968            }
 969        }
 970    }
 971}
 972
 973fn merge<T>(target: &mut T, value: Option<T>) {
 974    if let Some(value) = value {
 975        *target = value;
 976    }
 977}
 978
 979#[cfg(test)]
 980mod tests {
 981    use fs::Fs;
 982    use gpui::{ReadGlobal, TestAppContext};
 983    use settings::SettingsStore;
 984
 985    use super::*;
 986
 987    #[gpui::test]
 988    async fn test_deserialize_agent_settings_with_version(cx: &mut TestAppContext) {
 989        let fs = fs::FakeFs::new(cx.executor().clone());
 990        fs.create_dir(paths::settings_file().parent().unwrap())
 991            .await
 992            .unwrap();
 993
 994        cx.update(|cx| {
 995            let test_settings = settings::SettingsStore::test(cx);
 996            cx.set_global(test_settings);
 997            AgentSettings::register(cx);
 998        });
 999
1000        cx.update(|cx| {
1001            assert!(!AgentSettings::get_global(cx).using_outdated_settings_version);
1002            assert_eq!(
1003                AgentSettings::get_global(cx).default_model,
1004                LanguageModelSelection {
1005                    provider: "zed.dev".into(),
1006                    model: "claude-sonnet-4".into(),
1007                }
1008            );
1009        });
1010
1011        cx.update(|cx| {
1012            settings::SettingsStore::global(cx).update_settings_file::<AgentSettings>(
1013                fs.clone(),
1014                |settings, _| {
1015                    *settings = AgentSettingsContent {
1016                        inner: Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
1017                            default_model: Some(LanguageModelSelection {
1018                                provider: "test-provider".into(),
1019                                model: "gpt-99".into(),
1020                            }),
1021                            inline_assistant_model: None,
1022                            commit_message_model: None,
1023                            thread_summary_model: None,
1024                            inline_alternatives: None,
1025                            enabled: None,
1026                            button: None,
1027                            dock: None,
1028                            default_width: None,
1029                            default_height: None,
1030                            default_profile: None,
1031                            default_view: None,
1032                            profiles: None,
1033                            always_allow_tool_actions: None,
1034                            play_sound_when_agent_done: None,
1035                            notify_when_agent_waiting: None,
1036                            stream_edits: None,
1037                            single_file_review: None,
1038                            enable_feedback: None,
1039                            model_parameters: Vec::new(),
1040                            preferred_completion_mode: None,
1041                        })),
1042                    }
1043                },
1044            );
1045        });
1046
1047        cx.run_until_parked();
1048
1049        let raw_settings_value = fs.load(paths::settings_file()).await.unwrap();
1050        assert!(raw_settings_value.contains(r#""version": "2""#));
1051
1052        #[derive(Debug, Deserialize)]
1053        struct AgentSettingsTest {
1054            agent: AgentSettingsContent,
1055        }
1056
1057        let agent_settings: AgentSettingsTest =
1058            serde_json_lenient::from_str(&raw_settings_value).unwrap();
1059
1060        assert!(!agent_settings.agent.is_version_outdated());
1061    }
1062
1063    #[gpui::test]
1064    async fn test_load_settings_from_old_key(cx: &mut TestAppContext) {
1065        let fs = fs::FakeFs::new(cx.executor().clone());
1066        fs.create_dir(paths::settings_file().parent().unwrap())
1067            .await
1068            .unwrap();
1069
1070        cx.update(|cx| {
1071            let mut test_settings = settings::SettingsStore::test(cx);
1072            let user_settings_content = r#"{
1073            "assistant": {
1074                "enabled": true,
1075                "version": "2",
1076                "default_model": {
1077                  "provider": "zed.dev",
1078                  "model": "gpt-99"
1079                },
1080            }}"#;
1081            test_settings
1082                .set_user_settings(user_settings_content, cx)
1083                .unwrap();
1084            cx.set_global(test_settings);
1085            AgentSettings::register(cx);
1086        });
1087
1088        cx.run_until_parked();
1089
1090        let agent_settings = cx.update(|cx| AgentSettings::get_global(cx).clone());
1091        assert!(agent_settings.enabled);
1092        assert!(!agent_settings.using_outdated_settings_version);
1093        assert_eq!(agent_settings.default_model.model, "gpt-99");
1094
1095        cx.update_global::<SettingsStore, _>(|settings_store, cx| {
1096            settings_store.update_user_settings::<AgentSettings>(cx, |settings| {
1097                *settings = AgentSettingsContent {
1098                    inner: Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
1099                        enabled: Some(false),
1100                        default_model: Some(LanguageModelSelection {
1101                            provider: "xai".to_owned().into(),
1102                            model: "grok".to_owned(),
1103                        }),
1104                        ..Default::default()
1105                    })),
1106                };
1107            });
1108        });
1109
1110        cx.run_until_parked();
1111
1112        let settings = cx.update(|cx| SettingsStore::global(cx).raw_user_settings().clone());
1113
1114        #[derive(Debug, Deserialize)]
1115        struct AgentSettingsTest {
1116            assistant: AgentSettingsContent,
1117            agent: Option<serde_json_lenient::Value>,
1118        }
1119
1120        let agent_settings: AgentSettingsTest = serde_json::from_value(settings).unwrap();
1121        assert!(agent_settings.agent.is_none());
1122    }
1123}