matrixcode_core/tools/
todo_write.rs1use anyhow::Result;
2use async_trait::async_trait;
3use serde_json::{Value, json};
4
5use super::{Tool, ToolDefinition};
6use crate::approval::RiskLevel;
7
8pub struct TodoWriteTool;
9
10#[async_trait]
11impl Tool for TodoWriteTool {
12 fn definition(&self) -> ToolDefinition {
13 ToolDefinition {
14 name: "todo_write".to_string(),
15 description:
16 "维护结构化待办列表,用于规划和跟踪多步骤工作。\
17 每次调用会替换整个列表。用于非 trivial 任务(3 步以上)以展示进度。\
18 同时只保持一个任务 'in_progress',完成后立即标记为 'completed'。"
19 .to_string(),
20 parameters: json!({
21 "type": "object",
22 "properties": {
23 "todos": {
24 "type": "array",
25 "description": "待办项的完整替换列表",
26 "items": {
27 "type": "object",
28 "properties": {
29 "content": {
30 "type": "string",
31 "description": "命令式描述(如 '运行测试')"
32 },
33 "activeForm": {
34 "type": "string",
35 "description": "进行式描述(如 '正在运行测试')"
36 },
37 "status": {
38 "type": "string",
39 "enum": ["pending", "in_progress", "completed"]
40 }
41 },
42 "required": ["content", "activeForm", "status"]
43 }
44 }
45 },
46 "required": ["todos"]
47 }),
48 }
49 }
50
51 async fn execute(&self, params: Value) -> Result<String> {
52 let todos = params["todos"]
53 .as_array()
54 .ok_or_else(|| anyhow::anyhow!("missing 'todos' array"))?;
55
56 let mut in_progress_count = 0;
60 let _completed_count = 0;
61 let _pending_count = 0;
62 let mut lines = Vec::with_capacity(todos.len() + 1);
63
64 for (i, todo) in todos.iter().enumerate() {
65 let content = todo["content"]
66 .as_str()
67 .ok_or_else(|| anyhow::anyhow!("todo {}: missing 'content'", i))?;
68 let active = todo["activeForm"]
69 .as_str()
70 .ok_or_else(|| anyhow::anyhow!("todo {}: missing 'activeForm'", i))?;
71 let status = todo["status"]
72 .as_str()
73 .ok_or_else(|| anyhow::anyhow!("todo {}: missing 'status'", i))?;
74
75 let (marker, display) = match status {
76 "pending" => ("[ ]", content),
77 "in_progress" => {
78 in_progress_count += 1;
79 ("[~]", active)
80 }
81 "completed" => ("[x]", content),
82 other => anyhow::bail!("todo {}: invalid status '{}'", i, other),
83 };
84
85 lines.push(format!(" {} {}", marker, display));
86 }
87
88 if in_progress_count > 1 {
89 anyhow::bail!(
91 "at most one task may be 'in_progress' at a time (found {})",
92 in_progress_count
93 );
94 }
95
96 if lines.is_empty() {
97 return Ok("Todo list cleared.".to_string());
99 }
100
101 let header = format!("Todos ({}):", lines.len());
102 let mut out = String::with_capacity(header.len() + lines.iter().map(|l| l.len() + 1).sum::<usize>());
103 out.push_str(&header);
104 for l in &lines {
105 out.push('\n');
106 out.push_str(l);
107 }
108
109 Ok(out)
114 }
115
116 fn risk_level(&self) -> RiskLevel {
117 RiskLevel::Mutating
118 }
119}