@llblab/pi-codex-usage 0.7.0 → 0.8.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/CHANGELOG.md +5 -0
- package/README.md +14 -5
- package/index.ts +151 -59
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.8.0: Spark Model Usage Status
|
|
6
|
+
|
|
7
|
+
- Added active-bucket status support for `GPT-5.3-Codex-Spark`. Impact: selecting the Spark Codex subscription model now shows the parallel Spark rate-limit bucket with a `spark` label, while regular Codex models keep the existing `codex` bucket/status behavior.
|
|
8
|
+
- Added parsing for backend `additional_rate_limits`, including the Spark `GPT-5.3-Codex-Spark` / `codex_bengalfox` limit. Impact: Spark quota can be shown from the same Pi-auth usage response path as normal Codex quota instead of relying only on app-server fallback.
|
|
9
|
+
|
|
5
10
|
## 0.7.0: Exhausted Quota Visibility
|
|
6
11
|
|
|
7
12
|
- Added exhausted-quota warning background for the statusline quota bar. Impact: when either the 5-hour or weekly Codex window has 0% remaining, the bar keeps its shape but switches from the selected background to the error background for faster visual detection.
|
package/README.md
CHANGED
|
@@ -15,9 +15,11 @@ This repository is a minimal fork of [`narumiruna/pi-extensions/extensions/pi-co
|
|
|
15
15
|
## Features
|
|
16
16
|
|
|
17
17
|
- Shows an empty statusline bar immediately, then refreshes every 30 seconds while the active Pi model uses `openai-codex`
|
|
18
|
-
- Statusline output stays compact, with the
|
|
19
|
-
-
|
|
20
|
-
-
|
|
18
|
+
- Statusline output stays compact, with the active usage label accented and the quota bar plus weekly reset countdown drawn on a themed background
|
|
19
|
+
- Regular Codex subscription models show the primary `codex` quota bucket
|
|
20
|
+
- `GPT-5.3-Codex-Spark` shows the parallel Spark quota bucket with the `spark` label
|
|
21
|
+
- When `pi-telegram` is available, the same compact value appears as `codex: <value>` or `spark: <value>` in the `/start` menu status text for active OpenAI Codex subscription models
|
|
22
|
+
- Additional returned buckets unrelated to the active Codex/Spark model are ignored
|
|
21
23
|
- Pi OpenAI Codex provider auth is used first
|
|
22
24
|
- Codex CLI app-server remains available as a fallback
|
|
23
25
|
- Missing auth, subscription, plan, or quota windows are shown as `n/a`, not as an error
|
|
@@ -41,12 +43,18 @@ pi install git:github.com/llblab/pi-codex-usage
|
|
|
41
43
|
|
|
42
44
|
## Statusline
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
Regular Codex usage:
|
|
45
47
|
|
|
46
48
|
```text
|
|
47
49
|
codex ██████▀▀▀▀ 6d
|
|
48
50
|
```
|
|
49
51
|
|
|
52
|
+
Spark model usage:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
spark ██████████ 7d
|
|
56
|
+
```
|
|
57
|
+
|
|
50
58
|
The ten-character bar encodes two twenty-step limits at once: 40 total bits of quota state in 10 terminal cells. Each step is 5%: the top quadrants are the 5-hour limit, and the bottom quadrants are the weekly limit. If either quota window is exhausted, the bar keeps the same shape but switches to the error background color.
|
|
51
59
|
|
|
52
60
|
When the weekly reset time is available, the bar is followed by a countdown. 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.
|
|
@@ -77,9 +85,10 @@ If `@llblab/pi-telegram` is loaded with the public status-line provider API, thi
|
|
|
77
85
|
|
|
78
86
|
```text
|
|
79
87
|
codex: ██████▀▀▀▀ 6d
|
|
88
|
+
spark: ██████████ 7d
|
|
80
89
|
```
|
|
81
90
|
|
|
82
|
-
The value is the same compact quota bar plus weekly reset countdown used by the terminal statusline. If `pi-telegram` is absent, older, or the active model is not a Codex subscription model, no Telegram row is added.
|
|
91
|
+
The value is the same compact quota bar plus weekly reset countdown used by the terminal statusline, and the label follows the active Codex/Spark model. If `pi-telegram` is absent, older, or the active model is not a Codex subscription model, no Telegram row is added.
|
|
83
92
|
|
|
84
93
|
## Auth
|
|
85
94
|
|
package/index.ts
CHANGED
|
@@ -20,7 +20,11 @@ const REFRESH_INTERVAL_MS = 30 * SECOND_MS;
|
|
|
20
20
|
const REDRAW_BLINK_MS = 150;
|
|
21
21
|
const STATUS_KEY = "aa-codex-usage";
|
|
22
22
|
const MAX_ERROR_BODY_CHARS = 600;
|
|
23
|
-
const
|
|
23
|
+
const DEFAULT_STATUS_LABEL_TEXT = "codex";
|
|
24
|
+
const SPARK_STATUS_LABEL_TEXT = "spark";
|
|
25
|
+
const CODEX_USAGE_LIMIT_ID = "codex";
|
|
26
|
+
const SPARK_USAGE_LIMIT_ID = "spark";
|
|
27
|
+
const SPARK_MODEL_KEY = "gpt-5.3-codex-spark";
|
|
24
28
|
const DUAL_BAR_WIDTH = 10;
|
|
25
29
|
const TELEGRAM_STATUS_IMPORT_SPECIFIERS = [
|
|
26
30
|
"@llblab/pi-telegram/status",
|
|
@@ -49,7 +53,7 @@ type UsageSource = "pi-auth" | "codex-app-server";
|
|
|
49
53
|
type TimeoutHandle = ReturnType<typeof setTimeout> & { unref?: () => void };
|
|
50
54
|
type PiModel = NonNullable<ExtensionContext["model"]>;
|
|
51
55
|
export type CodexUsageModel = Pick<PiModel, "id" | "name" | "provider">;
|
|
52
|
-
type CodexUsageTelegramStatusModel = Pick<PiModel, "provider">;
|
|
56
|
+
type CodexUsageTelegramStatusModel = Pick<PiModel, "id" | "name" | "provider">;
|
|
53
57
|
type TelegramStatusLineProviderResult =
|
|
54
58
|
| { label: string; value: string }
|
|
55
59
|
| undefined;
|
|
@@ -99,6 +103,13 @@ export type NormalizedRateLimitWindow = {
|
|
|
99
103
|
|
|
100
104
|
type RateLimitStatusPayload = {
|
|
101
105
|
rate_limit?: unknown;
|
|
106
|
+
additional_rate_limits?: unknown;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
type BackendAdditionalRateLimit = {
|
|
110
|
+
limit_name?: unknown;
|
|
111
|
+
metered_feature?: unknown;
|
|
112
|
+
rate_limit?: unknown;
|
|
102
113
|
};
|
|
103
114
|
|
|
104
115
|
type BackendRateLimitDetails = {
|
|
@@ -167,8 +178,8 @@ export default function codexUsage(pi: ExtensionAPI) {
|
|
|
167
178
|
({ activeModel }) => {
|
|
168
179
|
if (!isOpenAICodexModel(activeModel)) return undefined;
|
|
169
180
|
if (!cache) return undefined;
|
|
170
|
-
const value = formatCodexUsageStatusValue(cache.report);
|
|
171
|
-
return value ? { label:
|
|
181
|
+
const value = formatCodexUsageStatusValue(cache.report, activeModel);
|
|
182
|
+
return value ? { label: activeUsageLabel(activeModel), value } : undefined;
|
|
172
183
|
},
|
|
173
184
|
)
|
|
174
185
|
.then((unregister) => {
|
|
@@ -219,7 +230,7 @@ export default function codexUsage(pi: ExtensionAPI) {
|
|
|
219
230
|
model: CodexUsageModel | undefined,
|
|
220
231
|
) => {
|
|
221
232
|
if (statuslineCountdownTimer) clearTimeout(statuslineCountdownTimer);
|
|
222
|
-
const delayMs = nextResetCountdownDelayMs(report);
|
|
233
|
+
const delayMs = nextResetCountdownDelayMs(report, Date.now(), model);
|
|
223
234
|
if (delayMs === undefined) {
|
|
224
235
|
statuslineCountdownTimer = undefined;
|
|
225
236
|
return;
|
|
@@ -268,11 +279,16 @@ export default function codexUsage(pi: ExtensionAPI) {
|
|
|
268
279
|
else scheduleTemporaryStatuslineClear(ctx);
|
|
269
280
|
};
|
|
270
281
|
|
|
271
|
-
const queryCurrentUsage = (
|
|
282
|
+
const queryCurrentUsage = (
|
|
283
|
+
ctx: ExtensionContext,
|
|
284
|
+
model: CodexUsageModel | undefined,
|
|
285
|
+
) => {
|
|
272
286
|
if (!inFlightUsageQuery) {
|
|
273
|
-
inFlightUsageQuery = queryUsage(
|
|
274
|
-
|
|
275
|
-
|
|
287
|
+
inFlightUsageQuery = queryUsage(
|
|
288
|
+
ctx,
|
|
289
|
+
{ timeoutMs: DEFAULT_TIMEOUT_MS },
|
|
290
|
+
model,
|
|
291
|
+
).finally(() => {
|
|
276
292
|
inFlightUsageQuery = undefined;
|
|
277
293
|
});
|
|
278
294
|
}
|
|
@@ -305,7 +321,7 @@ export default function codexUsage(pi: ExtensionAPI) {
|
|
|
305
321
|
return;
|
|
306
322
|
}
|
|
307
323
|
|
|
308
|
-
const result = await queryCurrentUsage(ctx);
|
|
324
|
+
const result = await queryCurrentUsage(ctx, model);
|
|
309
325
|
if (requestId !== statuslineRequestId) return;
|
|
310
326
|
if (!isOpenAICodexModel(ctx.model)) {
|
|
311
327
|
clearUsageStatusline(ctx);
|
|
@@ -373,6 +389,26 @@ function isOpenAICodexModel(
|
|
|
373
389
|
return model?.provider === CODEX_PROVIDER_ID;
|
|
374
390
|
}
|
|
375
391
|
|
|
392
|
+
function isSparkCodexModel(
|
|
393
|
+
model: Pick<PiModel, "id" | "name" | "provider"> | undefined,
|
|
394
|
+
): boolean {
|
|
395
|
+
if (!isOpenAICodexModel(model)) return false;
|
|
396
|
+
const key = `${model?.id ?? ""} ${model?.name ?? ""}`.toLowerCase();
|
|
397
|
+
return key.includes(SPARK_MODEL_KEY);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function activeUsageLimitId(
|
|
401
|
+
model: Pick<PiModel, "id" | "name" | "provider"> | undefined,
|
|
402
|
+
): string {
|
|
403
|
+
return isSparkCodexModel(model) ? SPARK_USAGE_LIMIT_ID : CODEX_USAGE_LIMIT_ID;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function activeUsageLabel(
|
|
407
|
+
model: Pick<PiModel, "id" | "name" | "provider"> | undefined,
|
|
408
|
+
): string {
|
|
409
|
+
return isSparkCodexModel(model) ? SPARK_STATUS_LABEL_TEXT : DEFAULT_STATUS_LABEL_TEXT;
|
|
410
|
+
}
|
|
411
|
+
|
|
376
412
|
async function importTelegramStatusLineModule(): Promise<
|
|
377
413
|
TelegramStatusLineModule | undefined
|
|
378
414
|
> {
|
|
@@ -401,25 +437,29 @@ async function registerCodexUsageTelegramStatusLine(
|
|
|
401
437
|
async function queryUsage(
|
|
402
438
|
ctx: ExtensionContext,
|
|
403
439
|
options: Pick<QueryUsageOptions, "timeoutMs">,
|
|
440
|
+
model: CodexUsageModel | undefined,
|
|
404
441
|
): Promise<QueryUsageResult> {
|
|
405
442
|
const errors: UsageQueryError[] = [];
|
|
443
|
+
const sources = isSparkCodexModel(model)
|
|
444
|
+
? (["codex-app-server", "pi-auth"] as const)
|
|
445
|
+
: (["pi-auth", "codex-app-server"] as const);
|
|
406
446
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
cause,
|
|
422
|
-
}
|
|
447
|
+
for (const source of sources) {
|
|
448
|
+
try {
|
|
449
|
+
const report =
|
|
450
|
+
source === "pi-auth"
|
|
451
|
+
? await queryViaPiAuth(ctx, options.timeoutMs)
|
|
452
|
+
: await queryViaCodexAppServer(options.timeoutMs);
|
|
453
|
+
if (selectUsageSnapshot(report, activeUsageLimitId(model))) {
|
|
454
|
+
return { ok: true, report };
|
|
455
|
+
}
|
|
456
|
+
errors.push({
|
|
457
|
+
source,
|
|
458
|
+
message: `${source} returned no displayable ${activeUsageLabel(model)} rate-limit windows`,
|
|
459
|
+
});
|
|
460
|
+
} catch (cause) {
|
|
461
|
+
errors.push({ source, message: errorMessage(cause), cause });
|
|
462
|
+
}
|
|
423
463
|
}
|
|
424
464
|
|
|
425
465
|
return { ok: false, errors };
|
|
@@ -711,17 +751,42 @@ export function normalizeBackendPayload(
|
|
|
711
751
|
_capturedAt: number,
|
|
712
752
|
_source: UsageSource,
|
|
713
753
|
): CodexUsageReport {
|
|
714
|
-
const
|
|
715
|
-
|
|
754
|
+
const snapshots: NormalizedRateLimitSnapshot[] = [];
|
|
755
|
+
const primarySnapshot = normalizeBackendSnapshot(
|
|
756
|
+
CODEX_USAGE_LIMIT_ID,
|
|
716
757
|
payload.rate_limit,
|
|
717
758
|
_capturedAt,
|
|
718
759
|
);
|
|
719
|
-
if (
|
|
760
|
+
if (primarySnapshot) snapshots.push(primarySnapshot);
|
|
761
|
+
|
|
762
|
+
if (Array.isArray(payload.additional_rate_limits)) {
|
|
763
|
+
for (const item of payload.additional_rate_limits) {
|
|
764
|
+
const additional = assertObject(
|
|
765
|
+
item,
|
|
766
|
+
"additional rate limit",
|
|
767
|
+
) as BackendAdditionalRateLimit;
|
|
768
|
+
const snapshot = normalizeBackendSnapshot(
|
|
769
|
+
backendAdditionalLimitId(additional),
|
|
770
|
+
additional.rate_limit,
|
|
771
|
+
_capturedAt,
|
|
772
|
+
);
|
|
773
|
+
if (snapshot) snapshots.push(snapshot);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (snapshots.length === 0) {
|
|
720
778
|
throw new Error(
|
|
721
779
|
"Codex usage endpoint returned no displayable rate-limit windows.",
|
|
722
780
|
);
|
|
723
781
|
}
|
|
724
|
-
return { snapshots
|
|
782
|
+
return { snapshots };
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function backendAdditionalLimitId(limit: BackendAdditionalRateLimit): string {
|
|
786
|
+
const raw = `${asString(limit.limit_name) ?? ""} ${asString(limit.metered_feature) ?? ""}`;
|
|
787
|
+
return raw.toLowerCase().includes("spark")
|
|
788
|
+
? SPARK_USAGE_LIMIT_ID
|
|
789
|
+
: normalizedUsageKey(raw) ?? CODEX_USAGE_LIMIT_ID;
|
|
725
790
|
}
|
|
726
791
|
|
|
727
792
|
function normalizeBackendSnapshot(
|
|
@@ -859,15 +924,16 @@ function mergeSnapshot(
|
|
|
859
924
|
export function formatCodexUsageStatusline(
|
|
860
925
|
report: CodexUsageReport,
|
|
861
926
|
ctx: ExtensionContext,
|
|
862
|
-
|
|
927
|
+
model?: CodexUsageModel,
|
|
863
928
|
): string {
|
|
864
|
-
const value = formatCodexUsageStatusValue(report);
|
|
865
|
-
if (!value) return formatStatuslineText(ctx, "n/a");
|
|
929
|
+
const value = formatCodexUsageStatusValue(report, model);
|
|
930
|
+
if (!value) return formatStatuslineText(ctx, "n/a", model);
|
|
866
931
|
const [bar, countdown] = value.split(" ", 2);
|
|
867
932
|
const barText = formatStatuslineBarText(
|
|
868
933
|
ctx,
|
|
869
934
|
bar ?? "",
|
|
870
|
-
hasExhaustedQuotaWindow(report) ? "toolErrorBg" : "selectedBg",
|
|
935
|
+
hasExhaustedQuotaWindow(report, model) ? "toolErrorBg" : "selectedBg",
|
|
936
|
+
model,
|
|
871
937
|
);
|
|
872
938
|
return countdown
|
|
873
939
|
? `${barText} ${ctx.ui.theme.fg("dim", countdown)}`
|
|
@@ -882,31 +948,40 @@ export function formatCodexUsageBar(
|
|
|
882
948
|
|
|
883
949
|
export function formatCodexUsageStatusValue(
|
|
884
950
|
report: CodexUsageReport,
|
|
951
|
+
modelOrNow?: CodexUsageModel | number,
|
|
885
952
|
now = Date.now(),
|
|
886
953
|
): string | undefined {
|
|
887
|
-
const
|
|
954
|
+
const model = typeof modelOrNow === "number" ? undefined : modelOrNow;
|
|
955
|
+
const capturedNow = typeof modelOrNow === "number" ? modelOrNow : now;
|
|
956
|
+
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
888
957
|
if (!snapshot || (!snapshot.primary && !snapshot.secondary)) return undefined;
|
|
889
958
|
const bar = formatDualLimitBar(snapshot.primary, snapshot.secondary);
|
|
890
959
|
if (isQuotaWindowExhausted(snapshot.primary) && snapshot.primary?.resetAt) {
|
|
891
|
-
const primaryCountdown = formatResetCountdown(
|
|
960
|
+
const primaryCountdown = formatResetCountdown(
|
|
961
|
+
snapshot.primary.resetAt,
|
|
962
|
+
capturedNow,
|
|
963
|
+
);
|
|
892
964
|
const weeklyCountdown = snapshot.secondary?.resetAt
|
|
893
|
-
? formatResetCountdown(snapshot.secondary.resetAt,
|
|
965
|
+
? formatResetCountdown(snapshot.secondary.resetAt, capturedNow)
|
|
894
966
|
: undefined;
|
|
895
967
|
return weeklyCountdown
|
|
896
968
|
? `${bar} ${primaryCountdown}/${weeklyCountdown}`
|
|
897
969
|
: `${bar} ${primaryCountdown}`;
|
|
898
970
|
}
|
|
899
|
-
const countdown = formatWeeklyResetCountdown(report,
|
|
971
|
+
const countdown = formatWeeklyResetCountdown(report, model, capturedNow);
|
|
900
972
|
return countdown ? `${bar} ${countdown}` : bar;
|
|
901
973
|
}
|
|
902
974
|
|
|
903
975
|
export function formatWeeklyResetCountdown(
|
|
904
976
|
report: CodexUsageReport,
|
|
977
|
+
modelOrNow?: CodexUsageModel | number,
|
|
905
978
|
now = Date.now(),
|
|
906
979
|
): string | undefined {
|
|
907
|
-
const
|
|
980
|
+
const model = typeof modelOrNow === "number" ? undefined : modelOrNow;
|
|
981
|
+
const capturedNow = typeof modelOrNow === "number" ? modelOrNow : now;
|
|
982
|
+
const resetAt = selectActiveUsageSnapshot(report, model)?.secondary?.resetAt;
|
|
908
983
|
if (resetAt === undefined) return undefined;
|
|
909
|
-
return formatResetCountdown(resetAt,
|
|
984
|
+
return formatResetCountdown(resetAt, capturedNow);
|
|
910
985
|
}
|
|
911
986
|
|
|
912
987
|
export function formatResetCountdown(
|
|
@@ -930,8 +1005,9 @@ export function formatResetCountdown(
|
|
|
930
1005
|
export function nextResetCountdownDelayMs(
|
|
931
1006
|
report: CodexUsageReport,
|
|
932
1007
|
now = Date.now(),
|
|
1008
|
+
model?: CodexUsageModel,
|
|
933
1009
|
): number | undefined {
|
|
934
|
-
const snapshot =
|
|
1010
|
+
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
935
1011
|
const resetTimes = [snapshot?.secondary?.resetAt];
|
|
936
1012
|
if (isQuotaWindowExhausted(snapshot?.primary)) {
|
|
937
1013
|
resetTimes.push(snapshot?.primary?.resetAt);
|
|
@@ -972,14 +1048,21 @@ function formatTenths(value: number): string {
|
|
|
972
1048
|
return value % 10 === 0 ? String(value / 10) : (value / 10).toFixed(1);
|
|
973
1049
|
}
|
|
974
1050
|
|
|
975
|
-
function formatReportBar(
|
|
976
|
-
|
|
1051
|
+
function formatReportBar(
|
|
1052
|
+
report: CodexUsageReport,
|
|
1053
|
+
model?: CodexUsageModel,
|
|
1054
|
+
): string | undefined {
|
|
1055
|
+
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
977
1056
|
if (!snapshot || (!snapshot.primary && !snapshot.secondary)) return undefined;
|
|
978
1057
|
return formatDualLimitBar(snapshot.primary, snapshot.secondary);
|
|
979
1058
|
}
|
|
980
1059
|
|
|
981
|
-
function formatStatuslineText(
|
|
982
|
-
|
|
1060
|
+
function formatStatuslineText(
|
|
1061
|
+
ctx: ExtensionContext,
|
|
1062
|
+
value: string,
|
|
1063
|
+
model?: CodexUsageModel,
|
|
1064
|
+
): string {
|
|
1065
|
+
const label = ctx.ui.theme.fg("accent", activeUsageLabel(model));
|
|
983
1066
|
return `${label} ${ctx.ui.theme.fg("dim", value)}`;
|
|
984
1067
|
}
|
|
985
1068
|
|
|
@@ -987,8 +1070,9 @@ function formatStatuslineBarText(
|
|
|
987
1070
|
ctx: ExtensionContext,
|
|
988
1071
|
bar: string,
|
|
989
1072
|
background: "selectedBg" | "toolErrorBg" = "selectedBg",
|
|
1073
|
+
model?: CodexUsageModel,
|
|
990
1074
|
): string {
|
|
991
|
-
const label = ctx.ui.theme.fg("accent",
|
|
1075
|
+
const label = ctx.ui.theme.fg("accent", activeUsageLabel(model));
|
|
992
1076
|
const value = ctx.ui.theme.bg(background, ctx.ui.theme.fg("dim", bar));
|
|
993
1077
|
return `${label} ${value}`;
|
|
994
1078
|
}
|
|
@@ -1001,7 +1085,7 @@ function formatStatuslineProblem(
|
|
|
1001
1085
|
ctx: ExtensionContext,
|
|
1002
1086
|
errors: UsageQueryError[],
|
|
1003
1087
|
): string {
|
|
1004
|
-
const label = ctx.ui.theme.fg("accent",
|
|
1088
|
+
const label = ctx.ui.theme.fg("accent", DEFAULT_STATUS_LABEL_TEXT);
|
|
1005
1089
|
const value = isUsageUnavailable(errors)
|
|
1006
1090
|
? ctx.ui.theme.fg("muted", "n/a")
|
|
1007
1091
|
: ctx.ui.theme.fg("error", "error");
|
|
@@ -1030,12 +1114,6 @@ function isUnavailableError(error: UsageQueryError): boolean {
|
|
|
1030
1114
|
);
|
|
1031
1115
|
}
|
|
1032
1116
|
|
|
1033
|
-
function selectPrimaryCodexSnapshot(
|
|
1034
|
-
report: CodexUsageReport,
|
|
1035
|
-
): NormalizedRateLimitSnapshot | undefined {
|
|
1036
|
-
return report.snapshots.find(isPrimaryCodexSnapshot);
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
1117
|
function normalizedUsageKey(value: string | undefined): string | undefined {
|
|
1040
1118
|
const key = value
|
|
1041
1119
|
?.toLowerCase()
|
|
@@ -1090,18 +1168,32 @@ function isQuotaWindowExhausted(
|
|
|
1090
1168
|
return window !== undefined && remainingPercent(window) <= 0;
|
|
1091
1169
|
}
|
|
1092
1170
|
|
|
1093
|
-
function hasExhaustedQuotaWindow(
|
|
1094
|
-
|
|
1171
|
+
function hasExhaustedQuotaWindow(
|
|
1172
|
+
report: CodexUsageReport,
|
|
1173
|
+
model?: CodexUsageModel,
|
|
1174
|
+
): boolean {
|
|
1175
|
+
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
1095
1176
|
return (
|
|
1096
1177
|
isQuotaWindowExhausted(snapshot?.primary) ||
|
|
1097
1178
|
isQuotaWindowExhausted(snapshot?.secondary)
|
|
1098
1179
|
);
|
|
1099
1180
|
}
|
|
1100
1181
|
|
|
1101
|
-
function
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1182
|
+
function selectActiveUsageSnapshot(
|
|
1183
|
+
report: CodexUsageReport,
|
|
1184
|
+
model: CodexUsageModel | undefined,
|
|
1185
|
+
): NormalizedRateLimitSnapshot | undefined {
|
|
1186
|
+
return selectUsageSnapshot(report, activeUsageLimitId(model));
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function selectUsageSnapshot(
|
|
1190
|
+
report: CodexUsageReport,
|
|
1191
|
+
limitId: string,
|
|
1192
|
+
): NormalizedRateLimitSnapshot | undefined {
|
|
1193
|
+
const normalizedLimitId = normalizedUsageKey(limitId);
|
|
1194
|
+
return report.snapshots.find(
|
|
1195
|
+
(snapshot) => normalizedUsageKey(snapshot.limitId) === normalizedLimitId,
|
|
1196
|
+
);
|
|
1105
1197
|
}
|
|
1106
1198
|
|
|
1107
1199
|
function clampPercent(value: number): number {
|