opendev_tools_impl/
ask_user.rs1use std::collections::HashMap;
4
5use opendev_runtime::{AskUserRequest, AskUserSender};
6use opendev_tools_core::{BaseTool, ToolContext, ToolResult};
7
8#[derive(Debug)]
14pub struct AskUserTool {
15 ask_tx: Option<AskUserSender>,
17}
18
19impl AskUserTool {
20 pub fn new() -> Self {
22 Self { ask_tx: None }
23 }
24
25 pub fn with_ask_tx(mut self, tx: AskUserSender) -> Self {
27 self.ask_tx = Some(tx);
28 self
29 }
30}
31
32impl Default for AskUserTool {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38#[async_trait::async_trait]
39impl BaseTool for AskUserTool {
40 fn name(&self) -> &str {
41 "ask_user"
42 }
43
44 fn description(&self) -> &str {
45 "Ask the user a question and wait for their response. Use when clarification is needed."
46 }
47
48 fn parameter_schema(&self) -> serde_json::Value {
49 serde_json::json!({
50 "type": "object",
51 "properties": {
52 "question": {
53 "type": "string",
54 "description": "The question to ask the user"
55 },
56 "options": {
57 "type": "array",
58 "items": { "type": "string" },
59 "description": "Optional list of choices for the user"
60 },
61 "default": {
62 "type": "string",
63 "description": "Default answer if user provides none"
64 }
65 },
66 "required": ["question"]
67 })
68 }
69
70 async fn execute(
71 &self,
72 args: HashMap<String, serde_json::Value>,
73 _ctx: &ToolContext,
74 ) -> ToolResult {
75 let question = match args.get("question").and_then(|v| v.as_str()) {
76 Some(q) => q,
77 None => return ToolResult::fail("question is required"),
78 };
79
80 let options: Vec<String> = args
81 .get("options")
82 .and_then(|v| v.as_array())
83 .map(|arr| {
84 arr.iter()
85 .filter_map(|v| v.as_str().map(String::from))
86 .collect()
87 })
88 .unwrap_or_default();
89
90 let default = args
91 .get("default")
92 .and_then(|v| v.as_str())
93 .map(String::from);
94
95 if let Some(ref tx) = self.ask_tx {
97 let (resp_tx, resp_rx) = tokio::sync::oneshot::channel();
98 if tx
99 .send(AskUserRequest {
100 question: question.to_string(),
101 options: options.clone(),
102 default: default.clone(),
103 response_tx: resp_tx,
104 })
105 .is_ok()
106 {
107 match resp_rx.await {
108 Ok(answer) => {
109 return ToolResult::ok(format!("User answered: {answer}"));
110 }
111 Err(_) => {
112 }
114 }
115 }
116 }
117
118 let mut output = format!("Question: {question}");
120 if !options.is_empty() {
121 output.push_str("\nOptions:");
122 for (i, opt) in options.iter().enumerate() {
123 output.push_str(&format!("\n {}. {opt}", i + 1));
124 }
125 }
126 if let Some(d) = &default {
127 output.push_str(&format!("\nDefault: {d}"));
128 }
129
130 let mut metadata = HashMap::new();
131 metadata.insert("requires_input".into(), serde_json::json!(true));
132 metadata.insert("question".into(), serde_json::json!(question));
133 if !options.is_empty() {
134 metadata.insert("options".into(), serde_json::json!(options));
135 }
136 if let Some(d) = &default {
137 metadata.insert("default".into(), serde_json::json!(d));
138 }
139
140 ToolResult::ok_with_metadata(output, metadata)
141 }
142}
143
144#[cfg(test)]
145#[path = "ask_user_tests.rs"]
146mod tests;