codetether_agent/tool/
task.rs1use 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, #[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}