Skip to content

Commit 9b06cac

Browse files
authored
Merge pull request #363 from rajbos/cli-token-update
Fix Copilot CLI token counts
2 parents 76b7de4 + 1c888e7 commit 9b06cac

File tree

1 file changed

+42
-4
lines changed

1 file changed

+42
-4
lines changed

src/extension.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ interface WorkspaceCustomizationSummary {
387387

388388
class 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

Comments
 (0)