@@ -5,7 +5,7 @@ use async_trait::async_trait;
55use serde:: { Deserialize , Serialize } ;
66
77use crate :: { ItemId , ResponseItem , SessionId , SummaryModelSelection , TurnId } ;
8- use devo_protocol:: { ContentBlock , Message , Role } ;
8+ use devo_protocol:: { ContentBlock , Message , Model , Role } ;
99
1010// ---------------------------------------------------------------------------
1111// Contextual user fragment traits and registration
@@ -120,6 +120,10 @@ pub struct TokenBudget {
120120 pub max_output_tokens : usize ,
121121 /// The threshold at which automatic compaction should trigger.
122122 pub compact_threshold : f64 ,
123+ /// Absolute token limit for automatic compaction, when model metadata
124+ /// provides a more precise context boundary than the default ratio.
125+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
126+ pub auto_compact_token_limit : Option < usize > ,
123127}
124128
125129impl TokenBudget {
@@ -129,6 +133,22 @@ impl TokenBudget {
129133 context_window,
130134 max_output_tokens,
131135 compact_threshold : 0.9 ,
136+ auto_compact_token_limit : None ,
137+ }
138+ }
139+
140+ /// Creates a token budget aligned with the active model's effective context window.
141+ pub fn for_model ( model : & Model ) -> Self {
142+ let default_budget = Self :: default ( ) ;
143+ let context_window = model. effective_context_window ( ) as usize ;
144+ let max_output_tokens = model
145+ . max_tokens
146+ . map_or ( default_budget. max_output_tokens , |value| value as usize ) ;
147+ Self {
148+ context_window,
149+ max_output_tokens,
150+ compact_threshold : 1.0 ,
151+ auto_compact_token_limit : Some ( context_window) ,
132152 }
133153 }
134154
@@ -139,6 +159,9 @@ impl TokenBudget {
139159
140160 /// Returns whether compaction should run for the supplied prompt token count.
141161 pub fn should_compact ( & self , current_tokens : usize ) -> bool {
162+ if let Some ( limit) = self . auto_compact_token_limit {
163+ return current_tokens > limit;
164+ }
142165 current_tokens as f64 > self . input_budget ( ) as f64 * self . compact_threshold
143166 }
144167}
@@ -383,6 +406,8 @@ mod tests {
383406 ByteTokenEstimator , ContextualUserFragment , PromptAssemblyInput , SnapshotPersistFailure ,
384407 TokenBudget , TokenEstimator ,
385408 } ;
409+ use devo_protocol:: Model ;
410+ use pretty_assertions:: assert_eq;
386411 use std:: hint:: black_box;
387412 use std:: time:: Instant ;
388413
@@ -403,11 +428,52 @@ mod tests {
403428 #[ test]
404429 fn token_budget_default_values ( ) {
405430 let budget = TokenBudget :: default ( ) ;
406- assert_eq ! ( budget. context_window, 200_000 ) ;
407- assert_eq ! ( budget. max_output_tokens, 8192 ) ;
431+ assert_eq ! (
432+ budget,
433+ TokenBudget {
434+ context_window: 200_000 ,
435+ max_output_tokens: 8192 ,
436+ compact_threshold: 0.9 ,
437+ auto_compact_token_limit: None ,
438+ }
439+ ) ;
408440 assert ! ( ( budget. compact_threshold - 0.9 ) . abs( ) < f64 :: EPSILON ) ;
409441 }
410442
443+ #[ test]
444+ fn token_budget_for_model_uses_effective_context_as_auto_compact_limit ( ) {
445+ let model = Model {
446+ context_window : 1_000_000 ,
447+ effective_context_window_percent : Some ( 95 ) ,
448+ max_tokens : Some ( 384_000 ) ,
449+ ..Model :: default ( )
450+ } ;
451+
452+ assert_eq ! (
453+ TokenBudget :: for_model( & model) ,
454+ TokenBudget {
455+ context_window: 950_000 ,
456+ max_output_tokens: 384_000 ,
457+ compact_threshold: 1.0 ,
458+ auto_compact_token_limit: Some ( 950_000 ) ,
459+ }
460+ ) ;
461+ }
462+
463+ #[ test]
464+ fn model_token_budget_does_not_compact_before_effective_context_limit ( ) {
465+ let model = Model {
466+ context_window : 1_000_000 ,
467+ effective_context_window_percent : Some ( 95 ) ,
468+ max_tokens : Some ( 384_000 ) ,
469+ ..Model :: default ( )
470+ } ;
471+ let budget = TokenBudget :: for_model ( & model) ;
472+
473+ assert_eq ! ( budget. should_compact( 950_000 ) , false ) ;
474+ assert_eq ! ( budget. should_compact( 950_001 ) , true ) ;
475+ }
476+
411477 #[ test]
412478 fn token_budget_input_budget_saturates ( ) {
413479 let budget = TokenBudget :: new ( 100 , 200 ) ;
0 commit comments