mcp_langbase_reasoning/modes/
auto.rs

1//! Auto mode router - automatically selects the most appropriate reasoning mode
2
3use serde::{Deserialize, Serialize};
4use std::time::Instant;
5use tracing::{debug, info, warn};
6
7use super::{serialize_for_log, ModeCore};
8use crate::config::Config;
9use crate::error::{AppResult, ToolError};
10use crate::langbase::{LangbaseClient, Message, PipeRequest};
11use crate::modes::ReasoningMode;
12use crate::prompts::AUTO_ROUTER_PROMPT;
13use crate::storage::{Invocation, SqliteStorage, Storage};
14
15/// Input parameters for auto mode routing
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct AutoParams {
18    /// The content to analyze for mode selection
19    pub content: String,
20    /// Optional hints about the problem type
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub hints: Option<Vec<String>>,
23    /// Optional session ID for context
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub session_id: Option<String>,
26}
27
28/// Result of auto mode routing.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct AutoResult {
31    /// The recommended reasoning mode.
32    pub recommended_mode: ReasoningMode,
33    /// Confidence in the recommendation (0.0-1.0).
34    pub confidence: f64,
35    /// Explanation for the recommendation.
36    pub rationale: String,
37    /// Estimated problem complexity (0.0-1.0).
38    pub complexity: f64,
39    /// Alternative mode recommendations.
40    pub alternative_modes: Vec<ModeRecommendation>,
41    /// Whether a fallback was used due to invalid mode from Langbase.
42    #[serde(default)]
43    pub fallback_used: bool,
44    /// The original invalid mode string if fallback was used.
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub original_invalid_mode: Option<String>,
47}
48
49/// A mode recommendation with confidence.
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct ModeRecommendation {
52    /// The reasoning mode.
53    pub mode: ReasoningMode,
54    /// Confidence in this recommendation (0.0-1.0).
55    pub confidence: f64,
56    /// Explanation for this recommendation.
57    pub rationale: String,
58}
59
60/// Langbase response for auto routing
61#[derive(Debug, Clone, Serialize, Deserialize)]
62struct AutoResponse {
63    recommended_mode: String,
64    confidence: f64,
65    rationale: String,
66    #[serde(default = "default_complexity")]
67    complexity: f64,
68    #[serde(default)]
69    metadata: Option<serde_json::Value>,
70}
71
72fn default_complexity() -> f64 {
73    0.5
74}
75
76impl AutoResponse {
77    /// Parse completion - returns error on parse failure (no fallbacks).
78    fn from_completion(completion: &str) -> Result<Self, ToolError> {
79        serde_json::from_str::<AutoResponse>(completion).map_err(|e| {
80            let preview: String = completion.chars().take(200).collect();
81            ToolError::ParseFailed {
82                mode: "auto".to_string(),
83                message: format!("JSON parse error: {} | Response preview: {}", e, preview),
84            }
85        })
86    }
87}
88
89/// Auto mode router
90#[derive(Clone)]
91pub struct AutoMode {
92    /// Core infrastructure (storage and langbase client).
93    core: ModeCore,
94    /// The Langbase pipe name for auto routing.
95    pipe_name: String,
96}
97
98impl AutoMode {
99    /// Create a new auto mode router
100    pub fn new(storage: SqliteStorage, langbase: LangbaseClient, config: &Config) -> Self {
101        Self {
102            core: ModeCore::new(storage, langbase),
103            pipe_name: config
104                .pipes
105                .auto
106                .clone()
107                .unwrap_or_else(|| "mode-router-v1".to_string()),
108        }
109    }
110
111    /// Route to the appropriate reasoning mode
112    pub async fn route(&self, params: AutoParams) -> AppResult<AutoResult> {
113        let start = Instant::now();
114
115        debug!(content_len = params.content.len(), "Auto-routing content");
116
117        // First, try local heuristics for obvious cases
118        if let Some(result) = self.local_heuristics(&params) {
119            info!(
120                mode = %result.recommended_mode,
121                confidence = result.confidence,
122                source = "heuristics",
123                "Auto-routing completed via heuristics"
124            );
125            return Ok(result);
126        }
127
128        // Build messages for Langbase
129        let messages = self.build_messages(&params);
130
131        // Log invocation
132        let mut invocation = Invocation::new(
133            "reasoning.auto",
134            serialize_for_log(&params, "reasoning.auto input"),
135        )
136        .with_pipe(&self.pipe_name);
137
138        if let Some(session_id) = &params.session_id {
139            invocation = invocation.with_session(session_id);
140        }
141
142        // Call Langbase
143        let request = PipeRequest::new(&self.pipe_name, messages);
144        let response = match self.core.langbase().call_pipe(request).await {
145            Ok(resp) => resp,
146            Err(e) => {
147                let latency = start.elapsed().as_millis() as i64;
148                invocation = invocation.failure(e.to_string(), latency);
149                if let Err(log_err) = self.core.storage().log_invocation(&invocation).await {
150                    warn!(
151                        error = %log_err,
152                        tool = %invocation.tool_name,
153                        "Failed to log invocation - audit trail incomplete"
154                    );
155                }
156                return Err(e.into());
157            }
158        };
159
160        // Parse response
161        let auto_response = AutoResponse::from_completion(&response.completion)?;
162
163        // Convert mode string to enum with fallback tracking
164        let (recommended_mode, fallback_used, original_invalid_mode) =
165            match auto_response.recommended_mode.parse() {
166                Ok(mode) => (mode, false, None),
167                Err(_) => {
168                    warn!(
169                        invalid_mode = %auto_response.recommended_mode,
170                        "Invalid mode returned by auto-router, falling back to Linear"
171                    );
172                    (
173                        ReasoningMode::Linear,
174                        true,
175                        Some(auto_response.recommended_mode.clone()),
176                    )
177                }
178            };
179
180        // Generate alternative recommendations based on complexity
181        let alternatives = self.generate_alternatives(&auto_response);
182
183        let latency = start.elapsed().as_millis() as i64;
184
185        // Track fallback in invocation if used
186        if fallback_used {
187            invocation = invocation.with_fallback("invalid_mode_parse");
188        }
189
190        invocation = invocation.success(
191            serialize_for_log(&auto_response, "reasoning.auto output"),
192            latency,
193        );
194        if let Err(log_err) = self.core.storage().log_invocation(&invocation).await {
195            warn!(
196                error = %log_err,
197                tool = %invocation.tool_name,
198                "Failed to log invocation - audit trail incomplete"
199            );
200        }
201
202        info!(
203            mode = %recommended_mode,
204            confidence = auto_response.confidence,
205            complexity = auto_response.complexity,
206            fallback_used = fallback_used,
207            latency_ms = latency,
208            "Auto-routing completed"
209        );
210
211        Ok(AutoResult {
212            recommended_mode,
213            confidence: auto_response.confidence,
214            rationale: auto_response.rationale,
215            complexity: auto_response.complexity,
216            alternative_modes: alternatives,
217            fallback_used,
218            original_invalid_mode,
219        })
220    }
221
222    /// Apply local heuristics for obvious cases
223    fn local_heuristics(&self, params: &AutoParams) -> Option<AutoResult> {
224        let content_lower = params.content.to_lowercase();
225
226        // Very short content -> linear
227        if params.content.len() < 50 {
228            return Some(AutoResult {
229                recommended_mode: ReasoningMode::Linear,
230                confidence: 0.9,
231                rationale: "Short content is best handled with linear reasoning".to_string(),
232                complexity: 0.2,
233                alternative_modes: vec![],
234                fallback_used: false,
235                original_invalid_mode: None,
236            });
237        }
238
239        // Explicit reflection keywords
240        if content_lower.contains("evaluate")
241            || content_lower.contains("assess")
242            || content_lower.contains("review quality")
243            || content_lower.contains("critique")
244        {
245            return Some(AutoResult {
246                recommended_mode: ReasoningMode::Reflection,
247                confidence: 0.85,
248                rationale: "Content contains evaluation/assessment keywords".to_string(),
249                complexity: 0.5,
250                alternative_modes: vec![ModeRecommendation {
251                    mode: ReasoningMode::Linear,
252                    confidence: 0.6,
253                    rationale: "Could also use linear for structured evaluation".to_string(),
254                }],
255                fallback_used: false,
256                original_invalid_mode: None,
257            });
258        }
259
260        // Explicit creativity keywords
261        if content_lower.contains("creative")
262            || content_lower.contains("brainstorm")
263            || content_lower.contains("novel")
264            || content_lower.contains("unconventional")
265        {
266            return Some(AutoResult {
267                recommended_mode: ReasoningMode::Divergent,
268                confidence: 0.85,
269                rationale: "Content requires creative/divergent thinking".to_string(),
270                complexity: 0.6,
271                alternative_modes: vec![ModeRecommendation {
272                    mode: ReasoningMode::Tree,
273                    confidence: 0.5,
274                    rationale: "Could explore multiple creative paths with tree mode".to_string(),
275                }],
276                fallback_used: false,
277                original_invalid_mode: None,
278            });
279        }
280
281        // Explicit branching keywords
282        if content_lower.contains("options")
283            || content_lower.contains("alternatives")
284            || content_lower.contains("compare")
285            || content_lower.contains("trade-offs")
286        {
287            return Some(AutoResult {
288                recommended_mode: ReasoningMode::Tree,
289                confidence: 0.8,
290                rationale: "Content requires exploring multiple options".to_string(),
291                complexity: 0.5,
292                alternative_modes: vec![ModeRecommendation {
293                    mode: ReasoningMode::Divergent,
294                    confidence: 0.4,
295                    rationale: "Could also use divergent for creative alternatives".to_string(),
296                }],
297                fallback_used: false,
298                original_invalid_mode: None,
299            });
300        }
301
302        // Complex graph keywords
303        if content_lower.contains("complex system")
304            || content_lower.contains("interconnected")
305            || content_lower.contains("multi-step")
306            || content_lower.contains("graph")
307        {
308            return Some(AutoResult {
309                recommended_mode: ReasoningMode::Got,
310                confidence: 0.75,
311                rationale: "Content suggests complex graph-based reasoning".to_string(),
312                complexity: 0.8,
313                alternative_modes: vec![ModeRecommendation {
314                    mode: ReasoningMode::Tree,
315                    confidence: 0.6,
316                    rationale: "Tree mode could also handle multi-path exploration".to_string(),
317                }],
318                fallback_used: false,
319                original_invalid_mode: None,
320            });
321        }
322
323        None
324    }
325
326    /// Generate alternative mode recommendations
327    fn generate_alternatives(&self, response: &AutoResponse) -> Vec<ModeRecommendation> {
328        let mut alternatives = Vec::new();
329
330        // Based on complexity, suggest alternatives
331        if response.complexity < 0.3 {
332            if response.recommended_mode != "linear" {
333                alternatives.push(ModeRecommendation {
334                    mode: ReasoningMode::Linear,
335                    confidence: 0.7,
336                    rationale: "Low complexity could be handled linearly".to_string(),
337                });
338            }
339        } else if response.complexity > 0.7 {
340            if response.recommended_mode != "got" {
341                alternatives.push(ModeRecommendation {
342                    mode: ReasoningMode::Got,
343                    confidence: 0.6,
344                    rationale: "High complexity might benefit from graph exploration".to_string(),
345                });
346            }
347            if response.recommended_mode != "tree" {
348                alternatives.push(ModeRecommendation {
349                    mode: ReasoningMode::Tree,
350                    confidence: 0.5,
351                    rationale: "Multi-path exploration could also work".to_string(),
352                });
353            }
354        } else {
355            // Medium complexity
356            if response.recommended_mode != "tree" {
357                alternatives.push(ModeRecommendation {
358                    mode: ReasoningMode::Tree,
359                    confidence: 0.5,
360                    rationale: "Moderate complexity could use branching".to_string(),
361                });
362            }
363        }
364
365        alternatives
366    }
367
368    /// Build messages for the Langbase pipe
369    fn build_messages(&self, params: &AutoParams) -> Vec<Message> {
370        let mut messages = Vec::new();
371
372        messages.push(Message::system(AUTO_ROUTER_PROMPT));
373
374        // Add content to analyze
375        let mut user_message = format!(
376            "Analyze this content and recommend the best reasoning mode:\n\n{}",
377            params.content
378        );
379
380        // Add hints if provided
381        if let Some(hints) = &params.hints {
382            user_message.push_str(&format!(
383                "\n\nHints about the problem:\n- {}",
384                hints.join("\n- ")
385            ));
386        }
387
388        messages.push(Message::user(user_message));
389
390        messages
391    }
392}
393
394impl AutoParams {
395    /// Create new params with content
396    pub fn new(content: impl Into<String>) -> Self {
397        Self {
398            content: content.into(),
399            hints: None,
400            session_id: None,
401        }
402    }
403
404    /// Add hints
405    pub fn with_hints(mut self, hints: Vec<String>) -> Self {
406        self.hints = Some(hints);
407        self
408    }
409
410    /// Set session ID
411    pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
412        self.session_id = Some(session_id.into());
413        self
414    }
415}
416
417#[cfg(test)]
418mod tests {
419    use super::*;
420
421    // ============================================================================
422    // AutoParams Tests
423    // ============================================================================
424
425    #[test]
426    fn test_auto_params_new() {
427        let params = AutoParams::new("Test content");
428        assert_eq!(params.content, "Test content");
429        assert!(params.hints.is_none());
430        assert!(params.session_id.is_none());
431    }
432
433    #[test]
434    fn test_auto_params_with_hints() {
435        let params = AutoParams::new("Content").with_hints(vec!["hint1".to_string()]);
436        assert_eq!(params.hints, Some(vec!["hint1".to_string()]));
437    }
438
439    #[test]
440    fn test_auto_params_with_session() {
441        let params = AutoParams::new("Content").with_session("sess-123");
442        assert_eq!(params.session_id, Some("sess-123".to_string()));
443    }
444
445    #[test]
446    fn test_auto_params_builder_chain() {
447        let params = AutoParams::new("Content")
448            .with_hints(vec!["hint1".to_string(), "hint2".to_string()])
449            .with_session("sess-abc");
450
451        assert_eq!(params.content, "Content");
452        assert_eq!(params.hints.as_ref().unwrap().len(), 2);
453        assert_eq!(params.session_id, Some("sess-abc".to_string()));
454    }
455
456    #[test]
457    fn test_auto_params_serialize() {
458        let params = AutoParams::new("Test").with_hints(vec!["h1".to_string()]);
459        let json = serde_json::to_string(&params).unwrap();
460        assert!(json.contains("Test"));
461        assert!(json.contains("hints"));
462    }
463
464    #[test]
465    fn test_auto_params_deserialize() {
466        let json = r#"{"content": "Test content", "hints": ["hint1", "hint2"]}"#;
467        let params: AutoParams = serde_json::from_str(json).unwrap();
468        assert_eq!(params.content, "Test content");
469        assert_eq!(params.hints.as_ref().unwrap().len(), 2);
470    }
471
472    #[test]
473    fn test_auto_params_deserialize_minimal() {
474        let json = r#"{"content": "Minimal"}"#;
475        let params: AutoParams = serde_json::from_str(json).unwrap();
476        assert_eq!(params.content, "Minimal");
477        assert!(params.hints.is_none());
478        assert!(params.session_id.is_none());
479    }
480
481    // ============================================================================
482    // AutoResponse Tests
483    // ============================================================================
484
485    #[test]
486    fn test_auto_response_from_json() {
487        let json = r#"{"recommended_mode": "tree", "confidence": 0.9, "rationale": "Multiple paths", "complexity": 0.6}"#;
488        let resp = AutoResponse::from_completion(json).unwrap();
489        assert_eq!(resp.recommended_mode, "tree");
490        assert_eq!(resp.confidence, 0.9);
491        assert_eq!(resp.complexity, 0.6);
492    }
493
494    #[test]
495    fn test_auto_response_from_plain_text_returns_error() {
496        let text = "Invalid JSON";
497        // All parse failures return errors (no fallbacks)
498        let result = AutoResponse::from_completion(text);
499        assert!(result.is_err());
500        let err = result.unwrap_err();
501        assert!(matches!(err, crate::error::ToolError::ParseFailed { .. }));
502    }
503
504    #[test]
505    fn test_auto_response_with_metadata() {
506        let json = r#"{
507            "recommended_mode": "divergent",
508            "confidence": 0.85,
509            "rationale": "Creative task",
510            "complexity": 0.7,
511            "metadata": {"source": "test"}
512        }"#;
513        let resp = AutoResponse::from_completion(json).unwrap();
514        assert_eq!(resp.recommended_mode, "divergent");
515        assert!(resp.metadata.is_some());
516    }
517
518    #[test]
519    fn test_auto_response_default_complexity() {
520        let json = r#"{"recommended_mode": "linear", "confidence": 0.8, "rationale": "Test"}"#;
521        let resp = AutoResponse::from_completion(json).unwrap();
522        assert_eq!(resp.complexity, 0.5); // default
523    }
524
525    #[test]
526    fn test_auto_response_all_modes() {
527        let modes = vec![
528            ("linear", ReasoningMode::Linear),
529            ("tree", ReasoningMode::Tree),
530            ("divergent", ReasoningMode::Divergent),
531            ("reflection", ReasoningMode::Reflection),
532            ("got", ReasoningMode::Got),
533        ];
534
535        for (mode_str, _expected_mode) in modes {
536            let json = format!(
537                r#"{{"recommended_mode": "{}", "confidence": 0.8, "rationale": "Test"}}"#,
538                mode_str
539            );
540            let resp = AutoResponse::from_completion(&json).unwrap();
541            assert_eq!(resp.recommended_mode, mode_str);
542        }
543    }
544
545    #[test]
546    fn test_auto_response_valid_json() {
547        let json = r#"{"recommended_mode": "tree", "confidence": 0.9, "rationale": "Test"}"#;
548        let result = AutoResponse::from_completion(json);
549        assert!(result.is_ok());
550        let resp = result.unwrap();
551        assert_eq!(resp.recommended_mode, "tree");
552    }
553
554    #[test]
555    fn test_auto_response_error_message() {
556        let invalid_json = "{ broken json";
557        let result = AutoResponse::from_completion(invalid_json);
558        assert!(result.is_err());
559        let err_str = result.unwrap_err().to_string();
560        assert!(err_str.contains("Parse error in auto mode"));
561        assert!(err_str.contains("broken json"));
562    }
563
564    // ============================================================================
565    // AutoResult Tests
566    // ============================================================================
567
568    #[test]
569    fn test_auto_result_serialize() {
570        let result = AutoResult {
571            recommended_mode: ReasoningMode::Tree,
572            confidence: 0.85,
573            rationale: "Multiple paths needed".to_string(),
574            complexity: 0.6,
575            alternative_modes: vec![ModeRecommendation {
576                mode: ReasoningMode::Divergent,
577                confidence: 0.6,
578                rationale: "Could also be creative".to_string(),
579            }],
580            fallback_used: false,
581            original_invalid_mode: None,
582        };
583        let json = serde_json::to_string(&result).unwrap();
584        assert!(json.contains("tree"));
585        assert!(json.contains("0.85"));
586        assert!(json.contains("alternative_modes"));
587    }
588
589    #[test]
590    fn test_auto_result_deserialize() {
591        let json = r#"{
592            "recommended_mode": "linear",
593            "confidence": 0.9,
594            "rationale": "Simple task",
595            "complexity": 0.2,
596            "alternative_modes": []
597        }"#;
598        let result: AutoResult = serde_json::from_str(json).unwrap();
599        assert_eq!(result.recommended_mode, ReasoningMode::Linear);
600        assert_eq!(result.confidence, 0.9);
601        assert!(result.alternative_modes.is_empty());
602    }
603
604    #[test]
605    fn test_auto_result_with_alternatives() {
606        let result = AutoResult {
607            recommended_mode: ReasoningMode::Got,
608            confidence: 0.75,
609            rationale: "Complex system".to_string(),
610            complexity: 0.8,
611            alternative_modes: vec![
612                ModeRecommendation {
613                    mode: ReasoningMode::Tree,
614                    confidence: 0.6,
615                    rationale: "Alt 1".to_string(),
616                },
617                ModeRecommendation {
618                    mode: ReasoningMode::Divergent,
619                    confidence: 0.4,
620                    rationale: "Alt 2".to_string(),
621                },
622            ],
623            fallback_used: false,
624            original_invalid_mode: None,
625        };
626        let json = serde_json::to_string(&result).unwrap();
627        let parsed: AutoResult = serde_json::from_str(&json).unwrap();
628        assert_eq!(parsed.alternative_modes.len(), 2);
629    }
630
631    #[test]
632    fn test_auto_result_fallback_fields_default() {
633        // Test that fallback_used defaults to false when deserializing JSON without it
634        let json = r#"{
635            "recommended_mode": "linear",
636            "confidence": 0.9,
637            "rationale": "test",
638            "complexity": 0.5,
639            "alternative_modes": []
640        }"#;
641        let result: AutoResult = serde_json::from_str(json).unwrap();
642        assert!(!result.fallback_used);
643        assert!(result.original_invalid_mode.is_none());
644    }
645
646    #[test]
647    fn test_auto_result_with_fallback() {
648        // Test result with fallback indicators set
649        let result = AutoResult {
650            recommended_mode: ReasoningMode::Linear,
651            confidence: 0.5,
652            rationale: "Fallback due to invalid mode".to_string(),
653            complexity: 0.5,
654            alternative_modes: vec![],
655            fallback_used: true,
656            original_invalid_mode: Some("invalid_mode_xyz".to_string()),
657        };
658        let json = serde_json::to_string(&result).unwrap();
659        assert!(json.contains("\"fallback_used\":true"));
660        assert!(json.contains("\"original_invalid_mode\":\"invalid_mode_xyz\""));
661    }
662
663    #[test]
664    fn test_auto_result_fallback_not_serialized_when_none() {
665        // Test that original_invalid_mode is skipped when None
666        let result = AutoResult {
667            recommended_mode: ReasoningMode::Linear,
668            confidence: 0.9,
669            rationale: "test".to_string(),
670            complexity: 0.5,
671            alternative_modes: vec![],
672            fallback_used: false,
673            original_invalid_mode: None,
674        };
675        let json = serde_json::to_string(&result).unwrap();
676        // original_invalid_mode should be skipped when None
677        assert!(!json.contains("original_invalid_mode"));
678        // fallback_used should still be present
679        assert!(json.contains("\"fallback_used\":false"));
680    }
681
682    #[test]
683    fn test_auto_result_fallback_round_trip() {
684        // Test that fallback fields survive serialization round-trip
685        let original = AutoResult {
686            recommended_mode: ReasoningMode::Linear,
687            confidence: 0.5,
688            rationale: "Fallback test".to_string(),
689            complexity: 0.5,
690            alternative_modes: vec![],
691            fallback_used: true,
692            original_invalid_mode: Some("bad_mode".to_string()),
693        };
694        let json = serde_json::to_string(&original).unwrap();
695        let parsed: AutoResult = serde_json::from_str(&json).unwrap();
696        assert!(parsed.fallback_used);
697        assert_eq!(parsed.original_invalid_mode, Some("bad_mode".to_string()));
698    }
699
700    // ============================================================================
701    // ModeRecommendation Tests
702    // ============================================================================
703
704    #[test]
705    fn test_mode_recommendation_serialize() {
706        let rec = ModeRecommendation {
707            mode: ReasoningMode::Tree,
708            confidence: 0.8,
709            rationale: "Test".to_string(),
710        };
711        let json = serde_json::to_string(&rec).unwrap();
712        assert!(json.contains("tree"));
713    }
714
715    #[test]
716    fn test_mode_recommendation_deserialize() {
717        let json = r#"{"mode": "reflection", "confidence": 0.7, "rationale": "Needs evaluation"}"#;
718        let rec: ModeRecommendation = serde_json::from_str(json).unwrap();
719        assert_eq!(rec.mode, ReasoningMode::Reflection);
720        assert_eq!(rec.confidence, 0.7);
721    }
722
723    #[test]
724    fn test_mode_recommendation_all_modes() {
725        let modes = vec![
726            ReasoningMode::Linear,
727            ReasoningMode::Tree,
728            ReasoningMode::Divergent,
729            ReasoningMode::Reflection,
730            ReasoningMode::Got,
731        ];
732
733        for mode in modes {
734            let rec = ModeRecommendation {
735                mode,
736                confidence: 0.5,
737                rationale: "Test".to_string(),
738            };
739            let json = serde_json::to_string(&rec).unwrap();
740            let parsed: ModeRecommendation = serde_json::from_str(&json).unwrap();
741            assert_eq!(parsed.mode, mode);
742        }
743    }
744
745    // ============================================================================
746    // Default Function Tests
747    // ============================================================================
748
749    #[test]
750    fn test_default_complexity() {
751        assert_eq!(default_complexity(), 0.5);
752    }
753
754    // ============================================================================
755    // ReasoningMode Parse Tests
756    // ============================================================================
757
758    #[test]
759    fn test_reasoning_mode_from_string() {
760        assert_eq!(
761            "linear".parse::<ReasoningMode>().unwrap(),
762            ReasoningMode::Linear
763        );
764        assert_eq!(
765            "tree".parse::<ReasoningMode>().unwrap(),
766            ReasoningMode::Tree
767        );
768        assert_eq!(
769            "divergent".parse::<ReasoningMode>().unwrap(),
770            ReasoningMode::Divergent
771        );
772        assert_eq!(
773            "reflection".parse::<ReasoningMode>().unwrap(),
774            ReasoningMode::Reflection
775        );
776        assert_eq!("got".parse::<ReasoningMode>().unwrap(), ReasoningMode::Got);
777    }
778
779    #[test]
780    fn test_reasoning_mode_invalid_string() {
781        assert!("invalid".parse::<ReasoningMode>().is_err());
782        assert!("unknown".parse::<ReasoningMode>().is_err());
783        assert!("".parse::<ReasoningMode>().is_err());
784    }
785
786    // ============================================================================
787    // Edge Case Tests
788    // ============================================================================
789
790    #[test]
791    fn test_auto_params_empty_content() {
792        let params = AutoParams::new("");
793        assert_eq!(params.content, "");
794    }
795
796    #[test]
797    fn test_auto_params_empty_hints() {
798        let params = AutoParams::new("Content").with_hints(vec![]);
799        assert_eq!(params.hints, Some(vec![]));
800    }
801
802    #[test]
803    fn test_auto_response_zero_confidence() {
804        let json =
805            r#"{"recommended_mode": "linear", "confidence": 0.0, "rationale": "No confidence"}"#;
806        let resp = AutoResponse::from_completion(json).unwrap();
807        assert_eq!(resp.confidence, 0.0);
808    }
809
810    #[test]
811    fn test_auto_response_max_confidence() {
812        let json =
813            r#"{"recommended_mode": "linear", "confidence": 1.0, "rationale": "Full confidence"}"#;
814        let resp = AutoResponse::from_completion(json).unwrap();
815        assert_eq!(resp.confidence, 1.0);
816    }
817
818    #[test]
819    fn test_auto_result_empty_alternatives() {
820        let result = AutoResult {
821            recommended_mode: ReasoningMode::Linear,
822            confidence: 0.9,
823            rationale: "Simple".to_string(),
824            complexity: 0.1,
825            alternative_modes: vec![],
826            fallback_used: false,
827            original_invalid_mode: None,
828        };
829        assert!(result.alternative_modes.is_empty());
830    }
831
832    #[test]
833    fn test_mode_recommendation_zero_confidence() {
834        let rec = ModeRecommendation {
835            mode: ReasoningMode::Linear,
836            confidence: 0.0,
837            rationale: "Low confidence alt".to_string(),
838        };
839        assert_eq!(rec.confidence, 0.0);
840    }
841
842    // ============================================================================
843    // Serialization Round-Trip Tests
844    // ============================================================================
845
846    #[test]
847    fn test_auto_params_round_trip() {
848        let original = AutoParams::new("Complex content")
849            .with_hints(vec!["hint1".to_string(), "hint2".to_string()])
850            .with_session("sess-xyz");
851
852        let json = serde_json::to_string(&original).unwrap();
853        let parsed: AutoParams = serde_json::from_str(&json).unwrap();
854
855        assert_eq!(parsed.content, original.content);
856        assert_eq!(parsed.hints, original.hints);
857        assert_eq!(parsed.session_id, original.session_id);
858    }
859
860    #[test]
861    fn test_auto_result_round_trip() {
862        let original = AutoResult {
863            recommended_mode: ReasoningMode::Divergent,
864            confidence: 0.87,
865            rationale: "Creative exploration needed".to_string(),
866            complexity: 0.65,
867            alternative_modes: vec![ModeRecommendation {
868                mode: ReasoningMode::Tree,
869                confidence: 0.55,
870                rationale: "Could branch".to_string(),
871            }],
872            fallback_used: false,
873            original_invalid_mode: None,
874        };
875
876        let json = serde_json::to_string(&original).unwrap();
877        let parsed: AutoResult = serde_json::from_str(&json).unwrap();
878
879        assert_eq!(parsed.recommended_mode, original.recommended_mode);
880        assert_eq!(parsed.confidence, original.confidence);
881        assert_eq!(parsed.rationale, original.rationale);
882        assert_eq!(parsed.complexity, original.complexity);
883        assert_eq!(parsed.fallback_used, original.fallback_used);
884        assert_eq!(parsed.original_invalid_mode, original.original_invalid_mode);
885        assert_eq!(parsed.alternative_modes.len(), 1);
886    }
887
888    // ============================================================================
889    // local_heuristics() Tests
890    // ============================================================================
891
892    #[test]
893    fn test_local_heuristics_short_content() {
894        let mode = create_test_mode();
895        let params = AutoParams::new("Short text");
896        let result = mode.local_heuristics(&params).unwrap();
897        assert_eq!(result.recommended_mode, ReasoningMode::Linear);
898        assert_eq!(result.confidence, 0.9);
899        assert_eq!(result.complexity, 0.2);
900    }
901
902    #[test]
903    fn test_local_heuristics_evaluate_keyword() {
904        let mode = create_test_mode();
905        let params =
906            AutoParams::new("Please evaluate this solution for correctness and efficiency");
907        let result = mode.local_heuristics(&params).unwrap();
908        assert_eq!(result.recommended_mode, ReasoningMode::Reflection);
909        assert_eq!(result.confidence, 0.85);
910        assert_eq!(result.alternative_modes.len(), 1);
911    }
912
913    #[test]
914    fn test_local_heuristics_assess_keyword() {
915        let mode = create_test_mode();
916        let params = AutoParams::new("I need you to assess the quality of this implementation");
917        let result = mode.local_heuristics(&params).unwrap();
918        assert_eq!(result.recommended_mode, ReasoningMode::Reflection);
919    }
920
921    #[test]
922    fn test_local_heuristics_review_quality_keyword() {
923        let mode = create_test_mode();
924        let params = AutoParams::new(
925            "Can you review quality of the architecture design and implementation patterns?",
926        );
927        let result = mode.local_heuristics(&params).unwrap();
928        assert_eq!(result.recommended_mode, ReasoningMode::Reflection);
929    }
930
931    #[test]
932    fn test_local_heuristics_critique_keyword() {
933        let mode = create_test_mode();
934        let params = AutoParams::new("Please critique this approach to see if it works effectively and meets our requirements");
935        let result = mode.local_heuristics(&params).unwrap();
936        assert_eq!(result.recommended_mode, ReasoningMode::Reflection);
937    }
938
939    #[test]
940    fn test_local_heuristics_creative_keyword() {
941        let mode = create_test_mode();
942        let params = AutoParams::new("We need a creative solution to this unique problem that differs from existing approaches");
943        let result = mode.local_heuristics(&params).unwrap();
944        assert_eq!(result.recommended_mode, ReasoningMode::Divergent);
945        assert_eq!(result.confidence, 0.85);
946    }
947
948    #[test]
949    fn test_local_heuristics_brainstorm_keyword() {
950        let mode = create_test_mode();
951        let params = AutoParams::new(
952            "Let's brainstorm ideas for improving user engagement and retention metrics",
953        );
954        let result = mode.local_heuristics(&params).unwrap();
955        assert_eq!(result.recommended_mode, ReasoningMode::Divergent);
956    }
957
958    #[test]
959    fn test_local_heuristics_novel_keyword() {
960        let mode = create_test_mode();
961        let params = AutoParams::new(
962            "We need novel approaches to solve this challenge in the competitive market",
963        );
964        let result = mode.local_heuristics(&params).unwrap();
965        assert_eq!(result.recommended_mode, ReasoningMode::Divergent);
966    }
967
968    #[test]
969    fn test_local_heuristics_unconventional_keyword() {
970        let mode = create_test_mode();
971        let params = AutoParams::new(
972            "Looking for unconventional strategies to differentiate our product offering",
973        );
974        let result = mode.local_heuristics(&params).unwrap();
975        assert_eq!(result.recommended_mode, ReasoningMode::Divergent);
976    }
977
978    #[test]
979    fn test_local_heuristics_options_keyword() {
980        let mode = create_test_mode();
981        let params = AutoParams::new("What options do we have for implementing authentication?");
982        let result = mode.local_heuristics(&params).unwrap();
983        assert_eq!(result.recommended_mode, ReasoningMode::Tree);
984        assert_eq!(result.confidence, 0.8);
985    }
986
987    #[test]
988    fn test_local_heuristics_alternatives_keyword() {
989        let mode = create_test_mode();
990        let params = AutoParams::new("Consider alternatives to the current database design");
991        let result = mode.local_heuristics(&params).unwrap();
992        assert_eq!(result.recommended_mode, ReasoningMode::Tree);
993    }
994
995    #[test]
996    fn test_local_heuristics_compare_keyword() {
997        let mode = create_test_mode();
998        let params = AutoParams::new(
999            "Compare different approaches to API versioning and list their pros and cons",
1000        );
1001        let result = mode.local_heuristics(&params).unwrap();
1002        assert_eq!(result.recommended_mode, ReasoningMode::Tree);
1003    }
1004
1005    #[test]
1006    fn test_local_heuristics_tradeoffs_keyword() {
1007        let mode = create_test_mode();
1008        let params = AutoParams::new(
1009            "Analyze the trade-offs between consistency and availability in distributed systems",
1010        );
1011        let result = mode.local_heuristics(&params).unwrap();
1012        assert_eq!(result.recommended_mode, ReasoningMode::Tree);
1013    }
1014
1015    #[test]
1016    fn test_local_heuristics_complex_system_keyword() {
1017        let mode = create_test_mode();
1018        let params = AutoParams::new(
1019            "We have a complex system with many interdependencies that need careful analysis",
1020        );
1021        let result = mode.local_heuristics(&params).unwrap();
1022        assert_eq!(result.recommended_mode, ReasoningMode::Got);
1023        assert_eq!(result.confidence, 0.75);
1024        assert_eq!(result.complexity, 0.8);
1025    }
1026
1027    #[test]
1028    fn test_local_heuristics_interconnected_keyword() {
1029        let mode = create_test_mode();
1030        let params = AutoParams::new(
1031            "These services are highly interconnected across multiple domains and boundaries",
1032        );
1033        let result = mode.local_heuristics(&params).unwrap();
1034        assert_eq!(result.recommended_mode, ReasoningMode::Got);
1035    }
1036
1037    #[test]
1038    fn test_local_heuristics_multistep_keyword() {
1039        let mode = create_test_mode();
1040        let params = AutoParams::new(
1041            "This requires a multi-step deployment process with various dependencies and stages",
1042        );
1043        let result = mode.local_heuristics(&params).unwrap();
1044        assert_eq!(result.recommended_mode, ReasoningMode::Got);
1045    }
1046
1047    #[test]
1048    fn test_local_heuristics_graph_keyword() {
1049        let mode = create_test_mode();
1050        let params = AutoParams::new("Analyze this graph of dependencies between services and components in our architecture");
1051        let result = mode.local_heuristics(&params).unwrap();
1052        assert_eq!(result.recommended_mode, ReasoningMode::Got);
1053    }
1054
1055    #[test]
1056    fn test_local_heuristics_no_match() {
1057        let mode = create_test_mode();
1058        let params = AutoParams::new("Just a regular query without special keywords that would trigger heuristics and patterns");
1059        let result = mode.local_heuristics(&params);
1060        assert!(result.is_none());
1061    }
1062
1063    #[test]
1064    fn test_local_heuristics_case_insensitive() {
1065        let mode = create_test_mode();
1066        let params = AutoParams::new(
1067            "BRAINSTORM new IDEAS for this CREATIVE project with innovative solutions",
1068        );
1069        let result = mode.local_heuristics(&params).unwrap();
1070        assert_eq!(result.recommended_mode, ReasoningMode::Divergent);
1071    }
1072
1073    // ============================================================================
1074    // generate_alternatives() Tests
1075    // ============================================================================
1076
1077    #[test]
1078    fn test_generate_alternatives_low_complexity() {
1079        let mode = create_test_mode();
1080        let response = AutoResponse {
1081            recommended_mode: "tree".to_string(),
1082            confidence: 0.8,
1083            rationale: "Test".to_string(),
1084            complexity: 0.2, // Low complexity
1085            metadata: None,
1086        };
1087        let alternatives = mode.generate_alternatives(&response);
1088        assert!(!alternatives.is_empty());
1089        assert_eq!(alternatives[0].mode, ReasoningMode::Linear);
1090        assert_eq!(alternatives[0].confidence, 0.7);
1091    }
1092
1093    #[test]
1094    fn test_generate_alternatives_low_complexity_already_linear() {
1095        let mode = create_test_mode();
1096        let response = AutoResponse {
1097            recommended_mode: "linear".to_string(),
1098            confidence: 0.9,
1099            rationale: "Simple".to_string(),
1100            complexity: 0.1,
1101            metadata: None,
1102        };
1103        let alternatives = mode.generate_alternatives(&response);
1104        // Should not suggest linear if already recommended
1105        assert!(alternatives.is_empty() || alternatives[0].mode != ReasoningMode::Linear);
1106    }
1107
1108    #[test]
1109    fn test_generate_alternatives_high_complexity() {
1110        let mode = create_test_mode();
1111        let response = AutoResponse {
1112            recommended_mode: "linear".to_string(),
1113            confidence: 0.7,
1114            rationale: "Test".to_string(),
1115            complexity: 0.8, // High complexity
1116            metadata: None,
1117        };
1118        let alternatives = mode.generate_alternatives(&response);
1119        assert!(!alternatives.is_empty());
1120        // Should suggest GoT for high complexity
1121        assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Got));
1122    }
1123
1124    #[test]
1125    fn test_generate_alternatives_high_complexity_already_got() {
1126        let mode = create_test_mode();
1127        let response = AutoResponse {
1128            recommended_mode: "got".to_string(),
1129            confidence: 0.9,
1130            rationale: "Complex".to_string(),
1131            complexity: 0.9,
1132            metadata: None,
1133        };
1134        let alternatives = mode.generate_alternatives(&response);
1135        // Should suggest Tree as alternative
1136        assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Tree));
1137    }
1138
1139    #[test]
1140    fn test_generate_alternatives_medium_complexity() {
1141        let mode = create_test_mode();
1142        let response = AutoResponse {
1143            recommended_mode: "linear".to_string(),
1144            confidence: 0.8,
1145            rationale: "Test".to_string(),
1146            complexity: 0.5, // Medium complexity
1147            metadata: None,
1148        };
1149        let alternatives = mode.generate_alternatives(&response);
1150        assert!(!alternatives.is_empty());
1151        // Should suggest Tree for medium complexity
1152        assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Tree));
1153    }
1154
1155    #[test]
1156    fn test_generate_alternatives_medium_complexity_already_tree() {
1157        let mode = create_test_mode();
1158        let response = AutoResponse {
1159            recommended_mode: "tree".to_string(),
1160            confidence: 0.85,
1161            rationale: "Branching".to_string(),
1162            complexity: 0.5,
1163            metadata: None,
1164        };
1165        let alternatives = mode.generate_alternatives(&response);
1166        // Should not suggest Tree if already recommended
1167        assert!(
1168            alternatives.is_empty() || !alternatives.iter().any(|a| a.mode == ReasoningMode::Tree)
1169        );
1170    }
1171
1172    // ============================================================================
1173    // build_messages() Tests
1174    // ============================================================================
1175
1176    #[test]
1177    fn test_build_messages_basic() {
1178        let mode = create_test_mode();
1179        let params = AutoParams::new("Test content for analysis");
1180        let messages = mode.build_messages(&params);
1181
1182        assert_eq!(messages.len(), 2);
1183        // First message should be system with AUTO_ROUTER_PROMPT
1184        assert!(messages[0].content.contains("reasoning mode"));
1185        // Second message should be user with content
1186        assert!(messages[1].content.contains("Test content for analysis"));
1187    }
1188
1189    #[test]
1190    fn test_build_messages_with_hints() {
1191        let mode = create_test_mode();
1192        let params = AutoParams::new("Complex problem").with_hints(vec![
1193            "performance critical".to_string(),
1194            "needs scalability".to_string(),
1195        ]);
1196        let messages = mode.build_messages(&params);
1197
1198        assert_eq!(messages.len(), 2);
1199        assert!(messages[1].content.contains("Hints about the problem"));
1200        assert!(messages[1].content.contains("performance critical"));
1201        assert!(messages[1].content.contains("needs scalability"));
1202    }
1203
1204    #[test]
1205    fn test_build_messages_with_single_hint() {
1206        let mode = create_test_mode();
1207        let params = AutoParams::new("Question").with_hints(vec!["security".to_string()]);
1208        let messages = mode.build_messages(&params);
1209
1210        assert!(messages[1].content.contains("Hints"));
1211        assert!(messages[1].content.contains("security"));
1212    }
1213
1214    #[test]
1215    fn test_build_messages_with_empty_hints() {
1216        let mode = create_test_mode();
1217        let params = AutoParams::new("Question").with_hints(vec![]);
1218        let messages = mode.build_messages(&params);
1219
1220        // Empty hints should still be included
1221        assert!(messages[1].content.contains("Hints"));
1222    }
1223
1224    #[test]
1225    fn test_build_messages_no_hints() {
1226        let mode = create_test_mode();
1227        let params = AutoParams::new("Simple question");
1228        let messages = mode.build_messages(&params);
1229
1230        assert_eq!(messages.len(), 2);
1231        assert!(!messages[1].content.contains("Hints"));
1232    }
1233
1234    #[test]
1235    fn test_build_messages_long_content() {
1236        let mode = create_test_mode();
1237        let long_content = "A".repeat(5000);
1238        let params = AutoParams::new(long_content.clone());
1239        let messages = mode.build_messages(&params);
1240
1241        assert_eq!(messages.len(), 2);
1242        assert!(messages[1].content.contains(&long_content));
1243    }
1244
1245    #[test]
1246    fn test_build_messages_special_characters_in_content() {
1247        let mode = create_test_mode();
1248        let params = AutoParams::new("Special: \n\t\"quotes\" and <brackets>");
1249        let messages = mode.build_messages(&params);
1250
1251        assert_eq!(messages.len(), 2);
1252        assert!(messages[1].content.contains("Special:"));
1253    }
1254
1255    #[test]
1256    fn test_build_messages_special_characters_in_hints() {
1257        let mode = create_test_mode();
1258        let params = AutoParams::new("Question").with_hints(vec![
1259            "hint with \"quotes\"".to_string(),
1260            "hint with \nnewlines".to_string(),
1261        ]);
1262        let messages = mode.build_messages(&params);
1263
1264        assert!(messages[1].content.contains("Hints"));
1265        assert!(messages[1].content.contains("quotes"));
1266    }
1267
1268    // ============================================================================
1269    // AutoMode Constructor Tests
1270    // ============================================================================
1271
1272    #[test]
1273    fn test_auto_mode_new_creates_instance() {
1274        let mode = create_test_mode();
1275        // Verify the mode is created successfully
1276        assert_eq!(mode.pipe_name, "mode-router-v1");
1277    }
1278
1279    #[test]
1280    fn test_auto_mode_new_with_custom_pipe() {
1281        use crate::config::{
1282            Config, DatabaseConfig, ErrorHandlingConfig, LangbaseConfig, LogFormat, LoggingConfig,
1283            PipeConfig, RequestConfig,
1284        };
1285        use std::path::PathBuf;
1286
1287        let langbase_config = LangbaseConfig {
1288            api_key: "test-key".to_string(),
1289            base_url: "https://api.langbase.com".to_string(),
1290        };
1291
1292        let pipes = PipeConfig {
1293            auto: Some("custom-auto-pipe".to_string()),
1294            ..Default::default()
1295        };
1296
1297        let config = Config {
1298            langbase: langbase_config.clone(),
1299            database: DatabaseConfig {
1300                path: PathBuf::from(":memory:"),
1301                max_connections: 5,
1302            },
1303            logging: LoggingConfig {
1304                level: "info".to_string(),
1305                format: LogFormat::Pretty,
1306            },
1307            request: RequestConfig::default(),
1308            pipes,
1309            error_handling: ErrorHandlingConfig::default(),
1310        };
1311
1312        let rt = tokio::runtime::Runtime::new().unwrap();
1313        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1314        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1315
1316        let mode = AutoMode::new(storage, langbase, &config);
1317        assert_eq!(mode.pipe_name, "custom-auto-pipe");
1318    }
1319
1320    #[test]
1321    fn test_auto_mode_new_without_custom_pipe_uses_default() {
1322        use crate::config::{
1323            Config, DatabaseConfig, LangbaseConfig, LogFormat, LoggingConfig, PipeConfig,
1324            RequestConfig,
1325        };
1326        use std::path::PathBuf;
1327
1328        let langbase_config = LangbaseConfig {
1329            api_key: "test-key".to_string(),
1330            base_url: "https://api.langbase.com".to_string(),
1331        };
1332
1333        let pipes = PipeConfig {
1334            auto: None, // Explicitly set to None
1335            ..Default::default()
1336        };
1337
1338        let config = Config {
1339            langbase: langbase_config.clone(),
1340            database: DatabaseConfig {
1341                path: PathBuf::from(":memory:"),
1342                max_connections: 5,
1343            },
1344            logging: LoggingConfig {
1345                level: "info".to_string(),
1346                format: LogFormat::Pretty,
1347            },
1348            request: RequestConfig::default(),
1349            pipes,
1350            error_handling: crate::config::ErrorHandlingConfig::default(),
1351        };
1352
1353        let rt = tokio::runtime::Runtime::new().unwrap();
1354        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1355        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1356
1357        let mode = AutoMode::new(storage, langbase, &config);
1358        assert_eq!(mode.pipe_name, "mode-router-v1");
1359    }
1360
1361    // ============================================================================
1362    // AutoResponse Edge Cases
1363    // ============================================================================
1364
1365    #[test]
1366    fn test_auto_response_from_empty_json_returns_error() {
1367        let json = "{}";
1368        // Empty JSON is missing required fields, returns error
1369        let result = AutoResponse::from_completion(json);
1370        assert!(result.is_err());
1371    }
1372
1373    #[test]
1374    fn test_auto_response_from_json_missing_rationale_returns_error() {
1375        let json = r#"{"recommended_mode": "tree", "confidence": 0.8}"#;
1376        // Missing required field 'rationale', returns error
1377        let result = AutoResponse::from_completion(json);
1378        assert!(result.is_err());
1379    }
1380
1381    #[test]
1382    fn test_auto_response_from_json_null_metadata() {
1383        let json = r#"{
1384            "recommended_mode": "reflection",
1385            "confidence": 0.82,
1386            "rationale": "Test",
1387            "complexity": 0.5,
1388            "metadata": null
1389        }"#;
1390        let resp = AutoResponse::from_completion(json).unwrap();
1391        assert_eq!(resp.recommended_mode, "reflection");
1392        assert!(resp.metadata.is_none());
1393    }
1394
1395    #[test]
1396    fn test_auto_response_from_json_empty_string_mode() {
1397        let json = r#"{"recommended_mode": "", "confidence": 0.8, "rationale": "Test"}"#;
1398        let resp = AutoResponse::from_completion(json).unwrap();
1399        assert_eq!(resp.recommended_mode, "");
1400    }
1401
1402    #[test]
1403    fn test_auto_response_from_json_invalid_mode_string() {
1404        let json =
1405            r#"{"recommended_mode": "invalid_mode", "confidence": 0.8, "rationale": "Test"}"#;
1406        let resp = AutoResponse::from_completion(json).unwrap();
1407        assert_eq!(resp.recommended_mode, "invalid_mode");
1408    }
1409
1410    #[test]
1411    fn test_auto_response_from_json_negative_confidence() {
1412        let json = r#"{"recommended_mode": "linear", "confidence": -0.5, "rationale": "Test"}"#;
1413        let resp = AutoResponse::from_completion(json).unwrap();
1414        assert_eq!(resp.confidence, -0.5); // No clamping in AutoResponse
1415    }
1416
1417    #[test]
1418    fn test_auto_response_from_json_over_one_confidence() {
1419        let json = r#"{"recommended_mode": "linear", "confidence": 1.5, "rationale": "Test"}"#;
1420        let resp = AutoResponse::from_completion(json).unwrap();
1421        assert_eq!(resp.confidence, 1.5); // No clamping in AutoResponse
1422    }
1423
1424    #[test]
1425    fn test_auto_response_from_json_negative_complexity() {
1426        let json = r#"{"recommended_mode": "linear", "confidence": 0.8, "rationale": "Test", "complexity": -0.3}"#;
1427        let resp = AutoResponse::from_completion(json).unwrap();
1428        assert_eq!(resp.complexity, -0.3);
1429    }
1430
1431    #[test]
1432    fn test_auto_response_from_json_over_one_complexity() {
1433        let json = r#"{"recommended_mode": "linear", "confidence": 0.8, "rationale": "Test", "complexity": 1.5}"#;
1434        let resp = AutoResponse::from_completion(json).unwrap();
1435        assert_eq!(resp.complexity, 1.5);
1436    }
1437
1438    #[test]
1439    fn test_auto_response_from_long_preview_text_returns_error() {
1440        let long_text = "Not JSON: ".to_owned() + &"a".repeat(300);
1441        // Non-JSON input returns error
1442        let result = AutoResponse::from_completion(&long_text);
1443        assert!(result.is_err());
1444    }
1445
1446    #[test]
1447    fn test_auto_response_from_backtracking_mode() {
1448        let json = r#"{"recommended_mode": "backtracking", "confidence": 0.85, "rationale": "Needs backtracking"}"#;
1449        let resp = AutoResponse::from_completion(json).unwrap();
1450        assert_eq!(resp.recommended_mode, "backtracking");
1451    }
1452
1453    // ============================================================================
1454    // local_heuristics() Additional Coverage
1455    // ============================================================================
1456
1457    #[test]
1458    fn test_local_heuristics_exactly_50_chars() {
1459        let mode = create_test_mode();
1460        let params = AutoParams::new("A".repeat(50));
1461        let result = mode.local_heuristics(&params);
1462        // Should NOT trigger short content heuristic (< 50)
1463        assert!(result.is_none());
1464    }
1465
1466    #[test]
1467    fn test_local_heuristics_49_chars() {
1468        let mode = create_test_mode();
1469        let params = AutoParams::new("A".repeat(49));
1470        let result = mode.local_heuristics(&params).unwrap();
1471        assert_eq!(result.recommended_mode, ReasoningMode::Linear);
1472    }
1473
1474    #[test]
1475    fn test_local_heuristics_multiple_keywords_first_wins() {
1476        let mode = create_test_mode();
1477        // Contains both "evaluate" (reflection) and "creative" (divergent)
1478        // Reflection check comes first, should win
1479        let params = AutoParams::new("Please evaluate this creative solution with novel ideas");
1480        let result = mode.local_heuristics(&params).unwrap();
1481        assert_eq!(result.recommended_mode, ReasoningMode::Reflection);
1482    }
1483
1484    #[test]
1485    fn test_local_heuristics_mixed_case_keywords() {
1486        let mode = create_test_mode();
1487        // Content must be >= 50 chars to bypass short content check
1488        let params = AutoParams::new(
1489            "EVALUATE this CREATIVE approach with NOVEL IDEAS and more context here",
1490        );
1491        let result = mode.local_heuristics(&params).unwrap();
1492        // Should match case-insensitively
1493        assert_eq!(result.recommended_mode, ReasoningMode::Reflection);
1494    }
1495
1496    #[test]
1497    fn test_local_heuristics_keyword_in_middle() {
1498        let mode = create_test_mode();
1499        let params = AutoParams::new("The system has many interconnected parts that need analysis");
1500        let result = mode.local_heuristics(&params).unwrap();
1501        assert_eq!(result.recommended_mode, ReasoningMode::Got);
1502    }
1503
1504    #[test]
1505    fn test_local_heuristics_partial_keyword_no_match() {
1506        let mode = create_test_mode();
1507        // "creating" contains "creative" but as substring
1508        let params = AutoParams::new("We are creating a new system for data processing");
1509        let result = mode.local_heuristics(&params);
1510        // "creative" should still match as substring via contains()
1511        assert!(result.is_some());
1512    }
1513
1514    #[test]
1515    fn test_local_heuristics_whitespace_around_keywords() {
1516        let mode = create_test_mode();
1517        // Content must be >= 50 chars to bypass short content check
1518        let params =
1519            AutoParams::new("  evaluate  this  approach  with significant context padding  ");
1520        let result = mode.local_heuristics(&params).unwrap();
1521        assert_eq!(result.recommended_mode, ReasoningMode::Reflection);
1522    }
1523
1524    #[test]
1525    fn test_local_heuristics_backtracking_not_detected() {
1526        let mode = create_test_mode();
1527        // Content must be >= 50 chars to bypass short content check
1528        let params = AutoParams::new(
1529            "This requires backtracking to find the solution with additional context",
1530        );
1531        let result = mode.local_heuristics(&params);
1532        // "backtracking" is not in the heuristics keywords
1533        assert!(result.is_none());
1534    }
1535
1536    #[test]
1537    fn test_local_heuristics_empty_string() {
1538        let mode = create_test_mode();
1539        let params = AutoParams::new("");
1540        let result = mode.local_heuristics(&params).unwrap();
1541        assert_eq!(result.recommended_mode, ReasoningMode::Linear);
1542        assert_eq!(result.complexity, 0.2);
1543    }
1544
1545    #[test]
1546    fn test_local_heuristics_single_char() {
1547        let mode = create_test_mode();
1548        let params = AutoParams::new("x");
1549        let result = mode.local_heuristics(&params).unwrap();
1550        assert_eq!(result.recommended_mode, ReasoningMode::Linear);
1551    }
1552
1553    // ============================================================================
1554    // generate_alternatives() Complete Coverage
1555    // ============================================================================
1556
1557    #[test]
1558    fn test_generate_alternatives_boundary_low_complexity() {
1559        let mode = create_test_mode();
1560        let response = AutoResponse {
1561            recommended_mode: "tree".to_string(),
1562            confidence: 0.8,
1563            rationale: "Test".to_string(),
1564            complexity: 0.29, // Just below 0.3
1565            metadata: None,
1566        };
1567        let alternatives = mode.generate_alternatives(&response);
1568        assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Linear));
1569    }
1570
1571    #[test]
1572    fn test_generate_alternatives_boundary_high_complexity() {
1573        let mode = create_test_mode();
1574        let response = AutoResponse {
1575            recommended_mode: "linear".to_string(),
1576            confidence: 0.7,
1577            rationale: "Test".to_string(),
1578            complexity: 0.71, // Just above 0.7
1579            metadata: None,
1580        };
1581        let alternatives = mode.generate_alternatives(&response);
1582        assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Got));
1583    }
1584
1585    #[test]
1586    fn test_generate_alternatives_exactly_0_3_complexity() {
1587        let mode = create_test_mode();
1588        let response = AutoResponse {
1589            recommended_mode: "tree".to_string(),
1590            confidence: 0.8,
1591            rationale: "Test".to_string(),
1592            complexity: 0.3, // Boundary
1593            metadata: None,
1594        };
1595        let alternatives = mode.generate_alternatives(&response);
1596        // Should NOT suggest linear (complexity >= 0.3)
1597        assert!(
1598            alternatives.is_empty()
1599                || !alternatives.iter().any(|a| a.mode == ReasoningMode::Linear)
1600        );
1601    }
1602
1603    #[test]
1604    fn test_generate_alternatives_exactly_0_7_complexity() {
1605        let mode = create_test_mode();
1606        let response = AutoResponse {
1607            recommended_mode: "linear".to_string(),
1608            confidence: 0.8,
1609            rationale: "Test".to_string(),
1610            complexity: 0.7, // Boundary
1611            metadata: None,
1612        };
1613        let alternatives = mode.generate_alternatives(&response);
1614        // Should NOT suggest high-complexity modes (complexity <= 0.7)
1615        assert!(
1616            alternatives.is_empty() || !alternatives.iter().any(|a| a.mode == ReasoningMode::Got)
1617        );
1618    }
1619
1620    #[test]
1621    fn test_generate_alternatives_high_complexity_both_alternatives() {
1622        let mode = create_test_mode();
1623        let response = AutoResponse {
1624            recommended_mode: "linear".to_string(),
1625            confidence: 0.7,
1626            rationale: "Test".to_string(),
1627            complexity: 0.9,
1628            metadata: None,
1629        };
1630        let alternatives = mode.generate_alternatives(&response);
1631        // Should suggest both GoT and Tree for high complexity
1632        assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Got));
1633        assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Tree));
1634    }
1635
1636    #[test]
1637    fn test_generate_alternatives_recommended_divergent() {
1638        let mode = create_test_mode();
1639        let response = AutoResponse {
1640            recommended_mode: "divergent".to_string(),
1641            confidence: 0.8,
1642            rationale: "Test".to_string(),
1643            complexity: 0.5,
1644            metadata: None,
1645        };
1646        let alternatives = mode.generate_alternatives(&response);
1647        // Medium complexity, not divergent -> should suggest Tree
1648        assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Tree));
1649    }
1650
1651    #[test]
1652    fn test_generate_alternatives_recommended_reflection() {
1653        let mode = create_test_mode();
1654        let response = AutoResponse {
1655            recommended_mode: "reflection".to_string(),
1656            confidence: 0.8,
1657            rationale: "Test".to_string(),
1658            complexity: 0.5,
1659            metadata: None,
1660        };
1661        let alternatives = mode.generate_alternatives(&response);
1662        // Should suggest Tree for medium complexity
1663        assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Tree));
1664    }
1665
1666    #[test]
1667    fn test_generate_alternatives_zero_complexity() {
1668        let mode = create_test_mode();
1669        let response = AutoResponse {
1670            recommended_mode: "got".to_string(),
1671            confidence: 0.8,
1672            rationale: "Test".to_string(),
1673            complexity: 0.0,
1674            metadata: None,
1675        };
1676        let alternatives = mode.generate_alternatives(&response);
1677        // Very low complexity -> suggest Linear
1678        assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Linear));
1679    }
1680
1681    #[test]
1682    fn test_generate_alternatives_max_complexity() {
1683        let mode = create_test_mode();
1684        let response = AutoResponse {
1685            recommended_mode: "linear".to_string(),
1686            confidence: 0.8,
1687            rationale: "Test".to_string(),
1688            complexity: 1.0,
1689            metadata: None,
1690        };
1691        let alternatives = mode.generate_alternatives(&response);
1692        // Very high complexity -> suggest GoT and Tree
1693        assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Got));
1694        assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Tree));
1695    }
1696
1697    #[test]
1698    fn test_generate_alternatives_backtracking_mode() {
1699        let mode = create_test_mode();
1700        let response = AutoResponse {
1701            recommended_mode: "backtracking".to_string(),
1702            confidence: 0.8,
1703            rationale: "Test".to_string(),
1704            complexity: 0.5,
1705            metadata: None,
1706        };
1707        let alternatives = mode.generate_alternatives(&response);
1708        // Medium complexity, not one of the standard modes -> suggest Tree
1709        assert!(alternatives.iter().any(|a| a.mode == ReasoningMode::Tree));
1710    }
1711
1712    // ============================================================================
1713    // Additional Edge Cases for All Structs
1714    // ============================================================================
1715
1716    #[test]
1717    fn test_auto_result_complexity_boundaries() {
1718        let result = AutoResult {
1719            recommended_mode: ReasoningMode::Linear,
1720            confidence: 0.9,
1721            rationale: "Test".to_string(),
1722            complexity: 0.0,
1723            alternative_modes: vec![],
1724            fallback_used: false,
1725            original_invalid_mode: None,
1726        };
1727        assert_eq!(result.complexity, 0.0);
1728
1729        let result2 = AutoResult {
1730            recommended_mode: ReasoningMode::Linear,
1731            confidence: 0.9,
1732            rationale: "Test".to_string(),
1733            complexity: 1.0,
1734            alternative_modes: vec![],
1735            fallback_used: false,
1736            original_invalid_mode: None,
1737        };
1738        assert_eq!(result2.complexity, 1.0);
1739    }
1740
1741    #[test]
1742    fn test_auto_result_many_alternatives() {
1743        let result = AutoResult {
1744            recommended_mode: ReasoningMode::Linear,
1745            confidence: 0.9,
1746            rationale: "Test".to_string(),
1747            complexity: 0.5,
1748            alternative_modes: vec![
1749                ModeRecommendation {
1750                    mode: ReasoningMode::Tree,
1751                    confidence: 0.8,
1752                    rationale: "Alt 1".to_string(),
1753                },
1754                ModeRecommendation {
1755                    mode: ReasoningMode::Divergent,
1756                    confidence: 0.7,
1757                    rationale: "Alt 2".to_string(),
1758                },
1759                ModeRecommendation {
1760                    mode: ReasoningMode::Reflection,
1761                    confidence: 0.6,
1762                    rationale: "Alt 3".to_string(),
1763                },
1764            ],
1765            fallback_used: false,
1766            original_invalid_mode: None,
1767        };
1768        assert_eq!(result.alternative_modes.len(), 3);
1769    }
1770
1771    #[test]
1772    fn test_mode_recommendation_all_reasoning_modes() {
1773        let modes = vec![
1774            (ReasoningMode::Linear, "linear"),
1775            (ReasoningMode::Tree, "tree"),
1776            (ReasoningMode::Divergent, "divergent"),
1777            (ReasoningMode::Reflection, "reflection"),
1778            (ReasoningMode::Got, "got"),
1779            (ReasoningMode::Backtracking, "backtracking"),
1780        ];
1781
1782        for (mode, expected_str) in modes {
1783            let rec = ModeRecommendation {
1784                mode,
1785                confidence: 0.5,
1786                rationale: "Test".to_string(),
1787            };
1788            let json = serde_json::to_string(&rec).unwrap();
1789            assert!(json.contains(expected_str));
1790
1791            let parsed: ModeRecommendation = serde_json::from_str(&json).unwrap();
1792            assert_eq!(parsed.mode, mode);
1793        }
1794    }
1795
1796    #[test]
1797    fn test_auto_params_very_long_hints() {
1798        let long_hints: Vec<String> = (0..100).map(|i| format!("Hint number {}", i)).collect();
1799        let params = AutoParams::new("Content").with_hints(long_hints.clone());
1800        assert_eq!(params.hints.as_ref().unwrap().len(), 100);
1801    }
1802
1803    #[test]
1804    fn test_auto_params_empty_string_hints() {
1805        let params =
1806            AutoParams::new("Content").with_hints(vec!["".to_string(), "valid".to_string()]);
1807        let hints = params.hints.unwrap();
1808        assert_eq!(hints.len(), 2);
1809        assert_eq!(hints[0], "");
1810        assert_eq!(hints[1], "valid");
1811    }
1812
1813    #[test]
1814    fn test_auto_params_unicode_hints() {
1815        let params = AutoParams::new("Content")
1816            .with_hints(vec!["Unicode: δΈ–η•Œ".to_string(), "Emoji: 🌍".to_string()]);
1817        let hints = params.hints.unwrap();
1818        assert!(hints[0].contains("δΈ–η•Œ"));
1819        assert!(hints[1].contains("🌍"));
1820    }
1821
1822    #[test]
1823    fn test_auto_params_newlines_in_hints() {
1824        let params = AutoParams::new("Content").with_hints(vec!["Multi\nline\nhint".to_string()]);
1825        assert!(params.hints.unwrap()[0].contains('\n'));
1826    }
1827
1828    // ============================================================================
1829    // Helper Functions
1830    // ============================================================================
1831
1832    fn create_test_mode() -> AutoMode {
1833        use crate::config::{
1834            Config, DatabaseConfig, ErrorHandlingConfig, LangbaseConfig, LogFormat, LoggingConfig,
1835            PipeConfig, RequestConfig,
1836        };
1837        use std::path::PathBuf;
1838
1839        let langbase_config = LangbaseConfig {
1840            api_key: "test-key".to_string(),
1841            base_url: "https://api.langbase.com".to_string(),
1842        };
1843
1844        let config = Config {
1845            langbase: langbase_config.clone(),
1846            database: DatabaseConfig {
1847                path: PathBuf::from(":memory:"),
1848                max_connections: 5,
1849            },
1850            logging: LoggingConfig {
1851                level: "info".to_string(),
1852                format: LogFormat::Pretty,
1853            },
1854            request: RequestConfig::default(),
1855            pipes: PipeConfig::default(),
1856            error_handling: ErrorHandlingConfig::default(),
1857        };
1858
1859        // Use a runtime for async operations in tests
1860        let rt = tokio::runtime::Runtime::new().unwrap();
1861        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1862        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1863
1864        AutoMode::new(storage, langbase, &config)
1865    }
1866}