1#![allow(dead_code)]
3use std::sync::Arc;
4
5use std::collections::HashMap;
6
7use super::load_agents_dir::AgentDefinition;
8
9#[derive(Debug, Clone)]
11pub struct AgentSourceGroup {
12 pub label: &'static str,
13 pub source: &'static str,
14}
15
16pub const AGENT_SOURCE_GROUPS: &[AgentSourceGroup] = &[
18 AgentSourceGroup {
19 label: "User agents",
20 source: "userSettings",
21 },
22 AgentSourceGroup {
23 label: "Project agents",
24 source: "projectSettings",
25 },
26 AgentSourceGroup {
27 label: "Local agents",
28 source: "localSettings",
29 },
30 AgentSourceGroup {
31 label: "Managed agents",
32 source: "policySettings",
33 },
34 AgentSourceGroup {
35 label: "Plugin agents",
36 source: "plugin",
37 },
38 AgentSourceGroup {
39 label: "CLI arg agents",
40 source: "flagSettings",
41 },
42 AgentSourceGroup {
43 label: "Built-in agents",
44 source: "built-in",
45 },
46];
47
48#[derive(Debug, Clone)]
50pub struct ResolvedAgent {
51 pub definition: AgentDefinition,
52 pub overridden_by: Option<String>,
53}
54
55pub fn resolve_agent_overrides(
61 all_agents: Vec<AgentDefinition>,
62 active_agents: &[AgentDefinition],
63) -> Vec<ResolvedAgent> {
64 let mut active_map: HashMap<String, &AgentDefinition> = HashMap::new();
65 for agent in active_agents {
66 active_map.insert(agent.agent_type.clone(), agent);
67 }
68
69 let mut seen: HashMap<String, bool> = HashMap::new();
70 let mut resolved: Vec<ResolvedAgent> = Vec::new();
71
72 for agent in all_agents {
73 let key = format!("{}:{}", agent.agent_type, agent.source);
74 if seen.contains_key(&key) {
75 continue;
76 }
77 seen.insert(key, true);
78
79 let overridden_by = active_map.get(&agent.agent_type).and_then(|active| {
80 if active.source != agent.source {
81 Some(active.source.clone())
82 } else {
83 None
84 }
85 });
86
87 resolved.push(ResolvedAgent {
88 definition: agent,
89 overridden_by,
90 });
91 }
92
93 resolved
94}
95
96pub fn resolve_agent_model_display(agent: &AgentDefinition) -> Option<String> {
98 let model = agent
99 .model
100 .as_deref()
101 .unwrap_or_else(|| get_default_subagent_model().unwrap_or("sonnet"));
102 if model.is_empty() {
103 return None;
104 }
105 Some(if model == "inherit" {
106 "inherit".to_string()
107 } else {
108 model.to_string()
109 })
110}
111
112fn get_default_subagent_model() -> Option<&'static str> {
114 Some("sonnet")
115}
116
117pub fn get_override_source_label(source: &str) -> String {
119 get_source_display_name(source).to_lowercase()
120}
121
122fn get_source_display_name(source: &str) -> &str {
124 match source {
125 "userSettings" => "User",
126 "projectSettings" => "Project",
127 "localSettings" => "Local",
128 "policySettings" => "Managed",
129 "plugin" => "Plugin",
130 "flagSettings" => "CLI",
131 "built-in" => "Built-in",
132 _ => source,
133 }
134}
135
136pub fn compare_agents_by_name(a: &AgentDefinition, b: &AgentDefinition) -> std::cmp::Ordering {
138 a.agent_type
139 .to_lowercase()
140 .cmp(&b.agent_type.to_lowercase())
141}
142
143pub fn format_agent_line(agent: &AgentDefinition) -> String {
145 let tools_description = get_tools_description(agent);
146 format!(
147 "- {}: {} (Tools: {})",
148 agent.agent_type, agent.when_to_use, tools_description
149 )
150}
151
152fn get_tools_description(agent: &AgentDefinition) -> String {
154 let has_allowlist = agent.tools.iter().any(|t| !t.starts_with('-'));
155 let denylist: Vec<&String> = agent
156 .disallowed_tools
157 .iter()
158 .filter(|t| t.starts_with('-'))
159 .collect();
160 let has_denylist = !denylist.is_empty();
161
162 if has_allowlist && has_denylist {
163 let deny_set: std::collections::HashSet<&str> =
165 denylist.iter().map(|t| t.as_str()).collect();
166 let effective: Vec<&String> = agent
167 .tools
168 .iter()
169 .filter(|t| !deny_set.contains(t.as_str()))
170 .collect();
171 if effective.is_empty() {
172 return "None".to_string();
173 }
174 effective
175 .iter()
176 .map(|t| t.as_str())
177 .collect::<Vec<_>>()
178 .join(", ")
179 } else if has_allowlist {
180 agent.tools.join(", ")
182 } else if has_denylist {
183 let tools: Vec<&str> = denylist.iter().map(|t| t.as_str()).collect();
185 format!("All tools except {}", tools.join(", "))
186 } else {
187 "All tools".to_string()
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 fn make_agent(agent_type: &str, source: &str) -> AgentDefinition {
197 AgentDefinition {
198 agent_type: agent_type.to_string(),
199 when_to_use: "test".to_string(),
200 tools: vec!["*".to_string()],
201 source: source.to_string(),
202 base_dir: "built-in".to_string(),
203 get_system_prompt: Arc::new(|| String::new()),
204 model: None,
205 disallowed_tools: vec![],
206 max_turns: None,
207 permission_mode: None,
208 effort: None,
209 color: None,
210 mcp_servers: vec![],
211 hooks: None,
212 skills: vec![],
213 background: false,
214 initial_prompt: None,
215 memory: None,
216 isolation: None,
217 required_mcp_servers: vec![],
218 omit_claude_md: false,
219 critical_system_reminder_experimental: None,
220 }
221 }
222
223 #[test]
224 fn test_resolve_overrides() {
225 let all_agents = vec![
226 make_agent("test", "built-in"),
227 make_agent("test", "userSettings"),
228 ];
229 let active = vec![make_agent("test", "userSettings")];
230 let resolved = resolve_agent_overrides(all_agents, &active);
231 assert_eq!(resolved.len(), 2);
232 }
233
234 #[test]
235 fn test_compare_agents_by_name() {
236 let a = make_agent("Beta", "built-in");
237 let b = make_agent("alpha", "built-in");
238 assert_eq!(compare_agents_by_name(&a, &b), std::cmp::Ordering::Greater);
239 }
240
241 #[test]
242 fn test_resolve_agent_model_display_inherit() {
243 let agent = make_agent("test", "built-in");
244 assert_eq!(
245 resolve_agent_model_display(&agent),
246 Some("sonnet".to_string())
247 );
248 }
249}