agent_code_lib/tools/
tasks.rs1use async_trait::async_trait;
8use serde_json::json;
9use std::sync::atomic::{AtomicU64, Ordering};
10
11use super::{Tool, ToolContext, ToolResult};
12use crate::error::ToolError;
13
14static TASK_COUNTER: AtomicU64 = AtomicU64::new(1);
15
16pub struct TaskCreateTool;
17
18#[async_trait]
19impl Tool for TaskCreateTool {
20 fn name(&self) -> &'static str {
21 "TaskCreate"
22 }
23
24 fn description(&self) -> &'static str {
25 "Create a task to track progress on a piece of work."
26 }
27
28 fn input_schema(&self) -> serde_json::Value {
29 json!({
30 "type": "object",
31 "required": ["description"],
32 "properties": {
33 "description": {
34 "type": "string",
35 "description": "What needs to be done"
36 },
37 "status": {
38 "type": "string",
39 "enum": ["pending", "in_progress", "completed"],
40 "default": "pending"
41 }
42 }
43 })
44 }
45
46 fn is_read_only(&self) -> bool {
47 true
48 }
49
50 fn is_concurrency_safe(&self) -> bool {
51 true
52 }
53
54 async fn call(
55 &self,
56 input: serde_json::Value,
57 _ctx: &ToolContext,
58 ) -> Result<ToolResult, ToolError> {
59 let description = input
60 .get("description")
61 .and_then(|v| v.as_str())
62 .ok_or_else(|| ToolError::InvalidInput("'description' is required".into()))?;
63
64 let status = input
65 .get("status")
66 .and_then(|v| v.as_str())
67 .unwrap_or("pending");
68
69 let id = TASK_COUNTER.fetch_add(1, Ordering::Relaxed);
70
71 Ok(ToolResult::success(format!(
72 "Task #{id} created: {description} [{status}]"
73 )))
74 }
75}
76
77pub struct TaskUpdateTool;
78
79#[async_trait]
80impl Tool for TaskUpdateTool {
81 fn name(&self) -> &'static str {
82 "TaskUpdate"
83 }
84
85 fn description(&self) -> &'static str {
86 "Update a task's status (pending, in_progress, completed)."
87 }
88
89 fn input_schema(&self) -> serde_json::Value {
90 json!({
91 "type": "object",
92 "required": ["id", "status"],
93 "properties": {
94 "id": { "type": "string", "description": "Task ID" },
95 "status": {
96 "type": "string",
97 "enum": ["pending", "in_progress", "completed"]
98 }
99 }
100 })
101 }
102
103 fn is_read_only(&self) -> bool {
104 true
105 }
106
107 fn is_concurrency_safe(&self) -> bool {
108 true
109 }
110
111 async fn call(
112 &self,
113 input: serde_json::Value,
114 _ctx: &ToolContext,
115 ) -> Result<ToolResult, ToolError> {
116 let id = input
117 .get("id")
118 .and_then(|v| v.as_str())
119 .ok_or_else(|| ToolError::InvalidInput("'id' is required".into()))?;
120
121 let status = input
122 .get("status")
123 .and_then(|v| v.as_str())
124 .ok_or_else(|| ToolError::InvalidInput("'status' is required".into()))?;
125
126 Ok(ToolResult::success(format!(
127 "Task #{id} updated to [{status}]"
128 )))
129 }
130}
131
132pub struct TaskGetTool;
133
134#[async_trait]
135impl Tool for TaskGetTool {
136 fn name(&self) -> &'static str {
137 "TaskGet"
138 }
139
140 fn description(&self) -> &'static str {
141 "Get details about a specific task by ID."
142 }
143
144 fn input_schema(&self) -> serde_json::Value {
145 json!({
146 "type": "object",
147 "required": ["id"],
148 "properties": {
149 "id": { "type": "string", "description": "Task ID to look up" }
150 }
151 })
152 }
153
154 fn is_read_only(&self) -> bool {
155 true
156 }
157
158 fn is_concurrency_safe(&self) -> bool {
159 true
160 }
161
162 async fn call(
163 &self,
164 input: serde_json::Value,
165 _ctx: &ToolContext,
166 ) -> Result<ToolResult, ToolError> {
167 let id = input
168 .get("id")
169 .and_then(|v| v.as_str())
170 .ok_or_else(|| ToolError::InvalidInput("'id' is required".into()))?;
171
172 Ok(ToolResult::success(format!(
174 "Task #{id}: status and details would be shown here. \
175 Use the background task manager for active task tracking."
176 )))
177 }
178}
179
180pub struct TaskListTool;
181
182#[async_trait]
183impl Tool for TaskListTool {
184 fn name(&self) -> &'static str {
185 "TaskList"
186 }
187
188 fn description(&self) -> &'static str {
189 "List all tasks in the current session."
190 }
191
192 fn input_schema(&self) -> serde_json::Value {
193 json!({
194 "type": "object",
195 "properties": {}
196 })
197 }
198
199 fn is_read_only(&self) -> bool {
200 true
201 }
202
203 fn is_concurrency_safe(&self) -> bool {
204 true
205 }
206
207 async fn call(
208 &self,
209 _input: serde_json::Value,
210 _ctx: &ToolContext,
211 ) -> Result<ToolResult, ToolError> {
212 let count = TASK_COUNTER.load(Ordering::Relaxed) - 1;
213 Ok(ToolResult::success(format!(
214 "{count} task(s) created this session."
215 )))
216 }
217}
218
219pub struct TaskStopTool;
220
221#[async_trait]
222impl Tool for TaskStopTool {
223 fn name(&self) -> &'static str {
224 "TaskStop"
225 }
226
227 fn description(&self) -> &'static str {
228 "Stop a running background task."
229 }
230
231 fn input_schema(&self) -> serde_json::Value {
232 json!({
233 "type": "object",
234 "required": ["id"],
235 "properties": {
236 "id": { "type": "string", "description": "Task ID to stop" }
237 }
238 })
239 }
240
241 fn is_read_only(&self) -> bool {
242 false
243 }
244
245 fn is_concurrency_safe(&self) -> bool {
246 true
247 }
248
249 async fn call(
250 &self,
251 input: serde_json::Value,
252 _ctx: &ToolContext,
253 ) -> Result<ToolResult, ToolError> {
254 let id = input
255 .get("id")
256 .and_then(|v| v.as_str())
257 .ok_or_else(|| ToolError::InvalidInput("'id' is required".into()))?;
258
259 Ok(ToolResult::success(format!("Task #{id} stop requested.")))
260 }
261}
262
263pub struct TaskOutputTool;
264
265#[async_trait]
266impl Tool for TaskOutputTool {
267 fn name(&self) -> &'static str {
268 "TaskOutput"
269 }
270
271 fn description(&self) -> &'static str {
272 "Read the output of a completed background task."
273 }
274
275 fn input_schema(&self) -> serde_json::Value {
276 json!({
277 "type": "object",
278 "required": ["id"],
279 "properties": {
280 "id": { "type": "string", "description": "Task ID to read output from" }
281 }
282 })
283 }
284
285 fn is_read_only(&self) -> bool {
286 true
287 }
288
289 fn is_concurrency_safe(&self) -> bool {
290 true
291 }
292
293 async fn call(
294 &self,
295 input: serde_json::Value,
296 _ctx: &ToolContext,
297 ) -> Result<ToolResult, ToolError> {
298 let id = input
299 .get("id")
300 .and_then(|v| v.as_str())
301 .ok_or_else(|| ToolError::InvalidInput("'id' is required".into()))?;
302
303 let output_path = dirs::cache_dir()
305 .unwrap_or_else(|| std::path::PathBuf::from("/tmp"))
306 .join("agent-code")
307 .join("tasks")
308 .join(format!("{id}.out"));
309
310 if output_path.exists() {
311 let content = std::fs::read_to_string(&output_path)
312 .map_err(|e| ToolError::ExecutionFailed(format!("Read failed: {e}")))?;
313 Ok(ToolResult::success(content))
314 } else {
315 Ok(ToolResult::success(format!(
316 "No output file found for task #{id}. It may still be running."
317 )))
318 }
319 }
320}