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, Settings as _, SettingsContent,
 10    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 const fn new(id: AgentProfileId) -> Self {
 37        Self { id }
 38    }
 39
 40    pub const 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        let profile_settings = AgentProfileSettings {
 57            name: name.into(),
 58            tools: base_profile
 59                .as_ref()
 60                .map(|profile| profile.tools.clone())
 61                .unwrap_or_default(),
 62            enable_all_context_servers: base_profile
 63                .as_ref()
 64                .map(|profile| profile.enable_all_context_servers)
 65                .unwrap_or_default(),
 66            context_servers: base_profile
 67                .map(|profile| profile.context_servers)
 68                .unwrap_or_default(),
 69        };
 70
 71        update_settings_file(fs, cx, {
 72            let id = id.clone();
 73            move |settings, _cx| {
 74                profile_settings.save_to_settings(id, settings).log_err();
 75            }
 76        });
 77
 78        id
 79    }
 80
 81    /// Returns a map of AgentProfileIds to their names
 82    pub fn available_profiles(cx: &App) -> AvailableProfiles {
 83        let mut profiles = AvailableProfiles::default();
 84        for (id, profile) in AgentSettings::get_global(cx).profiles.iter() {
 85            profiles.insert(id.clone(), profile.name.clone());
 86        }
 87        profiles
 88    }
 89}
 90
 91/// A profile for the Zed Agent that controls its behavior.
 92#[derive(Debug, Clone)]
 93pub struct AgentProfileSettings {
 94    /// The name of the profile.
 95    pub name: SharedString,
 96    pub tools: IndexMap<Arc<str>, bool>,
 97    pub enable_all_context_servers: bool,
 98    pub context_servers: IndexMap<Arc<str>, ContextServerPreset>,
 99}
100
101impl AgentProfileSettings {
102    pub fn is_tool_enabled(&self, tool_name: &str) -> bool {
103        self.tools.get(tool_name) == Some(&true)
104    }
105
106    pub fn is_context_server_tool_enabled(&self, server_id: &str, tool_name: &str) -> bool {
107        self.enable_all_context_servers
108            || self
109                .context_servers
110                .get(server_id)
111                .is_some_and(|preset| preset.tools.get(tool_name) == Some(&true))
112    }
113
114    pub fn save_to_settings(
115        &self,
116        profile_id: AgentProfileId,
117        content: &mut SettingsContent,
118    ) -> Result<()> {
119        let profiles = content
120            .agent
121            .get_or_insert_default()
122            .profiles
123            .get_or_insert_default();
124        if profiles.contains_key(&profile_id.0) {
125            bail!("profile with ID '{profile_id}' already exists");
126        }
127
128        profiles.insert(
129            profile_id.0,
130            AgentProfileContent {
131                name: self.name.clone().into(),
132                tools: self.tools.clone(),
133                enable_all_context_servers: Some(self.enable_all_context_servers),
134                context_servers: self
135                    .context_servers
136                    .clone()
137                    .into_iter()
138                    .map(|(server_id, preset)| {
139                        (
140                            server_id,
141                            ContextServerPresetContent {
142                                tools: preset.tools,
143                            },
144                        )
145                    })
146                    .collect(),
147            },
148        );
149
150        Ok(())
151    }
152}
153
154impl From<AgentProfileContent> for AgentProfileSettings {
155    fn from(content: AgentProfileContent) -> Self {
156        Self {
157            name: content.name.into(),
158            tools: content.tools,
159            enable_all_context_servers: content.enable_all_context_servers.unwrap_or_default(),
160            context_servers: content
161                .context_servers
162                .into_iter()
163                .map(|(server_id, preset)| (server_id, preset.into()))
164                .collect(),
165        }
166    }
167}
168
169#[derive(Debug, Clone, Default)]
170pub struct ContextServerPreset {
171    pub tools: IndexMap<Arc<str>, bool>,
172}
173
174impl From<settings::ContextServerPresetContent> for ContextServerPreset {
175    fn from(content: settings::ContextServerPresetContent) -> Self {
176        Self {
177            tools: content.tools,
178        }
179    }
180}