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 每次调用会替换整个列表。用于非 trivial 任务(3 步以上)以展示进度。\
17 同时只保持一个任务 'in_progress',完成后立即标记为 'completed'。"
18 .to_string(),
19 parameters: json!({
20 "type": "object",
21 "properties": {
22 "todos": {
23 "type": "array",
24 "description": "待办项的完整替换列表",
25 "items": {
26 "type": "object",
27 "properties": {
28 "content": {
29 "type": "string",
30 "description": "命令式描述(如 '运行测试')"
31 },
32 "activeForm": {
33 "type": "string",
34 "description": "进行式描述(如 '正在运行测试')"
35 },
36 "status": {
37 "type": "string",
38 "enum": ["pending", "in_progress", "completed"]
39 }
40 },
41 "required": ["content", "activeForm", "status"]
42 }
43 }
44 },
45 "required": ["todos"]
46 }),
47 }
48 }
49
50 async fn execute(&self, params: Value) -> Result<String> {
51 let todos = params["todos"]
52 .as_array()
53 .ok_or_else(|| anyhow::anyhow!("missing 'todos' array"))?;
54
55 let mut in_progress_count = 0;
59 let _completed_count = 0;
60 let _pending_count = 0;
61 let mut lines = Vec::with_capacity(todos.len() + 1);
62
63 for (i, todo) in todos.iter().enumerate() {
64 let content = todo["content"]
65 .as_str()
66 .ok_or_else(|| anyhow::anyhow!("todo {}: missing 'content'", i))?;
67 let active = todo["activeForm"]
68 .as_str()
69 .ok_or_else(|| anyhow::anyhow!("todo {}: missing 'activeForm'", i))?;
70 let status = todo["status"]
71 .as_str()
72 .ok_or_else(|| anyhow::anyhow!("todo {}: missing 'status'", i))?;
73
74 let (marker, display) = match status {
75 "pending" => ("[ ]", content),
76 "in_progress" => {
77 in_progress_count += 1;
78 ("[~]", active)
79 }
80 "completed" => ("[x]", content),
81 other => anyhow::bail!("todo {}: invalid status '{}'", i, other),
82 };
83
84 lines.push(format!(" {} {}", marker, display));
85 }
86
87 if in_progress_count > 1 {
88 anyhow::bail!(
90 "at most one task may be 'in_progress' at a time (found {})",
91 in_progress_count
92 );
93 }
94
95 if lines.is_empty() {
96 return Ok("Todo list cleared.".to_string());
98 }
99
100 let header = format!("Todos ({}):", lines.len());
101 let mut out =
102 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}