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}
30
31#[derive(Debug, Error)]
33pub enum BudgetError {
34 #[error("System prompt ({system_tokens} tokens) exceeds available budget ({available_tokens} tokens)")]
35 SystemPromptTooLarge {
36 system_tokens: u32,
37 available_tokens: u32,
38 },
39
40 #[error("Single message ({message_tokens} tokens) exceeds available budget ({available_tokens} tokens). Consider splitting the message or attaching as a file.")]
41 SingleMessageTooLarge {
42 message_tokens: u32,
43 available_tokens: u32,
44 },
45
46 #[error("Failed to count tokens: {0}")]
47 TokenCountError(String),
48
49 #[error("Failed to segment messages: {0}")]
50 SegmentationError(String),
51}
52
53#[cfg(test)]
54mod tests {
55 use super::{BudgetStrategy, TokenBudget};
56
57 #[test]
58 fn compression_trigger_defaults_to_eighty_five_percent() {
59 let budget = TokenBudget::for_model(128_000);
60 assert_eq!(budget.compression_trigger_percent, 85);
61 }
62
63 #[test]
64 fn compression_target_defaults_to_forty_percent() {
65 let budget = TokenBudget::for_model(128_000);
66 assert_eq!(budget.compression_target_percent, 40);
67 }
68
69 #[test]
70 fn prompt_cache_defaults_match_current_compaction_policy() {
71 let budget = TokenBudget::for_model(128_000);
72 assert_eq!(budget.prompt_cache_min_tool_output_chars, 1_200);
73 assert_eq!(budget.prompt_cache_head_chars, 280);
74 assert_eq!(budget.prompt_cache_tail_chars, 180);
75 assert_eq!(budget.prompt_cache_recent_user_turns, 2);
76 assert_eq!(budget.prompt_cache_recent_tool_chains, 2);
77 }
78
79 #[test]
80 fn compression_trigger_context_tokens_respects_percent() {
81 let mut budget =
82 TokenBudget::with_safety_margin(1000, 200, BudgetStrategy::Window { size: 20 }, 100);
83 budget.working_reserve_tokens = 0; budget.compression_trigger_percent = 50;
85 assert_eq!(budget.compression_trigger_context_tokens(), 500);
86 }
87
88 #[test]
89 fn compression_target_context_tokens_respects_percent() {
90 let mut budget =
91 TokenBudget::with_safety_margin(1000, 200, BudgetStrategy::Window { size: 20 }, 100);
92 budget.compression_target_percent = 50;
93 assert_eq!(budget.compression_target_context_tokens(), 500);
94 }
95
96 #[test]
97 fn compression_target_percent_is_clamped_to_supported_range() {
98 let mut budget =
99 TokenBudget::with_safety_margin(1000, 200, BudgetStrategy::Window { size: 20 }, 100);
100 budget.compression_target_percent = 10;
101 assert_eq!(budget.compression_target_context_tokens(), 200);
102 }
103
104 #[test]
105 fn compression_target_always_stays_below_trigger_limit() {
106 let mut budget =
107 TokenBudget::with_safety_margin(1000, 200, BudgetStrategy::Window { size: 20 }, 100);
108 budget.working_reserve_tokens = 0; budget.compression_trigger_percent = 30;
110 budget.compression_target_percent = 50;
111 assert_eq!(budget.compression_target_context_tokens(), 299);
112 }
113
114 #[test]
115 fn trigger_percent_zero_means_disabled() {
116 let mut budget =
117 TokenBudget::with_safety_margin(1000, 200, BudgetStrategy::Window { size: 20 }, 100);
118 budget.working_reserve_tokens = 0; budget.compression_trigger_percent = 0;
120 assert_eq!(
121 budget.compression_trigger_context_tokens(),
122 budget.max_context_tokens
123 );
124 }
125
126 #[test]
127 fn fixed_reserve_trigger_subtracts_reserve_from_context() {
128 let budget =
129 TokenBudget::with_safety_margin(200_000, 4_096, BudgetStrategy::default(), 1000);
130 assert_eq!(budget.compression_trigger_context_tokens(), 150_000);
132 }
133
134 #[test]
135 fn fixed_reserve_trigger_for_small_context() {
136 let budget =
137 TokenBudget::with_safety_margin(100_000, 4_096, BudgetStrategy::default(), 1000);
138 assert_eq!(budget.compression_trigger_context_tokens(), 50_000);
140 }
141
142 #[test]
143 fn fixed_reserve_fallback_for_tiny_context() {
144 let budget =
145 TokenBudget::with_safety_margin(60_000, 4_096, BudgetStrategy::default(), 1000);
146 assert_eq!(budget.compression_trigger_context_tokens(), 45_000);
148 }
149
150 #[test]
151 fn working_reserve_zero_uses_legacy_percentage() {
152 let mut budget =
153 TokenBudget::with_safety_margin(200_000, 4_096, BudgetStrategy::default(), 1000);
154 budget.working_reserve_tokens = 0;
155 budget.compression_trigger_percent = 85;
156 assert_eq!(budget.compression_trigger_context_tokens(), 170_000);
157 }
158}