ricecoder_modes/
manager.rs

1//! Mode manager for lifecycle and transitions
2
3use std::collections::HashMap;
4use std::sync::Arc;
5use tokio::sync::RwLock;
6
7use crate::error::{ModeError, Result};
8use crate::mode::Mode;
9use crate::models::ModeContext;
10
11/// Central coordinator for mode lifecycle and transitions
12///
13/// The ModeManager is responsible for:
14/// - Registering and managing available modes
15/// - Switching between modes
16/// - Maintaining the current mode state
17/// - Managing the execution context
18pub struct ModeManager {
19    modes: HashMap<String, Arc<dyn Mode>>,
20    current_mode: Arc<RwLock<Option<String>>>,
21    context: Arc<RwLock<ModeContext>>,
22}
23
24impl ModeManager {
25    /// Create a new mode manager with the given context
26    pub fn new(context: ModeContext) -> Self {
27        Self {
28            modes: HashMap::new(),
29            current_mode: Arc::new(RwLock::new(None)),
30            context: Arc::new(RwLock::new(context)),
31        }
32    }
33
34    /// Register a mode
35    pub fn register_mode(&mut self, mode: Arc<dyn Mode>) {
36        self.modes.insert(mode.id().to_string(), mode);
37    }
38
39    /// Get a registered mode by ID
40    pub fn get_mode(&self, id: &str) -> Result<Arc<dyn Mode>> {
41        self.modes
42            .get(id)
43            .cloned()
44            .ok_or_else(|| ModeError::NotFound(id.to_string()))
45    }
46
47    /// Get all registered modes
48    pub fn list_modes(&self) -> Vec<Arc<dyn Mode>> {
49        self.modes.values().cloned().collect()
50    }
51
52    /// Get the current active mode
53    pub async fn current_mode(&self) -> Result<Option<Arc<dyn Mode>>> {
54        let mode_id = self.current_mode.read().await;
55        match mode_id.as_ref() {
56            Some(id) => Ok(Some(self.get_mode(id)?)),
57            None => Ok(None),
58        }
59    }
60
61    /// Switch to a different mode
62    pub async fn switch_mode(&self, mode_id: &str) -> Result<Arc<dyn Mode>> {
63        let mode = self.get_mode(mode_id)?;
64        let mut current = self.current_mode.write().await;
65        *current = Some(mode_id.to_string());
66        Ok(mode)
67    }
68
69    /// Get the current context
70    pub async fn context(&self) -> ModeContext {
71        self.context.read().await.clone()
72    }
73
74    /// Update the context with a closure
75    pub async fn update_context<F>(&self, f: F) -> Result<()>
76    where
77        F: FnOnce(&mut ModeContext),
78    {
79        let mut ctx = self.context.write().await;
80        f(&mut ctx);
81        Ok(())
82    }
83
84    /// Check if a mode is registered
85    pub fn has_mode(&self, id: &str) -> bool {
86        self.modes.contains_key(id)
87    }
88
89    /// Get the number of registered modes
90    pub fn mode_count(&self) -> usize {
91        self.modes.len()
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use crate::models::{Capability, ModeConfig, ModeConstraints, Operation};
99
100    struct TestMode {
101        id: String,
102        config: ModeConfig,
103    }
104
105    #[async_trait::async_trait]
106    impl Mode for TestMode {
107        fn id(&self) -> &str {
108            &self.id
109        }
110
111        fn name(&self) -> &str {
112            "Test Mode"
113        }
114
115        fn description(&self) -> &str {
116            "A test mode"
117        }
118
119        fn system_prompt(&self) -> &str {
120            "You are a test mode"
121        }
122
123        async fn process(
124            &self,
125            _input: &str,
126            _context: &ModeContext,
127        ) -> Result<crate::models::ModeResponse> {
128            Ok(crate::models::ModeResponse::new(
129                "Test response".to_string(),
130                self.id.clone(),
131            ))
132        }
133
134        fn capabilities(&self) -> Vec<Capability> {
135            vec![Capability::QuestionAnswering]
136        }
137
138        fn config(&self) -> &ModeConfig {
139            &self.config
140        }
141
142        fn can_execute(&self, _operation: &Operation) -> bool {
143            true
144        }
145
146        fn constraints(&self) -> ModeConstraints {
147            ModeConstraints {
148                allow_file_operations: false,
149                allow_command_execution: false,
150                allow_code_generation: false,
151                require_specs: false,
152                auto_think_more_threshold: None,
153            }
154        }
155    }
156
157    #[test]
158    fn test_mode_manager_creation() {
159        let context = ModeContext::new("test-session".to_string());
160        let manager = ModeManager::new(context);
161        assert_eq!(manager.mode_count(), 0);
162    }
163
164    #[test]
165    fn test_register_mode() {
166        let context = ModeContext::new("test-session".to_string());
167        let mut manager = ModeManager::new(context);
168
169        let mode = Arc::new(TestMode {
170            id: "test".to_string(),
171            config: ModeConfig {
172                temperature: 0.7,
173                max_tokens: 1000,
174                system_prompt: "Test".to_string(),
175                capabilities: vec![Capability::QuestionAnswering],
176                constraints: ModeConstraints {
177                    allow_file_operations: false,
178                    allow_command_execution: false,
179                    allow_code_generation: false,
180                    require_specs: false,
181                    auto_think_more_threshold: None,
182                },
183            },
184        });
185
186        manager.register_mode(mode);
187        assert_eq!(manager.mode_count(), 1);
188        assert!(manager.has_mode("test"));
189    }
190
191    #[test]
192    fn test_get_mode() {
193        let context = ModeContext::new("test-session".to_string());
194        let mut manager = ModeManager::new(context);
195
196        let mode = Arc::new(TestMode {
197            id: "test".to_string(),
198            config: ModeConfig {
199                temperature: 0.7,
200                max_tokens: 1000,
201                system_prompt: "Test".to_string(),
202                capabilities: vec![Capability::QuestionAnswering],
203                constraints: ModeConstraints {
204                    allow_file_operations: false,
205                    allow_command_execution: false,
206                    allow_code_generation: false,
207                    require_specs: false,
208                    auto_think_more_threshold: None,
209                },
210            },
211        });
212
213        manager.register_mode(mode);
214        let retrieved = manager.get_mode("test");
215        assert!(retrieved.is_ok());
216    }
217
218    #[test]
219    fn test_get_nonexistent_mode() {
220        let context = ModeContext::new("test-session".to_string());
221        let manager = ModeManager::new(context);
222        let result = manager.get_mode("nonexistent");
223        assert!(result.is_err());
224    }
225
226    #[tokio::test]
227    async fn test_switch_mode() {
228        let context = ModeContext::new("test-session".to_string());
229        let mut manager = ModeManager::new(context);
230
231        let mode = Arc::new(TestMode {
232            id: "test".to_string(),
233            config: ModeConfig {
234                temperature: 0.7,
235                max_tokens: 1000,
236                system_prompt: "Test".to_string(),
237                capabilities: vec![Capability::QuestionAnswering],
238                constraints: ModeConstraints {
239                    allow_file_operations: false,
240                    allow_command_execution: false,
241                    allow_code_generation: false,
242                    require_specs: false,
243                    auto_think_more_threshold: None,
244                },
245            },
246        });
247
248        manager.register_mode(mode);
249        let result = manager.switch_mode("test").await;
250        assert!(result.is_ok());
251
252        let current = manager.current_mode().await;
253        assert!(current.is_ok());
254        assert!(current.unwrap().is_some());
255    }
256
257    #[tokio::test]
258    async fn test_context_operations() {
259        let context = ModeContext::new("test-session".to_string());
260        let manager = ModeManager::new(context);
261
262        let ctx = manager.context().await;
263        assert_eq!(ctx.session_id, "test-session");
264
265        manager
266            .update_context(|ctx| {
267                ctx.think_more_enabled = true;
268            })
269            .await
270            .unwrap();
271
272        let updated_ctx = manager.context().await;
273        assert!(updated_ctx.think_more_enabled);
274    }
275}