bamboo_compression/
types.rs1pub use bamboo_domain::budget_types::{
9 BudgetStrategy, TokenBudget, TokenBudgetUsage, TokenUsageBreakdown,
10};
11
12use thiserror::Error;
13
14#[derive(Debug, Clone)]
16pub struct PreparedContext {
17 pub messages: Vec<bamboo_domain::Message>,
19 pub token_usage: TokenUsageBreakdown,
21 pub truncation_occurred: bool,
23 pub segments_removed: usize,
25 pub compressed_message_ids: Vec<String>,
27 pub prompt_cached_tool_outputs: usize,
29 pub prompt_cached_tool_tokens_saved: u32,
31}
32
33#[derive(Debug, Error)]
35pub enum BudgetError {
36 #[error("System prompt ({system_tokens} tokens) exceeds available budget ({available_tokens} tokens)")]
37 SystemPromptTooLarge {
38 system_tokens: u32,
39 available_tokens: u32,
40 },
41
42 #[error("Single message ({message_tokens} tokens) exceeds available budget ({available_tokens} tokens). Consider splitting the message or attaching as a file.")]
43 SingleMessageTooLarge {
44 message_tokens: u32,
45 available_tokens: u32,
46 },
47
48 #[error("Failed to count tokens: {0}")]
49 TokenCountError(String),
50
51 #[error("Failed to segment messages: {0}")]
52 SegmentationError(String),
53}
54
55#[cfg(test)]
56mod tests {
57 use super::{BudgetStrategy, TokenBudget};
58
59 #[test]
60 fn compression_trigger_defaults_to_eighty_five_percent() {
61 let budget = TokenBudget::for_model(128_000);
62 assert_eq!(budget.compression_trigger_percent, 85);
63 }
64
65 #[test]
66 fn compression_target_defaults_to_forty_percent() {
67 let budget = TokenBudget::for_model(128_000);
68 assert_eq!(budget.compression_target_percent, 40);
69 }
70
71 #[test]
72 fn prompt_cache_defaults_match_current_compaction_policy() {
73 let budget = TokenBudget::for_model(128_000);
74 assert_eq!(budget.prompt_cache_min_tool_output_chars, 1_200);
75 assert_eq!(budget.prompt_cache_head_chars, 280);
76 assert_eq!(budget.prompt_cache_tail_chars, 180);
77 assert_eq!(budget.prompt_cache_recent_user_turns, 2);
78 assert_eq!(budget.prompt_cache_recent_tool_chains, 2);
79 }
80
81 #[test]
82 fn compression_trigger_context_tokens_respects_percent() {
83 let mut budget =
84 TokenBudget::with_safety_margin(1000, 200, BudgetStrategy::Window { size: 20 }, 100);
85 budget.working_reserve_tokens = 0; budget.compression_trigger_percent = 50;
87 assert_eq!(budget.compression_trigger_context_tokens(), 500);
88 }
89
90 #[test]
91 fn compression_target_context_tokens_respects_percent() {
92 let mut budget =
93 TokenBudget::with_safety_margin(1000, 200, BudgetStrategy::Window { size: 20 }, 100);
94 budget.compression_target_percent = 50;
95 assert_eq!(budget.compression_target_context_tokens(), 500);
96 }
97
98 #[test]
99 fn compression_target_percent_is_clamped_to_supported_range() {
100 let mut budget =
101 TokenBudget::with_safety_margin(1000, 200, BudgetStrategy::Window { size: 20 }, 100);
102 budget.compression_target_percent = 10;
103 assert_eq!(budget.compression_target_context_tokens(), 200);
104 }
105
106 #[test]
107 fn compression_target_always_stays_below_trigger_limit() {
108 let mut budget =
109 TokenBudget::with_safety_margin(1000, 200, BudgetStrategy::Window { size: 20 }, 100);
110 budget.working_reserve_tokens = 0; budget.compression_trigger_percent = 30;
112 budget.compression_target_percent = 50;
113 assert_eq!(budget.compression_target_context_tokens(), 299);
114 }
115
116 #[test]
117 fn trigger_percent_zero_means_disabled() {
118 let mut budget =
119 TokenBudget::with_safety_margin(1000, 200, BudgetStrategy::Window { size: 20 }, 100);
120 budget.working_reserve_tokens = 0; budget.compression_trigger_percent = 0;
122 assert_eq!(
123 budget.compression_trigger_context_tokens(),
124 budget.max_context_tokens
125 );
126 }
127
128 #[test]
129 fn fixed_reserve_trigger_subtracts_reserve_from_context() {
130 let budget =
131 TokenBudget::with_safety_margin(200_000, 4_096, BudgetStrategy::default(), 1000);
132 assert_eq!(budget.compression_trigger_context_tokens(), 150_000);
134 }
135
136 #[test]
137 fn fixed_reserve_trigger_for_small_context() {
138 let budget =
139 TokenBudget::with_safety_margin(100_000, 4_096, BudgetStrategy::default(), 1000);
140 assert_eq!(budget.compression_trigger_context_tokens(), 50_000);
142 }
143
144 #[test]
145 fn fixed_reserve_fallback_for_tiny_context() {
146 let budget =
147 TokenBudget::with_safety_margin(60_000, 4_096, BudgetStrategy::default(), 1000);
148 assert_eq!(budget.compression_trigger_context_tokens(), 45_000);
150 }
151
152 #[test]
153 fn working_reserve_zero_uses_legacy_percentage() {
154 let mut budget =
155 TokenBudget::with_safety_margin(200_000, 4_096, BudgetStrategy::default(), 1000);
156 budget.working_reserve_tokens = 0;
157 budget.compression_trigger_percent = 85;
158 assert_eq!(budget.compression_trigger_context_tokens(), 170_000);
159 }
160}