diff --git a/README.md b/README.md
index bc6a008e..fd5d4f66 100644
--- a/README.md
+++ b/README.md
@@ -71,6 +71,33 @@ This caching significantly reduces the time needed for periodic updates, especia
npx vsce package
```
+### Running the Extension Locally
+
+To test and debug the extension in a local VS Code environment:
+
+1. Install dependencies:
+ ```bash
+ npm install
+ ```
+
+2. Start watch mode (automatically recompiles on file changes):
+ ```bash
+ npm run watch
+ ```
+
+3. In VS Code, press **F5** to launch the Extension Development Host
+ - This opens a new VS Code window with the extension running
+ - The original window shows debug output and allows you to set breakpoints
+
+4. In the Extension Development Host window:
+ - The extension will be active and you'll see the token tracker in the status bar
+ - Any changes you make to the code will be automatically compiled (thanks to watch mode)
+ - Reload the Extension Development Host window (Ctrl+R or Cmd+R) to see your changes
+
+5. To view console logs and debug information:
+ - In the Extension Development Host window, open Developer Tools: **Help > Toggle Developer Tools**
+ - Check the Console tab for any `console.log` output from the extension
+
### Available Scripts
- `npm run lint` - Run ESLint
diff --git a/src/extension.ts b/src/extension.ts
index 97303e7a..439126d8 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -12,7 +12,10 @@ interface TokenUsageStats {
}
interface ModelUsage {
- [modelName: string]: number;
+ [modelName: string]: {
+ inputTokens: number;
+ outputTokens: number;
+ };
}
interface ModelPricing {
@@ -56,6 +59,11 @@ interface SessionFileCache {
class CopilotTokenTracker implements vscode.Disposable {
private statusBarItem: vscode.StatusBarItem;
+
+ // Helper method to get total tokens from ModelUsage
+ private getTotalTokensFromModelUsage(modelUsage: ModelUsage): number {
+ return Object.values(modelUsage).reduce((sum, usage) => sum + usage.inputTokens + usage.outputTokens, 0);
+ }
private updateInterval: NodeJS.Timeout | undefined;
private initialDelayTimeout: NodeJS.Timeout | undefined;
private detailsPanel: vscode.WebviewPanel | undefined;
@@ -174,7 +182,7 @@ class CopilotTokenTracker implements vscode.Disposable {
if (extensionsExistButInactive) {
// Use shorter delay for testing in Codespaces
- const delaySeconds = process.env.CODESPACES === 'true' ? 10 : 60;
+ const delaySeconds = process.env.CODESPACES === 'true' ? 10 : 15;
this.log(`Copilot extensions found but not active yet - delaying initial update by ${delaySeconds} seconds to allow extensions to load`);
this.log(`Setting timeout for ${new Date(Date.now() + (delaySeconds * 1000)).toLocaleTimeString()}`);
@@ -251,7 +259,7 @@ class CopilotTokenTracker implements vscode.Disposable {
tooltip.appendMarkdown(`**Avg Interactions/Session:** ${detailedStats.month.avgInteractionsPerSession}\n\n`);
tooltip.appendMarkdown(`**Avg Tokens/Session:** ${detailedStats.month.avgTokensPerSession.toLocaleString()}\n\n`);
tooltip.appendMarkdown('---\n\n');
- tooltip.appendMarkdown('*Cost estimates based on OpenAI/Anthropic API pricing*\n\n');
+ tooltip.appendMarkdown('*Cost estimates based on actual input/output token ratios*\n\n');
tooltip.appendMarkdown('*Updates automatically every 5 minutes*');
this.statusBarItem.tooltip = tooltip;
@@ -358,8 +366,12 @@ class CopilotTokenTracker implements vscode.Disposable {
monthStats.interactions += interactions;
// Add model usage to month stats
- for (const [model, modelTokens] of Object.entries(modelUsage)) {
- monthStats.modelUsage[model] = (monthStats.modelUsage[model] || 0) + (modelTokens as number);
+ for (const [model, usage] of Object.entries(modelUsage)) {
+ if (!monthStats.modelUsage[model]) {
+ monthStats.modelUsage[model] = { inputTokens: 0, outputTokens: 0 };
+ }
+ monthStats.modelUsage[model].inputTokens += usage.inputTokens;
+ monthStats.modelUsage[model].outputTokens += usage.outputTokens;
}
if (fileStats.mtime >= todayStart) {
@@ -368,8 +380,12 @@ class CopilotTokenTracker implements vscode.Disposable {
todayStats.interactions += interactions;
// Add model usage to today stats
- for (const [model, modelTokens] of Object.entries(modelUsage)) {
- todayStats.modelUsage[model] = (todayStats.modelUsage[model] || 0) + (modelTokens as number);
+ for (const [model, usage] of Object.entries(modelUsage)) {
+ if (!todayStats.modelUsage[model]) {
+ todayStats.modelUsage[model] = { inputTokens: 0, outputTokens: 0 };
+ }
+ todayStats.modelUsage[model].inputTokens += usage.inputTokens;
+ todayStats.modelUsage[model].outputTokens += usage.outputTokens;
}
}
}
@@ -452,22 +468,27 @@ class CopilotTokenTracker implements vscode.Disposable {
// Get model for this request
const model = this.getModelFromRequest(request);
- // Estimate tokens from user message
+ // Initialize model if not exists
+ if (!modelUsage[model]) {
+ modelUsage[model] = { inputTokens: 0, outputTokens: 0 };
+ }
+
+ // Estimate tokens from user message (input)
if (request.message && request.message.parts) {
for (const part of request.message.parts) {
if (part.text) {
const tokens = this.estimateTokensFromText(part.text, model);
- modelUsage[model] = (modelUsage[model] || 0) + tokens;
+ modelUsage[model].inputTokens += tokens;
}
}
}
- // Estimate tokens from assistant response
+ // Estimate tokens from assistant response (output)
if (request.response && Array.isArray(request.response)) {
for (const responseItem of request.response) {
if (responseItem.value) {
const tokens = this.estimateTokensFromText(responseItem.value, model);
- modelUsage[model] = (modelUsage[model] || 0) + tokens;
+ modelUsage[model].outputTokens += tokens;
}
}
}
@@ -528,27 +549,21 @@ class CopilotTokenTracker implements vscode.Disposable {
private calculateEstimatedCost(modelUsage: ModelUsage): number {
let totalCost = 0;
- for (const [model, tokens] of Object.entries(modelUsage)) {
+ for (const [model, usage] of Object.entries(modelUsage)) {
const pricing = this.modelPricing[model];
if (pricing) {
- // Assume 50/50 split between input and output tokens
- // This is a simplification since we don't track them separately
- const inputTokens = tokens * 0.5;
- const outputTokens = tokens * 0.5;
-
- const inputCost = (inputTokens / 1_000_000) * pricing.inputCostPerMillion;
- const outputCost = (outputTokens / 1_000_000) * pricing.outputCostPerMillion;
+ // Use actual input and output token counts
+ const inputCost = (usage.inputTokens / 1_000_000) * pricing.inputCostPerMillion;
+ const outputCost = (usage.outputTokens / 1_000_000) * pricing.outputCostPerMillion;
totalCost += inputCost + outputCost;
} else {
// Fallback for models without pricing data - use GPT-4o-mini as default
const fallbackPricing = this.modelPricing['gpt-4o-mini'];
- const inputTokens = tokens * 0.5;
- const outputTokens = tokens * 0.5;
- const inputCost = (inputTokens / 1_000_000) * fallbackPricing.inputCostPerMillion;
- const outputCost = (outputTokens / 1_000_000) * fallbackPricing.outputCostPerMillion;
+ const inputCost = (usage.inputTokens / 1_000_000) * fallbackPricing.inputCostPerMillion;
+ const outputCost = (usage.outputTokens / 1_000_000) * fallbackPricing.outputCostPerMillion;
totalCost += inputCost + outputCost;
@@ -790,31 +805,32 @@ class CopilotTokenTracker implements vscode.Disposable {
private async estimateTokensFromSession(sessionFilePath: string): Promise