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 = r#"当遇到不确定或多种选择时,向用户提问以获取明确指示。
21
22【何时使用】
23- 用户请求含义模糊
24- 存在多种可行方案需要选择
25- 决策可能对项目产生重大影响
26- 用户说 "怎么处理"、"选哪个"、"你觉得呢" 时
27
28【何时不使用】
29- 简单问题不需要确认(明显最优、低风险、可逆)
30- 任务可以直接执行不需要用户决策
31- 用户说 "开始做"、"直接改" 时 — 直接执行
32
33【Plan Mode 注意】
34- 不要问 "我的计划准备好了吗?" — 用 ExitPlanMode 获取批准
35- 不要问 "你对计划有反馈吗?" — 用户看不到计划直到 ExitPlanMode
36- 不要在问题中引用 "the plan"
37
38【最佳实践】
39- 提供具体选项(不要只给问题描述)
40- 提供推荐方案及理由
41- 如果推荐特定选项,放在列表第一位并在 label 结尾加 "(推荐)" "#;
42
43#[async_trait]
44impl Tool for AskTool {
45 fn definition(&self) -> ToolDefinition {
46 ToolDefinition {
47 name: "ask".to_string(),
48 description: ASK_TOOL_DESCRIPTION.to_string(),
49 parameters: ask_tool_schema(),
50 ..Default::default()
51 }
52 }
53
54 async fn execute(&self, params: Value) -> Result<String> {
55 if let Some(questions) = params.get("questions").and_then(|q| q.as_array()) {
57 render_multi_questions(questions);
59 let answer = read_user_answer();
60 println!();
61 return Ok(answer);
62 }
63
64 let question = params["question"]
66 .as_str()
67 .ok_or_else(|| anyhow::anyhow!("missing 'question'"))?;
68
69 let options = params
70 .get("options")
71 .and_then(|o| o.get("options"))
72 .and_then(|o| o.as_array())
73 .or_else(|| params["options"].as_array());
74 let recommendation = params["recommendation"].as_object();
75
76 render_question_ui(question, options, recommendation);
78
79 let answer = read_user_answer();
81 println!();
82
83 Ok(answer)
84 }
85
86 fn risk_level(&self) -> RiskLevel {
87 RiskLevel::Safe
88 }
89}
90
91fn ask_tool_schema() -> Value {
93 json!({
94 "type": "object",
95 "properties": {
96 "question": {
97 "type": "string",
98 "description": "要向用户提问的问题,需具体清晰(单问题模式)"
99 },
100 "options": {
101 "type": "object",
102 "description": "选项配置(单问题模式)",
103 "properties": {
104 "multiSelect": {
105 "type": "boolean",
106 "description": "是否允许多选,默认 false"
107 },
108 "options": {
109 "type": "array",
110 "description": "可选方案列表",
111 "items": {
112 "type": "object",
113 "properties": {
114 "id": {
115 "type": "string",
116 "description": "选项短标识符(如 'A'、'B'、'1'、'2')"
117 },
118 "label": {
119 "type": "string",
120 "description": "选项的可读标签"
121 },
122 "description": {
123 "type": "string",
124 "description": "该选项的简要说明"
125 }
126 },
127 "required": ["id", "label"]
128 }
129 }
130 },
131 "required": ["options"]
132 },
133 "recommendation": {
134 "type": "object",
135 "description": "你的推荐方案及理由",
136 "properties": {
137 "option_id": {
138 "type": "string",
139 "description": "推荐选项的标识符"
140 },
141 "reason": {
142 "type": "string",
143 "description": "推荐该方案的理由"
144 }
145 },
146 "required": ["option_id", "reason"]
147 },
148 "questions": {
149 "type": "array",
150 "description": "多问题列表(多问题模式,用户可用 Tab 切换问题)",
151 "items": {
152 "type": "object",
153 "properties": {
154 "id": {
155 "type": "string",
156 "description": "问题唯一标识符"
157 },
158 "question": {
159 "type": "string",
160 "description": "问题内容"
161 },
162 "options": {
163 "type": "object",
164 "description": "选项配置",
165 "properties": {
166 "multiSelect": {
167 "type": "boolean",
168 "description": "是否允许多选,默认 false"
169 },
170 "options": {
171 "type": "array",
172 "description": "可选方案列表",
173 "items": {
174 "type": "object",
175 "properties": {
176 "id": { "type": "string", "description": "选项标识符" },
177 "label": { "type": "string", "description": "选项标签" },
178 "description": { "type": "string", "description": "选项说明" }
179 },
180 "required": ["id", "label"]
181 }
182 }
183 },
184 "required": ["options"]
185 }
186 },
187 "required": ["id", "question"]
188 }
189 }
190 }
191 })
192}
193
194fn render_question_ui(
200 question: &str,
201 options: Option<&Vec<Value>>,
202 recommendation: Option<&serde_json::Map<String, Value>>,
203) {
204 println!();
205 println!("┌─ AI 询问 ─────────────────────────────────────────");
206
207 for line in question.lines() {
209 println!("│ {}", line);
210 }
211 println!("│");
212
213 if let Some(opts) = options
215 && !opts.is_empty()
216 {
217 render_options(opts);
218 }
219
220 if let Some(rec) = recommendation {
222 render_recommendation(rec, options);
223 }
224
225 println!("│ 请输入你的选择或补充想法:");
226 println!("└────────────────────────────────────────────────────");
227 print!("> ");
228 let _ = io::stdout().flush();
229}
230
231fn render_options(opts: &[Value]) {
233 println!("│ 可选方案:");
234 for opt in opts {
235 let id = opt["id"].as_str().unwrap_or("?");
236 let label = opt["label"].as_str().unwrap_or("未命名");
237 let desc = opt["description"].as_str();
238 if let Some(d) = desc {
239 println!("│ {}) {} - {}", id, label, d);
240 } else {
241 println!("│ {}) {}", id, label);
242 }
243 }
244 println!("│");
245}
246
247fn render_multi_questions(questions: &[Value]) {
249 println!();
250 println!(
251 "┌─ AI 询问 (共 {} 个问题) ───────────────────────────",
252 questions.len()
253 );
254
255 for (idx, q) in questions.iter().enumerate() {
256 let question = q["question"].as_str().unwrap_or("");
257 println!("│");
258 println!("│ 【问题 {}】", idx + 1);
259 for line in question.lines() {
260 println!("│ {}", line);
261 }
262
263 if let Some(opts_obj) = q.get("options") {
265 let opts = opts_obj.get("options").and_then(|o| o.as_array());
266 if let Some(opts) = opts {
267 println!("│ 可选方案:");
268 for opt in opts {
269 let id = opt["id"].as_str().unwrap_or("?");
270 let label = opt["label"].as_str().unwrap_or("未命名");
271 println!("│ {} - {}", id, label);
272 }
273 }
274 }
275 }
276
277 println!("│");
278 println!("│ 请依次回答所有问题:");
279 println!("└────────────────────────────────────────────────────");
280 print!("> ");
281 let _ = io::stdout().flush();
282}
283
284fn render_recommendation(rec: &serde_json::Map<String, Value>, options: Option<&Vec<Value>>) {
286 let opt_id = rec["option_id"].as_str().unwrap_or("?");
287 let reason = rec["reason"].as_str().unwrap_or("无理由");
288
289 let rec_label = options
291 .and_then(|opts| opts.iter().find(|o| o["id"].as_str() == Some(opt_id)))
292 .and_then(|o| o["label"].as_str())
293 .unwrap_or(opt_id);
294
295 println!("│ 💡 推荐方案:{} ({})", rec_label, opt_id);
296 println!("│ 理由:{}", reason);
297 println!("│");
298}
299
300fn read_user_answer() -> String {
306 let stdin = io::stdin();
307 let mut line = String::new();
308 if stdin.lock().read_line(&mut line).is_err() {
309 return "stdin_read_error".to_string();
310 }
311 line.trim().to_string()
312}