Skip to main content

agent_code_lib/tools/
ask_user.rs

1//! AskUserQuestion tool: interactive prompts during execution.
2//!
3//! Allows the agent to ask the user questions and collect structured
4//! responses. Used for gathering preferences, clarifying ambiguity,
5//! and making implementation decisions.
6
7use async_trait::async_trait;
8use serde_json::json;
9use std::io::Write;
10
11use super::{Tool, ToolContext, ToolResult};
12use crate::error::ToolError;
13
14pub struct AskUserQuestionTool;
15
16#[async_trait]
17impl Tool for AskUserQuestionTool {
18    fn name(&self) -> &'static str {
19        "AskUserQuestion"
20    }
21
22    fn description(&self) -> &'static str {
23        "Ask the user a question to gather preferences or clarify requirements. \
24         Present 2-4 options for the user to choose from."
25    }
26
27    fn input_schema(&self) -> serde_json::Value {
28        json!({
29            "type": "object",
30            "required": ["questions"],
31            "properties": {
32                "questions": {
33                    "type": "array",
34                    "description": "Questions to ask (1-4)",
35                    "items": {
36                        "type": "object",
37                        "required": ["question", "options"],
38                        "properties": {
39                            "question": {
40                                "type": "string",
41                                "description": "The question to ask"
42                            },
43                            "options": {
44                                "type": "array",
45                                "description": "Available choices (2-4)",
46                                "items": {
47                                    "type": "object",
48                                    "required": ["label", "description"],
49                                    "properties": {
50                                        "label": {
51                                            "type": "string",
52                                            "description": "Short choice label"
53                                        },
54                                        "description": {
55                                            "type": "string",
56                                            "description": "What this choice means"
57                                        }
58                                    }
59                                }
60                            }
61                        }
62                    }
63                }
64            }
65        })
66    }
67
68    fn is_read_only(&self) -> bool {
69        true
70    }
71
72    fn is_concurrency_safe(&self) -> bool {
73        false // Requires user interaction.
74    }
75
76    async fn call(
77        &self,
78        input: serde_json::Value,
79        _ctx: &ToolContext,
80    ) -> Result<ToolResult, ToolError> {
81        let questions = input
82            .get("questions")
83            .and_then(|v| v.as_array())
84            .ok_or_else(|| ToolError::InvalidInput("'questions' array is required".into()))?;
85
86        let mut answers = Vec::new();
87
88        for q in questions {
89            let question_text = q.get("question").and_then(|v| v.as_str()).unwrap_or("?");
90
91            let options = q
92                .get("options")
93                .and_then(|v| v.as_array())
94                .ok_or_else(|| ToolError::InvalidInput("'options' array required".into()))?;
95
96            // Display the question.
97            eprintln!("\n{question_text}");
98            for (i, opt) in options.iter().enumerate() {
99                let label = opt.get("label").and_then(|v| v.as_str()).unwrap_or("?");
100                let desc = opt
101                    .get("description")
102                    .and_then(|v| v.as_str())
103                    .unwrap_or("");
104                let letter = (b'A' + i as u8) as char;
105                eprintln!("  {letter}) {label} — {desc}");
106            }
107            eprint!("Choice: ");
108            let _ = std::io::stderr().flush();
109
110            // Read user input from stdin.
111            let mut line = String::new();
112            std::io::stdin()
113                .read_line(&mut line)
114                .map_err(|e| ToolError::ExecutionFailed(format!("Failed to read input: {e}")))?;
115
116            let choice = line.trim().to_uppercase();
117
118            // Map letter to option label.
119            let selected = if choice.len() == 1 {
120                let idx = choice.as_bytes()[0].wrapping_sub(b'A') as usize;
121                if idx < options.len() {
122                    options[idx]
123                        .get("label")
124                        .and_then(|v| v.as_str())
125                        .unwrap_or(&choice)
126                        .to_string()
127                } else {
128                    choice
129                }
130            } else {
131                choice
132            };
133
134            answers.push(format!("{question_text}={selected}"));
135        }
136
137        let result = format!(
138            "User has answered your questions: {}. You can now continue with the user's answers in mind.",
139            answers
140                .iter()
141                .map(|a| format!("\"{a}\""))
142                .collect::<Vec<_>>()
143                .join(", ")
144        );
145
146        Ok(ToolResult::success(result))
147    }
148}