j_agent/tools/todo/
todo_write_tool.rs1use 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#[derive(Deserialize, JsonSchema)]
12struct TodoItemParam {
13 #[serde(default)]
15 id: Option<String>,
16 #[serde(default)]
18 content: String,
19 #[serde(default = "default_status")]
21 status: String,
22}
23
24fn default_status() -> String {
25 "pending".to_string()
26}
27
28#[derive(Deserialize, JsonSchema)]
30struct TodoWriteParams {
31 todos: Vec<TodoItemParam>,
33 #[serde(default)]
35 merge: bool,
36}
37
38#[derive(Debug)]
40pub struct TodoWriteTool {
41 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}