use anyhow::Result;
use async_trait::async_trait;
use serde_json::{Value, json};
use std::io::{self, BufRead, Write as _};
use super::{Tool, ToolDefinition};
use crate::approval::RiskLevel;
pub struct AskTool;
const ASK_TOOL_DESCRIPTION: &str = "当遇到不确定或多种选择时,向用户提问以获取明确指示。\
必须使用此工具的情况:(1) 用户请求含义模糊,(2) 存在多种可行方案需要选择,\
(3) 决策可能对项目产生重大影响。\
提供推荐方案及理由,让用户做出知情选择。\
不确定时切勿猜测或直接推进。\
支持单问题或多问题模式,多问题时用户可用 Tab 切换。";
#[async_trait]
impl Tool for AskTool {
fn definition(&self) -> ToolDefinition {
ToolDefinition {
name: "ask".to_string(),
description: ASK_TOOL_DESCRIPTION.to_string(),
parameters: ask_tool_schema(),
}
}
async fn execute(&self, params: Value) -> Result<String> {
if let Some(questions) = params.get("questions").and_then(|q| q.as_array()) {
render_multi_questions(questions);
let answer = read_user_answer();
println!();
return Ok(answer);
}
let question = params["question"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("missing 'question'"))?;
let options = params
.get("options")
.and_then(|o| o.get("options"))
.and_then(|o| o.as_array())
.or_else(|| params["options"].as_array());
let recommendation = params["recommendation"].as_object();
render_question_ui(question, options, recommendation);
let answer = read_user_answer();
println!();
Ok(answer)
}
fn risk_level(&self) -> RiskLevel {
RiskLevel::Safe
}
}
fn ask_tool_schema() -> Value {
json!({
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "要向用户提问的问题,需具体清晰(单问题模式)"
},
"options": {
"type": "object",
"description": "选项配置(单问题模式)",
"properties": {
"multiSelect": {
"type": "boolean",
"description": "是否允许多选,默认 false"
},
"options": {
"type": "array",
"description": "可选方案列表",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "选项短标识符(如 'A'、'B'、'1'、'2')"
},
"label": {
"type": "string",
"description": "选项的可读标签"
},
"description": {
"type": "string",
"description": "该选项的简要说明"
}
},
"required": ["id", "label"]
}
}
},
"required": ["options"]
},
"recommendation": {
"type": "object",
"description": "你的推荐方案及理由",
"properties": {
"option_id": {
"type": "string",
"description": "推荐选项的标识符"
},
"reason": {
"type": "string",
"description": "推荐该方案的理由"
}
},
"required": ["option_id", "reason"]
},
"questions": {
"type": "array",
"description": "多问题列表(多问题模式,用户可用 Tab 切换问题)",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "问题唯一标识符"
},
"question": {
"type": "string",
"description": "问题内容"
},
"options": {
"type": "object",
"description": "选项配置",
"properties": {
"multiSelect": {
"type": "boolean",
"description": "是否允许多选,默认 false"
},
"options": {
"type": "array",
"description": "可选方案列表",
"items": {
"type": "object",
"properties": {
"id": { "type": "string", "description": "选项标识符" },
"label": { "type": "string", "description": "选项标签" },
"description": { "type": "string", "description": "选项说明" }
},
"required": ["id", "label"]
}
}
},
"required": ["options"]
}
},
"required": ["id", "question"]
}
}
},
"oneOf": [
{ "required": ["question"] },
{ "required": ["questions"] }
]
})
}
fn render_question_ui(
question: &str,
options: Option<&Vec<Value>>,
recommendation: Option<&serde_json::Map<String, Value>>,
) {
println!();
println!("┌─ AI 询问 ─────────────────────────────────────────");
for line in question.lines() {
println!("│ {}", line);
}
println!("│");
if let Some(opts) = options
&& !opts.is_empty()
{
render_options(opts);
}
if let Some(rec) = recommendation {
render_recommendation(rec, options);
}
println!("│ 请输入你的选择或补充想法:");
println!("└────────────────────────────────────────────────────");
print!("> ");
let _ = io::stdout().flush();
}
fn render_options(opts: &[Value]) {
println!("│ 可选方案:");
for opt in opts {
let id = opt["id"].as_str().unwrap_or("?");
let label = opt["label"].as_str().unwrap_or("未命名");
let desc = opt["description"].as_str();
if let Some(d) = desc {
println!("│ {}) {} - {}", id, label, d);
} else {
println!("│ {}) {}", id, label);
}
}
println!("│");
}
fn render_multi_questions(questions: &[Value]) {
println!();
println!(
"┌─ AI 询问 (共 {} 个问题) ───────────────────────────",
questions.len()
);
for (idx, q) in questions.iter().enumerate() {
let question = q["question"].as_str().unwrap_or("");
println!("│");
println!("│ 【问题 {}】", idx + 1);
for line in question.lines() {
println!("│ {}", line);
}
if let Some(opts_obj) = q.get("options") {
let opts = opts_obj.get("options").and_then(|o| o.as_array());
if let Some(opts) = opts {
println!("│ 可选方案:");
for opt in opts {
let id = opt["id"].as_str().unwrap_or("?");
let label = opt["label"].as_str().unwrap_or("未命名");
println!("│ {} - {}", id, label);
}
}
}
}
println!("│");
println!("│ 请依次回答所有问题:");
println!("└────────────────────────────────────────────────────");
print!("> ");
let _ = io::stdout().flush();
}
fn render_recommendation(rec: &serde_json::Map<String, Value>, options: Option<&Vec<Value>>) {
let opt_id = rec["option_id"].as_str().unwrap_or("?");
let reason = rec["reason"].as_str().unwrap_or("无理由");
let rec_label = options
.and_then(|opts| opts.iter().find(|o| o["id"].as_str() == Some(opt_id)))
.and_then(|o| o["label"].as_str())
.unwrap_or(opt_id);
println!("│ 💡 推荐方案:{} ({})", rec_label, opt_id);
println!("│ 理由:{}", reason);
println!("│");
}
fn read_user_answer() -> String {
let stdin = io::stdin();
let mut line = String::new();
if stdin.lock().read_line(&mut line).is_err() {
return "stdin_read_error".to_string();
}
line.trim().to_string()
}