@llblab/pi-codex-usage 0.9.3 → 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/BACKLOG.md +1 -6
- package/CHANGELOG.md +9 -0
- package/README.md +7 -1
- package/index.ts +82 -8
- package/package.json +1 -1
package/BACKLOG.md
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
1
|
# Backlog
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
- [x] Fix stale extension context usage after session replacement or reload. Source: GitHub issue #1. Timers must not keep using captured stale `ctx` after Pi session lifecycle changes.
|
|
6
|
-
- [x] Make usage refresh cache/in-flight state model-aware for Codex vs Spark. A regular Codex cache or request must not temporarily render Spark as `n/a` after model switching.
|
|
7
|
-
- [x] Preserve the active `codex`/`spark` label for failure states. Spark failures should render `spark n/a` or `spark error`, not `codex ...`.
|
|
8
|
-
- [x] Replace the visually full startup/request placeholder with counter-moving upper/lower in-bar loading markers. Keep the last usable active-bucket bar visible during ordinary refreshes, and hold provisional zero-usage reports behind the loader until confirmed stable.
|
|
3
|
+
No open work.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0: Business Credit Usage
|
|
4
|
+
|
|
5
|
+
- Added Business-account credit usage support for responses that do not expose Codex rate-limit windows. Impact: the statusline now shows rounded remaining credit percentage and an optional reset countdown while preserving existing rate-limit displays when both forms are available.
|
|
6
|
+
- Scheduled credit reset countdown redraws at their exact display boundaries and retained credits-only reports in the active cache. Impact: credit status remains locally responsive between API refreshes without displacing model-specific quota windows.
|
|
7
|
+
|
|
8
|
+
## 0.9.4: Responsive Quota Polling
|
|
9
|
+
|
|
10
|
+
- Increased the automatic Codex quota refresh interval from 30 to 60 seconds. Impact: status values remain responsive down to the locally rendered seconds countdown while reducing repeated OpenAI requests, especially when several Pi instances are open.
|
|
11
|
+
|
|
3
12
|
## 0.9.3: Trusted Release Automation
|
|
4
13
|
|
|
5
14
|
- Added tag-gated release automation with exact package/lock/tag identity checks, full package validation, npm Trusted Publisher provenance, public registry verification, and workflow-owned GitHub Release creation through `.github/workflows/release.yml`.
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ This repository is a minimal fork of [`narumiruna/pi-extensions/extensions/pi-co
|
|
|
14
14
|
|
|
15
15
|
## Features
|
|
16
16
|
|
|
17
|
-
- Shows two counter-moving half-height markers in the statusline bar while the active Codex/Spark quota bucket is loading, then refreshes every
|
|
17
|
+
- Shows two counter-moving half-height markers in the statusline bar while the active Codex/Spark quota bucket is loading, then refreshes every 60 seconds
|
|
18
18
|
- Keeps the last usable active-bucket bar visible during ordinary refreshes instead of replacing known quota with a loading state
|
|
19
19
|
- Statusline output adapts to the response: weekly-only limits show an explicit remaining percentage and reset countdown, while dual-window limits keep the compact themed bar
|
|
20
20
|
- Regular Codex subscription models show the primary `codex` quota bucket
|
|
@@ -68,6 +68,12 @@ Before the first usable report for the active quota bucket arrives, two half-hei
|
|
|
68
68
|
|
|
69
69
|
When the weekly reset time is available, it follows either the single-window percentage or the dual-window bar. More than a day remains is shown in 144-minute day-tenth steps such as `7d`, `6.9d`, `6.6d`, `5.1d`, `5d`, `3.7d`, `3d`, `2d`, `1.9d`, `1.5d`, and `1.1d`, rounded upward to the next tenth. At 24 hours and below it switches to upward-rounded 6-minute hour-tenth steps such as `24h`, `23.7h`, `20.1h`, `20h`, `19.9h`, `1.4h`, `1.3h`, `1.2h`, `1.1h`, and `1h`. Under an hour it switches to floored minutes, and under a minute to seconds. After the reset timestamp passes, `0s` is held until the next successful quota refresh reports the new weekly window.
|
|
70
70
|
|
|
71
|
+
Business accounts that return credits instead of Codex rate-limit windows show the rounded remaining percentage. When the response includes a credit reset cycle, its countdown is shown after the percentage:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
codex 81% 2.8d
|
|
75
|
+
```
|
|
76
|
+
|
|
71
77
|
When the 5-hour window is exhausted and exposes its own reset time, the statusline adds the 5-hour reset before the weekly reset:
|
|
72
78
|
|
|
73
79
|
```text
|
package/index.ts
CHANGED
|
@@ -16,7 +16,7 @@ const HOUR_MS = 60 * MINUTE_MS;
|
|
|
16
16
|
const HOUR_TENTH_MS = 6 * MINUTE_MS;
|
|
17
17
|
const DAY_MS = 24 * HOUR_MS;
|
|
18
18
|
const DAY_TENTH_MS = 144 * MINUTE_MS;
|
|
19
|
-
const REFRESH_INTERVAL_MS =
|
|
19
|
+
const REFRESH_INTERVAL_MS = 60 * SECOND_MS;
|
|
20
20
|
const PROVISIONAL_RETRY_MS = SECOND_MS;
|
|
21
21
|
const FULL_AVAILABILITY_CONFIRMATION_MS = 15 * SECOND_MS;
|
|
22
22
|
const LOADING_FRAME_MS = 30;
|
|
@@ -90,6 +90,12 @@ export type UsageQueryError = {
|
|
|
90
90
|
|
|
91
91
|
export type CodexUsageReport = {
|
|
92
92
|
snapshots: NormalizedRateLimitSnapshot[];
|
|
93
|
+
credits?: NormalizedCreditUsage;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type NormalizedCreditUsage = {
|
|
97
|
+
remainingPercent: number;
|
|
98
|
+
resetAt?: number;
|
|
93
99
|
};
|
|
94
100
|
|
|
95
101
|
export type NormalizedRateLimitSnapshot = {
|
|
@@ -106,6 +112,8 @@ export type NormalizedRateLimitWindow = {
|
|
|
106
112
|
type RateLimitStatusPayload = {
|
|
107
113
|
rate_limit?: unknown;
|
|
108
114
|
additional_rate_limits?: unknown;
|
|
115
|
+
credits?: unknown;
|
|
116
|
+
spend_control?: unknown;
|
|
109
117
|
};
|
|
110
118
|
|
|
111
119
|
type BackendAdditionalRateLimit = {
|
|
@@ -593,7 +601,10 @@ async function queryUsage(
|
|
|
593
601
|
source === "pi-auth"
|
|
594
602
|
? await queryViaPiAuth(ctx, options.timeoutMs)
|
|
595
603
|
: await queryViaCodexAppServer(options.timeoutMs);
|
|
596
|
-
if (
|
|
604
|
+
if (
|
|
605
|
+
selectUsageSnapshot(report, activeUsageLimitId(model)) ||
|
|
606
|
+
report.credits
|
|
607
|
+
) {
|
|
597
608
|
return { ok: true, report };
|
|
598
609
|
}
|
|
599
610
|
errors.push({
|
|
@@ -920,12 +931,56 @@ export function normalizeBackendPayload(
|
|
|
920
931
|
}
|
|
921
932
|
}
|
|
922
933
|
|
|
923
|
-
|
|
934
|
+
const credits = normalizeBackendCredits(payload, _capturedAt);
|
|
935
|
+
if (snapshots.length === 0 && !credits) {
|
|
924
936
|
throw new Error(
|
|
925
|
-
"Codex usage endpoint returned no displayable rate-limit windows.",
|
|
937
|
+
"Codex usage endpoint returned no displayable rate-limit windows or credits.",
|
|
926
938
|
);
|
|
927
939
|
}
|
|
928
|
-
return { snapshots };
|
|
940
|
+
return credits ? { snapshots, credits } : { snapshots };
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
function normalizeBackendCredits(
|
|
944
|
+
payload: RateLimitStatusPayload,
|
|
945
|
+
capturedAt: number,
|
|
946
|
+
): NormalizedCreditUsage | undefined {
|
|
947
|
+
const spendControl = payload.spend_control as
|
|
948
|
+
| Record<string, unknown>
|
|
949
|
+
| undefined;
|
|
950
|
+
const individualLimit = spendControl?.individual_limit as
|
|
951
|
+
| Record<string, unknown>
|
|
952
|
+
| undefined;
|
|
953
|
+
const credits = payload.credits as Record<string, unknown> | undefined;
|
|
954
|
+
const limit = asNumber(individualLimit?.limit);
|
|
955
|
+
const used = asNumber(individualLimit?.used);
|
|
956
|
+
const remaining = asNumber(individualLimit?.remaining);
|
|
957
|
+
const resetAt = asResetTime(
|
|
958
|
+
[
|
|
959
|
+
individualLimit?.reset_at,
|
|
960
|
+
individualLimit?.resets_at,
|
|
961
|
+
individualLimit?.reset_time,
|
|
962
|
+
spendControl?.reset_at,
|
|
963
|
+
spendControl?.resets_at,
|
|
964
|
+
credits?.reset_at,
|
|
965
|
+
credits?.resets_at,
|
|
966
|
+
],
|
|
967
|
+
individualLimit?.reset_after_seconds ?? spendControl?.reset_after_seconds,
|
|
968
|
+
capturedAt,
|
|
969
|
+
);
|
|
970
|
+
if (
|
|
971
|
+
limit === undefined ||
|
|
972
|
+
used === undefined ||
|
|
973
|
+
remaining === undefined ||
|
|
974
|
+
limit <= 0
|
|
975
|
+
)
|
|
976
|
+
return undefined;
|
|
977
|
+
const remainingPercent = Math.min(
|
|
978
|
+
100,
|
|
979
|
+
Math.max(0, (remaining / limit) * 100),
|
|
980
|
+
);
|
|
981
|
+
return resetAt === undefined
|
|
982
|
+
? { remainingPercent }
|
|
983
|
+
: { remainingPercent, resetAt };
|
|
929
984
|
}
|
|
930
985
|
|
|
931
986
|
function backendAdditionalLimitId(limit: BackendAdditionalRateLimit): string {
|
|
@@ -1103,7 +1158,9 @@ export function formatCodexUsageStatusValue(
|
|
|
1103
1158
|
const model = typeof modelOrNow === "number" ? undefined : modelOrNow;
|
|
1104
1159
|
const capturedNow = typeof modelOrNow === "number" ? modelOrNow : now;
|
|
1105
1160
|
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
1106
|
-
if (!snapshot || (!snapshot.primary && !snapshot.secondary))
|
|
1161
|
+
if (!snapshot || (!snapshot.primary && !snapshot.secondary)) {
|
|
1162
|
+
return formatCreditUsage(report.credits, capturedNow);
|
|
1163
|
+
}
|
|
1107
1164
|
if (!snapshot.primary || !snapshot.secondary) {
|
|
1108
1165
|
const window = snapshot.primary ?? snapshot.secondary;
|
|
1109
1166
|
if (!window) return undefined;
|
|
@@ -1130,6 +1187,17 @@ export function formatCodexUsageStatusValue(
|
|
|
1130
1187
|
return countdown ? `${bar} ${countdown}` : bar;
|
|
1131
1188
|
}
|
|
1132
1189
|
|
|
1190
|
+
function formatCreditUsage(
|
|
1191
|
+
credits: NormalizedCreditUsage | undefined,
|
|
1192
|
+
now: number,
|
|
1193
|
+
): string | undefined {
|
|
1194
|
+
if (!credits) return undefined;
|
|
1195
|
+
const percentage = `${Math.round(credits.remainingPercent)}%`;
|
|
1196
|
+
return credits.resetAt
|
|
1197
|
+
? `${percentage} ${formatResetCountdown(credits.resetAt, now)}`
|
|
1198
|
+
: percentage;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1133
1201
|
export function formatWeeklyResetCountdown(
|
|
1134
1202
|
report: CodexUsageReport,
|
|
1135
1203
|
modelOrNow?: CodexUsageModel | number,
|
|
@@ -1167,7 +1235,10 @@ export function nextResetCountdownDelayMs(
|
|
|
1167
1235
|
model?: CodexUsageModel,
|
|
1168
1236
|
): number | undefined {
|
|
1169
1237
|
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
1170
|
-
const
|
|
1238
|
+
const hasRateLimitWindow = Boolean(snapshot?.primary || snapshot?.secondary);
|
|
1239
|
+
const resetTimes = [
|
|
1240
|
+
hasRateLimitWindow ? weeklyWindow(snapshot)?.resetAt : report.credits?.resetAt,
|
|
1241
|
+
];
|
|
1171
1242
|
if (snapshot?.secondary && isQuotaWindowExhausted(snapshot.primary)) {
|
|
1172
1243
|
resetTimes.push(snapshot.primary?.resetAt);
|
|
1173
1244
|
}
|
|
@@ -1391,7 +1462,10 @@ export function canReuseCachedReport(
|
|
|
1391
1462
|
report: CodexUsageReport,
|
|
1392
1463
|
model: CodexUsageModel | undefined,
|
|
1393
1464
|
): boolean {
|
|
1394
|
-
return
|
|
1465
|
+
return (
|
|
1466
|
+
selectActiveUsageSnapshot(report, model) !== undefined ||
|
|
1467
|
+
report.credits !== undefined
|
|
1468
|
+
);
|
|
1395
1469
|
}
|
|
1396
1470
|
|
|
1397
1471
|
export function isFullyAvailableReport(
|