@ravshansbox/pi-quota 0.8.1 → 0.10.0
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 +30 -28
- 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;
|
|
@@ -106,8 +106,10 @@ function formatResetTime(reset: Date): string {
|
|
|
106
106
|
const hours = Math.floor((diff % 86400000) / 3600000);
|
|
107
107
|
const minutes = Math.floor((diff % 3600000) / 60000);
|
|
108
108
|
|
|
109
|
-
if (days
|
|
110
|
-
if (
|
|
109
|
+
if (days === 1) return `${days}d${hours}h`;
|
|
110
|
+
if (days > 1) return `${Math.round(diff / 86400000)}d`;
|
|
111
|
+
if (hours === 1) return `${hours}h${minutes}m`;
|
|
112
|
+
if (hours > 1) return `${Math.round(diff / 3600000)}h`;
|
|
111
113
|
if (minutes > 0) return `${minutes}m`;
|
|
112
114
|
return 'now';
|
|
113
115
|
}
|
|
@@ -116,7 +118,7 @@ function parseAnthropicHeaderWindow(headers: Record<string, string>, name: strin
|
|
|
116
118
|
const utilization = headers[`anthropic-ratelimit-unified-${name}-utilization`];
|
|
117
119
|
const reset = headers[`anthropic-ratelimit-unified-${name}-reset`];
|
|
118
120
|
return {
|
|
119
|
-
|
|
121
|
+
used: utilization === undefined ? null : clampPercent(Number(utilization) * 100),
|
|
120
122
|
reset: reset === undefined ? null : new Date(Number(reset) * 1000),
|
|
121
123
|
};
|
|
122
124
|
}
|
|
@@ -124,12 +126,12 @@ function parseAnthropicHeaderWindow(headers: Record<string, string>, name: strin
|
|
|
124
126
|
function parseAnthropicHeaders(headers: Record<string, string>): QuotaState | null {
|
|
125
127
|
const fiveHour = parseAnthropicHeaderWindow(headers, '5h');
|
|
126
128
|
const sevenDay = parseAnthropicHeaderWindow(headers, '7d');
|
|
127
|
-
if (fiveHour.
|
|
129
|
+
if (fiveHour.used === null && sevenDay.used === null) return null;
|
|
128
130
|
return {
|
|
129
131
|
provider: 'anthropic',
|
|
130
|
-
|
|
132
|
+
fiveHourUsed: fiveHour.used,
|
|
131
133
|
fiveHourReset: fiveHour.reset,
|
|
132
|
-
|
|
134
|
+
sevenDayUsed: sevenDay.used,
|
|
133
135
|
sevenDayReset: sevenDay.reset,
|
|
134
136
|
resetsAvailable: 0,
|
|
135
137
|
resetSoonestExpiry: null,
|
|
@@ -211,13 +213,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
211
213
|
if (!state) return undefined;
|
|
212
214
|
|
|
213
215
|
const parts: string[] = [];
|
|
214
|
-
if (state.
|
|
216
|
+
if (state.fiveHourUsed !== null) {
|
|
215
217
|
const resetStr = state.fiveHourReset ? formatResetTime(state.fiveHourReset) : '?';
|
|
216
|
-
parts.push(`${state.
|
|
218
|
+
parts.push(`${state.fiveHourUsed}% ${resetStr}`);
|
|
217
219
|
}
|
|
218
|
-
if (state.
|
|
220
|
+
if (state.sevenDayUsed !== null) {
|
|
219
221
|
const resetStr = state.sevenDayReset ? formatResetTime(state.sevenDayReset) : '?';
|
|
220
|
-
parts.push(`${state.
|
|
222
|
+
parts.push(`${state.sevenDayUsed}% ${resetStr}`);
|
|
221
223
|
}
|
|
222
224
|
if (state.resetsAvailable > 0) {
|
|
223
225
|
const expiryStr = state.resetSoonestExpiry
|
|
@@ -240,13 +242,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
240
242
|
function windowResetDetected(
|
|
241
243
|
previousReset: Date | null,
|
|
242
244
|
nextReset: Date | null,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
previousUsed: number | null,
|
|
246
|
+
nextUsed: number | null,
|
|
245
247
|
): boolean {
|
|
246
248
|
if (!previousReset || !nextReset) return false;
|
|
247
249
|
if (nextReset.getTime() <= previousReset.getTime()) return false;
|
|
248
|
-
if (
|
|
249
|
-
return
|
|
250
|
+
if (previousUsed === null || nextUsed === null) return false;
|
|
251
|
+
return nextUsed < previousUsed;
|
|
250
252
|
}
|
|
251
253
|
|
|
252
254
|
function shouldPingOnReset(previous: QuotaState, next: QuotaState): boolean {
|
|
@@ -254,14 +256,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
254
256
|
windowResetDetected(
|
|
255
257
|
previous.fiveHourReset,
|
|
256
258
|
next.fiveHourReset,
|
|
257
|
-
previous.
|
|
258
|
-
next.
|
|
259
|
+
previous.fiveHourUsed,
|
|
260
|
+
next.fiveHourUsed,
|
|
259
261
|
) ||
|
|
260
262
|
windowResetDetected(
|
|
261
263
|
previous.sevenDayReset,
|
|
262
264
|
next.sevenDayReset,
|
|
263
|
-
previous.
|
|
264
|
-
next.
|
|
265
|
+
previous.sevenDayUsed,
|
|
266
|
+
next.sevenDayUsed,
|
|
265
267
|
)
|
|
266
268
|
);
|
|
267
269
|
}
|
|
@@ -319,9 +321,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
319
321
|
|
|
320
322
|
const shouldPing = parsed.provider !== 'anthropic' && shouldPingOnReset(existing, parsed);
|
|
321
323
|
|
|
322
|
-
existing.
|
|
324
|
+
existing.fiveHourUsed = parsed.fiveHourUsed;
|
|
323
325
|
existing.fiveHourReset = parsed.fiveHourReset;
|
|
324
|
-
existing.
|
|
326
|
+
existing.sevenDayUsed = parsed.sevenDayUsed;
|
|
325
327
|
existing.sevenDayReset = parsed.sevenDayReset;
|
|
326
328
|
existing.resetsAvailable = parsed.resetsAvailable;
|
|
327
329
|
existing.resetSoonestExpiry = parsed.resetSoonestExpiry;
|
|
@@ -498,9 +500,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
498
500
|
const sevenDay = data.seven_day;
|
|
499
501
|
updateState({
|
|
500
502
|
provider: 'anthropic',
|
|
501
|
-
|
|
503
|
+
fiveHourUsed: fiveHour ? clampPercent(fiveHour.utilization ?? 0) : null,
|
|
502
504
|
fiveHourReset: fiveHour?.resets_at ? new Date(fiveHour.resets_at) : null,
|
|
503
|
-
|
|
505
|
+
sevenDayUsed: sevenDay ? clampPercent(sevenDay.utilization ?? 0) : null,
|
|
504
506
|
sevenDayReset: sevenDay?.resets_at ? new Date(sevenDay.resets_at) : null,
|
|
505
507
|
resetsAvailable: 0,
|
|
506
508
|
resetSoonestExpiry: null,
|
|
@@ -534,9 +536,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
534
536
|
resetsAvailable !== undefined ? Math.max(0, Math.trunc(resetsAvailable)) : 0;
|
|
535
537
|
updateState({
|
|
536
538
|
provider: 'openai-codex',
|
|
537
|
-
|
|
539
|
+
fiveHourUsed: fiveHour ? clampPercent(fiveHour.used_percent ?? 0) : null,
|
|
538
540
|
fiveHourReset: fiveHour?.reset_at ? new Date(fiveHour.reset_at * 1000) : null,
|
|
539
|
-
|
|
541
|
+
sevenDayUsed: sevenDay ? clampPercent(sevenDay.used_percent ?? 0) : null,
|
|
540
542
|
sevenDayReset: sevenDay?.reset_at ? new Date(sevenDay.reset_at * 1000) : null,
|
|
541
543
|
resetsAvailable: resetCount,
|
|
542
544
|
resetSoonestExpiry:
|
|
@@ -560,11 +562,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
560
562
|
const codexState = states.find((s) => s.provider === 'openai-codex');
|
|
561
563
|
if (!codexState) return;
|
|
562
564
|
|
|
563
|
-
if (codexState.
|
|
565
|
+
if (codexState.sevenDayUsed !== null && codexState.sevenDayUsed < 100) {
|
|
564
566
|
codexRedeemAttempted = false;
|
|
565
567
|
}
|
|
566
568
|
|
|
567
|
-
if (codexState.
|
|
569
|
+
if (codexState.sevenDayUsed !== 100) return;
|
|
568
570
|
if (codexState.resetsAvailable === 0) return;
|
|
569
571
|
if (codexRedeemAttempted) return;
|
|
570
572
|
|