Skip to main content

opendev_tools_impl/agents/
list.rs

1use std::collections::HashMap;
2
3use opendev_tools_core::{BaseTool, ToolContext, ToolDisplayMeta, ToolResult};
4
5/// Built-in subagent type definition.
6#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
7struct AgentType {
8    name: String,
9    description: String,
10    tools: Vec<String>,
11}
12
13/// Tool for listing available subagent types.
14#[derive(Debug)]
15pub struct AgentsTool;
16
17/// Default agent types available in the system.
18fn default_agent_types() -> Vec<AgentType> {
19    vec![
20        AgentType {
21            name: "explore".into(),
22            description: "Read-only agent for exploring and understanding codebases. \
23                           Has access to file reading, search, and listing tools."
24                .into(),
25            tools: vec!["read_file".into(), "search".into(), "list_files".into()],
26        },
27        AgentType {
28            name: "planner".into(),
29            description: "Planning agent that creates implementation plans. \
30                           Has read-only access to understand the codebase before planning."
31                .into(),
32            tools: vec![
33                "read_file".into(),
34                "search".into(),
35                "list_files".into(),
36                "write_file".into(),
37            ],
38        },
39        AgentType {
40            name: "ask_user".into(),
41            description: "Agent that interacts with the user to gather information \
42                           or clarify requirements."
43                .into(),
44            tools: vec!["ask_user".into()],
45        },
46    ]
47}
48
49#[async_trait::async_trait]
50impl BaseTool for AgentsTool {
51    fn name(&self) -> &str {
52        "agents"
53    }
54
55    fn description(&self) -> &str {
56        "List available subagent types with their descriptions and allowed tools."
57    }
58
59    fn parameter_schema(&self) -> serde_json::Value {
60        serde_json::json!({
61            "type": "object",
62            "properties": {
63                "action": {
64                    "type": "string",
65                    "description": "Action to perform. Currently only 'list' is supported.",
66                    "enum": ["list"]
67                }
68            }
69        })
70    }
71
72    async fn execute(
73        &self,
74        args: HashMap<String, serde_json::Value>,
75        ctx: &ToolContext,
76    ) -> ToolResult {
77        let action = args
78            .get("action")
79            .and_then(|v| v.as_str())
80            .unwrap_or("list");
81
82        match action {
83            "list" => self.list_agents(ctx),
84            other => ToolResult::fail(format!("Unknown action: {other}. Available actions: list")),
85        }
86    }
87
88    fn display_meta(&self) -> Option<ToolDisplayMeta> {
89        Some(ToolDisplayMeta {
90            verb: "Agents",
91            label: "agents",
92            category: "Agent",
93            primary_arg_keys: &["action"],
94        })
95    }
96}
97
98impl AgentsTool {
99    fn list_agents(&self, ctx: &ToolContext) -> ToolResult {
100        // Try to read agent types from context values (injected by runtime)
101        let agents = if let Some(custom_agents) = ctx.values.get("agent_types") {
102            match serde_json::from_value::<Vec<AgentType>>(custom_agents.clone()) {
103                Ok(agents) => agents,
104                Err(_) => default_agent_types(),
105            }
106        } else {
107            default_agent_types()
108        };
109
110        if agents.is_empty() {
111            return ToolResult::ok("No subagent types found.");
112        }
113
114        let mut parts = vec![format!("Available agents ({}):\n", agents.len())];
115
116        for agent in &agents {
117            parts.push(format!("  {}: {}", agent.name, agent.description));
118            if !agent.tools.is_empty() {
119                let tools_display: Vec<&str> =
120                    agent.tools.iter().take(10).map(|s| s.as_str()).collect();
121                parts.push(format!("    Tools: {}", tools_display.join(", ")));
122            }
123        }
124
125        let output = parts.join("\n");
126
127        let mut metadata = HashMap::new();
128        metadata.insert(
129            "agents".into(),
130            serde_json::to_value(&agents).unwrap_or_default(),
131        );
132        metadata.insert("count".into(), serde_json::json!(agents.len()));
133
134        ToolResult::ok_with_metadata(output, metadata)
135    }
136}
137
138#[cfg(test)]
139#[path = "list_tests.rs"]
140mod tests;