use crate::agent_tool::{Tool, ToolError, ToolOutput};
use crate::context::AgentContext;
use serde_json::Value;
pub struct ClarificationTool;
#[async_trait::async_trait]
impl Tool for ClarificationTool {
fn name(&self) -> &str {
"ask_user"
}
fn description(&self) -> &str {
"Ask the user a clarifying question when you need more information to proceed"
}
fn is_system(&self) -> bool {
true
}
fn parameters_schema(&self) -> Value {
serde_json::json!({
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The question to ask the user"
}
},
"required": ["question"]
})
}
async fn execute(&self, args: Value, _ctx: &mut AgentContext) -> Result<ToolOutput, ToolError> {
let question = args
.get("question")
.and_then(|v| v.as_str())
.unwrap_or("Could you provide more details?");
Ok(ToolOutput::waiting(question))
}
}
pub struct PlanTool;
#[async_trait::async_trait]
impl Tool for PlanTool {
fn name(&self) -> &str {
"submit_plan"
}
fn description(&self) -> &str {
"Submit your implementation plan after analyzing the codebase. Call when ready to present the plan."
}
fn is_system(&self) -> bool {
true
}
fn parameters_schema(&self) -> Value {
serde_json::json!({
"type": "object",
"properties": {
"summary": {
"type": "string",
"description": "Brief summary of what the plan achieves"
},
"steps": {
"type": "array",
"description": "Ordered list of implementation steps",
"items": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "What this step does"
},
"files": {
"type": "array",
"items": { "type": "string" },
"description": "Files to create or modify"
},
"tool_hints": {
"type": "array",
"items": { "type": "string" },
"description": "Tools likely needed for this step"
}
},
"required": ["description"]
}
}
},
"required": ["summary", "steps"]
})
}
async fn execute(&self, args: Value, ctx: &mut AgentContext) -> Result<ToolOutput, ToolError> {
let summary = args
.get("summary")
.and_then(|v| v.as_str())
.unwrap_or("Plan submitted")
.to_string();
ctx.set("plan", args);
Ok(ToolOutput::done(format!("Plan submitted: {summary}")))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn clarification_returns_waiting() {
let tool = ClarificationTool;
let mut ctx = AgentContext::new();
let args = serde_json::json!({"question": "Which database?"});
let output = tool.execute(args, &mut ctx).await.unwrap();
assert!(output.waiting);
assert!(!output.done);
assert_eq!(output.content, "Which database?");
}
#[tokio::test]
async fn clarification_default_question() {
let tool = ClarificationTool;
let mut ctx = AgentContext::new();
let output = tool.execute(serde_json::json!({}), &mut ctx).await.unwrap();
assert!(output.waiting);
assert!(output.content.contains("more details"));
}
#[tokio::test]
async fn plan_tool_stores_and_completes() {
let tool = PlanTool;
let mut ctx = AgentContext::new();
let args = serde_json::json!({
"summary": "Add auth",
"steps": [
{"description": "Create module", "files": ["src/auth.rs"]},
{"description": "Add tests"}
]
});
let output = tool.execute(args, &mut ctx).await.unwrap();
assert!(output.done);
assert!(output.content.contains("Add auth"));
let plan = ctx.get("plan").unwrap();
assert_eq!(plan["steps"].as_array().unwrap().len(), 2);
}
#[test]
fn clarification_is_system_tool() {
assert!(ClarificationTool.is_system());
}
#[test]
fn plan_is_system_tool() {
assert!(PlanTool.is_system());
}
}