Skip to main content

matrixcode_core/tools/
ask.rs

1//! Ask tool: allow the AI to proactively ask the user for clarification or choice.
2//!
3//! When the AI encounters ambiguity, multiple options, or uncertain decisions,
4//! it can use this tool to pause and ask the user instead of guessing.
5
6use 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
14// ============================================================================
15// Tool Definition
16// ============================================================================
17
18pub struct AskTool;
19
20const ASK_TOOL_DESCRIPTION: &str =
21    "当遇到不确定或多种选择时,向用户提问以获取明确指示。\
22     必须使用此工具的情况:(1) 用户请求含义模糊,(2) 存在多种可行方案需要选择,\
23     (3) 决策可能对项目产生重大影响。\
24     提供推荐方案及理由,让用户做出知情选择。\
25     不确定时切勿猜测或直接推进。";
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        let question = params["question"]
39            .as_str()
40            .ok_or_else(|| anyhow::anyhow!("missing 'question'"))?;
41
42        let options = params["options"].as_array();
43        let recommendation = params["recommendation"].as_object();
44
45        // Render the question UI
46        render_question_ui(question, options, recommendation);
47
48        // Read user answer
49        let answer = read_user_answer();
50        println!();
51
52        Ok(answer)
53    }
54
55    fn risk_level(&self) -> RiskLevel {
56        RiskLevel::Safe
57    }
58}
59
60/// Get the JSON schema for ask tool parameters.
61fn ask_tool_schema() -> Value {
62    json!({
63        "type": "object",
64        "properties": {
65            "question": {
66                "type": "string",
67                "description": "要向用户提问的问题,需具体清晰"
68            },
69            "options": {
70                "type": "array",
71                "description": "可选方案列表",
72                "items": {
73                    "type": "object",
74                    "properties": {
75                        "id": {
76                            "type": "string",
77                            "description": "选项短标识符(如 'A'、'B'、'1'、'2')"
78                        },
79                        "label": {
80                            "type": "string",
81                            "description": "选项的可读标签"
82                        },
83                        "description": {
84                            "type": "string",
85                            "description": "该选项的简要说明"
86                        }
87                    },
88                    "required": ["id", "label"]
89                }
90            },
91            "recommendation": {
92                "type": "object",
93                "description": "你的推荐方案及理由",
94                "properties": {
95                    "option_id": {
96                        "type": "string",
97                        "description": "推荐选项的标识符"
98                    },
99                    "reason": {
100                        "type": "string",
101                        "description": "推荐该方案的理由"
102                    }
103                },
104                "required": ["option_id", "reason"]
105            }
106        },
107        "required": ["question"]
108    })
109}
110
111// ============================================================================
112// UI Rendering
113// ============================================================================
114
115/// Render the question UI with options and recommendation.
116fn render_question_ui(
117    question: &str,
118    options: Option<&Vec<Value>>,
119    recommendation: Option<&serde_json::Map<String, Value>>,
120) {
121    println!();
122    println!("┌─ AI 询问 ─────────────────────────────────────────");
123
124    // Print the question
125    for line in question.lines() {
126        println!("│ {}", line);
127    }
128    println!("│");
129
130    // Print options if provided
131    if let Some(opts) = options
132        && !opts.is_empty() {
133            render_options(opts);
134        }
135
136    // Print recommendation if provided
137    if let Some(rec) = recommendation {
138        render_recommendation(rec, options);
139    }
140
141    println!("│ 请输入你的选择或补充想法:");
142    println!("└────────────────────────────────────────────────────");
143    print!("> ");
144    let _ = io::stdout().flush();
145}
146
147/// Render the list of options.
148fn render_options(opts: &[Value]) {
149    println!("│ 可选方案:");
150    for opt in opts {
151        let id = opt["id"].as_str().unwrap_or("?");
152        let label = opt["label"].as_str().unwrap_or("未命名");
153        let desc = opt["description"].as_str();
154        if let Some(d) = desc {
155            println!("│   {}) {} - {}", id, label, d);
156        } else {
157            println!("│   {}) {}", id, label);
158        }
159    }
160    println!("│");
161}
162
163/// Render the recommendation with reasoning.
164fn render_recommendation(
165    rec: &serde_json::Map<String, Value>,
166    options: Option<&Vec<Value>>,
167) {
168    let opt_id = rec["option_id"].as_str().unwrap_or("?");
169    let reason = rec["reason"].as_str().unwrap_or("无理由");
170
171    // Find the label for the recommended option
172    let rec_label = options
173        .and_then(|opts| opts.iter().find(|o| o["id"].as_str() == Some(opt_id)))
174        .and_then(|o| o["label"].as_str())
175        .unwrap_or(opt_id);
176
177    println!("│ 💡 推荐方案:{} ({})", rec_label, opt_id);
178    println!("│    理由:{}", reason);
179    println!("│");
180}
181
182// ============================================================================
183// Input Reading
184// ============================================================================
185
186/// Read user's answer from stdin.
187fn read_user_answer() -> String {
188    let stdin = io::stdin();
189    let mut line = String::new();
190    if stdin.lock().read_line(&mut line).is_err() {
191        return "无法读取回答".to_string();
192    }
193    line.trim().to_string()
194}