1use crate::message_types::{AskOption, AskQuestion, AskRequest};
2use crate::tools::{PlanDecision, Tool, ToolResult, parse_tool_args, schema_to_tool_params};
3use schemars::JsonSchema;
4use serde::Deserialize;
5use serde_json::Value;
6use std::borrow::Cow;
7use std::sync::{Arc, atomic::AtomicBool, mpsc};
8
9#[derive(Deserialize, JsonSchema)]
11struct AskOptionParam {
12 label: String,
14 #[serde(default)]
16 description: String,
17}
18
19#[derive(Deserialize, JsonSchema)]
21struct AskQuestionParam {
22 question: String,
24 header: String,
26 options: Vec<AskOptionParam>,
28 #[serde(default)]
30 multi_select: bool,
31}
32
33#[derive(Deserialize, JsonSchema)]
35struct AskParams {
36 questions: Vec<AskQuestionParam>,
38}
39
40#[derive(Debug)]
44pub struct AskTool {
45 pub ask_tx: mpsc::Sender<AskRequest>,
47}
48
49impl AskTool {
50 pub const NAME: &'static str = "Ask";
52}
53
54impl Tool for AskTool {
55 fn name(&self) -> &str {
56 Self::NAME
57 }
58
59 fn description(&self) -> Cow<'_, str> {
60 r#"
61 Present structured questions to the user with single-select or multi-select options. Supports 1-4 questions per call, each with 2-4 options.
62
63 When to use: whenever user input is needed, including but not limited to:
64 - Asking the user to make a choice or confirm an action
65 - Gathering user preferences or configuration
66 - Presenting multiple approaches for the user to decide
67 - Showing intermediate results and requesting feedback
68
69 IMPORTANT — Input format:
70 - `questions` is an array of question OBJECTS (NOT strings).
71 - Each question object has: `question` (full text), `header` (short tag ≤12 chars), `options` (array of OPTION OBJECTS), `multi_select` (bool, optional).
72 - Each option object has: `label` (1-5 words shown to user) and `description` (short explanation). DO NOT pass options as plain strings.
73
74 Concrete example (copy this structure exactly):
75 ```json
76 {
77 "questions": [
78 {
79 "question": "需求文档是否符合预期?",
80 "header": "需求确认",
81 "options": [
82 {"label": "确认无误", "description": "继续进入 API 设计和前端开发阶段"},
83 {"label": "需要调整", "description": "我会指出需要修改的部分"}
84 ],
85 "multi_select": false
86 }
87 ]
88 }
89 ```
90
91 Response format:
92 Returns JSON:
93 ```json
94 {
95 "answers": {
96 "question text": "selected label or free-text input"
97 }
98 }
99 ```
100 For multi-select, multiple labels are comma-separated.
101 "#.into()
102 }
103
104 fn parameters_schema(&self) -> Value {
105 schema_to_tool_params::<AskParams>()
106 }
107
108 fn execute(&self, arguments: &str, _cancelled: &Arc<AtomicBool>) -> ToolResult {
109 let params: AskParams = match parse_tool_args(arguments) {
110 Ok(p) => p,
111 Err(e) => return e,
112 };
113
114 if params.questions.is_empty() || params.questions.len() > 4 {
115 return ToolResult {
116 output: "questions 数量必须为 1-4 个".to_string(),
117 is_error: true,
118 images: vec![],
119 plan_decision: PlanDecision::None,
120 };
121 }
122
123 let mut questions: Vec<AskQuestion> = Vec::new();
124 for q in ¶ms.questions {
125 if q.options.len() < 2 || q.options.len() > 4 {
126 return ToolResult {
127 output: format!("问题 '{}' 的选项数量必须为 2-4 个", q.question),
128 is_error: true,
129 images: vec![],
130 plan_decision: PlanDecision::None,
131 };
132 }
133
134 questions.push(AskQuestion {
135 question: q.question.clone(),
136 header: q.header.clone(),
137 options: q
138 .options
139 .iter()
140 .map(|o| AskOption {
141 label: o.label.clone(),
142 description: o.description.clone(),
143 })
144 .collect(),
145 multi_select: q.multi_select,
146 });
147 }
148
149 let (response_tx, response_rx) = mpsc::channel::<String>();
151
152 let ask_request = AskRequest {
153 questions,
154 response_tx,
155 };
156
157 if self.ask_tx.send(ask_request).is_err() {
158 return ToolResult {
159 output: "无法发送提问请求(主线程可能已退出)".to_string(),
160 is_error: true,
161 images: vec![],
162 plan_decision: PlanDecision::None,
163 };
164 }
165
166 match response_rx.recv() {
168 Ok(response) => ToolResult {
169 output: response,
170 is_error: false,
171 images: vec![],
172 plan_decision: PlanDecision::None,
173 },
174 Err(_) => ToolResult {
175 output: "等待用户响应时连接断开".to_string(),
176 is_error: true,
177 images: vec![],
178 plan_decision: PlanDecision::None,
179 },
180 }
181 }
182
183 fn requires_confirmation(&self) -> bool {
184 false
185 }
186}