@narumitw/pi-usage 0.52.2 → 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 CHANGED
@@ -148,11 +148,25 @@ OpenRouter documents the distinction between credit and rate limits in its [API
148
148
 
149
149
  - Provider ID: `opencode-go`
150
150
  - Semantics: OpenCode Zen plan usage windows—rolling, weekly, and monthly
151
- - Source: `GET {model base URL}/usage` on the configured `opencode.ai` gateway using Pi's resolved inference API key
151
+ - Source: `GET https://opencode.ai/zen/go/v1/usage` using Pi's resolved inference API key
152
152
  - Displayed data: used percentage and reset time for each window; `rate-limited` windows remain visible at their reported usage, while unknown statuses are reported as unavailable notes
153
153
  - Statusline examples: `zen 0% r 4% w 2% m`
154
154
 
155
- The usage endpoint is derived from the model's base URL (`…/zen/go/v1/usage`) and is only queried when the resolved origin is `https://opencode.ai`; other origins fail before sending the credential.
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
+
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.
156
170
 
157
171
  ## 🧭 Current and configured accounts
158
172
 
@@ -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 the selected model provider.
172
- It refreshes every five minutes while the session remains on a supported provider and is cleared when the model changes to an unsupported provider.
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/ # Codex, GitHub Copilot, and OpenRouter normalization adapters
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,10 +682,125 @@ 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";
688
802
  var OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
803
+ var OPENCODE_GO_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
689
804
  var MAX_SUCCESS_BODY_BYTES = 64 * 1024;
690
805
  var MAX_ERROR_BODY_BYTES = 4 * 1024;
691
806
  var AUTH_FINGERPRINT_SALT = randomBytes(32);
@@ -747,7 +862,7 @@ var SUPPORTED_ADAPTERS = [
747
862
  semantics: { kind: "consumer-subscription", label: "OpenCode Zen plan usage" },
748
863
  async query(auth, signal, timeoutMs) {
749
864
  const payload = await fetchProviderJson(
750
- opencodeUsageUrl(auth.model.baseUrl),
865
+ OPENCODE_GO_USAGE_URL,
751
866
  auth,
752
867
  signal,
753
868
  timeoutMs,
@@ -755,6 +870,43 @@ var SUPPORTED_ADAPTERS = [
755
870
  );
756
871
  return normalizeOpenCodeZenPayload(payload, Date.now());
757
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
+ }
758
910
  }
759
911
  ];
760
912
  function adapterForProvider(providerId) {
@@ -953,7 +1105,7 @@ function resolveGitHubCopilotUsageAuth(auth, model, salt, candidates, standalone
953
1105
  const matches = /* @__PURE__ */ new Map();
954
1106
  for (const candidate of candidates) {
955
1107
  try {
956
- const credential = asObject5(candidate);
1108
+ const credential = asObject6(candidate);
957
1109
  if (credential?.type !== "oauth") continue;
958
1110
  sawOAuth = true;
959
1111
  const storedAccess2 = typeof credential.access === "string" && credential.access ? credential.access : void 0;
@@ -1015,7 +1167,7 @@ function bearerToken(authorization) {
1015
1167
  const match = /^Bearer\s+(.+)$/iu.exec(authorization ?? "");
1016
1168
  return match?.[1];
1017
1169
  }
1018
- function asObject5(value) {
1170
+ function asObject6(value) {
1019
1171
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
1020
1172
  return value;
1021
1173
  }
@@ -1036,6 +1188,8 @@ function hasOfficialUrlOrigin(value, providerId) {
1036
1188
  if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
1037
1189
  if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
1038
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";
1039
1193
  if (providerId === "github-copilot") {
1040
1194
  return url.protocol === "https:" && /^api\.[a-z0-9-]+\.githubcopilot\.com$/u.test(url.hostname);
1041
1195
  }
@@ -1053,10 +1207,16 @@ function headerValue(headers, name) {
1053
1207
  function hasHeader(headers, name) {
1054
1208
  return Object.keys(headers).some((key) => key.toLowerCase() === name.toLowerCase());
1055
1209
  }
1056
- function opencodeUsageUrl(baseUrl) {
1057
- const base = baseUrl?.trim().replace(/\/+$/u, "");
1058
- if (!base) throw new Error("OpenCode Go model base URL is unavailable.");
1059
- return `${base}/usage`;
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 } };
1060
1220
  }
1061
1221
  function isAbortError(error) {
1062
1222
  return error instanceof Error && error.name === "AbortError";
@@ -1200,7 +1360,7 @@ function normalizeCodexResetCreditsPayload(payload) {
1200
1360
  if (rawCredits !== void 0 && !Array.isArray(rawCredits)) {
1201
1361
  throw new Error("Codex reset credits response returned invalid credits.");
1202
1362
  }
1203
- const options = (rawCredits ?? []).map(asObject6).filter((credit) => Boolean(credit)).filter((credit) => credit.status === "available" && credit.reset_type === "codex_rate_limits").map(normalizeResetOption).sort(
1363
+ const options = (rawCredits ?? []).map(asObject7).filter((credit) => Boolean(credit)).filter((credit) => credit.status === "available" && credit.reset_type === "codex_rate_limits").map(normalizeResetOption).sort(
1204
1364
  (left, right) => (left.expiresAt ?? Number.MAX_SAFE_INTEGER) - (right.expiresAt ?? Number.MAX_SAFE_INTEGER)
1205
1365
  ).slice(0, Math.min(availableCount, MAX_RESET_OPTIONS));
1206
1366
  if (availableCount > 0 && options.length === 0) {
@@ -1215,7 +1375,7 @@ function selectCodexResetCredential(candidates, resolvedAccess, resolvedAccountI
1215
1375
  const matches = /* @__PURE__ */ new Map();
1216
1376
  for (const candidate of candidates) {
1217
1377
  try {
1218
- const credential = asObject6(candidate);
1378
+ const credential = asObject7(candidate);
1219
1379
  if (credential?.type !== "oauth") continue;
1220
1380
  sawOAuth = true;
1221
1381
  const storedAccess = asNonemptyString(credential.access);
@@ -1254,7 +1414,7 @@ function codexAccountIdFromAccessToken(access) {
1254
1414
  const parts = access.split(".");
1255
1415
  if (parts.length !== 3 || !parts[1]) return void 0;
1256
1416
  const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
1257
- const claims = asObject6(asObject6(payload)?.["https://api.openai.com/auth"]);
1417
+ const claims = asObject7(asObject7(payload)?.["https://api.openai.com/auth"]);
1258
1418
  return validHeaderValue(claims?.chatgpt_account_id);
1259
1419
  } catch {
1260
1420
  return void 0;
@@ -1286,7 +1446,7 @@ function normalizeResetOption(credit) {
1286
1446
  function isCodexResetOutcomeCode(value) {
1287
1447
  return value === "reset" || value === "nothing_to_reset" || value === "no_credit" || value === "already_redeemed";
1288
1448
  }
1289
- function asObject6(value) {
1449
+ function asObject7(value) {
1290
1450
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
1291
1451
  return value;
1292
1452
  }
@@ -1334,7 +1494,9 @@ function formatUsageReport(report, displayState) {
1334
1494
  else if (report.providerId === "github-copilot") formatGitHubCopilotReport(lines, report);
1335
1495
  else if (report.providerId === "openrouter") formatOpenRouterReport(lines, report);
1336
1496
  else if (report.providerId === "opencode-go") formatOpenCodeZenReport(lines, report);
1337
- else formatGenericReport(lines, report);
1497
+ else if (report.providerId === "zai" || report.providerId === "zai-coding-cn") {
1498
+ formatZaiReport(lines, report);
1499
+ } else formatGenericReport(lines, report);
1338
1500
  if (report.notes) {
1339
1501
  for (const note of report.notes) lines.push(note);
1340
1502
  }
@@ -1421,7 +1583,7 @@ function compactGitHubCopilotQuotaKind(bucket) {
1421
1583
  }
1422
1584
  function percentRemaining(bucket) {
1423
1585
  if (!bucket.limit || bucket.remaining === void 0) return 0;
1424
- return Math.round(clampPercent3(bucket.remaining / bucket.limit * 100));
1586
+ return Math.round(clampPercent4(bucket.remaining / bucket.limit * 100));
1425
1587
  }
1426
1588
  function formatOpenRouterReport(lines, report) {
1427
1589
  const limit = report.buckets.find((bucket) => bucket.id === "key-limit");
@@ -1448,10 +1610,33 @@ function formatOpenCodeZenStatusline(report) {
1448
1610
  for (const bucket of report.buckets) {
1449
1611
  if (bucket.used === void 0) continue;
1450
1612
  const compact = bucket.id === "rolling" ? "r" : bucket.id === "weekly" ? "w" : "m";
1451
- parts.push(`${clampPercent3(bucket.used).toFixed(0)}% ${compact}`);
1613
+ parts.push(`${clampPercent4(bucket.used).toFixed(0)}% ${compact}`);
1452
1614
  }
1453
1615
  return parts.length > 1 ? parts.join(" ") : void 0;
1454
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
+ }
1455
1640
  function formatGenericReport(lines, report) {
1456
1641
  for (const bucket of report.buckets) {
1457
1642
  lines.push(
@@ -1476,7 +1661,7 @@ function formatCodexStatusline(report, model) {
1476
1661
  if (bucket.remaining === void 0) continue;
1477
1662
  const fallback = bucket.id.endsWith(":secondary") ? "weekly" : "5h";
1478
1663
  parts.push(
1479
- `${clampPercent3(bucket.remaining).toFixed(0)}% ${formatWindowLabel(bucket.windowMinutes, fallback, true)}`
1664
+ `${clampPercent4(bucket.remaining).toFixed(0)}% ${formatWindowLabel(bucket.windowMinutes, fallback, true)}`
1480
1665
  );
1481
1666
  }
1482
1667
  return parts.length > 1 ? parts.join(" ") : formatCodexCreditsStatus(report);
@@ -1543,7 +1728,7 @@ function compactLimitLabel(label) {
1543
1728
  return (suffix || normalized).toLowerCase().replace(/\s+/g, " ");
1544
1729
  }
1545
1730
  function formatPercentBucket(bucket) {
1546
- const remaining = clampPercent3(bucket.remaining ?? 0);
1731
+ const remaining = clampPercent4(bucket.remaining ?? 0);
1547
1732
  const filled = Math.round(remaining / 100 * BAR_SEGMENTS);
1548
1733
  const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
1549
1734
  return `[${"\u2588".repeat(filled)}${"\u2591".repeat(BAR_SEGMENTS - filled)}] ${remaining.toFixed(0)}% left${reset}`;
@@ -1576,7 +1761,7 @@ function formatReset(epochSeconds) {
1576
1761
  function capitalize(value) {
1577
1762
  return `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}`;
1578
1763
  }
1579
- function clampPercent3(value) {
1764
+ function clampPercent4(value) {
1580
1765
  return Math.min(100, Math.max(0, value));
1581
1766
  }
1582
1767
 
@@ -1975,6 +2160,11 @@ function usageExtension(pi, dependencies = {}) {
1975
2160
  statusRefreshTimer.unref?.();
1976
2161
  };
1977
2162
  const publishStatus = (ctx, outcome, model, shouldSchedule) => {
2163
+ if (adapterForProvider(model.provider)?.publishesStatusline === false) {
2164
+ clearStatusTimer();
2165
+ safeSetStatus(ctx, void 0);
2166
+ return;
2167
+ }
1978
2168
  if (outcome.state.status === "unsupported") {
1979
2169
  clearStatusTimer();
1980
2170
  safeSetStatus(ctx, void 0);
@@ -2157,6 +2347,10 @@ function usageExtension(pi, dependencies = {}) {
2157
2347
  clearStatus(ctx);
2158
2348
  return;
2159
2349
  }
2350
+ if (adapter.publishesStatusline === false) {
2351
+ clearStatus(ctx);
2352
+ return;
2353
+ }
2160
2354
  statusGeneration += 1;
2161
2355
  const generation = statusGeneration;
2162
2356
  statusController?.abort();
@@ -2747,6 +2941,7 @@ export {
2747
2941
  normalizeOpenCodeZenPayload,
2748
2942
  normalizeOpenRouterKeyPayload,
2749
2943
  normalizeUsageSettings,
2944
+ normalizeZaiQuotaPayload,
2750
2945
  providerIsConfigured,
2751
2946
  queryProviderUsage,
2752
2947
  redactUsageError,