1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct TreeParams {
25 pub content: String,
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub session_id: Option<String>,
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub branch_id: Option<String>,
33 #[serde(default = "default_confidence")]
35 pub confidence: f64,
36 #[serde(default = "default_num_branches")]
38 pub num_branches: usize,
39 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct CrossRefInput {
55 pub to_branch: String,
57 #[serde(rename = "type")]
59 pub ref_type: String,
60 pub reason: Option<String>,
62 pub strength: Option<f64>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct TreeResponse {
69 pub branches: Vec<TreeBranch>,
71 pub recommended_branch: usize,
73 #[serde(default)]
75 pub metadata: serde_json::Value,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct TreeBranch {
81 pub thought: String,
83 pub confidence: f64,
85 pub rationale: String,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct TreeResult {
92 pub session_id: String,
94 pub branch_id: String,
96 pub thought_id: String,
98 pub content: String,
100 pub confidence: f64,
102 pub child_branches: Vec<BranchInfo>,
104 pub recommended_branch_index: usize,
106 pub parent_branch: Option<String>,
108 pub cross_refs_created: usize,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct BranchInfo {
115 pub id: String,
117 pub name: String,
119 pub confidence: f64,
121 pub rationale: String,
123}
124
125#[derive(Clone)]
127pub struct TreeMode {
128 core: ModeCore,
130 pipe_name: String,
132}
133
134impl TreeMode {
135 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 pub async fn process(&self, params: TreeParams) -> AppResult<TreeResult> {
145 let start = Instant::now();
146
147 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 let session = self
160 .core
161 .storage()
162 .get_or_create_session(¶ms.session_id, "tree")
163 .await?;
164 debug!(session_id = %session.id, "Processing tree reasoning");
165
166 let parent_branch = match ¶ms.branch_id {
168 Some(id) => self.core.storage().get_branch(id).await?,
169 None => None,
170 };
171
172 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 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 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 let branch_thoughts = self.core.storage().get_branch_thoughts(&branch.id).await?;
206
207 let messages = self.build_messages(¶ms.content, &branch_thoughts, num_branches);
209
210 let mut invocation = Invocation::new(
212 "reasoning.tree",
213 serialize_for_log(¶ms, "reasoning.tree input"),
214 )
215 .with_session(&session.id)
216 .with_pipe(&self.pipe_name);
217
218 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 let tree_response = self.parse_response(&response.completion)?;
232
233 let thought = Thought::new(&session.id, ¶ms.content, "tree")
235 .with_confidence(params.confidence)
236 .with_branch(&branch.id);
237 self.core.storage().create_thought(&thought).await?;
238
239 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 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 let mut cross_refs_created = 0;
274 for cr_input in ¶ms.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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn with_confidence(mut self, confidence: f64) -> Self {
454 self.confidence = confidence.clamp(0.0, 1.0);
455 self
456 }
457
458 pub fn with_num_branches(mut self, num: usize) -> Self {
460 self.num_branches = num.clamp(2, 4);
461 self
462 }
463
464 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 #[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 #[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 #[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); }
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); }
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(¶ms).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); assert_eq!(params.num_branches, 3); assert!(params.cross_refs.is_empty());
670 }
671
672 #[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 #[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 #[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 #[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 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 #[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 #[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); }
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 #[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 #[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 assert!(response.metadata.is_null());
1248 }
1249
1250 #[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 #[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 #[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 let result = truncate("Hello World!", 10);
1371 assert!(result.len() <= 10);
1372 assert!(result.ends_with("..."));
1373 }
1374
1375 #[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(¶ms).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 #[test]
1501 fn test_tree_params_serialize_skips_none_session() {
1502 let params = TreeParams::new("Content");
1503 let json = serde_json::to_string(¶ms).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(¶ms).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(¶ms).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(¶ms).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 #[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 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 #[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 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 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 #[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 #[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 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 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 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 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}