@ravshansbox/pi-quota 0.9.0 → 0.10.1
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 +2 -2
- package/index.ts +28 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,12 +12,12 @@ Add `-l` to install it in project settings.
|
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
|
-
Pi loads the extension from `./index.ts` and shows the
|
|
15
|
+
Pi loads the extension from `./index.ts` and shows the used quota for the provider backing the active model in the footer.
|
|
16
16
|
|
|
17
17
|
- Calls the Anthropic usage endpoint once on session start, then reads the Anthropic quota from the rate limit headers of each Anthropic response
|
|
18
18
|
- Polls the OpenAI Codex usage endpoint on session start, then at every 10-minute wall-clock mark (`HH:00`, `HH:10`, `HH:20`, `HH:30`, `HH:40`, `HH:50`)
|
|
19
19
|
- Shows a footer status for the provider backing the **active model**, and only when quota data for that provider has been polled; switching models re-renders it immediately
|
|
20
|
-
- The text stays compact, e.g. `
|
|
20
|
+
- The text stays compact, e.g. `7% 1h29m · 33% 4d`, where the 5-hour window comes first and the 7-day window second, and each window shows its used percentage followed by time until reset
|
|
21
21
|
- Available resets, when present, are appended as `· 2x 5h` (count, then time until the soonest expires)
|
|
22
22
|
- Both providers are checked on session start whenever their credentials exist, so quota is already known when the active model changes
|
|
23
23
|
- Refreshes Anthropic and OpenAI Codex OAuth access tokens from `~/.pi/agent/auth.json` when needed, writes updated credentials back (re-reading the file first to avoid clobbering concurrent updates), and notifies on the first successful refresh per provider each session
|
package/index.ts
CHANGED
|
@@ -13,9 +13,9 @@ interface QuotaConfig {
|
|
|
13
13
|
|
|
14
14
|
interface QuotaState {
|
|
15
15
|
provider: 'anthropic' | 'openai-codex';
|
|
16
|
-
|
|
16
|
+
fiveHourUsed: number | null;
|
|
17
17
|
fiveHourReset: Date | null;
|
|
18
|
-
|
|
18
|
+
sevenDayUsed: number | null;
|
|
19
19
|
sevenDayReset: Date | null;
|
|
20
20
|
resetsAvailable: number;
|
|
21
21
|
resetSoonestExpiry: Date | null;
|
|
@@ -118,7 +118,7 @@ function parseAnthropicHeaderWindow(headers: Record<string, string>, name: strin
|
|
|
118
118
|
const utilization = headers[`anthropic-ratelimit-unified-${name}-utilization`];
|
|
119
119
|
const reset = headers[`anthropic-ratelimit-unified-${name}-reset`];
|
|
120
120
|
return {
|
|
121
|
-
|
|
121
|
+
used: utilization === undefined ? null : clampPercent(Number(utilization) * 100),
|
|
122
122
|
reset: reset === undefined ? null : new Date(Number(reset) * 1000),
|
|
123
123
|
};
|
|
124
124
|
}
|
|
@@ -126,12 +126,12 @@ function parseAnthropicHeaderWindow(headers: Record<string, string>, name: strin
|
|
|
126
126
|
function parseAnthropicHeaders(headers: Record<string, string>): QuotaState | null {
|
|
127
127
|
const fiveHour = parseAnthropicHeaderWindow(headers, '5h');
|
|
128
128
|
const sevenDay = parseAnthropicHeaderWindow(headers, '7d');
|
|
129
|
-
if (fiveHour.
|
|
129
|
+
if (fiveHour.used === null && sevenDay.used === null) return null;
|
|
130
130
|
return {
|
|
131
131
|
provider: 'anthropic',
|
|
132
|
-
|
|
132
|
+
fiveHourUsed: fiveHour.used,
|
|
133
133
|
fiveHourReset: fiveHour.reset,
|
|
134
|
-
|
|
134
|
+
sevenDayUsed: sevenDay.used,
|
|
135
135
|
sevenDayReset: sevenDay.reset,
|
|
136
136
|
resetsAvailable: 0,
|
|
137
137
|
resetSoonestExpiry: null,
|
|
@@ -213,13 +213,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
213
213
|
if (!state) return undefined;
|
|
214
214
|
|
|
215
215
|
const parts: string[] = [];
|
|
216
|
-
if (state.
|
|
216
|
+
if (state.fiveHourUsed !== null) {
|
|
217
217
|
const resetStr = state.fiveHourReset ? formatResetTime(state.fiveHourReset) : '?';
|
|
218
|
-
parts.push(`${state.
|
|
218
|
+
parts.push(`${state.fiveHourUsed}% ${resetStr}`);
|
|
219
219
|
}
|
|
220
|
-
if (state.
|
|
220
|
+
if (state.sevenDayUsed !== null) {
|
|
221
221
|
const resetStr = state.sevenDayReset ? formatResetTime(state.sevenDayReset) : '?';
|
|
222
|
-
parts.push(`${state.
|
|
222
|
+
parts.push(`${state.sevenDayUsed}% ${resetStr}`);
|
|
223
223
|
}
|
|
224
224
|
if (state.resetsAvailable > 0) {
|
|
225
225
|
const expiryStr = state.resetSoonestExpiry
|
|
@@ -242,13 +242,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
242
242
|
function windowResetDetected(
|
|
243
243
|
previousReset: Date | null,
|
|
244
244
|
nextReset: Date | null,
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
previousUsed: number | null,
|
|
246
|
+
nextUsed: number | null,
|
|
247
247
|
): boolean {
|
|
248
248
|
if (!previousReset || !nextReset) return false;
|
|
249
249
|
if (nextReset.getTime() <= previousReset.getTime()) return false;
|
|
250
|
-
if (
|
|
251
|
-
return
|
|
250
|
+
if (previousUsed === null || nextUsed === null) return false;
|
|
251
|
+
return nextUsed < previousUsed;
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
function shouldPingOnReset(previous: QuotaState, next: QuotaState): boolean {
|
|
@@ -256,14 +256,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
256
256
|
windowResetDetected(
|
|
257
257
|
previous.fiveHourReset,
|
|
258
258
|
next.fiveHourReset,
|
|
259
|
-
previous.
|
|
260
|
-
next.
|
|
259
|
+
previous.fiveHourUsed,
|
|
260
|
+
next.fiveHourUsed,
|
|
261
261
|
) ||
|
|
262
262
|
windowResetDetected(
|
|
263
263
|
previous.sevenDayReset,
|
|
264
264
|
next.sevenDayReset,
|
|
265
|
-
previous.
|
|
266
|
-
next.
|
|
265
|
+
previous.sevenDayUsed,
|
|
266
|
+
next.sevenDayUsed,
|
|
267
267
|
)
|
|
268
268
|
);
|
|
269
269
|
}
|
|
@@ -312,6 +312,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
function updateState(parsed: QuotaState) {
|
|
315
|
+
if (!ctxRef) return;
|
|
315
316
|
if (parsed.provider === 'anthropic') scheduleAnthropicPing(parsed);
|
|
316
317
|
const existing = states.find((s) => s.provider === parsed.provider);
|
|
317
318
|
if (!existing) {
|
|
@@ -321,9 +322,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
321
322
|
|
|
322
323
|
const shouldPing = parsed.provider !== 'anthropic' && shouldPingOnReset(existing, parsed);
|
|
323
324
|
|
|
324
|
-
existing.
|
|
325
|
+
existing.fiveHourUsed = parsed.fiveHourUsed;
|
|
325
326
|
existing.fiveHourReset = parsed.fiveHourReset;
|
|
326
|
-
existing.
|
|
327
|
+
existing.sevenDayUsed = parsed.sevenDayUsed;
|
|
327
328
|
existing.sevenDayReset = parsed.sevenDayReset;
|
|
328
329
|
existing.resetsAvailable = parsed.resetsAvailable;
|
|
329
330
|
existing.resetSoonestExpiry = parsed.resetSoonestExpiry;
|
|
@@ -500,9 +501,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
500
501
|
const sevenDay = data.seven_day;
|
|
501
502
|
updateState({
|
|
502
503
|
provider: 'anthropic',
|
|
503
|
-
|
|
504
|
+
fiveHourUsed: fiveHour ? clampPercent(fiveHour.utilization ?? 0) : null,
|
|
504
505
|
fiveHourReset: fiveHour?.resets_at ? new Date(fiveHour.resets_at) : null,
|
|
505
|
-
|
|
506
|
+
sevenDayUsed: sevenDay ? clampPercent(sevenDay.utilization ?? 0) : null,
|
|
506
507
|
sevenDayReset: sevenDay?.resets_at ? new Date(sevenDay.resets_at) : null,
|
|
507
508
|
resetsAvailable: 0,
|
|
508
509
|
resetSoonestExpiry: null,
|
|
@@ -536,9 +537,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
536
537
|
resetsAvailable !== undefined ? Math.max(0, Math.trunc(resetsAvailable)) : 0;
|
|
537
538
|
updateState({
|
|
538
539
|
provider: 'openai-codex',
|
|
539
|
-
|
|
540
|
+
fiveHourUsed: fiveHour ? clampPercent(fiveHour.used_percent ?? 0) : null,
|
|
540
541
|
fiveHourReset: fiveHour?.reset_at ? new Date(fiveHour.reset_at * 1000) : null,
|
|
541
|
-
|
|
542
|
+
sevenDayUsed: sevenDay ? clampPercent(sevenDay.used_percent ?? 0) : null,
|
|
542
543
|
sevenDayReset: sevenDay?.reset_at ? new Date(sevenDay.reset_at * 1000) : null,
|
|
543
544
|
resetsAvailable: resetCount,
|
|
544
545
|
resetSoonestExpiry:
|
|
@@ -562,11 +563,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
562
563
|
const codexState = states.find((s) => s.provider === 'openai-codex');
|
|
563
564
|
if (!codexState) return;
|
|
564
565
|
|
|
565
|
-
if (codexState.
|
|
566
|
+
if (codexState.sevenDayUsed !== null && codexState.sevenDayUsed < 100) {
|
|
566
567
|
codexRedeemAttempted = false;
|
|
567
568
|
}
|
|
568
569
|
|
|
569
|
-
if (codexState.
|
|
570
|
+
if (codexState.sevenDayUsed !== 100) return;
|
|
570
571
|
if (codexState.resetsAvailable === 0) return;
|
|
571
572
|
if (codexRedeemAttempted) return;
|
|
572
573
|
|
|
@@ -666,5 +667,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
666
667
|
|
|
667
668
|
pi.on('session_shutdown', () => {
|
|
668
669
|
stopTimers();
|
|
670
|
+
ctxRef = null;
|
|
669
671
|
});
|
|
670
672
|
}
|