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, AgentProfile>,
 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                            )),
 376                            api_url,
 377                        });
 378                    }
 379                    "lmstudio" => {
 380                        let api_url = match &settings.provider {
 381                            Some(AgentProviderContentV1::LmStudio { api_url, .. }) => {
 382                                api_url.clone()
 383                            }
 384                            _ => None,
 385                        };
 386                        settings.provider = Some(AgentProviderContentV1::LmStudio {
 387                            default_model: Some(lmstudio::Model::new(&model, None, None, false)),
 388                            api_url,
 389                        });
 390                    }
 391                    "openai" => {
 392                        let (api_url, available_models) = match &settings.provider {
 393                            Some(AgentProviderContentV1::OpenAi {
 394                                api_url,
 395                                available_models,
 396                                ..
 397                            }) => (api_url.clone(), available_models.clone()),
 398                            _ => (None, None),
 399                        };
 400                        settings.provider = Some(AgentProviderContentV1::OpenAi {
 401                            default_model: OpenAiModel::from_id(&model).ok(),
 402                            api_url,
 403                            available_models,
 404                        });
 405                    }
 406                    "deepseek" => {
 407                        let api_url = match &settings.provider {
 408                            Some(AgentProviderContentV1::DeepSeek { api_url, .. }) => {
 409                                api_url.clone()
 410                            }
 411                            _ => None,
 412                        };
 413                        settings.provider = Some(AgentProviderContentV1::DeepSeek {
 414                            default_model: DeepseekModel::from_id(&model).ok(),
 415                            api_url,
 416                        });
 417                    }
 418                    _ => {}
 419                },
 420                VersionedAgentSettingsContent::V2(ref mut settings) => {
 421                    settings.default_model = Some(LanguageModelSelection {
 422                        provider: provider.into(),
 423                        model,
 424                    });
 425                }
 426            },
 427            Some(AgentSettingsContentInner::Legacy(settings)) => {
 428                if let Ok(model) = OpenAiModel::from_id(&language_model.id().0) {
 429                    settings.default_open_ai_model = Some(model);
 430                }
 431            }
 432            None => {
 433                self.inner = Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
 434                    default_model: Some(LanguageModelSelection {
 435                        provider: provider.into(),
 436                        model,
 437                    }),
 438                    ..Default::default()
 439                }));
 440            }
 441        }
 442    }
 443
 444    pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
 445        self.v2_setting(|setting| {
 446            setting.inline_assistant_model = Some(LanguageModelSelection {
 447                provider: provider.into(),
 448                model,
 449            });
 450            Ok(())
 451        })
 452        .ok();
 453    }
 454
 455    pub fn set_commit_message_model(&mut self, provider: String, model: String) {
 456        self.v2_setting(|setting| {
 457            setting.commit_message_model = Some(LanguageModelSelection {
 458                provider: provider.into(),
 459                model,
 460            });
 461            Ok(())
 462        })
 463        .ok();
 464    }
 465
 466    pub fn v2_setting(
 467        &mut self,
 468        f: impl FnOnce(&mut AgentSettingsContentV2) -> anyhow::Result<()>,
 469    ) -> anyhow::Result<()> {
 470        match self.inner.get_or_insert_with(|| {
 471            AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
 472                ..Default::default()
 473            })
 474        }) {
 475            AgentSettingsContentInner::Versioned(boxed) => {
 476                if let VersionedAgentSettingsContent::V2(ref mut settings) = **boxed {
 477                    f(settings)
 478                } else {
 479                    Ok(())
 480                }
 481            }
 482            _ => Ok(()),
 483        }
 484    }
 485
 486    pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
 487        self.v2_setting(|setting| {
 488            setting.thread_summary_model = Some(LanguageModelSelection {
 489                provider: provider.into(),
 490                model,
 491            });
 492            Ok(())
 493        })
 494        .ok();
 495    }
 496
 497    pub fn set_always_allow_tool_actions(&mut self, allow: bool) {
 498        self.v2_setting(|setting| {
 499            setting.always_allow_tool_actions = Some(allow);
 500            Ok(())
 501        })
 502        .ok();
 503    }
 504
 505    pub fn set_play_sound_when_agent_done(&mut self, allow: bool) {
 506        self.v2_setting(|setting| {
 507            setting.play_sound_when_agent_done = Some(allow);
 508            Ok(())
 509        })
 510        .ok();
 511    }
 512
 513    pub fn set_single_file_review(&mut self, allow: bool) {
 514        self.v2_setting(|setting| {
 515            setting.single_file_review = Some(allow);
 516            Ok(())
 517        })
 518        .ok();
 519    }
 520
 521    pub fn set_profile(&mut self, profile_id: AgentProfileId) {
 522        self.v2_setting(|setting| {
 523            setting.default_profile = Some(profile_id);
 524            Ok(())
 525        })
 526        .ok();
 527    }
 528
 529    pub fn create_profile(
 530        &mut self,
 531        profile_id: AgentProfileId,
 532        profile: AgentProfile,
 533    ) -> Result<()> {
 534        self.v2_setting(|settings| {
 535            let profiles = settings.profiles.get_or_insert_default();
 536            if profiles.contains_key(&profile_id) {
 537                bail!("profile with ID '{profile_id}' already exists");
 538            }
 539
 540            profiles.insert(
 541                profile_id,
 542                AgentProfileContent {
 543                    name: profile.name.into(),
 544                    tools: profile.tools,
 545                    enable_all_context_servers: Some(profile.enable_all_context_servers),
 546                    context_servers: profile
 547                        .context_servers
 548                        .into_iter()
 549                        .map(|(server_id, preset)| {
 550                            (
 551                                server_id,
 552                                ContextServerPresetContent {
 553                                    tools: preset.tools,
 554                                },
 555                            )
 556                        })
 557                        .collect(),
 558                },
 559            );
 560
 561            Ok(())
 562        })
 563    }
 564}
 565
 566#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
 567#[serde(tag = "version")]
 568#[schemars(deny_unknown_fields)]
 569pub enum VersionedAgentSettingsContent {
 570    #[serde(rename = "1")]
 571    V1(AgentSettingsContentV1),
 572    #[serde(rename = "2")]
 573    V2(AgentSettingsContentV2),
 574}
 575
 576impl Default for VersionedAgentSettingsContent {
 577    fn default() -> Self {
 578        Self::V2(AgentSettingsContentV2 {
 579            enabled: None,
 580            button: None,
 581            dock: None,
 582            default_width: None,
 583            default_height: None,
 584            default_model: None,
 585            inline_assistant_model: None,
 586            commit_message_model: None,
 587            thread_summary_model: None,
 588            inline_alternatives: None,
 589            default_profile: None,
 590            default_view: None,
 591            profiles: None,
 592            always_allow_tool_actions: None,
 593            notify_when_agent_waiting: None,
 594            stream_edits: None,
 595            single_file_review: None,
 596            model_parameters: Vec::new(),
 597            preferred_completion_mode: None,
 598            enable_feedback: None,
 599            play_sound_when_agent_done: None,
 600        })
 601    }
 602}
 603
 604#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
 605#[schemars(deny_unknown_fields)]
 606pub struct AgentSettingsContentV2 {
 607    /// Whether the Agent is enabled.
 608    ///
 609    /// Default: true
 610    enabled: Option<bool>,
 611    /// Whether to show the agent panel button in the status bar.
 612    ///
 613    /// Default: true
 614    button: Option<bool>,
 615    /// Where to dock the agent panel.
 616    ///
 617    /// Default: right
 618    dock: Option<AgentDockPosition>,
 619    /// Default width in pixels when the agent panel is docked to the left or right.
 620    ///
 621    /// Default: 640
 622    default_width: Option<f32>,
 623    /// Default height in pixels when the agent panel is docked to the bottom.
 624    ///
 625    /// Default: 320
 626    default_height: Option<f32>,
 627    /// The default model to use when creating new chats and for other features when a specific model is not specified.
 628    default_model: Option<LanguageModelSelection>,
 629    /// Model to use for the inline assistant. Defaults to default_model when not specified.
 630    inline_assistant_model: Option<LanguageModelSelection>,
 631    /// Model to use for generating git commit messages. Defaults to default_model when not specified.
 632    commit_message_model: Option<LanguageModelSelection>,
 633    /// Model to use for generating thread summaries. Defaults to default_model when not specified.
 634    thread_summary_model: Option<LanguageModelSelection>,
 635    /// Additional models with which to generate alternatives when performing inline assists.
 636    inline_alternatives: Option<Vec<LanguageModelSelection>>,
 637    /// The default profile to use in the Agent.
 638    ///
 639    /// Default: write
 640    default_profile: Option<AgentProfileId>,
 641    /// Which view type to show by default in the agent panel.
 642    ///
 643    /// Default: "thread"
 644    default_view: Option<DefaultView>,
 645    /// The available agent profiles.
 646    pub profiles: Option<IndexMap<AgentProfileId, AgentProfileContent>>,
 647    /// Whenever a tool action would normally wait for your confirmation
 648    /// that you allow it, always choose to allow it.
 649    ///
 650    /// Default: false
 651    always_allow_tool_actions: Option<bool>,
 652    /// Where to show a popup notification when the agent is waiting for user input.
 653    ///
 654    /// Default: "primary_screen"
 655    notify_when_agent_waiting: Option<NotifyWhenAgentWaiting>,
 656    /// Whether to play a sound when the agent has either completed its response, or needs user input.
 657    ///
 658    /// Default: false
 659    play_sound_when_agent_done: Option<bool>,
 660    /// Whether to stream edits from the agent as they are received.
 661    ///
 662    /// Default: false
 663    stream_edits: Option<bool>,
 664    /// Whether to display agent edits in single-file editors in addition to the review multibuffer pane.
 665    ///
 666    /// Default: true
 667    single_file_review: Option<bool>,
 668    /// Additional parameters for language model requests. When making a request
 669    /// to a model, parameters will be taken from the last entry in this list
 670    /// that matches the model's provider and name. In each entry, both provider
 671    /// and model are optional, so that you can specify parameters for either
 672    /// one.
 673    ///
 674    /// Default: []
 675    #[serde(default)]
 676    model_parameters: Vec<LanguageModelParameters>,
 677    /// What completion mode to enable for new threads
 678    ///
 679    /// Default: normal
 680    preferred_completion_mode: Option<CompletionMode>,
 681    /// Whether to show thumb buttons for feedback in the agent panel.
 682    ///
 683    /// Default: true
 684    enable_feedback: Option<bool>,
 685}
 686
 687#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
 688#[serde(rename_all = "snake_case")]
 689pub enum CompletionMode {
 690    #[default]
 691    Normal,
 692    #[serde(alias = "max")]
 693    Burn,
 694}
 695
 696impl From<CompletionMode> for zed_llm_client::CompletionMode {
 697    fn from(value: CompletionMode) -> Self {
 698        match value {
 699            CompletionMode::Normal => zed_llm_client::CompletionMode::Normal,
 700            CompletionMode::Burn => zed_llm_client::CompletionMode::Max,
 701        }
 702    }
 703}
 704
 705#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
 706pub struct LanguageModelSelection {
 707    pub provider: LanguageModelProviderSetting,
 708    pub model: String,
 709}
 710
 711#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 712pub struct LanguageModelProviderSetting(pub String);
 713
 714impl JsonSchema for LanguageModelProviderSetting {
 715    fn schema_name() -> String {
 716        "LanguageModelProviderSetting".into()
 717    }
 718
 719    fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> Schema {
 720        schemars::schema::SchemaObject {
 721            enum_values: Some(vec![
 722                "anthropic".into(),
 723                "amazon-bedrock".into(),
 724                "google".into(),
 725                "lmstudio".into(),
 726                "ollama".into(),
 727                "openai".into(),
 728                "zed.dev".into(),
 729                "copilot_chat".into(),
 730                "deepseek".into(),
 731                "mistral".into(),
 732            ]),
 733            ..Default::default()
 734        }
 735        .into()
 736    }
 737}
 738
 739impl From<String> for LanguageModelProviderSetting {
 740    fn from(provider: String) -> Self {
 741        Self(provider)
 742    }
 743}
 744
 745impl From<&str> for LanguageModelProviderSetting {
 746    fn from(provider: &str) -> Self {
 747        Self(provider.to_string())
 748    }
 749}
 750
 751impl Default for LanguageModelSelection {
 752    fn default() -> Self {
 753        Self {
 754            provider: LanguageModelProviderSetting("openai".to_string()),
 755            model: "gpt-4".to_string(),
 756        }
 757    }
 758}
 759
 760#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]
 761pub struct AgentProfileContent {
 762    pub name: Arc<str>,
 763    #[serde(default)]
 764    pub tools: IndexMap<Arc<str>, bool>,
 765    /// Whether all context servers are enabled by default.
 766    pub enable_all_context_servers: Option<bool>,
 767    #[serde(default)]
 768    pub context_servers: IndexMap<Arc<str>, ContextServerPresetContent>,
 769}
 770
 771#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)]
 772pub struct ContextServerPresetContent {
 773    pub tools: IndexMap<Arc<str>, bool>,
 774}
 775
 776#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
 777#[schemars(deny_unknown_fields)]
 778pub struct AgentSettingsContentV1 {
 779    /// Whether the Agent is enabled.
 780    ///
 781    /// Default: true
 782    enabled: Option<bool>,
 783    /// Whether to show the Agent panel button in the status bar.
 784    ///
 785    /// Default: true
 786    button: Option<bool>,
 787    /// Where to dock the Agent.
 788    ///
 789    /// Default: right
 790    dock: Option<AgentDockPosition>,
 791    /// Default width in pixels when the Agent is docked to the left or right.
 792    ///
 793    /// Default: 640
 794    default_width: Option<f32>,
 795    /// Default height in pixels when the Agent is docked to the bottom.
 796    ///
 797    /// Default: 320
 798    default_height: Option<f32>,
 799    /// The provider of the Agent service.
 800    ///
 801    /// This can be "openai", "anthropic", "ollama", "lmstudio", "deepseek", "zed.dev"
 802    /// each with their respective default models and configurations.
 803    provider: Option<AgentProviderContentV1>,
 804}
 805
 806#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
 807#[schemars(deny_unknown_fields)]
 808pub struct LegacyAgentSettingsContent {
 809    /// Whether to show the Agent panel button in the status bar.
 810    ///
 811    /// Default: true
 812    pub button: Option<bool>,
 813    /// Where to dock the Agent.
 814    ///
 815    /// Default: right
 816    pub dock: Option<AgentDockPosition>,
 817    /// Default width in pixels when the Agent is docked to the left or right.
 818    ///
 819    /// Default: 640
 820    pub default_width: Option<f32>,
 821    /// Default height in pixels when the Agent is docked to the bottom.
 822    ///
 823    /// Default: 320
 824    pub default_height: Option<f32>,
 825    /// The default OpenAI model to use when creating new chats.
 826    ///
 827    /// Default: gpt-4-1106-preview
 828    pub default_open_ai_model: Option<OpenAiModel>,
 829    /// OpenAI API base URL to use when creating new chats.
 830    ///
 831    /// Default: <https://api.openai.com/v1>
 832    pub openai_api_url: Option<String>,
 833}
 834
 835impl Settings for AgentSettings {
 836    const KEY: Option<&'static str> = Some("agent");
 837
 838    const FALLBACK_KEY: Option<&'static str> = Some("assistant");
 839
 840    const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
 841
 842    type FileContent = AgentSettingsContent;
 843
 844    fn load(
 845        sources: SettingsSources<Self::FileContent>,
 846        _: &mut gpui::App,
 847    ) -> anyhow::Result<Self> {
 848        let mut settings = AgentSettings::default();
 849
 850        for value in sources.defaults_and_customizations() {
 851            if value.is_version_outdated() {
 852                settings.using_outdated_settings_version = true;
 853            }
 854
 855            let value = value.upgrade();
 856            merge(&mut settings.enabled, value.enabled);
 857            merge(&mut settings.button, value.button);
 858            merge(&mut settings.dock, value.dock);
 859            merge(
 860                &mut settings.default_width,
 861                value.default_width.map(Into::into),
 862            );
 863            merge(
 864                &mut settings.default_height,
 865                value.default_height.map(Into::into),
 866            );
 867            merge(&mut settings.default_model, value.default_model);
 868            settings.inline_assistant_model = value
 869                .inline_assistant_model
 870                .or(settings.inline_assistant_model.take());
 871            settings.commit_message_model = value
 872                .commit_message_model
 873                .or(settings.commit_message_model.take());
 874            settings.thread_summary_model = value
 875                .thread_summary_model
 876                .or(settings.thread_summary_model.take());
 877            merge(&mut settings.inline_alternatives, value.inline_alternatives);
 878            merge(
 879                &mut settings.always_allow_tool_actions,
 880                value.always_allow_tool_actions,
 881            );
 882            merge(
 883                &mut settings.notify_when_agent_waiting,
 884                value.notify_when_agent_waiting,
 885            );
 886            merge(
 887                &mut settings.play_sound_when_agent_done,
 888                value.play_sound_when_agent_done,
 889            );
 890            merge(&mut settings.stream_edits, value.stream_edits);
 891            merge(&mut settings.single_file_review, value.single_file_review);
 892            merge(&mut settings.default_profile, value.default_profile);
 893            merge(&mut settings.default_view, value.default_view);
 894            merge(
 895                &mut settings.preferred_completion_mode,
 896                value.preferred_completion_mode,
 897            );
 898            merge(&mut settings.enable_feedback, value.enable_feedback);
 899
 900            settings
 901                .model_parameters
 902                .extend_from_slice(&value.model_parameters);
 903
 904            if let Some(profiles) = value.profiles {
 905                settings
 906                    .profiles
 907                    .extend(profiles.into_iter().map(|(id, profile)| {
 908                        (
 909                            id,
 910                            AgentProfile {
 911                                name: profile.name.into(),
 912                                tools: profile.tools,
 913                                enable_all_context_servers: profile
 914                                    .enable_all_context_servers
 915                                    .unwrap_or_default(),
 916                                context_servers: profile
 917                                    .context_servers
 918                                    .into_iter()
 919                                    .map(|(context_server_id, preset)| {
 920                                        (
 921                                            context_server_id,
 922                                            ContextServerPreset {
 923                                                tools: preset.tools.clone(),
 924                                            },
 925                                        )
 926                                    })
 927                                    .collect(),
 928                            },
 929                        )
 930                    }));
 931            }
 932        }
 933
 934        Ok(settings)
 935    }
 936
 937    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
 938        if let Some(b) = vscode
 939            .read_value("chat.agent.enabled")
 940            .and_then(|b| b.as_bool())
 941        {
 942            match &mut current.inner {
 943                Some(AgentSettingsContentInner::Versioned(versioned)) => match versioned.as_mut() {
 944                    VersionedAgentSettingsContent::V1(setting) => {
 945                        setting.enabled = Some(b);
 946                        setting.button = Some(b);
 947                    }
 948
 949                    VersionedAgentSettingsContent::V2(setting) => {
 950                        setting.enabled = Some(b);
 951                        setting.button = Some(b);
 952                    }
 953                },
 954                Some(AgentSettingsContentInner::Legacy(setting)) => setting.button = Some(b),
 955                None => {
 956                    current.inner =
 957                        Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
 958                            enabled: Some(b),
 959                            button: Some(b),
 960                            ..Default::default()
 961                        }));
 962                }
 963            }
 964        }
 965    }
 966}
 967
 968fn merge<T>(target: &mut T, value: Option<T>) {
 969    if let Some(value) = value {
 970        *target = value;
 971    }
 972}
 973
 974#[cfg(test)]
 975mod tests {
 976    use fs::Fs;
 977    use gpui::{ReadGlobal, TestAppContext};
 978    use settings::SettingsStore;
 979
 980    use super::*;
 981
 982    #[gpui::test]
 983    async fn test_deserialize_agent_settings_with_version(cx: &mut TestAppContext) {
 984        let fs = fs::FakeFs::new(cx.executor().clone());
 985        fs.create_dir(paths::settings_file().parent().unwrap())
 986            .await
 987            .unwrap();
 988
 989        cx.update(|cx| {
 990            let test_settings = settings::SettingsStore::test(cx);
 991            cx.set_global(test_settings);
 992            AgentSettings::register(cx);
 993        });
 994
 995        cx.update(|cx| {
 996            assert!(!AgentSettings::get_global(cx).using_outdated_settings_version);
 997            assert_eq!(
 998                AgentSettings::get_global(cx).default_model,
 999                LanguageModelSelection {
1000                    provider: "zed.dev".into(),
1001                    model: "claude-sonnet-4".into(),
1002                }
1003            );
1004        });
1005
1006        cx.update(|cx| {
1007            settings::SettingsStore::global(cx).update_settings_file::<AgentSettings>(
1008                fs.clone(),
1009                |settings, _| {
1010                    *settings = AgentSettingsContent {
1011                        inner: Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
1012                            default_model: Some(LanguageModelSelection {
1013                                provider: "test-provider".into(),
1014                                model: "gpt-99".into(),
1015                            }),
1016                            inline_assistant_model: None,
1017                            commit_message_model: None,
1018                            thread_summary_model: None,
1019                            inline_alternatives: None,
1020                            enabled: None,
1021                            button: None,
1022                            dock: None,
1023                            default_width: None,
1024                            default_height: None,
1025                            default_profile: None,
1026                            default_view: None,
1027                            profiles: None,
1028                            always_allow_tool_actions: None,
1029                            play_sound_when_agent_done: None,
1030                            notify_when_agent_waiting: None,
1031                            stream_edits: None,
1032                            single_file_review: None,
1033                            enable_feedback: None,
1034                            model_parameters: Vec::new(),
1035                            preferred_completion_mode: None,
1036                        })),
1037                    }
1038                },
1039            );
1040        });
1041
1042        cx.run_until_parked();
1043
1044        let raw_settings_value = fs.load(paths::settings_file()).await.unwrap();
1045        assert!(raw_settings_value.contains(r#""version": "2""#));
1046
1047        #[derive(Debug, Deserialize)]
1048        struct AgentSettingsTest {
1049            agent: AgentSettingsContent,
1050        }
1051
1052        let agent_settings: AgentSettingsTest =
1053            serde_json_lenient::from_str(&raw_settings_value).unwrap();
1054
1055        assert!(!agent_settings.agent.is_version_outdated());
1056    }
1057
1058    #[gpui::test]
1059    async fn test_load_settings_from_old_key(cx: &mut TestAppContext) {
1060        let fs = fs::FakeFs::new(cx.executor().clone());
1061        fs.create_dir(paths::settings_file().parent().unwrap())
1062            .await
1063            .unwrap();
1064
1065        cx.update(|cx| {
1066            let mut test_settings = settings::SettingsStore::test(cx);
1067            let user_settings_content = r#"{
1068            "assistant": {
1069                "enabled": true,
1070                "version": "2",
1071                "default_model": {
1072                  "provider": "zed.dev",
1073                  "model": "gpt-99"
1074                },
1075            }}"#;
1076            test_settings
1077                .set_user_settings(user_settings_content, cx)
1078                .unwrap();
1079            cx.set_global(test_settings);
1080            AgentSettings::register(cx);
1081        });
1082
1083        cx.run_until_parked();
1084
1085        let agent_settings = cx.update(|cx| AgentSettings::get_global(cx).clone());
1086        assert!(agent_settings.enabled);
1087        assert!(!agent_settings.using_outdated_settings_version);
1088        assert_eq!(agent_settings.default_model.model, "gpt-99");
1089
1090        cx.update_global::<SettingsStore, _>(|settings_store, cx| {
1091            settings_store.update_user_settings::<AgentSettings>(cx, |settings| {
1092                *settings = AgentSettingsContent {
1093                    inner: Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
1094                        enabled: Some(false),
1095                        default_model: Some(LanguageModelSelection {
1096                            provider: "xai".to_owned().into(),
1097                            model: "grok".to_owned(),
1098                        }),
1099                        ..Default::default()
1100                    })),
1101                };
1102            });
1103        });
1104
1105        cx.run_until_parked();
1106
1107        let settings = cx.update(|cx| SettingsStore::global(cx).raw_user_settings().clone());
1108
1109        #[derive(Debug, Deserialize)]
1110        struct AgentSettingsTest {
1111            assistant: AgentSettingsContent,
1112            agent: Option<serde_json_lenient::Value>,
1113        }
1114
1115        let agent_settings: AgentSettingsTest = serde_json::from_value(settings).unwrap();
1116        assert!(agent_settings.agent.is_none());
1117    }
1118}