Skip to repository content310 lines · 10.3 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T03:57:15.721Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
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, SettingsStore, 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 Omega 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 /// Whether the built-in profile with the given id still matches the shipped
120 /// default — i.e. the user has neither customized the built-in profile nor
121 /// shadowed it with a custom profile of the same id. Custom profile ids are
122 /// never considered unmodified defaults.
123 pub fn is_unmodified_default(profile_id: &AgentProfileId, cx: &App) -> bool {
124 if !builtin_profiles::is_builtin(profile_id) {
125 return false;
126 }
127 let store = cx.global::<SettingsStore>();
128 let profile_in = |content: &SettingsContent| {
129 content
130 .agent
131 .as_ref()
132 .and_then(|agent| agent.profiles.as_ref())
133 .and_then(|profiles| profiles.get(profile_id.as_str()))
134 .cloned()
135 };
136 match (
137 profile_in(store.merged_settings()),
138 profile_in(store.raw_default_settings()),
139 ) {
140 (Some(merged), Some(default)) => merged == default,
141 _ => false,
142 }
143 }
144
145 pub fn is_context_server_tool_enabled(&self, server_id: &str, tool_name: &str) -> bool {
146 self.context_servers
147 .get(server_id)
148 .and_then(|preset| preset.tools.get(tool_name).copied())
149 .unwrap_or(self.enable_all_context_servers)
150 }
151
152 pub fn save_to_settings(
153 &self,
154 profile_id: AgentProfileId,
155 content: &mut SettingsContent,
156 ) -> Result<()> {
157 let profiles = content
158 .agent
159 .get_or_insert_default()
160 .profiles
161 .get_or_insert_default();
162 if profiles.contains_key(&profile_id.0) {
163 bail!("profile with ID '{profile_id}' already exists");
164 }
165
166 profiles.insert(
167 profile_id.0,
168 AgentProfileContent {
169 name: self.name.clone().into(),
170 tools: self.tools.clone(),
171 enable_all_context_servers: Some(self.enable_all_context_servers),
172 context_servers: self
173 .context_servers
174 .clone()
175 .into_iter()
176 .map(|(server_id, preset)| {
177 (
178 server_id,
179 ContextServerPresetContent {
180 tools: preset.tools,
181 },
182 )
183 })
184 .collect(),
185 default_model: self.default_model.clone(),
186 },
187 );
188
189 Ok(())
190 }
191}
192
193impl From<AgentProfileContent> for AgentProfileSettings {
194 fn from(content: AgentProfileContent) -> Self {
195 let AgentProfileContent {
196 name,
197 tools,
198 enable_all_context_servers,
199 context_servers,
200 default_model,
201 } = content;
202
203 Self {
204 name: name.into(),
205 tools,
206 enable_all_context_servers: enable_all_context_servers.unwrap_or_default(),
207 context_servers: context_servers
208 .into_iter()
209 .map(|(server_id, preset)| (server_id, preset.into()))
210 .collect(),
211 default_model,
212 }
213 }
214}
215
216#[derive(Debug, Clone, Default)]
217pub struct ContextServerPreset {
218 pub tools: IndexMap<Arc<str>, bool>,
219}
220
221impl From<settings::ContextServerPresetContent> for ContextServerPreset {
222 fn from(content: settings::ContextServerPresetContent) -> Self {
223 Self {
224 tools: content.tools,
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 fn profile(
234 enable_all_context_servers: bool,
235 context_servers: IndexMap<Arc<str>, ContextServerPreset>,
236 ) -> AgentProfileSettings {
237 AgentProfileSettings {
238 name: "test".into(),
239 tools: IndexMap::default(),
240 enable_all_context_servers,
241 context_servers,
242 default_model: None,
243 }
244 }
245
246 fn preset(tools: &[(&str, bool)]) -> ContextServerPreset {
247 ContextServerPreset {
248 tools: tools
249 .iter()
250 .map(|(name, enabled)| (Arc::from(*name), *enabled))
251 .collect(),
252 }
253 }
254
255 #[test]
256 fn explicit_false_disables_tool_when_enable_all_is_true() {
257 let mut servers = IndexMap::default();
258 servers.insert(Arc::from("server"), preset(&[("disabled_tool", false)]));
259 let profile = profile(true, servers);
260
261 assert!(!profile.is_context_server_tool_enabled("server", "disabled_tool"));
262 assert!(profile.is_context_server_tool_enabled("server", "other_tool"));
263 assert!(profile.is_context_server_tool_enabled("other_server", "any_tool"));
264 }
265
266 #[test]
267 fn explicit_true_enables_tool_when_enable_all_is_false() {
268 let mut servers = IndexMap::default();
269 servers.insert(Arc::from("server"), preset(&[("enabled_tool", true)]));
270 let profile = profile(false, servers);
271
272 assert!(profile.is_context_server_tool_enabled("server", "enabled_tool"));
273 assert!(!profile.is_context_server_tool_enabled("server", "other_tool"));
274 assert!(!profile.is_context_server_tool_enabled("other_server", "any_tool"));
275 }
276
277 #[gpui::test]
278 fn unmodified_default_detection(cx: &mut gpui::App) {
279 use gpui::UpdateGlobal as _;
280
281 let store = SettingsStore::test(cx);
282 cx.set_global(store);
283 project::DisableAiSettings::register(cx);
284 AgentSettings::register(cx);
285
286 let write = AgentProfileId(builtin_profiles::WRITE.into());
287 let minimal = AgentProfileId(builtin_profiles::MINIMAL.into());
288 let custom = AgentProfileId("custom".into());
289
290 // Fresh defaults: the shipped built-in profiles are unmodified.
291 assert!(AgentProfileSettings::is_unmodified_default(&write, cx));
292 assert!(AgentProfileSettings::is_unmodified_default(&minimal, cx));
293 // Custom (non-built-in) ids are never considered unmodified defaults.
294 assert!(!AgentProfileSettings::is_unmodified_default(&custom, cx));
295
296 // The user customizes the `write` profile; `minimal` stays untouched.
297 SettingsStore::update_global(cx, |store, cx| {
298 store
299 .set_user_settings(
300 r#"{ "agent": { "profiles": { "write": { "name": "Write", "tools": { "fetch": false } } } } }"#,
301 cx,
302 )
303 .unwrap();
304 });
305
306 assert!(!AgentProfileSettings::is_unmodified_default(&write, cx));
307 assert!(AgentProfileSettings::is_unmodified_default(&minimal, cx));
308 }
309}
310