1use serde::{Deserialize, Serialize};
9use std::path::Path;
10
11use crate::thinking::client::ClientThinkingConfig;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct ServerConfig {
16 pub name: String,
18 pub version: String,
20 pub transport: String,
22 pub port: u16,
24 pub thinking: ThinkingConfig,
26 pub export: ExportConfig,
28 pub analytics: AnalyticsConfig,
30 pub logging: LoggingConfig,
32 pub security: SecurityConfig,
34}
35
36impl Default for ServerConfig {
37 fn default() -> Self {
38 Self {
39 name: "ultrafast-sequential-thinking".to_string(),
40 version: env!("CARGO_PKG_VERSION").to_string(),
41 transport: "stdio".to_string(),
42 port: 8080,
43 thinking: ThinkingConfig::default(),
44 export: ExportConfig::default(),
45 analytics: AnalyticsConfig::default(),
46 logging: LoggingConfig::default(),
47 security: SecurityConfig::default(),
48 }
49 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ClientConfig {
55 pub server_url: String,
57 pub timeout_seconds: u64,
59 pub retry_attempts: u32,
61 pub thinking: ClientThinkingConfig,
63 pub connection: ConnectionConfig,
65 pub ui: UIConfig,
67}
68
69impl Default for ClientConfig {
70 fn default() -> Self {
71 Self {
72 server_url: "stdio://".to_string(),
73 timeout_seconds: 30,
74 retry_attempts: 3,
75 thinking: ClientThinkingConfig::default(),
76 connection: ConnectionConfig::default(),
77 ui: UIConfig::default(),
78 }
79 }
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct ThinkingConfig {
85 pub max_thoughts_per_session: u32,
87 pub max_branches_per_session: u32,
89 pub session_timeout_seconds: u64,
91 pub enable_analytics: bool,
93 pub enable_thought_logging: bool,
95 pub max_thought_length: usize,
97 pub min_thought_length: usize,
99 pub rate_limiting: RateLimitingConfig,
101}
102
103impl Default for ThinkingConfig {
104 fn default() -> Self {
105 Self {
106 max_thoughts_per_session: 100,
107 max_branches_per_session: 10,
108 session_timeout_seconds: 3600,
109 enable_analytics: true,
110 enable_thought_logging: true,
111 max_thought_length: 10000,
112 min_thought_length: 10,
113 rate_limiting: RateLimitingConfig::default(),
114 }
115 }
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct RateLimitingConfig {
121 pub requests_per_minute: u32,
123 pub thoughts_per_minute: u32,
125 pub burst_size: u32,
127 pub enabled: bool,
129}
130
131impl Default for RateLimitingConfig {
132 fn default() -> Self {
133 Self {
134 requests_per_minute: 1000,
135 thoughts_per_minute: 100,
136 burst_size: 10,
137 enabled: true,
138 }
139 }
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct ExportConfig {
145 pub formats: Vec<String>,
147 pub auto_export: bool,
149 pub export_directory: String,
151 pub filename_template: String,
153 pub include_metadata: bool,
155 pub include_statistics: bool,
157}
158
159impl Default for ExportConfig {
160 fn default() -> Self {
161 Self {
162 formats: vec![
163 "json".to_string(),
164 "markdown".to_string(),
165 "pdf".to_string(),
166 ],
167 auto_export: false,
168 export_directory: "./exports".to_string(),
169 filename_template: "session_{session_id}_{timestamp}".to_string(),
170 include_metadata: true,
171 include_statistics: true,
172 }
173 }
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct AnalyticsConfig {
179 pub enabled: bool,
181 pub endpoint: String,
183 pub api_key: Option<String>,
185 pub collection_interval: u64,
187 pub detailed_metrics: bool,
189 pub retention_days: u32,
191}
192
193impl Default for AnalyticsConfig {
194 fn default() -> Self {
195 Self {
196 enabled: false,
197 endpoint: "http://localhost:9090".to_string(),
198 api_key: None,
199 collection_interval: 60,
200 detailed_metrics: true,
201 retention_days: 30,
202 }
203 }
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct LoggingConfig {
209 pub level: String,
211 pub file_path: Option<String>,
213 pub console: bool,
215 pub file: bool,
217 pub format: String,
219 pub include_timestamps: bool,
221 pub include_thread_ids: bool,
223}
224
225impl Default for LoggingConfig {
226 fn default() -> Self {
227 Self {
228 level: "info".to_string(),
229 file_path: Some("./logs/sequential-thinking.log".to_string()),
230 console: true,
231 file: true,
232 format: "text".to_string(),
233 include_timestamps: true,
234 include_thread_ids: false,
235 }
236 }
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct SecurityConfig {
242 pub require_auth: bool,
244 pub allowed_origins: Vec<String>,
246 pub api_key_validation: bool,
248 pub rate_limiting_enabled: bool,
250 pub session_encryption: bool,
252 pub audit_logging: bool,
254}
255
256impl Default for SecurityConfig {
257 fn default() -> Self {
258 Self {
259 require_auth: false,
260 allowed_origins: vec!["*".to_string()],
261 api_key_validation: false,
262 rate_limiting_enabled: true,
263 session_encryption: false,
264 audit_logging: true,
265 }
266 }
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct ConnectionConfig {
272 pub timeout_seconds: u64,
274 pub keep_alive_interval: u64,
276 pub max_retries: u32,
278 pub retry_delay: u64,
280 pub connection_pooling: bool,
282 pub pool_size: u32,
284}
285
286impl Default for ConnectionConfig {
287 fn default() -> Self {
288 Self {
289 timeout_seconds: 30,
290 keep_alive_interval: 60,
291 max_retries: 3,
292 retry_delay: 1,
293 connection_pooling: true,
294 pool_size: 10,
295 }
296 }
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct UIConfig {
302 pub show_progress_bars: bool,
304 pub show_thought_visualization: bool,
306 pub show_session_stats: bool,
308 pub theme: String,
310 pub color_output: bool,
312 pub show_timestamps: bool,
314}
315
316impl Default for UIConfig {
317 fn default() -> Self {
318 Self {
319 show_progress_bars: true,
320 show_thought_visualization: true,
321 show_session_stats: true,
322 theme: "default".to_string(),
323 color_output: true,
324 show_timestamps: true,
325 }
326 }
327}
328
329pub struct ConfigManager {
331 server_config: Option<ServerConfig>,
333 client_config: Option<ClientConfig>,
335 config_path: Option<String>,
337}
338
339impl ConfigManager {
340 pub fn new() -> Self {
342 Self {
343 server_config: None,
344 client_config: None,
345 config_path: None,
346 }
347 }
348
349 pub fn load_from_file<P: AsRef<Path>>(
351 &mut self,
352 path: P,
353 ) -> Result<(), Box<dyn std::error::Error>> {
354 let path = path.as_ref();
355 let content = std::fs::read_to_string(path)?;
356
357 if path.extension().and_then(|s| s.to_str()) == Some("toml") {
358 self.load_from_toml(&content)?;
359 } else if path.extension().and_then(|s| s.to_str()) == Some("json") {
360 self.load_from_json(&content)?;
361 } else {
362 return Err("Unsupported configuration file format".into());
363 }
364
365 self.config_path = Some(path.to_string_lossy().to_string());
366 Ok(())
367 }
368
369 pub fn load_from_toml(&mut self, content: &str) -> Result<(), Box<dyn std::error::Error>> {
371 let config: toml::Value = toml::from_str(content)?;
372
373 if let Some(server) = config.get("server") {
374 self.server_config = Some(server.clone().try_into()?);
375 }
376
377 if let Some(client) = config.get("client") {
378 self.client_config = Some(client.clone().try_into()?);
379 }
380
381 Ok(())
382 }
383
384 pub fn load_from_json(&mut self, content: &str) -> Result<(), Box<dyn std::error::Error>> {
386 let config: serde_json::Value = serde_json::from_str(content)?;
387
388 if let Some(server) = config.get("server") {
389 self.server_config = Some(serde_json::from_value(server.clone())?);
390 }
391
392 if let Some(client) = config.get("client") {
393 self.client_config = Some(serde_json::from_value(client.clone())?);
394 }
395
396 Ok(())
397 }
398
399 pub fn load_from_env(&mut self) {
401 if let Ok(name) = std::env::var("SEQUENTIAL_THINKING_SERVER_NAME") {
403 self.server_config
404 .get_or_insert_with(ServerConfig::default)
405 .name = name;
406 }
407
408 if let Ok(transport) = std::env::var("SEQUENTIAL_THINKING_TRANSPORT") {
409 self.server_config
410 .get_or_insert_with(ServerConfig::default)
411 .transport = transport;
412 }
413
414 if let Ok(port) = std::env::var("SEQUENTIAL_THINKING_PORT") {
415 if let Ok(port_num) = port.parse::<u16>() {
416 self.server_config
417 .get_or_insert_with(ServerConfig::default)
418 .port = port_num;
419 }
420 }
421
422 if let Ok(server_url) = std::env::var("SEQUENTIAL_THINKING_SERVER_URL") {
424 self.client_config
425 .get_or_insert_with(ClientConfig::default)
426 .server_url = server_url;
427 }
428
429 if let Ok(timeout) = std::env::var("SEQUENTIAL_THINKING_TIMEOUT") {
430 if let Ok(timeout_num) = timeout.parse::<u64>() {
431 self.client_config
432 .get_or_insert_with(ClientConfig::default)
433 .timeout_seconds = timeout_num;
434 }
435 }
436 }
437
438 pub fn get_server_config(&self) -> ServerConfig {
440 self.server_config.clone().unwrap_or_default()
441 }
442
443 pub fn get_client_config(&self) -> ClientConfig {
445 self.client_config.clone().unwrap_or_default()
446 }
447
448 pub fn set_server_config(&mut self, config: ServerConfig) {
450 self.server_config = Some(config);
451 }
452
453 pub fn set_client_config(&mut self, config: ClientConfig) {
455 self.client_config = Some(config);
456 }
457
458 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), Box<dyn std::error::Error>> {
460 let path = path.as_ref();
461 let config = serde_json::json!({
462 "server": self.server_config,
463 "client": self.client_config
464 });
465
466 let content = serde_json::to_string_pretty(&config)?;
467 std::fs::write(path, content)?;
468
469 Ok(())
470 }
471
472 pub fn validate(&self) -> Result<(), Vec<String>> {
474 let mut errors = Vec::new();
475
476 if let Some(ref server_config) = self.server_config {
478 if server_config.name.is_empty() {
479 errors.push("Server name cannot be empty".to_string());
480 }
481
482 if server_config.port == 0 {
483 errors.push("Server port must be greater than 0".to_string());
484 }
485
486 if server_config.thinking.max_thoughts_per_session == 0 {
487 errors.push("Max thoughts per session must be greater than 0".to_string());
488 }
489 }
490
491 if let Some(ref client_config) = self.client_config {
493 if client_config.server_url.is_empty() {
494 errors.push("Server URL cannot be empty".to_string());
495 }
496
497 if client_config.timeout_seconds == 0 {
498 errors.push("Timeout must be greater than 0".to_string());
499 }
500 }
501
502 if errors.is_empty() {
503 Ok(())
504 } else {
505 Err(errors)
506 }
507 }
508}
509
510impl Default for ConfigManager {
511 fn default() -> Self {
512 Self::new()
513 }
514}
515
516pub mod utils {
518 use super::*;
519
520 pub fn load_default_config() -> Result<ConfigManager, Box<dyn std::error::Error>> {
522 let mut manager = ConfigManager::new();
523
524 let default_paths = [
526 "./config.toml",
527 "./config.json",
528 "./sequential-thinking.toml",
529 "./sequential-thinking.json",
530 ];
531
532 for path in &default_paths {
533 if std::path::Path::new(path).exists() {
534 if let Ok(()) = manager.load_from_file(path) {
535 break;
536 }
537 }
538 }
539
540 manager.load_from_env();
542
543 let _ = manager.validate();
545
546 Ok(manager)
547 }
548
549 pub fn create_default_config<P: AsRef<Path>>(
551 path: P,
552 ) -> Result<(), Box<dyn std::error::Error>> {
553 let mut manager = ConfigManager::new();
554 manager.set_server_config(ServerConfig::default());
555 manager.set_client_config(ClientConfig::default());
556 manager.save_to_file(path)?;
557 Ok(())
558 }
559
560 pub fn merge_configs(base: &mut ServerConfig, override_config: &ServerConfig) {
562 if !override_config.name.is_empty() {
563 base.name = override_config.name.clone();
564 }
565 if !override_config.version.is_empty() {
566 base.version = override_config.version.clone();
567 }
568 if !override_config.transport.is_empty() {
569 base.transport = override_config.transport.clone();
570 }
571 if override_config.port != 0 {
572 base.port = override_config.port;
573 }
574 }
576}
577
578#[cfg(test)]
579mod tests {
580 use super::*;
581
582 #[test]
583 fn test_server_config_default() {
584 let config = ServerConfig::default();
585 assert_eq!(config.name, "ultrafast-sequential-thinking");
586 assert_eq!(config.transport, "stdio");
587 assert_eq!(config.port, 8080);
588 }
589
590 #[test]
591 fn test_client_config_default() {
592 let config = ClientConfig::default();
593 assert_eq!(config.server_url, "stdio://");
594 assert_eq!(config.timeout_seconds, 30);
595 assert_eq!(config.retry_attempts, 3);
596 }
597
598 #[test]
599 fn test_thinking_config_default() {
600 let config = ThinkingConfig::default();
601 assert_eq!(config.max_thoughts_per_session, 100);
602 assert_eq!(config.max_branches_per_session, 10);
603 assert!(config.enable_analytics);
604 }
605
606 #[test]
607 fn test_config_manager() {
608 let mut manager = ConfigManager::new();
609 let server_config = ServerConfig::default();
610 manager.set_server_config(server_config);
611
612 let loaded_config = manager.get_server_config();
613 assert_eq!(loaded_config.name, "ultrafast-sequential-thinking");
614 }
615
616 #[test]
617 fn test_config_validation() {
618 let mut manager = ConfigManager::new();
619 let server_config = ServerConfig {
620 name: String::new(),
621 ..Default::default()
622 };
623 manager.set_server_config(server_config);
624
625 let result = manager.validate();
626 assert!(result.is_err());
627 }
628}