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 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 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}