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 "oneOf": [
192 { "required": ["question"] },
193 { "required": ["questions"] }
194 ]
195 })
196}
197
198fn render_question_ui(
204 question: &str,
205 options: Option<&Vec<Value>>,
206 recommendation: Option<&serde_json::Map<String, Value>>,
207) {
208 println!();
209 println!("┌─ AI 询问 ─────────────────────────────────────────");
210
211 for line in question.lines() {
213 println!("│ {}", line);
214 }
215 println!("│");
216
217 if let Some(opts) = options
219 && !opts.is_empty()
220 {
221 render_options(opts);
222 }
223
224 if let Some(rec) = recommendation {
226 render_recommendation(rec, options);
227 }
228
229 println!("│ 请输入你的选择或补充想法:");
230 println!("└────────────────────────────────────────────────────");
231 print!("> ");
232 let _ = io::stdout().flush();
233}
234
235fn render_options(opts: &[Value]) {
237 println!("│ 可选方案:");
238 for opt in opts {
239 let id = opt["id"].as_str().unwrap_or("?");
240 let label = opt["label"].as_str().unwrap_or("未命名");
241 let desc = opt["description"].as_str();
242 if let Some(d) = desc {
243 println!("│ {}) {} - {}", id, label, d);
244 } else {
245 println!("│ {}) {}", id, label);
246 }
247 }
248 println!("│");
249}
250
251fn render_multi_questions(questions: &[Value]) {
253 println!();
254 println!(
255 "┌─ AI 询问 (共 {} 个问题) ───────────────────────────",
256 questions.len()
257 );
258
259 for (idx, q) in questions.iter().enumerate() {
260 let question = q["question"].as_str().unwrap_or("");
261 println!("│");
262 println!("│ 【问题 {}】", idx + 1);
263 for line in question.lines() {
264 println!("│ {}", line);
265 }
266
267 if let Some(opts_obj) = q.get("options") {
269 let opts = opts_obj.get("options").and_then(|o| o.as_array());
270 if let Some(opts) = opts {
271 println!("│ 可选方案:");
272 for opt in opts {
273 let id = opt["id"].as_str().unwrap_or("?");
274 let label = opt["label"].as_str().unwrap_or("未命名");
275 println!("│ {} - {}", id, label);
276 }
277 }
278 }
279 }
280
281 println!("│");
282 println!("│ 请依次回答所有问题:");
283 println!("└────────────────────────────────────────────────────");
284 print!("> ");
285 let _ = io::stdout().flush();
286}
287
288fn render_recommendation(rec: &serde_json::Map<String, Value>, options: Option<&Vec<Value>>) {
290 let opt_id = rec["option_id"].as_str().unwrap_or("?");
291 let reason = rec["reason"].as_str().unwrap_or("无理由");
292
293 let rec_label = options
295 .and_then(|opts| opts.iter().find(|o| o["id"].as_str() == Some(opt_id)))
296 .and_then(|o| o["label"].as_str())
297 .unwrap_or(opt_id);
298
299 println!("│ 💡 推荐方案:{} ({})", rec_label, opt_id);
300 println!("│ 理由:{}", reason);
301 println!("│");
302}
303
304fn read_user_answer() -> String {
310 let stdin = io::stdin();
311 let mut line = String::new();
312 if stdin.lock().read_line(&mut line).is_err() {
313 return "stdin_read_error".to_string();
314 }
315 line.trim().to_string()
316}