@astrosheep/pi-quota 0.5.3 → 0.5.5
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 +5 -5
- package/package.json +1 -1
- package/src/bar/card.ts +1 -1
- package/src/bar/quota.ts +1 -3
- package/src/extension.ts +14 -6
- package/src/query/deepseek.ts +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# @astrosheep/pi-quota
|
|
2
2
|
|
|
3
|
-
Compact quota status for Pi: Codex, Kimi
|
|
3
|
+
Compact quota status for Pi: Codex, Kimi, OpenCode Go and DeepSeek work out of the box; new-api and Sub2API use explicit provider bindings.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
pi install npm:@astrosheep/pi-quota
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Reload Pi after installing or changing settings. Use `/usage` for a persistent, context-free snapshot
|
|
9
|
+
Reload Pi after installing or changing settings. Use `/usage` for a persistent, context-free snapshot of providers with available quota data, including the current one. Use `/usage --all` to inspect provider errors. Automatic footer polling never adds chat items.
|
|
10
10
|
|
|
11
11
|
## What it shows
|
|
12
12
|
|
|
@@ -18,7 +18,7 @@ Reload Pi after installing or changing settings. Use `/usage` for a persistent,
|
|
|
18
18
|
|
|
19
19
|
A percentage always means **remaining**, not used. Unknown is `[?] ?`; an expired reset is `↺ due` with the stale percentage hidden until the next successful query. A wallet balance never becomes a percentage. Historical spend is not a quota.
|
|
20
20
|
|
|
21
|
-
The footer shows at most two windows and `+N` for additional ones, for the current provider only. `/usage` queries all configured supported providers and
|
|
21
|
+
The footer shows at most two windows and `+N` for additional ones, for the current provider only. `/usage` queries all configured supported providers and shows successful results with all windows, grouped by provider; it does not open a modal or send messages to the model. Snapshots remain frozen at capture time. Query failures replace stale values in the footer and appear in `/usage --all`.
|
|
22
22
|
|
|
23
23
|
## Configure providers
|
|
24
24
|
|
|
@@ -36,7 +36,7 @@ Add bindings in global `~/.pi/agent/settings.json` under `quotaUsage` (not proje
|
|
|
36
36
|
}
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Use the **exact Pi provider ID**.
|
|
39
|
+
Use the **exact Pi provider ID**. Built-in providers and explicitly bound providers are queried; explicit bindings replace a built-in adapter with the same ID. Unknown keys or adapter options are rejected. The API key and model `baseUrl` come from Pi's active provider auth; do not put model API keys in `quotaUsage`.
|
|
40
40
|
|
|
41
41
|
| Adapter | Endpoint | Result |
|
|
42
42
|
|---|---|---|
|
|
@@ -63,7 +63,7 @@ Pi model/auth → adapter → QuotaSnapshot → controller → footer / /usage /
|
|
|
63
63
|
|
|
64
64
|
`QuotaSnapshot` separates `balance` (wallet), `allowance` (finite key quota), `windows` (renewable limits), and optional `spend` (Today/Lifetime actual charge). This prevents unrelated amounts from sharing an ambiguous `Used` label. Custom adapters can be passed to `createQuotaExtension({ adapters: [...] })`; see `examples/custom-provider.ts`. Provider aliases are never inferred.
|
|
65
65
|
|
|
66
|
-
The extension publishes structured state on `quota-bar:state:v1`. Use `createQuotaExtension({ footer: false })` to consume it without this extension's footer. Normal queries are throttled, cancellable, timeout-bounded and backed off after errors. `/usage` forces a fresh query for every configured supported provider. Switching provider discards old footer values immediately.
|
|
66
|
+
The extension publishes structured state on `quota-bar:state:v1`. Use `createQuotaExtension({ footer: false })` to consume it without this extension's footer. Normal queries are throttled, cancellable, timeout-bounded and backed off after errors. `/usage` forces a fresh query for every configured supported provider, then omits failed results from its default view. Switching provider discards old footer values immediately.
|
|
67
67
|
|
|
68
68
|
## Development
|
|
69
69
|
|
package/package.json
CHANGED
package/src/bar/card.ts
CHANGED
|
@@ -24,7 +24,7 @@ export function captureAllUsage(states: readonly QuotaState[], now = Date.now())
|
|
|
24
24
|
|
|
25
25
|
export function usageLines(card: UsageCard, paint?: Paint, barWidth = 20): string[] {
|
|
26
26
|
if (card.version === 1) return renderUsage(card.state, paint, card.capturedAt, barWidth);
|
|
27
|
-
if (card.states.length === 0) return ['No
|
|
27
|
+
if (card.states.length === 0) return ['No quota data available. Run /usage --all for details.'];
|
|
28
28
|
return card.states.flatMap((state, index) => [
|
|
29
29
|
...(index ? ['', ''] : []), ...renderUsage(state, paint, card.capturedAt, barWidth),
|
|
30
30
|
]);
|
package/src/bar/quota.ts
CHANGED
|
@@ -46,11 +46,9 @@ export function renderFooter(state: QuotaState, paint: Paint = plain, now = Date
|
|
|
46
46
|
return renderBar([quotaElement(state, now)], paint);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const displayLabel: Record<string, string> = { deepseek: 'DeepSeek' };
|
|
50
|
-
|
|
51
49
|
export function renderUsage(state: QuotaState, paint: Paint = plain, now = Date.now(), barWidth = 16): string[] {
|
|
52
50
|
if (state.kind === 'hidden') return ['No quota adapter for the current provider.'];
|
|
53
|
-
const title =
|
|
51
|
+
const title = state.label;
|
|
54
52
|
const indent = (line: string) => line ? ` ${line}` : line;
|
|
55
53
|
if (state.kind === 'loading') return [title, '', indent('Loading…')];
|
|
56
54
|
if (state.kind === 'error') return [title, '', indent(errorText[state.code])];
|
package/src/extension.ts
CHANGED
|
@@ -16,7 +16,8 @@ const STATUS_KEY = 'quota-bar';
|
|
|
16
16
|
const USAGE_WIDGET = 'quota-bar:usage';
|
|
17
17
|
|
|
18
18
|
export interface QuotaExtensionOptions {
|
|
19
|
-
|
|
19
|
+
// Explicit adapters replace matching built-ins; duplicate explicit IDs are rejected.
|
|
20
|
+
adapters?: readonly QuotaAdapter[];
|
|
20
21
|
providers?: readonly string[]; // Extra configured quota providers to include in /usage
|
|
21
22
|
footer?: boolean; // false: query + /usage + structured events only
|
|
22
23
|
intervalMs?: number;
|
|
@@ -25,8 +26,13 @@ export interface QuotaExtensionOptions {
|
|
|
25
26
|
|
|
26
27
|
export function createQuotaExtension(options: QuotaExtensionOptions = {}) {
|
|
27
28
|
return (pi: ExtensionAPI): void => {
|
|
28
|
-
const
|
|
29
|
-
|
|
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));
|
|
30
36
|
let current: ExtensionContext | undefined;
|
|
31
37
|
let selectedProvider: string | undefined;
|
|
32
38
|
let timer: ReturnType<typeof setInterval> | undefined;
|
|
@@ -161,8 +167,8 @@ export function createQuotaExtension(options: QuotaExtensionOptions = {}) {
|
|
|
161
167
|
});
|
|
162
168
|
|
|
163
169
|
pi.registerCommand('usage', {
|
|
164
|
-
description: '
|
|
165
|
-
handler: async (
|
|
170
|
+
description: 'Show available quotas; use /usage --all to include provider errors',
|
|
171
|
+
handler: async (args, ctx) => {
|
|
166
172
|
if (!ctx.hasUI) return;
|
|
167
173
|
bind(ctx);
|
|
168
174
|
const generation = ++commandGeneration;
|
|
@@ -174,7 +180,9 @@ export function createQuotaExtension(options: QuotaExtensionOptions = {}) {
|
|
|
174
180
|
// Never append via a stale Pi runtime, or label a replacement model's data
|
|
175
181
|
// as the result of the original command. A newer command owns its result.
|
|
176
182
|
if (!current || generation !== commandGeneration) return;
|
|
177
|
-
const
|
|
183
|
+
const visibleStates = args.trim() === '--all'
|
|
184
|
+
? states : states.filter(state => state.kind === 'ready');
|
|
185
|
+
const card = captureAllUsage(visibleStates);
|
|
178
186
|
pi.appendEntry<UsageCard>(USAGE_ENTRY, card);
|
|
179
187
|
// Custom entry renderers are TUI-only; RPC clients still get readable output.
|
|
180
188
|
if (ctx.mode !== 'tui') {
|
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`,
|