@@ -12,7 +12,10 @@ interface TokenUsageStats {
1212}
1313
1414interface ModelUsage {
15- [ modelName : string ] : number ;
15+ [ modelName : string ] : {
16+ inputTokens : number ;
17+ outputTokens : number ;
18+ } ;
1619}
1720
1821interface ModelPricing {
@@ -56,6 +59,11 @@ interface SessionFileCache {
5659
5760class CopilotTokenTracker implements vscode . Disposable {
5861 private statusBarItem : vscode . StatusBarItem ;
62+
63+ // Helper method to get total tokens from ModelUsage
64+ private getTotalTokensFromModelUsage ( modelUsage : ModelUsage ) : number {
65+ return Object . values ( modelUsage ) . reduce ( ( sum , usage ) => sum + usage . inputTokens + usage . outputTokens , 0 ) ;
66+ }
5967 private updateInterval : NodeJS . Timeout | undefined ;
6068 private initialDelayTimeout : NodeJS . Timeout | undefined ;
6169 private detailsPanel : vscode . WebviewPanel | undefined ;
@@ -174,7 +182,7 @@ class CopilotTokenTracker implements vscode.Disposable {
174182
175183 if ( extensionsExistButInactive ) {
176184 // Use shorter delay for testing in Codespaces
177- const delaySeconds = process . env . CODESPACES === 'true' ? 10 : 60 ;
185+ const delaySeconds = process . env . CODESPACES === 'true' ? 10 : 15 ;
178186 this . log ( `Copilot extensions found but not active yet - delaying initial update by ${ delaySeconds } seconds to allow extensions to load` ) ;
179187 this . log ( `Setting timeout for ${ new Date ( Date . now ( ) + ( delaySeconds * 1000 ) ) . toLocaleTimeString ( ) } ` ) ;
180188
@@ -251,7 +259,7 @@ class CopilotTokenTracker implements vscode.Disposable {
251259 tooltip . appendMarkdown ( `**Avg Interactions/Session:** ${ detailedStats . month . avgInteractionsPerSession } \n\n` ) ;
252260 tooltip . appendMarkdown ( `**Avg Tokens/Session:** ${ detailedStats . month . avgTokensPerSession . toLocaleString ( ) } \n\n` ) ;
253261 tooltip . appendMarkdown ( '---\n\n' ) ;
254- tooltip . appendMarkdown ( '*Cost estimates based on OpenAI/Anthropic API pricing *\n\n' ) ;
262+ tooltip . appendMarkdown ( '*Cost estimates based on actual input/output token ratios *\n\n' ) ;
255263 tooltip . appendMarkdown ( '*Updates automatically every 5 minutes*' ) ;
256264
257265 this . statusBarItem . tooltip = tooltip ;
@@ -358,8 +366,12 @@ class CopilotTokenTracker implements vscode.Disposable {
358366 monthStats . interactions += interactions ;
359367
360368 // Add model usage to month stats
361- for ( const [ model , modelTokens ] of Object . entries ( modelUsage ) ) {
362- monthStats . modelUsage [ model ] = ( monthStats . modelUsage [ model ] || 0 ) + ( modelTokens as number ) ;
369+ for ( const [ model , usage ] of Object . entries ( modelUsage ) ) {
370+ if ( ! monthStats . modelUsage [ model ] ) {
371+ monthStats . modelUsage [ model ] = { inputTokens : 0 , outputTokens : 0 } ;
372+ }
373+ monthStats . modelUsage [ model ] . inputTokens += usage . inputTokens ;
374+ monthStats . modelUsage [ model ] . outputTokens += usage . outputTokens ;
363375 }
364376
365377 if ( fileStats . mtime >= todayStart ) {
@@ -368,8 +380,12 @@ class CopilotTokenTracker implements vscode.Disposable {
368380 todayStats . interactions += interactions ;
369381
370382 // Add model usage to today stats
371- for ( const [ model , modelTokens ] of Object . entries ( modelUsage ) ) {
372- todayStats . modelUsage [ model ] = ( todayStats . modelUsage [ model ] || 0 ) + ( modelTokens as number ) ;
383+ for ( const [ model , usage ] of Object . entries ( modelUsage ) ) {
384+ if ( ! todayStats . modelUsage [ model ] ) {
385+ todayStats . modelUsage [ model ] = { inputTokens : 0 , outputTokens : 0 } ;
386+ }
387+ todayStats . modelUsage [ model ] . inputTokens += usage . inputTokens ;
388+ todayStats . modelUsage [ model ] . outputTokens += usage . outputTokens ;
373389 }
374390 }
375391 }
@@ -452,22 +468,27 @@ class CopilotTokenTracker implements vscode.Disposable {
452468 // Get model for this request
453469 const model = this . getModelFromRequest ( request ) ;
454470
455- // Estimate tokens from user message
471+ // Initialize model if not exists
472+ if ( ! modelUsage [ model ] ) {
473+ modelUsage [ model ] = { inputTokens : 0 , outputTokens : 0 } ;
474+ }
475+
476+ // Estimate tokens from user message (input)
456477 if ( request . message && request . message . parts ) {
457478 for ( const part of request . message . parts ) {
458479 if ( part . text ) {
459480 const tokens = this . estimateTokensFromText ( part . text , model ) ;
460- modelUsage [ model ] = ( modelUsage [ model ] || 0 ) + tokens ;
481+ modelUsage [ model ] . inputTokens += tokens ;
461482 }
462483 }
463484 }
464485
465- // Estimate tokens from assistant response
486+ // Estimate tokens from assistant response (output)
466487 if ( request . response && Array . isArray ( request . response ) ) {
467488 for ( const responseItem of request . response ) {
468489 if ( responseItem . value ) {
469490 const tokens = this . estimateTokensFromText ( responseItem . value , model ) ;
470- modelUsage [ model ] = ( modelUsage [ model ] || 0 ) + tokens ;
491+ modelUsage [ model ] . outputTokens += tokens ;
471492 }
472493 }
473494 }
@@ -528,27 +549,21 @@ class CopilotTokenTracker implements vscode.Disposable {
528549 private calculateEstimatedCost ( modelUsage : ModelUsage ) : number {
529550 let totalCost = 0 ;
530551
531- for ( const [ model , tokens ] of Object . entries ( modelUsage ) ) {
552+ for ( const [ model , usage ] of Object . entries ( modelUsage ) ) {
532553 const pricing = this . modelPricing [ model ] ;
533554
534555 if ( pricing ) {
535- // Assume 50/50 split between input and output tokens
536- // This is a simplification since we don't track them separately
537- const inputTokens = tokens * 0.5 ;
538- const outputTokens = tokens * 0.5 ;
539-
540- const inputCost = ( inputTokens / 1_000_000 ) * pricing . inputCostPerMillion ;
541- const outputCost = ( outputTokens / 1_000_000 ) * pricing . outputCostPerMillion ;
556+ // Use actual input and output token counts
557+ const inputCost = ( usage . inputTokens / 1_000_000 ) * pricing . inputCostPerMillion ;
558+ const outputCost = ( usage . outputTokens / 1_000_000 ) * pricing . outputCostPerMillion ;
542559
543560 totalCost += inputCost + outputCost ;
544561 } else {
545562 // Fallback for models without pricing data - use GPT-4o-mini as default
546563 const fallbackPricing = this . modelPricing [ 'gpt-4o-mini' ] ;
547- const inputTokens = tokens * 0.5 ;
548- const outputTokens = tokens * 0.5 ;
549564
550- const inputCost = ( inputTokens / 1_000_000 ) * fallbackPricing . inputCostPerMillion ;
551- const outputCost = ( outputTokens / 1_000_000 ) * fallbackPricing . outputCostPerMillion ;
565+ const inputCost = ( usage . inputTokens / 1_000_000 ) * fallbackPricing . inputCostPerMillion ;
566+ const outputCost = ( usage . outputTokens / 1_000_000 ) * fallbackPricing . outputCostPerMillion ;
552567
553568 totalCost += inputCost + outputCost ;
554569
@@ -790,31 +805,32 @@ class CopilotTokenTracker implements vscode.Disposable {
790805 private async estimateTokensFromSession ( sessionFilePath : string ) : Promise < number > {
791806 try {
792807 const sessionContent = JSON . parse ( fs . readFileSync ( sessionFilePath , 'utf8' ) ) ;
793- let totalTokens = 0 ;
808+ let totalInputTokens = 0 ;
809+ let totalOutputTokens = 0 ;
794810
795811 if ( sessionContent . requests && Array . isArray ( sessionContent . requests ) ) {
796812 for ( const request of sessionContent . requests ) {
797- // Estimate tokens from user message
813+ // Estimate tokens from user message (input)
798814 if ( request . message && request . message . parts ) {
799815 for ( const part of request . message . parts ) {
800816 if ( part . text ) {
801- totalTokens += this . estimateTokensFromText ( part . text ) ;
817+ totalInputTokens += this . estimateTokensFromText ( part . text ) ;
802818 }
803819 }
804820 }
805821
806- // Estimate tokens from assistant response
822+ // Estimate tokens from assistant response (output)
807823 if ( request . response && Array . isArray ( request . response ) ) {
808824 for ( const responseItem of request . response ) {
809825 if ( responseItem . value ) {
810- totalTokens += this . estimateTokensFromText ( responseItem . value , this . getModelFromRequest ( request ) ) ;
826+ totalOutputTokens += this . estimateTokensFromText ( responseItem . value , this . getModelFromRequest ( request ) ) ;
811827 }
812828 }
813829 }
814830 }
815831 }
816832
817- return totalTokens ;
833+ return totalInputTokens + totalOutputTokens ;
818834 } catch ( error ) {
819835 this . warn ( `Error parsing session file ${ sessionFilePath } : ${ error } ` ) ;
820836 return 0 ;
@@ -1170,7 +1186,7 @@ class CopilotTokenTracker implements vscode.Disposable {
11701186 Token counts are estimated based on character count. CO₂, tree equivalents, water usage, and costs are derived from these token estimates.
11711187 </p>
11721188 <ul style="font-size: 12px; color: #b3b3b3; padding-left: 20px; list-style-position: inside; margin-top: 8px;">
1173- <li><b>Cost Estimate:</b> Based on OpenAI API pricing (as of Dec 2025, <a href="https://openai .com/api/pricing/ " style="color: #3794ff;">openai.com/api/pricing </a>) and standard Anthropic rates. Assumes 50/50 split between input and output tokens . <b>Note:</b> GitHub Copilot pricing may differ from direct API usage. These are reference estimates only.</li>
1189+ <li><b>Cost Estimate:</b> Based on public API pricing (see <a href="https://github .com/rajbos/github-copilot-token-usage/blob/main/src/modelPricing.json " style="color: #3794ff;">modelPricing.json </a> for sources and rates). Uses actual input/output token counts for accurate cost calculation . <b>Note:</b> GitHub Copilot pricing may differ from direct API usage. These are reference estimates only.</li>
11741190 <li><b>CO₂ Estimate:</b> Based on ~${ this . co2Per1kTokens } g of CO₂e per 1,000 tokens.</li>
11751191 <li><b>Tree Equivalent:</b> Represents the fraction of a single mature tree's annual CO₂ absorption (~${ ( this . co2AbsorptionPerTreePerYear / 1000 ) . toFixed ( 1 ) } kg/year).</li>
11761192 <li><b>Water Estimate:</b> Based on ~${ this . waterUsagePer1kTokens } L of water per 1,000 tokens for data center cooling and operations.</li>
@@ -1226,17 +1242,33 @@ class CopilotTokenTracker implements vscode.Disposable {
12261242 const modelRows = Array . from ( allModels ) . map ( model => {
12271243 const ratio = this . tokenEstimators [ model ] || 0.25 ;
12281244 const charsPerToken = ( 1 / ratio ) . toFixed ( 1 ) ;
1229- const monthlyTokens = stats . month . modelUsage [ model ] || 0 ;
1230- const projectedTokens = calculateProjection ( monthlyTokens ) ;
1245+
1246+ const todayUsage = stats . today . modelUsage [ model ] || { inputTokens : 0 , outputTokens : 0 } ;
1247+ const monthUsage = stats . month . modelUsage [ model ] || { inputTokens : 0 , outputTokens : 0 } ;
1248+
1249+ const todayTotal = todayUsage . inputTokens + todayUsage . outputTokens ;
1250+ const monthTotal = monthUsage . inputTokens + monthUsage . outputTokens ;
1251+ const projectedTokens = calculateProjection ( monthTotal ) ;
1252+
1253+ const todayInputPercent = todayTotal > 0 ? ( ( todayUsage . inputTokens / todayTotal ) * 100 ) . toFixed ( 0 ) : 0 ;
1254+ const todayOutputPercent = todayTotal > 0 ? ( ( todayUsage . outputTokens / todayTotal ) * 100 ) . toFixed ( 0 ) : 0 ;
1255+ const monthInputPercent = monthTotal > 0 ? ( ( monthUsage . inputTokens / monthTotal ) * 100 ) . toFixed ( 0 ) : 0 ;
1256+ const monthOutputPercent = monthTotal > 0 ? ( ( monthUsage . outputTokens / monthTotal ) * 100 ) . toFixed ( 0 ) : 0 ;
12311257
12321258 return `
12331259 <tr>
12341260 <td class="metric-label">
12351261 ${ this . getModelDisplayName ( model ) }
12361262 <span style="font-size: 11px; color: #a0a0a0; font-weight: normal;">(~${ charsPerToken } chars/tk)</span>
12371263 </td>
1238- <td class="today-value">${ ( stats . today . modelUsage [ model ] || 0 ) . toLocaleString ( ) } </td>
1239- <td class="month-value">${ monthlyTokens . toLocaleString ( ) } </td>
1264+ <td class="today-value">
1265+ ${ todayTotal . toLocaleString ( ) }
1266+ <div style="font-size: 10px; color: #999; font-weight: normal; margin-top: 2px;">↑${ todayInputPercent } % ↓${ todayOutputPercent } %</div>
1267+ </td>
1268+ <td class="month-value">
1269+ ${ monthTotal . toLocaleString ( ) }
1270+ <div style="font-size: 10px; color: #999; font-weight: normal; margin-top: 2px;">↑${ monthInputPercent } % ↓${ monthOutputPercent } %</div>
1271+ </td>
12401272 <td class="month-value">${ Math . round ( projectedTokens ) . toLocaleString ( ) } </td>
12411273 </tr>
12421274 ` ;
0 commit comments