matrixcode_core/tools/
ask.rs1use anyhow::Result;
7use async_trait::async_trait;
8use serde_json::{Value, json};
9use std::io::{self, BufRead, Write as _};
10
11use super::{Tool, ToolDefinition};
12use crate::approval::RiskLevel;
13
14pub struct AskTool;
19
20const ASK_TOOL_DESCRIPTION: &str =
21 "Ask the user a question when you are uncertain or there are multiple options. \
22 ALWAYS use this tool when: (1) the user's request is ambiguous, \
23 (2) there are multiple viable approaches and you need to choose one, \
24 (3) a decision could significantly impact the project. \
25 Provide your recommendation with reasoning so the user can make an informed choice. \
26 Do NOT guess or proceed without clarification when uncertain.";
27
28#[async_trait]
29impl Tool for AskTool {
30 fn definition(&self) -> ToolDefinition {
31 ToolDefinition {
32 name: "ask".to_string(),
33 description: ASK_TOOL_DESCRIPTION.to_string(),
34 parameters: ask_tool_schema(),
35 }
36 }
37
38 async fn execute(&self, params: Value) -> Result<String> {
39 let question = params["question"]
40 .as_str()
41 .ok_or_else(|| anyhow::anyhow!("missing 'question'"))?;
42
43 let options = params["options"].as_array();
44 let recommendation = params["recommendation"].as_object();
45
46 render_question_ui(question, options, recommendation);
48
49 let answer = read_user_answer();
51 println!();
52
53 Ok(answer)
54 }
55
56 fn risk_level(&self) -> RiskLevel {
57 RiskLevel::Safe
58 }
59}
60
61fn ask_tool_schema() -> Value {
63 json!({
64 "type": "object",
65 "properties": {
66 "question": {
67 "type": "string",
68 "description": "The question to ask the user. Be specific and clear."
69 },
70 "options": {
71 "type": "array",
72 "description": "List of available options/choices",
73 "items": {
74 "type": "object",
75 "properties": {
76 "id": {
77 "type": "string",
78 "description": "Short identifier for the option (e.g., 'A', 'B', '1', '2')"
79 },
80 "label": {
81 "type": "string",
82 "description": "Human-readable label for the option"
83 },
84 "description": {
85 "type": "string",
86 "description": "Brief description of what this option entails"
87 }
88 },
89 "required": ["id", "label"]
90 }
91 },
92 "recommendation": {
93 "type": "object",
94 "description": "Your recommended option with reasoning",
95 "properties": {
96 "option_id": {
97 "type": "string",
98 "description": "The id of your recommended option"
99 },
100 "reason": {
101 "type": "string",
102 "description": "Why you recommend this option"
103 }
104 },
105 "required": ["option_id", "reason"]
106 }
107 },
108 "required": ["question"]
109 })
110}
111
112fn render_question_ui(
118 question: &str,
119 options: Option<&Vec<Value>>,
120 recommendation: Option<&serde_json::Map<String, Value>>,
121) {
122 println!();
123 println!("┌─ AI 询问 ─────────────────────────────────────────");
124
125 for line in question.lines() {
127 println!("│ {}", line);
128 }
129 println!("│");
130
131 if let Some(opts) = options
133 && !opts.is_empty() {
134 render_options(opts);
135 }
136
137 if let Some(rec) = recommendation {
139 render_recommendation(rec, options);
140 }
141
142 println!("│ 请输入你的选择或补充想法:");
143 println!("└────────────────────────────────────────────────────");
144 print!("> ");
145 let _ = io::stdout().flush();
146}
147
148fn render_options(opts: &[Value]) {
150 println!("│ 可选方案:");
151 for opt in opts {
152 let id = opt["id"].as_str().unwrap_or("?");
153 let label = opt["label"].as_str().unwrap_or("未命名");
154 let desc = opt["description"].as_str();
155 if let Some(d) = desc {
156 println!("│ {}) {} - {}", id, label, d);
157 } else {
158 println!("│ {}) {}", id, label);
159 }
160 }
161 println!("│");
162}
163
164fn render_recommendation(
166 rec: &serde_json::Map<String, Value>,
167 options: Option<&Vec<Value>>,
168) {
169 let opt_id = rec["option_id"].as_str().unwrap_or("?");
170 let reason = rec["reason"].as_str().unwrap_or("无理由");
171
172 let rec_label = options
174 .and_then(|opts| opts.iter().find(|o| o["id"].as_str() == Some(opt_id)))
175 .and_then(|o| o["label"].as_str())
176 .unwrap_or(opt_id);
177
178 println!("│ 💡 推荐方案:{} ({})", rec_label, opt_id);
179 println!("│ 理由:{}", reason);
180 println!("│");
181}
182
183fn read_user_answer() -> String {
189 let stdin = io::stdin();
190 let mut line = String::new();
191 if stdin.lock().read_line(&mut line).is_err() {
192 return "无法读取回答".to_string();
193 }
194 line.trim().to_string()
195}