AI Token Monitor
A VS Code extension that shows token usage, model, duration, and cost for every AI request, and shows where tokens are going to waste.
The one design rule
Nothing is estimated. A value the provider did not report renders as Unavailable, never as 0, a blank, or a number derived from a tokenizer. This applies all the way through:
- A model with no verified pricing gets no cost, not a guess from a similar model.
- A request whose cache reads can't be priced gets no cost at all, rather than a partial total that looks complete but is always too low.
- A context gauge is only drawn when the model's real window size is known.
- Summary cost says how many requests it actually covers when some were unpriced, so the figure is never mistaken for total spend.
- CSV exports leave unreported values as empty cells, so a spreadsheet
SUM can't silently count absent data as zero.
The cost figure is meant to be auditable. Hover any cost to see whether it came from the built-in verified table or your own configured rates.
Recording usage
Four ways in, all converging on the same pricing logic.
On startup the extension detects AI tools installed on this machine and tracks them with no setup. Today that means Claude Code: it reads the JSONL transcripts Claude Code already writes under ~/.claude/projects/, so requests appear as you make them.
Run AI Tokens: Show Detected Tools to see what was found and where it's reading from.
This is read-only and local:
- Nothing is written to, configured, or sent to the monitored tool.
- Only
message.usage and a few fields around it are read. Prompt and response text is never read, so nothing you typed can reach the history or an export — there's a test asserting exactly that.
- Costs are recomputed from the verified pricing table rather than taken from the transcript, so every number in the UI comes from one set of rules.
| Setting |
Default |
Does |
sources.enabled |
true |
Master switch for automatic tracking |
sources.disabled |
[] |
Source ids to skip, e.g. ["claude-code"] |
sources.backfillDays |
1 |
Days of existing history to import on start; 0 means new requests only |
Requests are deduplicated on the provider's own request id, so restarts, settings changes, and resumed sessions never double-count spend.
Adding another tool means writing one UsageSource in src/sources/ — the store, pricing, and views don't change.
2. The ingest endpoint
A loopback HTTP endpoint for your own tooling — a LiteLLM callback, a proxy, a test harness.
Enable aiUsageMonitor.ingest.enabled, then run AI Tokens: Copy Ingest Endpoint Snippet for a ready-to-paste curl with the current token.
curl -sS -X POST http://127.0.0.1:7337/usage \
-H "authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
-d '{"durationMs": 1234, "response": { ...raw provider response... }}'
Post either a raw provider response, or an envelope adding what the response itself doesn't carry:
| Envelope field |
Purpose |
response |
The raw provider response. Omit the envelope to post one directly. |
durationMs |
Wall-clock duration you measured. Providers don't report this. |
provider |
The real provider, when the call went through a proxy. |
Security posture, since this opens a socket:
- Bound to
127.0.0.1 explicitly — unreachable from off-host, even on an open network.
- Off unless you enable it.
- Per-session bearer token, compared in constant time, so other local processes can't inject records. The token is regenerated on every restart — re-copy the snippet after a window reload.
- 256 KB body cap.
- Only usage metadata is read. Prompts and completions are never parsed or stored.
3. From another extension
const api = vscode.extensions.getExtension("wadizaatour.ai-token-monitor")?.exports;
api?.record(response, { durationMs: 1234, provider: "anthropic" });
record(payload, meta?) parses a raw provider response and returns the stored record, or undefined if no adapter recognized it. recordUsage(record) takes an already-built record; history() returns everything held.
4. Supported payload shapes
| Adapter |
Recognizes |
anthropic |
Messages API responses (input_tokens, cache_read_input_tokens, per-TTL cache_creation breakdowns) |
openai |
Chat Completions and Responses (prompt_tokens/input_tokens, cached and reasoning token details) |
generic |
A flat documented envelope — the fallback when you control the caller |
One normalization worth knowing about, because getting it wrong double-counts the bill: inputTokens is always the input billed at the full rate, exclusive of cached tokens. Anthropic already reports it that way; OpenAI's prompt_tokens is inclusive, so that adapter subtracts. If you post to the generic shape, subtract cached tokens yourself.
Adding a provider means writing one ProviderAdapter and registering it in src/adapters/index.ts — nothing else changes.
Spending fewer tokens
The same extension also shows where tokens are wasted: while you write a prompt, after you send one in Claude Code, and across your recorded usage.
While you write: the prompt optimizer
Prompt files are analyzed as you type. By default that means CLAUDE.md, CLAUDE.local.md, *.prompt.md, and the markdown under .claude/commands, .claude/agents and .claude/skills. AI Tokens: New Prompt opens a scratch prompt; AI Tokens: Analyze Current File as a Prompt opts in any other file.
Findings appear as ordinary editor UI:
- Faded text for words that can simply go ("Please", "I would like you to", "Thanks in advance!"), the same treatment VS Code gives unused code.
- Squiggles for bigger waste: a 40-frame stack trace that is mostly
node_modules, terminal color codes in pasted output, a base64 blob, a block pasted twice.
- A CodeLens at the top: apply all safe fixes, review suggestions, optimize with Claude, copy the optimized prompt, count tokens.
- A status bar item with the prompt's size and how much the safe fixes remove.
Fixes come in two kinds, and the difference matters:
|
What |
Applied |
| Safe |
Change wording, never meaning: filler, wordy phrases (in order to → to), extra whitespace, decorative dividers, terminal escapes, tracking parameters, repeated log lines |
Together — Apply Safe Token Fixes, the lightbulb, or on save |
| Suggestions |
Drop information or need judgment: trimming a stack trace to your own frames, removing a duplicated block or encoded blob, minifying JSON, asking for a whole file back (output costs 5× input) |
One at a time, by you |
To clean prompt files on every save:
"[markdown]": { "editor.codeActionsOnSave": { "source.fixAll.aiTokens": "explicit" } }
CLAUDE.md is labeled Loaded into every Claude Code request: it is sent on every turn, so a character saved there is saved on every request.
Optimize with Claude
For what rules can't do, like restating a rambling request in three lines, AI Tokens: Optimize Prompt with Claude sends the prompt (or the selection) to Claude and shows the rewrite as a diff with before/after token counts. Apply it, copy it, or close it.
- Safe fixes are applied locally first, so the rewrite call itself pays for fewer input tokens.
- It runs at low effort, and the call is recorded in the Requests view like any other request. The optimizer doesn't get to hide what it spends.
- The model is
optimizer.model, claude-opus-5-5 by default. claude-haiku-5-5 costs a small fraction of that per rewrite.
In Claude Code: token tips
AI Tokens: Show Token Tips in Claude Code installs a UserPromptSubmit hook. On each prompt you send, it runs the same rules locally and, when there is obvious waste, shows a short note under the prompt:
AI Tokens — this prompt could have been leaner:
• Stack trace with 42 frames, 31 of them in libraries or the runtime. Claude usually needs the error message and the frames in your own code.
• Mechanical cleanups (filler phrases, extra whitespace) would remove 412 characters (17%).
What it deliberately doesn't do:
- Rewrite the prompt. Claude Code gives hooks no way to.
- Add to Claude's context. It uses
systemMessage, which is shown to you and never sent to the model. A tip that cost tokens would defeat the point.
- Block. The same hook fires for
/loop iterations and background agents; blocking those would break Claude Code.
- Nag. It stays quiet unless something is worth acting on. A lone "please" doesn't qualify.
Installing writes one entry to ~/.claude/settings.json, keeping everything else intact and refusing to touch a file it can't parse, and copies the script to ~/.claude/hooks/. AI Tokens: Remove Token Tips from Claude Code removes both. Requires node on your PATH.
From your usage: tips to spend less
The Requests view gains a Tips to spend less section, computed from token counts already recorded. No prompt text is involved.
| Tip |
Appears when |
| Context size |
The current session sends 100k+ tokens per turn: /compact or /clear |
| Cache rebuilds |
Long conversations were re-cached instead of read (resumed after a break, a model switch) |
| Output share |
Output is most of your spend: ask for diffs, not whole files |
| Uncached input |
Most input bills at the full rate: caching isn't taking effect |
| Model choice |
Many short turns on Opus or Fable, and what the same tokens cost at Sonnet 5.5 rates |
Dollar figures follow the design rule: reported tokens priced at verified rates, or no figure. "Would cost" is the same tokens recomputed at another model's verified rates, not a forecast.
Characters, tokens, and the design rule
Token counts are model-specific, and only the provider's counting endpoint gives a real one. So the optimizer measures in characters, which are exact, and shows tokens only when Anthropic counted them: on demand with AI Tokens: Count Prompt Tokens Exactly, or as you type with optimizer.exactTokenCounts. Both use Anthropic's free count_tokens endpoint against the model you actually use, by default the model of your latest Claude Code request.
What leaves your machine
Rule analysis is local, in the editor and in the hook. Prompt text goes to the Anthropic API only when you ask for a token count or a Claude rewrite (confirmed the first time), or when you turn on optimizer.exactTokenCounts.
The API key is stored in VS Code's secret storage via AI Tokens: Set Anthropic API Key. Without one, the SDK's usual sources apply: ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, or an ant auth login profile. If ANTHROPIC_BASE_URL points at a proxy such as LiteLLM, requests go there, without the beta parameters a proxy might reject.
| Setting |
Default |
Does |
optimizer.enabled |
true |
Analyze prompt files as you type |
optimizer.include |
CLAUDE.md, *.prompt.md, .claude/**/*.md, … |
Files treated as prompts |
optimizer.disabledRules |
[] |
Rule ids to skip, e.g. ["filler"] |
optimizer.model |
claude-opus-5-5 |
Model for Claude rewrites |
optimizer.tokenCountModel |
auto |
Tokenizer for exact counts; auto follows your latest Claude Code model |
optimizer.exactTokenCounts |
false |
Count tokens live (sends prompt text to the API) |
Pricing
The built-in table covers current Anthropic first-party models, each row carrying the date it was reconciled with published pricing.
Bedrock and Vertex deployments resolve to no built-in price by design. They're partner-operated and billed separately, so first-party rates would be confidently wrong. Configure your actual rates instead — user pricing always wins, including over the built-in table:
"aiUsageMonitor.pricing.custom": {
"anthropic.claude-opus-5": { "input": 6, "output": 30 },
"my-finetune": { "input": 3, "output": 15, "cachedInput": 0.3 }
}
Rates are USD per 1M tokens. A rate you omit is treated as unverified, not as zero.
Claude Haiku 5.5 has no built-in row on purpose: its rates step up for prompts over 100k tokens, which a flat row can't represent. Add it under pricing.custom if you want a figure anyway.
Commands
| Command |
Does |
AI Tokens: Show Usage Panel |
Focus the Requests view |
AI Tokens: Show Detected Tools |
List installed tools and whether they're tracked |
AI Tokens: Clear History |
Delete all records (confirms first) |
AI Tokens: Export History as JSON / as CSV |
Write history to a file |
AI Tokens: Copy Ingest Endpoint Snippet |
Copy a curl with the live token |
AI Tokens: Show Log |
Open the diagnostics channel |
AI Tokens: New Prompt |
Open a scratch prompt with live token analysis |
AI Tokens: Analyze Current File as a Prompt |
Opt the current file in or out |
AI Tokens: Apply Safe Token Fixes |
Apply every meaning-preserving fix |
AI Tokens: Show Token Suggestions |
List the findings that need your judgment |
AI Tokens: Optimize Prompt with Claude |
Rewrite with Claude and review as a diff |
AI Tokens: Copy Optimized Prompt |
Copy with safe fixes applied, ready to paste into Claude Code |
AI Tokens: Count Prompt Tokens Exactly |
Count via Anthropic's free counting endpoint |
AI Tokens: Set Anthropic API Key / Clear … |
Manage the key in secret storage |
AI Tokens: Show Token Tips in Claude Code / Remove … |
Install or remove the prompt hook |
Running it
npm install
npm run build
Then open this folder in VS Code and press F5 ("Run Extension"). A second VS Code window opens with the extension loaded — look for the AI Tokens icon in the activity bar.
The window it opens is a sandbox: the extension is active only there, not in your main editor. Edits rebuild automatically (F5 starts esbuild in watch mode), so after changing code just press Ctrl+R in that window to reload.
Seeing it do something
If Claude Code is installed, the view populates on its own — the last day of requests appears at startup, and new ones show up as you make them. Use Claude Code in any terminal and watch the Requests view.
Nothing showing up? Run AI Tokens: Show Detected Tools; it reports what was found, or Not installed. AI Tokens: Show Log has the detection diagnostics.
To feed it by hand instead:
- In the dev window, turn on
aiUsageMonitor.ingest.enabled.
- Run AI Tokens: Copy Ingest Endpoint Snippet (
Ctrl+Shift+P).
- Paste it into any terminal and run it.
Worth trying either way: change the model to a name that isn't in the pricing table. Cost shows Unavailable rather than a plausible guess — the design rule in action.
Without the debugger
code --extensionDevelopmentPath="$PWD" .
To install it into your real editor, package it with vsce:
npx @vscode/vsce package # produces ai-usage-monitor-0.1.0.vsix
code --install-extension ai-usage-monitor-0.1.0.vsix
Development
npm run verify # typecheck, lint, test, build — run this before committing
npm run watch # esbuild in watch mode (F5 starts this for you)
npm test # node:test over compiled output
The core logic — pricing, cost, adapters, store, formatting, and the optimizer's rules, insights and hook — has no vscode imports, so the whole suite runs under node --test without an extension host. Debug Tests in the debugger dropdown steps through it with breakpoints.