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