ricecoder_modes/
think_more_controller.rs

1use crate::error::{ModeError, Result};
2use crate::models::{ComplexityLevel, ThinkMoreConfig, ThinkingDepth};
3use std::sync::{Arc, Mutex};
4use std::time::{Duration, Instant};
5
6/// Manages extended thinking capabilities for complex tasks
7#[derive(Debug, Clone)]
8pub struct ThinkMoreController {
9    /// Global Think More configuration
10    global_config: Arc<Mutex<ThinkMoreConfig>>,
11    /// Per-task configuration overrides
12    task_configs: Arc<Mutex<std::collections::HashMap<String, ThinkMoreConfig>>>,
13    /// Current thinking state
14    thinking_state: Arc<Mutex<ThinkingState>>,
15}
16
17/// Represents the current state of thinking
18#[derive(Debug, Clone)]
19struct ThinkingState {
20    /// Whether thinking is currently active
21    active: bool,
22    /// When thinking started
23    start_time: Option<Instant>,
24    /// Accumulated thinking content
25    thinking_content: String,
26    /// Current thinking depth
27    depth: ThinkingDepth,
28}
29
30impl Default for ThinkingState {
31    fn default() -> Self {
32        Self {
33            active: false,
34            start_time: None,
35            thinking_content: String::new(),
36            depth: ThinkingDepth::Medium,
37        }
38    }
39}
40
41impl ThinkMoreController {
42    /// Create a new Think More controller with default configuration
43    pub fn new() -> Self {
44        Self {
45            global_config: Arc::new(Mutex::new(ThinkMoreConfig::default())),
46            task_configs: Arc::new(Mutex::new(std::collections::HashMap::new())),
47            thinking_state: Arc::new(Mutex::new(ThinkingState::default())),
48        }
49    }
50
51    /// Create a new Think More controller with custom configuration
52    pub fn with_config(config: ThinkMoreConfig) -> Self {
53        Self {
54            global_config: Arc::new(Mutex::new(config)),
55            task_configs: Arc::new(Mutex::new(std::collections::HashMap::new())),
56            thinking_state: Arc::new(Mutex::new(ThinkingState::default())),
57        }
58    }
59
60    /// Enable extended thinking globally
61    pub fn enable(&self) -> Result<()> {
62        let mut config = self.global_config.lock().map_err(|_| {
63            ModeError::ConfigError("Failed to acquire lock on global config".to_string())
64        })?;
65        config.enabled = true;
66        Ok(())
67    }
68
69    /// Disable extended thinking globally
70    pub fn disable(&self) -> Result<()> {
71        let mut config = self.global_config.lock().map_err(|_| {
72            ModeError::ConfigError("Failed to acquire lock on global config".to_string())
73        })?;
74        config.enabled = false;
75        Ok(())
76    }
77
78    /// Check if extended thinking is enabled
79    pub fn is_enabled(&self) -> Result<bool> {
80        let config = self.global_config.lock().map_err(|_| {
81            ModeError::ConfigError("Failed to acquire lock on global config".to_string())
82        })?;
83        Ok(config.enabled)
84    }
85
86    /// Set the thinking depth
87    pub fn set_depth(&self, depth: ThinkingDepth) -> Result<()> {
88        let mut config = self.global_config.lock().map_err(|_| {
89            ModeError::ConfigError("Failed to acquire lock on global config".to_string())
90        })?;
91        config.depth = depth;
92        Ok(())
93    }
94
95    /// Get the current thinking depth
96    pub fn get_depth(&self) -> Result<ThinkingDepth> {
97        let config = self.global_config.lock().map_err(|_| {
98            ModeError::ConfigError("Failed to acquire lock on global config".to_string())
99        })?;
100        Ok(config.depth)
101    }
102
103    /// Set the thinking timeout
104    pub fn set_timeout(&self, timeout: Duration) -> Result<()> {
105        let mut config = self.global_config.lock().map_err(|_| {
106            ModeError::ConfigError("Failed to acquire lock on global config".to_string())
107        })?;
108        config.timeout = timeout;
109        Ok(())
110    }
111
112    /// Get the current thinking timeout
113    pub fn get_timeout(&self) -> Result<Duration> {
114        let config = self.global_config.lock().map_err(|_| {
115            ModeError::ConfigError("Failed to acquire lock on global config".to_string())
116        })?;
117        Ok(config.timeout)
118    }
119
120    /// Enable auto-enable based on task complexity
121    pub fn enable_auto_enable(&self) -> Result<()> {
122        let mut config = self.global_config.lock().map_err(|_| {
123            ModeError::ConfigError("Failed to acquire lock on global config".to_string())
124        })?;
125        config.auto_enable = true;
126        Ok(())
127    }
128
129    /// Disable auto-enable
130    pub fn disable_auto_enable(&self) -> Result<()> {
131        let mut config = self.global_config.lock().map_err(|_| {
132            ModeError::ConfigError("Failed to acquire lock on global config".to_string())
133        })?;
134        config.auto_enable = false;
135        Ok(())
136    }
137
138    /// Check if auto-enable is enabled
139    pub fn is_auto_enable_enabled(&self) -> Result<bool> {
140        let config = self.global_config.lock().map_err(|_| {
141            ModeError::ConfigError("Failed to acquire lock on global config".to_string())
142        })?;
143        Ok(config.auto_enable)
144    }
145
146    /// Set per-task configuration
147    pub fn set_task_config(&self, task_id: String, config: ThinkMoreConfig) -> Result<()> {
148        let mut task_configs = self.task_configs.lock().map_err(|_| {
149            ModeError::ConfigError("Failed to acquire lock on task configs".to_string())
150        })?;
151        task_configs.insert(task_id, config);
152        Ok(())
153    }
154
155    /// Get per-task configuration
156    pub fn get_task_config(&self, task_id: &str) -> Result<Option<ThinkMoreConfig>> {
157        let task_configs = self.task_configs.lock().map_err(|_| {
158            ModeError::ConfigError("Failed to acquire lock on task configs".to_string())
159        })?;
160        Ok(task_configs.get(task_id).cloned())
161    }
162
163    /// Remove per-task configuration
164    pub fn remove_task_config(&self, task_id: &str) -> Result<()> {
165        let mut task_configs = self.task_configs.lock().map_err(|_| {
166            ModeError::ConfigError("Failed to acquire lock on task configs".to_string())
167        })?;
168        task_configs.remove(task_id);
169        Ok(())
170    }
171
172    /// Get effective configuration for a task (task-specific or global)
173    pub fn get_effective_config(&self, task_id: Option<&str>) -> Result<ThinkMoreConfig> {
174        // Check for task-specific config first
175        if let Some(id) = task_id {
176            if let Some(config) = self.get_task_config(id)? {
177                return Ok(config);
178            }
179        }
180        // Fall back to global config
181        let config = self.global_config.lock().map_err(|_| {
182            ModeError::ConfigError("Failed to acquire lock on global config".to_string())
183        })?;
184        Ok(config.clone())
185    }
186
187    /// Start thinking for a task
188    pub fn start_thinking(&self, depth: ThinkingDepth) -> Result<()> {
189        let mut state = self.thinking_state.lock().map_err(|_| {
190            ModeError::ConfigError("Failed to acquire lock on thinking state".to_string())
191        })?;
192        state.active = true;
193        state.start_time = Some(Instant::now());
194        state.thinking_content.clear();
195        state.depth = depth;
196        Ok(())
197    }
198
199    /// Stop thinking and return the accumulated content
200    pub fn stop_thinking(&self) -> Result<String> {
201        let mut state = self.thinking_state.lock().map_err(|_| {
202            ModeError::ConfigError("Failed to acquire lock on thinking state".to_string())
203        })?;
204        state.active = false;
205        state.start_time = None;
206        Ok(state.thinking_content.clone())
207    }
208
209    /// Add content to the thinking process
210    pub fn add_thinking_content(&self, content: &str) -> Result<()> {
211        let mut state = self.thinking_state.lock().map_err(|_| {
212            ModeError::ConfigError("Failed to acquire lock on thinking state".to_string())
213        })?;
214        if state.active {
215            state.thinking_content.push_str(content);
216            state.thinking_content.push('\n');
217        }
218        Ok(())
219    }
220
221    /// Check if thinking is currently active
222    pub fn is_thinking(&self) -> Result<bool> {
223        let state = self.thinking_state.lock().map_err(|_| {
224            ModeError::ConfigError("Failed to acquire lock on thinking state".to_string())
225        })?;
226        Ok(state.active)
227    }
228
229    /// Get the current thinking content
230    pub fn get_thinking_content(&self) -> Result<String> {
231        let state = self.thinking_state.lock().map_err(|_| {
232            ModeError::ConfigError("Failed to acquire lock on thinking state".to_string())
233        })?;
234        Ok(state.thinking_content.clone())
235    }
236
237    /// Get the elapsed time since thinking started
238    pub fn get_elapsed_time(&self) -> Result<Option<Duration>> {
239        let state = self.thinking_state.lock().map_err(|_| {
240            ModeError::ConfigError("Failed to acquire lock on thinking state".to_string())
241        })?;
242        Ok(state.start_time.map(|start| start.elapsed()))
243    }
244
245    /// Check if thinking has exceeded the timeout
246    pub fn has_exceeded_timeout(&self) -> Result<bool> {
247        let config = self.global_config.lock().map_err(|_| {
248            ModeError::ConfigError("Failed to acquire lock on global config".to_string())
249        })?;
250        let state = self.thinking_state.lock().map_err(|_| {
251            ModeError::ConfigError("Failed to acquire lock on thinking state".to_string())
252        })?;
253
254        if let Some(start) = state.start_time {
255            Ok(start.elapsed() > config.timeout)
256        } else {
257            Ok(false)
258        }
259    }
260
261    /// Cancel thinking
262    pub fn cancel_thinking(&self) -> Result<()> {
263        let mut state = self.thinking_state.lock().map_err(|_| {
264            ModeError::ConfigError("Failed to acquire lock on thinking state".to_string())
265        })?;
266        state.active = false;
267        state.start_time = None;
268        Ok(())
269    }
270
271    /// Determine if Think More should be auto-enabled for a given complexity level
272    pub fn should_auto_enable(&self, complexity: ComplexityLevel) -> Result<bool> {
273        let config = self.global_config.lock().map_err(|_| {
274            ModeError::ConfigError("Failed to acquire lock on global config".to_string())
275        })?;
276
277        if !config.auto_enable {
278            return Ok(false);
279        }
280
281        // Auto-enable for Complex tasks
282        Ok(complexity == ComplexityLevel::Complex)
283    }
284
285    /// Get metadata about the current thinking session
286    pub fn get_thinking_metadata(&self) -> Result<ThinkingMetadata> {
287        let config = self.global_config.lock().map_err(|_| {
288            ModeError::ConfigError("Failed to acquire lock on global config".to_string())
289        })?;
290        let state = self.thinking_state.lock().map_err(|_| {
291            ModeError::ConfigError("Failed to acquire lock on thinking state".to_string())
292        })?;
293
294        Ok(ThinkingMetadata {
295            enabled: config.enabled,
296            active: state.active,
297            depth: state.depth,
298            elapsed_time: state.start_time.map(|start| start.elapsed()),
299            timeout: config.timeout,
300            content_length: state.thinking_content.len(),
301            auto_enable: config.auto_enable,
302        })
303    }
304}
305
306impl Default for ThinkMoreController {
307    fn default() -> Self {
308        Self::new()
309    }
310}
311
312/// Metadata about a thinking session
313#[derive(Debug, Clone)]
314pub struct ThinkingMetadata {
315    /// Whether Think More is enabled
316    pub enabled: bool,
317    /// Whether thinking is currently active
318    pub active: bool,
319    /// Current thinking depth
320    pub depth: ThinkingDepth,
321    /// Elapsed time since thinking started
322    pub elapsed_time: Option<Duration>,
323    /// Timeout for thinking
324    pub timeout: Duration,
325    /// Length of accumulated thinking content
326    pub content_length: usize,
327    /// Whether auto-enable is enabled
328    pub auto_enable: bool,
329}
330
331#[cfg(test)]
332mod tests {
333    use super::*;
334
335    #[test]
336    fn test_think_more_controller_creation() {
337        let controller = ThinkMoreController::new();
338        assert!(!controller.is_enabled().unwrap());
339    }
340
341    #[test]
342    fn test_enable_disable() {
343        let controller = ThinkMoreController::new();
344        controller.enable().unwrap();
345        assert!(controller.is_enabled().unwrap());
346        controller.disable().unwrap();
347        assert!(!controller.is_enabled().unwrap());
348    }
349
350    #[test]
351    fn test_set_get_depth() {
352        let controller = ThinkMoreController::new();
353        controller.set_depth(ThinkingDepth::Deep).unwrap();
354        assert_eq!(controller.get_depth().unwrap(), ThinkingDepth::Deep);
355    }
356
357    #[test]
358    fn test_set_get_timeout() {
359        let controller = ThinkMoreController::new();
360        let timeout = Duration::from_secs(60);
361        controller.set_timeout(timeout).unwrap();
362        assert_eq!(controller.get_timeout().unwrap(), timeout);
363    }
364
365    #[test]
366    fn test_auto_enable() {
367        let controller = ThinkMoreController::new();
368        controller.enable_auto_enable().unwrap();
369        assert!(controller.is_auto_enable_enabled().unwrap());
370        controller.disable_auto_enable().unwrap();
371        assert!(!controller.is_auto_enable_enabled().unwrap());
372    }
373
374    #[test]
375    fn test_task_config() {
376        let controller = ThinkMoreController::new();
377        let config = ThinkMoreConfig {
378            enabled: true,
379            depth: ThinkingDepth::Deep,
380            timeout: Duration::from_secs(60),
381            auto_enable: false,
382        };
383        controller
384            .set_task_config("task1".to_string(), config.clone())
385            .unwrap();
386        let retrieved = controller.get_task_config("task1").unwrap();
387        assert!(retrieved.is_some());
388        assert_eq!(retrieved.unwrap().depth, ThinkingDepth::Deep);
389    }
390
391    #[test]
392    fn test_remove_task_config() {
393        let controller = ThinkMoreController::new();
394        let config = ThinkMoreConfig::default();
395        controller
396            .set_task_config("task1".to_string(), config)
397            .unwrap();
398        controller.remove_task_config("task1").unwrap();
399        assert!(controller.get_task_config("task1").unwrap().is_none());
400    }
401
402    #[test]
403    fn test_effective_config_task_override() {
404        let controller = ThinkMoreController::new();
405        let global_config = ThinkMoreConfig {
406            enabled: false,
407            depth: ThinkingDepth::Light,
408            timeout: Duration::from_secs(30),
409            auto_enable: false,
410        };
411        let task_config = ThinkMoreConfig {
412            enabled: true,
413            depth: ThinkingDepth::Deep,
414            timeout: Duration::from_secs(60),
415            auto_enable: true,
416        };
417
418        *controller.global_config.lock().unwrap() = global_config;
419        controller
420            .set_task_config("task1".to_string(), task_config)
421            .unwrap();
422
423        let effective = controller.get_effective_config(Some("task1")).unwrap();
424        assert!(effective.enabled);
425        assert_eq!(effective.depth, ThinkingDepth::Deep);
426    }
427
428    #[test]
429    fn test_effective_config_global_fallback() {
430        let controller = ThinkMoreController::new();
431        let global_config = ThinkMoreConfig {
432            enabled: true,
433            depth: ThinkingDepth::Medium,
434            timeout: Duration::from_secs(30),
435            auto_enable: false,
436        };
437        *controller.global_config.lock().unwrap() = global_config;
438
439        let effective = controller
440            .get_effective_config(Some("nonexistent"))
441            .unwrap();
442        assert!(effective.enabled);
443        assert_eq!(effective.depth, ThinkingDepth::Medium);
444    }
445
446    #[test]
447    fn test_start_stop_thinking() {
448        let controller = ThinkMoreController::new();
449        controller.start_thinking(ThinkingDepth::Deep).unwrap();
450        assert!(controller.is_thinking().unwrap());
451
452        let content = controller.stop_thinking().unwrap();
453        assert!(!controller.is_thinking().unwrap());
454        assert_eq!(content, "");
455    }
456
457    #[test]
458    fn test_add_thinking_content() {
459        let controller = ThinkMoreController::new();
460        controller.start_thinking(ThinkingDepth::Deep).unwrap();
461        controller.add_thinking_content("First thought").unwrap();
462        controller.add_thinking_content("Second thought").unwrap();
463
464        let content = controller.get_thinking_content().unwrap();
465        assert!(content.contains("First thought"));
466        assert!(content.contains("Second thought"));
467    }
468
469    #[test]
470    fn test_cancel_thinking() {
471        let controller = ThinkMoreController::new();
472        controller.start_thinking(ThinkingDepth::Deep).unwrap();
473        assert!(controller.is_thinking().unwrap());
474
475        controller.cancel_thinking().unwrap();
476        assert!(!controller.is_thinking().unwrap());
477    }
478
479    #[test]
480    fn test_should_auto_enable() {
481        let controller = ThinkMoreController::new();
482        controller.enable_auto_enable().unwrap();
483
484        assert!(controller
485            .should_auto_enable(ComplexityLevel::Complex)
486            .unwrap());
487        assert!(!controller
488            .should_auto_enable(ComplexityLevel::Moderate)
489            .unwrap());
490        assert!(!controller
491            .should_auto_enable(ComplexityLevel::Simple)
492            .unwrap());
493    }
494
495    #[test]
496    fn test_should_not_auto_enable_when_disabled() {
497        let controller = ThinkMoreController::new();
498        controller.disable_auto_enable().unwrap();
499
500        assert!(!controller
501            .should_auto_enable(ComplexityLevel::Complex)
502            .unwrap());
503    }
504
505    #[test]
506    fn test_thinking_metadata() {
507        let controller = ThinkMoreController::new();
508        controller.enable().unwrap();
509        controller.start_thinking(ThinkingDepth::Deep).unwrap();
510
511        let metadata = controller.get_thinking_metadata().unwrap();
512        assert!(metadata.enabled);
513        assert!(metadata.active);
514        assert_eq!(metadata.depth, ThinkingDepth::Deep);
515        assert!(metadata.elapsed_time.is_some());
516    }
517
518    #[test]
519    fn test_has_exceeded_timeout() {
520        let controller = ThinkMoreController::new();
521        controller.set_timeout(Duration::from_millis(1)).unwrap();
522        controller.start_thinking(ThinkingDepth::Deep).unwrap();
523
524        // Sleep to exceed timeout
525        std::thread::sleep(Duration::from_millis(10));
526
527        assert!(controller.has_exceeded_timeout().unwrap());
528    }
529
530    #[test]
531    fn test_default_implementation() {
532        let controller = ThinkMoreController::default();
533        assert!(!controller.is_enabled().unwrap());
534    }
535
536    #[test]
537    fn test_with_config() {
538        let config = ThinkMoreConfig {
539            enabled: true,
540            depth: ThinkingDepth::Deep,
541            timeout: Duration::from_secs(60),
542            auto_enable: true,
543        };
544        let controller = ThinkMoreController::with_config(config);
545        assert!(controller.is_enabled().unwrap());
546        assert_eq!(controller.get_depth().unwrap(), ThinkingDepth::Deep);
547    }
548}