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