opendev_tools_impl/
todo.rs1use std::collections::HashMap;
7use std::sync::{Arc, Mutex};
8
9use opendev_tools_core::{BaseTool, ToolContext, ToolDisplayMeta, ToolResult};
10
11#[derive(Debug)]
13pub struct TodoTool {
14 manager: Arc<Mutex<opendev_runtime::TodoManager>>,
19}
20
21impl TodoTool {
22 pub fn new(manager: Arc<Mutex<opendev_runtime::TodoManager>>) -> Self {
24 Self { manager }
25 }
26}
27
28#[async_trait::async_trait]
29impl BaseTool for TodoTool {
30 fn name(&self) -> &str {
31 "todo"
32 }
33
34 fn description(&self) -> &str {
35 "Manage plan execution todos. List current todos, mark items as \
36 in-progress or completed, or add new items."
37 }
38
39 fn parameter_schema(&self) -> serde_json::Value {
40 serde_json::json!({
41 "type": "object",
42 "properties": {
43 "action": {
44 "type": "string",
45 "enum": ["list", "start", "complete", "add"],
46 "description": "Action to perform on todos"
47 },
48 "id": {
49 "type": "integer",
50 "description": "Todo item ID (for start/complete)"
51 },
52 "title": {
53 "type": "string",
54 "description": "Title for a new todo item (for add)"
55 }
56 },
57 "required": ["action"]
58 })
59 }
60
61 async fn execute(
62 &self,
63 args: HashMap<String, serde_json::Value>,
64 _ctx: &ToolContext,
65 ) -> ToolResult {
66 let action = match args.get("action").and_then(|v| v.as_str()) {
67 Some(a) => a,
68 None => return ToolResult::fail("action is required"),
69 };
70
71 let mut mgr = match self.manager.lock() {
72 Ok(m) => m,
73 Err(e) => return ToolResult::fail(format!("Lock error: {e}")),
74 };
75
76 match action {
77 "list" => {
78 if !mgr.has_todos() {
79 return ToolResult::ok("No todos.");
80 }
81 ToolResult::ok(mgr.format_status())
82 }
83 "start" => {
84 let id = match args.get("id").and_then(|v| v.as_u64()) {
85 Some(id) => id as usize,
86 None => return ToolResult::fail("id is required for start"),
87 };
88 if mgr.start(id) {
89 ToolResult::ok(format!(
90 "Todo {id} marked as in-progress.\n\n{}",
91 mgr.format_status()
92 ))
93 } else {
94 ToolResult::fail(format!("Todo {id} not found"))
95 }
96 }
97 "complete" => {
98 let id = match args.get("id").and_then(|v| v.as_u64()) {
99 Some(id) => id as usize,
100 None => return ToolResult::fail("id is required for complete"),
101 };
102 if mgr.complete(id) {
103 let status = mgr.format_status();
104 if mgr.all_completed() {
105 ToolResult::ok(format!(
106 "Todo {id} completed. All todos are done!\n\n{status}"
107 ))
108 } else {
109 ToolResult::ok(format!("Todo {id} completed.\n\n{status}"))
110 }
111 } else {
112 ToolResult::fail(format!("Todo {id} not found"))
113 }
114 }
115 "add" => {
116 let title = match args.get("title").and_then(|v| v.as_str()) {
117 Some(t) if !t.is_empty() => t,
118 _ => return ToolResult::fail("title is required for add"),
119 };
120 let id = mgr.add(title.to_string());
121 ToolResult::ok(format!(
122 "Added todo {id}: {title}\n\n{}",
123 mgr.format_status()
124 ))
125 }
126 _ => ToolResult::fail(format!(
127 "Unknown action: {action}. Available: list, start, complete, add"
128 )),
129 }
130 }
131
132 fn display_meta(&self) -> Option<ToolDisplayMeta> {
133 Some(ToolDisplayMeta {
134 verb: "Todo",
135 label: "task",
136 category: "Plan",
137 primary_arg_keys: &["action", "id", "title"],
138 })
139 }
140}
141
142#[cfg(test)]
143#[path = "todo_tests.rs"]
144mod tests;