@@ -417,9 +417,9 @@ pub enum EventMsg {
417417 /// Agent has completed all actions
418418 TaskComplete ( TaskCompleteEvent ) ,
419419
420- /// Token count event, sent periodically to report the number of tokens
421- /// used in the current session .
422- TokenCount ( TokenUsage ) ,
420+ /// Usage update for the current session, including totals and last turn.
421+ /// Optional means unknown — UIs should not display when `None` .
422+ TokenCount ( TokenCountEvent ) ,
423423
424424 /// Agent text output message
425425 AgentMessage ( AgentMessageEvent ) ,
@@ -521,12 +521,54 @@ pub struct TaskStartedEvent {
521521#[ derive( Debug , Clone , Deserialize , Serialize , Default ) ]
522522pub struct TokenUsage {
523523 pub input_tokens : u64 ,
524- pub cached_input_tokens : Option < u64 > ,
524+ pub cached_input_tokens : u64 ,
525525 pub output_tokens : u64 ,
526- pub reasoning_output_tokens : Option < u64 > ,
526+ pub reasoning_output_tokens : u64 ,
527527 pub total_tokens : u64 ,
528528}
529529
530+ #[ derive( Debug , Clone , Deserialize , Serialize ) ]
531+ pub struct TokenUsageInfo {
532+ pub total_token_usage : TokenUsage ,
533+ pub last_token_usage : TokenUsage ,
534+ pub model_context_window : Option < u64 > ,
535+ }
536+
537+ impl TokenUsageInfo {
538+ pub fn new_or_append (
539+ info : & Option < TokenUsageInfo > ,
540+ last : & Option < TokenUsage > ,
541+ model_context_window : Option < u64 > ,
542+ ) -> Option < Self > {
543+ if info. is_none ( ) && last. is_none ( ) {
544+ return None ;
545+ }
546+
547+ let mut info = match info {
548+ Some ( info) => info. clone ( ) ,
549+ None => Self {
550+ total_token_usage : TokenUsage :: default ( ) ,
551+ last_token_usage : TokenUsage :: default ( ) ,
552+ model_context_window,
553+ } ,
554+ } ;
555+ if let Some ( last) = last {
556+ info. append_last_usage ( last) ;
557+ }
558+ Some ( info)
559+ }
560+
561+ pub fn append_last_usage ( & mut self , last : & TokenUsage ) {
562+ self . total_token_usage . add_assign ( last) ;
563+ self . last_token_usage = last. clone ( ) ;
564+ }
565+ }
566+
567+ #[ derive( Debug , Clone , Deserialize , Serialize ) ]
568+ pub struct TokenCountEvent {
569+ pub info : Option < TokenUsageInfo > ,
570+ }
571+
530572// Includes prompts, tools and space to call compact.
531573const BASELINE_TOKENS : u64 = 12000 ;
532574
@@ -536,7 +578,7 @@ impl TokenUsage {
536578 }
537579
538580 pub fn cached_input ( & self ) -> u64 {
539- self . cached_input_tokens . unwrap_or ( 0 )
581+ self . cached_input_tokens
540582 }
541583
542584 pub fn non_cached_input ( & self ) -> u64 {
@@ -554,7 +596,7 @@ impl TokenUsage {
554596 /// This will be off for the current turn and pending function calls.
555597 pub fn tokens_in_context_window ( & self ) -> u64 {
556598 self . total_tokens
557- . saturating_sub ( self . reasoning_output_tokens . unwrap_or ( 0 ) )
599+ . saturating_sub ( self . reasoning_output_tokens )
558600 }
559601
560602 /// Estimate the remaining user-controllable percentage of the model's context window.
@@ -579,6 +621,15 @@ impl TokenUsage {
579621 let remaining = effective_window. saturating_sub ( used) ;
580622 ( ( remaining as f32 / effective_window as f32 ) * 100.0 ) . clamp ( 0.0 , 100.0 ) as u8
581623 }
624+
625+ /// In-place element-wise sum of token counts.
626+ pub fn add_assign ( & mut self , other : & TokenUsage ) {
627+ self . input_tokens += other. input_tokens ;
628+ self . cached_input_tokens += other. cached_input_tokens ;
629+ self . output_tokens += other. output_tokens ;
630+ self . reasoning_output_tokens += other. reasoning_output_tokens ;
631+ self . total_tokens += other. total_tokens ;
632+ }
582633}
583634
584635#[ derive( Debug , Clone , Deserialize , Serialize ) ]
@@ -606,10 +657,11 @@ impl fmt::Display for FinalOutput {
606657 String :: new( )
607658 } ,
608659 token_usage. output_tokens,
609- token_usage
610- . reasoning_output_tokens
611- . map( |r| format!( " (reasoning {r})" ) )
612- . unwrap_or_default( )
660+ if token_usage. reasoning_output_tokens > 0 {
661+ format!( " (reasoning {})" , token_usage. reasoning_output_tokens)
662+ } else {
663+ String :: new( )
664+ }
613665 )
614666 }
615667}
0 commit comments