ricecoder_modes/
manager.rs1use 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
11pub 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 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 pub fn register_mode(&mut self, mode: Arc<dyn Mode>) {
36 self.modes.insert(mode.id().to_string(), mode);
37 }
38
39 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 pub fn list_modes(&self) -> Vec<Arc<dyn Mode>> {
49 self.modes.values().cloned().collect()
50 }
51
52 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 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 pub async fn context(&self) -> ModeContext {
71 self.context.read().await.clone()
72 }
73
74 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 pub fn has_mode(&self, id: &str) -> bool {
86 self.modes.contains_key(id)
87 }
88
89 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}