use anyhow::Result;
use async_trait::async_trait;
use serde_json::{Value, json};
use super::{Tool, ToolDefinition};
use crate::approval::RiskLevel;
pub struct TodoWriteTool;
#[async_trait]
impl Tool for TodoWriteTool {
fn definition(&self) -> ToolDefinition {
ToolDefinition {
name: "todo_write".to_string(),
description:
"Maintain a structured todo list to plan and track multi-step work. \
Each call REPLACES the full list. Use for non-trivial tasks (3+ \
steps) to show progress. Keep exactly one task 'in_progress' at \
a time and mark tasks 'completed' as soon as they finish."
.to_string(),
parameters: json!({
"type": "object",
"properties": {
"todos": {
"type": "array",
"description": "Full replacement list of todo items",
"items": {
"type": "object",
"properties": {
"content": {
"type": "string",
"description": "Imperative form (e.g. 'Run tests')"
},
"activeForm": {
"type": "string",
"description": "Present-continuous form (e.g. 'Running tests')"
},
"status": {
"type": "string",
"enum": ["pending", "in_progress", "completed"]
}
},
"required": ["content", "activeForm", "status"]
}
}
},
"required": ["todos"]
}),
}
}
async fn execute(&self, params: Value) -> Result<String> {
let todos = params["todos"]
.as_array()
.ok_or_else(|| anyhow::anyhow!("missing 'todos' array"))?;
let mut in_progress_count = 0;
let _completed_count = 0;
let _pending_count = 0;
let mut lines = Vec::with_capacity(todos.len() + 1);
for (i, todo) in todos.iter().enumerate() {
let content = todo["content"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("todo {}: missing 'content'", i))?;
let active = todo["activeForm"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("todo {}: missing 'activeForm'", i))?;
let status = todo["status"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("todo {}: missing 'status'", i))?;
let (marker, display) = match status {
"pending" => ("[ ]", content),
"in_progress" => {
in_progress_count += 1;
("[~]", active)
}
"completed" => ("[x]", content),
other => anyhow::bail!("todo {}: invalid status '{}'", i, other),
};
lines.push(format!(" {} {}", marker, display));
}
if in_progress_count > 1 {
anyhow::bail!(
"at most one task may be 'in_progress' at a time (found {})",
in_progress_count
);
}
if lines.is_empty() {
return Ok("Todo list cleared.".to_string());
}
let header = format!("Todos ({}):", lines.len());
let mut out = String::with_capacity(header.len() + lines.iter().map(|l| l.len() + 1).sum::<usize>());
out.push_str(&header);
for l in &lines {
out.push('\n');
out.push_str(l);
}
Ok(out)
}
fn risk_level(&self) -> RiskLevel {
RiskLevel::Mutating
}
}