opendev_tools_impl/agents/
list.rs1use std::collections::HashMap;
2
3use opendev_tools_core::{BaseTool, ToolContext, ToolDisplayMeta, ToolResult};
4
5#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
7struct AgentType {
8 name: String,
9 description: String,
10 tools: Vec<String>,
11}
12
13#[derive(Debug)]
15pub struct AgentsTool;
16
17fn 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 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;