Skip to main content

j_agent/tools/todo/
todo_write_tool.rs

1use super::entity::TodoItem;
2use super::todo_manager::TodoManager;
3use crate::tools::{PlanDecision, Tool, ToolResult, parse_tool_args, schema_to_tool_params};
4use schemars::JsonSchema;
5use serde::Deserialize;
6use serde_json::Value;
7use std::borrow::Cow;
8use std::sync::{Arc, atomic::AtomicBool};
9
10/// Todo 项参数
11#[derive(Deserialize, JsonSchema)]
12struct TodoItemParam {
13    /// Item ID. Required when merge=true to update existing items. Auto-generated if omitted.
14    #[serde(default)]
15    id: Option<String>,
16    /// The todo item text. Required for new items; optional when merge=true (omit to keep existing).
17    #[serde(default)]
18    content: String,
19    /// Item status: pending, in_progress, completed, or cancelled
20    #[serde(default = "default_status")]
21    status: String,
22}
23
24fn default_status() -> String {
25    "pending".to_string()
26}
27
28/// TodoWriteTool 参数
29#[derive(Deserialize, JsonSchema)]
30struct TodoWriteParams {
31    /// Array of todo items
32    todos: Vec<TodoItemParam>,
33    /// If false (default), replace the entire list. If true, only update/add the provided items by id.
34    #[serde(default)]
35    merge: bool,
36}
37
38/// 待办事项写入工具,用于创建和管理结构化待办列表
39#[derive(Debug)]
40pub struct TodoWriteTool {
41    /// 待办事项管理器实例
42    pub manager: Arc<TodoManager>,
43}
44
45impl TodoWriteTool {
46    pub const NAME: &'static str = "TodoWrite";
47}
48
49impl Tool for TodoWriteTool {
50    fn name(&self) -> &str {
51        Self::NAME
52    }
53
54    fn description(&self) -> Cow<'_, str> {
55        r#"
56        Create and manage a structured todo list to maintain state across long turns.
57
58        CRITICAL RULES:
59        1. Only ONE item can be 'in_progress' at any time; the system enforces this automatically.
60        2. For updates, always use 'merge=true' and only provide the specific items being modified.
61        3. Support batch updates: efficiently transition states by marking a task 'completed' and the next 'in_progress' in a single call.
62        4. Use this to demonstrate progress and ensure complex requirements are not missed.
63        "#.into()
64    }
65
66    fn parameters_schema(&self) -> Value {
67        schema_to_tool_params::<TodoWriteParams>()
68    }
69
70    fn execute(&self, arguments: &str, _cancelled: &Arc<AtomicBool>) -> ToolResult {
71        let params: TodoWriteParams = match parse_tool_args(arguments) {
72            Ok(p) => p,
73            Err(e) => return e,
74        };
75
76        if !params.merge {
77            let empty_indices: Vec<usize> = params
78                .todos
79                .iter()
80                .enumerate()
81                .filter(|(_, item)| item.content.is_empty())
82                .map(|(i, _)| i + 1)
83                .collect();
84            if !empty_indices.is_empty() {
85                return ToolResult {
86                    output: format!(
87                        "content is required for new items (merge=false). Missing in item(s): {}",
88                        empty_indices
89                            .iter()
90                            .map(|i| i.to_string())
91                            .collect::<Vec<_>>()
92                            .join(", ")
93                    ),
94                    is_error: true,
95                    images: vec![],
96                    plan_decision: PlanDecision::None,
97                };
98            }
99        }
100
101        let items: Vec<TodoItem> = params
102            .todos
103            .iter()
104            .map(|item| TodoItem {
105                id: item.id.clone().unwrap_or_default(),
106                content: item.content.clone(),
107                status: item.status.clone(),
108            })
109            .collect();
110
111        match self.manager.write_todos(items, params.merge) {
112            Ok(all_todos) => ToolResult {
113                output: serde_json::to_string_pretty(&all_todos).unwrap_or_default(),
114                is_error: false,
115                images: vec![],
116                plan_decision: PlanDecision::None,
117            },
118            Err(e) => ToolResult {
119                output: e,
120                is_error: true,
121                images: vec![],
122                plan_decision: PlanDecision::None,
123            },
124        }
125    }
126}