Skip to main content

codetether_agent/tool/
task.rs

1//! Task Tool - Spawn sub-tasks for parallel or sequential execution.
2
3use super::{Tool, ToolResult};
4use anyhow::{Context, Result};
5use async_trait::async_trait;
6use parking_lot::RwLock;
7use serde::Deserialize;
8use serde_json::{Value, json};
9use std::collections::HashMap;
10use std::sync::atomic::{AtomicUsize, Ordering};
11
12static TASK_COUNTER: AtomicUsize = AtomicUsize::new(1);
13
14lazy_static::lazy_static! {
15    static ref TASK_STORE: RwLock<HashMap<String, TaskInfo>> = RwLock::new(HashMap::new());
16}
17
18#[derive(Debug, Clone)]
19struct TaskInfo {
20    id: String,
21    description: String,
22    status: TaskStatus,
23    result: Option<String>,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq)]
27enum TaskStatus {
28    Pending,
29    #[allow(dead_code)]
30    Running,
31    Complete,
32    Failed,
33}
34
35pub struct TaskTool;
36
37impl Default for TaskTool {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl TaskTool {
44    pub fn new() -> Self {
45        Self
46    }
47}
48
49#[derive(Deserialize)]
50struct Params {
51    action: String, // create, status, complete, list, cancel
52    #[serde(default)]
53    id: Option<String>,
54    #[serde(default)]
55    description: Option<String>,
56    #[serde(default)]
57    result: Option<String>,
58}
59
60#[async_trait]
61impl Tool for TaskTool {
62    fn id(&self) -> &str {
63        "task"
64    }
65    fn name(&self) -> &str {
66        "Task Manager"
67    }
68    fn description(&self) -> &str {
69        "Manage sub-tasks: create, query status, complete, or list tasks for tracking complex workflows."
70    }
71    fn parameters(&self) -> Value {
72        json!({
73            "type": "object",
74            "properties": {
75                "action": {
76                    "type": "string",
77                    "enum": ["create", "status", "complete", "list", "cancel"],
78                    "description": "Action to perform"
79                },
80                "id": {"type": "string", "description": "Task ID (for status/complete/cancel)"},
81                "description": {"type": "string", "description": "Task description (for create)"},
82                "result": {"type": "string", "description": "Task result (for complete)"}
83            },
84            "required": ["action"]
85        })
86    }
87
88    async fn execute(&self, params: Value) -> Result<ToolResult> {
89        let p: Params = serde_json::from_value(params).context("Invalid params")?;
90
91        match p.action.as_str() {
92            "create" => {
93                let description = p
94                    .description
95                    .ok_or_else(|| anyhow::anyhow!("description required"))?;
96                let id = format!("task_{}", TASK_COUNTER.fetch_add(1, Ordering::SeqCst));
97
98                let task = TaskInfo {
99                    id: id.clone(),
100                    description: description.clone(),
101                    status: TaskStatus::Pending,
102                    result: None,
103                };
104
105                TASK_STORE.write().insert(id.clone(), task);
106                Ok(
107                    ToolResult::success(format!("Created task: {} - {}", id, description))
108                        .with_metadata("task_id", json!(id)),
109                )
110            }
111            "status" => {
112                let id = p.id.ok_or_else(|| anyhow::anyhow!("id required"))?;
113                let store = TASK_STORE.read();
114
115                match store.get(&id) {
116                    Some(task) => {
117                        let status_str = match task.status {
118                            TaskStatus::Pending => "pending",
119                            TaskStatus::Running => "running",
120                            TaskStatus::Complete => "complete",
121                            TaskStatus::Failed => "failed",
122                        };
123                        Ok(ToolResult::success(format!(
124                            "Task {} [{}]: {}\n{}",
125                            task.id,
126                            status_str,
127                            task.description,
128                            task.result.as_deref().unwrap_or("")
129                        )))
130                    }
131                    None => Ok(ToolResult::error(format!("Task not found: {}", id))),
132                }
133            }
134            "complete" => {
135                let id = p.id.ok_or_else(|| anyhow::anyhow!("id required"))?;
136                let result = p.result.unwrap_or_else(|| "Completed".to_string());
137
138                let mut store = TASK_STORE.write();
139                match store.get_mut(&id) {
140                    Some(task) => {
141                        task.status = TaskStatus::Complete;
142                        task.result = Some(result.clone());
143                        Ok(ToolResult::success(format!("Completed task: {}", id)))
144                    }
145                    None => Ok(ToolResult::error(format!("Task not found: {}", id))),
146                }
147            }
148            "cancel" => {
149                let id = p.id.ok_or_else(|| anyhow::anyhow!("id required"))?;
150
151                let mut store = TASK_STORE.write();
152                match store.get_mut(&id) {
153                    Some(task) => {
154                        task.status = TaskStatus::Failed;
155                        task.result = Some("Cancelled".to_string());
156                        Ok(ToolResult::success(format!("Cancelled task: {}", id)))
157                    }
158                    None => Ok(ToolResult::error(format!("Task not found: {}", id))),
159                }
160            }
161            "list" => {
162                let store = TASK_STORE.read();
163                if store.is_empty() {
164                    return Ok(ToolResult::success("No active tasks".to_string()));
165                }
166
167                let output = store
168                    .values()
169                    .map(|t| {
170                        let icon = match t.status {
171                            TaskStatus::Pending => "○",
172                            TaskStatus::Running => "◐",
173                            TaskStatus::Complete => "●",
174                            TaskStatus::Failed => "✗",
175                        };
176                        format!("{} {} - {}", icon, t.id, t.description)
177                    })
178                    .collect::<Vec<_>>()
179                    .join("\n");
180
181                Ok(ToolResult::success(output).with_metadata("count", json!(store.len())))
182            }
183            _ => Ok(ToolResult::error(format!("Unknown action: {}", p.action))),
184        }
185    }
186}