@@ -5,6 +5,8 @@ use fake::Dummy;
55use schemars:: JsonSchema ;
66use serde:: { Deserialize , Serialize } ;
77
8+ use crate :: Percentage ;
9+
810/// Frequency at which forge checks for updates
911#[ derive( Default , Debug , Clone , Serialize , Deserialize , JsonSchema , PartialEq , fake:: Dummy ) ]
1012#[ serde( rename_all = "snake_case" ) ]
@@ -37,21 +39,6 @@ pub struct Update {
3739 pub auto_update : Option < bool > ,
3840}
3941
40- fn deserialize_percentage < ' de , D > ( deserializer : D ) -> Result < f64 , D :: Error >
41- where
42- D : serde:: Deserializer < ' de > ,
43- {
44- use serde:: de:: Error ;
45-
46- let value = f64:: deserialize ( deserializer) ?;
47- if !( 0.0 ..=1.0 ) . contains ( & value) {
48- return Err ( Error :: custom ( format ! (
49- "percentage must be between 0.0 and 1.0, got {value}"
50- ) ) ) ;
51- }
52- Ok ( value)
53- }
54-
5542/// Configuration for automatic context compaction for all agents
5643#[ derive( Debug , Clone , Serialize , Deserialize , Setters , JsonSchema , PartialEq ) ]
5744#[ setters( strip_option, into) ]
@@ -68,8 +55,8 @@ pub struct Compact {
6855 /// compaction and 1.0 allows summarizing all messages. Works alongside
6956 /// retention_window - the more conservative limit (fewer messages to
7057 /// compact) takes precedence.
71- #[ serde( default , deserialize_with = "deserialize_percentage" ) ]
72- pub eviction_window : f64 ,
58+ #[ serde( default ) ]
59+ pub eviction_window : Percentage ,
7360
7461 /// Maximum number of tokens to keep after compaction
7562 #[ serde( skip_serializing_if = "Option::is_none" ) ]
@@ -113,7 +100,7 @@ impl Compact {
113100 turn_threshold : None ,
114101 message_threshold : None ,
115102 model : None ,
116- eviction_window : 0.2 ,
103+ eviction_window : Percentage :: new ( 0.2 ) . unwrap ( ) ,
117104 retention_window : 0 ,
118105 on_turn_end : None ,
119106 }
@@ -125,7 +112,7 @@ impl Dummy<fake::Faker> for Compact {
125112 use fake:: Fake ;
126113 Self {
127114 retention_window : fake:: Faker . fake_with_rng ( rng) ,
128- eviction_window : ( 0.0f64 ..=1.0f64 ) . fake_with_rng ( rng) ,
115+ eviction_window : Percentage :: from ( ( 0.0f64 ..=1.0f64 ) . fake_with_rng :: < f64 , R > ( rng) ) ,
129116 max_tokens : fake:: Faker . fake_with_rng ( rng) ,
130117 token_threshold : fake:: Faker . fake_with_rng ( rng) ,
131118 turn_threshold : fake:: Faker . fake_with_rng ( rng) ,
@@ -135,3 +122,62 @@ impl Dummy<fake::Faker> for Compact {
135122 }
136123 }
137124}
125+
126+ #[ cfg( test) ]
127+ mod tests {
128+ use pretty_assertions:: assert_eq;
129+
130+ use super :: * ;
131+ use crate :: reader:: ConfigReader ;
132+
133+ #[ test]
134+ fn test_f64_eviction_window_round_trip ( ) {
135+ let fixture = Compact {
136+ eviction_window : Percentage :: new ( 0.2 ) . unwrap ( ) ,
137+ ..Compact :: new ( )
138+ } ;
139+
140+ let toml = toml_edit:: ser:: to_string_pretty ( & fixture) . unwrap ( ) ;
141+
142+ assert ! (
143+ toml. contains( "eviction_window = 0.2\n " ) ,
144+ "expected `eviction_window = 0.2` in TOML output, got:\n {toml}"
145+ ) ;
146+ }
147+
148+ #[ test]
149+ fn test_f64_eviction_window_deserialize_round_trip ( ) {
150+ let fixture = Compact {
151+ eviction_window : Percentage :: new ( 0.2 ) . unwrap ( ) ,
152+ ..Compact :: new ( )
153+ } ;
154+
155+ let toml = toml_edit:: ser:: to_string_pretty ( & fixture) . unwrap ( ) ;
156+
157+ let actual = ConfigReader :: default ( )
158+ . read_defaults ( )
159+ . read_toml ( & toml)
160+ . build ( )
161+ . unwrap ( )
162+ . compact
163+ . unwrap_or_default ( ) ;
164+
165+ assert_eq ! ( actual. eviction_window, fixture. eviction_window) ;
166+ }
167+
168+ #[ test]
169+ fn test_eviction_window_rejects_out_of_range ( ) {
170+ let toml = "[compact]\n eviction_window = 1.5\n " ;
171+
172+ let result = ConfigReader :: default ( )
173+ . read_defaults ( )
174+ . read_toml ( toml)
175+ . build ( ) ;
176+
177+ assert ! (
178+ result. is_err( ) ,
179+ "expected error for eviction_window = 1.5, got: {:?}" ,
180+ result. ok( )
181+ ) ;
182+ }
183+ }
0 commit comments