agent_profile.rs

  1use std::sync::Arc;
  2
  3use anyhow::{Result, bail};
  4use collections::IndexMap;
  5use convert_case::{Case, Casing as _};
  6use fs::Fs;
  7use gpui::{App, SharedString};
  8use settings::{
  9    AgentProfileContent, ContextServerPresetContent, LanguageModelSelection, Settings as _,
 10    SettingsContent, update_settings_file,
 11};
 12use util::ResultExt as _;
 13
 14use crate::{AgentProfileId, AgentSettings};
 15
 16pub mod builtin_profiles {
 17    use super::AgentProfileId;
 18
 19    pub const WRITE: &str = "write";
 20    pub const ASK: &str = "ask";
 21    pub const MINIMAL: &str = "minimal";
 22
 23    pub fn is_builtin(profile_id: &AgentProfileId) -> bool {
 24        profile_id.as_str() == WRITE || profile_id.as_str() == ASK || profile_id.as_str() == MINIMAL
 25    }
 26}
 27
 28#[derive(Clone, Debug, Eq, PartialEq)]
 29pub struct AgentProfile {
 30    id: AgentProfileId,
 31}
 32
 33pub type AvailableProfiles = IndexMap<AgentProfileId, SharedString>;
 34
 35impl AgentProfile {
 36    pub fn new(id: AgentProfileId) -> Self {
 37        Self { id }
 38    }
 39
 40    pub fn id(&self) -> &AgentProfileId {
 41        &self.id
 42    }
 43
 44    /// Saves a new profile to the settings.
 45    pub fn create(
 46        name: String,
 47        base_profile_id: Option<AgentProfileId>,
 48        fs: Arc<dyn Fs>,
 49        cx: &App,
 50    ) -> AgentProfileId {
 51        let id = AgentProfileId(name.to_case(Case::Kebab).into());
 52
 53        let base_profile =
 54            base_profile_id.and_then(|id| AgentSettings::get_global(cx).profiles.get(&id).cloned());
 55
 56        // Copy toggles from the base profile so the new profile starts with familiar defaults.
 57        let tools = base_profile
 58            .as_ref()
 59            .map(|profile| profile.tools.clone())
 60            .unwrap_or_default();
 61        let enable_all_context_servers = base_profile
 62            .as_ref()
 63            .map(|profile| profile.enable_all_context_servers)
 64            .unwrap_or_default();
 65        let context_servers = base_profile
 66            .as_ref()
 67            .map(|profile| profile.context_servers.clone())
 68            .unwrap_or_default();
 69        // Preserve the base profile's model preference when cloning into a new profile.
 70        let default_model = base_profile
 71            .as_ref()
 72            .and_then(|profile| profile.default_model.clone());
 73
 74        let profile_settings = AgentProfileSettings {
 75            name: name.into(),
 76            tools,
 77            enable_all_context_servers,
 78            context_servers,
 79            default_model,
 80        };
 81
 82        update_settings_file(fs, cx, {
 83            let id = id.clone();
 84            move |settings, _cx| {
 85                profile_settings.save_to_settings(id, settings).log_err();
 86            }
 87        });
 88
 89        id
 90    }
 91
 92    /// Returns a map of AgentProfileIds to their names
 93    pub fn available_profiles(cx: &App) -> AvailableProfiles {
 94        let mut profiles = AvailableProfiles::default();
 95        for (id, profile) in AgentSettings::get_global(cx).profiles.iter() {
 96            profiles.insert(id.clone(), profile.name.clone());
 97        }
 98        profiles
 99    }
100}
101
102/// A profile for the Zed Agent that controls its behavior.
103#[derive(Debug, Clone)]
104pub struct AgentProfileSettings {
105    /// The name of the profile.
106    pub name: SharedString,
107    pub tools: IndexMap<Arc<str>, bool>,
108    pub enable_all_context_servers: bool,
109    pub context_servers: IndexMap<Arc<str>, ContextServerPreset>,
110    /// Default language model to apply when this profile becomes active.
111    pub default_model: Option<LanguageModelSelection>,
112}
113
114impl AgentProfileSettings {
115    pub fn is_tool_enabled(&self, tool_name: &str) -> bool {
116        self.tools.get(tool_name) == Some(&true)
117    }
118
119    pub fn is_context_server_tool_enabled(&self, server_id: &str, tool_name: &str) -> bool {
120        self.enable_all_context_servers
121            || self
122                .context_servers
123                .get(server_id)
124                .is_some_and(|preset| preset.tools.get(tool_name) == Some(&true))
125    }
126
127    pub fn save_to_settings(
128        &self,
129        profile_id: AgentProfileId,
130        content: &mut SettingsContent,
131    ) -> Result<()> {
132        let profiles = content
133            .agent
134            .get_or_insert_default()
135            .profiles
136            .get_or_insert_default();
137        if profiles.contains_key(&profile_id.0) {
138            bail!("profile with ID '{profile_id}' already exists");
139        }
140
141        profiles.insert(
142            profile_id.0,
143            AgentProfileContent {
144                name: self.name.clone().into(),
145                tools: self.tools.clone(),
146                enable_all_context_servers: Some(self.enable_all_context_servers),
147                context_servers: self
148                    .context_servers
149                    .clone()
150                    .into_iter()
151                    .map(|(server_id, preset)| {
152                        (
153                            server_id,
154                            ContextServerPresetContent {
155                                tools: preset.tools,
156                            },
157                        )
158                    })
159                    .collect(),
160                default_model: self.default_model.clone(),
161            },
162        );
163
164        Ok(())
165    }
166}
167
168impl From<AgentProfileContent> for AgentProfileSettings {
169    fn from(content: AgentProfileContent) -> Self {
170        let AgentProfileContent {
171            name,
172            tools,
173            enable_all_context_servers,
174            context_servers,
175            default_model,
176        } = content;
177
178        Self {
179            name: name.into(),
180            tools,
181            enable_all_context_servers: enable_all_context_servers.unwrap_or_default(),
182            context_servers: context_servers
183                .into_iter()
184                .map(|(server_id, preset)| (server_id, preset.into()))
185                .collect(),
186            default_model,
187        }
188    }
189}
190
191#[derive(Debug, Clone, Default)]
192pub struct ContextServerPreset {
193    pub tools: IndexMap<Arc<str>, bool>,
194}
195
196impl From<settings::ContextServerPresetContent> for ContextServerPreset {
197    fn from(content: settings::ContextServerPresetContent) -> Self {
198        Self {
199            tools: content.tools,
200        }
201    }
202}