@astrosheep/pi-quota 0.5.2 → 0.5.3
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/package.json +1 -1
- package/src/bar/balance.ts +14 -7
- package/src/bar/card.ts +1 -1
- package/src/bar/quota.ts +14 -17
- package/src/config.ts +3 -0
- package/src/extension.ts +6 -2
package/package.json
CHANGED
package/src/bar/balance.ts
CHANGED
|
@@ -4,21 +4,29 @@ import type { Paint, Span } from './bar.ts';
|
|
|
4
4
|
|
|
5
5
|
export function money(amount: number, currency: string): string {
|
|
6
6
|
const sign = amount < 0 ? '-' : '';
|
|
7
|
-
const
|
|
7
|
+
const symbol = currency === 'CNY' ? '¥' : currency === 'USD' ? '$' : `${currency} `;
|
|
8
8
|
const absolute = Math.abs(amount);
|
|
9
|
-
if (absolute > 0 && absolute < 0.01) return `${sign}<${
|
|
10
|
-
return `${sign}${
|
|
9
|
+
if (absolute > 0 && absolute < 0.01) return `${sign}<${symbol}0.01`;
|
|
10
|
+
return `${sign}${symbol}${absolute.toFixed(2)}`;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function remainingMoney(balance: AccountBalance): string {
|
|
14
|
-
if (balance.unlimited === true) return balance.currency === 'USD' ? '$∞' : `${balance.currency} ∞`;
|
|
14
|
+
if (balance.unlimited === true) return balance.currency === 'CNY' ? '¥∞' : balance.currency === 'USD' ? '$∞' : `${balance.currency} ∞`;
|
|
15
15
|
return money(balance.remaining, balance.currency);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export function compactMoney(amount: number, currency: string): string {
|
|
19
|
+
return money(amount, currency);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function compactRemainingMoney(balance: AccountBalance): string {
|
|
23
|
+
return remainingMoney(balance);
|
|
24
|
+
}
|
|
25
|
+
|
|
18
26
|
export function balanceSpans(balance: AccountBalance): Span[] {
|
|
19
27
|
return [
|
|
20
28
|
{ text: 'Bal ', tone: 'dim' },
|
|
21
|
-
{ text:
|
|
29
|
+
{ text: compactRemainingMoney(balance), tone: !balance.unlimited && balance.remaining <= 0 ? 'error' : 'text' },
|
|
22
30
|
];
|
|
23
31
|
}
|
|
24
32
|
|
|
@@ -35,7 +43,7 @@ export function allowanceSpans(allowance: QuotaAllowance): Span[] {
|
|
|
35
43
|
const remaining = allowancePercent(allowance);
|
|
36
44
|
return [
|
|
37
45
|
{ text: 'Quota ', tone: 'dim' },
|
|
38
|
-
{ text: `${verticalBar(remaining)} ${
|
|
46
|
+
{ text: `${verticalBar(remaining)} ${compactMoney(allowance.remaining, allowance.currency)}`, tone: remainingTone(remaining) },
|
|
39
47
|
];
|
|
40
48
|
}
|
|
41
49
|
|
|
@@ -52,6 +60,5 @@ export function allowanceLines(allowance: QuotaAllowance, paint: Paint, barWidth
|
|
|
52
60
|
export function spendLines(spend: SpendSummary, paint: Paint): string[] {
|
|
53
61
|
const lines: string[] = [];
|
|
54
62
|
if (spend.today !== undefined) lines.push(paint('dim', `Today ${money(spend.today, spend.currency)}`));
|
|
55
|
-
if (spend.lifetime !== undefined) lines.push(paint('dim', `Lifetime ${money(spend.lifetime, spend.currency)}`));
|
|
56
63
|
return lines;
|
|
57
64
|
}
|
package/src/bar/card.ts
CHANGED
|
@@ -38,7 +38,7 @@ export function usageCardComponent(card: UsageCard | undefined, paint?: Paint):
|
|
|
38
38
|
return new Text('Usage snapshot unavailable.', 0, 0).render(width);
|
|
39
39
|
}
|
|
40
40
|
// Rebuild on every render for theme/width changes, but freeze time at capture.
|
|
41
|
-
const lines = usageLines(card, paint, width < 55 ? 10 :
|
|
41
|
+
const lines = usageLines(card, paint, width < 55 ? 10 : 18);
|
|
42
42
|
// Wrapping, not clipping: long quota names remain readable in narrow terminals.
|
|
43
43
|
return new Text(lines.join('\n'), 0, 0).render(width);
|
|
44
44
|
},
|
package/src/bar/quota.ts
CHANGED
|
@@ -46,16 +46,14 @@ export function renderFooter(state: QuotaState, paint: Paint = plain, now = Date
|
|
|
46
46
|
return renderBar([quotaElement(state, now)], paint);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
const displayLabel: Record<string, string> = { deepseek: 'DeepSeek' };
|
|
50
|
+
|
|
51
|
+
export function renderUsage(state: QuotaState, paint: Paint = plain, now = Date.now(), barWidth = 16): string[] {
|
|
50
52
|
if (state.kind === 'hidden') return ['No quota adapter for the current provider.'];
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const title = `${state.label} · ${balanceOnly ? (state.snapshot.spend ? 'Account' : 'Balance')
|
|
56
|
-
: spendOnly ? 'Usage' : 'Remaining quota'}`;
|
|
57
|
-
if (state.kind === 'loading') return [title, '', 'Loading…'];
|
|
58
|
-
if (state.kind === 'error') return [title, '', errorText[state.code]];
|
|
53
|
+
const title = displayLabel[state.provider] ?? state.label;
|
|
54
|
+
const indent = (line: string) => line ? ` ${line}` : line;
|
|
55
|
+
if (state.kind === 'loading') return [title, '', indent('Loading…')];
|
|
56
|
+
if (state.kind === 'error') return [title, '', indent(errorText[state.code])];
|
|
59
57
|
const rows = state.snapshot.windows.map(window => {
|
|
60
58
|
const remaining = window.resetAt !== null && window.resetAt <= now ? null : window.remainingPercent;
|
|
61
59
|
return {
|
|
@@ -69,26 +67,25 @@ export function renderUsage(state: QuotaState, paint: Paint = plain, now = Date.
|
|
|
69
67
|
const labelWidth = Math.max(0, ...rows.map(row => visibleWidth(row.label)));
|
|
70
68
|
const valueWidth = Math.max(4, ...rows.map(row => visibleWidth(row.value)));
|
|
71
69
|
const lines = [title, '', ...rows.map(row => {
|
|
72
|
-
const label = row.label + ' '.repeat(labelWidth - visibleWidth(row.label));
|
|
73
|
-
const value = ' '.repeat(valueWidth - visibleWidth(row.value)) + row.value;
|
|
74
70
|
const tone = remainingTone(row.remaining);
|
|
75
|
-
|
|
71
|
+
const value = ' '.repeat(valueWidth - visibleWidth(row.value)) + row.value;
|
|
72
|
+
return indent(paint('dim', `${row.label}${' '.repeat(labelWidth - visibleWidth(row.label))} `)
|
|
76
73
|
+ paint(tone, `${horizontalBar(row.remaining, barWidth)} ${value}`)
|
|
77
74
|
+ paint('dim', ` ↺ ${row.reset}${row.amounts
|
|
78
75
|
? ` · ${money(row.amounts.remaining, row.amounts.currency)}/${money(row.amounts.limit, row.amounts.currency)}`
|
|
79
|
-
: ''}`);
|
|
76
|
+
: ''}`));
|
|
80
77
|
})];
|
|
81
78
|
if (state.snapshot.allowance) {
|
|
82
79
|
if (rows.length) lines.push('');
|
|
83
|
-
lines.push(...allowanceLines(state.snapshot.allowance, paint, barWidth));
|
|
80
|
+
lines.push(...allowanceLines(state.snapshot.allowance, paint, barWidth).map(indent));
|
|
84
81
|
}
|
|
85
82
|
if (state.snapshot.balance) {
|
|
86
83
|
if (rows.length || state.snapshot.allowance) lines.push('');
|
|
87
|
-
lines.push(...balanceLines(state.snapshot.balance, paint));
|
|
84
|
+
lines.push(...balanceLines(state.snapshot.balance, paint).map(indent));
|
|
88
85
|
}
|
|
89
|
-
if (state.snapshot.spend) {
|
|
86
|
+
if (state.snapshot.spend?.today !== undefined) {
|
|
90
87
|
if (rows.length || state.snapshot.allowance || state.snapshot.balance) lines.push('');
|
|
91
|
-
lines.push(...spendLines(state.snapshot.spend, paint));
|
|
88
|
+
lines.push(...spendLines(state.snapshot.spend, paint).map(indent));
|
|
92
89
|
}
|
|
93
90
|
return lines;
|
|
94
91
|
}
|
package/src/config.ts
CHANGED
|
@@ -77,3 +77,6 @@ export function loadQuotaAdaptersFromSettings(settings: unknown): QuotaAdapter[]
|
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
|
+
|
|
81
|
+
// Explicit adapter bindings from quotaUsage already have adapters registered; listing every
|
|
82
|
+
// registered adapter is the boundary between quota providers and ordinary model providers.
|
package/src/extension.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { renderFooter } from './bar/quota.ts';
|
|
|
3
3
|
import { captureAllUsage, USAGE_ENTRY, usageCardComponent, usageLines } from './bar/card.ts';
|
|
4
4
|
import type { UsageCard } from './bar/card.ts';
|
|
5
5
|
import { codexAdapter } from './query/codex.ts';
|
|
6
|
+
import { createDeepSeekAdapter } from './query/deepseek.ts';
|
|
6
7
|
import { kimiAdapter } from './query/kimi.ts';
|
|
7
8
|
import { openCodeGoAdapter } from './query/opencode-go.ts';
|
|
8
9
|
import { QuotaController } from './query/controller.ts';
|
|
@@ -16,6 +17,7 @@ const USAGE_WIDGET = 'quota-bar:usage';
|
|
|
16
17
|
|
|
17
18
|
export interface QuotaExtensionOptions {
|
|
18
19
|
adapters?: readonly QuotaAdapter[]; // Extra adapters; duplicate IDs are rejected.
|
|
20
|
+
providers?: readonly string[]; // Extra configured quota providers to include in /usage
|
|
19
21
|
footer?: boolean; // false: query + /usage + structured events only
|
|
20
22
|
intervalMs?: number;
|
|
21
23
|
timeoutMs?: number;
|
|
@@ -23,7 +25,8 @@ export interface QuotaExtensionOptions {
|
|
|
23
25
|
|
|
24
26
|
export function createQuotaExtension(options: QuotaExtensionOptions = {}) {
|
|
25
27
|
return (pi: ExtensionAPI): void => {
|
|
26
|
-
const registry = new AdapterRegistry([codexAdapter, kimiAdapter, openCodeGoAdapter,
|
|
28
|
+
const registry = new AdapterRegistry([codexAdapter, kimiAdapter, openCodeGoAdapter,
|
|
29
|
+
createDeepSeekAdapter('deepseek'), ...(options.adapters ?? [])]);
|
|
27
30
|
let current: ExtensionContext | undefined;
|
|
28
31
|
let selectedProvider: string | undefined;
|
|
29
32
|
let timer: ReturnType<typeof setInterval> | undefined;
|
|
@@ -97,7 +100,8 @@ export function createQuotaExtension(options: QuotaExtensionOptions = {}) {
|
|
|
97
100
|
}
|
|
98
101
|
};
|
|
99
102
|
add(ctx.model?.provider);
|
|
100
|
-
for (const
|
|
103
|
+
for (const adapter of registry.list()) add(adapter.provider);
|
|
104
|
+
for (const provider of options.providers ?? []) add(provider);
|
|
101
105
|
return providers;
|
|
102
106
|
};
|
|
103
107
|
|