Skip to main content

ai_agent/tools/agent/
agent_display.rs

1// Source: ~/claudecode/openclaudecode/src/tools/AgentTool/agentDisplay.ts
2#![allow(dead_code)]
3use std::sync::Arc;
4
5use std::collections::HashMap;
6
7use super::load_agents_dir::AgentDefinition;
8
9/// Represents a source group for display ordering.
10#[derive(Debug, Clone)]
11pub struct AgentSourceGroup {
12    pub label: &'static str,
13    pub source: &'static str,
14}
15
16/// Ordered list of agent source groups for display.
17pub 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/// Agent definition with override information.
49#[derive(Debug, Clone)]
50pub struct ResolvedAgent {
51    pub definition: AgentDefinition,
52    pub overridden_by: Option<String>,
53}
54
55/// Annotate agents with override information by comparing against the active
56/// (winning) agent list. An agent is "overridden" when another agent with the
57/// same type from a higher-priority source takes precedence.
58///
59/// Also deduplicates by (agent_type, source) to handle git worktree duplicates.
60pub 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
96/// Resolve the display model string for an agent.
97pub 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
112/// Get the default subagent model.
113fn get_default_subagent_model() -> Option<&'static str> {
114    Some("sonnet")
115}
116
117/// Get a human-readable label for the source that overrides an agent.
118pub fn get_override_source_label(source: &str) -> String {
119    get_source_display_name(source).to_lowercase()
120}
121
122/// Get the display name for a source.
123fn 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
136/// Compare agents alphabetically by name (case-insensitive).
137pub 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
143/// Format one agent line for display.
144pub 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
152/// Get a description of the tools available to an agent.
153fn 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        // Both defined: filter allowlist by denylist to match runtime behavior
164        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        // Allowlist only: show the specific tools available
181        agent.tools.join(", ")
182    } else if has_denylist {
183        // Denylist only: show "All tools except X, Y, Z"
184        let tools: Vec<&str> = denylist.iter().map(|t| t.as_str()).collect();
185        format!("All tools except {}", tools.join(", "))
186    } else {
187        // No restrictions
188        "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}