Skip to main content

bamboo_tools/tools/
enter_plan_mode.rs

1use async_trait::async_trait;
2use bamboo_agent_core::{Tool, ToolError, ToolResult};
3use serde::Deserialize;
4use serde_json::json;
5
6#[derive(Debug, Deserialize)]
7struct EnterPlanModeArgs {
8    #[serde(default)]
9    reason: Option<String>,
10}
11
12pub struct EnterPlanModeTool;
13
14impl EnterPlanModeTool {
15    pub fn new() -> Self {
16        Self
17    }
18}
19
20impl Default for EnterPlanModeTool {
21    fn default() -> Self {
22        Self::new()
23    }
24}
25
26#[async_trait]
27impl Tool for EnterPlanModeTool {
28    fn name(&self) -> &str {
29        "EnterPlanMode"
30    }
31
32    fn description(&self) -> &str {
33        "Switch to plan mode for complex tasks requiring exploration and design before implementation"
34    }
35
36    fn parameters_schema(&self) -> serde_json::Value {
37        json!({
38            "type": "object",
39            "properties": {
40                "reason": {
41                    "type": "string",
42                    "description": "Optional reason for entering plan mode"
43                }
44            },
45            "additionalProperties": false
46        })
47    }
48
49    async fn execute(&self, args: serde_json::Value) -> Result<ToolResult, ToolError> {
50        let parsed: EnterPlanModeArgs = serde_json::from_value(args).map_err(|e| {
51            ToolError::InvalidArguments(format!("Invalid EnterPlanMode args: {}", e))
52        })?;
53
54        let question = if let Some(ref reason) = parsed.reason {
55            format!(
56                "Enter plan mode? The assistant wants to switch to read-only exploration to design an approach before any changes. Reason: {}",
57                reason
58            )
59        } else {
60            "Enter plan mode? The assistant will switch to read-only exploration to design an approach before any changes.".to_string()
61        };
62
63        let payload = json!({
64            "status": "awaiting_user_input",
65            "question": question,
66            "options": ["Enter plan mode", "Stay in normal mode"],
67            "allow_custom": false,
68        });
69
70        Ok(ToolResult {
71            success: true,
72            result: payload.to_string(),
73            display_preference: Some("conclusion_with_options".to_string()),
74            images: Vec::new(),
75        })
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use serde_json::json;
83
84    #[test]
85    fn enter_plan_mode_has_correct_name() {
86        let tool = EnterPlanModeTool::new();
87        assert_eq!(tool.name(), "EnterPlanMode");
88    }
89
90    #[test]
91    fn enter_plan_mode_has_description() {
92        let tool = EnterPlanModeTool::new();
93        assert!(!tool.description().is_empty());
94        assert!(tool.description().contains("plan"));
95    }
96
97    #[tokio::test]
98    async fn enter_plan_mode_returns_conclusion_with_options() {
99        let tool = EnterPlanModeTool::new();
100        let result = tool.execute(json!({})).await.unwrap();
101
102        assert!(result.success);
103        assert_eq!(
104            result.display_preference,
105            Some("conclusion_with_options".to_string())
106        );
107
108        let payload: serde_json::Value = serde_json::from_str(&result.result).unwrap();
109        assert_eq!(payload["status"], "awaiting_user_input");
110        assert!(payload["question"]
111            .as_str()
112            .unwrap()
113            .contains("Enter plan mode"));
114        let options = payload["options"].as_array().unwrap();
115        assert_eq!(options.len(), 2);
116        assert!(options.contains(&json!("Enter plan mode")));
117        assert!(options.contains(&json!("Stay in normal mode")));
118        assert_eq!(payload["allow_custom"], false);
119    }
120
121    #[tokio::test]
122    async fn enter_plan_mode_includes_reason() {
123        let tool = EnterPlanModeTool::new();
124        let result = tool
125            .execute(json!({
126                "reason": "This is a complex refactor"
127            }))
128            .await
129            .unwrap();
130
131        assert!(result.success);
132        let payload: serde_json::Value = serde_json::from_str(&result.result).unwrap();
133        let question = payload["question"].as_str().unwrap();
134        assert!(question.contains("This is a complex refactor"));
135    }
136
137    #[tokio::test]
138    async fn enter_plan_mode_accepts_empty_args() {
139        let tool = EnterPlanModeTool::new();
140        let result = tool.execute(json!({})).await.unwrap();
141        assert!(result.success);
142    }
143}