opencode-token-tracker 1.3.1 → 1.4.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/README.md +74 -0
- package/dist/bin/opencode-tokens.js +146 -28
- package/dist/index.js +170 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ Real-time token usage and cost tracking plugin for [OpenCode](https://opencode.a
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- **Real-time Toast notifications** - See token usage and cost after each AI response
|
|
10
|
+
- **Budget control** - Set daily/weekly/monthly spending limits with warnings
|
|
10
11
|
- **Session statistics** - Track cumulative usage across your entire session
|
|
11
12
|
- **CLI statistics tool** - Query usage by day/week/month with breakdowns by model/agent
|
|
12
13
|
- **Cost calculation** - Automatic cost estimation based on model pricing
|
|
@@ -37,6 +38,13 @@ Once installed, you'll see Toast notifications after each AI response:
|
|
|
37
38
|
$0.023 | Session: $0.156
|
|
38
39
|
```
|
|
39
40
|
|
|
41
|
+
When budget limits are configured, you'll see warnings:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
⚠️ Budget exceeded!
|
|
45
|
+
Daily: $5.50/$5.00 (110%)
|
|
46
|
+
```
|
|
47
|
+
|
|
40
48
|
When a session becomes idle, you'll see a summary:
|
|
41
49
|
|
|
42
50
|
```
|
|
@@ -44,6 +52,49 @@ Session: 45.2K tokens
|
|
|
44
52
|
$0.156 | 8 msgs | 5min
|
|
45
53
|
```
|
|
46
54
|
|
|
55
|
+
### Budget Control
|
|
56
|
+
|
|
57
|
+
Set spending limits to avoid unexpected costs:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Check current budget status
|
|
61
|
+
opencode-tokens budget
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Example output:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
Budget Status
|
|
68
|
+
══════════════════════════════════════════════════════════════════
|
|
69
|
+
|
|
70
|
+
🟢 Daily
|
|
71
|
+
$3.50 / $10.00 [███████░░░░░░░░░░░░░] 35%
|
|
72
|
+
Remaining: $6.50
|
|
73
|
+
|
|
74
|
+
🟡 Weekly
|
|
75
|
+
$42.00 / $50.00 [████████████████░░░░] 84%
|
|
76
|
+
Remaining: $8.00
|
|
77
|
+
|
|
78
|
+
🟢 Monthly
|
|
79
|
+
$120.00 / $200.00 [████████████░░░░░░░░] 60%
|
|
80
|
+
Remaining: $80.00
|
|
81
|
+
|
|
82
|
+
Legend: 🟢 OK 🟡 Warning (>80%) 🔴 Exceeded
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Configure budget in `~/.config/opencode/token-tracker.json`:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"budget": {
|
|
90
|
+
"daily": 10, // $10 per day
|
|
91
|
+
"weekly": 50, // $50 per week
|
|
92
|
+
"monthly": 200, // $200 per month
|
|
93
|
+
"warnAt": 0.8 // Warn at 80% usage
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
47
98
|
### CLI Statistics
|
|
48
99
|
|
|
49
100
|
Query your token usage from the command line:
|
|
@@ -97,6 +148,9 @@ Breakdown options (`--by`):
|
|
|
97
148
|
### Pricing & Config Commands
|
|
98
149
|
|
|
99
150
|
```bash
|
|
151
|
+
# Check budget status
|
|
152
|
+
opencode-tokens budget
|
|
153
|
+
|
|
100
154
|
# Show built-in pricing table
|
|
101
155
|
opencode-tokens pricing
|
|
102
156
|
|
|
@@ -179,10 +233,30 @@ Create a config file at `~/.config/opencode/token-tracker.json`:
|
|
|
179
233
|
"enabled": true,
|
|
180
234
|
"duration": 3000,
|
|
181
235
|
"showOnIdle": true
|
|
236
|
+
},
|
|
237
|
+
"budget": {
|
|
238
|
+
"daily": 10,
|
|
239
|
+
"weekly": 50,
|
|
240
|
+
"monthly": 200,
|
|
241
|
+
"warnAt": 0.8
|
|
182
242
|
}
|
|
183
243
|
}
|
|
184
244
|
```
|
|
185
245
|
|
|
246
|
+
### Budget Settings
|
|
247
|
+
|
|
248
|
+
| Option | Type | Default | Description |
|
|
249
|
+
|--------|------|---------|-------------|
|
|
250
|
+
| `daily` | number | - | Maximum daily spend in USD |
|
|
251
|
+
| `weekly` | number | - | Maximum weekly spend in USD |
|
|
252
|
+
| `monthly` | number | - | Maximum monthly spend in USD |
|
|
253
|
+
| `warnAt` | number | `0.8` | Warning threshold (0-1), e.g., 0.8 = warn at 80% |
|
|
254
|
+
|
|
255
|
+
When you exceed a budget limit:
|
|
256
|
+
- Toast notifications change to warning/error style
|
|
257
|
+
- Use `opencode-tokens budget` to check detailed status
|
|
258
|
+
- Budgets reset at midnight (daily), Monday (weekly), or 1st of month (monthly)
|
|
259
|
+
|
|
186
260
|
### Pricing Fields Explained
|
|
187
261
|
|
|
188
262
|
All prices are in **USD per 1 million tokens**:
|
|
@@ -5,37 +5,46 @@ import { homedir } from "os";
|
|
|
5
5
|
const CONFIG_DIR = join(homedir(), ".config", "opencode");
|
|
6
6
|
const CONFIG_FILE = join(CONFIG_DIR, "token-tracker.json");
|
|
7
7
|
const LOG_FILE = join(CONFIG_DIR, "logs", "token-tracker", "tokens.jsonl");
|
|
8
|
+
// Built-in pricing (USD per 1M tokens) - Updated 2026-02-05
|
|
9
|
+
// Keep in sync with index.ts BUILTIN_PRICING
|
|
8
10
|
const BUILTIN_PRICING = {
|
|
9
|
-
// Anthropic Claude
|
|
10
|
-
"claude-opus-4.5": { input:
|
|
11
|
+
// Anthropic Claude (https://www.anthropic.com/pricing#api)
|
|
12
|
+
"claude-opus-4.5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
11
13
|
"claude-sonnet-4.5": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
12
14
|
"claude-sonnet-4": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
13
|
-
"claude-haiku-4.5": { input:
|
|
14
|
-
"claude-haiku-4": { input:
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
"
|
|
15
|
+
"claude-haiku-4.5": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
16
|
+
"claude-haiku-4": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
17
|
+
"claude-opus-4.1": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
|
|
18
|
+
"claude-opus-4": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
|
|
19
|
+
"claude-haiku-3": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 },
|
|
20
|
+
// OpenAI GPT (https://openai.com/api/pricing/)
|
|
21
|
+
"gpt-5.2": { input: 1.75, output: 14, cacheRead: 0.175 },
|
|
22
|
+
"gpt-5.2-pro": { input: 21, output: 168 },
|
|
23
|
+
"gpt-5-mini": { input: 0.25, output: 2, cacheRead: 0.025 },
|
|
18
24
|
"gpt-5.1": { input: 2, output: 8 },
|
|
19
25
|
"gpt-5": { input: 5, output: 15 },
|
|
20
|
-
"gpt-4.1": { input:
|
|
21
|
-
"gpt-4.1-mini": { input: 0.
|
|
22
|
-
"gpt-4.1-nano": { input: 0.
|
|
26
|
+
"gpt-4.1": { input: 3, output: 12, cacheRead: 0.75 },
|
|
27
|
+
"gpt-4.1-mini": { input: 0.8, output: 3.2, cacheRead: 0.2 },
|
|
28
|
+
"gpt-4.1-nano": { input: 0.2, output: 0.8, cacheRead: 0.05 },
|
|
23
29
|
"gpt-4o": { input: 2.5, output: 10 },
|
|
24
30
|
"gpt-4o-mini": { input: 0.15, output: 0.6 },
|
|
25
31
|
"o3": { input: 10, output: 40 },
|
|
26
32
|
"o3-mini": { input: 1.1, output: 4.4 },
|
|
33
|
+
"o4-mini": { input: 4, output: 16, cacheRead: 1 },
|
|
27
34
|
"o1": { input: 15, output: 60 },
|
|
28
35
|
"o1-mini": { input: 1.1, output: 4.4 },
|
|
29
|
-
// DeepSeek
|
|
30
|
-
"deepseek-chat": { input: 0.
|
|
31
|
-
"deepseek-reasoner": { input: 0.
|
|
32
|
-
// Google Gemini
|
|
33
|
-
"gemini-3-pro": { input:
|
|
34
|
-
"gemini-3-pro-preview": { input:
|
|
35
|
-
"gemini-3-flash": { input: 0.
|
|
36
|
-
"gemini-
|
|
37
|
-
"gemini-2.5-
|
|
38
|
-
"gemini-2.
|
|
36
|
+
// DeepSeek (https://api-docs.deepseek.com/quick_start/pricing)
|
|
37
|
+
"deepseek-chat": { input: 0.28, output: 0.42, cacheRead: 0.028 },
|
|
38
|
+
"deepseek-reasoner": { input: 0.28, output: 0.42, cacheRead: 0.028 },
|
|
39
|
+
// Google Gemini (https://cloud.google.com/vertex-ai/generative-ai/pricing)
|
|
40
|
+
"gemini-3-pro": { input: 2, output: 12, cacheRead: 0.2 },
|
|
41
|
+
"gemini-3-pro-preview": { input: 2, output: 12, cacheRead: 0.2 },
|
|
42
|
+
"gemini-3-flash": { input: 0.5, output: 2, cacheRead: 0.05 },
|
|
43
|
+
"gemini-3-flash-preview": { input: 0.5, output: 2, cacheRead: 0.05 },
|
|
44
|
+
"gemini-2.5-pro": { input: 1.25, output: 10, cacheRead: 0.125 },
|
|
45
|
+
"gemini-2.5-flash": { input: 0.1, output: 0.4, cacheRead: 0.01 },
|
|
46
|
+
"gemini-2.5-flash-lite": { input: 0.1, output: 0.4, cacheRead: 0.01 },
|
|
47
|
+
"gemini-2.0-flash": { input: 0.15, output: 0.6, cacheRead: 0.015 },
|
|
39
48
|
// Fallback
|
|
40
49
|
"_default": { input: 1, output: 4 },
|
|
41
50
|
};
|
|
@@ -276,15 +285,15 @@ function cmdStats(period, breakdown) {
|
|
|
276
285
|
function cmdPricing() {
|
|
277
286
|
const config = loadConfig();
|
|
278
287
|
console.log(`
|
|
279
|
-
Built-in Pricing Table (USD per 1M tokens)
|
|
288
|
+
Built-in Pricing Table (USD per 1M tokens) - Updated 2026-02-05
|
|
280
289
|
══════════════════════════════════════════════════════════════════
|
|
281
290
|
`);
|
|
282
291
|
// Group by provider
|
|
283
292
|
const groups = {
|
|
284
|
-
"Anthropic Claude": ["claude-opus-4.5", "claude-sonnet-4.5", "claude-sonnet-4", "claude-haiku-4.5", "claude-haiku-4"],
|
|
285
|
-
"OpenAI": ["gpt-5.2", "gpt-5.2-
|
|
293
|
+
"Anthropic Claude": ["claude-opus-4.5", "claude-sonnet-4.5", "claude-sonnet-4", "claude-haiku-4.5", "claude-haiku-4", "claude-opus-4.1", "claude-opus-4", "claude-haiku-3"],
|
|
294
|
+
"OpenAI": ["gpt-5.2", "gpt-5.2-pro", "gpt-5-mini", "gpt-5.1", "gpt-5", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "gpt-4o", "gpt-4o-mini", "o3", "o3-mini", "o4-mini", "o1", "o1-mini"],
|
|
286
295
|
"DeepSeek": ["deepseek-chat", "deepseek-reasoner"],
|
|
287
|
-
"Google Gemini": ["gemini-3-pro", "gemini-3-pro-preview", "gemini-3-flash", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.0-flash"],
|
|
296
|
+
"Google Gemini": ["gemini-3-pro", "gemini-3-pro-preview", "gemini-3-flash", "gemini-3-flash-preview", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite", "gemini-2.0-flash"],
|
|
288
297
|
};
|
|
289
298
|
const modelWidth = 20;
|
|
290
299
|
const priceWidth = 10;
|
|
@@ -395,6 +404,12 @@ function cmdConfig(action) {
|
|
|
395
404
|
duration: 3000,
|
|
396
405
|
showOnIdle: true,
|
|
397
406
|
},
|
|
407
|
+
budget: {
|
|
408
|
+
daily: 5,
|
|
409
|
+
weekly: 25,
|
|
410
|
+
monthly: 100,
|
|
411
|
+
warnAt: 0.8,
|
|
412
|
+
},
|
|
398
413
|
};
|
|
399
414
|
// Add providers as comments/examples
|
|
400
415
|
for (const provider of providers) {
|
|
@@ -409,11 +424,11 @@ function cmdConfig(action) {
|
|
|
409
424
|
}
|
|
410
425
|
// Print explanation first
|
|
411
426
|
console.log(`
|
|
412
|
-
|
|
427
|
+
Configuration Guide
|
|
413
428
|
══════════════════════════════════════════════════════════════════
|
|
414
429
|
|
|
415
|
-
|
|
416
|
-
|
|
430
|
+
PRICING (prices in USD per 1 MILLION tokens)
|
|
431
|
+
────────────────────────────────────────────────────────────────
|
|
417
432
|
Fields:
|
|
418
433
|
input Cost for input/prompt tokens sent to the model
|
|
419
434
|
output Cost for output/completion tokens from the model
|
|
@@ -425,7 +440,7 @@ function cmdConfig(action) {
|
|
|
425
440
|
{ "input": 0, "output": 0 } = Free (subscription or local model)
|
|
426
441
|
|
|
427
442
|
Common scenarios:
|
|
428
|
-
- GitHub Copilot, Cursor, etc. → Set to 0
|
|
443
|
+
- GitHub Copilot, Cursor, etc. → Set provider to { input: 0, output: 0 }
|
|
429
444
|
- Local/self-hosted models → Set to 0
|
|
430
445
|
- Direct API usage → Look up provider's pricing page
|
|
431
446
|
|
|
@@ -436,6 +451,17 @@ function cmdConfig(action) {
|
|
|
436
451
|
- Google: https://ai.google.dev/pricing
|
|
437
452
|
- Or run: opencode-tokens pricing
|
|
438
453
|
|
|
454
|
+
BUDGET CONTROL
|
|
455
|
+
────────────────────────────────────────────────────────────────
|
|
456
|
+
Set spending limits to avoid unexpected costs:
|
|
457
|
+
daily Maximum spend per day (USD)
|
|
458
|
+
weekly Maximum spend per week (USD)
|
|
459
|
+
monthly Maximum spend per month (USD)
|
|
460
|
+
warnAt Warning threshold (0-1), default 0.8 = 80%
|
|
461
|
+
|
|
462
|
+
When budget is exceeded, you'll see a warning toast.
|
|
463
|
+
Check status anytime with: opencode-tokens budget
|
|
464
|
+
|
|
439
465
|
────────────────────────────────────────────────────────────────
|
|
440
466
|
Example config based on your usage:
|
|
441
467
|
`);
|
|
@@ -476,6 +502,92 @@ function cmdConfig(action) {
|
|
|
476
502
|
console.log(` opencode-tokens config generate Create config file`);
|
|
477
503
|
console.log();
|
|
478
504
|
}
|
|
505
|
+
function cmdBudget() {
|
|
506
|
+
const config = loadConfig();
|
|
507
|
+
const budget = config.budget;
|
|
508
|
+
if (!budget?.daily && !budget?.weekly && !budget?.monthly) {
|
|
509
|
+
console.log(`
|
|
510
|
+
Budget Status
|
|
511
|
+
══════════════════════════════════════════════════════════════════
|
|
512
|
+
|
|
513
|
+
No budget configured.
|
|
514
|
+
|
|
515
|
+
To set a budget, add to your config file (${CONFIG_FILE}):
|
|
516
|
+
|
|
517
|
+
{
|
|
518
|
+
"budget": {
|
|
519
|
+
"daily": 5, // $5 per day
|
|
520
|
+
"weekly": 25, // $25 per week (optional)
|
|
521
|
+
"monthly": 100, // $100 per month (optional)
|
|
522
|
+
"warnAt": 0.8 // Warn at 80% usage
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
Run: opencode-tokens config init for more details.
|
|
527
|
+
`);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
const now = new Date();
|
|
531
|
+
const warnAt = budget.warnAt ?? 0.8;
|
|
532
|
+
console.log(`
|
|
533
|
+
Budget Status
|
|
534
|
+
══════════════════════════════════════════════════════════════════
|
|
535
|
+
`);
|
|
536
|
+
// Helper to create progress bar
|
|
537
|
+
const progressBar = (pct, width = 20) => {
|
|
538
|
+
const filled = Math.min(Math.round(pct * width), width);
|
|
539
|
+
const empty = width - filled;
|
|
540
|
+
const bar = "█".repeat(filled) + "░".repeat(empty);
|
|
541
|
+
return bar;
|
|
542
|
+
};
|
|
543
|
+
// Helper to get color indicator
|
|
544
|
+
const statusIndicator = (pct) => {
|
|
545
|
+
if (pct >= 1)
|
|
546
|
+
return "🔴";
|
|
547
|
+
if (pct >= warnAt)
|
|
548
|
+
return "🟡";
|
|
549
|
+
return "🟢";
|
|
550
|
+
};
|
|
551
|
+
const entries = loadEntries();
|
|
552
|
+
// Daily budget
|
|
553
|
+
if (budget.daily) {
|
|
554
|
+
const dayStart = getStartOfDay(now);
|
|
555
|
+
const dayEntries = entries.filter(e => e._ts >= dayStart);
|
|
556
|
+
const spent = dayEntries.reduce((sum, e) => sum + (e.cost ?? 0), 0);
|
|
557
|
+
const pct = spent / budget.daily;
|
|
558
|
+
const pctDisplay = Math.round(pct * 100);
|
|
559
|
+
console.log(` ${statusIndicator(pct)} Daily`);
|
|
560
|
+
console.log(` ${formatCost(spent)} / ${formatCost(budget.daily)} [${progressBar(pct)}] ${pctDisplay}%`);
|
|
561
|
+
console.log(` Remaining: ${formatCost(Math.max(0, budget.daily - spent))}`);
|
|
562
|
+
console.log();
|
|
563
|
+
}
|
|
564
|
+
// Weekly budget
|
|
565
|
+
if (budget.weekly) {
|
|
566
|
+
const weekStart = getStartOfWeek(now);
|
|
567
|
+
const weekEntries = entries.filter(e => e._ts >= weekStart);
|
|
568
|
+
const spent = weekEntries.reduce((sum, e) => sum + (e.cost ?? 0), 0);
|
|
569
|
+
const pct = spent / budget.weekly;
|
|
570
|
+
const pctDisplay = Math.round(pct * 100);
|
|
571
|
+
console.log(` ${statusIndicator(pct)} Weekly`);
|
|
572
|
+
console.log(` ${formatCost(spent)} / ${formatCost(budget.weekly)} [${progressBar(pct)}] ${pctDisplay}%`);
|
|
573
|
+
console.log(` Remaining: ${formatCost(Math.max(0, budget.weekly - spent))}`);
|
|
574
|
+
console.log();
|
|
575
|
+
}
|
|
576
|
+
// Monthly budget
|
|
577
|
+
if (budget.monthly) {
|
|
578
|
+
const monthStart = getStartOfMonth(now);
|
|
579
|
+
const monthEntries = entries.filter(e => e._ts >= monthStart);
|
|
580
|
+
const spent = monthEntries.reduce((sum, e) => sum + (e.cost ?? 0), 0);
|
|
581
|
+
const pct = spent / budget.monthly;
|
|
582
|
+
const pctDisplay = Math.round(pct * 100);
|
|
583
|
+
console.log(` ${statusIndicator(pct)} Monthly`);
|
|
584
|
+
console.log(` ${formatCost(spent)} / ${formatCost(budget.monthly)} [${progressBar(pct)}] ${pctDisplay}%`);
|
|
585
|
+
console.log(` Remaining: ${formatCost(Math.max(0, budget.monthly - spent))}`);
|
|
586
|
+
console.log();
|
|
587
|
+
}
|
|
588
|
+
console.log(` Legend: 🟢 OK 🟡 Warning (>${Math.round(warnAt * 100)}%) 🔴 Exceeded`);
|
|
589
|
+
console.log();
|
|
590
|
+
}
|
|
479
591
|
function cmdHelp() {
|
|
480
592
|
console.log(`
|
|
481
593
|
opencode-tokens - Token usage statistics CLI
|
|
@@ -485,6 +597,7 @@ function cmdHelp() {
|
|
|
485
597
|
|
|
486
598
|
Commands:
|
|
487
599
|
(default) Show usage statistics
|
|
600
|
+
budget Show budget status (daily/weekly/monthly)
|
|
488
601
|
pricing Show built-in pricing table
|
|
489
602
|
models Show your used models and their pricing status
|
|
490
603
|
config Show/generate configuration
|
|
@@ -499,6 +612,7 @@ function cmdHelp() {
|
|
|
499
612
|
|
|
500
613
|
Examples:
|
|
501
614
|
opencode-tokens # All-time summary
|
|
615
|
+
opencode-tokens budget # Check budget status
|
|
502
616
|
opencode-tokens today # Today's summary
|
|
503
617
|
opencode-tokens week --by model # This week, by model
|
|
504
618
|
opencode-tokens pricing # Show pricing table
|
|
@@ -517,6 +631,10 @@ function main() {
|
|
|
517
631
|
return;
|
|
518
632
|
}
|
|
519
633
|
// Handle subcommands
|
|
634
|
+
if (command === "budget") {
|
|
635
|
+
cmdBudget();
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
520
638
|
if (command === "pricing") {
|
|
521
639
|
cmdPricing();
|
|
522
640
|
return;
|
package/dist/index.js
CHANGED
|
@@ -13,39 +13,70 @@ const DEFAULT_CONFIG = {
|
|
|
13
13
|
duration: 3000,
|
|
14
14
|
showOnIdle: true,
|
|
15
15
|
},
|
|
16
|
+
budget: {
|
|
17
|
+
warnAt: 0.8,
|
|
18
|
+
},
|
|
16
19
|
};
|
|
17
|
-
// Built-in pricing table (USD per 1M tokens) -
|
|
20
|
+
// Built-in pricing table (USD per 1M tokens) - Updated 2026-02-05
|
|
21
|
+
// Sources:
|
|
22
|
+
// - Anthropic: https://www.anthropic.com/pricing#api
|
|
23
|
+
// - OpenAI: https://openai.com/api/pricing/
|
|
24
|
+
// - DeepSeek: https://api-docs.deepseek.com/quick_start/pricing
|
|
25
|
+
// - Google: https://cloud.google.com/vertex-ai/generative-ai/pricing
|
|
18
26
|
const BUILTIN_PRICING = {
|
|
19
|
-
// Anthropic Claude
|
|
20
|
-
|
|
27
|
+
// Anthropic Claude (https://www.anthropic.com/pricing#api)
|
|
28
|
+
// Opus 4.5: $5 input, $25 output, cache write $6.25, cache read $0.50
|
|
29
|
+
"claude-opus-4.5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
30
|
+
// Sonnet 4.5: $3 input, $15 output (≤200K), cache write $3.75, cache read $0.30
|
|
21
31
|
"claude-sonnet-4.5": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
32
|
+
// Sonnet 4: $3 input, $15 output
|
|
22
33
|
"claude-sonnet-4": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
23
|
-
|
|
24
|
-
"claude-haiku-4": { input:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
34
|
+
// Haiku 4.5: $1 input, $5 output, cache write $1.25, cache read $0.10
|
|
35
|
+
"claude-haiku-4.5": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
36
|
+
"claude-haiku-4": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
37
|
+
// Legacy models
|
|
38
|
+
"claude-opus-4.1": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
|
|
39
|
+
"claude-opus-4": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
|
|
40
|
+
"claude-haiku-3": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 },
|
|
41
|
+
// OpenAI GPT (https://openai.com/api/pricing/)
|
|
42
|
+
// GPT-5.2: $1.75 input, $14 output (flagship)
|
|
43
|
+
"gpt-5.2": { input: 1.75, output: 14, cacheRead: 0.175 },
|
|
44
|
+
"gpt-5.2-pro": { input: 21, output: 168 },
|
|
45
|
+
"gpt-5-mini": { input: 0.25, output: 2, cacheRead: 0.025 },
|
|
28
46
|
"gpt-5.1": { input: 2, output: 8 },
|
|
29
47
|
"gpt-5": { input: 5, output: 15 },
|
|
30
|
-
|
|
31
|
-
"gpt-4.1
|
|
32
|
-
"gpt-4.1-
|
|
48
|
+
// GPT-4.1 series (fine-tuning prices, base may differ)
|
|
49
|
+
"gpt-4.1": { input: 3, output: 12, cacheRead: 0.75 },
|
|
50
|
+
"gpt-4.1-mini": { input: 0.8, output: 3.2, cacheRead: 0.2 },
|
|
51
|
+
"gpt-4.1-nano": { input: 0.2, output: 0.8, cacheRead: 0.05 },
|
|
52
|
+
// GPT-4o series (may be deprecated)
|
|
33
53
|
"gpt-4o": { input: 2.5, output: 10 },
|
|
34
54
|
"gpt-4o-mini": { input: 0.15, output: 0.6 },
|
|
55
|
+
// Reasoning models
|
|
35
56
|
"o3": { input: 10, output: 40 },
|
|
36
57
|
"o3-mini": { input: 1.1, output: 4.4 },
|
|
58
|
+
"o4-mini": { input: 4, output: 16, cacheRead: 1 },
|
|
37
59
|
"o1": { input: 15, output: 60 },
|
|
38
60
|
"o1-mini": { input: 1.1, output: 4.4 },
|
|
39
|
-
// DeepSeek
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"gemini-
|
|
47
|
-
"gemini-
|
|
48
|
-
|
|
61
|
+
// DeepSeek (https://api-docs.deepseek.com/quick_start/pricing)
|
|
62
|
+
// DeepSeek-V3.2: unified pricing for both chat and reasoner
|
|
63
|
+
// $0.28 input (cache miss), $0.028 input (cache hit), $0.42 output
|
|
64
|
+
"deepseek-chat": { input: 0.28, output: 0.42, cacheRead: 0.028 },
|
|
65
|
+
"deepseek-reasoner": { input: 0.28, output: 0.42, cacheRead: 0.028 },
|
|
66
|
+
// Google Gemini (https://cloud.google.com/vertex-ai/generative-ai/pricing)
|
|
67
|
+
// Gemini 3 Pro Preview: $2 input, $12 output (≤200K)
|
|
68
|
+
"gemini-3-pro": { input: 2, output: 12, cacheRead: 0.2 },
|
|
69
|
+
"gemini-3-pro-preview": { input: 2, output: 12, cacheRead: 0.2 },
|
|
70
|
+
// Gemini 3 Flash Preview: $0.5 input
|
|
71
|
+
"gemini-3-flash": { input: 0.5, output: 2, cacheRead: 0.05 },
|
|
72
|
+
"gemini-3-flash-preview": { input: 0.5, output: 2, cacheRead: 0.05 },
|
|
73
|
+
// Gemini 2.5 Pro: $1.25 input, $10 output (≤200K)
|
|
74
|
+
"gemini-2.5-pro": { input: 1.25, output: 10, cacheRead: 0.125 },
|
|
75
|
+
// Gemini 2.5 Flash Lite: $0.1 input
|
|
76
|
+
"gemini-2.5-flash": { input: 0.1, output: 0.4, cacheRead: 0.01 },
|
|
77
|
+
"gemini-2.5-flash-lite": { input: 0.1, output: 0.4, cacheRead: 0.01 },
|
|
78
|
+
// Gemini 2.0 Flash: $0.15 input
|
|
79
|
+
"gemini-2.0-flash": { input: 0.15, output: 0.6, cacheRead: 0.015 },
|
|
49
80
|
// Fallback for unknown models
|
|
50
81
|
"_default": { input: 1, output: 4 },
|
|
51
82
|
};
|
|
@@ -59,6 +90,7 @@ function loadConfig() {
|
|
|
59
90
|
providers: { ...DEFAULT_CONFIG.providers, ...userConfig.providers },
|
|
60
91
|
models: { ...DEFAULT_CONFIG.models, ...userConfig.models },
|
|
61
92
|
toast: { ...DEFAULT_CONFIG.toast, ...userConfig.toast },
|
|
93
|
+
budget: { ...DEFAULT_CONFIG.budget, ...userConfig.budget },
|
|
62
94
|
};
|
|
63
95
|
}
|
|
64
96
|
}
|
|
@@ -167,6 +199,102 @@ function logJson(data) {
|
|
|
167
199
|
const entry = JSON.stringify({ ...data, _ts: Date.now() }) + "\n";
|
|
168
200
|
appendFileSync(LOG_FILE, entry);
|
|
169
201
|
}
|
|
202
|
+
function getStartOfDay(date = new Date()) {
|
|
203
|
+
const d = new Date(date);
|
|
204
|
+
d.setHours(0, 0, 0, 0);
|
|
205
|
+
return d.getTime();
|
|
206
|
+
}
|
|
207
|
+
function getStartOfWeek(date = new Date()) {
|
|
208
|
+
const d = new Date(date);
|
|
209
|
+
const day = d.getDay();
|
|
210
|
+
const diff = d.getDate() - day + (day === 0 ? -6 : 1); // Monday as first day
|
|
211
|
+
d.setDate(diff);
|
|
212
|
+
d.setHours(0, 0, 0, 0);
|
|
213
|
+
return d.getTime();
|
|
214
|
+
}
|
|
215
|
+
function getStartOfMonth(date = new Date()) {
|
|
216
|
+
const d = new Date(date);
|
|
217
|
+
d.setDate(1);
|
|
218
|
+
d.setHours(0, 0, 0, 0);
|
|
219
|
+
return d.getTime();
|
|
220
|
+
}
|
|
221
|
+
function loadEntriesSince(since) {
|
|
222
|
+
if (!existsSync(LOG_FILE))
|
|
223
|
+
return [];
|
|
224
|
+
try {
|
|
225
|
+
const content = readFileSync(LOG_FILE, "utf-8");
|
|
226
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
227
|
+
const entries = [];
|
|
228
|
+
for (const line of lines) {
|
|
229
|
+
try {
|
|
230
|
+
const entry = JSON.parse(line);
|
|
231
|
+
if (entry.type === "tokens" && entry._ts >= since && entry.cost) {
|
|
232
|
+
entries.push(entry);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch { }
|
|
236
|
+
}
|
|
237
|
+
return entries;
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function calculateSpentSince(since) {
|
|
244
|
+
const entries = loadEntriesSince(since);
|
|
245
|
+
return entries.reduce((sum, e) => sum + (e.cost ?? 0), 0);
|
|
246
|
+
}
|
|
247
|
+
function checkBudgetStatus() {
|
|
248
|
+
const budget = config.budget;
|
|
249
|
+
if (!budget.daily && !budget.weekly && !budget.monthly) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
const warnAt = budget.warnAt ?? 0.8;
|
|
253
|
+
const now = new Date();
|
|
254
|
+
// Check in order: daily -> weekly -> monthly (most restrictive first)
|
|
255
|
+
if (budget.daily) {
|
|
256
|
+
const spent = calculateSpentSince(getStartOfDay(now));
|
|
257
|
+
const percentage = spent / budget.daily;
|
|
258
|
+
return {
|
|
259
|
+
period: "daily",
|
|
260
|
+
spent,
|
|
261
|
+
limit: budget.daily,
|
|
262
|
+
percentage,
|
|
263
|
+
exceeded: percentage >= 1,
|
|
264
|
+
warning: percentage >= warnAt && percentage < 1,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (budget.weekly) {
|
|
268
|
+
const spent = calculateSpentSince(getStartOfWeek(now));
|
|
269
|
+
const percentage = spent / budget.weekly;
|
|
270
|
+
return {
|
|
271
|
+
period: "weekly",
|
|
272
|
+
spent,
|
|
273
|
+
limit: budget.weekly,
|
|
274
|
+
percentage,
|
|
275
|
+
exceeded: percentage >= 1,
|
|
276
|
+
warning: percentage >= warnAt && percentage < 1,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
if (budget.monthly) {
|
|
280
|
+
const spent = calculateSpentSince(getStartOfMonth(now));
|
|
281
|
+
const percentage = spent / budget.monthly;
|
|
282
|
+
return {
|
|
283
|
+
period: "monthly",
|
|
284
|
+
spent,
|
|
285
|
+
limit: budget.monthly,
|
|
286
|
+
percentage,
|
|
287
|
+
exceeded: percentage >= 1,
|
|
288
|
+
warning: percentage >= warnAt && percentage < 1,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
function formatBudgetMessage(status) {
|
|
294
|
+
const pct = Math.round(status.percentage * 100);
|
|
295
|
+
const periodLabel = status.period.charAt(0).toUpperCase() + status.period.slice(1);
|
|
296
|
+
return `${periodLabel}: ${formatCost(status.spent)}/${formatCost(status.limit)} (${pct}%)`;
|
|
297
|
+
}
|
|
170
298
|
export const TokenTrackerPlugin = async ({ directory, client }) => {
|
|
171
299
|
// Load config on plugin init
|
|
172
300
|
config = loadConfig();
|
|
@@ -226,13 +354,30 @@ export const TokenTrackerPlugin = async ({ directory, client }) => {
|
|
|
226
354
|
// Show toast for this message
|
|
227
355
|
if (config.toast.enabled) {
|
|
228
356
|
const totalTokens = input + output;
|
|
357
|
+
// Check budget status
|
|
358
|
+
const budgetStatus = checkBudgetStatus();
|
|
359
|
+
let title = `${formatTokens(totalTokens)} tokens`;
|
|
360
|
+
let message = `${formatCost(cost)} | Session: ${formatCost(stats.totalCost)}`;
|
|
361
|
+
let variant = "info";
|
|
362
|
+
// Add budget warning/alert if applicable
|
|
363
|
+
if (budgetStatus) {
|
|
364
|
+
if (budgetStatus.exceeded) {
|
|
365
|
+
title = `⚠️ Budget exceeded!`;
|
|
366
|
+
message = formatBudgetMessage(budgetStatus);
|
|
367
|
+
variant = "error";
|
|
368
|
+
}
|
|
369
|
+
else if (budgetStatus.warning) {
|
|
370
|
+
message = `${formatCost(cost)} | ${formatBudgetMessage(budgetStatus)}`;
|
|
371
|
+
variant = "warning";
|
|
372
|
+
}
|
|
373
|
+
}
|
|
229
374
|
try {
|
|
230
375
|
await client.tui.showToast({
|
|
231
376
|
body: {
|
|
232
|
-
title
|
|
233
|
-
message
|
|
234
|
-
variant
|
|
235
|
-
duration: config.toast.duration,
|
|
377
|
+
title,
|
|
378
|
+
message,
|
|
379
|
+
variant,
|
|
380
|
+
duration: budgetStatus?.exceeded ? 5000 : config.toast.duration,
|
|
236
381
|
},
|
|
237
382
|
});
|
|
238
383
|
}
|
package/package.json
CHANGED