Skip to main content

codetether_agent/tool/
question.rs

1//! Question Tool - Ask the user a question and wait for response.
2
3use anyhow::{Context, Result};
4use async_trait::async_trait;
5use serde::Deserialize;
6use serde_json::{json, Value};
7use super::{Tool, ToolResult};
8use std::io::{self, Write};
9
10pub struct QuestionTool;
11
12impl Default for QuestionTool {
13    fn default() -> Self { Self::new() }
14}
15
16impl QuestionTool {
17    pub fn new() -> Self { Self }
18}
19
20#[derive(Deserialize)]
21struct Params {
22    question: String,
23    #[serde(default)]
24    options: Vec<String>,
25    #[serde(default)]
26    default: Option<String>,
27}
28
29#[async_trait]
30impl Tool for QuestionTool {
31    fn id(&self) -> &str { "question" }
32    fn name(&self) -> &str { "Ask Question" }
33    fn description(&self) -> &str { "Ask the user a question and wait for their response. Use for clarification or user input." }
34    fn parameters(&self) -> Value {
35        json!({
36            "type": "object",
37            "properties": {
38                "question": {"type": "string", "description": "Question to ask the user"},
39                "options": {"type": "array", "items": {"type": "string"}, "description": "Optional list of valid responses"},
40                "default": {"type": "string", "description": "Default value if user presses Enter"}
41            },
42            "required": ["question"]
43        })
44    }
45
46    async fn execute(&self, params: Value) -> Result<ToolResult> {
47        let p: Params = serde_json::from_value(params).context("Invalid params")?;
48        
49        // Print question
50        print!("\nšŸ“ {}", p.question);
51        
52        if !p.options.is_empty() {
53            print!(" [{}]", p.options.join("/"));
54        }
55        
56        if let Some(ref default) = p.default {
57            print!(" (default: {})", default);
58        }
59        
60        print!(": ");
61        io::stdout().flush()?;
62        
63        // Read response
64        let mut input = String::new();
65        io::stdin().read_line(&mut input)?;
66        let response = input.trim().to_string();
67        
68        let final_response = if response.is_empty() {
69            p.default.unwrap_or_default()
70        } else {
71            response
72        };
73        
74        // Validate against options if provided
75        if !p.options.is_empty() && !p.options.contains(&final_response) {
76            return Ok(ToolResult::error(format!(
77                "Invalid response '{}'. Valid options: {}", 
78                final_response, 
79                p.options.join(", ")
80            )));
81        }
82        
83        Ok(ToolResult::success(final_response.clone())
84            .with_metadata("response", json!(final_response)))
85    }
86}