coro_core/tools/builtin/
thinking.rs

1//! Sequential thinking tool
2
3use 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/// Data structure for a single thought
13#[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
26/// Tool for structured thinking and reasoning with comprehensive features
27pub 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                // Create output that includes both the thought content and status
133                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    /// Validate input arguments and create ThoughtData
205    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        // Validate minimum values
223        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        // Handle optional fields
232        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        // Validate optional revision fields
245        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        // Adjust total thoughts if current thought number exceeds it
270        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    /// Add thought to history
278    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    /// Handle branching logic
285    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    /// Create response data for output
299    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    /// Format a thought for display (optional, for future use)
322    #[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);