@leo000001/opencode-quota-sidebar 2.0.12 → 2.0.15
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 +49 -68
- package/dist/cost.d.ts +3 -0
- package/dist/cost.js +131 -8
- package/dist/format.js +193 -114
- package/dist/title.js +46 -1
- package/dist/tools.js +2 -2
- package/dist/types.d.ts +11 -14
- package/dist/usage.d.ts +1 -1
- package/dist/usage.js +5 -12
- package/dist/usage_service.js +5 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,28 +55,25 @@ Want to add support for another provider (Google Antigravity, Zhipu AI, Firmware
|
|
|
55
55
|
|
|
56
56
|
## Features
|
|
57
57
|
|
|
58
|
-
- TUI session title
|
|
58
|
+
- TUI session title uses a compact multiline sidebar layout:
|
|
59
59
|
- line 1: original session title
|
|
60
60
|
- line 2: blank separator
|
|
61
|
-
- line 3:
|
|
62
|
-
- line 4
|
|
63
|
-
- line
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- XYAI daily quota follows the same balance-style layout and prefers the real reset time (for example `XYAI Daily $70.2/$90 Rst 22:18`)
|
|
70
|
-
- Desktop automatically switches to a compact single-line title. It keeps recently used providers from the last `50` requests or last `60` minutes ahead of `Requests/Input/Output`, and expands all windows/balance for those selected providers in short form such as `OAI 5h80 W70` or `RC D88.9/60 B260`
|
|
71
|
-
- TUI is always rendered as multiline; Desktop is always rendered as compact single-line. This behavior no longer depends on `sidebar.multilineTitle`
|
|
61
|
+
- line 3: compact usage tokens such as `R3 I16.3k O916`
|
|
62
|
+
- line 4+: compact cache tokens such as `CW300 CR31.4k Cd66%`
|
|
63
|
+
- optional cost line: `Est$0.12`
|
|
64
|
+
- quota lines also use compact tokens, for example `XYAI D$31.3/$90 R22:39` or `OAI 5h80 R22:18 W70 R04-03`
|
|
65
|
+
- short windows (`5h`, `1d`, `Daily`) still show same-day resets as `HH:MM` and cross-day resets as `MM-DD HH:MM`; longer windows continue to show `MM-DD`
|
|
66
|
+
- long quota content wraps across extra compact lines instead of dropping fields from the sidebar, and continuation lines align to the quota content column
|
|
67
|
+
- Desktop automatically switches to a compact monitoring-style single-line title. It keeps recently used providers from the last `50` requests or last `60` minutes, expands all windows/balance for those selected providers in short form such as `OAI 5h80 R16:20 W70 R04-03` or `RC D88.9/60 B260`, and keeps only summary usage signals such as `Cd66%` and `Est$0.12`
|
|
68
|
+
- TUI is always rendered as compact multiline; Desktop is always rendered as compact monitoring-style single-line. This behavior no longer depends on `sidebar.multilineTitle`
|
|
72
69
|
- Web UI currently cannot be reliably detected by the plugin, so it follows the non-desktop multiline path
|
|
73
70
|
- Session-scoped usage/quota can include descendant subagent sessions (enabled by default via `sidebar.includeChildren=true`). Traversal is bounded by `childrenMaxDepth` (default 6), `childrenMaxSessions` (default 128), and `childrenConcurrency` (default 5); truncation is logged when `OPENCODE_QUOTA_DEBUG=1`. Day/week/month ranges never merge children — only session scope does.
|
|
74
|
-
- Toast message can include four sections: `Token Usage`, `Cost as API` (per provider), `Provider Cache` (when provider-level
|
|
71
|
+
- Toast message can include four sections: `Token Usage`, `Cost as API` (per provider), `Provider Cache` (when provider-level cached ratios are available), and `Quota`
|
|
75
72
|
- Expiry reminders are shown in a separate `Expiry Soon` toast section only for providers with real subscription expiry timestamps, and each session shows that auto-reminder at most once
|
|
76
|
-
- `quota_summary` markdown / toast also include `
|
|
73
|
+
- `quota_summary` markdown / toast also include `Cached` summary lines when cache activity is available
|
|
77
74
|
- Quota snapshots are de-duplicated before rendering to avoid repeated provider lines
|
|
78
75
|
- Custom tools:
|
|
79
|
-
- `quota_summary` — generate usage report for session/day/week/month (markdown + toast)
|
|
76
|
+
- `quota_summary` — generate usage report for session/day/week/month (full markdown report + toast). The markdown report and toast keep the full human-readable wording; they do not switch to compact sidebar tokens.
|
|
80
77
|
- `quota_show` — toggle sidebar title display on/off (state persists across sessions)
|
|
81
78
|
- After startup, titles are restored immediately when persisted display mode is OFF; when persisted display mode is ON, touched titles refresh on startup and the rest update on the next relevant session/message event or when `quota_show` is toggled
|
|
82
79
|
- Quota connectors:
|
|
@@ -121,10 +118,10 @@ The plugin stores lightweight global state and date-partitioned session chunks.
|
|
|
121
118
|
- `createdAt`
|
|
122
119
|
- `parentID` (when the session is a subagent child session)
|
|
123
120
|
- `expiryToastShown` (session-level dedupe for automatic expiry reminders)
|
|
124
|
-
- cached usage summary used by `quota_summary`, including session-level and provider-level `cacheBuckets` for cache
|
|
121
|
+
- cached usage summary used by `quota_summary`, including session-level and provider-level `cacheBuckets` for cached-ratio reporting and legacy cache classification
|
|
125
122
|
- incremental aggregation cursor
|
|
126
123
|
|
|
127
|
-
Notes on cache
|
|
124
|
+
Notes on cache bucket persistence:
|
|
128
125
|
|
|
129
126
|
- Older cached usage written before `cacheBuckets` existed can only be approximated from top-level `cache_read` / `cache_write` totals.
|
|
130
127
|
- In those legacy cases, mixed read-only + read-write cache traffic may be attributed to a single fallback bucket until the session is recomputed from messages.
|
|
@@ -208,6 +205,8 @@ You can add these command templates in `opencode.json` so you can run `/qday`, `
|
|
|
208
205
|
}
|
|
209
206
|
```
|
|
210
207
|
|
|
208
|
+
When calling `quota_summary`, make sure the client shows the returned markdown report directly to the user. The tool already returns the full report body; do not replace it with a compact summary.
|
|
209
|
+
|
|
211
210
|
## Configuration files
|
|
212
211
|
|
|
213
212
|
Recommended global config:
|
|
@@ -324,7 +323,7 @@ Other defaults:
|
|
|
324
323
|
- `sidebar.showCost` controls API-cost visibility in sidebar title, `quota_summary` markdown report, and toast message.
|
|
325
324
|
- `quota_summary` follows the same reset compaction rules for short windows in its subscription section (`5h` / `1d` / `Daily` show time, long windows show date, RightCode `Exp` stays date-only).
|
|
326
325
|
- `sidebar.width` is measured in terminal cells. CJK/emoji truncation is best-effort to avoid sidebar overflow.
|
|
327
|
-
- `sidebar.multilineTitle` is kept for backward compatibility, but current rendering is fixed by client type: TUI uses multiline titles and Desktop uses compact single-line titles.
|
|
326
|
+
- `sidebar.multilineTitle` is kept for backward compatibility, but current rendering is fixed by client type: TUI uses multiline titles and Desktop uses compact monitoring-style single-line titles.
|
|
328
327
|
- `sidebar.wrapQuotaLines` controls quota line wrapping and continuation indentation (default: `true`).
|
|
329
328
|
- `sidebar.includeChildren` controls whether session-scoped usage/quota includes descendant subagent sessions (default: `true`).
|
|
330
329
|
- `sidebar.childrenMaxDepth` limits how many levels of nested subagents are traversed (default: `6`, clamped 1–32).
|
|
@@ -360,7 +359,7 @@ The adapter also tolerates `https://buzzai.cc/v1`, but `https://buzzai.cc` is th
|
|
|
360
359
|
With that setup, the sidebar/toast quota line will look like:
|
|
361
360
|
|
|
362
361
|
```text
|
|
363
|
-
Buzz
|
|
362
|
+
Buzz B¥10.17
|
|
364
363
|
```
|
|
365
364
|
|
|
366
365
|
## Rendering examples
|
|
@@ -369,7 +368,7 @@ These examples show the quota block portion of the sidebar title.
|
|
|
369
368
|
|
|
370
369
|
### TUI layout
|
|
371
370
|
|
|
372
|
-
This section describes the TUI layout. Desktop uses its own compact single-line format and Web UI currently follows the multiline path.
|
|
371
|
+
This section describes the TUI layout. Desktop uses its own compact monitoring-style single-line format and Web UI currently follows the multiline path.
|
|
373
372
|
|
|
374
373
|
0 providers (no quota data):
|
|
375
374
|
|
|
@@ -380,113 +379,95 @@ This section describes the TUI layout. Desktop uses its own compact single-line
|
|
|
380
379
|
1 provider, 1 window (fits):
|
|
381
380
|
|
|
382
381
|
```text
|
|
383
|
-
|
|
382
|
+
Cop M78 R04-01
|
|
384
383
|
```
|
|
385
384
|
|
|
386
385
|
1 provider, multi-window (for example OpenAI 5h + Weekly):
|
|
387
386
|
|
|
388
387
|
```text
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
388
|
+
OAI 5h78 R05:05 W73 R03-12
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
1 provider, multi-window on narrow width:
|
|
392
|
+
|
|
393
|
+
```text
|
|
394
|
+
OAI 5h78 R05:05
|
|
395
|
+
W73 R03-12
|
|
392
396
|
```
|
|
393
397
|
|
|
394
398
|
1 provider, short window crossing into the next day:
|
|
395
399
|
|
|
396
400
|
```text
|
|
397
|
-
|
|
398
|
-
5h 0% Rst 03-10 01:00
|
|
399
|
-
Weekly 46% Rst 03-15
|
|
401
|
+
Ant 5h0 R03-10 01:00 W46 R03-15
|
|
400
402
|
```
|
|
401
403
|
|
|
402
404
|
2+ providers (even if each provider is single-window):
|
|
403
405
|
|
|
404
406
|
```text
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
Copilot
|
|
408
|
-
Monthly 78% Rst 04-01
|
|
407
|
+
OAI 5h78 R05:05
|
|
408
|
+
Cop M78 R04-01
|
|
409
409
|
```
|
|
410
410
|
|
|
411
411
|
2+ providers mixed (multi-window + single-window):
|
|
412
412
|
|
|
413
413
|
```text
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
Weekly 73% Rst 03-12
|
|
417
|
-
Copilot
|
|
418
|
-
Monthly 78% Rst 04-01
|
|
414
|
+
OAI 5h78 R05:05 W73 R03-12
|
|
415
|
+
Cop M78 R04-01
|
|
419
416
|
```
|
|
420
417
|
|
|
421
418
|
2+ providers mixed (window providers + Buzz balance):
|
|
422
419
|
|
|
423
420
|
```text
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
Monthly 78% Rst 04-01
|
|
428
|
-
Buzz Balance ¥10.2
|
|
421
|
+
OAI 5h78 R05:05
|
|
422
|
+
Cop M78 R04-01
|
|
423
|
+
Buzz B¥10.2
|
|
429
424
|
```
|
|
430
425
|
|
|
431
426
|
Balance-style quota:
|
|
432
427
|
|
|
433
428
|
```text
|
|
434
|
-
RC
|
|
429
|
+
RC B260
|
|
435
430
|
```
|
|
436
431
|
|
|
437
432
|
Buzz balance quota:
|
|
438
433
|
|
|
439
434
|
```text
|
|
440
|
-
Buzz
|
|
435
|
+
Buzz B¥10.17
|
|
441
436
|
```
|
|
442
437
|
|
|
443
438
|
Multi-detail quota (window + balance):
|
|
444
439
|
|
|
445
440
|
```text
|
|
446
|
-
RC
|
|
447
|
-
Daily $88.9/$60 Exp 02-27
|
|
448
|
-
Balance $260
|
|
441
|
+
RC D$88.9/$60 E02-27 B260
|
|
449
442
|
```
|
|
450
443
|
|
|
451
444
|
Provider status / quota (examples):
|
|
452
445
|
|
|
453
446
|
```text
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
### Legacy compact single-line layout
|
|
460
|
-
|
|
461
|
-
For historical reference, the old non-desktop compact layout looked like this:
|
|
462
|
-
|
|
463
|
-
```text
|
|
464
|
-
<base> | Req ... | Input ... | Output ... | OpenAI 5h 78%+ | Copilot Monthly 78% | ...
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
Mixed with Buzz balance:
|
|
468
|
-
|
|
469
|
-
```text
|
|
470
|
-
<base> | Req ... | Input ... | Output ... | OpenAI 5h 78%+ | Copilot Monthly 78% | Buzz Balance ¥10.2
|
|
447
|
+
Ant 5h80
|
|
448
|
+
Cop unavailable
|
|
449
|
+
OAI ?
|
|
471
450
|
```
|
|
472
451
|
|
|
473
452
|
### Desktop compact mode
|
|
474
453
|
|
|
475
|
-
Desktop always uses a compact single-line title. Recently used providers are selected from the last `50` assistant requests or last `60` minutes, and each selected provider expands all of its windows and balances in shorthand. To survive upstream Desktop truncation better, quota segments are emitted before usage
|
|
454
|
+
Desktop always uses a compact monitoring-style single-line title. Recently used providers are selected from the last `50` assistant requests or last `60` minutes, and each selected provider expands all of its windows and balances in shorthand. To survive upstream Desktop truncation better, quota segments are emitted before usage summary signals:
|
|
476
455
|
|
|
477
456
|
```text
|
|
478
|
-
<base> | OAI 5h80 W70 | Cop M78 | RC D88.9/60 B260 | Buzz B¥10.2 |
|
|
457
|
+
<base> | OAI 5h80 R16:20 W70 R04-03 | Cop M78 R04-01 | RC D88.9/60 B260 | Buzz B¥10.2 | Cd66% | Est$0.12
|
|
479
458
|
```
|
|
480
459
|
|
|
481
460
|
Shorthand rules:
|
|
482
461
|
|
|
483
|
-
- `R12` = 12 requests
|
|
484
|
-
- `I18.9k` / `O53` = input / output tokens
|
|
485
462
|
- `5h80` = `5h 80%`
|
|
486
463
|
- `W70` / `M78` / `D46` = weekly / monthly / daily window remaining percent
|
|
464
|
+
- `R16:20` / `R04-03` = reset time/date for that quota window
|
|
487
465
|
- `D88.9/60` = daily remaining / daily total
|
|
488
466
|
- `B260` / `B¥10.2` = balance
|
|
489
|
-
-
|
|
467
|
+
- `Cd66%` = cached ratio (`cache.read / (input + cache.read)`)
|
|
468
|
+
- `Est$0.12` = equivalent API cost estimate
|
|
469
|
+
- Desktop omits `R/I/O/CR/CW`; TUI keeps the full compact multiline breakdown.
|
|
470
|
+
- Order is `base | quota... | usage-summary`, while TUI keeps its multiline usage-first layout.
|
|
490
471
|
|
|
491
472
|
`quota_summary` also supports an optional `includeChildren` flag (only effective for `period=session`) to override the config per call. For `day`/`week`/`month` periods, children are never merged — each session is counted independently.
|
|
492
473
|
|
package/dist/cost.d.ts
CHANGED
|
@@ -16,6 +16,9 @@ export type ModelCostRates = {
|
|
|
16
16
|
};
|
|
17
17
|
export declare function modelCostKey(providerID: string, modelID: string): string;
|
|
18
18
|
export declare function modelCostLookupKeys(providerID: string, modelID: string): string[];
|
|
19
|
+
export declare function getBundledModelCostMap(): {
|
|
20
|
+
[x: string]: ModelCostRates;
|
|
21
|
+
};
|
|
19
22
|
export declare function parseModelCostRates(value: unknown): ModelCostRates | undefined;
|
|
20
23
|
export declare function guessModelCostDivisor(rates: ModelCostRates): 1 | 1000000;
|
|
21
24
|
export declare function cacheCoverageModeFromRates(rates: ModelCostRates | undefined): CacheCoverageMode;
|
package/dist/cost.js
CHANGED
|
@@ -10,25 +10,41 @@ const MODEL_COST_RATE_ALIASES = {
|
|
|
10
10
|
};
|
|
11
11
|
function anthropicModelAliases(modelID) {
|
|
12
12
|
const aliases = [];
|
|
13
|
+
const queue = [];
|
|
13
14
|
const push = (value) => {
|
|
14
15
|
if (!value)
|
|
15
16
|
return;
|
|
16
|
-
if (!aliases.includes(value))
|
|
17
|
+
if (!aliases.includes(value)) {
|
|
17
18
|
aliases.push(value);
|
|
19
|
+
queue.push(value);
|
|
20
|
+
}
|
|
18
21
|
};
|
|
19
22
|
push(modelID);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
for (let index = 0; index < queue.length; index++) {
|
|
24
|
+
const stem = queue[index];
|
|
25
|
+
const withoutProviderPrefix = stem
|
|
26
|
+
.replace(/^(?:[a-z]+\.)*anthropic\./, '')
|
|
27
|
+
.replace(/^anthropic[/.]/, '');
|
|
28
|
+
push(withoutProviderPrefix);
|
|
29
|
+
push(`anthropic/${withoutProviderPrefix}`);
|
|
30
|
+
const withoutVersionSuffix = withoutProviderPrefix.replace(/-v\d+(?::\d+)?$/, '');
|
|
31
|
+
push(withoutVersionSuffix);
|
|
32
|
+
push(`anthropic/${withoutVersionSuffix}`);
|
|
33
|
+
const atDate = withoutVersionSuffix.replace(/@(\d{8})$/, '-$1');
|
|
34
|
+
push(atDate);
|
|
35
|
+
push(`anthropic/${atDate}`);
|
|
36
|
+
const withAtDate = withoutVersionSuffix.replace(/-(\d{8})$/, '@$1');
|
|
37
|
+
push(withAtDate);
|
|
38
|
+
push(`anthropic/${withAtDate}`);
|
|
39
|
+
const withoutThinkingSuffix = withoutVersionSuffix.replace(/-thinking$/, '');
|
|
26
40
|
push(withoutThinkingSuffix);
|
|
27
41
|
push(`anthropic/${withoutThinkingSuffix}`);
|
|
28
42
|
const withoutLatestSuffix = withoutThinkingSuffix.replace(/-latest$/, '');
|
|
29
43
|
push(withoutLatestSuffix);
|
|
30
44
|
push(`anthropic/${withoutLatestSuffix}`);
|
|
31
|
-
const withoutDateSuffix = withoutLatestSuffix
|
|
45
|
+
const withoutDateSuffix = withoutLatestSuffix
|
|
46
|
+
.replace(/-\d{8}$/, '')
|
|
47
|
+
.replace(/@\d{8}$/, '');
|
|
32
48
|
push(withoutDateSuffix);
|
|
33
49
|
push(`anthropic/${withoutDateSuffix}`);
|
|
34
50
|
const dotted = withoutDateSuffix.replace(/(\d)-(\d)(?=-|$)/g, '$1.$2');
|
|
@@ -59,6 +75,100 @@ export function canonicalApiCostProviderID(providerID) {
|
|
|
59
75
|
}
|
|
60
76
|
return normalized;
|
|
61
77
|
}
|
|
78
|
+
function anthropicPricing(input, output, options) {
|
|
79
|
+
// OpenCode currently reports zero Anthropic model prices in runtime metadata,
|
|
80
|
+
// so keep a bundled fallback sourced from Anthropic's pricing docs.
|
|
81
|
+
return {
|
|
82
|
+
input,
|
|
83
|
+
output,
|
|
84
|
+
cacheRead: input * 0.1,
|
|
85
|
+
// OpenCode only exposes aggregate cache.write tokens, so use Anthropic's
|
|
86
|
+
// default 5-minute prompt-caching write rate.
|
|
87
|
+
cacheWrite: input * 1.25,
|
|
88
|
+
contextOver200k: options?.longContextInput !== undefined &&
|
|
89
|
+
options?.longContextOutput !== undefined
|
|
90
|
+
? {
|
|
91
|
+
input: options.longContextInput,
|
|
92
|
+
output: options.longContextOutput,
|
|
93
|
+
cacheRead: options.longContextInput * 0.1,
|
|
94
|
+
cacheWrite: options.longContextInput * 1.25,
|
|
95
|
+
}
|
|
96
|
+
: undefined,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const BUNDLED_MODEL_COST_RATES = [
|
|
100
|
+
{
|
|
101
|
+
providerID: 'anthropic',
|
|
102
|
+
modelID: 'claude-opus-4-6',
|
|
103
|
+
rates: anthropicPricing(5, 25),
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
providerID: 'anthropic',
|
|
107
|
+
modelID: 'claude-opus-4-5',
|
|
108
|
+
rates: anthropicPricing(5, 25),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
providerID: 'anthropic',
|
|
112
|
+
modelID: 'claude-opus-4-1',
|
|
113
|
+
rates: anthropicPricing(15, 75),
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
providerID: 'anthropic',
|
|
117
|
+
modelID: 'claude-opus-4',
|
|
118
|
+
rates: anthropicPricing(15, 75),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
providerID: 'anthropic',
|
|
122
|
+
modelID: 'claude-sonnet-4-6',
|
|
123
|
+
rates: anthropicPricing(3, 15),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
providerID: 'anthropic',
|
|
127
|
+
modelID: 'claude-sonnet-4-5',
|
|
128
|
+
rates: anthropicPricing(3, 15, {
|
|
129
|
+
longContextInput: 6,
|
|
130
|
+
longContextOutput: 22.5,
|
|
131
|
+
}),
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
providerID: 'anthropic',
|
|
135
|
+
modelID: 'claude-sonnet-4',
|
|
136
|
+
rates: anthropicPricing(3, 15, {
|
|
137
|
+
longContextInput: 6,
|
|
138
|
+
longContextOutput: 22.5,
|
|
139
|
+
}),
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
providerID: 'anthropic',
|
|
143
|
+
modelID: 'claude-3-7-sonnet',
|
|
144
|
+
rates: anthropicPricing(3, 15),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
providerID: 'anthropic',
|
|
148
|
+
modelID: 'claude-3-5-sonnet',
|
|
149
|
+
rates: anthropicPricing(3, 15),
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
providerID: 'anthropic',
|
|
153
|
+
modelID: 'claude-haiku-4-5',
|
|
154
|
+
rates: anthropicPricing(1, 5),
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
providerID: 'anthropic',
|
|
158
|
+
modelID: 'claude-3-5-haiku',
|
|
159
|
+
rates: anthropicPricing(0.8, 4),
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
providerID: 'anthropic',
|
|
163
|
+
modelID: 'claude-3-opus',
|
|
164
|
+
rates: anthropicPricing(15, 75),
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
providerID: 'anthropic',
|
|
168
|
+
modelID: 'claude-3-haiku',
|
|
169
|
+
rates: anthropicPricing(0.25, 1.25),
|
|
170
|
+
},
|
|
171
|
+
];
|
|
62
172
|
export function modelCostKey(providerID, modelID) {
|
|
63
173
|
return `${providerID}:${modelID}`;
|
|
64
174
|
}
|
|
@@ -85,6 +195,19 @@ export function modelCostLookupKeys(providerID, modelID) {
|
|
|
85
195
|
}
|
|
86
196
|
return keys;
|
|
87
197
|
}
|
|
198
|
+
function createBundledModelCostMap() {
|
|
199
|
+
const map = {};
|
|
200
|
+
for (const entry of BUNDLED_MODEL_COST_RATES) {
|
|
201
|
+
for (const key of modelCostLookupKeys(entry.providerID, entry.modelID)) {
|
|
202
|
+
map[key] = entry.rates;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return map;
|
|
206
|
+
}
|
|
207
|
+
const BUNDLED_MODEL_COST_MAP = createBundledModelCostMap();
|
|
208
|
+
export function getBundledModelCostMap() {
|
|
209
|
+
return { ...BUNDLED_MODEL_COST_MAP };
|
|
210
|
+
}
|
|
88
211
|
export function parseModelCostRates(value) {
|
|
89
212
|
if (!isRecord(value))
|
|
90
213
|
return undefined;
|
package/dist/format.js
CHANGED
|
@@ -207,7 +207,7 @@ function compactProviderLabel(quota) {
|
|
|
207
207
|
if (canonical === 'rightcode')
|
|
208
208
|
return 'RC';
|
|
209
209
|
if (canonical === 'xyai-vibe')
|
|
210
|
-
return '
|
|
210
|
+
return 'XYAI';
|
|
211
211
|
if (canonical === 'buzz')
|
|
212
212
|
return 'Buzz';
|
|
213
213
|
return sanitizeLine(quotaDisplayLabel(quota));
|
|
@@ -226,6 +226,78 @@ function compactWindowToken(label) {
|
|
|
226
226
|
return 'D';
|
|
227
227
|
return safe;
|
|
228
228
|
}
|
|
229
|
+
function compactQuotaResetToken(resetLabel) {
|
|
230
|
+
const safe = sanitizeLine(resetLabel || '');
|
|
231
|
+
if (!safe || /^rst$/i.test(safe))
|
|
232
|
+
return 'R';
|
|
233
|
+
if (/^exp\+$/i.test(safe))
|
|
234
|
+
return 'E+';
|
|
235
|
+
if (/^exp$/i.test(safe))
|
|
236
|
+
return 'E';
|
|
237
|
+
return safe;
|
|
238
|
+
}
|
|
239
|
+
function compactQuotaPercentToken(label, percent) {
|
|
240
|
+
const rounded = percent !== undefined && Number.isFinite(percent)
|
|
241
|
+
? `${Math.round(percent)}`
|
|
242
|
+
: '';
|
|
243
|
+
const safe = sanitizeLine(label || '');
|
|
244
|
+
if (!safe)
|
|
245
|
+
return rounded ? `R${rounded}` : '';
|
|
246
|
+
if (/^sonnet\s+7d$/i.test(safe))
|
|
247
|
+
return rounded ? `S7d${rounded}` : 'S7d';
|
|
248
|
+
const token = compactWindowToken(safe).replace(/\s+/g, '');
|
|
249
|
+
if (!rounded)
|
|
250
|
+
return token;
|
|
251
|
+
if (/^(?:D|W|M|\d+[hdw])$/i.test(token))
|
|
252
|
+
return `${token}${rounded}`;
|
|
253
|
+
return `${token} ${rounded}%`;
|
|
254
|
+
}
|
|
255
|
+
function compactQuotaWindowText(win) {
|
|
256
|
+
const reset = compactReset(win.resetAt, win.resetLabel, win.label);
|
|
257
|
+
const resetToken = reset
|
|
258
|
+
? `${compactQuotaResetToken(win.resetLabel)}${reset}`
|
|
259
|
+
: undefined;
|
|
260
|
+
if (win.showPercent === false) {
|
|
261
|
+
const safe = sanitizeLine(win.label || '');
|
|
262
|
+
const daily = safe ? safe.replace(/^Daily\s+/i, 'D') : '';
|
|
263
|
+
return [daily, resetToken].filter(Boolean).join(' ');
|
|
264
|
+
}
|
|
265
|
+
const percentToken = compactQuotaPercentToken(win.label, win.remainingPercent);
|
|
266
|
+
return [percentToken, resetToken].filter(Boolean).join(' ');
|
|
267
|
+
}
|
|
268
|
+
function compactQuotaWindowTokens(win) {
|
|
269
|
+
const reset = compactReset(win.resetAt, win.resetLabel, win.label);
|
|
270
|
+
const resetToken = reset
|
|
271
|
+
? `${compactQuotaResetToken(win.resetLabel)}${reset}`
|
|
272
|
+
: undefined;
|
|
273
|
+
if (win.showPercent === false) {
|
|
274
|
+
const safe = sanitizeLine(win.label || '');
|
|
275
|
+
const daily = safe ? safe.replace(/^Daily\s+/i, 'D') : '';
|
|
276
|
+
return [daily, resetToken].filter((value) => Boolean(value));
|
|
277
|
+
}
|
|
278
|
+
const percentToken = compactQuotaPercentToken(win.label, win.remainingPercent);
|
|
279
|
+
return [percentToken, resetToken].filter((value) => Boolean(value));
|
|
280
|
+
}
|
|
281
|
+
function compactQuotaBalanceText(balance) {
|
|
282
|
+
return `B${compactDesktopCurrencyValue(balance.amount, balance.currency)}`;
|
|
283
|
+
}
|
|
284
|
+
function packInlineTokens(label, tokens, width, indent = ' ') {
|
|
285
|
+
if (tokens.length === 0)
|
|
286
|
+
return [label];
|
|
287
|
+
const lines = [];
|
|
288
|
+
let current = label;
|
|
289
|
+
for (const token of tokens) {
|
|
290
|
+
const candidate = `${current} ${token}`;
|
|
291
|
+
if (stringCellWidth(candidate) <= width || current === label) {
|
|
292
|
+
current = candidate;
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
lines.push(current);
|
|
296
|
+
current = `${indent}${token}`;
|
|
297
|
+
}
|
|
298
|
+
lines.push(current);
|
|
299
|
+
return lines;
|
|
300
|
+
}
|
|
229
301
|
function compactDesktopCurrencyValue(value, currency) {
|
|
230
302
|
const rendered = formatCurrency(value, currency);
|
|
231
303
|
if (currency === '$')
|
|
@@ -243,23 +315,7 @@ function compactDesktopQuotaSegment(quota) {
|
|
|
243
315
|
let hasBalanceToken = false;
|
|
244
316
|
if (quota.windows && quota.windows.length > 0) {
|
|
245
317
|
for (const win of quota.windows) {
|
|
246
|
-
|
|
247
|
-
if (win.showPercent === false) {
|
|
248
|
-
const daily = winLabel.match(/^Daily\s+\$?([\d.,]+)\/\$?([\d.,]+)/i);
|
|
249
|
-
if (daily) {
|
|
250
|
-
parts.push(`D${daily[1]}/${daily[2]}`);
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
if (winLabel)
|
|
254
|
-
parts.push(winLabel.replace(/^Daily\s+/i, 'D'));
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
const percent = win.remainingPercent !== undefined &&
|
|
258
|
-
Number.isFinite(win.remainingPercent)
|
|
259
|
-
? `${compactWindowToken(winLabel)}${Math.round(win.remainingPercent)}`
|
|
260
|
-
: compactWindowToken(winLabel);
|
|
261
|
-
if (percent)
|
|
262
|
-
parts.push(percent);
|
|
318
|
+
parts.push(...compactQuotaWindowTokens(win));
|
|
263
319
|
}
|
|
264
320
|
}
|
|
265
321
|
else if (quota.balance) {
|
|
@@ -283,10 +339,15 @@ function renderDesktopCompactTitle(baseTitle, usage, quotas, config, _width) {
|
|
|
283
339
|
.filter((quota) => selectedProviderIDs.has(quota.providerID))
|
|
284
340
|
.map(compactDesktopQuotaSegment)
|
|
285
341
|
.filter(Boolean);
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
342
|
+
const cacheMetrics = getCacheCoverageMetrics(usage);
|
|
343
|
+
const usageSegments = [];
|
|
344
|
+
if (cacheMetrics.cachedRatio !== undefined) {
|
|
345
|
+
usageSegments.push(`Cd${formatPercent(cacheMetrics.cachedRatio, 0)}`);
|
|
346
|
+
}
|
|
347
|
+
if (config.sidebar.showCost && usage.apiCost > 0) {
|
|
348
|
+
usageSegments.push(`Est${formatApiCostValue(usage.apiCost)}`);
|
|
349
|
+
}
|
|
350
|
+
const segments = [...quotaSegments, ...usageSegments];
|
|
290
351
|
const detail = segments.join(' | ');
|
|
291
352
|
const safeBase = sanitizeLine(baseTitle) || 'Session';
|
|
292
353
|
if (!detail)
|
|
@@ -365,17 +426,14 @@ function renderSingleLineTitle(baseTitle, usage, quotas, config, width) {
|
|
|
365
426
|
formatRequestsLabel(usage.assistantMessages, true),
|
|
366
427
|
`Input ${sidebarNumber(usage.input)} Output ${sidebarNumber(usage.output)}`,
|
|
367
428
|
];
|
|
368
|
-
if (usage.cacheRead > 0) {
|
|
369
|
-
segments.push(`Cache Read ${sidebarNumber(usage.cacheRead)}`);
|
|
370
|
-
}
|
|
371
429
|
if (usage.cacheWrite > 0) {
|
|
372
430
|
segments.push(`Cache Write ${sidebarNumber(usage.cacheWrite)}`);
|
|
373
431
|
}
|
|
374
|
-
if (
|
|
375
|
-
segments.push(`Cache
|
|
432
|
+
if (usage.cacheRead > 0) {
|
|
433
|
+
segments.push(`Cache Read ${sidebarNumber(usage.cacheRead)}`);
|
|
376
434
|
}
|
|
377
|
-
if (cacheMetrics.
|
|
378
|
-
segments.push(`
|
|
435
|
+
if (cacheMetrics.cachedRatio !== undefined) {
|
|
436
|
+
segments.push(`Cached ${formatPercent(cacheMetrics.cachedRatio, 0)}`);
|
|
379
437
|
}
|
|
380
438
|
if (config.sidebar.showCost && usage.apiCost > 0) {
|
|
381
439
|
segments.push(formatApiCostLine(usage.apiCost));
|
|
@@ -413,34 +471,21 @@ export function renderSidebarTitle(baseTitle, usage, quotas, config) {
|
|
|
413
471
|
lines.push(fitLine(line || 'Session', width));
|
|
414
472
|
}
|
|
415
473
|
lines.push('');
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (usage.cacheRead > 0) {
|
|
422
|
-
lines.push(fitLine(`Cache Read ${sidebarNumber(usage.cacheRead)}`, width));
|
|
423
|
-
}
|
|
424
|
-
if (usage.cacheWrite > 0) {
|
|
425
|
-
lines.push(fitLine(`Cache Write ${sidebarNumber(usage.cacheWrite)}`, width));
|
|
426
|
-
}
|
|
427
|
-
if (cacheMetrics.cacheCoverage !== undefined) {
|
|
428
|
-
lines.push(fitLine(`Cache Coverage ${formatPercent(cacheMetrics.cacheCoverage, 0)}`, width));
|
|
429
|
-
}
|
|
430
|
-
if (cacheMetrics.cacheReadCoverage !== undefined) {
|
|
431
|
-
lines.push(fitLine(`Cache Read Coverage ${formatPercent(cacheMetrics.cacheReadCoverage, 0)}`, width));
|
|
432
|
-
}
|
|
433
|
-
if (config.sidebar.showCost && usage.apiCost > 0) {
|
|
434
|
-
lines.push(fitLine(formatApiCostLine(usage.apiCost), width));
|
|
474
|
+
for (const detailLine of usageDetailLines(usage, cacheMetrics, {
|
|
475
|
+
width,
|
|
476
|
+
showCost: config.sidebar.showCost,
|
|
477
|
+
})) {
|
|
478
|
+
lines.push(fitLine(detailLine, width));
|
|
435
479
|
}
|
|
436
480
|
// Quota lines (one provider per line for stable wrapping)
|
|
437
481
|
if (config.sidebar.showQuota) {
|
|
438
482
|
const visibleQuotas = collapseQuotaSnapshots(quotas).filter((q) => ['ok', 'error', 'unsupported', 'unavailable'].includes(q.status));
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const forceWrappedProviders = visibleQuotas.length > 1;
|
|
483
|
+
const compactQuotaDetails = true;
|
|
484
|
+
const forceWrappedProviders = false;
|
|
442
485
|
const labelWidth = visibleQuotas.reduce((max, item) => {
|
|
443
|
-
const label =
|
|
486
|
+
const label = compactQuotaDetails
|
|
487
|
+
? compactProviderLabel(item)
|
|
488
|
+
: sanitizeLine(quotaDisplayLabel(item));
|
|
444
489
|
return Math.max(max, stringCellWidth(label));
|
|
445
490
|
}, 0);
|
|
446
491
|
const quotaItems = visibleQuotas
|
|
@@ -448,6 +493,7 @@ export function renderSidebarTitle(baseTitle, usage, quotas, config) {
|
|
|
448
493
|
width,
|
|
449
494
|
wrapLines: config.sidebar.wrapQuotaLines,
|
|
450
495
|
forceWrapped: forceWrappedProviders,
|
|
496
|
+
compactDetails: compactQuotaDetails,
|
|
451
497
|
}))
|
|
452
498
|
.filter((s) => Boolean(s));
|
|
453
499
|
if (quotaItems.length > 0) {
|
|
@@ -457,6 +503,49 @@ export function renderSidebarTitle(baseTitle, usage, quotas, config) {
|
|
|
457
503
|
lines.push(fitLine(line, width));
|
|
458
504
|
}
|
|
459
505
|
}
|
|
506
|
+
function fitsLine(value, width) {
|
|
507
|
+
return stringCellWidth(sanitizeLine(value)) <= width;
|
|
508
|
+
}
|
|
509
|
+
function usageDetailLines(usage, cacheMetrics, options) {
|
|
510
|
+
const width = options.width;
|
|
511
|
+
const groups = [];
|
|
512
|
+
groups.push([
|
|
513
|
+
`R${shortNumber(usage.assistantMessages, 1)}`,
|
|
514
|
+
`I${sidebarNumber(usage.input)}`,
|
|
515
|
+
`O${sidebarNumber(usage.output)}`,
|
|
516
|
+
]);
|
|
517
|
+
const secondary = [];
|
|
518
|
+
if (usage.cacheWrite > 0) {
|
|
519
|
+
secondary.push(`CW${sidebarNumber(usage.cacheWrite)}`);
|
|
520
|
+
}
|
|
521
|
+
if (usage.cacheRead > 0) {
|
|
522
|
+
secondary.push(`CR${sidebarNumber(usage.cacheRead)}`);
|
|
523
|
+
}
|
|
524
|
+
if (cacheMetrics.cachedRatio !== undefined) {
|
|
525
|
+
secondary.push(`Cd${formatPercent(cacheMetrics.cachedRatio, 0)}`);
|
|
526
|
+
}
|
|
527
|
+
if (secondary.length > 0)
|
|
528
|
+
groups.push(secondary);
|
|
529
|
+
if (options.showCost && usage.apiCost > 0) {
|
|
530
|
+
groups.push([`Est${formatApiCostValue(usage.apiCost)}`]);
|
|
531
|
+
}
|
|
532
|
+
const packed = [];
|
|
533
|
+
for (const group of groups) {
|
|
534
|
+
let current = '';
|
|
535
|
+
for (const token of group) {
|
|
536
|
+
const candidate = current ? `${current} ${token}` : token;
|
|
537
|
+
if (!current || fitsLine(candidate, width)) {
|
|
538
|
+
current = candidate;
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
packed.push(current);
|
|
542
|
+
current = token;
|
|
543
|
+
}
|
|
544
|
+
if (current)
|
|
545
|
+
packed.push(current);
|
|
546
|
+
}
|
|
547
|
+
return packed;
|
|
548
|
+
}
|
|
460
549
|
return lines.join('\n');
|
|
461
550
|
}
|
|
462
551
|
/**
|
|
@@ -476,7 +565,10 @@ export function renderSidebarTitle(baseTitle, usage, quotas, config) {
|
|
|
476
565
|
* " Balance $108.88"
|
|
477
566
|
*/
|
|
478
567
|
function compactQuotaWide(quota, labelWidth = 0, options) {
|
|
479
|
-
const
|
|
568
|
+
const compactDetails = options?.compactDetails === true;
|
|
569
|
+
const label = compactDetails
|
|
570
|
+
? compactProviderLabel(quota)
|
|
571
|
+
: sanitizeLine(quotaDisplayLabel(quota));
|
|
480
572
|
const labelPadded = padEndCells(label, labelWidth);
|
|
481
573
|
const detailIndent = ' ';
|
|
482
574
|
const withLabel = (content) => `${labelPadded} ${content}`;
|
|
@@ -501,9 +593,13 @@ function compactQuotaWide(quota, labelWidth = 0, options) {
|
|
|
501
593
|
if (quota.status !== 'ok')
|
|
502
594
|
return [];
|
|
503
595
|
const balanceText = quota.balance
|
|
504
|
-
?
|
|
596
|
+
? compactDetails
|
|
597
|
+
? compactQuotaBalanceText(quota.balance)
|
|
598
|
+
: `Balance ${formatCurrency(quota.balance.amount, quota.balance.currency)}`
|
|
505
599
|
: undefined;
|
|
506
600
|
const renderWindow = (win) => {
|
|
601
|
+
if (compactDetails)
|
|
602
|
+
return compactQuotaWindowText(win);
|
|
507
603
|
const showPercent = win.showPercent !== false;
|
|
508
604
|
const pct = formatQuotaPercent(win.remainingPercent, { rounded: true });
|
|
509
605
|
const parts = win.label
|
|
@@ -520,11 +616,20 @@ function compactQuotaWide(quota, labelWidth = 0, options) {
|
|
|
520
616
|
// Multi-window rendering
|
|
521
617
|
if (quota.windows && quota.windows.length > 0) {
|
|
522
618
|
const parts = quota.windows.map(renderWindow);
|
|
619
|
+
const compactTokens = compactDetails
|
|
620
|
+
? quota.windows.flatMap((win) => compactQuotaWindowTokens(win))
|
|
621
|
+
: [];
|
|
523
622
|
// Build the detail lines (window texts + optional balance)
|
|
524
623
|
const details = [...parts];
|
|
525
624
|
if (balanceText && !parts.some((p) => p.includes('Balance '))) {
|
|
526
625
|
details.push(balanceText);
|
|
527
626
|
}
|
|
627
|
+
if (compactDetails) {
|
|
628
|
+
const tokens = [...compactTokens];
|
|
629
|
+
if (balanceText)
|
|
630
|
+
tokens.push(balanceText);
|
|
631
|
+
return packInlineTokens(label, tokens, width, ' '.repeat(stringCellWidth(label) + 1));
|
|
632
|
+
}
|
|
528
633
|
// Keep a unified wrapped layout for providers that have multiple detail
|
|
529
634
|
// lines so OpenAI/Copilot/others match the RightCode multi-line style,
|
|
530
635
|
// regardless of wrapLines.
|
|
@@ -541,7 +646,11 @@ function compactQuotaWide(quota, labelWidth = 0, options) {
|
|
|
541
646
|
// Fallback: single value from top-level remainingPercent
|
|
542
647
|
const percent = formatQuotaPercent(quota.remainingPercent, { rounded: true });
|
|
543
648
|
const reset = compactReset(quota.resetAt, 'Rst');
|
|
544
|
-
const fallbackText =
|
|
649
|
+
const fallbackText = compactDetails
|
|
650
|
+
? [`R${percent.replace(/%$/, '')}`, reset ? `R${reset}` : undefined]
|
|
651
|
+
.filter(Boolean)
|
|
652
|
+
.join(' ')
|
|
653
|
+
: `Remaining ${percent}${reset ? ` Rst ${reset}` : ''}`;
|
|
545
654
|
return maybeBreak(fallbackText, [fallbackText]);
|
|
546
655
|
}
|
|
547
656
|
function isShortResetWindow(label) {
|
|
@@ -679,16 +788,10 @@ export function renderMarkdownReport(period, usage, quotas, options) {
|
|
|
679
788
|
return '-';
|
|
680
789
|
return formatApiCostValue(usage.apiCost);
|
|
681
790
|
};
|
|
682
|
-
const
|
|
683
|
-
const metrics = getProviderCacheCoverageMetrics(provider);
|
|
684
|
-
return metrics.cacheCoverage !== undefined
|
|
685
|
-
? formatPercent(metrics.cacheCoverage, 1)
|
|
686
|
-
: '-';
|
|
687
|
-
};
|
|
688
|
-
const cacheReadCoverageCell = (provider) => {
|
|
791
|
+
const cachedCell = (provider) => {
|
|
689
792
|
const metrics = getProviderCacheCoverageMetrics(provider);
|
|
690
|
-
return metrics.
|
|
691
|
-
? formatPercent(metrics.
|
|
793
|
+
return metrics.cachedRatio !== undefined
|
|
794
|
+
? formatPercent(metrics.cachedRatio, 1)
|
|
692
795
|
: '-';
|
|
693
796
|
};
|
|
694
797
|
const providerEntries = Object.values(usage.providers).sort((a, b) => b.total - a.total);
|
|
@@ -711,25 +814,15 @@ export function renderMarkdownReport(period, usage, quotas, options) {
|
|
|
711
814
|
checkedAt: 0,
|
|
712
815
|
})} (${formatUsd(topApiCost.apiCost)})`);
|
|
713
816
|
}
|
|
714
|
-
const
|
|
817
|
+
const bestCachedRatio = providerEntries
|
|
715
818
|
.map((provider) => ({
|
|
716
819
|
provider,
|
|
717
|
-
value: getProviderCacheCoverageMetrics(provider).
|
|
820
|
+
value: getProviderCacheCoverageMetrics(provider).cachedRatio,
|
|
718
821
|
}))
|
|
719
822
|
.filter((entry) => entry.value !== undefined)
|
|
720
823
|
.sort((a, b) => b.value - a.value)[0];
|
|
721
|
-
if (
|
|
722
|
-
lines.push(`- Best
|
|
723
|
-
}
|
|
724
|
-
const bestCacheReadCoverage = providerEntries
|
|
725
|
-
.map((provider) => ({
|
|
726
|
-
provider,
|
|
727
|
-
value: getProviderCacheCoverageMetrics(provider).cacheReadCoverage,
|
|
728
|
-
}))
|
|
729
|
-
.filter((entry) => entry.value !== undefined)
|
|
730
|
-
.sort((a, b) => b.value - a.value)[0];
|
|
731
|
-
if (bestCacheReadCoverage) {
|
|
732
|
-
lines.push(`- Best Cache Read Coverage: ${providerLabel(bestCacheReadCoverage.provider.providerID)} (${formatPercent(bestCacheReadCoverage.value, 1)})`);
|
|
824
|
+
if (bestCachedRatio) {
|
|
825
|
+
lines.push(`- Best Cached Ratio: ${providerLabel(bestCachedRatio.provider.providerID)} (${formatPercent(bestCachedRatio.value, 1)})`);
|
|
733
826
|
}
|
|
734
827
|
const highestMeasured = providerEntries
|
|
735
828
|
.filter((provider) => measuredCostCell(provider.providerID, provider.cost) !== '-')
|
|
@@ -742,9 +835,15 @@ export function renderMarkdownReport(period, usage, quotas, options) {
|
|
|
742
835
|
const providerRows = providerEntries.map((provider) => {
|
|
743
836
|
const providerID = mdCell(provider.providerID);
|
|
744
837
|
return showCost
|
|
745
|
-
? `| ${providerID} | ${shortNumber(provider.assistantMessages)} | ${shortNumber(provider.input)} | ${shortNumber(provider.output)} | ${shortNumber(provider.cacheRead + provider.cacheWrite)} | ${shortNumber(provider.total)} | ${
|
|
838
|
+
? `| ${providerID} | ${shortNumber(provider.assistantMessages)} | ${shortNumber(provider.input)} | ${shortNumber(provider.output)} | ${shortNumber(provider.cacheRead + provider.cacheWrite)} | ${shortNumber(provider.total)} | ${cachedCell(provider)} | ${measuredCostCell(provider.providerID, provider.cost)} | ${apiCostCell(provider.providerID, provider.apiCost)} |`
|
|
746
839
|
: `| ${providerID} | ${shortNumber(provider.assistantMessages)} | ${shortNumber(provider.input)} | ${shortNumber(provider.output)} | ${shortNumber(provider.cacheRead + provider.cacheWrite)} | ${shortNumber(provider.total)} |`;
|
|
747
840
|
});
|
|
841
|
+
const providerHeader = showCost
|
|
842
|
+
? '| Provider | Requests | Input | Output | Cache | Total | Cached | Measured Cost | API Cost |'
|
|
843
|
+
: '| Provider | Requests | Input | Output | Cache | Total |';
|
|
844
|
+
const providerDivider = showCost
|
|
845
|
+
? '| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |'
|
|
846
|
+
: '| --- | ---: | ---: | ---: | ---: | ---: |';
|
|
748
847
|
const quotaLines = collapseQuotaSnapshots(quotas).flatMap((quota) => {
|
|
749
848
|
const displayLabel = quotaDisplayLabel(quota);
|
|
750
849
|
// Multi-window detail
|
|
@@ -785,13 +884,8 @@ export function renderMarkdownReport(period, usage, quotas, options) {
|
|
|
785
884
|
`- Sessions: ${usage.sessionCount}`,
|
|
786
885
|
`- Requests: ${usage.assistantMessages}`,
|
|
787
886
|
`- Tokens: input ${usage.input}, output ${usage.output}, cache_read ${usage.cacheRead}, cache_write ${usage.cacheWrite}, total ${usage.total}`,
|
|
788
|
-
...(cacheMetrics.
|
|
789
|
-
? [`-
|
|
790
|
-
: []),
|
|
791
|
-
...(cacheMetrics.cacheReadCoverage !== undefined
|
|
792
|
-
? [
|
|
793
|
-
`- Cache Read Coverage: ${formatPercent(cacheMetrics.cacheReadCoverage, 1)}`,
|
|
794
|
-
]
|
|
887
|
+
...(cacheMetrics.cachedRatio !== undefined
|
|
888
|
+
? [`- Cached: ${formatPercent(cacheMetrics.cachedRatio, 1)}`]
|
|
795
889
|
: []),
|
|
796
890
|
...(showCost
|
|
797
891
|
? [
|
|
@@ -804,21 +898,19 @@ export function renderMarkdownReport(period, usage, quotas, options) {
|
|
|
804
898
|
: []),
|
|
805
899
|
'',
|
|
806
900
|
'### Usage by Provider',
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
showCost
|
|
811
|
-
? '|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|'
|
|
812
|
-
: '|---|---:|---:|---:|---:|---:|',
|
|
901
|
+
'',
|
|
902
|
+
providerHeader,
|
|
903
|
+
providerDivider,
|
|
813
904
|
...(providerRows.length
|
|
814
905
|
? providerRows
|
|
815
906
|
: [
|
|
816
907
|
showCost
|
|
817
|
-
? '| - | - | - | - | - | - | - | - | - |
|
|
908
|
+
? '| - | - | - | - | - | - | - | - | - |'
|
|
818
909
|
: '| - | - | - | - | - | - |',
|
|
819
910
|
]),
|
|
820
911
|
'',
|
|
821
912
|
'### Subscription Quota',
|
|
913
|
+
'',
|
|
822
914
|
...(quotaLines.length
|
|
823
915
|
? quotaLines
|
|
824
916
|
: ['- no provider quota data available']),
|
|
@@ -837,28 +929,22 @@ export function renderToastMessage(period, usage, quotas, options) {
|
|
|
837
929
|
{ label: 'Input', value: shortNumber(usage.input) },
|
|
838
930
|
{ label: 'Output', value: shortNumber(usage.output) },
|
|
839
931
|
];
|
|
840
|
-
if (usage.cacheRead > 0) {
|
|
841
|
-
tokenPairs.push({
|
|
842
|
-
label: 'Cache Read',
|
|
843
|
-
value: shortNumber(usage.cacheRead),
|
|
844
|
-
});
|
|
845
|
-
}
|
|
846
932
|
if (usage.cacheWrite > 0) {
|
|
847
933
|
tokenPairs.push({
|
|
848
934
|
label: 'Cache Write',
|
|
849
935
|
value: shortNumber(usage.cacheWrite),
|
|
850
936
|
});
|
|
851
937
|
}
|
|
852
|
-
if (
|
|
938
|
+
if (usage.cacheRead > 0) {
|
|
853
939
|
tokenPairs.push({
|
|
854
|
-
label: 'Cache
|
|
855
|
-
value:
|
|
940
|
+
label: 'Cache Read',
|
|
941
|
+
value: shortNumber(usage.cacheRead),
|
|
856
942
|
});
|
|
857
943
|
}
|
|
858
|
-
if (cacheMetrics.
|
|
944
|
+
if (cacheMetrics.cachedRatio !== undefined) {
|
|
859
945
|
tokenPairs.push({
|
|
860
|
-
label: '
|
|
861
|
-
value: formatPercent(cacheMetrics.
|
|
946
|
+
label: 'Cached',
|
|
947
|
+
value: formatPercent(cacheMetrics.cachedRatio, 1),
|
|
862
948
|
});
|
|
863
949
|
}
|
|
864
950
|
lines.push(...alignPairs(tokenPairs).map((line) => fitLine(line, width)));
|
|
@@ -884,18 +970,11 @@ export function renderToastMessage(period, usage, quotas, options) {
|
|
|
884
970
|
const providerCachePairs = Object.values(usage.providers)
|
|
885
971
|
.map((provider) => {
|
|
886
972
|
const metrics = getProviderCacheCoverageMetrics(provider);
|
|
887
|
-
|
|
888
|
-
if (metrics.cacheCoverage !== undefined) {
|
|
889
|
-
parts.push(`Cov ${formatPercent(metrics.cacheCoverage, 1)}`);
|
|
890
|
-
}
|
|
891
|
-
if (metrics.cacheReadCoverage !== undefined) {
|
|
892
|
-
parts.push(`Read ${formatPercent(metrics.cacheReadCoverage, 1)}`);
|
|
893
|
-
}
|
|
894
|
-
if (parts.length === 0)
|
|
973
|
+
if (metrics.cachedRatio === undefined)
|
|
895
974
|
return undefined;
|
|
896
975
|
return {
|
|
897
976
|
label: displayShortLabel(provider.providerID),
|
|
898
|
-
value:
|
|
977
|
+
value: `Cached ${formatPercent(metrics.cachedRatio, 1)}`,
|
|
899
978
|
};
|
|
900
979
|
})
|
|
901
980
|
.filter((item) => Boolean(item));
|
package/dist/title.js
CHANGED
|
@@ -6,6 +6,8 @@ function sanitizeTitleFragment(value) {
|
|
|
6
6
|
function isCoreDecoratedDetail(line) {
|
|
7
7
|
if (!line)
|
|
8
8
|
return false;
|
|
9
|
+
// Legacy coverage/Cov/CRC/CC tokens remain recognized so old decorated titles
|
|
10
|
+
// can still be detected and cleaned up after format migrations.
|
|
9
11
|
if (/^Requests\s+\$?\d[\d.,]*[kKmM]?$/.test(line))
|
|
10
12
|
return true;
|
|
11
13
|
if (/^Input\s+\$?[\d.,]+[kKmM]?(?:\s+Output(?:\s+\$?[\d.,]+[kKmM]?)?)?~?$/.test(line)) {
|
|
@@ -16,8 +18,20 @@ function isCoreDecoratedDetail(line) {
|
|
|
16
18
|
if (/^Cache(?:\s+Read)?\s+Coverage\s+\d[\d.,]*(?:%|~)?$/.test(line)) {
|
|
17
19
|
return true;
|
|
18
20
|
}
|
|
21
|
+
if (/^Cache(?:\s+Read|\s+R)?\s+Cov\s+\d[\d.,]*(?:%|~)?$/.test(line)) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
if (/^Cached\s+\d[\d.,]*(?:%|~)?$/.test(line))
|
|
25
|
+
return true;
|
|
19
26
|
if (/^\$\S+\s+as API cost$/.test(line))
|
|
20
27
|
return true;
|
|
28
|
+
if (/^API\s+\$\S+$/.test(line))
|
|
29
|
+
return true;
|
|
30
|
+
if (/^Est\$\S+$/.test(line))
|
|
31
|
+
return true;
|
|
32
|
+
if (/^(?:R\$?[\d.,]+[kKmM]?|I\$?[\d.,]+[kKmM]?|O\$?[\d.,]+[kKmM]?|CR\$?[\d.,]+[kKmM]?|CW\$?[\d.,]+[kKmM]?|Cd\d[\d.,]*%|CC\d[\d.,]*%|CRC\d[\d.,]*%|API\$\S+|Est\$\S+)(?:\s+(?:R\$?[\d.,]+[kKmM]?|I\$?[\d.,]+[kKmM]?|O\$?[\d.,]+[kKmM]?|CR\$?[\d.,]+[kKmM]?|CW\$?[\d.,]+[kKmM]?|Cd\d[\d.,]*%|CC\d[\d.,]*%|CRC\d[\d.,]*%|API\$\S+|Est\$\S+))*$/.test(line)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
21
35
|
// Single-line compact mode compatibility.
|
|
22
36
|
if (/^I(?:nput)?\s+\$?\d[\d.,]*[kKmM]?\s+O(?:utput)?\s+\$?\d[\d.,]*[kKmM]?$/.test(line))
|
|
23
37
|
return true;
|
|
@@ -31,12 +45,19 @@ function isCoreDecoratedDetail(line) {
|
|
|
31
45
|
if (/^C(?:ache(?:\s*R(?:ead)?)?)?\s*Coverage\s+\d[\d.,]*(?:%|~)?$/.test(line)) {
|
|
32
46
|
return true;
|
|
33
47
|
}
|
|
48
|
+
if (/^C(?:ache(?:\s*(?:R|Read))?)?\s*Cov\s+\d[\d.,]*(?:%|~)?$/.test(line)) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (/^Cached\s+\d[\d.,]*(?:%|~)?$/.test(line))
|
|
52
|
+
return true;
|
|
53
|
+
if (/^API\s+\$\S+$/.test(line))
|
|
54
|
+
return true;
|
|
34
55
|
return false;
|
|
35
56
|
}
|
|
36
57
|
function isQuotaDecoratedDetail(line) {
|
|
37
58
|
if (!line)
|
|
38
59
|
return false;
|
|
39
|
-
if (/^(OAI|Cop|Ant|Kimi|
|
|
60
|
+
if (/^(OAI|Cop|Ant|Kimi|XYAI|Buzz|RC(?:-[^\s]+)?)(?:\s+(?:\?|unsupported|unavailable|error|(?:\d+h|D|W|M)\d{1,3}|D[\d.,]+\/[\d.,]+|B(?:[¥$-])?[\d.,]+))+$/i.test(line)) {
|
|
40
61
|
return true;
|
|
41
62
|
}
|
|
42
63
|
if (/^(OpenAI|Copilot|Anthropic|Kimi|XYAI|Buzz|RC(?:-[^\s]+)?)\s*$/.test(line)) {
|
|
@@ -45,9 +66,21 @@ function isQuotaDecoratedDetail(line) {
|
|
|
45
66
|
if (/^(?:(?:Daily\s+\$[\d.,]+\/\$[\d.,]+|\$[\d.,]+\/\$[\d.,]+)(?:\s+(?:Rst|Exp\+?)\s+[-:\d]+)?|(?:\d+[hdw]|Weekly|Monthly)\s+\d{1,3}%(?:\s+Rst\s+[-:\d]+)?|Balance\s+\$[\d.,]+|Remaining\s+\?|(?:error|unsupported|unavailable))$/.test(line)) {
|
|
46
67
|
return true;
|
|
47
68
|
}
|
|
69
|
+
if (/^(?:D\$?[\d.,]+\/\$?[\d.,]+|B(?:[¥$-])?[\d.,]+|(?:\d+[hdw]|[DWM])\d{1,3}|S7d\d{1,3})(?:\s+(?:R|E\+?)\d[\d:.-]*)?$/.test(line)) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
48
72
|
if (/^(OpenAI|Copilot|Anthropic|Kimi|XYAI|Buzz|RC(?:-[^\s]+)?)(?:\s+(?:(?:Daily\s+\$[\d.,]+\/\$[\d.,]+|\$[\d.,]+\/\$[\d.,]+)(?:\s+(?:Rst|Exp\+?)\s+[-:\d]+)?|(?:\d+[hdw]|Weekly|Monthly)\s+\d{1,3}%(?:\s+Rst\s+[-:\d]+)?|(?:error|unsupported|unavailable)))$/.test(line)) {
|
|
49
73
|
return true;
|
|
50
74
|
}
|
|
75
|
+
if (/^(OpenAI|Copilot|Anthropic|Kimi|XYAI|Buzz|RC(?:-[^\s]+)?)(?:\s+(?:D\$?[\d.,]+\/\$?[\d.,]+|B(?:[¥$-])?[\d.,]+|(?:\d+[hdw]|[DWM])\d{1,3}|S7d\d{1,3})(?:\s+(?:R|E\+?)\d[\d:.-]*)?)$/.test(line)) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (/^(?:(?:D\$?[\d.,]+\/\$?[\d.,]+|B(?:[¥$-])?[\d.,]+|(?:\d+[hdw]|[DWM])\d{1,3}|S7d\d{1,3}|(?:R|E\+?)\d[\d:.-]*))(?:\s+(?:(?:D\$?[\d.,]+\/\$?[\d.,]+|B(?:[¥$-])?[\d.,]+|(?:\d+[hdw]|[DWM])\d{1,3}|S7d\d{1,3}|(?:R|E\+?)\d[\d:.-]*)))*$/.test(line)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
if (/^(OpenAI|Copilot|Anthropic|Kimi|XYAI|Buzz|RC(?:-[^\s]+)?)(?:\s+(?:(?:D\$?[\d.,]+\/\$?[\d.,]+|B(?:[¥$-])?[\d.,]+|(?:\d+[hdw]|[DWM])\d{1,3}|S7d\d{1,3}|(?:R|E\+?)\d[\d:.-]*)))*$/.test(line)) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
51
84
|
return false;
|
|
52
85
|
}
|
|
53
86
|
function isSingleLineDecoratedPrefix(line) {
|
|
@@ -69,8 +102,20 @@ function isSingleLineDecoratedPrefix(line) {
|
|
|
69
102
|
if (/^Cache(?:\s+Read)?\s+Coverage\s+\d[\d.,]*(?:%|~)$/.test(line)) {
|
|
70
103
|
return true;
|
|
71
104
|
}
|
|
105
|
+
if (/^Cache(?:\s+Read|\s+R)?\s+Cov\s+\d[\d.,]*(?:%|~)$/.test(line)) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (/^Cached\s+\d[\d.,]*(?:%|~)$/.test(line))
|
|
109
|
+
return true;
|
|
72
110
|
if (/^\$\S+\s+as API cost(?:~|$)/.test(line))
|
|
73
111
|
return true;
|
|
112
|
+
if (/^API\s+\$\S+(?:~|$)/.test(line))
|
|
113
|
+
return true;
|
|
114
|
+
if (/^Est\$\S+(?:~|$)/.test(line))
|
|
115
|
+
return true;
|
|
116
|
+
if (/^(?:R\$?[\d.,]+[kKmM]?|I\$?[\d.,]+[kKmM]?|O\$?[\d.,]+[kKmM]?|CR\$?[\d.,]+[kKmM]?|CW\$?[\d.,]+[kKmM]?|Cd\d[\d.,]*%|CC\d[\d.,]*%|CRC\d[\d.,]*%|API\$\S+|Est\$\S+)(?:\s+(?:R\$?[\d.,]+[kKmM]?|I\$?[\d.,]+[kKmM]?|O\$?[\d.,]+[kKmM]?|CR\$?[\d.,]+[kKmM]?|CW\$?[\d.,]+[kKmM]?|Cd\d[\d.,]*%|CC\d[\d.,]*%|CRC\d[\d.,]*%|API\$\S+|Est\$\S+))*?(?:~|$)/.test(line)) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
74
119
|
return false;
|
|
75
120
|
}
|
|
76
121
|
function isSingleLineDetailPrefix(line) {
|
package/dist/tools.js
CHANGED
|
@@ -11,7 +11,7 @@ export function createQuotaSidebarTools(deps) {
|
|
|
11
11
|
};
|
|
12
12
|
return {
|
|
13
13
|
quota_summary: tool({
|
|
14
|
-
description: 'Show usage and quota summary for session/day/week/month.',
|
|
14
|
+
description: 'Show usage and quota summary for session/day/week/month. Returns the full markdown report with totals, highlights, provider table, and subscription quota so callers can present the report directly to the user.',
|
|
15
15
|
args: {
|
|
16
16
|
period: z.enum(['session', 'day', 'week', 'month']).optional(),
|
|
17
17
|
toast: z.boolean().optional(),
|
|
@@ -42,7 +42,7 @@ export function createQuotaSidebarTools(deps) {
|
|
|
42
42
|
},
|
|
43
43
|
}),
|
|
44
44
|
quota_show: tool({
|
|
45
|
-
description: 'Toggle sidebar title display mode. When on, titles show token usage and quota; when off, titles revert to original.',
|
|
45
|
+
description: 'Toggle sidebar title display mode. When on, titles show token usage and quota; when off, titles revert to original. Returns a user-facing status message that callers should present directly.',
|
|
46
46
|
args: {
|
|
47
47
|
enabled: z
|
|
48
48
|
.boolean()
|
package/dist/types.d.ts
CHANGED
|
@@ -53,18 +53,15 @@ export type CacheUsageBuckets = {
|
|
|
53
53
|
readWrite: CacheUsageBucket;
|
|
54
54
|
};
|
|
55
55
|
/**
|
|
56
|
-
* Derived cache
|
|
56
|
+
* Derived cache metrics.
|
|
57
57
|
*
|
|
58
|
-
* - `
|
|
59
|
-
* (`
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* (`cacheRead / (input + cacheRead)`).
|
|
63
|
-
* Only defined when the read-only bucket has traffic.
|
|
58
|
+
* - `cachedRatio`: fraction of the observed input surface that was served from
|
|
59
|
+
* cache (`cacheRead / (input + cacheRead)`).
|
|
60
|
+
* This is an exact ratio over normalized message totals, not a theoretical
|
|
61
|
+
* cache hit rate.
|
|
64
62
|
*/
|
|
65
63
|
export type CacheCoverageMetrics = {
|
|
66
|
-
|
|
67
|
-
cacheReadCoverage: number | undefined;
|
|
64
|
+
cachedRatio: number | undefined;
|
|
68
65
|
};
|
|
69
66
|
export type RecentProviderEvent = {
|
|
70
67
|
providerID: string;
|
|
@@ -81,7 +78,7 @@ export type CachedProviderUsage = {
|
|
|
81
78
|
/** Equivalent API billing cost (USD) computed from model pricing. */
|
|
82
79
|
apiCost: number;
|
|
83
80
|
assistantMessages: number;
|
|
84
|
-
/** Provider-level cache
|
|
81
|
+
/** Provider-level cache buckets grouped by model cache behavior. */
|
|
85
82
|
cacheBuckets?: CacheUsageBuckets;
|
|
86
83
|
};
|
|
87
84
|
export type CachedSessionUsage = {
|
|
@@ -98,7 +95,7 @@ export type CachedSessionUsage = {
|
|
|
98
95
|
apiCost: number;
|
|
99
96
|
assistantMessages: number;
|
|
100
97
|
/**
|
|
101
|
-
* Cache
|
|
98
|
+
* Cache buckets grouped by model cache behavior.
|
|
102
99
|
*
|
|
103
100
|
* `undefined` when no cache-capable models were used or data predates
|
|
104
101
|
* billingVersion 3. The fallback in `resolvedCacheUsageBuckets()` derives
|
|
@@ -150,9 +147,9 @@ export type QuotaSidebarConfig = {
|
|
|
150
147
|
enabled: boolean;
|
|
151
148
|
width: number;
|
|
152
149
|
/**
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
150
|
+
* Legacy switch retained for compatibility.
|
|
151
|
+
* TUI keeps a compact multiline sidebar layout; Desktop keeps a compact
|
|
152
|
+
* single-line layout.
|
|
156
153
|
*/
|
|
157
154
|
multilineTitle?: boolean;
|
|
158
155
|
showCost: boolean;
|
package/dist/usage.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { CacheCoverageMetrics, CacheCoverageMode, CacheUsageBuckets, Cached
|
|
|
6
6
|
* fields). This is distinct from the plugin *state* version managed by the
|
|
7
7
|
* persistence layer; billing version only governs usage-cache staleness.
|
|
8
8
|
*/
|
|
9
|
-
export declare const USAGE_BILLING_CACHE_VERSION =
|
|
9
|
+
export declare const USAGE_BILLING_CACHE_VERSION = 8;
|
|
10
10
|
export type ProviderUsage = {
|
|
11
11
|
providerID: string;
|
|
12
12
|
input: number;
|
package/dist/usage.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* fields). This is distinct from the plugin *state* version managed by the
|
|
5
5
|
* persistence layer; billing version only governs usage-cache staleness.
|
|
6
6
|
*/
|
|
7
|
-
export const USAGE_BILLING_CACHE_VERSION =
|
|
7
|
+
export const USAGE_BILLING_CACHE_VERSION = 8;
|
|
8
8
|
const MAX_RECENT_PROVIDER_EVENTS = 100;
|
|
9
9
|
function emptyCacheUsageBucket() {
|
|
10
10
|
return {
|
|
@@ -107,18 +107,11 @@ function resolvedCacheUsageBuckets(usage) {
|
|
|
107
107
|
return explicit;
|
|
108
108
|
}
|
|
109
109
|
export function getCacheCoverageMetrics(usage) {
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
buckets.readWrite.cacheRead +
|
|
113
|
-
buckets.readWrite.cacheWrite;
|
|
114
|
-
const readOnlyPromptSurface = buckets.readOnly.input + buckets.readOnly.cacheRead;
|
|
110
|
+
const hasCacheActivity = usage.cacheRead > 0 || usage.cacheWrite > 0;
|
|
111
|
+
const cachedSurface = usage.input + usage.cacheRead;
|
|
115
112
|
return {
|
|
116
|
-
|
|
117
|
-
?
|
|
118
|
-
readWritePromptSurface
|
|
119
|
-
: undefined,
|
|
120
|
-
cacheReadCoverage: readOnlyPromptSurface > 0
|
|
121
|
-
? buckets.readOnly.cacheRead / readOnlyPromptSurface
|
|
113
|
+
cachedRatio: hasCacheActivity && cachedSurface > 0
|
|
114
|
+
? usage.cacheRead / cachedSurface
|
|
122
115
|
: undefined,
|
|
123
116
|
};
|
|
124
117
|
}
|
package/dist/usage_service.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TtlValueCache } from './cache.js';
|
|
2
|
-
import { API_COST_ENABLED_PROVIDERS, cacheCoverageModeFromRates, calcEquivalentApiCostForMessage, canonicalApiCostProviderID, modelCostLookupKeys, modelCostKey, parseModelCostRates, } from './cost.js';
|
|
2
|
+
import { API_COST_ENABLED_PROVIDERS, cacheCoverageModeFromRates, calcEquivalentApiCostForMessage, canonicalApiCostProviderID, getBundledModelCostMap, modelCostLookupKeys, modelCostKey, parseModelCostRates, } from './cost.js';
|
|
3
3
|
import { deleteSessionFromDayChunk, dateKeyFromTimestamp, scanAllSessions, updateSessionsInDayChunks, } from './storage.js';
|
|
4
4
|
import { periodStart } from './period.js';
|
|
5
5
|
import { debug, debugError, isRecord, mapConcurrent, swallow, } from './helpers.js';
|
|
@@ -36,9 +36,10 @@ export function createUsageService(deps) {
|
|
|
36
36
|
const cached = modelCostCache.get();
|
|
37
37
|
if (cached)
|
|
38
38
|
return cached;
|
|
39
|
+
const fallbackMap = getBundledModelCostMap();
|
|
39
40
|
const providerClient = deps.client;
|
|
40
41
|
if (!providerClient.provider?.list) {
|
|
41
|
-
return modelCostCache.set(
|
|
42
|
+
return modelCostCache.set(fallbackMap, 30_000);
|
|
42
43
|
}
|
|
43
44
|
const response = await providerClient.provider
|
|
44
45
|
.list({
|
|
@@ -83,7 +84,7 @@ export function createUsageService(deps) {
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
return acc;
|
|
86
|
-
},
|
|
87
|
+
}, fallbackMap);
|
|
87
88
|
return modelCostCache.set(map, Math.max(30_000, deps.config.quota.refreshMs));
|
|
88
89
|
};
|
|
89
90
|
const calcEquivalentApiCost = (message, modelCostMap) => {
|
|
@@ -131,7 +132,7 @@ export function createUsageService(deps) {
|
|
|
131
132
|
return 'read-write';
|
|
132
133
|
}
|
|
133
134
|
// Last resort: if the message has cache.read tokens from an unknown provider,
|
|
134
|
-
// treat it as read-only (the safer default — avoids
|
|
135
|
+
// treat it as read-only (the safer default — avoids overstating cached ratio).
|
|
135
136
|
return 'read-only';
|
|
136
137
|
};
|
|
137
138
|
const isFiniteNumber = (value) => typeof value === 'number' && Number.isFinite(value);
|