1use crate::segment::{ContextPriority, ContextSegment, ContextSegmentType};
8use chrono::{DateTime, Utc};
9use enact_core::kernel::ExecutionId;
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12
13#[derive(Debug, Error)]
15pub enum CompactionError {
16 #[error("Nothing to compact")]
17 NothingToCompact,
18
19 #[error("Target token count too low: {0}")]
20 TargetTooLow(usize),
21
22 #[error("Summarization failed: {0}")]
23 SummarizationFailed(String),
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum CompactionStrategyType {
32 Truncate,
34 Summarize,
36 ExtractKeyPoints,
38 SlidingWindow,
40 ImportanceWeighted,
42 Hybrid,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct CompactionStrategy {
52 #[serde(rename = "type")]
54 pub strategy_type: CompactionStrategyType,
55
56 pub target_tokens: usize,
58
59 pub min_preserve_percent: u8,
61
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub segments_to_compact: Option<Vec<ContextSegmentType>>,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub protected_segments: Option<Vec<ContextSegmentType>>,
69
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub summary_max_tokens: Option<usize>,
73
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub window_size: Option<usize>,
77
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub min_importance_score: Option<f64>,
81}
82
83impl CompactionStrategy {
84 pub fn truncate(target_tokens: usize) -> Self {
86 Self {
87 strategy_type: CompactionStrategyType::Truncate,
88 target_tokens,
89 min_preserve_percent: 20,
90 segments_to_compact: None,
91 protected_segments: Some(vec![
92 ContextSegmentType::System,
93 ContextSegmentType::UserInput,
94 ]),
95 summary_max_tokens: None,
96 window_size: None,
97 min_importance_score: None,
98 }
99 }
100
101 pub fn sliding_window(target_tokens: usize, window_size: usize) -> Self {
103 Self {
104 strategy_type: CompactionStrategyType::SlidingWindow,
105 target_tokens,
106 min_preserve_percent: 20,
107 segments_to_compact: Some(vec![ContextSegmentType::History]),
108 protected_segments: Some(vec![
109 ContextSegmentType::System,
110 ContextSegmentType::UserInput,
111 ]),
112 summary_max_tokens: None,
113 window_size: Some(window_size),
114 min_importance_score: None,
115 }
116 }
117
118 pub fn summarize(target_tokens: usize, summary_max_tokens: usize) -> Self {
120 Self {
121 strategy_type: CompactionStrategyType::Summarize,
122 target_tokens,
123 min_preserve_percent: 30,
124 segments_to_compact: Some(vec![
125 ContextSegmentType::History,
126 ContextSegmentType::ToolResults,
127 ]),
128 protected_segments: Some(vec![
129 ContextSegmentType::System,
130 ContextSegmentType::UserInput,
131 ContextSegmentType::Guidance,
132 ]),
133 summary_max_tokens: Some(summary_max_tokens),
134 window_size: None,
135 min_importance_score: None,
136 }
137 }
138
139 pub fn is_protected(&self, segment_type: ContextSegmentType) -> bool {
141 self.protected_segments
142 .as_ref()
143 .map(|p| p.contains(&segment_type))
144 .unwrap_or(false)
145 }
146
147 pub fn should_compact(&self, segment_type: ContextSegmentType) -> bool {
149 if self.is_protected(segment_type) {
150 return false;
151 }
152
153 self.segments_to_compact
154 .as_ref()
155 .map(|s| s.contains(&segment_type))
156 .unwrap_or(true) }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
164#[serde(rename_all = "camelCase")]
165pub struct CompactionResult {
166 pub execution_id: ExecutionId,
168
169 pub strategy: CompactionStrategyType,
171
172 pub tokens_before: usize,
174
175 pub tokens_after: usize,
177
178 pub tokens_saved: usize,
180
181 pub compression_ratio: f64,
183
184 pub segments_compacted: usize,
186
187 pub duration_ms: u64,
189
190 pub success: bool,
192
193 #[serde(skip_serializing_if = "Option::is_none")]
195 pub error: Option<String>,
196
197 pub compacted_at: DateTime<Utc>,
199}
200
201impl CompactionResult {
202 pub fn success(
204 execution_id: ExecutionId,
205 strategy: CompactionStrategyType,
206 tokens_before: usize,
207 tokens_after: usize,
208 segments_compacted: usize,
209 duration_ms: u64,
210 ) -> Self {
211 let tokens_saved = tokens_before.saturating_sub(tokens_after);
212 let compression_ratio = if tokens_before > 0 {
213 tokens_after as f64 / tokens_before as f64
214 } else {
215 1.0
216 };
217
218 Self {
219 execution_id,
220 strategy,
221 tokens_before,
222 tokens_after,
223 tokens_saved,
224 compression_ratio,
225 segments_compacted,
226 duration_ms,
227 success: true,
228 error: None,
229 compacted_at: Utc::now(),
230 }
231 }
232
233 pub fn failure(
235 execution_id: ExecutionId,
236 strategy: CompactionStrategyType,
237 tokens_before: usize,
238 error: String,
239 duration_ms: u64,
240 ) -> Self {
241 Self {
242 execution_id,
243 strategy,
244 tokens_before,
245 tokens_after: tokens_before,
246 tokens_saved: 0,
247 compression_ratio: 1.0,
248 segments_compacted: 0,
249 duration_ms,
250 success: false,
251 error: Some(error),
252 compacted_at: Utc::now(),
253 }
254 }
255}
256
257pub struct Compactor {
259 strategy: CompactionStrategy,
260}
261
262impl Compactor {
263 pub fn new(strategy: CompactionStrategy) -> Self {
265 Self { strategy }
266 }
267
268 pub fn truncate(target_tokens: usize) -> Self {
270 Self::new(CompactionStrategy::truncate(target_tokens))
271 }
272
273 pub fn sliding_window(target_tokens: usize, window_size: usize) -> Self {
275 Self::new(CompactionStrategy::sliding_window(
276 target_tokens,
277 window_size,
278 ))
279 }
280
281 pub fn strategy(&self) -> &CompactionStrategy {
283 &self.strategy
284 }
285
286 pub fn compact_truncate(
290 &self,
291 segments: &mut Vec<ContextSegment>,
292 current_tokens: usize,
293 ) -> Result<usize, CompactionError> {
294 if current_tokens <= self.strategy.target_tokens {
295 return Ok(0);
296 }
297
298 let tokens_to_remove = current_tokens - self.strategy.target_tokens;
299 let mut removed = 0;
300
301 segments.sort_by(|a, b| {
303 a.priority
304 .cmp(&b.priority)
305 .then(a.sequence.cmp(&b.sequence))
306 });
307
308 let mut i = 0;
310 while i < segments.len() && removed < tokens_to_remove {
311 let segment = &segments[i];
312
313 if !segment.compressible || self.strategy.is_protected(segment.segment_type) {
315 i += 1;
316 continue;
317 }
318
319 if segment.priority == ContextPriority::Critical {
321 i += 1;
322 continue;
323 }
324
325 removed += segment.token_count;
326 segments.remove(i);
327 }
328
329 Ok(removed)
330 }
331
332 pub fn compact_sliding_window(
336 &self,
337 segments: &mut Vec<ContextSegment>,
338 ) -> Result<usize, CompactionError> {
339 let window_size = self.strategy.window_size.unwrap_or(10);
340
341 let history_indices: Vec<usize> = segments
343 .iter()
344 .enumerate()
345 .filter(|(_, s)| s.segment_type == ContextSegmentType::History)
346 .map(|(i, _)| i)
347 .collect();
348
349 if history_indices.len() <= window_size {
350 return Ok(0);
351 }
352
353 let to_remove = history_indices.len() - window_size;
355 let mut removed_tokens = 0;
356
357 for &idx in history_indices.iter().take(to_remove).rev() {
359 removed_tokens += segments[idx].token_count;
360 segments.remove(idx);
361 }
362
363 Ok(removed_tokens)
364 }
365
366 pub fn summarize(target_tokens: usize, summary_max_tokens: usize) -> Self {
368 Self::new(CompactionStrategy::summarize(
369 target_tokens,
370 summary_max_tokens,
371 ))
372 }
373
374 pub fn compact_summarize(
385 &self,
386 segments: &mut Vec<ContextSegment>,
387 current_tokens: usize,
388 ) -> Result<usize, CompactionError> {
389 if current_tokens <= self.strategy.target_tokens {
390 return Ok(0);
391 }
392
393 let summary_max = self.strategy.summary_max_tokens.unwrap_or(500);
394 let mut removed_tokens = 0;
395
396 let segments_to_compact = self
398 .strategy
399 .segments_to_compact
400 .clone()
401 .unwrap_or_else(|| vec![ContextSegmentType::History, ContextSegmentType::ToolResults]);
402
403 for segment_type in segments_to_compact {
405 let mut indices_to_summarize: Vec<usize> = Vec::new();
407 let mut combined_content = String::new();
408 let mut total_tokens_in_group = 0;
409
410 for (i, segment) in segments.iter().enumerate() {
411 if segment.segment_type == segment_type
412 && segment.compressible
413 && !self.strategy.is_protected(segment.segment_type)
414 && segment.priority != ContextPriority::Critical
415 {
416 indices_to_summarize.push(i);
417 if !combined_content.is_empty() {
418 combined_content.push_str("\n---\n");
419 }
420 combined_content.push_str(&segment.content);
421 total_tokens_in_group += segment.token_count;
422 }
423 }
424
425 if indices_to_summarize.is_empty() || total_tokens_in_group <= summary_max {
427 continue;
428 }
429
430 let summary = self.extract_key_content(&combined_content, summary_max);
432 let summary_tokens = summary.len() / 4; for &idx in indices_to_summarize.iter().rev() {
436 removed_tokens += segments[idx].token_count;
437 segments.remove(idx);
438 }
439
440 if !summary.is_empty() {
442 let summarized_segment = ContextSegment::new(
443 segment_type,
444 format!(
445 "[Summarized {}]\n{}",
446 segment_type_display(segment_type),
447 summary
448 ),
449 summary_tokens,
450 0, )
452 .with_priority(ContextPriority::Low);
453
454 segments.push(summarized_segment);
455 removed_tokens = removed_tokens.saturating_sub(summary_tokens);
456 }
457 }
458
459 if removed_tokens == 0 {
460 return Err(CompactionError::NothingToCompact);
461 }
462
463 Ok(removed_tokens)
464 }
465
466 fn extract_key_content(&self, text: &str, max_tokens: usize) -> String {
473 let sentences: Vec<&str> = text
474 .split(&['.', '!', '?', '\n'][..])
475 .map(|s| s.trim())
476 .filter(|s| !s.is_empty() && s.len() > 10)
477 .collect();
478
479 if sentences.is_empty() {
480 return String::new();
481 }
482
483 let mut scored_sentences: Vec<(usize, &str, i32)> = sentences
485 .iter()
486 .enumerate()
487 .map(|(i, &s)| (i, s, self.score_sentence(s, i, sentences.len())))
488 .collect();
489
490 scored_sentences.sort_by(|a, b| b.2.cmp(&a.2));
492
493 let max_chars = max_tokens * 4; let mut summary_parts: Vec<(usize, &str)> = Vec::new();
496 let mut current_len = 0;
497
498 for (idx, sentence, _score) in scored_sentences {
499 if current_len + sentence.len() + 2 > max_chars {
500 break;
501 }
502 summary_parts.push((idx, sentence));
503 current_len += sentence.len() + 2;
504 }
505
506 summary_parts.sort_by_key(|(idx, _)| *idx);
508
509 summary_parts
511 .iter()
512 .map(|(_, s)| *s)
513 .collect::<Vec<_>>()
514 .join(". ")
515 + "."
516 }
517
518 fn score_sentence(&self, sentence: &str, position: usize, total: usize) -> i32 {
520 let mut score = 0i32;
521 let lower = sentence.to_lowercase();
522
523 if position == 0 {
525 score += 10; }
527 if position == total - 1 {
528 score += 8; }
530
531 let important_keywords = [
533 ("result", 5),
534 ("output", 4),
535 ("error", 6),
536 ("success", 5),
537 ("fail", 6),
538 ("complete", 4),
539 ("return", 3),
540 ("created", 3),
541 ("found", 3),
542 ("important", 4),
543 ("note", 3),
544 ("warning", 5),
545 ("summary", 4),
546 ("conclusion", 5),
547 ("decision", 4),
548 ("because", 3),
549 ("therefore", 3),
550 ];
551
552 for (keyword, keyword_score) in important_keywords {
553 if lower.contains(keyword) {
554 score += keyword_score;
555 }
556 }
557
558 let len = sentence.len();
560 if len < 20 {
561 score -= 2;
562 } else if len > 200 {
563 score -= 1;
564 }
565
566 if sentence.contains('`') || sentence.contains("()") || sentence.contains("::") {
568 score += 2;
569 }
570
571 score
572 }
573
574 pub fn compact_extract_key_points(
585 &self,
586 _segments: &mut Vec<ContextSegment>,
587 _current_tokens: usize,
588 ) -> Result<usize, CompactionError> {
589 Err(CompactionError::SummarizationFailed(
590 "ExtractKeyPoints strategy is not yet implemented. \
591 This strategy requires LLM integration for semantic key point extraction. \
592 Consider using 'Summarize' for extractive summarization or \
593 'SlidingWindow' for recency-based compaction."
594 .to_string(),
595 ))
596 }
597
598 pub fn compact_importance_weighted(
610 &self,
611 _segments: &mut Vec<ContextSegment>,
612 _current_tokens: usize,
613 ) -> Result<usize, CompactionError> {
614 Err(CompactionError::SummarizationFailed(
615 "ImportanceWeighted strategy is not yet implemented. \
616 This strategy requires embedding model integration for semantic importance scoring. \
617 Consider using 'Truncate' for priority-based removal or \
618 'SlidingWindow' for recency-based compaction."
619 .to_string(),
620 ))
621 }
622
623 pub fn compact_hybrid(
634 &self,
635 _segments: &mut Vec<ContextSegment>,
636 _current_tokens: usize,
637 ) -> Result<usize, CompactionError> {
638 Err(CompactionError::SummarizationFailed(
639 "Hybrid strategy is not yet implemented. \
640 This strategy combines multiple compaction approaches for optimal results. \
641 Consider using individual strategies: 'Summarize', 'Truncate', or 'SlidingWindow'."
642 .to_string(),
643 ))
644 }
645}
646
647fn segment_type_display(segment_type: ContextSegmentType) -> &'static str {
649 match segment_type {
650 ContextSegmentType::System => "System",
651 ContextSegmentType::History => "Conversation History",
652 ContextSegmentType::WorkingMemory => "Working Memory",
653 ContextSegmentType::ToolResults => "Tool Results",
654 ContextSegmentType::RagContext => "Retrieved Context",
655 ContextSegmentType::UserInput => "User Input",
656 ContextSegmentType::AgentScratchpad => "Agent Notes",
657 ContextSegmentType::ChildSummary => "Child Execution",
658 ContextSegmentType::Guidance => "Guidance",
659 }
660}
661
662#[cfg(test)]
663mod tests {
664 use super::*;
665
666 #[test]
667 fn test_truncation_strategy() {
668 let strategy = CompactionStrategy::truncate(1000);
669 assert_eq!(strategy.strategy_type, CompactionStrategyType::Truncate);
670 assert!(strategy.is_protected(ContextSegmentType::System));
671 assert!(!strategy.is_protected(ContextSegmentType::History));
672 }
673
674 #[test]
675 fn test_compaction_result() {
676 let exec_id = ExecutionId::new();
677 let result = CompactionResult::success(
678 exec_id,
679 CompactionStrategyType::Truncate,
680 10000,
681 5000,
682 5,
683 100,
684 );
685
686 assert!(result.success);
687 assert_eq!(result.tokens_saved, 5000);
688 assert!((result.compression_ratio - 0.5).abs() < 0.01);
689 }
690
691 #[test]
692 fn test_summarize_strategy_creation() {
693 let strategy = CompactionStrategy::summarize(5000, 500);
694 assert_eq!(strategy.strategy_type, CompactionStrategyType::Summarize);
695 assert_eq!(strategy.target_tokens, 5000);
696 assert_eq!(strategy.summary_max_tokens, Some(500));
697 assert!(strategy.is_protected(ContextSegmentType::System));
698 assert!(strategy.is_protected(ContextSegmentType::UserInput));
699 assert!(!strategy.is_protected(ContextSegmentType::History));
700 }
701
702 #[test]
703 fn test_summarize_compactor_creation() {
704 let compactor = Compactor::summarize(5000, 500);
705 assert_eq!(
706 compactor.strategy().strategy_type,
707 CompactionStrategyType::Summarize
708 );
709 }
710
711 #[test]
712 fn test_compact_summarize_no_change_under_target() {
713 let compactor = Compactor::summarize(10000, 500);
714 let mut segments = vec![ContextSegment::history(
715 "Some history content here.",
716 100,
717 1,
718 )];
719
720 let result = compactor.compact_summarize(&mut segments, 100);
722 assert_eq!(result.unwrap(), 0);
723 assert_eq!(segments.len(), 1);
724 }
725
726 #[test]
727 fn test_compact_summarize_with_history() {
728 let compactor = Compactor::summarize(500, 100);
729 let mut segments = vec![
730 ContextSegment::system("You are a helpful assistant.", 10),
731 ContextSegment::history(
732 "The user asked about Rust programming. We discussed memory safety and ownership. \
733 The result was a successful explanation. The conclusion is that Rust is great.",
734 800,
735 1,
736 ),
737 ContextSegment::history(
738 "Then we talked about error handling. The important point is that Result types are used. \
739 This is a note about the discussion. The output showed various patterns.",
740 700,
741 2,
742 ),
743 ];
744
745 let current_tokens = 10 + 800 + 700;
746 let result = compactor.compact_summarize(&mut segments, current_tokens);
747
748 assert!(result.is_ok());
749 let removed = result.unwrap();
750 assert!(removed > 0, "Should have removed some tokens");
751
752 assert!(segments
754 .iter()
755 .any(|s| s.segment_type == ContextSegmentType::System));
756
757 let summarized = segments
759 .iter()
760 .find(|s| s.segment_type == ContextSegmentType::History);
761 assert!(summarized.is_some());
762 assert!(summarized.unwrap().content.contains("[Summarized"));
763 }
764
765 #[test]
766 fn test_compact_summarize_preserves_protected() {
767 let compactor = Compactor::summarize(100, 50);
768 let mut segments = vec![
769 ContextSegment::system("System prompt", 20),
770 ContextSegment::user_input("User question", 15, 1),
771 ContextSegment::guidance("Important guidance", 25, 2),
772 ContextSegment::history("Some history that can be compressed.", 500, 3),
773 ];
774
775 let current_tokens = 20 + 15 + 25 + 500;
776 let _ = compactor.compact_summarize(&mut segments, current_tokens);
777
778 assert!(segments
780 .iter()
781 .any(|s| s.segment_type == ContextSegmentType::System));
782 assert!(segments
783 .iter()
784 .any(|s| s.segment_type == ContextSegmentType::UserInput));
785 assert!(segments
786 .iter()
787 .any(|s| s.segment_type == ContextSegmentType::Guidance));
788 }
789
790 #[test]
791 fn test_extract_key_content_prioritizes_important_sentences() {
792 let compactor = Compactor::summarize(5000, 100);
793
794 let text = "This is the first sentence and sets context for the discussion. \
796 Some filler information here that is not particularly important. \
797 Another sentence with no real significance to the outcome. \
798 The result of the operation was successful and completed without errors. \
799 More random content follows that could be removed. \
800 Yet another sentence that adds little value to understanding. \
801 Some additional padding content here. \
802 In conclusion, this is the summary of our findings.";
803
804 let summary = compactor.extract_key_content(text, 20);
806
807 assert!(!summary.is_empty());
809 assert!(
811 summary.len() < text.len(),
812 "Summary ({} chars) should be shorter than original ({} chars)",
813 summary.len(),
814 text.len()
815 );
816 }
817
818 #[test]
819 fn test_extract_key_content_handles_empty() {
820 let compactor = Compactor::summarize(5000, 100);
821 let summary = compactor.extract_key_content("", 50);
822 assert!(summary.is_empty() || summary == ".");
823 }
824
825 #[test]
826 fn test_score_sentence_keywords() {
827 let compactor = Compactor::summarize(5000, 100);
828
829 let error_sentence = "There was an error in the process";
830 let normal_sentence = "The weather is nice today";
831
832 let error_score = compactor.score_sentence(error_sentence, 1, 5);
833 let normal_score = compactor.score_sentence(normal_sentence, 1, 5);
834
835 assert!(
836 error_score > normal_score,
837 "Error sentence should score higher"
838 );
839 }
840
841 #[test]
842 fn test_score_sentence_position() {
843 let compactor = Compactor::summarize(5000, 100);
844 let sentence = "This is a test sentence";
845
846 let first_score = compactor.score_sentence(sentence, 0, 5);
847 let middle_score = compactor.score_sentence(sentence, 2, 5);
848 let last_score = compactor.score_sentence(sentence, 4, 5);
849
850 assert!(
851 first_score > middle_score,
852 "First sentence should score higher"
853 );
854 assert!(
855 last_score > middle_score,
856 "Last sentence should score higher"
857 );
858 }
859
860 #[test]
861 fn test_extract_key_points_not_implemented() {
862 let strategy = CompactionStrategy {
863 strategy_type: CompactionStrategyType::ExtractKeyPoints,
864 target_tokens: 5000,
865 min_preserve_percent: 20,
866 segments_to_compact: None,
867 protected_segments: None,
868 summary_max_tokens: None,
869 window_size: None,
870 min_importance_score: None,
871 };
872 let compactor = Compactor::new(strategy);
873 let mut segments = vec![];
874
875 let result = compactor.compact_extract_key_points(&mut segments, 1000);
876 assert!(result.is_err());
877
878 let err = result.unwrap_err();
879 match err {
880 CompactionError::SummarizationFailed(msg) => {
881 assert!(msg.contains("ExtractKeyPoints"));
882 assert!(msg.contains("not yet implemented"));
883 }
884 _ => panic!("Expected SummarizationFailed error"),
885 }
886 }
887
888 #[test]
889 fn test_importance_weighted_not_implemented() {
890 let strategy = CompactionStrategy {
891 strategy_type: CompactionStrategyType::ImportanceWeighted,
892 target_tokens: 5000,
893 min_preserve_percent: 20,
894 segments_to_compact: None,
895 protected_segments: None,
896 summary_max_tokens: None,
897 window_size: None,
898 min_importance_score: Some(0.5),
899 };
900 let compactor = Compactor::new(strategy);
901 let mut segments = vec![];
902
903 let result = compactor.compact_importance_weighted(&mut segments, 1000);
904 assert!(result.is_err());
905
906 let err = result.unwrap_err();
907 match err {
908 CompactionError::SummarizationFailed(msg) => {
909 assert!(msg.contains("ImportanceWeighted"));
910 assert!(msg.contains("not yet implemented"));
911 assert!(msg.contains("embedding model"));
912 }
913 _ => panic!("Expected SummarizationFailed error"),
914 }
915 }
916
917 #[test]
918 fn test_hybrid_not_implemented() {
919 let strategy = CompactionStrategy {
920 strategy_type: CompactionStrategyType::Hybrid,
921 target_tokens: 5000,
922 min_preserve_percent: 20,
923 segments_to_compact: None,
924 protected_segments: None,
925 summary_max_tokens: None,
926 window_size: None,
927 min_importance_score: None,
928 };
929 let compactor = Compactor::new(strategy);
930 let mut segments = vec![];
931
932 let result = compactor.compact_hybrid(&mut segments, 1000);
933 assert!(result.is_err());
934
935 let err = result.unwrap_err();
936 match err {
937 CompactionError::SummarizationFailed(msg) => {
938 assert!(msg.contains("Hybrid"));
939 assert!(msg.contains("not yet implemented"));
940 }
941 _ => panic!("Expected SummarizationFailed error"),
942 }
943 }
944
945 #[test]
946 fn test_segment_type_display() {
947 assert_eq!(
948 segment_type_display(ContextSegmentType::History),
949 "Conversation History"
950 );
951 assert_eq!(
952 segment_type_display(ContextSegmentType::ToolResults),
953 "Tool Results"
954 );
955 assert_eq!(segment_type_display(ContextSegmentType::System), "System");
956 }
957}