mcp_langbase_reasoning/modes/
tree.rs

1//! Tree reasoning mode - branching exploration with multiple paths.
2//!
3//! This module provides tree-based reasoning for exploring multiple directions:
4//! - Multiple branch exploration (2-4 branches)
5//! - Branch focusing and navigation
6//! - Cross-references between branches
7//! - Recommended path identification
8
9use serde::{Deserialize, Serialize};
10use std::time::Instant;
11use tracing::{debug, info, warn};
12
13use super::{extract_json_from_completion, serialize_for_log, ModeCore};
14use crate::config::Config;
15use crate::error::{AppResult, ToolError};
16use crate::langbase::{LangbaseClient, Message, PipeRequest};
17use crate::prompts::TREE_REASONING_PROMPT;
18use crate::storage::{
19    Branch, BranchState, CrossRef, CrossRefType, Invocation, SqliteStorage, Storage, Thought,
20};
21
22/// Input parameters for tree reasoning
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct TreeParams {
25    /// The thought content to process
26    pub content: String,
27    /// Optional session ID (creates new if not provided)
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub session_id: Option<String>,
30    /// Branch ID to extend (creates root branch if not provided)
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub branch_id: Option<String>,
33    /// Confidence threshold (0.0-1.0)
34    #[serde(default = "default_confidence")]
35    pub confidence: f64,
36    /// Number of branches to explore (2-4)
37    #[serde(default = "default_num_branches")]
38    pub num_branches: usize,
39    /// Cross-references to other branches
40    #[serde(default)]
41    pub cross_refs: Vec<CrossRefInput>,
42}
43
44fn default_confidence() -> f64 {
45    0.8
46}
47
48fn default_num_branches() -> usize {
49    3
50}
51
52/// Cross-reference input for tree reasoning.
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct CrossRefInput {
55    /// The target branch ID to reference.
56    pub to_branch: String,
57    /// The type of reference (supports, contradicts, extends, alternative, depends).
58    #[serde(rename = "type")]
59    pub ref_type: String,
60    /// Optional reason for the cross-reference.
61    pub reason: Option<String>,
62    /// Optional strength of the reference (0.0-1.0).
63    pub strength: Option<f64>,
64}
65
66/// Response from tree reasoning Langbase pipe.
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct TreeResponse {
69    /// The generated branches/paths.
70    pub branches: Vec<TreeBranch>,
71    /// Index of the recommended branch (0-based).
72    pub recommended_branch: usize,
73    /// Additional metadata from the response.
74    #[serde(default)]
75    pub metadata: serde_json::Value,
76}
77
78/// Individual branch in tree response.
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct TreeBranch {
81    /// The thought content for this branch.
82    pub thought: String,
83    /// Confidence in this branch (0.0-1.0).
84    pub confidence: f64,
85    /// Rationale for why this branch was generated.
86    pub rationale: String,
87}
88
89/// Result of tree reasoning.
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct TreeResult {
92    /// The session ID.
93    pub session_id: String,
94    /// The current branch ID.
95    pub branch_id: String,
96    /// The ID of the created thought.
97    pub thought_id: String,
98    /// The thought content.
99    pub content: String,
100    /// Confidence in the thought (0.0-1.0).
101    pub confidence: f64,
102    /// Child branches created for exploration.
103    pub child_branches: Vec<BranchInfo>,
104    /// Index of the recommended branch (0-based).
105    pub recommended_branch_index: usize,
106    /// Parent branch ID, if this is an extension.
107    pub parent_branch: Option<String>,
108    /// Number of cross-references created.
109    pub cross_refs_created: usize,
110}
111
112/// Branch information in result.
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct BranchInfo {
115    /// The branch ID.
116    pub id: String,
117    /// Human-readable branch name.
118    pub name: String,
119    /// Confidence in this branch (0.0-1.0).
120    pub confidence: f64,
121    /// Rationale for this branch.
122    pub rationale: String,
123}
124
125/// Tree reasoning mode handler for branching exploration.
126#[derive(Clone)]
127pub struct TreeMode {
128    /// Core infrastructure (storage and langbase client).
129    core: ModeCore,
130    /// The Langbase pipe name for tree reasoning.
131    pipe_name: String,
132}
133
134impl TreeMode {
135    /// Create a new tree mode handler
136    pub fn new(storage: SqliteStorage, langbase: LangbaseClient, config: &Config) -> Self {
137        Self {
138            core: ModeCore::new(storage, langbase),
139            pipe_name: config.pipes.tree.clone(),
140        }
141    }
142
143    /// Process a tree reasoning request
144    pub async fn process(&self, params: TreeParams) -> AppResult<TreeResult> {
145        let start = Instant::now();
146
147        // Validate input
148        if params.content.trim().is_empty() {
149            return Err(ToolError::Validation {
150                field: "content".to_string(),
151                reason: "Content cannot be empty".to_string(),
152            }
153            .into());
154        }
155
156        let num_branches = params.num_branches.clamp(2, 4);
157
158        // Get or create session
159        let session = self
160            .core
161            .storage()
162            .get_or_create_session(&params.session_id, "tree")
163            .await?;
164        debug!(session_id = %session.id, "Processing tree reasoning");
165
166        // Get or create branch
167        let parent_branch = match &params.branch_id {
168            Some(id) => self.core.storage().get_branch(id).await?,
169            None => None,
170        };
171
172        // Create the current branch if extending or creating new
173        let branch = match &parent_branch {
174            Some(parent) => {
175                let b = Branch::new(&session.id)
176                    .with_parent(&parent.id)
177                    .with_name(format!("Branch from {}", &parent.id[..8]))
178                    .with_confidence(params.confidence);
179                self.core.storage().create_branch(&b).await?;
180                b
181            }
182            None => {
183                // Check if session has an active branch, or create root
184                match &session.active_branch_id {
185                    Some(active_id) => self
186                        .core
187                        .storage()
188                        .get_branch(active_id)
189                        .await?
190                        .unwrap_or_else(|| Branch::new(&session.id).with_name("Root")),
191                    None => {
192                        let b = Branch::new(&session.id).with_name("Root");
193                        self.core.storage().create_branch(&b).await?;
194                        // Update session with active branch
195                        let mut updated_session = session.clone();
196                        updated_session.active_branch_id = Some(b.id.clone());
197                        self.core.storage().update_session(&updated_session).await?;
198                        b
199                    }
200                }
201            }
202        };
203
204        // Get context from branch history
205        let branch_thoughts = self.core.storage().get_branch_thoughts(&branch.id).await?;
206
207        // Build messages for Langbase
208        let messages = self.build_messages(&params.content, &branch_thoughts, num_branches);
209
210        // Create invocation log
211        let mut invocation = Invocation::new(
212            "reasoning.tree",
213            serialize_for_log(&params, "reasoning.tree input"),
214        )
215        .with_session(&session.id)
216        .with_pipe(&self.pipe_name);
217
218        // Call Langbase pipe
219        let request = PipeRequest::new(&self.pipe_name, messages);
220        let response = match self.core.langbase().call_pipe(request).await {
221            Ok(resp) => resp,
222            Err(e) => {
223                let latency = start.elapsed().as_millis() as i64;
224                invocation = invocation.failure(e.to_string(), latency);
225                self.core.storage().log_invocation(&invocation).await?;
226                return Err(e.into());
227            }
228        };
229
230        // Parse response
231        let tree_response = self.parse_response(&response.completion)?;
232
233        // Create main thought for this branch
234        let thought = Thought::new(&session.id, &params.content, "tree")
235            .with_confidence(params.confidence)
236            .with_branch(&branch.id);
237        self.core.storage().create_thought(&thought).await?;
238
239        // Create child branches for each explored path
240        let mut child_branches = Vec::new();
241        for (i, tb) in tree_response.branches.iter().enumerate() {
242            let child = Branch::new(&session.id)
243                .with_parent(&branch.id)
244                .with_name(format!("Option {}: {}", i + 1, truncate(&tb.thought, 30)))
245                .with_confidence(tb.confidence)
246                .with_priority(if i == tree_response.recommended_branch {
247                    2.0
248                } else {
249                    1.0
250                });
251
252            self.core.storage().create_branch(&child).await?;
253
254            // Create thought for this branch
255            let child_thought = Thought::new(&session.id, &tb.thought, "tree")
256                .with_confidence(tb.confidence)
257                .with_branch(&child.id)
258                .with_parent(&thought.id);
259            self.core.storage().create_thought(&child_thought).await?;
260
261            child_branches.push(BranchInfo {
262                id: child.id,
263                name: child
264                    .name
265                    .clone()
266                    .unwrap_or_else(|| "Unnamed Branch".to_string()),
267                confidence: tb.confidence,
268                rationale: tb.rationale.clone(),
269            });
270        }
271
272        // Create cross-references if specified
273        let mut cross_refs_created = 0;
274        for cr_input in &params.cross_refs {
275            if let Ok(ref_type) = cr_input.ref_type.parse::<CrossRefType>() {
276                let cr = CrossRef::new(&branch.id, &cr_input.to_branch, ref_type)
277                    .with_strength(cr_input.strength.unwrap_or(1.0));
278                let cr = if let Some(reason) = &cr_input.reason {
279                    cr.with_reason(reason)
280                } else {
281                    cr
282                };
283                self.core.storage().create_cross_ref(&cr).await?;
284                cross_refs_created += 1;
285            }
286        }
287
288        // Log successful invocation
289        let latency = start.elapsed().as_millis() as i64;
290        invocation = invocation.success(
291            serialize_for_log(&tree_response, "reasoning.tree output"),
292            latency,
293        );
294        self.core.storage().log_invocation(&invocation).await?;
295
296        info!(
297            session_id = %session.id,
298            branch_id = %branch.id,
299            thought_id = %thought.id,
300            num_children = child_branches.len(),
301            latency_ms = latency,
302            "Tree reasoning completed"
303        );
304
305        Ok(TreeResult {
306            session_id: session.id,
307            branch_id: branch.id,
308            thought_id: thought.id,
309            content: params.content,
310            confidence: params.confidence,
311            child_branches,
312            recommended_branch_index: tree_response.recommended_branch,
313            parent_branch: parent_branch.map(|b| b.id),
314            cross_refs_created,
315        })
316    }
317
318    /// Focus on a specific branch, making it the active branch
319    pub async fn focus_branch(&self, session_id: &str, branch_id: &str) -> AppResult<Branch> {
320        let branch = self
321            .core
322            .storage()
323            .get_branch(branch_id)
324            .await?
325            .ok_or_else(|| ToolError::Session(format!("Branch not found: {}", branch_id)))?;
326
327        // Verify branch belongs to session
328        if branch.session_id != session_id {
329            return Err(
330                ToolError::Session("Branch does not belong to this session".to_string()).into(),
331            );
332        }
333
334        // Update session's active branch
335        let session = self
336            .core
337            .storage()
338            .get_session(session_id)
339            .await?
340            .ok_or_else(|| ToolError::Session(format!("Session not found: {}", session_id)))?;
341
342        let mut updated_session = session;
343        updated_session.active_branch_id = Some(branch_id.to_string());
344        self.core.storage().update_session(&updated_session).await?;
345
346        Ok(branch)
347    }
348
349    /// Get all branches for a session
350    pub async fn list_branches(&self, session_id: &str) -> AppResult<Vec<Branch>> {
351        Ok(self.core.storage().get_session_branches(session_id).await?)
352    }
353
354    /// Update branch state (complete, abandon)
355    pub async fn update_branch_state(
356        &self,
357        branch_id: &str,
358        state: BranchState,
359    ) -> AppResult<Branch> {
360        let mut branch = self
361            .core
362            .storage()
363            .get_branch(branch_id)
364            .await?
365            .ok_or_else(|| ToolError::Session(format!("Branch not found: {}", branch_id)))?;
366
367        branch.state = state;
368        branch.updated_at = chrono::Utc::now();
369        self.core.storage().update_branch(&branch).await?;
370
371        Ok(branch)
372    }
373
374    fn build_messages(
375        &self,
376        content: &str,
377        history: &[Thought],
378        num_branches: usize,
379    ) -> Vec<Message> {
380        let mut messages = Vec::new();
381
382        // System prompt for tree reasoning
383        let system_prompt = TREE_REASONING_PROMPT.replace(
384            "2-4 distinct reasoning paths",
385            &format!("{} distinct reasoning paths", num_branches),
386        );
387        messages.push(Message::system(system_prompt));
388
389        // Add history context if available
390        if !history.is_empty() {
391            let history_text: Vec<String> =
392                history.iter().map(|t| format!("- {}", t.content)).collect();
393
394            messages.push(Message::user(format!(
395                "Previous reasoning in this branch:\n{}\n\nNow explore this thought:",
396                history_text.join("\n")
397            )));
398        }
399
400        // Add current content
401        messages.push(Message::user(content.to_string()));
402
403        messages
404    }
405
406    fn parse_response(&self, completion: &str) -> AppResult<TreeResponse> {
407        let json_str = extract_json_from_completion(completion).map_err(|e| {
408            warn!(
409                error = %e,
410                completion_preview = %completion.chars().take(200).collect::<String>(),
411                "Failed to extract JSON from tree response"
412            );
413            ToolError::Reasoning {
414                message: format!("Tree response extraction failed: {}", e),
415            }
416        })?;
417
418        serde_json::from_str::<TreeResponse>(json_str).map_err(|e| {
419            ToolError::Reasoning {
420                message: format!("Failed to parse tree response: {}", e),
421            }
422            .into()
423        })
424    }
425}
426
427impl TreeParams {
428    /// Create new params with just content
429    pub fn new(content: impl Into<String>) -> Self {
430        Self {
431            content: content.into(),
432            session_id: None,
433            branch_id: None,
434            confidence: default_confidence(),
435            num_branches: default_num_branches(),
436            cross_refs: Vec::new(),
437        }
438    }
439
440    /// Set the session ID
441    pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
442        self.session_id = Some(session_id.into());
443        self
444    }
445
446    /// Set the branch ID to extend
447    pub fn with_branch(mut self, branch_id: impl Into<String>) -> Self {
448        self.branch_id = Some(branch_id.into());
449        self
450    }
451
452    /// Set the confidence threshold
453    pub fn with_confidence(mut self, confidence: f64) -> Self {
454        self.confidence = confidence.clamp(0.0, 1.0);
455        self
456    }
457
458    /// Set the number of branches to explore
459    pub fn with_num_branches(mut self, num: usize) -> Self {
460        self.num_branches = num.clamp(2, 4);
461        self
462    }
463
464    /// Add a cross-reference
465    pub fn with_cross_ref(
466        mut self,
467        to_branch: impl Into<String>,
468        ref_type: impl Into<String>,
469    ) -> Self {
470        self.cross_refs.push(CrossRefInput {
471            to_branch: to_branch.into(),
472            ref_type: ref_type.into(),
473            reason: None,
474            strength: None,
475        });
476        self
477    }
478}
479
480fn truncate(s: &str, max_len: usize) -> String {
481    if s.len() <= max_len {
482        s.to_string()
483    } else {
484        format!("{}...", &s[..max_len.saturating_sub(3)])
485    }
486}
487
488#[cfg(test)]
489mod tests {
490    use super::*;
491
492    // ============================================================================
493    // Default Function Tests
494    // ============================================================================
495
496    #[test]
497    fn test_default_confidence() {
498        assert_eq!(default_confidence(), 0.8);
499    }
500
501    #[test]
502    fn test_default_num_branches() {
503        assert_eq!(default_num_branches(), 3);
504    }
505
506    // ============================================================================
507    // Truncate Function Tests
508    // ============================================================================
509
510    #[test]
511    fn test_truncate_short_string() {
512        assert_eq!(truncate("Hello", 10), "Hello");
513    }
514
515    #[test]
516    fn test_truncate_exact_length() {
517        assert_eq!(truncate("Hello", 5), "Hello");
518    }
519
520    #[test]
521    fn test_truncate_long_string() {
522        assert_eq!(truncate("Hello World", 8), "Hello...");
523    }
524
525    #[test]
526    fn test_truncate_very_short_max() {
527        assert_eq!(truncate("Hello World", 3), "...");
528    }
529
530    // ============================================================================
531    // TreeParams Tests
532    // ============================================================================
533
534    #[test]
535    fn test_tree_params_new() {
536        let params = TreeParams::new("Test content");
537        assert_eq!(params.content, "Test content");
538        assert!(params.session_id.is_none());
539        assert!(params.branch_id.is_none());
540        assert_eq!(params.confidence, 0.8);
541        assert_eq!(params.num_branches, 3);
542        assert!(params.cross_refs.is_empty());
543    }
544
545    #[test]
546    fn test_tree_params_with_session() {
547        let params = TreeParams::new("Content").with_session("sess-123");
548        assert_eq!(params.session_id, Some("sess-123".to_string()));
549    }
550
551    #[test]
552    fn test_tree_params_with_branch() {
553        let params = TreeParams::new("Content").with_branch("branch-456");
554        assert_eq!(params.branch_id, Some("branch-456".to_string()));
555    }
556
557    #[test]
558    fn test_tree_params_with_confidence() {
559        let params = TreeParams::new("Content").with_confidence(0.9);
560        assert_eq!(params.confidence, 0.9);
561    }
562
563    #[test]
564    fn test_tree_params_confidence_clamped_high() {
565        let params = TreeParams::new("Content").with_confidence(1.5);
566        assert_eq!(params.confidence, 1.0);
567    }
568
569    #[test]
570    fn test_tree_params_confidence_clamped_low() {
571        let params = TreeParams::new("Content").with_confidence(-0.5);
572        assert_eq!(params.confidence, 0.0);
573    }
574
575    #[test]
576    fn test_tree_params_with_num_branches() {
577        let params = TreeParams::new("Content").with_num_branches(4);
578        assert_eq!(params.num_branches, 4);
579    }
580
581    #[test]
582    fn test_tree_params_num_branches_clamped_high() {
583        let params = TreeParams::new("Content").with_num_branches(10);
584        assert_eq!(params.num_branches, 4); // max is 4
585    }
586
587    #[test]
588    fn test_tree_params_num_branches_clamped_low() {
589        let params = TreeParams::new("Content").with_num_branches(1);
590        assert_eq!(params.num_branches, 2); // min is 2
591    }
592
593    #[test]
594    fn test_tree_params_with_cross_ref() {
595        let params = TreeParams::new("Content").with_cross_ref("branch-target", "supports");
596        assert_eq!(params.cross_refs.len(), 1);
597        assert_eq!(params.cross_refs[0].to_branch, "branch-target");
598        assert_eq!(params.cross_refs[0].ref_type, "supports");
599        assert!(params.cross_refs[0].reason.is_none());
600        assert!(params.cross_refs[0].strength.is_none());
601    }
602
603    #[test]
604    fn test_tree_params_multiple_cross_refs() {
605        let params = TreeParams::new("Content")
606            .with_cross_ref("branch-1", "supports")
607            .with_cross_ref("branch-2", "contradicts");
608        assert_eq!(params.cross_refs.len(), 2);
609    }
610
611    #[test]
612    fn test_tree_params_builder_chain() {
613        let params = TreeParams::new("Chained")
614            .with_session("my-session")
615            .with_branch("my-branch")
616            .with_confidence(0.85)
617            .with_num_branches(4)
618            .with_cross_ref("ref-branch", "supports");
619
620        assert_eq!(params.content, "Chained");
621        assert_eq!(params.session_id, Some("my-session".to_string()));
622        assert_eq!(params.branch_id, Some("my-branch".to_string()));
623        assert_eq!(params.confidence, 0.85);
624        assert_eq!(params.num_branches, 4);
625        assert_eq!(params.cross_refs.len(), 1);
626    }
627
628    #[test]
629    fn test_tree_params_serialize() {
630        let params = TreeParams::new("Test")
631            .with_session("sess-1")
632            .with_num_branches(3);
633
634        let json = serde_json::to_string(&params).unwrap();
635        assert!(json.contains("Test"));
636        assert!(json.contains("sess-1"));
637        assert!(json.contains("\"num_branches\":3"));
638    }
639
640    #[test]
641    fn test_tree_params_deserialize() {
642        let json = r#"{
643            "content": "Parsed",
644            "session_id": "s-1",
645            "branch_id": "b-1",
646            "confidence": 0.9,
647            "num_branches": 4,
648            "cross_refs": []
649        }"#;
650        let params: TreeParams = serde_json::from_str(json).unwrap();
651
652        assert_eq!(params.content, "Parsed");
653        assert_eq!(params.session_id, Some("s-1".to_string()));
654        assert_eq!(params.branch_id, Some("b-1".to_string()));
655        assert_eq!(params.confidence, 0.9);
656        assert_eq!(params.num_branches, 4);
657    }
658
659    #[test]
660    fn test_tree_params_deserialize_minimal() {
661        let json = r#"{"content": "Only content"}"#;
662        let params: TreeParams = serde_json::from_str(json).unwrap();
663
664        assert_eq!(params.content, "Only content");
665        assert!(params.session_id.is_none());
666        assert!(params.branch_id.is_none());
667        assert_eq!(params.confidence, 0.8); // default
668        assert_eq!(params.num_branches, 3); // default
669        assert!(params.cross_refs.is_empty());
670    }
671
672    // ============================================================================
673    // CrossRefInput Tests
674    // ============================================================================
675
676    #[test]
677    fn test_cross_ref_input_serialize() {
678        let cr = CrossRefInput {
679            to_branch: "target-branch".to_string(),
680            ref_type: "supports".to_string(),
681            reason: Some("Strong evidence".to_string()),
682            strength: Some(0.9),
683        };
684
685        let json = serde_json::to_string(&cr).unwrap();
686        assert!(json.contains("target-branch"));
687        assert!(json.contains("supports"));
688        assert!(json.contains("Strong evidence"));
689        assert!(json.contains("0.9"));
690    }
691
692    #[test]
693    fn test_cross_ref_input_deserialize() {
694        let json = r#"{
695            "to_branch": "b-1",
696            "type": "contradicts",
697            "reason": "Conflicts with main thesis",
698            "strength": 0.8
699        }"#;
700        let cr: CrossRefInput = serde_json::from_str(json).unwrap();
701
702        assert_eq!(cr.to_branch, "b-1");
703        assert_eq!(cr.ref_type, "contradicts");
704        assert_eq!(cr.reason, Some("Conflicts with main thesis".to_string()));
705        assert_eq!(cr.strength, Some(0.8));
706    }
707
708    #[test]
709    fn test_cross_ref_input_deserialize_minimal() {
710        let json = r#"{"to_branch": "b-1", "type": "supports"}"#;
711        let cr: CrossRefInput = serde_json::from_str(json).unwrap();
712
713        assert_eq!(cr.to_branch, "b-1");
714        assert_eq!(cr.ref_type, "supports");
715        assert!(cr.reason.is_none());
716        assert!(cr.strength.is_none());
717    }
718
719    // ============================================================================
720    // TreeBranch Tests
721    // ============================================================================
722
723    #[test]
724    fn test_tree_branch_serialize() {
725        let branch = TreeBranch {
726            thought: "A branching thought".to_string(),
727            confidence: 0.85,
728            rationale: "This is the reasoning".to_string(),
729        };
730
731        let json = serde_json::to_string(&branch).unwrap();
732        assert!(json.contains("A branching thought"));
733        assert!(json.contains("0.85"));
734        assert!(json.contains("This is the reasoning"));
735    }
736
737    #[test]
738    fn test_tree_branch_deserialize() {
739        let json = r#"{
740            "thought": "Branch thought",
741            "confidence": 0.75,
742            "rationale": "Because reasons"
743        }"#;
744        let branch: TreeBranch = serde_json::from_str(json).unwrap();
745
746        assert_eq!(branch.thought, "Branch thought");
747        assert_eq!(branch.confidence, 0.75);
748        assert_eq!(branch.rationale, "Because reasons");
749    }
750
751    // ============================================================================
752    // TreeResponse Tests
753    // ============================================================================
754
755    #[test]
756    fn test_tree_response_serialize() {
757        let response = TreeResponse {
758            branches: vec![
759                TreeBranch {
760                    thought: "Option 1".to_string(),
761                    confidence: 0.8,
762                    rationale: "First path".to_string(),
763                },
764                TreeBranch {
765                    thought: "Option 2".to_string(),
766                    confidence: 0.7,
767                    rationale: "Second path".to_string(),
768                },
769            ],
770            recommended_branch: 0,
771            metadata: serde_json::json!({"analysis": "complete"}),
772        };
773
774        let json = serde_json::to_string(&response).unwrap();
775        assert!(json.contains("Option 1"));
776        assert!(json.contains("Option 2"));
777        assert!(json.contains("recommended_branch"));
778    }
779
780    #[test]
781    fn test_tree_response_deserialize() {
782        let json = r#"{
783            "branches": [
784                {"thought": "Path A", "confidence": 0.9, "rationale": "Strong"},
785                {"thought": "Path B", "confidence": 0.6, "rationale": "Weak"}
786            ],
787            "recommended_branch": 1
788        }"#;
789        let response: TreeResponse = serde_json::from_str(json).unwrap();
790
791        assert_eq!(response.branches.len(), 2);
792        assert_eq!(response.recommended_branch, 1);
793        assert_eq!(response.branches[0].thought, "Path A");
794    }
795
796    // ============================================================================
797    // BranchInfo Tests
798    // ============================================================================
799
800    #[test]
801    fn test_branch_info_serialize() {
802        let info = BranchInfo {
803            id: "branch-123".to_string(),
804            name: "Main branch".to_string(),
805            confidence: 0.82,
806            rationale: "Best option".to_string(),
807        };
808
809        let json = serde_json::to_string(&info).unwrap();
810        assert!(json.contains("branch-123"));
811        assert!(json.contains("Main branch"));
812        assert!(json.contains("0.82"));
813        assert!(json.contains("Best option"));
814    }
815
816    #[test]
817    fn test_branch_info_deserialize() {
818        let json = r#"{
819            "id": "b-1",
820            "name": "Test Branch",
821            "confidence": 0.95,
822            "rationale": "The rationale"
823        }"#;
824        let info: BranchInfo = serde_json::from_str(json).unwrap();
825
826        assert_eq!(info.id, "b-1");
827        assert_eq!(info.name, "Test Branch");
828        assert_eq!(info.confidence, 0.95);
829        assert_eq!(info.rationale, "The rationale");
830    }
831
832    #[test]
833    fn test_branch_info_unnamed_fallback() {
834        // Verify that "Unnamed Branch" is a valid name value for serialization
835        let info = BranchInfo {
836            id: "branch-1".to_string(),
837            name: "Unnamed Branch".to_string(),
838            confidence: 0.7,
839            rationale: "No name provided".to_string(),
840        };
841
842        let json = serde_json::to_string(&info).unwrap();
843        assert!(json.contains("Unnamed Branch"));
844
845        let parsed: BranchInfo = serde_json::from_str(&json).unwrap();
846        assert_eq!(parsed.name, "Unnamed Branch");
847    }
848
849    // ============================================================================
850    // TreeResult Tests
851    // ============================================================================
852
853    #[test]
854    fn test_tree_result_serialize() {
855        let result = TreeResult {
856            session_id: "sess-1".to_string(),
857            branch_id: "branch-1".to_string(),
858            thought_id: "thought-1".to_string(),
859            content: "Main thought content".to_string(),
860            confidence: 0.88,
861            child_branches: vec![BranchInfo {
862                id: "child-1".to_string(),
863                name: "Child Branch".to_string(),
864                confidence: 0.75,
865                rationale: "Exploring option".to_string(),
866            }],
867            recommended_branch_index: 0,
868            parent_branch: Some("parent-1".to_string()),
869            cross_refs_created: 2,
870        };
871
872        let json = serde_json::to_string(&result).unwrap();
873        assert!(json.contains("sess-1"));
874        assert!(json.contains("branch-1"));
875        assert!(json.contains("Main thought content"));
876        assert!(json.contains("parent-1"));
877    }
878
879    #[test]
880    fn test_tree_result_deserialize() {
881        let json = r#"{
882            "session_id": "s-1",
883            "branch_id": "b-1",
884            "thought_id": "t-1",
885            "content": "Content",
886            "confidence": 0.8,
887            "child_branches": [],
888            "recommended_branch_index": 0,
889            "parent_branch": null,
890            "cross_refs_created": 0
891        }"#;
892        let result: TreeResult = serde_json::from_str(json).unwrap();
893
894        assert_eq!(result.session_id, "s-1");
895        assert_eq!(result.branch_id, "b-1");
896        assert_eq!(result.thought_id, "t-1");
897        assert!(result.child_branches.is_empty());
898        assert!(result.parent_branch.is_none());
899        assert_eq!(result.cross_refs_created, 0);
900    }
901
902    #[test]
903    fn test_tree_result_with_children() {
904        let result = TreeResult {
905            session_id: "s-1".to_string(),
906            branch_id: "b-1".to_string(),
907            thought_id: "t-1".to_string(),
908            content: "Root".to_string(),
909            confidence: 0.9,
910            child_branches: vec![
911                BranchInfo {
912                    id: "c-1".to_string(),
913                    name: "Child 1".to_string(),
914                    confidence: 0.85,
915                    rationale: "First".to_string(),
916                },
917                BranchInfo {
918                    id: "c-2".to_string(),
919                    name: "Child 2".to_string(),
920                    confidence: 0.7,
921                    rationale: "Second".to_string(),
922                },
923            ],
924            recommended_branch_index: 1,
925            parent_branch: None,
926            cross_refs_created: 1,
927        };
928
929        assert_eq!(result.child_branches.len(), 2);
930        assert_eq!(result.recommended_branch_index, 1);
931        assert_eq!(result.cross_refs_created, 1);
932    }
933
934    // ============================================================================
935    // Edge Cases and Boundary Tests
936    // ============================================================================
937
938    #[test]
939    fn test_tree_params_empty_content() {
940        let params = TreeParams::new("");
941        assert_eq!(params.content, "");
942    }
943
944    #[test]
945    fn test_tree_params_whitespace_content() {
946        let params = TreeParams::new("   ");
947        assert_eq!(params.content, "   ");
948    }
949
950    #[test]
951    fn test_tree_params_num_branches_boundary_min() {
952        let params = TreeParams::new("Content").with_num_branches(2);
953        assert_eq!(params.num_branches, 2);
954    }
955
956    #[test]
957    fn test_tree_params_num_branches_boundary_max() {
958        let params = TreeParams::new("Content").with_num_branches(4);
959        assert_eq!(params.num_branches, 4);
960    }
961
962    #[test]
963    fn test_tree_params_num_branches_zero() {
964        let params = TreeParams::new("Content").with_num_branches(0);
965        assert_eq!(params.num_branches, 2); // clamped to min
966    }
967
968    #[test]
969    fn test_tree_params_confidence_boundary_zero() {
970        let params = TreeParams::new("Content").with_confidence(0.0);
971        assert_eq!(params.confidence, 0.0);
972    }
973
974    #[test]
975    fn test_tree_params_confidence_boundary_one() {
976        let params = TreeParams::new("Content").with_confidence(1.0);
977        assert_eq!(params.confidence, 1.0);
978    }
979
980    #[test]
981    fn test_tree_params_confidence_negative() {
982        let params = TreeParams::new("Content").with_confidence(-10.5);
983        assert_eq!(params.confidence, 0.0);
984    }
985
986    #[test]
987    fn test_tree_params_confidence_very_high() {
988        let params = TreeParams::new("Content").with_confidence(100.0);
989        assert_eq!(params.confidence, 1.0);
990    }
991
992    #[test]
993    fn test_tree_result_empty_child_branches() {
994        let result = TreeResult {
995            session_id: "s-1".to_string(),
996            branch_id: "b-1".to_string(),
997            thought_id: "t-1".to_string(),
998            content: "Content".to_string(),
999            confidence: 0.8,
1000            child_branches: vec![],
1001            recommended_branch_index: 0,
1002            parent_branch: None,
1003            cross_refs_created: 0,
1004        };
1005
1006        assert!(result.child_branches.is_empty());
1007    }
1008
1009    #[test]
1010    fn test_tree_result_with_parent_branch() {
1011        let result = TreeResult {
1012            session_id: "s-1".to_string(),
1013            branch_id: "b-1".to_string(),
1014            thought_id: "t-1".to_string(),
1015            content: "Content".to_string(),
1016            confidence: 0.8,
1017            child_branches: vec![],
1018            recommended_branch_index: 0,
1019            parent_branch: Some("parent-123".to_string()),
1020            cross_refs_created: 0,
1021        };
1022
1023        assert!(result.parent_branch.is_some());
1024        assert_eq!(result.parent_branch.unwrap(), "parent-123");
1025    }
1026
1027    #[test]
1028    fn test_tree_result_multiple_cross_refs() {
1029        let result = TreeResult {
1030            session_id: "s-1".to_string(),
1031            branch_id: "b-1".to_string(),
1032            thought_id: "t-1".to_string(),
1033            content: "Content".to_string(),
1034            confidence: 0.8,
1035            child_branches: vec![],
1036            recommended_branch_index: 0,
1037            parent_branch: None,
1038            cross_refs_created: 5,
1039        };
1040
1041        assert_eq!(result.cross_refs_created, 5);
1042    }
1043
1044    // ============================================================================
1045    // CrossRefInput All Variants Tests
1046    // ============================================================================
1047
1048    #[test]
1049    fn test_cross_ref_input_type_supports() {
1050        let cr = CrossRefInput {
1051            to_branch: "b-1".to_string(),
1052            ref_type: "supports".to_string(),
1053            reason: None,
1054            strength: None,
1055        };
1056        assert_eq!(cr.ref_type, "supports");
1057    }
1058
1059    #[test]
1060    fn test_cross_ref_input_type_contradicts() {
1061        let cr = CrossRefInput {
1062            to_branch: "b-1".to_string(),
1063            ref_type: "contradicts".to_string(),
1064            reason: None,
1065            strength: None,
1066        };
1067        assert_eq!(cr.ref_type, "contradicts");
1068    }
1069
1070    #[test]
1071    fn test_cross_ref_input_type_extends() {
1072        let cr = CrossRefInput {
1073            to_branch: "b-1".to_string(),
1074            ref_type: "extends".to_string(),
1075            reason: None,
1076            strength: None,
1077        };
1078        assert_eq!(cr.ref_type, "extends");
1079    }
1080
1081    #[test]
1082    fn test_cross_ref_input_type_alternative() {
1083        let cr = CrossRefInput {
1084            to_branch: "b-1".to_string(),
1085            ref_type: "alternative".to_string(),
1086            reason: None,
1087            strength: None,
1088        };
1089        assert_eq!(cr.ref_type, "alternative");
1090    }
1091
1092    #[test]
1093    fn test_cross_ref_input_type_depends() {
1094        let cr = CrossRefInput {
1095            to_branch: "b-1".to_string(),
1096            ref_type: "depends".to_string(),
1097            reason: None,
1098            strength: None,
1099        };
1100        assert_eq!(cr.ref_type, "depends");
1101    }
1102
1103    #[test]
1104    fn test_cross_ref_input_with_reason() {
1105        let cr = CrossRefInput {
1106            to_branch: "b-1".to_string(),
1107            ref_type: "supports".to_string(),
1108            reason: Some("Strong evidence".to_string()),
1109            strength: None,
1110        };
1111        assert_eq!(cr.reason, Some("Strong evidence".to_string()));
1112    }
1113
1114    #[test]
1115    fn test_cross_ref_input_with_strength_zero() {
1116        let cr = CrossRefInput {
1117            to_branch: "b-1".to_string(),
1118            ref_type: "supports".to_string(),
1119            reason: None,
1120            strength: Some(0.0),
1121        };
1122        assert_eq!(cr.strength, Some(0.0));
1123    }
1124
1125    #[test]
1126    fn test_cross_ref_input_with_strength_one() {
1127        let cr = CrossRefInput {
1128            to_branch: "b-1".to_string(),
1129            ref_type: "supports".to_string(),
1130            reason: None,
1131            strength: Some(1.0),
1132        };
1133        assert_eq!(cr.strength, Some(1.0));
1134    }
1135
1136    #[test]
1137    fn test_cross_ref_input_with_strength_mid() {
1138        let cr = CrossRefInput {
1139            to_branch: "b-1".to_string(),
1140            ref_type: "supports".to_string(),
1141            reason: None,
1142            strength: Some(0.5),
1143        };
1144        assert_eq!(cr.strength, Some(0.5));
1145    }
1146
1147    #[test]
1148    fn test_cross_ref_input_full_fields() {
1149        let cr = CrossRefInput {
1150            to_branch: "target-branch".to_string(),
1151            ref_type: "extends".to_string(),
1152            reason: Some("Builds on previous work".to_string()),
1153            strength: Some(0.95),
1154        };
1155
1156        assert_eq!(cr.to_branch, "target-branch");
1157        assert_eq!(cr.ref_type, "extends");
1158        assert_eq!(cr.reason, Some("Builds on previous work".to_string()));
1159        assert_eq!(cr.strength, Some(0.95));
1160    }
1161
1162    // ============================================================================
1163    // TreeResponse Additional Tests
1164    // ============================================================================
1165
1166    #[test]
1167    fn test_tree_response_empty_branches() {
1168        let response = TreeResponse {
1169            branches: vec![],
1170            recommended_branch: 0,
1171            metadata: serde_json::json!({}),
1172        };
1173        assert!(response.branches.is_empty());
1174    }
1175
1176    #[test]
1177    fn test_tree_response_single_branch() {
1178        let response = TreeResponse {
1179            branches: vec![TreeBranch {
1180                thought: "Only option".to_string(),
1181                confidence: 0.9,
1182                rationale: "Best choice".to_string(),
1183            }],
1184            recommended_branch: 0,
1185            metadata: serde_json::json!({}),
1186        };
1187        assert_eq!(response.branches.len(), 1);
1188        assert_eq!(response.recommended_branch, 0);
1189    }
1190
1191    #[test]
1192    fn test_tree_response_four_branches() {
1193        let response = TreeResponse {
1194            branches: vec![
1195                TreeBranch {
1196                    thought: "Branch 1".to_string(),
1197                    confidence: 0.8,
1198                    rationale: "Option 1".to_string(),
1199                },
1200                TreeBranch {
1201                    thought: "Branch 2".to_string(),
1202                    confidence: 0.85,
1203                    rationale: "Option 2".to_string(),
1204                },
1205                TreeBranch {
1206                    thought: "Branch 3".to_string(),
1207                    confidence: 0.7,
1208                    rationale: "Option 3".to_string(),
1209                },
1210                TreeBranch {
1211                    thought: "Branch 4".to_string(),
1212                    confidence: 0.9,
1213                    rationale: "Option 4".to_string(),
1214                },
1215            ],
1216            recommended_branch: 3,
1217            metadata: serde_json::json!({}),
1218        };
1219        assert_eq!(response.branches.len(), 4);
1220        assert_eq!(response.recommended_branch, 3);
1221    }
1222
1223    #[test]
1224    fn test_tree_response_with_metadata() {
1225        let response = TreeResponse {
1226            branches: vec![],
1227            recommended_branch: 0,
1228            metadata: serde_json::json!({
1229                "total_time": 123,
1230                "model": "gpt-4",
1231                "tokens": 456
1232            }),
1233        };
1234        assert_eq!(response.metadata["total_time"], 123);
1235        assert_eq!(response.metadata["model"], "gpt-4");
1236        assert_eq!(response.metadata["tokens"], 456);
1237    }
1238
1239    #[test]
1240    fn test_tree_response_deserialize_with_default_metadata() {
1241        let json = r#"{
1242            "branches": [],
1243            "recommended_branch": 0
1244        }"#;
1245        let response: TreeResponse = serde_json::from_str(json).unwrap();
1246        // Default metadata is Value::Null (serde_json::Value default)
1247        assert!(response.metadata.is_null());
1248    }
1249
1250    // ============================================================================
1251    // TreeBranch Additional Tests
1252    // ============================================================================
1253
1254    #[test]
1255    fn test_tree_branch_zero_confidence() {
1256        let branch = TreeBranch {
1257            thought: "Low confidence branch".to_string(),
1258            confidence: 0.0,
1259            rationale: "Uncertain".to_string(),
1260        };
1261        assert_eq!(branch.confidence, 0.0);
1262    }
1263
1264    #[test]
1265    fn test_tree_branch_max_confidence() {
1266        let branch = TreeBranch {
1267            thought: "High confidence branch".to_string(),
1268            confidence: 1.0,
1269            rationale: "Very certain".to_string(),
1270        };
1271        assert_eq!(branch.confidence, 1.0);
1272    }
1273
1274    #[test]
1275    fn test_tree_branch_empty_thought() {
1276        let branch = TreeBranch {
1277            thought: "".to_string(),
1278            confidence: 0.5,
1279            rationale: "No content".to_string(),
1280        };
1281        assert_eq!(branch.thought, "");
1282    }
1283
1284    #[test]
1285    fn test_tree_branch_empty_rationale() {
1286        let branch = TreeBranch {
1287            thought: "Branch content".to_string(),
1288            confidence: 0.5,
1289            rationale: "".to_string(),
1290        };
1291        assert_eq!(branch.rationale, "");
1292    }
1293
1294    // ============================================================================
1295    // BranchInfo Additional Tests
1296    // ============================================================================
1297
1298    #[test]
1299    fn test_branch_info_zero_confidence() {
1300        let info = BranchInfo {
1301            id: "b-1".to_string(),
1302            name: "Branch".to_string(),
1303            confidence: 0.0,
1304            rationale: "Low".to_string(),
1305        };
1306        assert_eq!(info.confidence, 0.0);
1307    }
1308
1309    #[test]
1310    fn test_branch_info_max_confidence() {
1311        let info = BranchInfo {
1312            id: "b-1".to_string(),
1313            name: "Branch".to_string(),
1314            confidence: 1.0,
1315            rationale: "High".to_string(),
1316        };
1317        assert_eq!(info.confidence, 1.0);
1318    }
1319
1320    #[test]
1321    fn test_branch_info_empty_name() {
1322        let info = BranchInfo {
1323            id: "b-1".to_string(),
1324            name: "".to_string(),
1325            confidence: 0.8,
1326            rationale: "Rationale".to_string(),
1327        };
1328        assert_eq!(info.name, "");
1329    }
1330
1331    #[test]
1332    fn test_branch_info_long_name() {
1333        let long_name = "A".repeat(200);
1334        let info = BranchInfo {
1335            id: "b-1".to_string(),
1336            name: long_name.clone(),
1337            confidence: 0.8,
1338            rationale: "Rationale".to_string(),
1339        };
1340        assert_eq!(info.name, long_name);
1341    }
1342
1343    // ============================================================================
1344    // Truncate Additional Edge Cases
1345    // ============================================================================
1346
1347    #[test]
1348    fn test_truncate_empty_string() {
1349        assert_eq!(truncate("", 10), "");
1350    }
1351
1352    #[test]
1353    fn test_truncate_max_len_zero() {
1354        assert_eq!(truncate("Hello", 0), "...");
1355    }
1356
1357    #[test]
1358    fn test_truncate_max_len_one() {
1359        assert_eq!(truncate("Hello", 1), "...");
1360    }
1361
1362    #[test]
1363    fn test_truncate_max_len_two() {
1364        assert_eq!(truncate("Hello", 2), "...");
1365    }
1366
1367    #[test]
1368    fn test_truncate_unicode() {
1369        // Test with ASCII string (truncate function uses byte indexing, not unicode-safe)
1370        let result = truncate("Hello World!", 10);
1371        assert!(result.len() <= 10);
1372        assert!(result.ends_with("..."));
1373    }
1374
1375    // ============================================================================
1376    // Serialization Round-trip Tests
1377    // ============================================================================
1378
1379    #[test]
1380    fn test_tree_params_roundtrip() {
1381        let params = TreeParams::new("Test content")
1382            .with_session("sess-123")
1383            .with_branch("branch-456")
1384            .with_confidence(0.75)
1385            .with_num_branches(3)
1386            .with_cross_ref("ref-1", "supports");
1387
1388        let json = serde_json::to_string(&params).unwrap();
1389        let parsed: TreeParams = serde_json::from_str(&json).unwrap();
1390
1391        assert_eq!(parsed.content, params.content);
1392        assert_eq!(parsed.session_id, params.session_id);
1393        assert_eq!(parsed.branch_id, params.branch_id);
1394        assert_eq!(parsed.confidence, params.confidence);
1395        assert_eq!(parsed.num_branches, params.num_branches);
1396        assert_eq!(parsed.cross_refs.len(), params.cross_refs.len());
1397    }
1398
1399    #[test]
1400    fn test_tree_response_roundtrip() {
1401        let response = TreeResponse {
1402            branches: vec![
1403                TreeBranch {
1404                    thought: "Path 1".to_string(),
1405                    confidence: 0.8,
1406                    rationale: "Reason 1".to_string(),
1407                },
1408                TreeBranch {
1409                    thought: "Path 2".to_string(),
1410                    confidence: 0.9,
1411                    rationale: "Reason 2".to_string(),
1412                },
1413            ],
1414            recommended_branch: 1,
1415            metadata: serde_json::json!({"key": "value"}),
1416        };
1417
1418        let json = serde_json::to_string(&response).unwrap();
1419        let parsed: TreeResponse = serde_json::from_str(&json).unwrap();
1420
1421        assert_eq!(parsed.branches.len(), response.branches.len());
1422        assert_eq!(parsed.recommended_branch, response.recommended_branch);
1423        assert_eq!(parsed.metadata, response.metadata);
1424    }
1425
1426    #[test]
1427    fn test_tree_result_roundtrip() {
1428        let result = TreeResult {
1429            session_id: "s-1".to_string(),
1430            branch_id: "b-1".to_string(),
1431            thought_id: "t-1".to_string(),
1432            content: "Content".to_string(),
1433            confidence: 0.88,
1434            child_branches: vec![BranchInfo {
1435                id: "c-1".to_string(),
1436                name: "Child".to_string(),
1437                confidence: 0.77,
1438                rationale: "Child rationale".to_string(),
1439            }],
1440            recommended_branch_index: 0,
1441            parent_branch: Some("p-1".to_string()),
1442            cross_refs_created: 3,
1443        };
1444
1445        let json = serde_json::to_string(&result).unwrap();
1446        let parsed: TreeResult = serde_json::from_str(&json).unwrap();
1447
1448        assert_eq!(parsed.session_id, result.session_id);
1449        assert_eq!(parsed.branch_id, result.branch_id);
1450        assert_eq!(parsed.thought_id, result.thought_id);
1451        assert_eq!(parsed.confidence, result.confidence);
1452        assert_eq!(parsed.child_branches.len(), result.child_branches.len());
1453        assert_eq!(
1454            parsed.recommended_branch_index,
1455            result.recommended_branch_index
1456        );
1457        assert_eq!(parsed.cross_refs_created, result.cross_refs_created);
1458    }
1459
1460    #[test]
1461    fn test_cross_ref_input_roundtrip() {
1462        let cr = CrossRefInput {
1463            to_branch: "target".to_string(),
1464            ref_type: "contradicts".to_string(),
1465            reason: Some("Conflicts".to_string()),
1466            strength: Some(0.85),
1467        };
1468
1469        let json = serde_json::to_string(&cr).unwrap();
1470        let parsed: CrossRefInput = serde_json::from_str(&json).unwrap();
1471
1472        assert_eq!(parsed.to_branch, cr.to_branch);
1473        assert_eq!(parsed.ref_type, cr.ref_type);
1474        assert_eq!(parsed.reason, cr.reason);
1475        assert_eq!(parsed.strength, cr.strength);
1476    }
1477
1478    #[test]
1479    fn test_branch_info_roundtrip() {
1480        let info = BranchInfo {
1481            id: "b-123".to_string(),
1482            name: "Test Branch".to_string(),
1483            confidence: 0.92,
1484            rationale: "Good choice".to_string(),
1485        };
1486
1487        let json = serde_json::to_string(&info).unwrap();
1488        let parsed: BranchInfo = serde_json::from_str(&json).unwrap();
1489
1490        assert_eq!(parsed.id, info.id);
1491        assert_eq!(parsed.name, info.name);
1492        assert_eq!(parsed.confidence, info.confidence);
1493        assert_eq!(parsed.rationale, info.rationale);
1494    }
1495
1496    // ============================================================================
1497    // TreeParams Field Skipping Tests (Optional Fields)
1498    // ============================================================================
1499
1500    #[test]
1501    fn test_tree_params_serialize_skips_none_session() {
1502        let params = TreeParams::new("Content");
1503        let json = serde_json::to_string(&params).unwrap();
1504        assert!(!json.contains("session_id"));
1505    }
1506
1507    #[test]
1508    fn test_tree_params_serialize_includes_some_session() {
1509        let params = TreeParams::new("Content").with_session("sess-1");
1510        let json = serde_json::to_string(&params).unwrap();
1511        assert!(json.contains("session_id"));
1512    }
1513
1514    #[test]
1515    fn test_tree_params_serialize_skips_none_branch() {
1516        let params = TreeParams::new("Content");
1517        let json = serde_json::to_string(&params).unwrap();
1518        assert!(!json.contains("branch_id"));
1519    }
1520
1521    #[test]
1522    fn test_tree_params_serialize_includes_some_branch() {
1523        let params = TreeParams::new("Content").with_branch("b-1");
1524        let json = serde_json::to_string(&params).unwrap();
1525        assert!(json.contains("branch_id"));
1526    }
1527
1528    #[test]
1529    fn test_tree_params_default_cross_refs_empty() {
1530        let params = TreeParams::new("Content");
1531        assert!(params.cross_refs.is_empty());
1532    }
1533
1534    // ============================================================================
1535    // JSON Field Name Tests (serde rename)
1536    // ============================================================================
1537
1538    #[test]
1539    fn test_cross_ref_input_json_uses_type_not_ref_type() {
1540        let cr = CrossRefInput {
1541            to_branch: "b-1".to_string(),
1542            ref_type: "supports".to_string(),
1543            reason: None,
1544            strength: None,
1545        };
1546        let json = serde_json::to_string(&cr).unwrap();
1547        assert!(json.contains(r#""type":"supports""#));
1548        assert!(!json.contains("ref_type"));
1549    }
1550
1551    #[test]
1552    fn test_cross_ref_input_deserialize_from_type_field() {
1553        let json = r#"{"to_branch":"b-1","type":"extends"}"#;
1554        let cr: CrossRefInput = serde_json::from_str(json).unwrap();
1555        assert_eq!(cr.ref_type, "extends");
1556    }
1557
1558    // ============================================================================
1559    // TreeMode Constructor and Pipe Name Tests
1560    // ============================================================================
1561
1562    fn create_test_config() -> Config {
1563        use crate::config::{
1564            DatabaseConfig, ErrorHandlingConfig, LangbaseConfig, LogFormat, LoggingConfig,
1565            PipeConfig,
1566        };
1567        use std::path::PathBuf;
1568
1569        Config {
1570            langbase: LangbaseConfig {
1571                api_key: "test-key".to_string(),
1572                base_url: "https://api.langbase.com".to_string(),
1573            },
1574            database: DatabaseConfig {
1575                path: PathBuf::from(":memory:"),
1576                max_connections: 5,
1577            },
1578            logging: LoggingConfig {
1579                level: "info".to_string(),
1580                format: LogFormat::Pretty,
1581            },
1582            request: crate::config::RequestConfig::default(),
1583            pipes: PipeConfig::default(),
1584            error_handling: ErrorHandlingConfig::default(),
1585        }
1586    }
1587
1588    #[test]
1589    fn test_tree_mode_new() {
1590        use crate::config::RequestConfig;
1591        use crate::langbase::LangbaseClient;
1592        use crate::storage::SqliteStorage;
1593
1594        let config = create_test_config();
1595        let rt = tokio::runtime::Runtime::new().unwrap();
1596        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1597        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1598
1599        let tree_mode = TreeMode::new(storage, langbase, &config);
1600        assert_eq!(tree_mode.pipe_name, config.pipes.tree);
1601    }
1602
1603    #[test]
1604    fn test_tree_mode_custom_pipe_name() {
1605        use crate::config::RequestConfig;
1606        use crate::langbase::LangbaseClient;
1607        use crate::storage::SqliteStorage;
1608
1609        let mut config = create_test_config();
1610        config.pipes.tree = "custom-tree-pipe".to_string();
1611
1612        let rt = tokio::runtime::Runtime::new().unwrap();
1613        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1614        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1615
1616        let tree_mode = TreeMode::new(storage, langbase, &config);
1617        assert_eq!(tree_mode.pipe_name, "custom-tree-pipe");
1618    }
1619
1620    // ============================================================================
1621    // build_messages() Tests
1622    // ============================================================================
1623
1624    #[test]
1625    fn test_build_messages_empty_content() {
1626        use crate::config::RequestConfig;
1627        use crate::langbase::LangbaseClient;
1628        use crate::storage::SqliteStorage;
1629
1630        let config = create_test_config();
1631        let rt = tokio::runtime::Runtime::new().unwrap();
1632        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1633        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1634
1635        let tree_mode = TreeMode::new(storage, langbase, &config);
1636        let messages = tree_mode.build_messages("", &[], 3);
1637
1638        // Should have system prompt + user message
1639        assert_eq!(messages.len(), 2);
1640        assert!(messages[0].content.contains("3 distinct reasoning paths"));
1641        assert_eq!(messages[1].content, "");
1642    }
1643
1644    #[test]
1645    fn test_build_messages_no_history() {
1646        use crate::config::RequestConfig;
1647        use crate::langbase::LangbaseClient;
1648        use crate::storage::SqliteStorage;
1649
1650        let config = create_test_config();
1651        let rt = tokio::runtime::Runtime::new().unwrap();
1652        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1653        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1654
1655        let tree_mode = TreeMode::new(storage, langbase, &config);
1656        let messages = tree_mode.build_messages("Test content", &[], 3);
1657
1658        assert_eq!(messages.len(), 2);
1659        assert_eq!(messages[1].content, "Test content");
1660    }
1661
1662    #[test]
1663    fn test_build_messages_with_history() {
1664        use crate::config::RequestConfig;
1665        use crate::langbase::LangbaseClient;
1666        use crate::storage::{SqliteStorage, Thought};
1667
1668        let config = create_test_config();
1669        let rt = tokio::runtime::Runtime::new().unwrap();
1670        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1671        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1672
1673        let tree_mode = TreeMode::new(storage, langbase, &config);
1674
1675        let history = vec![
1676            Thought::new("sess-1", "First thought", "tree"),
1677            Thought::new("sess-1", "Second thought", "tree"),
1678        ];
1679
1680        let messages = tree_mode.build_messages("Current thought", &history, 3);
1681
1682        // Should have: system prompt + history context + current content
1683        assert_eq!(messages.len(), 3);
1684        assert!(messages[1].content.contains("Previous reasoning"));
1685        assert!(messages[1].content.contains("First thought"));
1686        assert!(messages[1].content.contains("Second thought"));
1687        assert_eq!(messages[2].content, "Current thought");
1688    }
1689
1690    #[test]
1691    fn test_build_messages_num_branches_2() {
1692        use crate::config::RequestConfig;
1693        use crate::langbase::LangbaseClient;
1694        use crate::storage::SqliteStorage;
1695
1696        let config = create_test_config();
1697        let rt = tokio::runtime::Runtime::new().unwrap();
1698        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1699        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1700
1701        let tree_mode = TreeMode::new(storage, langbase, &config);
1702        let messages = tree_mode.build_messages("Content", &[], 2);
1703
1704        assert!(messages[0].content.contains("2 distinct reasoning paths"));
1705    }
1706
1707    #[test]
1708    fn test_build_messages_num_branches_4() {
1709        use crate::config::RequestConfig;
1710        use crate::langbase::LangbaseClient;
1711        use crate::storage::SqliteStorage;
1712
1713        let config = create_test_config();
1714        let rt = tokio::runtime::Runtime::new().unwrap();
1715        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1716        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1717
1718        let tree_mode = TreeMode::new(storage, langbase, &config);
1719        let messages = tree_mode.build_messages("Content", &[], 4);
1720
1721        assert!(messages[0].content.contains("4 distinct reasoning paths"));
1722    }
1723
1724    #[test]
1725    fn test_build_messages_unicode_content() {
1726        use crate::config::RequestConfig;
1727        use crate::langbase::LangbaseClient;
1728        use crate::storage::SqliteStorage;
1729
1730        let config = create_test_config();
1731        let rt = tokio::runtime::Runtime::new().unwrap();
1732        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1733        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1734
1735        let tree_mode = TreeMode::new(storage, langbase, &config);
1736        let unicode_content = "Unicode: 世界 🌍 مرحبا";
1737        let messages = tree_mode.build_messages(unicode_content, &[], 3);
1738
1739        assert_eq!(messages.len(), 2);
1740        assert_eq!(messages[1].content, unicode_content);
1741    }
1742
1743    #[test]
1744    fn test_build_messages_multiline_content() {
1745        use crate::config::RequestConfig;
1746        use crate::langbase::LangbaseClient;
1747        use crate::storage::SqliteStorage;
1748
1749        let config = create_test_config();
1750        let rt = tokio::runtime::Runtime::new().unwrap();
1751        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1752        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1753
1754        let tree_mode = TreeMode::new(storage, langbase, &config);
1755        let multiline = "Line 1\nLine 2\nLine 3";
1756        let messages = tree_mode.build_messages(multiline, &[], 3);
1757
1758        assert_eq!(messages[1].content, multiline);
1759        assert!(messages[1].content.contains('\n'));
1760    }
1761
1762    #[test]
1763    fn test_build_messages_special_characters() {
1764        use crate::config::RequestConfig;
1765        use crate::langbase::LangbaseClient;
1766        use crate::storage::SqliteStorage;
1767
1768        let config = create_test_config();
1769        let rt = tokio::runtime::Runtime::new().unwrap();
1770        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1771        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1772
1773        let tree_mode = TreeMode::new(storage, langbase, &config);
1774        let special = "Special: \n\t\r\"'\\{}[]()!@#$%^&*";
1775        let messages = tree_mode.build_messages(special, &[], 3);
1776
1777        assert_eq!(messages[1].content, special);
1778    }
1779
1780    #[test]
1781    fn test_build_messages_long_history() {
1782        use crate::config::RequestConfig;
1783        use crate::langbase::LangbaseClient;
1784        use crate::storage::{SqliteStorage, Thought};
1785
1786        let config = create_test_config();
1787        let rt = tokio::runtime::Runtime::new().unwrap();
1788        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1789        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1790
1791        let tree_mode = TreeMode::new(storage, langbase, &config);
1792
1793        let history: Vec<Thought> = (0..10)
1794            .map(|i| Thought::new("sess-1", format!("Thought {}", i), "tree"))
1795            .collect();
1796
1797        let messages = tree_mode.build_messages("Current", &history, 3);
1798
1799        assert_eq!(messages.len(), 3);
1800        assert!(messages[1].content.contains("Thought 0"));
1801        assert!(messages[1].content.contains("Thought 9"));
1802    }
1803
1804    // ============================================================================
1805    // parse_response() Tests
1806    // ============================================================================
1807
1808    #[test]
1809    fn test_parse_response_valid_json() {
1810        use crate::config::RequestConfig;
1811        use crate::langbase::LangbaseClient;
1812        use crate::storage::SqliteStorage;
1813
1814        let config = create_test_config();
1815        let rt = tokio::runtime::Runtime::new().unwrap();
1816        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1817        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1818
1819        let tree_mode = TreeMode::new(storage, langbase, &config);
1820
1821        let json = r#"{
1822            "branches": [
1823                {"thought": "Branch 1", "confidence": 0.8, "rationale": "Reason 1"},
1824                {"thought": "Branch 2", "confidence": 0.7, "rationale": "Reason 2"}
1825            ],
1826            "recommended_branch": 0
1827        }"#;
1828
1829        let response = tree_mode.parse_response(json).unwrap();
1830        assert_eq!(response.branches.len(), 2);
1831        assert_eq!(response.recommended_branch, 0);
1832        assert_eq!(response.branches[0].thought, "Branch 1");
1833    }
1834
1835    #[test]
1836    fn test_parse_response_with_markdown_json() {
1837        use crate::config::RequestConfig;
1838        use crate::langbase::LangbaseClient;
1839        use crate::storage::SqliteStorage;
1840
1841        let config = create_test_config();
1842        let rt = tokio::runtime::Runtime::new().unwrap();
1843        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1844        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1845
1846        let tree_mode = TreeMode::new(storage, langbase, &config);
1847
1848        let completion = r#"Here's the tree response:
1849```json
1850{
1851    "branches": [
1852        {"thought": "Path A", "confidence": 0.9, "rationale": "Strong"}
1853    ],
1854    "recommended_branch": 0
1855}
1856```"#;
1857
1858        let response = tree_mode.parse_response(completion).unwrap();
1859        assert_eq!(response.branches.len(), 1);
1860        assert_eq!(response.branches[0].thought, "Path A");
1861    }
1862
1863    #[test]
1864    fn test_parse_response_with_code_block() {
1865        use crate::config::RequestConfig;
1866        use crate::langbase::LangbaseClient;
1867        use crate::storage::SqliteStorage;
1868
1869        let config = create_test_config();
1870        let rt = tokio::runtime::Runtime::new().unwrap();
1871        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1872        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1873
1874        let tree_mode = TreeMode::new(storage, langbase, &config);
1875
1876        let completion = r#"
1877```
1878{
1879    "branches": [
1880        {"thought": "Option 1", "confidence": 0.85, "rationale": "Good"}
1881    ],
1882    "recommended_branch": 0
1883}
1884```"#;
1885
1886        let response = tree_mode.parse_response(completion).unwrap();
1887        assert_eq!(response.branches.len(), 1);
1888    }
1889
1890    #[test]
1891    fn test_parse_response_with_metadata() {
1892        use crate::config::RequestConfig;
1893        use crate::langbase::LangbaseClient;
1894        use crate::storage::SqliteStorage;
1895
1896        let config = create_test_config();
1897        let rt = tokio::runtime::Runtime::new().unwrap();
1898        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1899        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1900
1901        let tree_mode = TreeMode::new(storage, langbase, &config);
1902
1903        let json = r#"{
1904            "branches": [
1905                {"thought": "Test", "confidence": 0.8, "rationale": "Testing"}
1906            ],
1907            "recommended_branch": 0,
1908            "metadata": {"analysis": "complete", "duration": 123}
1909        }"#;
1910
1911        let response = tree_mode.parse_response(json).unwrap();
1912        assert_eq!(response.metadata["analysis"], "complete");
1913        assert_eq!(response.metadata["duration"], 123);
1914    }
1915
1916    #[test]
1917    fn test_parse_response_empty_branches() {
1918        use crate::config::RequestConfig;
1919        use crate::langbase::LangbaseClient;
1920        use crate::storage::SqliteStorage;
1921
1922        let config = create_test_config();
1923        let rt = tokio::runtime::Runtime::new().unwrap();
1924        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1925        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1926
1927        let tree_mode = TreeMode::new(storage, langbase, &config);
1928
1929        let json = r#"{
1930            "branches": [],
1931            "recommended_branch": 0
1932        }"#;
1933
1934        let response = tree_mode.parse_response(json).unwrap();
1935        assert!(response.branches.is_empty());
1936    }
1937
1938    #[test]
1939    fn test_parse_response_four_branches() {
1940        use crate::config::RequestConfig;
1941        use crate::langbase::LangbaseClient;
1942        use crate::storage::SqliteStorage;
1943
1944        let config = create_test_config();
1945        let rt = tokio::runtime::Runtime::new().unwrap();
1946        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1947        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1948
1949        let tree_mode = TreeMode::new(storage, langbase, &config);
1950
1951        let json = r#"{
1952            "branches": [
1953                {"thought": "A", "confidence": 0.8, "rationale": "R1"},
1954                {"thought": "B", "confidence": 0.85, "rationale": "R2"},
1955                {"thought": "C", "confidence": 0.7, "rationale": "R3"},
1956                {"thought": "D", "confidence": 0.9, "rationale": "R4"}
1957            ],
1958            "recommended_branch": 3
1959        }"#;
1960
1961        let response = tree_mode.parse_response(json).unwrap();
1962        assert_eq!(response.branches.len(), 4);
1963        assert_eq!(response.recommended_branch, 3);
1964    }
1965
1966    #[test]
1967    fn test_parse_response_unicode_in_branches() {
1968        use crate::config::RequestConfig;
1969        use crate::langbase::LangbaseClient;
1970        use crate::storage::SqliteStorage;
1971
1972        let config = create_test_config();
1973        let rt = tokio::runtime::Runtime::new().unwrap();
1974        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
1975        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
1976
1977        let tree_mode = TreeMode::new(storage, langbase, &config);
1978
1979        let json = r#"{
1980            "branches": [
1981                {"thought": "世界 🌍", "confidence": 0.8, "rationale": "مرحبا"}
1982            ],
1983            "recommended_branch": 0
1984        }"#;
1985
1986        let response = tree_mode.parse_response(json).unwrap();
1987        assert!(response.branches[0].thought.contains("世界"));
1988        assert!(response.branches[0].rationale.contains("مرحبا"));
1989    }
1990
1991    #[test]
1992    fn test_parse_response_invalid_json_error() {
1993        use crate::config::RequestConfig;
1994        use crate::langbase::LangbaseClient;
1995        use crate::storage::SqliteStorage;
1996
1997        let config = create_test_config();
1998        let rt = tokio::runtime::Runtime::new().unwrap();
1999        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
2000        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
2001
2002        let tree_mode = TreeMode::new(storage, langbase, &config);
2003
2004        let invalid = "This is not JSON at all";
2005        let result = tree_mode.parse_response(invalid);
2006        assert!(result.is_err());
2007    }
2008
2009    #[test]
2010    fn test_parse_response_missing_branches_field_error() {
2011        use crate::config::RequestConfig;
2012        use crate::langbase::LangbaseClient;
2013        use crate::storage::SqliteStorage;
2014
2015        let config = create_test_config();
2016        let rt = tokio::runtime::Runtime::new().unwrap();
2017        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
2018        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
2019
2020        let tree_mode = TreeMode::new(storage, langbase, &config);
2021
2022        let json = r#"{"recommended_branch": 0}"#;
2023        let result = tree_mode.parse_response(json);
2024        assert!(result.is_err());
2025    }
2026
2027    #[test]
2028    fn test_parse_response_missing_recommended_branch_error() {
2029        use crate::config::RequestConfig;
2030        use crate::langbase::LangbaseClient;
2031        use crate::storage::SqliteStorage;
2032
2033        let config = create_test_config();
2034        let rt = tokio::runtime::Runtime::new().unwrap();
2035        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
2036        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
2037
2038        let tree_mode = TreeMode::new(storage, langbase, &config);
2039
2040        let json = r#"{"branches": []}"#;
2041        let result = tree_mode.parse_response(json);
2042        assert!(result.is_err());
2043    }
2044
2045    #[test]
2046    fn test_parse_response_malformed_branch_error() {
2047        use crate::config::RequestConfig;
2048        use crate::langbase::LangbaseClient;
2049        use crate::storage::SqliteStorage;
2050
2051        let config = create_test_config();
2052        let rt = tokio::runtime::Runtime::new().unwrap();
2053        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
2054        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
2055
2056        let tree_mode = TreeMode::new(storage, langbase, &config);
2057
2058        let json = r#"{
2059            "branches": [
2060                {"thought": "Test"}
2061            ],
2062            "recommended_branch": 0
2063        }"#;
2064        let result = tree_mode.parse_response(json);
2065        assert!(result.is_err());
2066    }
2067
2068    #[test]
2069    fn test_parse_response_special_chars_in_thought() {
2070        use crate::config::RequestConfig;
2071        use crate::langbase::LangbaseClient;
2072        use crate::storage::SqliteStorage;
2073
2074        let config = create_test_config();
2075        let rt = tokio::runtime::Runtime::new().unwrap();
2076        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
2077        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
2078
2079        let tree_mode = TreeMode::new(storage, langbase, &config);
2080
2081        let json = r#"{
2082            "branches": [
2083                {"thought": "Test\n\twith \"quotes\"", "confidence": 0.8, "rationale": "Special chars"}
2084            ],
2085            "recommended_branch": 0
2086        }"#;
2087
2088        let response = tree_mode.parse_response(json).unwrap();
2089        assert!(response.branches[0].thought.contains("Test"));
2090    }
2091
2092    #[test]
2093    fn test_parse_response_empty_thought_string() {
2094        use crate::config::RequestConfig;
2095        use crate::langbase::LangbaseClient;
2096        use crate::storage::SqliteStorage;
2097
2098        let config = create_test_config();
2099        let rt = tokio::runtime::Runtime::new().unwrap();
2100        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
2101        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
2102
2103        let tree_mode = TreeMode::new(storage, langbase, &config);
2104
2105        let json = r#"{
2106            "branches": [
2107                {"thought": "", "confidence": 0.8, "rationale": "Empty thought"}
2108            ],
2109            "recommended_branch": 0
2110        }"#;
2111
2112        let response = tree_mode.parse_response(json).unwrap();
2113        assert_eq!(response.branches[0].thought, "");
2114    }
2115
2116    #[test]
2117    fn test_parse_response_zero_confidence() {
2118        use crate::config::RequestConfig;
2119        use crate::langbase::LangbaseClient;
2120        use crate::storage::SqliteStorage;
2121
2122        let config = create_test_config();
2123        let rt = tokio::runtime::Runtime::new().unwrap();
2124        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
2125        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
2126
2127        let tree_mode = TreeMode::new(storage, langbase, &config);
2128
2129        let json = r#"{
2130            "branches": [
2131                {"thought": "Low confidence", "confidence": 0.0, "rationale": "Uncertain"}
2132            ],
2133            "recommended_branch": 0
2134        }"#;
2135
2136        let response = tree_mode.parse_response(json).unwrap();
2137        assert_eq!(response.branches[0].confidence, 0.0);
2138    }
2139
2140    #[test]
2141    fn test_parse_response_max_confidence() {
2142        use crate::config::RequestConfig;
2143        use crate::langbase::LangbaseClient;
2144        use crate::storage::SqliteStorage;
2145
2146        let config = create_test_config();
2147        let rt = tokio::runtime::Runtime::new().unwrap();
2148        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
2149        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
2150
2151        let tree_mode = TreeMode::new(storage, langbase, &config);
2152
2153        let json = r#"{
2154            "branches": [
2155                {"thought": "High confidence", "confidence": 1.0, "rationale": "Very certain"}
2156            ],
2157            "recommended_branch": 0
2158        }"#;
2159
2160        let response = tree_mode.parse_response(json).unwrap();
2161        assert_eq!(response.branches[0].confidence, 1.0);
2162    }
2163
2164    #[test]
2165    fn test_parse_response_empty_completion_error() {
2166        use crate::config::RequestConfig;
2167        use crate::langbase::LangbaseClient;
2168        use crate::storage::SqliteStorage;
2169
2170        let config = create_test_config();
2171        let rt = tokio::runtime::Runtime::new().unwrap();
2172        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
2173        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
2174
2175        let tree_mode = TreeMode::new(storage, langbase, &config);
2176
2177        let result = tree_mode.parse_response("");
2178        assert!(result.is_err());
2179    }
2180
2181    #[test]
2182    fn test_parse_response_whitespace_only_error() {
2183        use crate::config::RequestConfig;
2184        use crate::langbase::LangbaseClient;
2185        use crate::storage::SqliteStorage;
2186
2187        let config = create_test_config();
2188        let rt = tokio::runtime::Runtime::new().unwrap();
2189        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
2190        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
2191
2192        let tree_mode = TreeMode::new(storage, langbase, &config);
2193
2194        let result = tree_mode.parse_response("   \n\t  ");
2195        assert!(result.is_err());
2196    }
2197
2198    #[test]
2199    fn test_parse_response_large_recommended_index() {
2200        use crate::config::RequestConfig;
2201        use crate::langbase::LangbaseClient;
2202        use crate::storage::SqliteStorage;
2203
2204        let config = create_test_config();
2205        let rt = tokio::runtime::Runtime::new().unwrap();
2206        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
2207        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
2208
2209        let tree_mode = TreeMode::new(storage, langbase, &config);
2210
2211        let json = r#"{
2212            "branches": [
2213                {"thought": "Branch 1", "confidence": 0.8, "rationale": "R1"}
2214            ],
2215            "recommended_branch": 999
2216        }"#;
2217
2218        let response = tree_mode.parse_response(json).unwrap();
2219        assert_eq!(response.recommended_branch, 999);
2220    }
2221
2222    #[test]
2223    fn test_parse_response_long_rationale() {
2224        use crate::config::RequestConfig;
2225        use crate::langbase::LangbaseClient;
2226        use crate::storage::SqliteStorage;
2227
2228        let config = create_test_config();
2229        let rt = tokio::runtime::Runtime::new().unwrap();
2230        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
2231        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
2232
2233        let tree_mode = TreeMode::new(storage, langbase, &config);
2234
2235        let long_rationale = "A".repeat(10000);
2236        let json = format!(
2237            r#"{{
2238            "branches": [
2239                {{"thought": "Test", "confidence": 0.8, "rationale": "{}"}}
2240            ],
2241            "recommended_branch": 0
2242        }}"#,
2243            long_rationale
2244        );
2245
2246        let response = tree_mode.parse_response(&json).unwrap();
2247        assert_eq!(response.branches[0].rationale.len(), 10000);
2248    }
2249
2250    #[test]
2251    fn test_parse_response_partial_json_block_error() {
2252        use crate::config::RequestConfig;
2253        use crate::langbase::LangbaseClient;
2254        use crate::storage::SqliteStorage;
2255
2256        let config = create_test_config();
2257        let rt = tokio::runtime::Runtime::new().unwrap();
2258        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
2259        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
2260
2261        let tree_mode = TreeMode::new(storage, langbase, &config);
2262
2263        let incomplete = r#"```json
2264{"branches": [{"thought""#;
2265        let result = tree_mode.parse_response(incomplete);
2266        assert!(result.is_err());
2267    }
2268
2269    #[test]
2270    fn test_parse_response_json_with_comments_error() {
2271        use crate::config::RequestConfig;
2272        use crate::langbase::LangbaseClient;
2273        use crate::storage::SqliteStorage;
2274
2275        let config = create_test_config();
2276        let rt = tokio::runtime::Runtime::new().unwrap();
2277        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
2278        let langbase = LangbaseClient::new(&config.langbase, RequestConfig::default()).unwrap();
2279
2280        let tree_mode = TreeMode::new(storage, langbase, &config);
2281
2282        let json_with_comments = r#"{
2283            // This is a comment
2284            "branches": [],
2285            "recommended_branch": 0
2286        }"#;
2287        let result = tree_mode.parse_response(json_with_comments);
2288        assert!(result.is_err());
2289    }
2290
2291    // ============================================================================
2292    // Additional Edge Case Tests
2293    // ============================================================================
2294
2295    #[test]
2296    fn test_tree_params_very_long_content() {
2297        let long_content = "a".repeat(100000);
2298        let params = TreeParams::new(long_content.clone());
2299        assert_eq!(params.content.len(), 100000);
2300    }
2301
2302    #[test]
2303    fn test_tree_params_cross_refs_with_all_fields() {
2304        let cr = CrossRefInput {
2305            to_branch: "target".to_string(),
2306            ref_type: "supports".to_string(),
2307            reason: Some("Strong evidence".to_string()),
2308            strength: Some(0.95),
2309        };
2310
2311        // Test serialization of fully-populated CrossRefInput
2312        let json = serde_json::to_string(&cr).unwrap();
2313        assert!(json.contains("Strong evidence"));
2314        assert!(json.contains("0.95"));
2315    }
2316
2317    #[test]
2318    fn test_truncate_unicode_safe() {
2319        // Note: truncate uses byte slicing, so may not be unicode-safe
2320        let ascii = "Hello World";
2321        let result = truncate(ascii, 8);
2322        assert_eq!(result, "Hello...");
2323    }
2324
2325    #[test]
2326    fn test_tree_branch_clone() {
2327        let branch = TreeBranch {
2328            thought: "Test".to_string(),
2329            confidence: 0.8,
2330            rationale: "Reason".to_string(),
2331        };
2332
2333        let cloned = branch.clone();
2334        assert_eq!(cloned.thought, branch.thought);
2335        assert_eq!(cloned.confidence, branch.confidence);
2336        assert_eq!(cloned.rationale, branch.rationale);
2337    }
2338
2339    #[test]
2340    fn test_tree_response_clone() {
2341        let response = TreeResponse {
2342            branches: vec![TreeBranch {
2343                thought: "Test".to_string(),
2344                confidence: 0.8,
2345                rationale: "Reason".to_string(),
2346            }],
2347            recommended_branch: 0,
2348            metadata: serde_json::json!({"key": "value"}),
2349        };
2350
2351        let cloned = response.clone();
2352        assert_eq!(cloned.branches.len(), response.branches.len());
2353        assert_eq!(cloned.recommended_branch, response.recommended_branch);
2354    }
2355
2356    #[test]
2357    fn test_tree_result_debug_format() {
2358        let result = TreeResult {
2359            session_id: "s-1".to_string(),
2360            branch_id: "b-1".to_string(),
2361            thought_id: "t-1".to_string(),
2362            content: "Content".to_string(),
2363            confidence: 0.8,
2364            child_branches: vec![],
2365            recommended_branch_index: 0,
2366            parent_branch: None,
2367            cross_refs_created: 0,
2368        };
2369
2370        let debug_str = format!("{:?}", result);
2371        assert!(debug_str.contains("TreeResult"));
2372        assert!(debug_str.contains("s-1"));
2373    }
2374
2375    #[test]
2376    fn test_cross_ref_input_debug_format() {
2377        let cr = CrossRefInput {
2378            to_branch: "b-1".to_string(),
2379            ref_type: "supports".to_string(),
2380            reason: Some("Test".to_string()),
2381            strength: Some(0.9),
2382        };
2383
2384        let debug_str = format!("{:?}", cr);
2385        assert!(debug_str.contains("CrossRefInput"));
2386        assert!(debug_str.contains("b-1"));
2387    }
2388
2389    #[test]
2390    fn test_tree_params_content_with_null_bytes() {
2391        // Test that null bytes are preserved (unusual but valid for strings)
2392        let content_with_null = "Before\0After";
2393        let params = TreeParams::new(content_with_null);
2394        assert_eq!(params.content, content_with_null);
2395        assert!(params.content.contains('\0'));
2396    }
2397
2398    #[test]
2399    fn test_tree_response_negative_recommended_branch() {
2400        // Note: In Rust, usize is unsigned so -1 would be serialized as a large number
2401        // This test verifies parsing doesn't panic on unusual values
2402        let json = r#"{
2403            "branches": [
2404                {"thought": "Test", "confidence": 0.8, "rationale": "R"}
2405            ],
2406            "recommended_branch": 0
2407        }"#;
2408
2409        let config = create_test_config();
2410        let rt = tokio::runtime::Runtime::new().unwrap();
2411        let storage = rt.block_on(SqliteStorage::new_in_memory()).unwrap();
2412        let langbase =
2413            LangbaseClient::new(&config.langbase, crate::config::RequestConfig::default()).unwrap();
2414
2415        let tree_mode = TreeMode::new(storage, langbase, &config);
2416        let response = tree_mode.parse_response(json).unwrap();
2417        assert_eq!(response.recommended_branch, 0);
2418    }
2419
2420    #[test]
2421    fn test_branch_info_very_long_rationale() {
2422        let long_rationale = "X".repeat(50000);
2423        let info = BranchInfo {
2424            id: "b-1".to_string(),
2425            name: "Branch".to_string(),
2426            confidence: 0.8,
2427            rationale: long_rationale.clone(),
2428        };
2429
2430        assert_eq!(info.rationale.len(), 50000);
2431        let json = serde_json::to_string(&info).unwrap();
2432        let parsed: BranchInfo = serde_json::from_str(&json).unwrap();
2433        assert_eq!(parsed.rationale.len(), 50000);
2434    }
2435}