agent_settings.rs

  1mod agent_profile;
  2
  3use std::sync::Arc;
  4
  5use anyhow::{Result, bail};
  6use collections::IndexMap;
  7use gpui::{App, Pixels, SharedString};
  8use language_model::LanguageModel;
  9use schemars::{JsonSchema, json_schema};
 10use serde::{Deserialize, Serialize};
 11use settings::{Settings, SettingsSources};
 12use std::borrow::Cow;
 13
 14pub use crate::agent_profile::*;
 15
 16pub fn init(cx: &mut App) {
 17    AgentSettings::register(cx);
 18}
 19
 20#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
 21#[serde(rename_all = "snake_case")]
 22pub enum AgentDockPosition {
 23    Left,
 24    #[default]
 25    Right,
 26    Bottom,
 27}
 28
 29#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
 30#[serde(rename_all = "snake_case")]
 31pub enum DefaultView {
 32    #[default]
 33    Thread,
 34    TextThread,
 35}
 36
 37#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
 38#[serde(rename_all = "snake_case")]
 39pub enum NotifyWhenAgentWaiting {
 40    #[default]
 41    PrimaryScreen,
 42    AllScreens,
 43    Never,
 44}
 45
 46#[derive(Default, Clone, Debug)]
 47pub struct AgentSettings {
 48    pub enabled: bool,
 49    pub button: bool,
 50    pub dock: AgentDockPosition,
 51    pub default_width: Pixels,
 52    pub default_height: Pixels,
 53    pub default_model: Option<LanguageModelSelection>,
 54    pub inline_assistant_model: Option<LanguageModelSelection>,
 55    pub commit_message_model: Option<LanguageModelSelection>,
 56    pub thread_summary_model: Option<LanguageModelSelection>,
 57    pub inline_alternatives: Vec<LanguageModelSelection>,
 58    pub using_outdated_settings_version: bool,
 59    pub default_profile: AgentProfileId,
 60    pub default_view: DefaultView,
 61    pub profiles: IndexMap<AgentProfileId, AgentProfileSettings>,
 62    pub always_allow_tool_actions: bool,
 63    pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
 64    pub play_sound_when_agent_done: bool,
 65    pub stream_edits: bool,
 66    pub single_file_review: bool,
 67    pub model_parameters: Vec<LanguageModelParameters>,
 68    pub preferred_completion_mode: CompletionMode,
 69    pub enable_feedback: bool,
 70    pub expand_edit_card: bool,
 71}
 72
 73impl AgentSettings {
 74    pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
 75        let settings = Self::get_global(cx);
 76        settings
 77            .model_parameters
 78            .iter()
 79            .rfind(|setting| setting.matches(model))
 80            .and_then(|m| m.temperature)
 81    }
 82
 83    pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
 84        self.inline_assistant_model = Some(LanguageModelSelection {
 85            provider: provider.into(),
 86            model,
 87        });
 88    }
 89
 90    pub fn set_commit_message_model(&mut self, provider: String, model: String) {
 91        self.commit_message_model = Some(LanguageModelSelection {
 92            provider: provider.into(),
 93            model,
 94        });
 95    }
 96
 97    pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
 98        self.thread_summary_model = Some(LanguageModelSelection {
 99            provider: provider.into(),
100            model,
101        });
102    }
103}
104
105#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
106pub struct LanguageModelParameters {
107    pub provider: Option<LanguageModelProviderSetting>,
108    pub model: Option<SharedString>,
109    pub temperature: Option<f32>,
110}
111
112impl LanguageModelParameters {
113    pub fn matches(&self, model: &Arc<dyn LanguageModel>) -> bool {
114        if let Some(provider) = &self.provider {
115            if provider.0 != model.provider_id().0 {
116                return false;
117            }
118        }
119        if let Some(setting_model) = &self.model {
120            if *setting_model != model.id().0 {
121                return false;
122            }
123        }
124        true
125    }
126}
127
128impl AgentSettingsContent {
129    pub fn set_dock(&mut self, dock: AgentDockPosition) {
130        self.dock = Some(dock);
131    }
132
133    pub fn set_model(&mut self, language_model: Arc<dyn LanguageModel>) {
134        let model = language_model.id().0.to_string();
135        let provider = language_model.provider_id().0.to_string();
136
137        self.default_model = Some(LanguageModelSelection {
138            provider: provider.into(),
139            model,
140        });
141    }
142
143    pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
144        self.inline_assistant_model = Some(LanguageModelSelection {
145            provider: provider.into(),
146            model,
147        });
148    }
149
150    pub fn set_commit_message_model(&mut self, provider: String, model: String) {
151        self.commit_message_model = Some(LanguageModelSelection {
152            provider: provider.into(),
153            model,
154        });
155    }
156
157    pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
158        self.thread_summary_model = Some(LanguageModelSelection {
159            provider: provider.into(),
160            model,
161        });
162    }
163
164    pub fn set_always_allow_tool_actions(&mut self, allow: bool) {
165        self.always_allow_tool_actions = Some(allow);
166    }
167
168    pub fn set_play_sound_when_agent_done(&mut self, allow: bool) {
169        self.play_sound_when_agent_done = Some(allow);
170    }
171
172    pub fn set_single_file_review(&mut self, allow: bool) {
173        self.single_file_review = Some(allow);
174    }
175
176    pub fn set_profile(&mut self, profile_id: AgentProfileId) {
177        self.default_profile = Some(profile_id);
178    }
179
180    pub fn create_profile(
181        &mut self,
182        profile_id: AgentProfileId,
183        profile_settings: AgentProfileSettings,
184    ) -> Result<()> {
185        let profiles = self.profiles.get_or_insert_default();
186        if profiles.contains_key(&profile_id) {
187            bail!("profile with ID '{profile_id}' already exists");
188        }
189
190        profiles.insert(
191            profile_id,
192            AgentProfileContent {
193                name: profile_settings.name.into(),
194                tools: profile_settings.tools,
195                enable_all_context_servers: Some(profile_settings.enable_all_context_servers),
196                context_servers: profile_settings
197                    .context_servers
198                    .into_iter()
199                    .map(|(server_id, preset)| {
200                        (
201                            server_id,
202                            ContextServerPresetContent {
203                                tools: preset.tools,
204                            },
205                        )
206                    })
207                    .collect(),
208            },
209        );
210
211        Ok(())
212    }
213}
214
215#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
216pub struct AgentSettingsContent {
217    /// Whether the Agent is enabled.
218    ///
219    /// Default: true
220    enabled: Option<bool>,
221    /// Whether to show the agent panel button in the status bar.
222    ///
223    /// Default: true
224    button: Option<bool>,
225    /// Where to dock the agent panel.
226    ///
227    /// Default: right
228    dock: Option<AgentDockPosition>,
229    /// Default width in pixels when the agent panel is docked to the left or right.
230    ///
231    /// Default: 640
232    default_width: Option<f32>,
233    /// Default height in pixels when the agent panel is docked to the bottom.
234    ///
235    /// Default: 320
236    default_height: Option<f32>,
237    /// The default model to use when creating new chats and for other features when a specific model is not specified.
238    default_model: Option<LanguageModelSelection>,
239    /// Model to use for the inline assistant. Defaults to default_model when not specified.
240    inline_assistant_model: Option<LanguageModelSelection>,
241    /// Model to use for generating git commit messages. Defaults to default_model when not specified.
242    commit_message_model: Option<LanguageModelSelection>,
243    /// Model to use for generating thread summaries. Defaults to default_model when not specified.
244    thread_summary_model: Option<LanguageModelSelection>,
245    /// Additional models with which to generate alternatives when performing inline assists.
246    inline_alternatives: Option<Vec<LanguageModelSelection>>,
247    /// The default profile to use in the Agent.
248    ///
249    /// Default: write
250    default_profile: Option<AgentProfileId>,
251    /// Which view type to show by default in the agent panel.
252    ///
253    /// Default: "thread"
254    default_view: Option<DefaultView>,
255    /// The available agent profiles.
256    pub profiles: Option<IndexMap<AgentProfileId, AgentProfileContent>>,
257    /// Whenever a tool action would normally wait for your confirmation
258    /// that you allow it, always choose to allow it.
259    ///
260    /// Default: false
261    always_allow_tool_actions: Option<bool>,
262    /// Where to show a popup notification when the agent is waiting for user input.
263    ///
264    /// Default: "primary_screen"
265    notify_when_agent_waiting: Option<NotifyWhenAgentWaiting>,
266    /// Whether to play a sound when the agent has either completed its response, or needs user input.
267    ///
268    /// Default: false
269    play_sound_when_agent_done: Option<bool>,
270    /// Whether to stream edits from the agent as they are received.
271    ///
272    /// Default: false
273    stream_edits: Option<bool>,
274    /// Whether to display agent edits in single-file editors in addition to the review multibuffer pane.
275    ///
276    /// Default: true
277    single_file_review: Option<bool>,
278    /// Additional parameters for language model requests. When making a request
279    /// to a model, parameters will be taken from the last entry in this list
280    /// that matches the model's provider and name. In each entry, both provider
281    /// and model are optional, so that you can specify parameters for either
282    /// one.
283    ///
284    /// Default: []
285    #[serde(default)]
286    model_parameters: Vec<LanguageModelParameters>,
287    /// What completion mode to enable for new threads
288    ///
289    /// Default: normal
290    preferred_completion_mode: Option<CompletionMode>,
291    /// Whether to show thumb buttons for feedback in the agent panel.
292    ///
293    /// Default: true
294    enable_feedback: Option<bool>,
295    /// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff.
296    ///
297    /// Default: true
298    expand_edit_card: Option<bool>,
299}
300
301#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
302#[serde(rename_all = "snake_case")]
303pub enum CompletionMode {
304    #[default]
305    Normal,
306    #[serde(alias = "max")]
307    Burn,
308}
309
310impl From<CompletionMode> for zed_llm_client::CompletionMode {
311    fn from(value: CompletionMode) -> Self {
312        match value {
313            CompletionMode::Normal => zed_llm_client::CompletionMode::Normal,
314            CompletionMode::Burn => zed_llm_client::CompletionMode::Max,
315        }
316    }
317}
318
319#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
320pub struct LanguageModelSelection {
321    pub provider: LanguageModelProviderSetting,
322    pub model: String,
323}
324
325#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
326pub struct LanguageModelProviderSetting(pub String);
327
328impl JsonSchema for LanguageModelProviderSetting {
329    fn schema_name() -> Cow<'static, str> {
330        "LanguageModelProviderSetting".into()
331    }
332
333    fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
334        json_schema!({
335            "enum": [
336                "anthropic",
337                "amazon-bedrock",
338                "google",
339                "lmstudio",
340                "ollama",
341                "openai",
342                "zed.dev",
343                "copilot_chat",
344                "deepseek",
345                "openrouter",
346                "mistral",
347                "vercel"
348            ]
349        })
350    }
351}
352
353impl From<String> for LanguageModelProviderSetting {
354    fn from(provider: String) -> Self {
355        Self(provider)
356    }
357}
358
359impl From<&str> for LanguageModelProviderSetting {
360    fn from(provider: &str) -> Self {
361        Self(provider.to_string())
362    }
363}
364
365#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]
366pub struct AgentProfileContent {
367    pub name: Arc<str>,
368    #[serde(default)]
369    pub tools: IndexMap<Arc<str>, bool>,
370    /// Whether all context servers are enabled by default.
371    pub enable_all_context_servers: Option<bool>,
372    #[serde(default)]
373    pub context_servers: IndexMap<Arc<str>, ContextServerPresetContent>,
374}
375
376#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)]
377pub struct ContextServerPresetContent {
378    pub tools: IndexMap<Arc<str>, bool>,
379}
380
381impl Settings for AgentSettings {
382    const KEY: Option<&'static str> = Some("agent");
383
384    const FALLBACK_KEY: Option<&'static str> = Some("assistant");
385
386    const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
387
388    type FileContent = AgentSettingsContent;
389
390    fn load(
391        sources: SettingsSources<Self::FileContent>,
392        _: &mut gpui::App,
393    ) -> anyhow::Result<Self> {
394        let mut settings = AgentSettings::default();
395
396        for value in sources.defaults_and_customizations() {
397            merge(&mut settings.enabled, value.enabled);
398            merge(&mut settings.button, value.button);
399            merge(&mut settings.dock, value.dock);
400            merge(
401                &mut settings.default_width,
402                value.default_width.map(Into::into),
403            );
404            merge(
405                &mut settings.default_height,
406                value.default_height.map(Into::into),
407            );
408            settings.default_model = value
409                .default_model
410                .clone()
411                .or(settings.default_model.take());
412            settings.inline_assistant_model = value
413                .inline_assistant_model
414                .clone()
415                .or(settings.inline_assistant_model.take());
416            settings.commit_message_model = value
417                .clone()
418                .commit_message_model
419                .or(settings.commit_message_model.take());
420            settings.thread_summary_model = value
421                .clone()
422                .thread_summary_model
423                .or(settings.thread_summary_model.take());
424            merge(
425                &mut settings.inline_alternatives,
426                value.inline_alternatives.clone(),
427            );
428            merge(
429                &mut settings.always_allow_tool_actions,
430                value.always_allow_tool_actions,
431            );
432            merge(
433                &mut settings.notify_when_agent_waiting,
434                value.notify_when_agent_waiting,
435            );
436            merge(
437                &mut settings.play_sound_when_agent_done,
438                value.play_sound_when_agent_done,
439            );
440            merge(&mut settings.stream_edits, value.stream_edits);
441            merge(&mut settings.single_file_review, value.single_file_review);
442            merge(&mut settings.default_profile, value.default_profile.clone());
443            merge(&mut settings.default_view, value.default_view);
444            merge(
445                &mut settings.preferred_completion_mode,
446                value.preferred_completion_mode,
447            );
448            merge(&mut settings.enable_feedback, value.enable_feedback);
449            merge(&mut settings.expand_edit_card, value.expand_edit_card);
450
451            settings
452                .model_parameters
453                .extend_from_slice(&value.model_parameters);
454
455            if let Some(profiles) = value.profiles.as_ref() {
456                settings
457                    .profiles
458                    .extend(profiles.into_iter().map(|(id, profile)| {
459                        (
460                            id.clone(),
461                            AgentProfileSettings {
462                                name: profile.name.clone().into(),
463                                tools: profile.tools.clone(),
464                                enable_all_context_servers: profile
465                                    .enable_all_context_servers
466                                    .unwrap_or_default(),
467                                context_servers: profile
468                                    .context_servers
469                                    .iter()
470                                    .map(|(context_server_id, preset)| {
471                                        (
472                                            context_server_id.clone(),
473                                            ContextServerPreset {
474                                                tools: preset.tools.clone(),
475                                            },
476                                        )
477                                    })
478                                    .collect(),
479                            },
480                        )
481                    }));
482            }
483        }
484
485        Ok(settings)
486    }
487
488    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
489        if let Some(b) = vscode
490            .read_value("chat.agent.enabled")
491            .and_then(|b| b.as_bool())
492        {
493            current.enabled = Some(b);
494            current.button = Some(b);
495        }
496    }
497}
498
499fn merge<T>(target: &mut T, value: Option<T>) {
500    if let Some(value) = value {
501        *target = value;
502    }
503}