Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:52:20.631Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

agent_profile.rs

310 lines · 10.3 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.