@narumitw/pi-usage 0.52.3 β 0.53.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 +18 -4
- package/dist/index.ts +211 -12
- package/dist/index.ts.map +4 -4
- package/package.json +5 -3
- package/src/format.ts +27 -1
- package/src/index.ts +1 -0
- package/src/providers/zai.ts +150 -0
- package/src/query.ts +55 -0
- package/src/types.ts +5 -0
- package/src/usage.ts +9 -0
package/README.md
CHANGED
|
@@ -154,6 +154,20 @@ OpenRouter documents the distinction between credit and rate limits in its [API
|
|
|
154
154
|
|
|
155
155
|
The fixed endpoint is queried only when the candidate OpenCode Go model and the resolved provider-auth base URL, when present, use the official `https://opencode.ai` origin; other origins fail before sending the credential.
|
|
156
156
|
|
|
157
|
+
### Z.AI (GLM Coding Plan)
|
|
158
|
+
|
|
159
|
+
- Provider ID: `zai` and `zai-coding-cn`
|
|
160
|
+
- Semantics: GLM Coding Plan quota windowsβthe rolling 5-hour and weekly plan-usage windows plus the monthly MCP allowance
|
|
161
|
+
- Source: Z.AI's undocumented `GET {origin}/api/monitor/usage/quota/limit` endpoint, also used by its official coding plugin, with the origin derived from the model base URL (`https://api.z.ai` or `https://open.bigmodel.cn`)
|
|
162
|
+
- Displayed data: explicit used and remaining values, reset times, provider-reported per-tool MCP details, and the reported plan level. Windows that report only a percentage remain percent-based
|
|
163
|
+
- Statusline: not published; Z.AI is queried only through `/usage` actions
|
|
164
|
+
|
|
165
|
+
The monitor endpoint is not a published API contract and may return legacy `TOKENS_LIMIT` or newer `CREDIT_LIMIT` window names.
|
|
166
|
+
The extension classifies both forms by the provider's window unit and does not label provider-reported counts as tokens or calls.
|
|
167
|
+
The quota monitor expects the raw API key without a `Bearer` prefix, so the extension strips a `Bearer` prefix from the resolved authorization before sending it to the monitor endpoint.
|
|
168
|
+
Fingerprinting and redaction keep using the original resolved credential.
|
|
169
|
+
Only the official `api.z.ai` and `open.bigmodel.cn` origins are queried; other origins fail before sending the credential.
|
|
170
|
+
|
|
157
171
|
## π§ Current and configured accounts
|
|
158
172
|
|
|
159
173
|
`Current` means the provider and credential used by Pi's selected model.
|
|
@@ -168,8 +182,8 @@ After the active runtime credential changes, the next command, turn, or schedule
|
|
|
168
182
|
|
|
169
183
|
## π Statusline behavior
|
|
170
184
|
|
|
171
|
-
The `usage` status item is active only for
|
|
172
|
-
It refreshes every five minutes while the session remains on a
|
|
185
|
+
The `usage` status item is active only for selected providers that publish statusline usage.
|
|
186
|
+
It refreshes every five minutes while the session remains on such a provider and is cleared when the model changes to an unsupported or menu-only provider.
|
|
173
187
|
|
|
174
188
|
Manual another-provider and all-provider queries never publish to the statusline.
|
|
175
189
|
`@narumitw/pi-statusline` supplies the default `π` icon; `pi-usage` publishes text-only values.
|
|
@@ -205,7 +219,7 @@ Protocol v1 interoperability is characterized for the repository's supported Pi
|
|
|
205
219
|
## π§ Limitations
|
|
206
220
|
|
|
207
221
|
- Only providers with a meaningful usage source and verifiable Pi runtime auth are supported.
|
|
208
|
-
- GitHub Copilot quota and OpenAI Codex reset redemption use undocumented provider endpoints that may change without notice.
|
|
222
|
+
- GitHub Copilot quota, Z.AI quota, and OpenAI Codex reset redemption use undocumented provider endpoints that may change without notice.
|
|
209
223
|
- Codex reset redemption requires a current ChatGPT OAuth credential from Pi's login or a compatible credential source; Codex API keys cannot redeem earned subscription resets.
|
|
210
224
|
- Credentials resolved for custom provider base URLs are never forwarded to the providers' official usage endpoints; effective auth origin validation requires Pi 0.81.0 or newer.
|
|
211
225
|
- Provider reports are snapshots and may themselves be delayed by the provider.
|
|
@@ -235,7 +249,7 @@ packages/pi-usage/
|
|
|
235
249
|
β βββ codex-resets.ts # Codex reset auth, API contracts, and normalization
|
|
236
250
|
β βββ format.ts # Provider-aware notifications and statusline text
|
|
237
251
|
β βββ core.ts # Cache, concurrency, fingerprint, and redaction helpers
|
|
238
|
-
β βββ providers/ #
|
|
252
|
+
β βββ providers/ # Provider-specific usage normalization adapters
|
|
239
253
|
β βββ types.ts # Common presentation and adapter contracts
|
|
240
254
|
βββ test/
|
|
241
255
|
βββ README.md
|
package/dist/index.ts
CHANGED
|
@@ -682,6 +682,120 @@ function asNonnegativeNumber3(value) {
|
|
|
682
682
|
return value;
|
|
683
683
|
}
|
|
684
684
|
|
|
685
|
+
// src/providers/zai.ts
|
|
686
|
+
var FIVE_HOUR_WINDOW_MINUTES = 300;
|
|
687
|
+
var WEEKLY_WINDOW_MINUTES = 10080;
|
|
688
|
+
function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt) {
|
|
689
|
+
const data = asObject5(payload.data);
|
|
690
|
+
if (!data) throw new Error("Z.AI quota response data was not an object.");
|
|
691
|
+
const limits = Array.isArray(data.limits) ? data.limits : [];
|
|
692
|
+
const buckets = [];
|
|
693
|
+
const metrics = [];
|
|
694
|
+
for (const raw of limits) {
|
|
695
|
+
const limit = asObject5(raw);
|
|
696
|
+
if (!limit) continue;
|
|
697
|
+
const type = asString5(limit.type);
|
|
698
|
+
const unit = asNonnegativeNumber4(limit.unit);
|
|
699
|
+
const isPlanUsage = type === "TOKENS_LIMIT" || type === "CREDIT_LIMIT";
|
|
700
|
+
if (type === "TIME_LIMIT") {
|
|
701
|
+
addCountBucket(buckets, limit, "mcp-monthly", "MCP monthly allowance");
|
|
702
|
+
addUsageDetailMetrics(metrics, limit.usageDetails);
|
|
703
|
+
} else if (isPlanUsage && unit === 3) {
|
|
704
|
+
addPercentBucket(buckets, limit, "five-hour", "5h window", FIVE_HOUR_WINDOW_MINUTES);
|
|
705
|
+
} else if (isPlanUsage && unit === 6) {
|
|
706
|
+
const used = asNonnegativeNumber4(limit.currentValue);
|
|
707
|
+
const quota = asNonnegativeNumber4(limit.usage);
|
|
708
|
+
if (used !== void 0 && quota !== void 0) {
|
|
709
|
+
addCountBucket(buckets, limit, "weekly", "Weekly window", WEEKLY_WINDOW_MINUTES);
|
|
710
|
+
} else {
|
|
711
|
+
addPercentBucket(buckets, limit, "weekly", "Weekly window", WEEKLY_WINDOW_MINUTES);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
if (buckets.length === 0) {
|
|
716
|
+
throw new Error("Z.AI quota endpoint returned no displayable usage data.");
|
|
717
|
+
}
|
|
718
|
+
const notes = [];
|
|
719
|
+
const level = asString5(data.level);
|
|
720
|
+
if (level) notes.push(`Plan: ${level}`);
|
|
721
|
+
return {
|
|
722
|
+
providerId,
|
|
723
|
+
providerName,
|
|
724
|
+
capturedAt,
|
|
725
|
+
source: "zai-quota",
|
|
726
|
+
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
727
|
+
buckets,
|
|
728
|
+
metrics,
|
|
729
|
+
...notes.length > 0 ? { notes } : {}
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function addPercentBucket(buckets, limit, id, label, windowMinutes) {
|
|
733
|
+
const used = asNonnegativeNumber4(limit.percentage);
|
|
734
|
+
if (used === void 0) return;
|
|
735
|
+
const percent = clampPercent3(used);
|
|
736
|
+
const resetsAt = asEpochSeconds2(limit.nextResetTime);
|
|
737
|
+
buckets.push({
|
|
738
|
+
id,
|
|
739
|
+
label,
|
|
740
|
+
used: percent,
|
|
741
|
+
remaining: 100 - percent,
|
|
742
|
+
limit: 100,
|
|
743
|
+
unit: "percent",
|
|
744
|
+
windowMinutes,
|
|
745
|
+
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
function addCountBucket(buckets, limit, id, label, windowMinutes) {
|
|
749
|
+
const used = asNonnegativeNumber4(limit.currentValue);
|
|
750
|
+
const quota = asNonnegativeNumber4(limit.usage);
|
|
751
|
+
if (used === void 0 || quota === void 0) return;
|
|
752
|
+
const resetsAt = asEpochSeconds2(limit.nextResetTime);
|
|
753
|
+
buckets.push({
|
|
754
|
+
id,
|
|
755
|
+
label,
|
|
756
|
+
used,
|
|
757
|
+
remaining: Math.max(0, quota - used),
|
|
758
|
+
limit: quota,
|
|
759
|
+
unit: "count",
|
|
760
|
+
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
761
|
+
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
function addUsageDetailMetrics(metrics, value) {
|
|
765
|
+
if (!Array.isArray(value)) return;
|
|
766
|
+
for (const raw of value) {
|
|
767
|
+
const detail = asObject5(raw);
|
|
768
|
+
if (!detail) continue;
|
|
769
|
+
const label = asString5(detail.modelCode);
|
|
770
|
+
const usage = asNonnegativeNumber4(detail.usage);
|
|
771
|
+
if (!label || usage === void 0) continue;
|
|
772
|
+
metrics.push({ id: `mcp-${kebabCase(label)}`, label, value: usage, unit: "count" });
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
function asObject5(value) {
|
|
776
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
777
|
+
return value;
|
|
778
|
+
}
|
|
779
|
+
function asString5(value) {
|
|
780
|
+
if (typeof value !== "string") return void 0;
|
|
781
|
+
return sanitizeDisplayText(value, 80) || void 0;
|
|
782
|
+
}
|
|
783
|
+
function asNonnegativeNumber4(value) {
|
|
784
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
785
|
+
return value;
|
|
786
|
+
}
|
|
787
|
+
function asEpochSeconds2(value) {
|
|
788
|
+
const millis = asNonnegativeNumber4(value);
|
|
789
|
+
if (millis === void 0) return void 0;
|
|
790
|
+
return Math.floor(millis / 1e3);
|
|
791
|
+
}
|
|
792
|
+
function kebabCase(label) {
|
|
793
|
+
return label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || "tool";
|
|
794
|
+
}
|
|
795
|
+
function clampPercent3(value) {
|
|
796
|
+
return Math.min(100, Math.max(0, value));
|
|
797
|
+
}
|
|
798
|
+
|
|
685
799
|
// src/query.ts
|
|
686
800
|
var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
687
801
|
var GITHUB_COPILOT_USAGE_URL = "https://api.github.com/copilot_internal/user";
|
|
@@ -756,6 +870,43 @@ var SUPPORTED_ADAPTERS = [
|
|
|
756
870
|
);
|
|
757
871
|
return normalizeOpenCodeZenPayload(payload, Date.now());
|
|
758
872
|
}
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
id: "zai",
|
|
876
|
+
displayName: "Z.AI",
|
|
877
|
+
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
878
|
+
publishesStatusline: false,
|
|
879
|
+
async query(auth, signal, timeoutMs) {
|
|
880
|
+
const payload = await fetchProviderJson(
|
|
881
|
+
zaiMonitorUrl(auth.model.baseUrl),
|
|
882
|
+
zaiMonitorAuth(auth),
|
|
883
|
+
signal,
|
|
884
|
+
timeoutMs,
|
|
885
|
+
"Z.AI quota endpoint"
|
|
886
|
+
);
|
|
887
|
+
return normalizeZaiQuotaPayload("zai", "Z.AI", payload, Date.now());
|
|
888
|
+
}
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
id: "zai-coding-cn",
|
|
892
|
+
displayName: "Z.AI Coding CN",
|
|
893
|
+
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
894
|
+
publishesStatusline: false,
|
|
895
|
+
async query(auth, signal, timeoutMs) {
|
|
896
|
+
const payload = await fetchProviderJson(
|
|
897
|
+
zaiMonitorUrl(auth.model.baseUrl),
|
|
898
|
+
zaiMonitorAuth(auth),
|
|
899
|
+
signal,
|
|
900
|
+
timeoutMs,
|
|
901
|
+
"Z.AI Coding CN quota endpoint"
|
|
902
|
+
);
|
|
903
|
+
return normalizeZaiQuotaPayload(
|
|
904
|
+
"zai-coding-cn",
|
|
905
|
+
"Z.AI Coding CN",
|
|
906
|
+
payload,
|
|
907
|
+
Date.now()
|
|
908
|
+
);
|
|
909
|
+
}
|
|
759
910
|
}
|
|
760
911
|
];
|
|
761
912
|
function adapterForProvider(providerId) {
|
|
@@ -954,7 +1105,7 @@ function resolveGitHubCopilotUsageAuth(auth, model, salt, candidates, standalone
|
|
|
954
1105
|
const matches = /* @__PURE__ */ new Map();
|
|
955
1106
|
for (const candidate of candidates) {
|
|
956
1107
|
try {
|
|
957
|
-
const credential =
|
|
1108
|
+
const credential = asObject6(candidate);
|
|
958
1109
|
if (credential?.type !== "oauth") continue;
|
|
959
1110
|
sawOAuth = true;
|
|
960
1111
|
const storedAccess2 = typeof credential.access === "string" && credential.access ? credential.access : void 0;
|
|
@@ -1016,7 +1167,7 @@ function bearerToken(authorization) {
|
|
|
1016
1167
|
const match = /^Bearer\s+(.+)$/iu.exec(authorization ?? "");
|
|
1017
1168
|
return match?.[1];
|
|
1018
1169
|
}
|
|
1019
|
-
function
|
|
1170
|
+
function asObject6(value) {
|
|
1020
1171
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1021
1172
|
return value;
|
|
1022
1173
|
}
|
|
@@ -1037,6 +1188,8 @@ function hasOfficialUrlOrigin(value, providerId) {
|
|
|
1037
1188
|
if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
|
|
1038
1189
|
if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
|
|
1039
1190
|
if (providerId === "opencode-go") return url.origin === "https://opencode.ai";
|
|
1191
|
+
if (providerId === "zai") return url.origin === "https://api.z.ai";
|
|
1192
|
+
if (providerId === "zai-coding-cn") return url.origin === "https://open.bigmodel.cn";
|
|
1040
1193
|
if (providerId === "github-copilot") {
|
|
1041
1194
|
return url.protocol === "https:" && /^api\.[a-z0-9-]+\.githubcopilot\.com$/u.test(url.hostname);
|
|
1042
1195
|
}
|
|
@@ -1054,6 +1207,17 @@ function headerValue(headers, name) {
|
|
|
1054
1207
|
function hasHeader(headers, name) {
|
|
1055
1208
|
return Object.keys(headers).some((key) => key.toLowerCase() === name.toLowerCase());
|
|
1056
1209
|
}
|
|
1210
|
+
function zaiMonitorUrl(baseUrl) {
|
|
1211
|
+
const base = baseUrl?.trim();
|
|
1212
|
+
if (!base) throw new Error("Z.AI model base URL is unavailable.");
|
|
1213
|
+
return `${new URL(base).origin}/api/monitor/usage/quota/limit`;
|
|
1214
|
+
}
|
|
1215
|
+
function zaiMonitorAuth(auth) {
|
|
1216
|
+
const authorization = headerValue(auth.headers, "Authorization");
|
|
1217
|
+
const token = authorization === void 0 ? void 0 : bearerToken(authorization) ?? authorization;
|
|
1218
|
+
if (token === void 0 || token === authorization) return auth;
|
|
1219
|
+
return { ...auth, headers: { ...auth.headers, Authorization: token } };
|
|
1220
|
+
}
|
|
1057
1221
|
function isAbortError(error) {
|
|
1058
1222
|
return error instanceof Error && error.name === "AbortError";
|
|
1059
1223
|
}
|
|
@@ -1196,7 +1360,7 @@ function normalizeCodexResetCreditsPayload(payload) {
|
|
|
1196
1360
|
if (rawCredits !== void 0 && !Array.isArray(rawCredits)) {
|
|
1197
1361
|
throw new Error("Codex reset credits response returned invalid credits.");
|
|
1198
1362
|
}
|
|
1199
|
-
const options = (rawCredits ?? []).map(
|
|
1363
|
+
const options = (rawCredits ?? []).map(asObject7).filter((credit) => Boolean(credit)).filter((credit) => credit.status === "available" && credit.reset_type === "codex_rate_limits").map(normalizeResetOption).sort(
|
|
1200
1364
|
(left, right) => (left.expiresAt ?? Number.MAX_SAFE_INTEGER) - (right.expiresAt ?? Number.MAX_SAFE_INTEGER)
|
|
1201
1365
|
).slice(0, Math.min(availableCount, MAX_RESET_OPTIONS));
|
|
1202
1366
|
if (availableCount > 0 && options.length === 0) {
|
|
@@ -1211,7 +1375,7 @@ function selectCodexResetCredential(candidates, resolvedAccess, resolvedAccountI
|
|
|
1211
1375
|
const matches = /* @__PURE__ */ new Map();
|
|
1212
1376
|
for (const candidate of candidates) {
|
|
1213
1377
|
try {
|
|
1214
|
-
const credential =
|
|
1378
|
+
const credential = asObject7(candidate);
|
|
1215
1379
|
if (credential?.type !== "oauth") continue;
|
|
1216
1380
|
sawOAuth = true;
|
|
1217
1381
|
const storedAccess = asNonemptyString(credential.access);
|
|
@@ -1250,7 +1414,7 @@ function codexAccountIdFromAccessToken(access) {
|
|
|
1250
1414
|
const parts = access.split(".");
|
|
1251
1415
|
if (parts.length !== 3 || !parts[1]) return void 0;
|
|
1252
1416
|
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
1253
|
-
const claims =
|
|
1417
|
+
const claims = asObject7(asObject7(payload)?.["https://api.openai.com/auth"]);
|
|
1254
1418
|
return validHeaderValue(claims?.chatgpt_account_id);
|
|
1255
1419
|
} catch {
|
|
1256
1420
|
return void 0;
|
|
@@ -1282,7 +1446,7 @@ function normalizeResetOption(credit) {
|
|
|
1282
1446
|
function isCodexResetOutcomeCode(value) {
|
|
1283
1447
|
return value === "reset" || value === "nothing_to_reset" || value === "no_credit" || value === "already_redeemed";
|
|
1284
1448
|
}
|
|
1285
|
-
function
|
|
1449
|
+
function asObject7(value) {
|
|
1286
1450
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1287
1451
|
return value;
|
|
1288
1452
|
}
|
|
@@ -1330,7 +1494,9 @@ function formatUsageReport(report, displayState) {
|
|
|
1330
1494
|
else if (report.providerId === "github-copilot") formatGitHubCopilotReport(lines, report);
|
|
1331
1495
|
else if (report.providerId === "openrouter") formatOpenRouterReport(lines, report);
|
|
1332
1496
|
else if (report.providerId === "opencode-go") formatOpenCodeZenReport(lines, report);
|
|
1333
|
-
else
|
|
1497
|
+
else if (report.providerId === "zai" || report.providerId === "zai-coding-cn") {
|
|
1498
|
+
formatZaiReport(lines, report);
|
|
1499
|
+
} else formatGenericReport(lines, report);
|
|
1334
1500
|
if (report.notes) {
|
|
1335
1501
|
for (const note of report.notes) lines.push(note);
|
|
1336
1502
|
}
|
|
@@ -1417,7 +1583,7 @@ function compactGitHubCopilotQuotaKind(bucket) {
|
|
|
1417
1583
|
}
|
|
1418
1584
|
function percentRemaining(bucket) {
|
|
1419
1585
|
if (!bucket.limit || bucket.remaining === void 0) return 0;
|
|
1420
|
-
return Math.round(
|
|
1586
|
+
return Math.round(clampPercent4(bucket.remaining / bucket.limit * 100));
|
|
1421
1587
|
}
|
|
1422
1588
|
function formatOpenRouterReport(lines, report) {
|
|
1423
1589
|
const limit = report.buckets.find((bucket) => bucket.id === "key-limit");
|
|
@@ -1444,10 +1610,33 @@ function formatOpenCodeZenStatusline(report) {
|
|
|
1444
1610
|
for (const bucket of report.buckets) {
|
|
1445
1611
|
if (bucket.used === void 0) continue;
|
|
1446
1612
|
const compact = bucket.id === "rolling" ? "r" : bucket.id === "weekly" ? "w" : "m";
|
|
1447
|
-
parts.push(`${
|
|
1613
|
+
parts.push(`${clampPercent4(bucket.used).toFixed(0)}% ${compact}`);
|
|
1448
1614
|
}
|
|
1449
1615
|
return parts.length > 1 ? parts.join(" ") : void 0;
|
|
1450
1616
|
}
|
|
1617
|
+
function formatZaiReport(lines, report) {
|
|
1618
|
+
for (const bucket of report.buckets) {
|
|
1619
|
+
const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
|
|
1620
|
+
let value = "unavailable";
|
|
1621
|
+
if (bucket.unit === "percent" && bucket.used !== void 0) {
|
|
1622
|
+
value = `${bucket.used}% used`;
|
|
1623
|
+
if (bucket.remaining !== void 0) value += ` \xB7 ${bucket.remaining}% left`;
|
|
1624
|
+
} else if (bucket.used !== void 0 && bucket.limit !== void 0) {
|
|
1625
|
+
value = `${bucket.used} of ${bucket.limit} used`;
|
|
1626
|
+
if (bucket.remaining !== void 0) value += ` \xB7 ${bucket.remaining} left`;
|
|
1627
|
+
} else if (bucket.used !== void 0) {
|
|
1628
|
+
value = `${bucket.used} used`;
|
|
1629
|
+
} else if (bucket.remaining !== void 0) {
|
|
1630
|
+
value = `${bucket.remaining} left`;
|
|
1631
|
+
}
|
|
1632
|
+
lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${value}${reset}`);
|
|
1633
|
+
}
|
|
1634
|
+
for (const metric of report.metrics) {
|
|
1635
|
+
lines.push(
|
|
1636
|
+
`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(metric.value, metric.unit)}`
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1451
1640
|
function formatGenericReport(lines, report) {
|
|
1452
1641
|
for (const bucket of report.buckets) {
|
|
1453
1642
|
lines.push(
|
|
@@ -1472,7 +1661,7 @@ function formatCodexStatusline(report, model) {
|
|
|
1472
1661
|
if (bucket.remaining === void 0) continue;
|
|
1473
1662
|
const fallback = bucket.id.endsWith(":secondary") ? "weekly" : "5h";
|
|
1474
1663
|
parts.push(
|
|
1475
|
-
`${
|
|
1664
|
+
`${clampPercent4(bucket.remaining).toFixed(0)}% ${formatWindowLabel(bucket.windowMinutes, fallback, true)}`
|
|
1476
1665
|
);
|
|
1477
1666
|
}
|
|
1478
1667
|
return parts.length > 1 ? parts.join(" ") : formatCodexCreditsStatus(report);
|
|
@@ -1539,7 +1728,7 @@ function compactLimitLabel(label) {
|
|
|
1539
1728
|
return (suffix || normalized).toLowerCase().replace(/\s+/g, " ");
|
|
1540
1729
|
}
|
|
1541
1730
|
function formatPercentBucket(bucket) {
|
|
1542
|
-
const remaining =
|
|
1731
|
+
const remaining = clampPercent4(bucket.remaining ?? 0);
|
|
1543
1732
|
const filled = Math.round(remaining / 100 * BAR_SEGMENTS);
|
|
1544
1733
|
const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
|
|
1545
1734
|
return `[${"\u2588".repeat(filled)}${"\u2591".repeat(BAR_SEGMENTS - filled)}] ${remaining.toFixed(0)}% left${reset}`;
|
|
@@ -1572,7 +1761,7 @@ function formatReset(epochSeconds) {
|
|
|
1572
1761
|
function capitalize(value) {
|
|
1573
1762
|
return `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}`;
|
|
1574
1763
|
}
|
|
1575
|
-
function
|
|
1764
|
+
function clampPercent4(value) {
|
|
1576
1765
|
return Math.min(100, Math.max(0, value));
|
|
1577
1766
|
}
|
|
1578
1767
|
|
|
@@ -1971,6 +2160,11 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
1971
2160
|
statusRefreshTimer.unref?.();
|
|
1972
2161
|
};
|
|
1973
2162
|
const publishStatus = (ctx, outcome, model, shouldSchedule) => {
|
|
2163
|
+
if (adapterForProvider(model.provider)?.publishesStatusline === false) {
|
|
2164
|
+
clearStatusTimer();
|
|
2165
|
+
safeSetStatus(ctx, void 0);
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
1974
2168
|
if (outcome.state.status === "unsupported") {
|
|
1975
2169
|
clearStatusTimer();
|
|
1976
2170
|
safeSetStatus(ctx, void 0);
|
|
@@ -2153,6 +2347,10 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2153
2347
|
clearStatus(ctx);
|
|
2154
2348
|
return;
|
|
2155
2349
|
}
|
|
2350
|
+
if (adapter.publishesStatusline === false) {
|
|
2351
|
+
clearStatus(ctx);
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2156
2354
|
statusGeneration += 1;
|
|
2157
2355
|
const generation = statusGeneration;
|
|
2158
2356
|
statusController?.abort();
|
|
@@ -2743,6 +2941,7 @@ export {
|
|
|
2743
2941
|
normalizeOpenCodeZenPayload,
|
|
2744
2942
|
normalizeOpenRouterKeyPayload,
|
|
2745
2943
|
normalizeUsageSettings,
|
|
2944
|
+
normalizeZaiQuotaPayload,
|
|
2746
2945
|
providerIsConfigured,
|
|
2747
2946
|
queryProviderUsage,
|
|
2748
2947
|
redactUsageError,
|