@@ -387,7 +387,7 @@ interface WorkspaceCustomizationSummary {
387387
388388class CopilotTokenTracker implements vscode . Disposable {
389389 // Cache version - increment this when making changes that require cache invalidation
390- private static readonly CACHE_VERSION = 28 ; // Fix actualTokens/thinkingTokens lost by updateCacheWithSessionDetails
390+ private static readonly CACHE_VERSION = 29 ; // Use actual token counts from CLI session.shutdown events
391391 // Maximum length for displaying workspace IDs in diagnostics/customization matrix
392392 private static readonly WORKSPACE_ID_DISPLAY_LENGTH = 8 ;
393393
@@ -726,6 +726,7 @@ class CopilotTokenTracker implements vscode.Disposable {
726726 for ( const r of results ) { uniq [ path . normalize ( r . path ) ] = r ; }
727727 return Object . values ( uniq ) ;
728728 }
729+ private _disposed = false ;
729730 private updateInterval : NodeJS . Timeout | undefined ;
730731 private initialDelayTimeout : NodeJS . Timeout | undefined ;
731732 private detailsPanel : vscode . WebviewPanel | undefined ;
@@ -1184,16 +1185,19 @@ class CopilotTokenTracker implements vscode.Disposable {
11841185
11851186 // Logging methods
11861187 public log ( message : string ) : void {
1188+ if ( this . _disposed ) { return ; }
11871189 const timestamp = new Date ( ) . toLocaleTimeString ( ) ;
11881190 this . outputChannel . appendLine ( `[${ timestamp } ] ${ message } ` ) ;
11891191 }
11901192
11911193 private warn ( message : string ) : void {
1194+ if ( this . _disposed ) { return ; }
11921195 const timestamp = new Date ( ) . toLocaleTimeString ( ) ;
11931196 this . outputChannel . appendLine ( `[${ timestamp } ] WARNING: ${ message } ` ) ;
11941197 }
11951198
11961199 private error ( message : string , error ?: any ) : void {
1200+ if ( this . _disposed ) { return ; }
11971201 const timestamp = new Date ( ) . toLocaleTimeString ( ) ;
11981202 this . outputChannel . appendLine ( `[${ timestamp } ] ERROR: ${ message } ` ) ;
11991203 if ( error ) {
@@ -2993,6 +2997,8 @@ class CopilotTokenTracker implements vscode.Disposable {
29932997 // For delta-based formats, reconstruct state to extract actual usage
29942998 let sessionState : any = { } ;
29952999 let isDeltaBased = false ;
3000+ // For CLI (non-delta) sessions: capture exact per-model usage from session.shutdown
3001+ let cliShutdownModelUsage : ModelUsage | null = null ;
29963002
29973003 for ( const line of lines ) {
29983004 if ( ! line . trim ( ) ) { continue ; }
@@ -3034,8 +3040,19 @@ class CopilotTokenTracker implements vscode.Disposable {
30343040
30353041 // For non-delta formats, estimate from event text (CLI format)
30363042 if ( ! isDeltaBased ) {
3037- // Handle Copilot CLI format
3038- if ( event . type === 'user.message' && event . data ?. content ) {
3043+ // Copilot CLI: session.shutdown has exact per-model token totals
3044+ if ( event . type === 'session.shutdown' && event . data ?. modelMetrics ) {
3045+ cliShutdownModelUsage = { } ;
3046+ for ( const [ modelName , metrics ] of Object . entries ( event . data . modelMetrics ) as [ string , any ] [ ] ) {
3047+ const usage = metrics ?. usage ;
3048+ if ( usage ) {
3049+ cliShutdownModelUsage [ modelName ] = {
3050+ inputTokens : typeof usage . inputTokens === 'number' ? usage . inputTokens : 0 ,
3051+ outputTokens : typeof usage . outputTokens === 'number' ? usage . outputTokens : 0 ,
3052+ } ;
3053+ }
3054+ }
3055+ } else if ( event . type === 'user.message' && event . data ?. content ) {
30393056 modelUsage [ model ] . inputTokens += this . estimateTokensFromText ( event . data . content , model ) ;
30403057 } else if ( event . type === 'assistant.message' && event . data ?. content ) {
30413058 modelUsage [ model ] . outputTokens += this . estimateTokensFromText ( event . data . content , model ) ;
@@ -3049,6 +3066,11 @@ class CopilotTokenTracker implements vscode.Disposable {
30493066 }
30503067 }
30513068
3069+ // If CLI session.shutdown provided exact per-model data, use it instead of estimates
3070+ if ( ! isDeltaBased && cliShutdownModelUsage ) {
3071+ return cliShutdownModelUsage ;
3072+ }
3073+
30523074 // For delta-based formats, extract actual usage from reconstructed state
30533075 if ( isDeltaBased && sessionState . requests && Array . isArray ( sessionState . requests ) ) {
30543076 for ( const request of sessionState . requests ) {
@@ -6154,6 +6176,8 @@ class CopilotTokenTracker implements vscode.Disposable {
61546176 let sessionState : any = { } ;
61556177 let isDeltaBased = false ;
61566178 let parseFailedLines = 0 ;
6179+ // For CLI (non-delta) format: accumulate actual token totals from session.shutdown
6180+ let cliActualTokens = 0 ;
61576181
61586182 for ( const line of lines ) {
61596183 if ( ! line . trim ( ) ) { continue ; }
@@ -6167,6 +6191,17 @@ class CopilotTokenTracker implements vscode.Disposable {
61676191 sessionState = this . applyDelta ( sessionState , event ) ;
61686192 }
61696193
6194+ // Copilot CLI: session.shutdown contains exact token totals per model
6195+ if ( event . type === 'session.shutdown' && event . data ?. modelMetrics ) {
6196+ for ( const metrics of Object . values ( event . data . modelMetrics ) as any [ ] ) {
6197+ const usage = metrics ?. usage ;
6198+ if ( usage ) {
6199+ cliActualTokens += ( typeof usage . inputTokens === 'number' ? usage . inputTokens : 0 )
6200+ + ( typeof usage . outputTokens === 'number' ? usage . outputTokens : 0 ) ;
6201+ }
6202+ }
6203+ }
6204+
61706205 // Handle Copilot CLI event types
61716206 if ( event . type === 'user.message' && event . data ?. content ) {
61726207 totalTokens += this . estimateTokensFromText ( event . data . content ) ;
@@ -6251,7 +6286,9 @@ class CopilotTokenTracker implements vscode.Disposable {
62516286 }
62526287 }
62536288
6254- return { tokens : totalTokens + totalThinkingTokens , thinkingTokens : totalThinkingTokens , actualTokens : totalActualTokens } ;
6289+ // If CLI session.shutdown provided actual totals, use them; otherwise fall back to per-request delta totals
6290+ const finalActualTokens = ! isDeltaBased && cliActualTokens > 0 ? cliActualTokens : totalActualTokens ;
6291+ return { tokens : totalTokens + totalThinkingTokens , thinkingTokens : totalThinkingTokens , actualTokens : finalActualTokens } ;
62556292 }
62566293
62576294 /**
@@ -11014,6 +11051,7 @@ ${hashtag}`;
1101411051 this . diagnosticsPanel . dispose ( ) ;
1101511052 }
1101611053 this . statusBarItem . dispose ( ) ;
11054+ this . _disposed = true ;
1101711055 this . outputChannel . dispose ( ) ;
1101811056 }
1101911057}
0 commit comments