use async_trait::async_trait;
use serde_json::json;
use super::{Tool, ToolContext, ToolResult};
use crate::error::ToolError;
pub struct EnterPlanModeTool;
#[async_trait]
impl Tool for EnterPlanModeTool {
fn name(&self) -> &'static str {
"EnterPlanMode"
}
fn description(&self) -> &'static str {
"Switch to plan mode for safe exploration before making changes."
}
fn prompt(&self) -> String {
"Use this tool when you need to plan an approach before making changes. \
In plan mode, only read-only tools are available (FileRead, Grep, Glob, Bash). \
Write tools are blocked until ExitPlanMode is called.\n\n\
When to enter plan mode:\n\
- Complex tasks requiring multiple file changes\n\
- Unclear requirements that need investigation first\n\
- Multiple possible approaches to evaluate\n\
- Large refactors where the plan should be reviewed\n\
- When the user asks to \"plan\", \"think through\", or \"design\"\n\n\
You should write your plan to a file before exiting plan mode."
.to_string()
}
fn input_schema(&self) -> serde_json::Value {
json!({
"type": "object",
"properties": {}
})
}
fn is_read_only(&self) -> bool {
true
}
fn is_concurrency_safe(&self) -> bool {
true
}
async fn call(
&self,
_input: serde_json::Value,
_ctx: &ToolContext,
) -> Result<ToolResult, ToolError> {
let plan_dir = dirs::data_dir()
.unwrap_or_else(|| std::path::PathBuf::from("."))
.join("agent-code")
.join("plans");
let _ = std::fs::create_dir_all(&plan_dir);
let slug = generate_slug();
let plan_path = plan_dir.join(format!("{slug}.md"));
let template = format!(
"# Plan\n\n\
Created: {}\n\n\
## Goal\n\n\
(describe what needs to be accomplished)\n\n\
## Approach\n\n\
(outline the steps)\n\n\
## Files to modify\n\n\
(list files and what changes each needs)\n\n\
## Risks / open questions\n\n\
(anything uncertain)\n",
chrono::Utc::now().format("%Y-%m-%d %H:%M UTC"),
);
let _ = std::fs::write(&plan_path, &template);
Ok(ToolResult::success(format!(
"Entered plan mode. Only read-only tools are available.\n\
Plan file created: {}\n\
Write your plan to this file, then call ExitPlanMode when ready.",
plan_path.display()
)))
}
}
pub struct ExitPlanModeTool;
#[async_trait]
impl Tool for ExitPlanModeTool {
fn name(&self) -> &'static str {
"ExitPlanMode"
}
fn description(&self) -> &'static str {
"Exit plan mode and re-enable all tools for execution. \
Call this after your plan is complete and ready to implement."
}
fn input_schema(&self) -> serde_json::Value {
json!({
"type": "object",
"properties": {}
})
}
fn is_read_only(&self) -> bool {
true
}
fn is_concurrency_safe(&self) -> bool {
true
}
async fn call(
&self,
_input: serde_json::Value,
_ctx: &ToolContext,
) -> Result<ToolResult, ToolError> {
Ok(ToolResult::success(
"Exited plan mode. All tools are now available for execution.",
))
}
}
fn generate_slug() -> String {
let adjectives = [
"brave", "calm", "dark", "eager", "fair", "golden", "hidden", "iron", "jade", "keen",
"light", "mystic", "noble", "ocean", "proud", "quick", "rapid", "silent", "true", "vivid",
];
let nouns = [
"anchor", "beacon", "cedar", "dawn", "ember", "falcon", "grove", "harbor", "island",
"jewel", "kernel", "lantern", "meadow", "nexus", "orbit", "peak", "quill", "river",
"spark", "tower",
];
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos();
let adj = adjectives[(now as usize) % adjectives.len()];
let noun = nouns[((now as usize) / adjectives.len()) % nouns.len()];
format!("{adj}-{noun}")
}