1use std::sync::Arc;
4
5use async_trait::async_trait;
6use serde_json::{json, Value};
7
8use super::{Tool, ToolResult};
9use crate::planning::types::StepStatus;
10use crate::planning::PlanManager;
11
12#[derive(Debug)]
17pub struct CreatePlanTool {
18 plan_manager: Arc<std::sync::Mutex<PlanManager>>,
19 task_id: String,
20}
21
22impl CreatePlanTool {
23 pub fn new(plan_manager: Arc<std::sync::Mutex<PlanManager>>, task_id: String) -> Self {
24 Self {
25 plan_manager,
26 task_id,
27 }
28 }
29}
30
31#[async_trait]
32impl Tool for CreatePlanTool {
33 fn name(&self) -> &str {
34 "create_plan"
35 }
36
37 fn description(&self) -> &str {
38 "Create a new execution plan with a list of steps. Returns the plan ID and rendered markdown."
39 }
40
41 fn parameters_schema(&self) -> Value {
42 json!({
43 "type": "object",
44 "properties": {
45 "title": {
46 "type": "string",
47 "description": "The plan title."
48 },
49 "steps": {
50 "type": "array",
51 "items": { "type": "string" },
52 "description": "Ordered list of step descriptions."
53 }
54 },
55 "required": ["title", "steps"]
56 })
57 }
58
59 async fn execute(&self, args: Value) -> ToolResult {
60 let title = match args.get("title").and_then(|v| v.as_str()) {
61 Some(t) => t,
62 None => return ToolResult::err("Missing required parameter 'title'"),
63 };
64
65 let steps: Vec<String> = match args.get("steps").and_then(|v| v.as_array()) {
66 Some(arr) => arr
67 .iter()
68 .filter_map(|v| v.as_str().map(String::from))
69 .collect(),
70 None => {
71 return ToolResult::err("Missing required parameter 'steps' (must be an array)")
72 }
73 };
74
75 if steps.is_empty() {
76 return ToolResult::err("Plan must have at least one step");
77 }
78
79 let pm = self.plan_manager.lock().unwrap();
80 match pm.create_plan(&self.task_id, title, steps) {
81 Ok(plan) => {
82 let rendered = pm.render_markdown(&plan);
83 ToolResult::ok(json!({
84 "plan_id": plan.id,
85 "title": plan.title,
86 "step_count": plan.steps.len(),
87 "file_path": plan.file_path.to_string_lossy(),
88 "rendered": rendered,
89 }))
90 }
91 Err(e) => ToolResult::err(format!("Failed to create plan: {}", e)),
92 }
93 }
94}
95
96#[derive(Debug)]
101pub struct UpdatePlanStepTool {
102 plan_manager: Arc<std::sync::Mutex<PlanManager>>,
103}
104
105impl UpdatePlanStepTool {
106 pub fn new(plan_manager: Arc<std::sync::Mutex<PlanManager>>) -> Self {
107 Self { plan_manager }
108 }
109}
110
111#[async_trait]
112impl Tool for UpdatePlanStepTool {
113 fn name(&self) -> &str {
114 "update_plan_step"
115 }
116
117 fn description(&self) -> &str {
118 "Update the status of a step in an existing plan. Status can be: in_progress, completed, failed, skipped."
119 }
120
121 fn parameters_schema(&self) -> Value {
122 json!({
123 "type": "object",
124 "properties": {
125 "plan_file_path": {
126 "type": "string",
127 "description": "The file path of the plan to update."
128 },
129 "step_number": {
130 "type": "integer",
131 "description": "The step number (1-indexed)."
132 },
133 "status": {
134 "type": "string",
135 "enum": ["in_progress", "completed", "failed", "skipped"],
136 "description": "The new status for the step."
137 },
138 "result": {
139 "type": "string",
140 "description": "Optional result description for completed/failed steps."
141 }
142 },
143 "required": ["plan_file_path", "step_number", "status"]
144 })
145 }
146
147 async fn execute(&self, args: Value) -> ToolResult {
148 let plan_file_path = match args.get("plan_file_path").and_then(|v| v.as_str()) {
149 Some(p) => p,
150 None => return ToolResult::err("Missing required parameter 'plan_file_path'"),
151 };
152
153 let step_number = match args.get("step_number").and_then(|v| v.as_u64()) {
154 Some(n) => n as usize,
155 None => return ToolResult::err("Missing required parameter 'step_number'"),
156 };
157
158 let status_str = match args.get("status").and_then(|v| v.as_str()) {
159 Some(s) => s,
160 None => return ToolResult::err("Missing required parameter 'status'"),
161 };
162
163 let status = match status_str {
164 "in_progress" => StepStatus::InProgress,
165 "completed" => StepStatus::Completed,
166 "failed" => StepStatus::Failed,
167 "skipped" => StepStatus::Skipped,
168 _ => {
169 return ToolResult::err(format!(
170 "Unknown status: '{}'. Use: in_progress, completed, failed, skipped",
171 status_str
172 ))
173 }
174 };
175
176 let result = args.get("result").and_then(|v| v.as_str());
177
178 let pm = self.plan_manager.lock().unwrap();
179 let path = std::path::Path::new(plan_file_path);
180
181 let mut plan = match pm.load_plan(path) {
182 Ok(p) => p,
183 Err(e) => return ToolResult::err(format!("Failed to load plan: {}", e)),
184 };
185
186 match pm.update_step(&mut plan, step_number, status, result) {
187 Ok(()) => {
188 let rendered = pm.render_markdown(&plan);
189 ToolResult::ok(json!({
190 "plan_id": plan.id,
191 "step_number": step_number,
192 "status": status_str,
193 "plan_status": format!("{:?}", plan.status),
194 "rendered": rendered,
195 }))
196 }
197 Err(e) => ToolResult::err(format!("Failed to update step: {}", e)),
198 }
199 }
200}
201
202#[derive(Debug)]
207pub struct ListPlansTool {
208 plan_manager: Arc<std::sync::Mutex<PlanManager>>,
209}
210
211impl ListPlansTool {
212 pub fn new(plan_manager: Arc<std::sync::Mutex<PlanManager>>) -> Self {
213 Self { plan_manager }
214 }
215}
216
217#[async_trait]
218impl Tool for ListPlansTool {
219 fn name(&self) -> &str {
220 "list_plans"
221 }
222
223 fn description(&self) -> &str {
224 "List all existing plans in the plans directory."
225 }
226
227 fn parameters_schema(&self) -> Value {
228 json!({
229 "type": "object",
230 "properties": {}
231 })
232 }
233
234 async fn execute(&self, _args: Value) -> ToolResult {
235 let pm = self.plan_manager.lock().unwrap();
236 match pm.list_plans() {
237 Ok(paths) => {
238 let plans: Vec<Value> = paths
239 .iter()
240 .filter_map(|p| {
241 let plan = pm.load_plan(p).ok()?;
242 Some(json!({
243 "plan_id": plan.id,
244 "title": plan.title,
245 "status": format!("{:?}", plan.status),
246 "steps": plan.steps.len(),
247 "file_path": p.to_string_lossy(),
248 }))
249 })
250 .collect();
251
252 ToolResult::ok(json!({
253 "count": plans.len(),
254 "plans": plans,
255 }))
256 }
257 Err(e) => ToolResult::err(format!("Failed to list plans: {}", e)),
258 }
259 }
260}
261
262#[derive(Debug)]
267pub struct GetPlanTool {
268 plan_manager: Arc<std::sync::Mutex<PlanManager>>,
269}
270
271impl GetPlanTool {
272 pub fn new(plan_manager: Arc<std::sync::Mutex<PlanManager>>) -> Self {
273 Self { plan_manager }
274 }
275}
276
277#[async_trait]
278impl Tool for GetPlanTool {
279 fn name(&self) -> &str {
280 "get_plan"
281 }
282
283 fn description(&self) -> &str {
284 "Load and render a specific plan by its file path."
285 }
286
287 fn parameters_schema(&self) -> Value {
288 json!({
289 "type": "object",
290 "properties": {
291 "plan_file_path": {
292 "type": "string",
293 "description": "The file path of the plan to load."
294 }
295 },
296 "required": ["plan_file_path"]
297 })
298 }
299
300 async fn execute(&self, args: Value) -> ToolResult {
301 let plan_file_path = match args.get("plan_file_path").and_then(|v| v.as_str()) {
302 Some(p) => p,
303 None => return ToolResult::err("Missing required parameter 'plan_file_path'"),
304 };
305
306 let pm = self.plan_manager.lock().unwrap();
307 match pm.load_plan(std::path::Path::new(plan_file_path)) {
308 Ok(plan) => {
309 let rendered = pm.render_markdown(&plan);
310 ToolResult::ok(json!({
311 "plan_id": plan.id,
312 "title": plan.title,
313 "status": format!("{:?}", plan.status),
314 "task_id": plan.task_id,
315 "steps": plan.steps.iter().map(|s| json!({
316 "number": s.number,
317 "description": s.description,
318 "status": format!("{:?}", s.status),
319 "result": s.result,
320 })).collect::<Vec<_>>(),
321 "rendered": rendered,
322 }))
323 }
324 Err(e) => ToolResult::err(format!("Failed to load plan: {}", e)),
325 }
326 }
327}