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 必须使用此工具的情况:(1) 用户请求含义模糊,(2) 存在多种可行方案需要选择,\
22 (3) 决策可能对项目产生重大影响。\
23 提供推荐方案及理由,让用户做出知情选择。\
24 不确定时切勿猜测或直接推进。\
25 支持单问题或多问题模式,多问题时用户可用 Tab 切换。";
26
27#[async_trait]
28impl Tool for AskTool {
29 fn definition(&self) -> ToolDefinition {
30 ToolDefinition {
31 name: "ask".to_string(),
32 description: ASK_TOOL_DESCRIPTION.to_string(),
33 parameters: ask_tool_schema(),
34 }
35 }
36
37 async fn execute(&self, params: Value) -> Result<String> {
38 if let Some(questions) = params.get("questions").and_then(|q| q.as_array()) {
40 render_multi_questions(questions);
42 let answer = read_user_answer();
43 println!();
44 return Ok(answer);
45 }
46
47 let question = params["question"]
49 .as_str()
50 .ok_or_else(|| anyhow::anyhow!("missing 'question'"))?;
51
52 let options = params
53 .get("options")
54 .and_then(|o| o.get("options"))
55 .and_then(|o| o.as_array())
56 .or_else(|| params["options"].as_array());
57 let recommendation = params["recommendation"].as_object();
58
59 render_question_ui(question, options, recommendation);
61
62 let answer = read_user_answer();
64 println!();
65
66 Ok(answer)
67 }
68
69 fn risk_level(&self) -> RiskLevel {
70 RiskLevel::Safe
71 }
72}
73
74fn ask_tool_schema() -> Value {
76 json!({
77 "type": "object",
78 "properties": {
79 "question": {
80 "type": "string",
81 "description": "要向用户提问的问题,需具体清晰(单问题模式)"
82 },
83 "options": {
84 "type": "object",
85 "description": "选项配置(单问题模式)",
86 "properties": {
87 "multiSelect": {
88 "type": "boolean",
89 "description": "是否允许多选,默认 false"
90 },
91 "options": {
92 "type": "array",
93 "description": "可选方案列表",
94 "items": {
95 "type": "object",
96 "properties": {
97 "id": {
98 "type": "string",
99 "description": "选项短标识符(如 'A'、'B'、'1'、'2')"
100 },
101 "label": {
102 "type": "string",
103 "description": "选项的可读标签"
104 },
105 "description": {
106 "type": "string",
107 "description": "该选项的简要说明"
108 }
109 },
110 "required": ["id", "label"]
111 }
112 }
113 },
114 "required": ["options"]
115 },
116 "recommendation": {
117 "type": "object",
118 "description": "你的推荐方案及理由",
119 "properties": {
120 "option_id": {
121 "type": "string",
122 "description": "推荐选项的标识符"
123 },
124 "reason": {
125 "type": "string",
126 "description": "推荐该方案的理由"
127 }
128 },
129 "required": ["option_id", "reason"]
130 },
131 "questions": {
132 "type": "array",
133 "description": "多问题列表(多问题模式,用户可用 Tab 切换问题)",
134 "items": {
135 "type": "object",
136 "properties": {
137 "id": {
138 "type": "string",
139 "description": "问题唯一标识符"
140 },
141 "question": {
142 "type": "string",
143 "description": "问题内容"
144 },
145 "options": {
146 "type": "object",
147 "description": "选项配置",
148 "properties": {
149 "multiSelect": {
150 "type": "boolean",
151 "description": "是否允许多选,默认 false"
152 },
153 "options": {
154 "type": "array",
155 "description": "可选方案列表",
156 "items": {
157 "type": "object",
158 "properties": {
159 "id": { "type": "string", "description": "选项标识符" },
160 "label": { "type": "string", "description": "选项标签" },
161 "description": { "type": "string", "description": "选项说明" }
162 },
163 "required": ["id", "label"]
164 }
165 }
166 },
167 "required": ["options"]
168 }
169 },
170 "required": ["id", "question"]
171 }
172 }
173 },
174 "oneOf": [
175 { "required": ["question"] },
176 { "required": ["questions"] }
177 ]
178 })
179}
180
181fn render_question_ui(
187 question: &str,
188 options: Option<&Vec<Value>>,
189 recommendation: Option<&serde_json::Map<String, Value>>,
190) {
191 println!();
192 println!("┌─ AI 询问 ─────────────────────────────────────────");
193
194 for line in question.lines() {
196 println!("│ {}", line);
197 }
198 println!("│");
199
200 if let Some(opts) = options
202 && !opts.is_empty()
203 {
204 render_options(opts);
205 }
206
207 if let Some(rec) = recommendation {
209 render_recommendation(rec, options);
210 }
211
212 println!("│ 请输入你的选择或补充想法:");
213 println!("└────────────────────────────────────────────────────");
214 print!("> ");
215 let _ = io::stdout().flush();
216}
217
218fn render_options(opts: &[Value]) {
220 println!("│ 可选方案:");
221 for opt in opts {
222 let id = opt["id"].as_str().unwrap_or("?");
223 let label = opt["label"].as_str().unwrap_or("未命名");
224 let desc = opt["description"].as_str();
225 if let Some(d) = desc {
226 println!("│ {}) {} - {}", id, label, d);
227 } else {
228 println!("│ {}) {}", id, label);
229 }
230 }
231 println!("│");
232}
233
234fn render_multi_questions(questions: &[Value]) {
236 println!();
237 println!(
238 "┌─ AI 询问 (共 {} 个问题) ───────────────────────────",
239 questions.len()
240 );
241
242 for (idx, q) in questions.iter().enumerate() {
243 let question = q["question"].as_str().unwrap_or("");
244 println!("│");
245 println!("│ 【问题 {}】", idx + 1);
246 for line in question.lines() {
247 println!("│ {}", line);
248 }
249
250 if let Some(opts_obj) = q.get("options") {
252 let opts = opts_obj.get("options").and_then(|o| o.as_array());
253 if let Some(opts) = opts {
254 println!("│ 可选方案:");
255 for opt in opts {
256 let id = opt["id"].as_str().unwrap_or("?");
257 let label = opt["label"].as_str().unwrap_or("未命名");
258 println!("│ {} - {}", id, label);
259 }
260 }
261 }
262 }
263
264 println!("│");
265 println!("│ 请依次回答所有问题:");
266 println!("└────────────────────────────────────────────────────");
267 print!("> ");
268 let _ = io::stdout().flush();
269}
270
271fn render_recommendation(rec: &serde_json::Map<String, Value>, options: Option<&Vec<Value>>) {
273 let opt_id = rec["option_id"].as_str().unwrap_or("?");
274 let reason = rec["reason"].as_str().unwrap_or("无理由");
275
276 let rec_label = options
278 .and_then(|opts| opts.iter().find(|o| o["id"].as_str() == Some(opt_id)))
279 .and_then(|o| o["label"].as_str())
280 .unwrap_or(opt_id);
281
282 println!("│ 💡 推荐方案:{} ({})", rec_label, opt_id);
283 println!("│ 理由:{}", reason);
284 println!("│");
285}
286
287fn read_user_answer() -> String {
293 let stdin = io::stdin();
294 let mut line = String::new();
295 if stdin.lock().read_line(&mut line).is_err() {
296 return "stdin_read_error".to_string();
297 }
298 line.trim().to_string()
299}