@astrosheep/pi-quota 0.5.2 → 0.5.4
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 +12 -17
- package/src/config.ts +3 -0
- package/src/extension.ts +13 -3
- package/src/query/deepseek.ts +1 -1
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,12 @@ export function renderFooter(state: QuotaState, paint: Paint = plain, now = Date
|
|
|
46
46
|
return renderBar([quotaElement(state, now)], paint);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
export function renderUsage(state: QuotaState, paint: Paint = plain, now = Date.now(), barWidth =
|
|
49
|
+
export function renderUsage(state: QuotaState, paint: Paint = plain, now = Date.now(), barWidth = 16): string[] {
|
|
50
50
|
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]];
|
|
51
|
+
const title = state.label;
|
|
52
|
+
const indent = (line: string) => line ? ` ${line}` : line;
|
|
53
|
+
if (state.kind === 'loading') return [title, '', indent('Loading…')];
|
|
54
|
+
if (state.kind === 'error') return [title, '', indent(errorText[state.code])];
|
|
59
55
|
const rows = state.snapshot.windows.map(window => {
|
|
60
56
|
const remaining = window.resetAt !== null && window.resetAt <= now ? null : window.remainingPercent;
|
|
61
57
|
return {
|
|
@@ -69,26 +65,25 @@ export function renderUsage(state: QuotaState, paint: Paint = plain, now = Date.
|
|
|
69
65
|
const labelWidth = Math.max(0, ...rows.map(row => visibleWidth(row.label)));
|
|
70
66
|
const valueWidth = Math.max(4, ...rows.map(row => visibleWidth(row.value)));
|
|
71
67
|
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
68
|
const tone = remainingTone(row.remaining);
|
|
75
|
-
|
|
69
|
+
const value = ' '.repeat(valueWidth - visibleWidth(row.value)) + row.value;
|
|
70
|
+
return indent(paint('dim', `${row.label}${' '.repeat(labelWidth - visibleWidth(row.label))} `)
|
|
76
71
|
+ paint(tone, `${horizontalBar(row.remaining, barWidth)} ${value}`)
|
|
77
72
|
+ paint('dim', ` ↺ ${row.reset}${row.amounts
|
|
78
73
|
? ` · ${money(row.amounts.remaining, row.amounts.currency)}/${money(row.amounts.limit, row.amounts.currency)}`
|
|
79
|
-
: ''}`);
|
|
74
|
+
: ''}`));
|
|
80
75
|
})];
|
|
81
76
|
if (state.snapshot.allowance) {
|
|
82
77
|
if (rows.length) lines.push('');
|
|
83
|
-
lines.push(...allowanceLines(state.snapshot.allowance, paint, barWidth));
|
|
78
|
+
lines.push(...allowanceLines(state.snapshot.allowance, paint, barWidth).map(indent));
|
|
84
79
|
}
|
|
85
80
|
if (state.snapshot.balance) {
|
|
86
81
|
if (rows.length || state.snapshot.allowance) lines.push('');
|
|
87
|
-
lines.push(...balanceLines(state.snapshot.balance, paint));
|
|
82
|
+
lines.push(...balanceLines(state.snapshot.balance, paint).map(indent));
|
|
88
83
|
}
|
|
89
|
-
if (state.snapshot.spend) {
|
|
84
|
+
if (state.snapshot.spend?.today !== undefined) {
|
|
90
85
|
if (rows.length || state.snapshot.allowance || state.snapshot.balance) lines.push('');
|
|
91
|
-
lines.push(...spendLines(state.snapshot.spend, paint));
|
|
86
|
+
lines.push(...spendLines(state.snapshot.spend, paint).map(indent));
|
|
92
87
|
}
|
|
93
88
|
return lines;
|
|
94
89
|
}
|
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';
|
|
@@ -15,7 +16,9 @@ const STATUS_KEY = 'quota-bar';
|
|
|
15
16
|
const USAGE_WIDGET = 'quota-bar:usage';
|
|
16
17
|
|
|
17
18
|
export interface QuotaExtensionOptions {
|
|
18
|
-
|
|
19
|
+
// Explicit adapters replace matching built-ins; duplicate explicit IDs are rejected.
|
|
20
|
+
adapters?: readonly QuotaAdapter[];
|
|
21
|
+
providers?: readonly string[]; // Extra configured quota providers to include in /usage
|
|
19
22
|
footer?: boolean; // false: query + /usage + structured events only
|
|
20
23
|
intervalMs?: number;
|
|
21
24
|
timeoutMs?: number;
|
|
@@ -23,7 +26,13 @@ export interface QuotaExtensionOptions {
|
|
|
23
26
|
|
|
24
27
|
export function createQuotaExtension(options: QuotaExtensionOptions = {}) {
|
|
25
28
|
return (pi: ExtensionAPI): void => {
|
|
26
|
-
const
|
|
29
|
+
const explicitAdapters = options.adapters ?? [];
|
|
30
|
+
const explicitProviderIds = new Set(explicitAdapters.map(adapter => adapter.provider));
|
|
31
|
+
// Settings are deliberate user bindings, so they take precedence over defaults.
|
|
32
|
+
// AdapterRegistry still detects an ambiguous duplicate among explicit adapters.
|
|
33
|
+
const registry = new AdapterRegistry([
|
|
34
|
+
codexAdapter, kimiAdapter, openCodeGoAdapter, createDeepSeekAdapter('deepseek'),
|
|
35
|
+
].filter(adapter => !explicitProviderIds.has(adapter.provider)).concat(explicitAdapters));
|
|
27
36
|
let current: ExtensionContext | undefined;
|
|
28
37
|
let selectedProvider: string | undefined;
|
|
29
38
|
let timer: ReturnType<typeof setInterval> | undefined;
|
|
@@ -97,7 +106,8 @@ export function createQuotaExtension(options: QuotaExtensionOptions = {}) {
|
|
|
97
106
|
}
|
|
98
107
|
};
|
|
99
108
|
add(ctx.model?.provider);
|
|
100
|
-
for (const
|
|
109
|
+
for (const adapter of registry.list()) add(adapter.provider);
|
|
110
|
+
for (const provider of options.providers ?? []) add(provider);
|
|
101
111
|
return providers;
|
|
102
112
|
};
|
|
103
113
|
|
package/src/query/deepseek.ts
CHANGED
|
@@ -24,7 +24,7 @@ export function parseDeepSeekBalance(payload: unknown): AccountBalance {
|
|
|
24
24
|
|
|
25
25
|
export function createDeepSeekAdapter(provider: string): QuotaAdapter {
|
|
26
26
|
return {
|
|
27
|
-
provider, label: provider,
|
|
27
|
+
provider, label: provider === 'deepseek' ? 'DeepSeek' : provider,
|
|
28
28
|
async query(context) {
|
|
29
29
|
const auth = await officialAuth(context, DEEPSEEK_ORIGIN);
|
|
30
30
|
const payload = await context.getJson(`${DEEPSEEK_ORIGIN}/user/balance`,
|