@@ -5,6 +5,7 @@ import * as os from 'os';
55import tokenEstimatorsData from './tokenEstimators.json' ;
66import modelPricingData from './modelPricing.json' ;
77import * as packageJson from '../package.json' ;
8+ import { getModelDisplayName } from './webview/shared/modelUtils' ;
89
910interface TokenUsageStats {
1011 todayTokens : number ;
@@ -789,7 +790,43 @@ class CopilotTokenTracker implements vscode.Disposable {
789790 }
790791
791792 // Convert map to array and sort by date
792- const dailyStatsArray = Array . from ( dailyStatsMap . values ( ) ) . sort ( ( a , b ) => a . date . localeCompare ( b . date ) ) ;
793+ let dailyStatsArray = Array . from ( dailyStatsMap . values ( ) ) . sort ( ( a , b ) => a . date . localeCompare ( b . date ) ) ;
794+
795+ // Fill in missing dates between the first date and today
796+ if ( dailyStatsArray . length > 0 ) {
797+ const firstDate = new Date ( dailyStatsArray [ 0 ] . date ) ;
798+ const today = new Date ( ) ;
799+
800+ // Create a set of existing dates for quick lookup
801+ const existingDates = new Set ( dailyStatsArray . map ( s => s . date ) ) ;
802+
803+ // Generate all dates from first date to today
804+ const allDates : string [ ] = [ ] ;
805+ const currentDate = new Date ( firstDate ) ;
806+
807+ while ( currentDate <= today ) {
808+ const dateKey = this . formatDateKey ( currentDate ) ;
809+ allDates . push ( dateKey ) ;
810+ currentDate . setDate ( currentDate . getDate ( ) + 1 ) ;
811+ }
812+
813+ // Add missing dates with zero values
814+ for ( const dateKey of allDates ) {
815+ if ( ! existingDates . has ( dateKey ) ) {
816+ dailyStatsMap . set ( dateKey , {
817+ date : dateKey ,
818+ tokens : 0 ,
819+ sessions : 0 ,
820+ interactions : 0 ,
821+ modelUsage : { } ,
822+ editorUsage : { }
823+ } ) ;
824+ }
825+ }
826+
827+ // Re-convert map to array and sort by date
828+ dailyStatsArray = Array . from ( dailyStatsMap . values ( ) ) . sort ( ( a , b ) => a . date . localeCompare ( b . date ) ) ;
829+ }
793830
794831 return dailyStatsArray ;
795832 }
@@ -1698,13 +1735,12 @@ class CopilotTokenTracker implements vscode.Disposable {
16981735 }
16991736 }
17001737
1701- // Handle Copilot CLI format
1738+ // Handle Copilot CLI format (type: 'user.message')
17021739 if ( event . type === 'user.message' && event . data ?. content ) {
17031740 turnNumber ++ ;
17041741 const contextRefs = this . createEmptyContextRefs ( ) ;
17051742 const userMessage = event . data . content ;
17061743 this . analyzeContextReferences ( userMessage , contextRefs ) ;
1707-
17081744 const turn : ChatTurn = {
17091745 turnNumber,
17101746 timestamp : event . timestamp ? new Date ( event . timestamp ) . toISOString ( ) : null ,
@@ -2308,7 +2344,7 @@ class CopilotTokenTracker implements vscode.Disposable {
23082344 {
23092345 enableScripts : true ,
23102346 retainContextWhenHidden : false ,
2311- localResourceRoots : [ vscode . Uri . joinPath ( this . extensionUri , 'dist' , 'webview' ) ]
2347+ localResourceRoots : [ vscode . Uri . joinPath ( this . extensionUri , 'dist' ) ]
23122348 }
23132349 ) ;
23142350
@@ -2700,7 +2736,9 @@ class CopilotTokenTracker implements vscode.Disposable {
27002736
27012737 private getDetailsHtml ( webview : vscode . Webview , stats : DetailedStats ) : string {
27022738 const nonce = this . getNonce ( ) ;
2703- const scriptUri = webview . asWebviewUri ( vscode . Uri . joinPath ( this . extensionUri , 'dist' , 'webview' , 'details.js' ) ) ;
2739+ const scriptUri = webview . asWebviewUri (
2740+ vscode . Uri . joinPath ( this . extensionUri , 'dist' , 'webview' , 'details.js' )
2741+ ) ;
27042742
27052743 const csp = [
27062744 `default-src 'none'` ,
@@ -2728,233 +2766,6 @@ class CopilotTokenTracker implements vscode.Disposable {
27282766 </html>` ;
27292767 }
27302768
2731- private getModelUsageHtml ( stats : DetailedStats ) : string {
2732- // Get all unique models from both periods
2733- const allModels = new Set ( [
2734- ...Object . keys ( stats . today . modelUsage ) ,
2735- ...Object . keys ( stats . month . modelUsage )
2736- ] ) ;
2737-
2738- if ( allModels . size === 0 ) {
2739- return '' ;
2740- }
2741-
2742- const now = new Date ( ) ;
2743- const currentDayOfMonth = now . getDate ( ) ;
2744- const daysInYear = ( now . getFullYear ( ) % 4 === 0 && now . getFullYear ( ) % 100 !== 0 ) || now . getFullYear ( ) % 400 === 0 ? 366 : 365 ;
2745-
2746- const calculateProjection = ( monthlyValue : number ) => {
2747- if ( currentDayOfMonth === 0 ) {
2748- return 0 ;
2749- }
2750- const dailyAverage = monthlyValue / currentDayOfMonth ;
2751- return dailyAverage * daysInYear ;
2752- } ;
2753-
2754- const modelRows = Array . from ( allModels ) . map ( model => {
2755- const ratio = this . tokenEstimators [ model ] || 0.25 ;
2756- const charsPerToken = ( 1 / ratio ) . toFixed ( 1 ) ;
2757-
2758- const todayUsage = stats . today . modelUsage [ model ] || { inputTokens : 0 , outputTokens : 0 } ;
2759- const monthUsage = stats . month . modelUsage [ model ] || { inputTokens : 0 , outputTokens : 0 } ;
2760-
2761- const todayTotal = todayUsage . inputTokens + todayUsage . outputTokens ;
2762- const monthTotal = monthUsage . inputTokens + monthUsage . outputTokens ;
2763- const projectedTokens = calculateProjection ( monthTotal ) ;
2764-
2765- const todayInputPercent = todayTotal > 0 ? ( ( todayUsage . inputTokens / todayTotal ) * 100 ) . toFixed ( 0 ) : 0 ;
2766- const todayOutputPercent = todayTotal > 0 ? ( ( todayUsage . outputTokens / todayTotal ) * 100 ) . toFixed ( 0 ) : 0 ;
2767- const monthInputPercent = monthTotal > 0 ? ( ( monthUsage . inputTokens / monthTotal ) * 100 ) . toFixed ( 0 ) : 0 ;
2768- const monthOutputPercent = monthTotal > 0 ? ( ( monthUsage . outputTokens / monthTotal ) * 100 ) . toFixed ( 0 ) : 0 ;
2769-
2770- return `
2771- <tr>
2772- <td class="metric-label">
2773- ${ this . getModelDisplayName ( model ) }
2774- <span style="font-size: 11px; color: #a0a0a0; font-weight: normal;">(~${ charsPerToken } chars/tk)</span>
2775- </td>
2776- <td class="today-value">
2777- ${ todayTotal . toLocaleString ( ) }
2778- <div style="font-size: 10px; color: #999; font-weight: normal; margin-top: 2px;">↑${ todayInputPercent } % ↓${ todayOutputPercent } %</div>
2779- </td>
2780- <td class="month-value">
2781- ${ monthTotal . toLocaleString ( ) }
2782- <div style="font-size: 10px; color: #999; font-weight: normal; margin-top: 2px;">↑${ monthInputPercent } % ↓${ monthOutputPercent } %</div>
2783- </td>
2784- <td class="month-value">${ Math . round ( projectedTokens ) . toLocaleString ( ) } </td>
2785- </tr>
2786- ` ;
2787- } ) . join ( '' ) ;
2788-
2789- return `
2790- <div style="margin-top: 16px;">
2791- <h3 style="color: #ffffff; font-size: 14px; margin-bottom: 8px; display: flex; align-items: center; gap: 6px;">
2792- <span>🎯</span>
2793- <span>Model Usage (Tokens)</span>
2794- </h3>
2795- <table class="stats-table">
2796- <colgroup>
2797- <col class="metric-col">
2798- <col class="value-col">
2799- <col class="value-col">
2800- <col class="value-col">
2801- </colgroup>
2802- <thead>
2803- <tr>
2804- <th>Model</th>
2805- <th>
2806- <div class="period-header">
2807- <span>📅</span>
2808- <span>Today</span>
2809- </div>
2810- </th>
2811- <th>
2812- <div class="period-header">
2813- <span>📊</span>
2814- <span>This Month</span>
2815- </div>
2816- </th>
2817- <th>
2818- <div class="period-header">
2819- <span>🌍</span>
2820- <span>Projected Year</span>
2821- </div>
2822- </th>
2823- </tr>
2824- </thead>
2825- <tbody>
2826- ${ modelRows }
2827- </tbody>
2828- </table>
2829- </div>
2830- ` ;
2831- }
2832-
2833- private getEditorUsageHtml ( stats : DetailedStats ) : string {
2834- // Get all unique editors from both periods
2835- const allEditors = new Set ( [
2836- ...Object . keys ( stats . today . editorUsage ) ,
2837- ...Object . keys ( stats . month . editorUsage )
2838- ] ) ;
2839-
2840- if ( allEditors . size === 0 ) {
2841- return '' ;
2842- }
2843-
2844- // Calculate totals for percentages
2845- const todayTotal = Object . values ( stats . today . editorUsage ) . reduce ( ( sum , e ) => sum + e . tokens , 0 ) ;
2846- const monthTotal = Object . values ( stats . month . editorUsage ) . reduce ( ( sum , e ) => sum + e . tokens , 0 ) ;
2847-
2848- const editorRows = Array . from ( allEditors ) . sort ( ) . map ( editor => {
2849- const todayUsage = stats . today . editorUsage [ editor ] || { tokens : 0 , sessions : 0 } ;
2850- const monthUsage = stats . month . editorUsage [ editor ] || { tokens : 0 , sessions : 0 } ;
2851-
2852- const todayPercent = todayTotal > 0 ? ( ( todayUsage . tokens / todayTotal ) * 100 ) . toFixed ( 1 ) : '0.0' ;
2853- const monthPercent = monthTotal > 0 ? ( ( monthUsage . tokens / monthTotal ) * 100 ) . toFixed ( 1 ) : '0.0' ;
2854-
2855- return `
2856- <tr>
2857- <td class="metric-label">
2858- ${ this . getEditorIcon ( editor ) } ${ editor }
2859- </td>
2860- <td class="today-value">
2861- ${ todayUsage . tokens . toLocaleString ( ) }
2862- <div style="font-size: 10px; color: #999; font-weight: normal; margin-top: 2px;">${ todayPercent } % · ${ todayUsage . sessions } sessions</div>
2863- </td>
2864- <td class="month-value">
2865- ${ monthUsage . tokens . toLocaleString ( ) }
2866- <div style="font-size: 10px; color: #999; font-weight: normal; margin-top: 2px;">${ monthPercent } % · ${ monthUsage . sessions } sessions</div>
2867- </td>
2868- </tr>
2869- ` ;
2870- } ) . join ( '' ) ;
2871-
2872- return `
2873- <div style="margin-top: 16px;">
2874- <h3 style="color: #ffffff; font-size: 14px; margin-bottom: 8px; display: flex; align-items: center; gap: 6px;">
2875- <span>💻</span>
2876- <span>Usage by Editor</span>
2877- </h3>
2878- <table class="stats-table">
2879- <colgroup>
2880- <col class="metric-col">
2881- <col class="value-col">
2882- <col class="value-col">
2883- </colgroup>
2884- <thead>
2885- <tr>
2886- <th>Editor</th>
2887- <th>
2888- <div class="period-header">
2889- <span>📅</span>
2890- <span>Today</span>
2891- </div>
2892- </th>
2893- <th>
2894- <div class="period-header">
2895- <span>📊</span>
2896- <span>This Month</span>
2897- </div>
2898- </th>
2899- </tr>
2900- </thead>
2901- <tbody>
2902- ${ editorRows }
2903- </tbody>
2904- </table>
2905- </div>
2906- ` ;
2907- }
2908-
2909- private getEditorIcon ( editor : string ) : string {
2910- const icons : { [ key : string ] : string } = {
2911- 'VS Code' : '💙' ,
2912- 'VS Code Insiders' : '💚' ,
2913- 'VS Code Exploration' : '🧪' ,
2914- 'VS Code Server' : '☁️' ,
2915- 'VS Code Server (Insiders)' : '☁️' ,
2916- 'VSCodium' : '🔷' ,
2917- 'Cursor' : '⚡' ,
2918- 'Copilot CLI' : '🤖' ,
2919- 'Unknown' : '❓'
2920- } ;
2921- return icons [ editor ] || '📝' ;
2922- }
2923-
2924- private getModelDisplayName ( model : string ) : string {
2925- const modelNames : { [ key : string ] : string } = {
2926- 'gpt-4' : 'GPT-4' ,
2927- 'gpt-4.1' : 'GPT-4.1' ,
2928- 'gpt-4o' : 'GPT-4o' ,
2929- 'gpt-4o-mini' : 'GPT-4o Mini' ,
2930- 'gpt-3.5-turbo' : 'GPT-3.5 Turbo' ,
2931- 'gpt-5' : 'GPT-5' ,
2932- 'gpt-5-codex' : 'GPT-5 Codex (Preview)' ,
2933- 'gpt-5-mini' : 'GPT-5 Mini' ,
2934- 'gpt-5.1' : 'GPT-5.1' ,
2935- 'gpt-5.1-codex' : 'GPT-5.1 Codex' ,
2936- 'gpt-5.1-codex-max' : 'GPT-5.1 Codex Max' ,
2937- 'gpt-5.1-codex-mini' : 'GPT-5.1 Codex Mini (Preview)' ,
2938- 'gpt-5.2' : 'GPT-5.2' ,
2939- 'claude-sonnet-3.5' : 'Claude Sonnet 3.5' ,
2940- 'claude-sonnet-3.7' : 'Claude Sonnet 3.7' ,
2941- 'claude-sonnet-4' : 'Claude Sonnet 4' ,
2942- 'claude-sonnet-4.5' : 'Claude Sonnet 4.5' ,
2943- 'claude-haiku' : 'Claude Haiku' ,
2944- 'claude-haiku-4.5' : 'Claude Haiku 4.5' ,
2945- 'claude-opus-4.1' : 'Claude Opus 4.1' ,
2946- 'claude-opus-4.5' : 'Claude Opus 4.5' ,
2947- 'gemini-2.5-pro' : 'Gemini 2.5 Pro' ,
2948- 'gemini-3-flash' : 'Gemini 3 Flash' ,
2949- 'gemini-3-pro' : 'Gemini 3 Pro' ,
2950- 'gemini-3-pro-preview' : 'Gemini 3 Pro (Preview)' ,
2951- 'grok-code-fast-1' : 'Grok Code Fast 1' ,
2952- 'raptor-mini' : 'Raptor Mini' ,
2953- 'o3-mini' : 'o3-mini' ,
2954- 'o4-mini' : 'o4-mini (Preview)'
2955- } ;
2956- return modelNames [ model ] || model ;
2957- }
29582769
29592770 public async generateDiagnosticReport ( ) : Promise < string > {
29602771 this . log ( 'Generating diagnostic report...' ) ;
@@ -3503,7 +3314,7 @@ class CopilotTokenTracker implements vscode.Disposable {
35033314 const modelDatasets = Array . from ( allModels ) . map ( ( model , idx ) => {
35043315 const color = modelColors [ idx % modelColors . length ] ;
35053316 return {
3506- label : this . getModelDisplayName ( model ) ,
3317+ label : getModelDisplayName ( model ) ,
35073318 data : dailyStats . map ( d => {
35083319 const usage = d . modelUsage [ model ] ;
35093320 return usage ? usage . inputTokens + usage . outputTokens : 0 ;
0 commit comments