|
| 1 | +--- |
| 2 | +applyTo: "vscode-extension/**" |
| 3 | +--- |
| 4 | + |
| 5 | +# VS Code Extension — Architecture & Development Guide |
| 6 | + |
| 7 | +## Architecture Overview |
| 8 | + |
| 9 | +The entire extension's logic is contained within the `CopilotTokenTracker` class in `vscode-extension/src/extension.ts`. Its primary function is to track GitHub Copilot token usage by reading and parsing session log files. |
| 10 | + |
| 11 | +- **Activation**: The extension activates on VS Code startup via the `onStartupFinished` event in `vscode-extension/package.json`. The `activate` function in `vscode-extension/src/extension.ts` instantiates `CopilotTokenTracker`. |
| 12 | + |
| 13 | +- **Data Source**: The core data comes from `.json` log files generated by the GitHub Copilot Chat extension. The `getCopilotSessionFiles` method locates these files in the user's OS-specific AppData directory for VS Code (e.g., `.../Code/User/workspaceStorage/.../chatSessions` and `.../Code/User/globalStorage/emptyWindowChatSessions`). |
| 14 | + |
| 15 | +- **Data Flow**: |
| 16 | + 1. A timer in the `constructor` calls `updateTokenStats()` every 5 minutes. |
| 17 | + 2. `updateTokenStats()` calls `calculateDetailedStats()` to aggregate data. |
| 18 | + 3. `calculateDetailedStats()` reads all session files found by `getCopilotSessionFiles()`. |
| 19 | + 4. For each file, it calculates token counts (`estimateTokensFromSession`), interactions (`countInteractionsInSession`), and per-model usage (`getModelUsageFromSession`). |
| 20 | + 5. Token counts are *estimated* using character-to-token ratios defined in the `tokenEstimators` class property. This is a key convention. |
| 21 | + |
| 22 | +- **Thinking Token Tracking**: Models that use extended thinking (e.g., Claude) produce `kind: "thinking"` response items in session logs. These are tracked separately: |
| 23 | + - `estimateTokensFromSession` and `estimateTokensFromJsonlSession` return `{ tokens, thinkingTokens }`. The `tokens` field is the **grand total** (input + output + thinking). The `thinkingTokens` field is a **breakdown** showing what portion of the total was thinking — it is NOT subtracted from `tokens`. |
| 24 | + - `SessionFileCache` stores `thinkingTokens` alongside `tokens`. |
| 25 | + - `extractResponseData` separates `responseText` and `thinkingText` so the logviewer can display them independently per turn. |
| 26 | + - `getModelUsageFromSession` does NOT separate thinking — thinking tokens are counted as `outputTokens` in the per-model breakdown. This is intentional since it keeps cost estimation simple. |
| 27 | + - **Critical invariant**: When computing token totals for display (per-turn, per-session, per-period), always include thinking tokens. In the logviewer, `totalTokens = input + output + thinking`. In stats aggregation, `sessionData.tokens` already includes thinking. Never subtract thinking from a total. |
| 28 | + - `CACHE_VERSION` must be bumped when changing how tokens are counted so stale caches are invalidated. |
| 29 | + |
| 30 | +- **OpenCode Support**: The extension also reads session data from [OpenCode](https://opencode.ai), a terminal-based coding agent. OpenCode stores its data in `~/.local/share/opencode/` (Linux/macOS) or the equivalent XDG data directory. |
| 31 | + |
| 32 | + - **Dual storage backends**: OpenCode originally stored sessions as individual JSON files under `storage/session/`, `storage/message/`, and `storage/part/`. It later migrated to a SQLite database (`opencode.db`) in the same data directory. The extension supports **both** backends — it tries the SQLite DB first and falls back to JSON files. |
| 33 | + - **SQLite reading**: The extension uses `sql.js` (a pure JS/WASM SQLite reader, no native dependencies) to read `opencode.db`. The WASM binary (`sql-wasm.wasm`) is copied to `dist/` during the esbuild step. The sql.js module is lazy-loaded and cached in `_sqlJsModule`. |
| 34 | + - **DB schema**: The `opencode.db` database has tables `session` (id, slug, title, directory, time_created, time_updated), `message` (id, session_id, time_created, data JSON), and `part` (id, message_id, session_id, time_created, data JSON). The `data` column in `message` contains JSON with `{role, model: {providerID, modelID}, tokens: {total, input, output, reasoning, cache: {read, write}}}`. |
| 35 | + - **Virtual path scheme**: Since the existing architecture is file-path-based, DB-stored sessions use virtual paths of the form `<opencode_dir>/opencode.db#ses_<id>`. Detection helpers: `isOpenCodeDbSession()` checks for the `opencode.db#ses_` pattern, `getOpenCodeSessionId()` parses both virtual paths and `ses_<id>.json` filenames. |
| 36 | + - **Async methods**: `getOpenCodeMessagesForSession(filePath)` and `getOpenCodePartsForMessage(messageId)` are the primary data access methods — they try DB first, fall back to JSON. The OpenCode token/interaction/model methods (`getTokensFromOpenCodeSession`, `countOpenCodeInteractions`, `getOpenCodeModelUsage`) are all async. |
| 37 | + - **`statSessionFile()`**: A helper that resolves DB virtual paths to `fs.promises.stat()` on the `opencode.db` file itself. **All `fs.promises.stat(sessionFile)` calls in loops that process session files must use `this.statSessionFile()` instead**, to avoid failures on DB virtual paths. |
| 38 | + - **Key invariant**: When adding new code that processes session file paths (stat calls, path splitting, folder grouping), always check `isOpenCodeDbSession()` first and handle the virtual path appropriately (use `statSessionFile()` for stats, use `getOpenCodeDataDir()` for folder grouping). |
| 39 | + |
| 40 | +- **UI Components**: |
| 41 | + 1. **Status Bar**: A `vscode.StatusBarItem` (`statusBarItem`) shows a brief summary. Its tooltip provides more detail. |
| 42 | + 2. **Details Panel**: The `copilot-token-tracker.showDetails` command opens a `vscode.WebviewPanel`. The content for this panel is generated dynamically as an HTML string by the `getDetailsHtml` method. |
| 43 | + |
| 44 | +## Developer Workflow |
| 45 | + |
| 46 | +- **Setup**: Run `npm install` inside `vscode-extension/` to install dependencies. |
| 47 | +- **Build**: Run `npm run compile` from `vscode-extension/` to lint and build the extension using `esbuild`. The output is a single file: `vscode-extension/dist/extension.js`. |
| 48 | +- **Watch Mode**: For active development, use `npm run watch` from `vscode-extension/`. This will automatically recompile the extension on file changes. |
| 49 | +- **Testing/Debugging**: Press `F5` in VS Code to open the Extension Development Host. This will launch a new VS Code window with the extension running. `console.log` statements from `vscode-extension/src/extension.ts` will appear in the Developer Tools console of this new window (Help > Toggle Developer Tools). |
| 50 | + |
| 51 | +**Important build guidance:** After making changes to source code or related files (TypeScript, JavaScript, JSON, or other code files used by the extension), always run `npm run compile` from `vscode-extension/` to validate that the project still builds and lints cleanly before opening a pull request or releasing. You do not need to run the full compile step for documentation-only changes (Markdown files), but you should run it after any edits that touch source, configuration, or JSON data files. |
| 52 | + |
| 53 | +## Development Guidelines |
| 54 | + |
| 55 | +- **Minimal Changes**: Only modify files that are directly needed for the actual changes being implemented. Avoid touching unrelated files, configuration files, or dependencies unless absolutely necessary for the feature or fix. |
| 56 | +- **Focused Modifications**: Make surgical, precise changes that address the specific requirements without affecting other functionality. |
| 57 | +- **Preserve Existing Structure**: Maintain the existing code organization and file structure. Don't refactor or reorganize code unless it's essential for the task. |
| 58 | + |
| 59 | +## Logging Best Practices |
| 60 | + |
| 61 | +**CRITICAL**: Do NOT add debug logging statements like `this.log('[DEBUG] message')` or `console.log('[DEBUG] ...')` for troubleshooting during development. This approach has been found to flood the output channel and cause messages to disappear. |
| 62 | + |
| 63 | +### Why DEBUG Logs Are Problematic |
| 64 | + |
| 65 | +**Extension Host Flooding**: When DEBUG log statements are added to frequently-called methods in `extension.ts` (e.g., cache lookups, file processing loops, webview message handlers), they can generate hundreds of log entries per operation. VS Code's OutputChannel has a buffer limit, and excessive logging causes older messages to be pushed out and lost. This was observed when: |
| 66 | +- Cache hit/miss logging was added to session file processing |
| 67 | +- JSONL content reference counting was logged for each file |
| 68 | +- Webview message handlers logged every incoming message with full JSON payloads |
| 69 | +- Session data was logged with repository counts on each webview update |
| 70 | + |
| 71 | +**Symptom**: After operations like clearing the cache, expected log messages would disappear from the Output panel because they were pushed out of the buffer by DEBUG logs. |
| 72 | + |
| 73 | +### Extension vs Webview Logging |
| 74 | + |
| 75 | +These are two completely separate logging systems: |
| 76 | + |
| 77 | +| Context | Method | Destination | Visibility | |
| 78 | +|---------|--------|-------------|------------| |
| 79 | +| Extension (`vscode-extension/src/extension.ts`) | `this.log()`, `this.warn()`, `this.error()` | VS Code Output Channel | Output panel → "Copilot Token Tracker" | |
| 80 | +| Webview (`vscode-extension/src/webview/*/main.ts`) | `console.log()` | Browser DevTools | Help → Toggle Developer Tools in webview | |
| 81 | + |
| 82 | +- Clearing the output channel (`outputChannel.clear()`) does NOT affect webview console logs |
| 83 | +- Webview console.log statements do NOT appear in the Output panel |
| 84 | +- DEBUG prefixes in webviews were removed to maintain consistency with extension guidelines |
| 85 | + |
| 86 | +### Best Practices |
| 87 | + |
| 88 | +- **Use Existing Logs**: The extension already has comprehensive logging. Review existing log statements before adding new ones. |
| 89 | +- **Minimal Logging**: Only add logging if absolutely necessary for a new feature. Keep messages concise. |
| 90 | +- **Remove Debug Logs**: Any temporary debug logging added during development MUST be removed before committing. |
| 91 | +- **Log Methods**: Use appropriate severity: |
| 92 | + - `log(message)` - Standard informational messages |
| 93 | + - `warn(message)` - Warnings or recoverable errors |
| 94 | + - `error(message)` - Critical errors |
| 95 | +- **No Debug Prefixes**: Avoid `[DEBUG]` markers. The log output is already timestamped. |
| 96 | +- **Avoid High-Frequency Logging**: Never log inside loops that process many items (files, sessions, cache entries). |
| 97 | + |
| 98 | +### Debugging Without Logs |
| 99 | + |
| 100 | +Prefer VS Code's debugger with breakpoints rather than adding log statements: |
| 101 | +1. Press `F5` to launch Extension Development Host |
| 102 | +2. Set breakpoints in `vscode-extension/src/extension.ts` |
| 103 | +3. Use the Debug Console to inspect variables |
| 104 | + |
| 105 | +## Key Files & Conventions |
| 106 | + |
| 107 | +- **`vscode-extension/src/extension.ts`**: The single source file containing all logic. |
| 108 | + - `CopilotTokenTracker`: The main class. |
| 109 | + - `calculateDetailedStats()`: The primary data aggregation method. |
| 110 | + - `getDetailsHtml()`: The method responsible for rendering the webview's HTML content. All styling is inlined within this method's template string. |
| 111 | +- **`vscode-extension/src/README.md`**: **IMPORTANT**: Contains detailed instructions for updating the JSON data files. Always consult this file when updating tokenEstimators.json or modelPricing.json. It includes structure definitions, update procedures, and current pricing information. |
| 112 | +- **`vscode-extension/src/tokenEstimators.json`**: Character-to-token ratio estimators for different AI models. See `vscode-extension/src/README.md` for update instructions. |
| 113 | +- **`vscode-extension/src/modelPricing.json`**: Model pricing data with input/output costs per million tokens. Includes metadata about pricing sources and last update date. See `vscode-extension/src/README.md` for detailed update instructions and current pricing sources. |
| 114 | +- **`docs/FLUENCY-LEVELS.md`**: Documents the scoring rules for the Copilot Fluency Score dashboard (4 stages, 6 categories, thresholds, and boosters). **Keep this file up to date** when changing the `calculateMaturityScores()` method in `vscode-extension/src/extension.ts`. |
| 115 | +- **`vscode-extension/package.json`**: Defines activation events, commands, and build scripts. |
| 116 | +- **`vscode-extension/esbuild.js`**: The build script that bundles the TypeScript source and JSON data files. Also copies `sql-wasm.wasm` from `node_modules/sql.js/dist/` to `dist/` for OpenCode SQLite support. |
| 117 | +- **`vscode-extension/src/types/json.d.ts`**: Type declarations for JSON module imports and the `sql.js` module. |
| 118 | + |
| 119 | +## Webview Navigation Buttons |
| 120 | + |
| 121 | +To maintain a consistent, VS Code-native look across all webview panels (Details, Chart, Usage Analysis, Diagnostics), use the VS Code Webview UI Toolkit for top-level navigation buttons. |
| 122 | + |
| 123 | +- **Use `vscode-button`**: Prefer the toolkit button component for header navigation controls instead of custom `<button>` elements. Example usage in a webview script: |
| 124 | + |
| 125 | + ```ts |
| 126 | + const { provideVSCodeDesignSystem, vsCodeButton } = await import('@vscode/webview-ui-toolkit'); |
| 127 | + provideVSCodeDesignSystem().register(vsCodeButton()); |
| 128 | + |
| 129 | + // then create buttons in the DOM: |
| 130 | + const btn = document.createElement('vscode-button'); |
| 131 | + btn.id = 'btn-details'; |
| 132 | + btn.textContent = '🤖 Details'; |
| 133 | + btn.setAttribute('appearance', 'primary'); |
| 134 | + ``` |
| 135 | + |
| 136 | +- **Register the toolkit in each webview**: Each webview that uses `vscode-button` should import and register the toolkit in its `bootstrap()` or initialization function before rendering the layout. |
| 137 | + |
| 138 | +- **Wire messages unchanged**: Buttons should `postMessage` the same navigation commands (`showDetails`, `showChart`, `showUsageAnalysis`, `showDiagnostics`, `refresh`) so the extension can reuse existing panels. Do not change the command names. |
| 139 | + |
| 140 | +- **Checklist for PRs touching webviews**: |
| 141 | + - Ensure the toolkit is registered before creating `vscode-button` elements. |
| 142 | + - Keep navigation command names unchanged so `extension.ts` handlers continue to work. |
| 143 | + - Run `npm run compile` and verify TypeScript and ESLint pass. |
| 144 | + - Visually compare the header with the Details and other panels to confirm parity. |
| 145 | + |
| 146 | +## Adding a New Editor / Data Source |
| 147 | + |
| 148 | +When adding support for a new editor or data source, you must wire it into **both** this extension (`vscode-extension/src/`) **and** the CLI (`cli/src/`). See `.github/instructions/cli.instructions.md` for the CLI integration checklist. |
0 commit comments