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}