Skip to main content

deepseek/agent/builtin_tools/
cron_delete.rs

1//! `CronDelete` tool — cancel a scheduled task by id.
2
3use std::sync::{Arc, Mutex};
4
5use async_trait::async_trait;
6use serde_json::{json, Value};
7
8use crate::agent::scheduler::{Scheduler, TaskId};
9use crate::agent::tool::{Tool, ToolDefinition};
10
11pub struct CronDeleteTool {
12    pub scheduler: Arc<Mutex<Scheduler>>,
13}
14
15impl CronDeleteTool {
16    pub fn new(scheduler: Arc<Mutex<Scheduler>>) -> Self {
17        Self { scheduler }
18    }
19}
20
21#[async_trait]
22impl Tool for CronDeleteTool {
23    fn name(&self) -> &str {
24        "CronDelete"
25    }
26
27    fn definition(&self) -> ToolDefinition {
28        ToolDefinition {
29            name: self.name().to_string(),
30            description: "Cancel a scheduled task by its 8-character `task_id` \
31                          (as returned by CronCreate or CronList)."
32                .into(),
33            parameters: json!({
34                "type": "object",
35                "properties": {
36                    "task_id": { "type": "string" }
37                },
38                "required": ["task_id"]
39            }),
40        }
41    }
42
43    async fn call_json(&self, args: Value) -> Result<String, String> {
44        let raw = args
45            .get("task_id")
46            .and_then(Value::as_str)
47            .ok_or_else(|| "CronDelete: missing string `task_id`".to_string())?;
48        let id = TaskId::from_raw(raw);
49
50        let mut sched = self
51            .scheduler
52            .lock()
53            .map_err(|_| "CronDelete: scheduler lock poisoned".to_string())?;
54
55        let deleted = sched.delete(&id);
56        Ok(serde_json::to_string(&json!({
57            "task_id": id.as_str(),
58            "deleted": deleted,
59        }))
60        .unwrap())
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use crate::agent::scheduler::{CronExpr, Schedule, Scheduler};
68    use serde_json::Value;
69
70    fn fresh_sched() -> Arc<Mutex<Scheduler>> {
71        std::env::remove_var("CLAUDE_CODE_DISABLE_CRON");
72        std::env::remove_var("DEEPSEEK_LOOP_DISABLE_CRON");
73        Arc::new(Mutex::new(Scheduler::new("cron-delete-test")))
74    }
75
76    #[test]
77    fn definition_marks_task_id_required() {
78        let tool = CronDeleteTool::new(fresh_sched());
79        let def = tool.definition();
80        assert_eq!(def.name, "CronDelete");
81        let required = def.parameters.get("required").unwrap().as_array().unwrap();
82        assert!(required.iter().any(|v| v == "task_id"));
83    }
84
85    #[tokio::test]
86    async fn delete_returns_false_for_unknown_id() {
87        let tool = CronDeleteTool::new(fresh_sched());
88        let raw = tool
89            .call_json(json!({"task_id": "ZZZZZZZZ"}))
90            .await
91            .unwrap();
92        let v: Value = serde_json::from_str(&raw).unwrap();
93        assert_eq!(v["deleted"].as_bool().unwrap(), false);
94        assert_eq!(v["task_id"].as_str().unwrap(), "ZZZZZZZZ");
95    }
96
97    #[tokio::test]
98    async fn delete_returns_true_then_false_on_repeat() {
99        let sched = fresh_sched();
100        let id = {
101            let mut s = sched.lock().unwrap();
102            let cron = CronExpr::parse("*/5 * * * *").unwrap();
103            s.create(Schedule::Cron(Box::new(cron)), "x", true).unwrap()
104        };
105        let tool = CronDeleteTool::new(sched);
106        let first: Value = serde_json::from_str(
107            &tool
108                .call_json(json!({"task_id": id.as_str()}))
109                .await
110                .unwrap(),
111        )
112        .unwrap();
113        assert_eq!(first["deleted"].as_bool().unwrap(), true);
114
115        let second: Value = serde_json::from_str(
116            &tool
117                .call_json(json!({"task_id": id.as_str()}))
118                .await
119                .unwrap(),
120        )
121        .unwrap();
122        assert_eq!(second["deleted"].as_bool().unwrap(), false);
123    }
124
125    #[tokio::test]
126    async fn missing_task_id_returns_error() {
127        let tool = CronDeleteTool::new(fresh_sched());
128        let err = tool.call_json(json!({})).await.unwrap_err();
129        assert!(err.contains("missing"), "got: {err}");
130    }
131
132    #[tokio::test]
133    async fn non_string_task_id_returns_error() {
134        let tool = CronDeleteTool::new(fresh_sched());
135        let err = tool.call_json(json!({"task_id": 12345})).await.unwrap_err();
136        assert!(err.contains("missing"), "got: {err}");
137    }
138}