1use crate::error::Result;
4use crate::impl_tool_factory;
5use crate::tools::{Tool, ToolCall, ToolExample, ToolResult};
6use async_trait::async_trait;
7use serde::{Deserialize, Serialize};
8use serde_json::json;
9use std::collections::HashMap;
10use std::sync::{Arc, Mutex};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ThoughtData {
15 pub thought: String,
16 pub thought_number: i32,
17 pub total_thoughts: i32,
18 pub next_thought_needed: bool,
19 pub is_revision: Option<bool>,
20 pub revises_thought: Option<i32>,
21 pub branch_from_thought: Option<i32>,
22 pub branch_id: Option<String>,
23 pub needs_more_thoughts: Option<bool>,
24}
25
26pub struct ThinkingTool {
28 thought_history: Arc<Mutex<Vec<ThoughtData>>>,
29 branches: Arc<Mutex<HashMap<String, Vec<ThoughtData>>>>,
30}
31
32impl ThinkingTool {
33 pub fn new() -> Self {
34 Self {
35 thought_history: Arc::new(Mutex::new(Vec::new())),
36 branches: Arc::new(Mutex::new(HashMap::new())),
37 }
38 }
39}
40
41#[async_trait]
42impl Tool for ThinkingTool {
43 fn name(&self) -> &str {
44 "sequentialthinking"
45 }
46
47 fn description(&self) -> &str {
48 "A detailed tool for dynamic and reflective problem-solving through thoughts.\n\
49 This tool helps analyze problems through a flexible thinking process that can adapt and evolve.\n\
50 Each thought can build on, question, or revise previous insights as understanding deepens.\n\
51 \n\
52 When to use this tool:\n\
53 - Breaking down complex problems into steps\n\
54 - Planning and design with room for revision\n\
55 - Analysis that might need course correction\n\
56 - Problems where the full scope might not be clear initially\n\
57 - Problems that require a multi-step solution\n\
58 - Tasks that need to maintain context over multiple steps\n\
59 - Situations where irrelevant information needs to be filtered out\n\
60 \n\
61 Key features:\n\
62 - You can adjust total_thoughts up or down as you progress\n\
63 - You can question or revise previous thoughts\n\
64 - You can add more thoughts even after reaching what seemed like the end\n\
65 - You can express uncertainty and explore alternative approaches\n\
66 - Not every thought needs to build linearly - you can branch or backtrack\n\
67 - Generates a solution hypothesis\n\
68 - Verifies the hypothesis based on the Chain of Thought steps\n\
69 - Repeats the process until satisfied\n\
70 - Provides a correct answer\n\
71 \n\
72 Only set next_thought_needed to false when truly done and a satisfactory answer is reached"
73 }
74
75 fn parameters_schema(&self) -> serde_json::Value {
76 json!({
77 "type": "object",
78 "properties": {
79 "thought": {
80 "type": "string",
81 "description": "Your current thinking step"
82 },
83 "next_thought_needed": {
84 "type": "boolean",
85 "description": "Whether another thought step is needed"
86 },
87 "thought_number": {
88 "type": "integer",
89 "description": "Current thought number. Minimum value is 1.",
90 "minimum": 1
91 },
92 "total_thoughts": {
93 "type": "integer",
94 "description": "Estimated total thoughts needed. Minimum value is 1.",
95 "minimum": 1
96 },
97 "is_revision": {
98 "type": "boolean",
99 "description": "Whether this revises previous thinking"
100 },
101 "revises_thought": {
102 "type": "integer",
103 "description": "Which thought is being reconsidered. Minimum value is 1.",
104 "minimum": 1
105 },
106 "branch_from_thought": {
107 "type": "integer",
108 "description": "Branching point thought number. Minimum value is 1.",
109 "minimum": 1
110 },
111 "branch_id": {
112 "type": "string",
113 "description": "Branch identifier"
114 },
115 "needs_more_thoughts": {
116 "type": "boolean",
117 "description": "If more thoughts are needed"
118 }
119 },
120 "required": ["thought", "next_thought_needed", "thought_number", "total_thoughts"]
121 })
122 }
123
124 async fn execute(&self, call: ToolCall) -> Result<ToolResult> {
125 match self.validate_and_process_thought(&call) {
126 Ok(thought_data) => {
127 self.add_to_history(thought_data.clone());
128 self.handle_branching(&thought_data);
129
130 let response_data = self.create_response_data(&thought_data);
131
132 let output_with_thought = json!({
134 "thought": thought_data.thought,
135 "Status": response_data
136 });
137
138 let output = format!(
139 "Sequential thinking step completed.\n\nThought: {}\n\nStatus:\n{}",
140 thought_data.thought,
141 serde_json::to_string_pretty(&response_data).unwrap_or_default()
142 );
143
144 Ok(ToolResult::success(&call.id, &output).with_data(output_with_thought))
145 }
146 Err(e) => {
147 let error_data = json!({
148 "error": e.to_string(),
149 "status": "failed"
150 });
151 Ok(ToolResult::error(
152 &call.id,
153 &format!(
154 "Sequential thinking failed: {}\n\nDetails:\n{}",
155 e,
156 serde_json::to_string_pretty(&error_data).unwrap_or_default()
157 ),
158 ))
159 }
160 }
161 }
162
163 fn examples(&self) -> Vec<ToolExample> {
164 vec![
165 ToolExample {
166 description: "Start a thinking process".to_string(),
167 parameters: json!({
168 "thought": "I need to break down this complex problem into manageable steps. First, let me understand what's being asked.",
169 "thought_number": 1,
170 "total_thoughts": 5,
171 "next_thought_needed": true
172 }),
173 expected_result: "Thinking step recorded with status".to_string(),
174 },
175 ToolExample {
176 description: "Revise a previous thought".to_string(),
177 parameters: json!({
178 "thought": "Actually, I think my previous analysis was incomplete. Let me reconsider the requirements more carefully.",
179 "thought_number": 3,
180 "total_thoughts": 6,
181 "next_thought_needed": true,
182 "is_revision": true,
183 "revises_thought": 2
184 }),
185 expected_result: "Revision recorded with updated thinking".to_string(),
186 },
187 ToolExample {
188 description: "Branch from a previous thought".to_string(),
189 parameters: json!({
190 "thought": "Let me explore an alternative approach from step 2.",
191 "thought_number": 4,
192 "total_thoughts": 7,
193 "next_thought_needed": true,
194 "branch_from_thought": 2,
195 "branch_id": "alternative_approach"
196 }),
197 expected_result: "Branch created with new thinking path".to_string(),
198 },
199 ]
200 }
201}
202
203impl ThinkingTool {
204 fn validate_and_process_thought(&self, call: &ToolCall) -> Result<ThoughtData> {
206 let thought: String = call
207 .get_parameter("thought")
208 .map_err(|_| "Invalid thought: must be a string")?;
209
210 let thought_number: i32 = call
211 .get_parameter("thought_number")
212 .map_err(|_| "Invalid thought_number: must be a number")?;
213
214 let total_thoughts: i32 = call
215 .get_parameter("total_thoughts")
216 .map_err(|_| "Invalid total_thoughts: must be a number")?;
217
218 let next_thought_needed: bool = call
219 .get_parameter("next_thought_needed")
220 .map_err(|_| "Invalid next_thought_needed: must be a boolean")?;
221
222 if thought_number < 1 {
224 return Err("thought_number must be at least 1".into());
225 }
226
227 if total_thoughts < 1 {
228 return Err("total_thoughts must be at least 1".into());
229 }
230
231 let is_revision: Option<bool> = call.get_parameter("is_revision").ok();
233 let revises_thought: Option<i32> = call
234 .get_parameter("revises_thought")
235 .ok()
236 .and_then(|v: i32| if v > 0 { Some(v) } else { None });
237 let branch_from_thought: Option<i32> = call
238 .get_parameter("branch_from_thought")
239 .ok()
240 .and_then(|v: i32| if v > 0 { Some(v) } else { None });
241 let branch_id: Option<String> = call.get_parameter("branch_id").ok();
242 let needs_more_thoughts: Option<bool> = call.get_parameter("needs_more_thoughts").ok();
243
244 if let Some(revises) = revises_thought {
246 if revises < 1 {
247 return Err("revises_thought must be a positive integer".into());
248 }
249 }
250
251 if let Some(branch_from) = branch_from_thought {
252 if branch_from < 1 {
253 return Err("branch_from_thought must be a positive integer".into());
254 }
255 }
256
257 let mut thought_data = ThoughtData {
258 thought,
259 thought_number,
260 total_thoughts,
261 next_thought_needed,
262 is_revision,
263 revises_thought,
264 branch_from_thought,
265 branch_id,
266 needs_more_thoughts,
267 };
268
269 if thought_data.thought_number > thought_data.total_thoughts {
271 thought_data.total_thoughts = thought_data.thought_number;
272 }
273
274 Ok(thought_data)
275 }
276
277 fn add_to_history(&self, thought_data: ThoughtData) {
279 if let Ok(mut history) = self.thought_history.lock() {
280 history.push(thought_data);
281 }
282 }
283
284 fn handle_branching(&self, thought_data: &ThoughtData) {
286 if let (Some(_branch_from), Some(branch_id)) =
287 (&thought_data.branch_from_thought, &thought_data.branch_id)
288 {
289 if let Ok(mut branches) = self.branches.lock() {
290 branches
291 .entry(branch_id.clone())
292 .or_insert_with(Vec::new)
293 .push(thought_data.clone());
294 }
295 }
296 }
297
298 fn create_response_data(&self, thought_data: &ThoughtData) -> serde_json::Value {
300 let branch_keys: Vec<String> = if let Ok(branches) = self.branches.lock() {
301 branches.keys().cloned().collect()
302 } else {
303 Vec::new()
304 };
305
306 let history_length = if let Ok(history) = self.thought_history.lock() {
307 history.len()
308 } else {
309 0
310 };
311
312 json!({
313 "thought_number": thought_data.thought_number,
314 "total_thoughts": thought_data.total_thoughts,
315 "next_thought_needed": thought_data.next_thought_needed,
316 "branches": branch_keys,
317 "thought_history_length": history_length
318 })
319 }
320
321 #[allow(dead_code)]
323 fn format_thought(&self, thought_data: &ThoughtData) -> String {
324 let prefix;
325 let context;
326
327 if thought_data.is_revision.unwrap_or(false) {
328 prefix = "š Revision";
329 context = if let Some(revises) = thought_data.revises_thought {
330 format!(" (revising thought {})", revises)
331 } else {
332 String::new()
333 };
334 } else if thought_data.branch_from_thought.is_some() {
335 prefix = "šæ Branch";
336 context = format!(
337 " (from thought {}, ID: {})",
338 thought_data.branch_from_thought.unwrap_or(0),
339 thought_data.branch_id.as_deref().unwrap_or("unknown")
340 );
341 } else {
342 prefix = "š Thought";
343 context = String::new();
344 }
345
346 let header = format!(
347 "{} {}/{}{}",
348 prefix, thought_data.thought_number, thought_data.total_thoughts, context
349 );
350 let border_length = std::cmp::max(header.len(), thought_data.thought.len()) + 4;
351 let border = "ā".repeat(border_length);
352
353 format!(
354 "\nā{}ā\nā {:<width$} ā\nā{}ā¤\nā {:<width$} ā\nā{}ā",
355 border,
356 header,
357 border,
358 thought_data.thought,
359 border,
360 width = border_length - 2
361 )
362 }
363}
364
365impl Default for ThinkingTool {
366 fn default() -> Self {
367 Self::new()
368 }
369}
370
371impl_tool_factory!(
372 ThinkingToolFactory,
373 ThinkingTool,
374 "sequentialthinking",
375 "Use this tool to think through problems step by step"
376);