@llblab/pi-codex-usage 0.9.4 → 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/CHANGELOG.md +11 -0
- package/README.md +6 -0
- package/index.ts +84 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.10.1: Softer Quota Bar
|
|
6
|
+
|
|
7
|
+
- Changed the normal and loading dual-quota bar background from `selectedBg` to the softer themed `userMessageBg`; exhausted quotas still use `toolErrorBg`. The bar is less prominent across dark and light themes without altering quota values or countdowns.
|
|
8
|
+
|
|
9
|
+
## 0.10.0: Business Credit Usage
|
|
10
|
+
|
|
11
|
+
- 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.
|
|
12
|
+
- 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.
|
|
13
|
+
|
|
3
14
|
## 0.9.4: Responsive Quota Polling
|
|
4
15
|
|
|
5
16
|
- 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.
|
package/README.md
CHANGED
|
@@ -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
|
@@ -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 {
|
|
@@ -1081,7 +1136,7 @@ export function formatCodexUsageStatusline(
|
|
|
1081
1136
|
const barText = formatStatuslineBarText(
|
|
1082
1137
|
ctx,
|
|
1083
1138
|
bar ?? "",
|
|
1084
|
-
hasExhaustedQuotaWindow(report, model) ? "toolErrorBg" : "
|
|
1139
|
+
hasExhaustedQuotaWindow(report, model) ? "toolErrorBg" : "userMessageBg",
|
|
1085
1140
|
model,
|
|
1086
1141
|
);
|
|
1087
1142
|
return countdown
|
|
@@ -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
|
}
|
|
@@ -1228,7 +1299,7 @@ function formatStatuslineText(
|
|
|
1228
1299
|
function formatStatuslineBarText(
|
|
1229
1300
|
ctx: ExtensionContext,
|
|
1230
1301
|
bar: string,
|
|
1231
|
-
background: "
|
|
1302
|
+
background: "userMessageBg" | "toolErrorBg",
|
|
1232
1303
|
model?: CodexUsageModel,
|
|
1233
1304
|
): string {
|
|
1234
1305
|
const label = ctx.ui.theme.fg("accent", activeUsageLabel(model));
|
|
@@ -1260,7 +1331,7 @@ function formatStatuslineLoading(
|
|
|
1260
1331
|
return formatStatuslineBarText(
|
|
1261
1332
|
ctx,
|
|
1262
1333
|
formatCodexUsageLoadingBar(frame),
|
|
1263
|
-
"
|
|
1334
|
+
"userMessageBg",
|
|
1264
1335
|
model,
|
|
1265
1336
|
);
|
|
1266
1337
|
}
|
|
@@ -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(
|