LLM Spend Tracker - VS Code Extension
Track and visualize your LLM (Large Language Model) expenditure directly in VS Code.
Features
- Status Bar Integration: Quick glance at current month's LLM spend
- Rich Sidebar Dashboard: Comprehensive spend visualization
- Multiple Views: Break down spend by model, project, and time period
- Budget Tracking: Monitor usage against budget limits with visual warnings
- Available Models: List the models your API key can use, served through the spend API
- GitHub Copilot Configs: Click a model to copy a ready-to-paste BYOK configuration
- Auto-Refresh: Keep data current with configurable refresh intervals
Requirements
- VS Code 1.85.0 or higher
- LLM Spend API backend running (see backend/README.md)
- LiteLLM API key
Installation
Build the extension:
cd extension
npm install
npm run package
Install the VSIX file:
- Open VS Code
- Go to Extensions (Ctrl+Shift+X)
- Click "..." menu → "Install from VSIX..."
- Select the generated
.vsix file
Configuration
Configure the extension in VS Code settings:
llmSpend.apiBaseUrl: Base URL of the LLM Spend API. This is the single endpoint for every request — spend data, budget, model list, and Copilot configurations.
llmSpend.refreshInterval: Auto-refresh interval in seconds (default: 300, 0 to disable)
llmSpend.defaultPeriod: Default time period (today/week/month, default: month)
llmSpend.statusBarFormat: Status bar display format (default: both)
amount — shows only the dollar amount (e.g. $12.34)
percentage — shows only the budget percentage (e.g. 45.2%)
both — shows amount and percentage (e.g. $12.34 / 45.2%)
llmSpend.copilotProviderName: Provider group name used in copied Copilot configurations (default: frs). VS Code groups providers by this name, so it must match your existing group in .vscode/chatLanguageModels.json or a copied block creates a second group in the model picker instead of merging.
Views
The LLM Spend activity bar contains three views:
| View |
Purpose |
| Spend Dashboard |
Webview summary: totals, budget state, per-model and per-project spend |
| Spend Breakdown |
Collapsible tree of summary, models, projects and daily spend |
| Settings |
Configuration-oriented view. Currently exposes Available Models |
Available Models and GitHub Copilot
The Settings → Available Models section lists the models the current API key may use.
The request goes to llmSpend.apiBaseUrl — the spend API proxies /v1/* to the LiteLLM
proxy, so the list reflects exactly what the key is entitled to (restricted keys see only
their allow-list). The extension never needs a LiteLLM host of its own; the proxy is
cluster-internal and could not be reached from a developer machine anyway.
flowchart LR
ext[VS Code Extension]
copilot[VS Code Chat - BYOK]
api[Spend API - apiBaseUrl]
litellm[LiteLLM Proxy]
ext -->|GET /v1/models| api
ext -->|POST /me/models/copilot-configuration| api
copilot -->|chat completions| api
api -->|proxy /v1/*| litellm
api -->|"/v1/model/info (master key)"| litellm
Clicking a model opens a menu with ready-to-paste GitHub Copilot configurations:
| Option | Use when |
| -------- | ---------- |
| Copy Copilot configuration | You have no LiteLLM provider configured yet — copies a full customendpoint provider entry |
| Copy model entry only | You already have a provider — copies just the model object to add to its models array |
| Copy configuration for all available models | First-time setup — one provider entry covering every model that has a configuration |
| Copy model ID | You need the identifier to send to the proxy |
| Copy endpoint URL | You need the resolved Chat Completions endpoint |
Clicking the "Available Models" section header skips the menu and copies a single provider entry covering every model directly — the fastest path for a first-time setup.
The payload targets .vscode/chatLanguageModels.json:
[
{
"name": "frs",
"vendor": "customendpoint",
"apiType": "chat-completions",
"models": [
{
"id": "minimax-m3",
"name": "minimax-m3",
"url": "https://spend-tracker.example.com/v1/chat/completions",
"apiType": "chat-completions",
"toolCalling": true,
"vision": true,
"thinking": true,
"contextWindow": 1048576,
"maxOutputTokens": 262144,
"modelOptions": { "temperature": 0.1, "top_p": 0.95 }
}
]
}
]
The url comes from the authored copilot_configuration — it names the host that
actually serves the model, so chat traffic goes to the model gateway rather than the spend
API. A model with no configuration has no url at all.
The extension copies, it never writes. VS Code owns chatLanguageModels.json and
rewrites it from the Language Models editor, so merging is left to the user. This keeps
the extension clear of a schema it does not control.
Where the configuration comes from
Capability and behaviour fields (toolCalling, vision, thinking, contextWindow,
maxOutputTokens, modelOptions, supportsReasoningEffort, …) are authored centrally in
LiteLLM under model_info.copilot_configuration, and read back through
POST /me/models/copilot-configuration.
The backend performs that read with the LiteLLM master key, which matters: a virtual
key only sees the models it can list itself, so models granted through an access group
would otherwise be invisible.
The extension derives id and name locally and never trusts the authored values. A
copied block in the proxy config may still carry another model's identity, so honouring it
would produce a picker entry pointing at the wrong model. Everything else — including
url — is passed through verbatim, because those keys belong to VS Code's schema.
Provider name is llmSpend.copilotProviderName (default frs). VS Code groups
providers by this name, so it must match your existing group or a copied block creates a
second group in the model picker instead of merging.
No API key is embedded. The key is per-developer and VS Code injects it via
${input:...}; copied blocks omit it entirely.
Models without a configuration are still included. A model that has no
copilot_configuration in LiteLLM is emitted with id and name only — never omitted.
Capability flags, token limits, url and apiType are left out rather than guessed,
because inventing them would misrepresent the model to VS Code. This keeps a copied "all
models" provider complete: the models needing attention are present and visibly bare, not
silently missing. The notification names them too.
apiType is only stated per model when it differs from the provider default. The
provider envelope declares chat-completions, so a model using it omits the field and
inherits. Only a genuine deviation — responses or messages — appears per model.
Usage
- Set API Key: On first use, you'll be prompted to enter your LiteLLM API key
- View Spend: Click the LLM Spend icon in the activity bar to open the sidebar
- Refresh: Use the refresh command or wait for auto-refresh
- Change Period: Select different time periods in the sidebar
- Discover Models: Open Settings → Available Models, then click a model to copy its Copilot configuration
Commands
LLM Spend: Refresh Data - Manually refresh spend data
LLM Spend: Open Sidebar - Open the spend dashboard
LLM Spend: Configure Settings - Open extension settings
LLM Spend: Copy Copilot Configuration for Model - Copy a GitHub Copilot configuration for an available model
LLM Spend: Copy Copilot Configurations for All Models - Copy one provider entry covering every available model (also triggered by clicking the Available Models section header)
Development
# Install dependencies
npm install
# Compile
npm run compile
# Watch mode
npm run watch
# Run tests
npm test
# Lint
npm run lint
# Format
npm run format
Project Structure
extension/
├── src/
│ ├── api/ # API client and repositories
│ ├── auth/ # Authentication management
│ ├── cache/ # Caching layer
│ ├── commands/ # Command handlers (e.g. copy Copilot configuration)
│ ├── config/ # Configuration management
│ ├── services/ # Business logic (spend, budget, models, config builder)
│ ├── state/ # State managers
│ ├── types/ # TypeScript type definitions
│ ├── ui/ # Tree views, webview, status bar
│ ├── utils/ # Utilities (logger, model list normalisation)
│ ├── constants.ts # Constants and configuration keys
│ └── extension.ts # Extension entry point
├── resources/ # Icons and assets
├── package.json # Extension manifest
└── tsconfig.json # TypeScript configuration
License
PolyForm Noncommercial License 1.0.0 - see LICENSE file for details.
This software is free for noncommercial use, including forking, studying, modifying, and contributing via pull requests. Commercial use requires a separate license from the copyright holder.