@@ -54,12 +54,14 @@ struct Config {
5454struct Snapshot {
5555 provider : String ,
5656 machine_id : String ,
57+ session_hash : String ,
5758 observed_at : String ,
5859 input_tokens : u64 ,
5960 cached_input_tokens : u64 ,
6061 output_tokens : u64 ,
6162 sessions : u32 ,
6263 five_hour_percent : Option < f64 > ,
64+ limit_source : Option < String > ,
6365 collector_version : String ,
6466 ide : String ,
6567}
@@ -190,14 +192,24 @@ fn sync() -> Result<()> {
190192 println ! ( "No Claude Code or Codex usage events found." ) ;
191193 return Ok ( ( ) ) ;
192194 }
193- let start = snapshots. len ( ) . saturating_sub ( 48 ) ;
195+ let recent_cutoff = Utc :: now ( ) - chrono:: Duration :: hours ( 48 ) ;
196+ let recent: Vec < & Snapshot > = snapshots
197+ . iter ( )
198+ . filter ( |snapshot| {
199+ DateTime :: parse_from_rfc3339 ( & snapshot. observed_at )
200+ . map ( |time| time >= recent_cutoff)
201+ . unwrap_or ( false )
202+ } )
203+ . rev ( )
204+ . take ( 500 )
205+ . collect ( ) ;
194206 let response = client ( )
195207 . post ( format ! (
196208 "{}/api/v1/ai-agent-usage/snapshots" ,
197209 config. base_url
198210 ) )
199211 . bearer_auth ( config. token )
200- . json ( & json ! ( { "snapshots" : & snapshots [ start.. ] } ) )
212+ . json ( & json ! ( { "snapshots" : recent . into_iter ( ) . rev ( ) . collect :: < Vec <_>> ( ) } ) )
201213 . send ( ) ?;
202214 if !response. status ( ) . is_success ( ) {
203215 bail ! ( "sync failed ({})" , response. status( ) ) ;
@@ -220,13 +232,13 @@ fn collect() -> Result<Vec<Snapshot>> {
220232 ] ;
221233 let machine_id = machine_id ( & home) ;
222234 let cutoff = SystemTime :: now ( ) - Duration :: from_secs ( 7 * 86_400 ) ;
223- let mut buckets: BTreeMap < ( String , String ) , Snapshot > = BTreeMap :: new ( ) ;
235+ let mut buckets: BTreeMap < ( String , String , String ) , Snapshot > = BTreeMap :: new ( ) ;
224236
225237 for ( provider, root) in sources {
226238 if !root. is_dir ( ) {
227239 continue ;
228240 }
229- for entry in WalkDir :: new ( root)
241+ for entry in WalkDir :: new ( & root)
230242 . into_iter ( )
231243 . filter_map ( |entry| entry. ok ( ) )
232244 {
@@ -240,6 +252,7 @@ fn collect() -> Result<Vec<Snapshot>> {
240252 continue ;
241253 }
242254 let fallback = DateTime :: < Utc > :: from ( fs:: metadata ( path) ?. modified ( ) ?) ;
255+ let session_hash = session_hash ( & machine_id, provider, path, & root) ;
243256 for line in io:: BufReader :: new ( fs:: File :: open ( path) ?)
244257 . lines ( )
245258 . map_while ( |line| line. ok ( ) )
@@ -262,16 +275,18 @@ fn collect() -> Result<Vec<Snapshot>> {
262275 . with_nanosecond ( 0 )
263276 . unwrap ( )
264277 . to_rfc3339 ( ) ;
265- let key = ( provider. to_owned ( ) , hour. clone ( ) ) ;
278+ let key = ( provider. to_owned ( ) , session_hash . clone ( ) , hour. clone ( ) ) ;
266279 let bucket = buckets. entry ( key) . or_insert_with ( || Snapshot {
267280 provider : provider. to_owned ( ) ,
268281 machine_id : machine_id. clone ( ) ,
282+ session_hash : session_hash. clone ( ) ,
269283 observed_at : hour,
270284 input_tokens : 0 ,
271285 cached_input_tokens : 0 ,
272286 output_tokens : 0 ,
273287 sessions : 0 ,
274288 five_hour_percent : None ,
289+ limit_source : None ,
275290 collector_version : VERSION . to_owned ( ) ,
276291 ide : std:: env:: var ( "WORKO_IDE" ) . unwrap_or_else ( |_| "terminal" . to_owned ( ) ) ,
277292 } ) ;
@@ -282,13 +297,23 @@ fn collect() -> Result<Vec<Snapshot>> {
282297 bucket. sessions += 1 ;
283298 if let Some ( percent) = find_percent ( & event) {
284299 bucket. five_hour_percent = Some ( percent. clamp ( 0.0 , 100.0 ) ) ;
300+ bucket. limit_source = Some ( "provider_log" . to_owned ( ) ) ;
285301 }
286302 }
287303 }
288304 }
289305 Ok ( buckets. into_values ( ) . collect ( ) )
290306}
291307
308+ fn session_hash ( machine_id : & str , provider : & str , path : & Path , root : & Path ) -> String {
309+ let relative = path. strip_prefix ( root) . unwrap_or ( path) ;
310+ let mut hash = Sha256 :: new ( ) ;
311+ hash. update ( machine_id. as_bytes ( ) ) ;
312+ hash. update ( provider. as_bytes ( ) ) ;
313+ hash. update ( relative. to_string_lossy ( ) . as_bytes ( ) ) ;
314+ hex:: encode ( hash. finalize ( ) )
315+ }
316+
292317fn find_usage ( value : & Value ) -> Option < & Value > {
293318 if let Value :: Object ( map) = value {
294319 for key in [ "usage" , "token_usage" , "last_token_usage" ] {
0 commit comments