copilot-usage-studio 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +150 -0
- package/bin/copilot-usage-studio.mjs +10 -0
- package/data/github-copilot-pricing.json +227 -0
- package/dist/copilot-usage-studio/browser/chunk-C6VWIY5S.js +1 -0
- package/dist/copilot-usage-studio/browser/chunk-DLWQO3VR.js +1 -0
- package/dist/copilot-usage-studio/browser/chunk-F6TIG2GE.js +4 -0
- package/dist/copilot-usage-studio/browser/chunk-JIP7ONRZ.js +1 -0
- package/dist/copilot-usage-studio/browser/chunk-RNKEPBEU.js +1 -0
- package/dist/copilot-usage-studio/browser/chunk-Z3XIAKMM.js +1 -0
- package/dist/copilot-usage-studio/browser/index.html +13 -0
- package/dist/copilot-usage-studio/browser/main-C6XOJRSH.js +2 -0
- package/dist/copilot-usage-studio/browser/styles-GZOQ5QIH.css +1 -0
- package/dist/copilot-usage-studio/browser/usage-studio.svg +12 -0
- package/docs/local-deployment.md +202 -0
- package/docs/pricing.md +274 -0
- package/docs/scanner-api.md +109 -0
- package/lib/cli.mjs +173 -0
- package/lib/local-runtime.mjs +231 -0
- package/lib/scanner-api.mjs +12 -0
- package/package.json +86 -0
- package/scripts/pricing-utils.mjs +71 -0
- package/scripts/scan-vscode-sessions.mjs +1632 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*{box-sizing:border-box}html,body{margin:0;min-height:100%;font-family:Aptos,Segoe UI Variable,Segoe UI,Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,sans-serif;font-size:14px;line-height:1.45;background:#090d14;color:#d7dde7;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased}html[data-theme=light],html[data-theme=light] body{background:#f4f7fb;color:#182536}html[data-theme=dark],html[data-theme=dark] body{background:#090d14;color:#d7dde7}button,input,select{font:inherit}.data-refresh{display:grid;justify-items:end;min-width:0}.data-refresh-button{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:32px;border:1px solid var(--line);border-radius:9px;padding:5px 10px;background:var(--control-bg);color:var(--text-strong);font-size:12px;font-weight:760;cursor:pointer;box-shadow:var(--shadow-soft)}.data-refresh-button:hover:not(:disabled){border-color:var(--accent);color:var(--accent)}.data-refresh-button:disabled{cursor:progress;opacity:.72}.refresh-icon{display:grid;place-items:center;width:16px;height:16px;font-size:17px;line-height:1;transform-origin:50% 50%}.refresh-icon.spinning{animation:refresh-spin .8s linear infinite}.data-refresh-status{max-width:190px;overflow:hidden;color:var(--muted-2);font-size:10px;line-height:1.2;text-overflow:ellipsis;white-space:nowrap}.data-refresh.error .data-refresh-status{color:var(--danger-text)}@keyframes refresh-spin{to{transform:rotate(360deg)}}@media(max-width:760px){.data-refresh-status{display:none}}@media(max-width:520px){.data-refresh-button>span:last-child{display:none}.data-refresh-button{padding-inline:5px}}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="mark" x1="13" y1="49" x2="51" y2="15" gradientUnits="userSpaceOnUse">
|
|
4
|
+
<stop stop-color="#27d6b0"/>
|
|
5
|
+
<stop offset="1" stop-color="#7b68ff"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect x="2" y="2" width="60" height="60" rx="15" fill="#0d1520"/>
|
|
9
|
+
<rect x="13" y="34" width="9" height="16" rx="4.5" fill="url(#mark)"/>
|
|
10
|
+
<rect x="27.5" y="24" width="9" height="26" rx="4.5" fill="url(#mark)"/>
|
|
11
|
+
<rect x="42" y="14" width="9" height="36" rx="4.5" fill="url(#mark)"/>
|
|
12
|
+
</svg>
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Local Deployment
|
|
2
|
+
|
|
3
|
+
The app is intended to run locally, near the VS Code data it reads. It should not require a hosted SaaS backend for the core workflow.
|
|
4
|
+
|
|
5
|
+
Current release posture: early local developer preview. The npm package is prepared and locally verified; clone-and-run remains available until version `0.1.0` is published to the registry.
|
|
6
|
+
|
|
7
|
+
Packaging foundation now available: the scanner exposes an in-memory Node API through `lib/scanner-api.mjs`. The existing scan command uses that same API and only adds argument parsing plus JSON persistence. This is the common core for the next local host, an `npx` command, Electron packaging, and a future thin VS Code extension.
|
|
8
|
+
|
|
9
|
+
Supported source today: VS Code GitHub Copilot Chat and Agent local storage. Visual Studio, JetBrains IDEs, Copilot CLI, GitHub.com chat, and GitHub billing exports are outside the current importer scope.
|
|
10
|
+
|
|
11
|
+
Default scan paths:
|
|
12
|
+
|
|
13
|
+
- Windows: `%APPDATA%\Code\User` and `%APPDATA%\Code - Insiders\User`
|
|
14
|
+
- macOS: `~/Library/Application Support/Code/User` and `~/Library/Application Support/Code - Insiders/User`
|
|
15
|
+
- Linux: `~/.config/Code/User` and `~/.config/Code - Insiders/User`
|
|
16
|
+
|
|
17
|
+
SQLite title/metadata enrichment uses Node's `node:sqlite` support. If that is unavailable on a user's local Node version, the scanner should still import debug-log sessions and report enrichment as skipped.
|
|
18
|
+
|
|
19
|
+
## Recommended For Development
|
|
20
|
+
|
|
21
|
+
Use the Angular dev server:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install
|
|
25
|
+
npm start
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then open the local Angular URL, usually:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
http://127.0.0.1:4200/
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Why: this is the fastest loop while the UI and scanner are changing. `npm start` launches the Angular dev server and the local runtime together. The runtime serves the last valid snapshot immediately, performs a background startup scan, and powers the in-app refresh action.
|
|
35
|
+
|
|
36
|
+
## First Public Release Path
|
|
37
|
+
|
|
38
|
+
The npm package is configured for a one-command local launch:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx copilot-usage-studio
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This command will become available after the package owner publishes version `0.1.0` to npm. It starts the packaged production UI and scanner runtime on `http://127.0.0.1:4312/` by default.
|
|
45
|
+
|
|
46
|
+
Useful packaged commands:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx copilot-usage-studio
|
|
50
|
+
npx copilot-usage-studio scan
|
|
51
|
+
npx copilot-usage-studio status
|
|
52
|
+
npx copilot-usage-studio --help
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The CLI requires Node.js 22.5 or newer because optional VS Code `state.vscdb` enrichment uses Node's built-in SQLite support.
|
|
56
|
+
|
|
57
|
+
Before registry publication, use the repository flow:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone <repo-url>
|
|
61
|
+
cd copilot-usage-studio
|
|
62
|
+
npm install
|
|
63
|
+
npm start
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Then open the local Angular URL printed by the dev server.
|
|
67
|
+
|
|
68
|
+
This is acceptable for an early developer preview because the audience is technical, the product is changing quickly, and the scanner needs local VS Code storage access anyway. The release page should be explicit that generated data stays local and may contain prompts, file paths, repository context, and tool results.
|
|
69
|
+
|
|
70
|
+
Before publishing a release post, run:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm run refresh:data
|
|
74
|
+
npm test -- --watch=false
|
|
75
|
+
npm run test:scripts
|
|
76
|
+
npm run build
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Release copy should describe the app as a local Copilot usage debugger for VS Code. The strongest promise is: "VS Code already logs useful usage data locally; this app makes it understandable for developers who do not have billing-console access."
|
|
80
|
+
|
|
81
|
+
### Packaged Data Location
|
|
82
|
+
|
|
83
|
+
The packaged CLI stores its current normalized snapshot outside the npm installation:
|
|
84
|
+
|
|
85
|
+
- Windows: `%LOCALAPPDATA%\Copilot Usage Studio\sessions.json`
|
|
86
|
+
- macOS: `~/Library/Application Support/Copilot Usage Studio/sessions.json`
|
|
87
|
+
- Linux: `${XDG_DATA_HOME:-~/.local/share}/copilot-usage-studio/sessions.json`
|
|
88
|
+
|
|
89
|
+
The npm tarball contains the compiled UI, scanner, pricing table, and runtime code. It must never contain `public/data/sessions.json` or any other developer's imported session data.
|
|
90
|
+
|
|
91
|
+
### Publishing
|
|
92
|
+
|
|
93
|
+
Run the full package gate:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm run release:check
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Inspect the tarball list, confirm no local session data or absolute paths are present, then publish deliberately:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npm publish --access public
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Publishing is an external release action and should not be automated from ordinary development commands.
|
|
106
|
+
|
|
107
|
+
If the browser shows updated markup with stale component styles, stop the dev server and restart it with a cache reset:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npm run start:clean
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This clears Angular's local build cache before starting `ng serve`. Use it after larger component-style moves if the dev server appears to mix old CSS with new templates.
|
|
114
|
+
|
|
115
|
+
## Recommended For Local Use Without Dev Mode
|
|
116
|
+
|
|
117
|
+
Build static files and serve them locally:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm run scan
|
|
121
|
+
npm run verify:data
|
|
122
|
+
npm run build
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Then serve the generated `dist/` output with a small local static server.
|
|
126
|
+
|
|
127
|
+
Why: Angular production output is just static assets plus the generated `public/data/sessions.json` copy. This is a good first packaging target because it avoids an app server and keeps the data model simple.
|
|
128
|
+
|
|
129
|
+
Current build status: `npm run build` passes. There is still an initial bundle budget warning to clean up later; it does not block the local preview release.
|
|
130
|
+
|
|
131
|
+
For day-to-day development, use the in-app **Refresh** action. `npm run refresh:data` remains the explicit command-line scan plus verification path.
|
|
132
|
+
|
|
133
|
+
## Local Runtime
|
|
134
|
+
|
|
135
|
+
The local runtime is available separately:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm run runtime
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
It listens on `http://127.0.0.1:4312/` by default and exposes:
|
|
142
|
+
|
|
143
|
+
- `GET /api/status`: scan state, timestamps, last error, and session count
|
|
144
|
+
- `GET /api/sessions`: the current normalized in-memory `SessionData`
|
|
145
|
+
- `POST /api/scan`: run a scan, persist the new snapshot, and return it
|
|
146
|
+
- `GET /data/sessions.json`: compatibility route backed by the current in-memory snapshot
|
|
147
|
+
|
|
148
|
+
The runtime seeds itself from `public/data/sessions.json` when needed, then keeps its live cache under `tmp/local-runtime/`. This avoids triggering an Angular development rebuild every time the user refreshes data. It serves cached data while a scan is running, and a failed scan does not replace the last valid snapshot.
|
|
149
|
+
|
|
150
|
+
To build the production UI and serve the complete local app with one command:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npm run preview:local
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Then open `http://127.0.0.1:4312/`.
|
|
157
|
+
|
|
158
|
+
## Future Option: Desktop App
|
|
159
|
+
|
|
160
|
+
A desktop wrapper can bundle:
|
|
161
|
+
|
|
162
|
+
- the static Angular UI
|
|
163
|
+
- the scanner command
|
|
164
|
+
- local settings for VS Code paths
|
|
165
|
+
- optional app-owned SQLite later
|
|
166
|
+
|
|
167
|
+
Why: a desktop app would make the project easier for non-frontend developers to run. It also gives a natural place for refresh controls, background scanning, and local file permissions.
|
|
168
|
+
|
|
169
|
+
Recommended first implementation: Electron. The current scanner is Node ESM and uses `node:sqlite`, so Electron can reuse it directly. Tauri remains possible later, but it would require a Node sidecar or a scanner rewrite before it offers a meaningful simplicity advantage.
|
|
170
|
+
|
|
171
|
+
## Single Command
|
|
172
|
+
|
|
173
|
+
A lightweight local CLI now provides:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
copilot-usage-studio scan
|
|
177
|
+
copilot-usage-studio
|
|
178
|
+
copilot-usage-studio scan
|
|
179
|
+
copilot-usage-studio status
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Why: this keeps the project local and scriptable without committing to a desktop shell too early.
|
|
183
|
+
|
|
184
|
+
The shared scanner API, runtime, and npm executable are complete. The same runtime can be embedded by Electron later.
|
|
185
|
+
|
|
186
|
+
## Future Option: VS Code Companion Extension
|
|
187
|
+
|
|
188
|
+
A thin VS Code extension can call the shared scanner from the local extension host and show the existing UI in a webview. It should remain a host, not a fork of the pricing and parsing logic.
|
|
189
|
+
|
|
190
|
+
Why not move the whole product into an extension: keeping the scanner and analysis core editor-independent preserves a path to a desktop app and possible Visual Studio support if Visual Studio exposes equivalent local evidence later.
|
|
191
|
+
|
|
192
|
+
## Later, Not First Release
|
|
193
|
+
|
|
194
|
+
Do not optimize for a hosted SaaS version yet.
|
|
195
|
+
|
|
196
|
+
Reasons:
|
|
197
|
+
|
|
198
|
+
- The strongest data source is on the developer machine under VS Code workspace storage.
|
|
199
|
+
- Local debug logs can contain sensitive prompts, file paths, repo context, and tool results.
|
|
200
|
+
- The product is currently a cost debugger, not an organization billing system.
|
|
201
|
+
|
|
202
|
+
If billing reconciliation becomes important later, it can be added as an optional import without changing the local-first default.
|
package/docs/pricing.md
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# Pricing
|
|
2
|
+
|
|
3
|
+
This app is a local usage inspector. It uses GitHub's published Copilot usage-based model pricing to explain why a local Copilot run looks expensive, but it is not a GitHub invoice.
|
|
4
|
+
|
|
5
|
+
Primary source:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
https://docs.github.com/en/copilot/reference/copilot-billing/models-and-pricing
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
AI credit and allowance source:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
https://docs.github.com/en/copilot/concepts/billing/usage-based-billing-for-organizations-and-enterprises
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Current version:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
github-copilot-usage-pricing-2026-06-14
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The source table says prices are per 1 million tokens. The committed snapshot was checked against GitHub Docs on June 14, 2026.
|
|
24
|
+
|
|
25
|
+
## Where Pricing Lives
|
|
26
|
+
|
|
27
|
+
- Shared rate-card data: `data/github-copilot-pricing.json`
|
|
28
|
+
- UI adapter: `src/app/pricing.ts`
|
|
29
|
+
- Scanner consumer: `scripts/scan-vscode-sessions.mjs`
|
|
30
|
+
- Verifier consumer: `scripts/verify-session-data.mjs`
|
|
31
|
+
- Generated session-data metadata: `public/data/sessions.json`
|
|
32
|
+
|
|
33
|
+
The rate card is a versioned JSON file so the scanner, verifier, and UI use the same pricing rows and fallback model. This avoids a quiet class of bugs where the UI explains one price table while the scanner calculated with another.
|
|
34
|
+
|
|
35
|
+
`src/app/pricing.ts` is intentionally just an Angular-facing adapter around the shared JSON. It adds TypeScript types and the `estimateCostUsd` helper, but it is not the source of truth.
|
|
36
|
+
|
|
37
|
+
## Updating The Price Snapshot
|
|
38
|
+
|
|
39
|
+
This should be a deliberate maintainer update, not a runtime web fetch.
|
|
40
|
+
|
|
41
|
+
1. Open GitHub's official model pricing page and record the check date.
|
|
42
|
+
2. Update `data/github-copilot-pricing.json`, including model availability, all token buckets, and request-size tiers.
|
|
43
|
+
3. Keep tier thresholds machine-readable. Apply them per model call using normal plus cached input, never aggregated session tokens.
|
|
44
|
+
4. Update the pricing version and snapshot date.
|
|
45
|
+
5. Add or update threshold boundary tests and verify that several smaller calls do not accidentally trigger a long-context tier.
|
|
46
|
+
6. Run `npm run refresh:data` to recalculate the local development snapshot.
|
|
47
|
+
7. Run `npm run release:check` and inspect the Prices page in both themes.
|
|
48
|
+
8. Update this document and the changelog when the pricing contract changed.
|
|
49
|
+
|
|
50
|
+
The future automation worth adding is a maintainer-only audit that detects changes on GitHub's page and opens a reviewable diff. It should not silently update rates or alter historical calculations at runtime.
|
|
51
|
+
|
|
52
|
+
## What The App Calculates
|
|
53
|
+
|
|
54
|
+
For each imported model call, the scanner reads local VS Code debug-log token totals when they are available:
|
|
55
|
+
|
|
56
|
+
- input tokens
|
|
57
|
+
- output tokens
|
|
58
|
+
- cached input tokens, only if the source exposes them
|
|
59
|
+
- cache-write tokens, only if the source exposes them
|
|
60
|
+
- model id and pricing row
|
|
61
|
+
|
|
62
|
+
## Calculation
|
|
63
|
+
|
|
64
|
+
For each model row:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
cost_usd =
|
|
68
|
+
input_tokens / 1,000,000 * input_price +
|
|
69
|
+
cached_input_tokens / 1,000,000 * cached_input_price +
|
|
70
|
+
cache_write_tokens / 1,000,000 * cache_write_price +
|
|
71
|
+
output_tokens / 1,000,000 * output_price
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The app displays this estimate in USD. It does not convert to EUR because GitHub's published rate card, AI-credit conversion, and additional-usage budgets are USD-native.
|
|
75
|
+
|
|
76
|
+
When `copilotUsageNanoAiu` is not available, GitHub AI credits are calculated from the USD token estimate:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
ai_credits = cost_usd / 0.01
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
GitHub documents `1 AI credit = $0.01 USD`.
|
|
83
|
+
|
|
84
|
+
### Long-context tiers
|
|
85
|
+
|
|
86
|
+
GitHub publishes a second rate tier for GPT-5.4 and GPT-5.5 requests over 272K input tokens, and for Gemini 3.1 Pro requests over 200K input tokens.
|
|
87
|
+
|
|
88
|
+
The app evaluates that threshold independently for every model call:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
request_input_tokens = normal_input_tokens + cached_input_tokens
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
This reconstructs the raw request input represented by VS Code's `inputTokens` field. A request exactly at the threshold remains on the default row; only a request above it uses the long-context row. The app never applies a long-context price merely because many smaller calls add up to a large session.
|
|
95
|
+
|
|
96
|
+
Each model summary therefore stores:
|
|
97
|
+
|
|
98
|
+
- the pricing tiers encountered across its calls
|
|
99
|
+
- normal-input, cached-input, cache-write, and output cost totals summed from those individually priced calls
|
|
100
|
+
|
|
101
|
+
When a model used more than one tier, the Cost view says `Per-call tiered rates` rather than showing one misleading rate for the whole run.
|
|
102
|
+
|
|
103
|
+
## GitHub Source Usage
|
|
104
|
+
|
|
105
|
+
Newer VS Code Copilot Agent Debug Logs can expose `llm_request.attrs.copilotUsageNanoAiu`.
|
|
106
|
+
|
|
107
|
+
When present, the app treats this as the primary source-usage total for the run:
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
source_ai_credits = copilotUsageNanoAiu / 1,000,000,000
|
|
111
|
+
source_usd = source_ai_credits * 0.01
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The token-bucket calculation remains the explanation layer and fallback. In other words:
|
|
115
|
+
|
|
116
|
+
- **GitHub source usage** answers "what usage did Copilot report locally?" This is the primary run total when present.
|
|
117
|
+
- **Token buckets** answer "why did that usage happen?"
|
|
118
|
+
|
|
119
|
+
The verifier compares source usage with the token-bucket estimate when `copilotUsageNanoAiu` is present. The main UI should stay simple: show source usage when it exists, and only call out fallback when source usage is absent. Reconciliation details belong in diagnostics or docs unless a divergence would change a user decision.
|
|
120
|
+
|
|
121
|
+
## Included AI Credit Allowances
|
|
122
|
+
|
|
123
|
+
The app also shows license allowance context for Copilot Business and Copilot Enterprise.
|
|
124
|
+
|
|
125
|
+
Standard included amounts:
|
|
126
|
+
|
|
127
|
+
| Plan | AI credits per user per month |
|
|
128
|
+
| ------------------ | ----------------------------: |
|
|
129
|
+
| Copilot Business | 1,900 |
|
|
130
|
+
| Copilot Enterprise | 3,900 |
|
|
131
|
+
|
|
132
|
+
Temporary promotional amounts documented by GitHub for existing customers from June 1 to September 1, 2026:
|
|
133
|
+
|
|
134
|
+
| Plan | AI credits per user per month |
|
|
135
|
+
| ------------------ | ----------------------------: |
|
|
136
|
+
| Copilot Business | 3,000 |
|
|
137
|
+
| Copilot Enterprise | 7,000 |
|
|
138
|
+
|
|
139
|
+
The UI treats these as allowance context, not as billing reconciliation. GitHub pools Business and Enterprise included credits at the billing entity level, so a run's percent-of-allowance is a per-seat mental model unless the app later adds organization seat counts.
|
|
140
|
+
|
|
141
|
+
For example, a 100-seat Copilot Business organization has a shared standard pool of 190,000 credits, not 100 isolated 1,900-credit buckets.
|
|
142
|
+
|
|
143
|
+
## What The UI Shows
|
|
144
|
+
|
|
145
|
+
The selected run header shows:
|
|
146
|
+
|
|
147
|
+
- GitHub source usage when available, otherwise estimated USD cost
|
|
148
|
+
- GitHub source credits when available, otherwise estimated AI credits
|
|
149
|
+
- selected allowance plan
|
|
150
|
+
- percent of that plan's per-user monthly allowance
|
|
151
|
+
|
|
152
|
+
The Prices page shows:
|
|
153
|
+
|
|
154
|
+
- the GitHub model price source used by the app
|
|
155
|
+
- the imported pricing version
|
|
156
|
+
- the Business and Enterprise allowance options
|
|
157
|
+
- the fixed AI credit conversion
|
|
158
|
+
- how the currently imported sessions compare with the selected allowance
|
|
159
|
+
|
|
160
|
+
This is useful for a quick mental model: "this single run would consume about X% of one Business user's monthly included credits."
|
|
161
|
+
|
|
162
|
+
## Why The GitHub Prices Page Exists
|
|
163
|
+
|
|
164
|
+
The user should be able to inspect the cost inputs directly. If a session looks expensive, the UI should make it clear whether that came from:
|
|
165
|
+
|
|
166
|
+
- high input tokens
|
|
167
|
+
- high output tokens
|
|
168
|
+
- a more expensive model
|
|
169
|
+
- cached input or cache-write accounting
|
|
170
|
+
- a model fallback because the raw model id was not in the known GitHub table
|
|
171
|
+
|
|
172
|
+
The `GitHub prices` view therefore shows every rate row the app knows about, where it came from, and whether any imported session currently uses it.
|
|
173
|
+
|
|
174
|
+
The same view also shows the selected Business/Enterprise AI-credit allowance and how the current imported sessions compare with it.
|
|
175
|
+
|
|
176
|
+
## How Realistic Is The Forecast?
|
|
177
|
+
|
|
178
|
+
Good for:
|
|
179
|
+
|
|
180
|
+
- using VS Code's `copilotUsageNanoAiu` source usage as the primary local usage total when that field is present
|
|
181
|
+
- reconciling source usage against app-calculated token pricing so token buckets still explain the total
|
|
182
|
+
- finding which model calls drove a run's local cost estimate
|
|
183
|
+
- comparing two runs under the same local assumptions
|
|
184
|
+
- spotting whether cost came mostly from input/context or output
|
|
185
|
+
- estimating AI credit consumption from visible local token totals, including `cachedTokens` when Agent Debug Logs expose them
|
|
186
|
+
- understanding whether a run is tiny, normal, or a large share of a monthly allowance
|
|
187
|
+
- spotting source-backed request payload clues such as large system prompts, large tool schemas, MCP tool presence, tool-result payload size, cached input totals, and request reasoning effort
|
|
188
|
+
|
|
189
|
+
Not invoice-grade for:
|
|
190
|
+
|
|
191
|
+
- exact GitHub invoice reconciliation
|
|
192
|
+
- provider-side cached input or cache-write when the generated session data does not include numeric cache-token fields
|
|
193
|
+
- billing adjustments, promotions, policy effects, or later GitHub pricing changes
|
|
194
|
+
- exact attribution of input tokens to instructions, MCP servers, workspace context, or tool results unless the source logs expose those sections directly
|
|
195
|
+
|
|
196
|
+
## Cache Reality
|
|
197
|
+
|
|
198
|
+
The scanner imports input, output, and cached-input token totals from VS Code Agent Debug Log `llm_request` events when those fields are present. In the observed VS Code Agent Debug Log shape, `attrs.cachedTokens` is present on many model calls and is the key field for cached input.
|
|
199
|
+
|
|
200
|
+
## Evidence For The Token Buckets
|
|
201
|
+
|
|
202
|
+
GitHub's Copilot pricing docs describe three token categories for usage-based billing: input tokens, output tokens, and cached tokens. The same page defines input as what is sent to the model, output as what the model generates, and cached tokens as context the model reuses or stores.
|
|
203
|
+
|
|
204
|
+
The GitHub Copilot model pricing table is also bucketed by rate. For OpenAI, Google, Microsoft, and GitHub fine-tuned models, the table has separate `Input`, `Cached input`, and `Output` columns. For Anthropic models, GitHub documents an additional `Cache write` column.
|
|
205
|
+
|
|
206
|
+
Those docs support this pricing shape:
|
|
207
|
+
|
|
208
|
+
```text
|
|
209
|
+
cost =
|
|
210
|
+
normal_input_tokens * input_rate +
|
|
211
|
+
cached_input_tokens * cached_input_rate +
|
|
212
|
+
cache_write_tokens * cache_write_rate +
|
|
213
|
+
output_tokens * output_rate
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The local VS Code Agent Debug Log field names are not a public GitHub billing API contract. They are observed local debug-log fields. In the sessions inspected so far, `attrs.inputTokens` is the raw input/context total sent to the model, `attrs.cachedTokens` is the cached portion of that input, and `attrs.outputTokens` is generated output.
|
|
217
|
+
|
|
218
|
+
Because GitHub prices cached input separately, the app treats the observed local fields like this:
|
|
219
|
+
|
|
220
|
+
```text
|
|
221
|
+
raw_input_tokens = attrs.inputTokens
|
|
222
|
+
cached_input_tokens = attrs.cachedTokens
|
|
223
|
+
normal_input_tokens = max(0, attrs.inputTokens - attrs.cachedTokens)
|
|
224
|
+
output_tokens = attrs.outputTokens
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
This avoids double-counting. If the app priced all `inputTokens` at the normal input rate and also priced `cachedTokens` at the cached-input rate, the cached portion would be counted twice.
|
|
228
|
+
|
|
229
|
+
Confidence level:
|
|
230
|
+
|
|
231
|
+
- High confidence that GitHub bills Copilot usage with distinct input, cached-input, cache-write, and output buckets where those rate columns exist.
|
|
232
|
+
- High confidence that the observed VS Code debug-log `cachedTokens` field should be treated as cached input, because the name and values match GitHub's cached-token billing category.
|
|
233
|
+
- Medium confidence that `cachedTokens` is always a subset of `inputTokens` for this local debug-log schema. The scanner preserves pricing safety by clamping impossible splits, emits a warning if it ever sees one, and the verifier fails generated data where `cachedInputTokens > inputTokens`. This remains an observed VS Code debug-log convention rather than a separately documented public API.
|
|
234
|
+
- Low confidence in any claim that a missing `cachedTokens` field means no provider-side caching occurred. Missing local fields mean "not visible locally", not "zero on GitHub's invoice".
|
|
235
|
+
|
|
236
|
+
The generated data now carries an ingestion-level cache audit. Run:
|
|
237
|
+
|
|
238
|
+
```text
|
|
239
|
+
npm run scan
|
|
240
|
+
npm run verify:data
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The verifier prints a line like:
|
|
244
|
+
|
|
245
|
+
```text
|
|
246
|
+
Cache split audit: 23/116 model calls include cachedTokens; 0 invalid cached/input splits; 3,272,520 normal input + 713,970 cached input from 3,986,490 raw inputTokens.
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
This audit does not make VS Code's debug-log schema a public billing API, but it proves the local data imported into the app obeys the relationship the pricing math depends on:
|
|
250
|
+
|
|
251
|
+
```text
|
|
252
|
+
raw_input_tokens = normal_input_tokens + cached_input_tokens
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
That means:
|
|
256
|
+
|
|
257
|
+
- `attrs.cachedTokens` is treated as cached input and priced with GitHub's cached-input rate for the model. The normal input bucket is `inputTokens - cachedTokens`.
|
|
258
|
+
- The app keeps raw `inputTokens` on trace events so the original VS Code number remains visible, but session/model pricing uses `inputTokens - cachedTokens` for normal input to avoid double-counting.
|
|
259
|
+
- UI cost views must keep normal input, cached input, cache write, and output separate. Cached input is not merged back into normal input for pricing, even though raw `inputTokens` can be useful when debugging how much context was sent to the model.
|
|
260
|
+
- When `cachedInput` and `cacheWrite` are zero in a debug-log import, that means no numeric cache-token totals were imported for that run. It should not be presented as proof that provider-side cache billing was zero.
|
|
261
|
+
- If Agent Debug Logs expose additional numeric cached-token fields for a model call, ingestion should preserve those exact fields and the Cost view should price them separately.
|
|
262
|
+
- `cache_control` hints or prompt-cache metadata can explain that caching was requested or used, but they are not enough on their own to calculate billable cached-token totals.
|
|
263
|
+
- Cached input is not a discount against output. It is a separate input/context bucket when a billing source exposes it. Output tokens remain priced as output tokens.
|
|
264
|
+
- Input-heavy sessions may be overestimated if GitHub billed a large portion of input as cheaper cached input.
|
|
265
|
+
- Output-heavy sessions are usually easier to reason about because output remains priced as output.
|
|
266
|
+
- The Cost view keeps normal input, cached input, cache write, and output as separate priced categories. If cached-token fields are missing for a run, the app does not infer them.
|
|
267
|
+
|
|
268
|
+
## Current Limitations
|
|
269
|
+
|
|
270
|
+
- GitHub billing can still differ because GitHub may apply provider-side cache accounting or billing adjustments not present in local logs.
|
|
271
|
+
- Unknown model ids are preserved for display and priced with a visible fallback until the pricing table is updated.
|
|
272
|
+
- Request payload sizes are optimization evidence, not exact cost allocation. The app can show that a tools file was large or that MCP tools were present, but it should not say "this MCP server cost $X" unless source logs expose section-level token totals.
|
|
273
|
+
- The pricing table should be rechecked against GitHub Docs whenever GitHub changes model availability or usage-based rates.
|
|
274
|
+
- Pricing is intentionally a versioned, committed snapshot rather than a runtime web fetch. This keeps scans reproducible and prevents an unavailable or changed web page from silently altering local historical calculations.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Shared Scanner API
|
|
2
|
+
|
|
3
|
+
The scanner is now reusable Node infrastructure rather than only a command that writes `public/data/sessions.json`.
|
|
4
|
+
|
|
5
|
+
## Stable Boundary
|
|
6
|
+
|
|
7
|
+
Import the host API from:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
lib/scanner-api.mjs
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
It exposes:
|
|
14
|
+
|
|
15
|
+
- `scanVsCodeSessions(options)`
|
|
16
|
+
- `writeSessionData(sessionData, outputFile)`
|
|
17
|
+
- `defaultCodeUserDirs()`
|
|
18
|
+
|
|
19
|
+
Host code should use this module instead of importing individual parser helpers from `scripts/scan-vscode-sessions.mjs`. Parser functions remain implementation details and can evolve with the observed VS Code schema.
|
|
20
|
+
|
|
21
|
+
## Scan Without Writing
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
import { scanVsCodeSessions } from './lib/scanner-api.mjs';
|
|
25
|
+
|
|
26
|
+
const sessionData = await scanVsCodeSessions();
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The result is the normalized `SessionData` document consumed by the Angular UI. No file is created or changed.
|
|
30
|
+
|
|
31
|
+
This is the intended path for:
|
|
32
|
+
|
|
33
|
+
- a local HTTP runtime
|
|
34
|
+
- an `npx` launcher
|
|
35
|
+
- an Electron main process
|
|
36
|
+
- a VS Code extension host
|
|
37
|
+
- tests and schema compatibility checks
|
|
38
|
+
|
|
39
|
+
## Options
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
const sessionData = await scanVsCodeSessions({
|
|
43
|
+
roots: ['/custom/Code/User'],
|
|
44
|
+
sqlite: true,
|
|
45
|
+
generatedAt: new Date(),
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- `roots`: optional array of VS Code `User` directories or individual workspace-storage directories. Defaults to stable VS Code and VS Code Insiders paths for the current operating system.
|
|
50
|
+
- `sqlite`: set to `false` to skip optional `state.vscdb` metadata enrichment. Debug-log token and usage import still works.
|
|
51
|
+
- `generatedAt`: optional `Date` or date-compatible value, primarily for deterministic hosts and tests.
|
|
52
|
+
- `usdToEur`: legacy generated-contract conversion value. The product UI is USD-first and callers should normally leave this unset.
|
|
53
|
+
|
|
54
|
+
Duplicate roots are normalized and scanned once. Each call receives fresh ingestion diagnostics; counters and warnings do not leak from earlier scans in a long-running process.
|
|
55
|
+
|
|
56
|
+
Only one scan may run at a time within one Node process. Hosts should debounce filesystem events and await the current scan instead of starting overlapping work.
|
|
57
|
+
|
|
58
|
+
## Local Runtime Host
|
|
59
|
+
|
|
60
|
+
`lib/local-runtime.mjs` is the first shared API host. It keeps the last valid `SessionData` snapshot in memory, serves it while scans run, and persists successful refreshes.
|
|
61
|
+
|
|
62
|
+
The runtime intentionally keeps scanner and transport concerns separate:
|
|
63
|
+
|
|
64
|
+
- the scanner knows how to read and normalize VS Code data
|
|
65
|
+
- the runtime owns cache lifetime, HTTP status, refresh coordination, and static UI delivery
|
|
66
|
+
- the Angular app only consumes normalized session data
|
|
67
|
+
|
|
68
|
+
This same split can be reused by Electron and a thin VS Code extension without copying parsing or pricing logic.
|
|
69
|
+
|
|
70
|
+
## Explicit Persistence
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
import { scanVsCodeSessions, writeSessionData } from './lib/scanner-api.mjs';
|
|
74
|
+
|
|
75
|
+
const sessionData = await scanVsCodeSessions();
|
|
76
|
+
const outputFile = writeSessionData(sessionData, 'public/data/sessions.json');
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Persistence is separate on purpose. A desktop or extension host can return the in-memory document directly, while the current static Angular workflow can keep using the generated JSON file.
|
|
80
|
+
|
|
81
|
+
## Existing CLI
|
|
82
|
+
|
|
83
|
+
The existing command remains compatible:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
node scripts/scan-vscode-sessions.mjs public/data/sessions.json <optional-root> [...more-roots]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
It now performs only three host tasks:
|
|
90
|
+
|
|
91
|
+
1. parse positional arguments
|
|
92
|
+
2. call `scanVsCodeSessions()`
|
|
93
|
+
3. call `writeSessionData()` and report the result
|
|
94
|
+
|
|
95
|
+
There is one parser and pricing implementation. Future hosts should not copy scanner logic.
|
|
96
|
+
|
|
97
|
+
## Privacy And Source Rules
|
|
98
|
+
|
|
99
|
+
The API reads local VS Code Copilot storage and can encounter prompts, file paths, repository context, tool schemas, and tool results. Hosts must keep results local unless a user explicitly exports them.
|
|
100
|
+
|
|
101
|
+
The API does not change evidence rules:
|
|
102
|
+
|
|
103
|
+
- source usage is preferred when VS Code logs it
|
|
104
|
+
- Agent Debug Log token fields drive token-bucket pricing
|
|
105
|
+
- cached input remains separate from normal input and output
|
|
106
|
+
- chat snapshots remain fallback data
|
|
107
|
+
- SQLite is metadata enrichment, not a billing source
|
|
108
|
+
|
|
109
|
+
See [data-ingestion.md](data-ingestion.md), [debug-log-schema.md](debug-log-schema.md), and [pricing.md](pricing.md) for those contracts.
|