@narumitw/pi-usage 0.54.0 → 0.57.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/dist/index.ts CHANGED
@@ -449,10 +449,81 @@ function clampPercent(value) {
449
449
  return Math.min(100, Math.max(0, value));
450
450
  }
451
451
 
452
+ // src/providers/deepseek.ts
453
+ var CURRENCIES = ["CNY", "USD"];
454
+ var BALANCE_FIELDS = [
455
+ ["total", "Total balance", "total_balance"],
456
+ ["granted", "Granted balance", "granted_balance"],
457
+ ["topped-up", "Topped-up balance", "topped_up_balance"]
458
+ ];
459
+ function normalizeDeepSeekBalancePayload(payload, capturedAt) {
460
+ if (typeof payload.is_available !== "boolean") {
461
+ throw new Error("DeepSeek API balance response availability was not a boolean.");
462
+ }
463
+ if (!Array.isArray(payload.balance_infos) || payload.balance_infos.length === 0) {
464
+ throw new Error("DeepSeek API balance response returned no balance information.");
465
+ }
466
+ const balances = /* @__PURE__ */ new Map();
467
+ for (const raw of payload.balance_infos) {
468
+ const balance = asObject2(raw);
469
+ if (!balance) throw new Error("DeepSeek API balance row was not an object.");
470
+ const currency = deepSeekCurrency(balance.currency);
471
+ if (!currency) throw new Error("DeepSeek API balance row returned an unsupported currency.");
472
+ if (balances.has(currency)) {
473
+ throw new Error(`DeepSeek API balance response repeated ${currency}.`);
474
+ }
475
+ for (const [, label, field] of BALANCE_FIELDS) {
476
+ if (!decimalAmount(balance[field])) {
477
+ throw new Error(`DeepSeek API balance ${label.toLowerCase()} was not a valid amount.`);
478
+ }
479
+ }
480
+ balances.set(currency, balance);
481
+ }
482
+ const metrics = [
483
+ {
484
+ id: "api-availability",
485
+ label: "API calls",
486
+ value: payload.is_available ? "available" : "unavailable"
487
+ }
488
+ ];
489
+ for (const currency of CURRENCIES) {
490
+ const balance = balances.get(currency);
491
+ if (!balance) continue;
492
+ for (const [id, label, field] of BALANCE_FIELDS) {
493
+ metrics.push({
494
+ id: `${currency.toLowerCase()}-${id}`,
495
+ label,
496
+ value: balance[field],
497
+ unit: "currency",
498
+ currency
499
+ });
500
+ }
501
+ }
502
+ return {
503
+ providerId: "deepseek",
504
+ providerName: "DeepSeek",
505
+ capturedAt,
506
+ source: "deepseek-balance",
507
+ semantics: { kind: "api-key", label: "DeepSeek API balance" },
508
+ buckets: [],
509
+ metrics
510
+ };
511
+ }
512
+ function asObject2(value) {
513
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
514
+ return value;
515
+ }
516
+ function deepSeekCurrency(value) {
517
+ return CURRENCIES.find((currency) => currency === value);
518
+ }
519
+ function decimalAmount(value) {
520
+ return typeof value === "string" && value.length <= 64 && /^(?:0|[1-9]\d*)(?:\.\d+)?$/u.test(value);
521
+ }
522
+
452
523
  // src/providers/github-copilot.ts
453
524
  function normalizeGitHubCopilotUsagePayload(payload, capturedAt) {
454
- const snapshots = asObject2(payload.quota_snapshots);
455
- const premium = asObject2(snapshots?.premium_interactions);
525
+ const snapshots = asObject3(payload.quota_snapshots);
526
+ const premium = asObject3(snapshots?.premium_interactions);
456
527
  const metrics = [];
457
528
  let semanticsLabel;
458
529
  let bucket;
@@ -493,8 +564,8 @@ function normalizeGitHubCopilotUsagePayload(payload, capturedAt) {
493
564
  };
494
565
  }
495
566
  } else {
496
- const limited = asObject2(payload.limited_user_quotas);
497
- const monthly = asObject2(payload.monthly_quotas);
567
+ const limited = asObject3(payload.limited_user_quotas);
568
+ const monthly = asObject3(payload.monthly_quotas);
498
569
  const remaining = asNonnegativeNumber(limited?.chat);
499
570
  const entitlement = asNonnegativeNumber(monthly?.chat);
500
571
  if (remaining === void 0 || entitlement === void 0) {
@@ -533,7 +604,7 @@ function resetTimestamp(payload) {
533
604
  const milliseconds = Date.parse(raw);
534
605
  return Number.isNaN(milliseconds) ? {} : { resetsAt: Math.floor(milliseconds / 1e3) };
535
606
  }
536
- function asObject2(value) {
607
+ function asObject3(value) {
537
608
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
538
609
  return value;
539
610
  }
@@ -556,7 +627,7 @@ var DAILY_WINDOW_MINUTES = 1440;
556
627
  var WEEKLY_WINDOW_MINUTES = 10080;
557
628
  var FIXED_POINT_UNITS_PER_CENT = 1e6;
558
629
  function normalizeKimiCodingUsagePayload(payload, capturedAt) {
559
- const root = asObject3(payload);
630
+ const root = asObject4(payload);
560
631
  if (!root) throw new Error("Kimi Coding usage response was not an object.");
561
632
  const candidates = [];
562
633
  let omittedWindow = false;
@@ -565,7 +636,7 @@ function normalizeKimiCodingUsagePayload(payload, capturedAt) {
565
636
  else if (root.usage !== void 0) omittedWindow = true;
566
637
  if (Array.isArray(root.limits)) {
567
638
  for (const raw of root.limits) {
568
- const item = asObject3(raw);
639
+ const item = asObject4(raw);
569
640
  const windowMinutes = parseWindowMinutes(item?.window);
570
641
  const label = sanitizedLabel(item?.name);
571
642
  const bucket = windowMinutes === void 0 ? void 0 : parseUsageRow(item?.detail, windowMinutes, label ?? defaultWindowLabel(windowMinutes));
@@ -602,7 +673,7 @@ function normalizeKimiCodingUsagePayload(payload, capturedAt) {
602
673
  };
603
674
  }
604
675
  function parseUsageRow(value, windowMinutes, label) {
605
- const row = asObject3(value);
676
+ const row = asObject4(value);
606
677
  if (!row) return void 0;
607
678
  const used = asNonnegativeInteger2(row.used);
608
679
  const limit = asNonnegativeInteger2(row.limit);
@@ -620,7 +691,7 @@ function parseUsageRow(value, windowMinutes, label) {
620
691
  };
621
692
  }
622
693
  function parseWindowMinutes(value) {
623
- const window = asObject3(value);
694
+ const window = asObject4(value);
624
695
  if (!window) return void 0;
625
696
  const duration = asPositiveInteger(window.duration);
626
697
  if (duration === void 0) return void 0;
@@ -630,8 +701,8 @@ function parseWindowMinutes(value) {
630
701
  return Number.isSafeInteger(minutes) ? minutes : void 0;
631
702
  }
632
703
  function parseBoosterWallet(value) {
633
- const wallet = asObject3(value);
634
- const balance = asObject3(wallet?.balance);
704
+ const wallet = asObject4(value);
705
+ const balance = asObject4(wallet?.balance);
635
706
  if (!wallet || !balance || balance.type !== "BOOSTER") return [];
636
707
  const totalRaw = asPositiveInteger(balance.amount);
637
708
  if (totalRaw === void 0) return [];
@@ -682,7 +753,7 @@ function parseBoosterWallet(value) {
682
753
  return metrics;
683
754
  }
684
755
  function parseMoney(value) {
685
- const money = asObject3(value);
756
+ const money = asObject4(value);
686
757
  if (!money) return void 0;
687
758
  const cents = asNonnegativeInteger2(money.priceInCents);
688
759
  if (cents === void 0) return void 0;
@@ -696,7 +767,7 @@ function fixedPointToMajor(value) {
696
767
  const major = roundedCents / 100;
697
768
  return Number.isSafeInteger(roundedCents) && Number.isFinite(major) ? major : void 0;
698
769
  }
699
- function asObject3(value) {
770
+ function asObject4(value) {
700
771
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
701
772
  return value;
702
773
  }
@@ -770,12 +841,12 @@ var ZEN_WINDOWS = [
770
841
  { key: "monthly", label: "Monthly" }
771
842
  ];
772
843
  function normalizeOpenCodeZenPayload(payload, capturedAt) {
773
- const usage = asObject4(payload.usage);
844
+ const usage = asObject5(payload.usage);
774
845
  if (!usage) throw new Error("OpenCode Zen usage response was not an object.");
775
846
  const buckets = [];
776
847
  const notes = [];
777
848
  for (const window of ZEN_WINDOWS) {
778
- const raw = asObject4(usage[window.key]);
849
+ const raw = asObject5(usage[window.key]);
779
850
  if (!raw) continue;
780
851
  const status = asString3(raw.status);
781
852
  if (status !== "ok" && status !== "rate-limited") {
@@ -812,7 +883,7 @@ function normalizeOpenCodeZenPayload(payload, capturedAt) {
812
883
  ...notes.length > 0 ? { notes } : {}
813
884
  };
814
885
  }
815
- function asObject4(value) {
886
+ function asObject5(value) {
816
887
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
817
888
  return value;
818
889
  }
@@ -836,7 +907,7 @@ function clampPercent2(value) {
836
907
 
837
908
  // src/providers/openrouter.ts
838
909
  function normalizeOpenRouterKeyPayload(payload, capturedAt) {
839
- const data = asObject5(payload.data);
910
+ const data = asObject6(payload.data);
840
911
  if (!data) throw new Error("OpenRouter key response data was not an object.");
841
912
  const limit = asNonnegativeNumber3(data.limit);
842
913
  const remaining = asNonnegativeNumber3(data.limit_remaining);
@@ -882,7 +953,7 @@ function addUsageMetric(metrics, id, label, value) {
882
953
  if (amount === void 0) return;
883
954
  metrics.push({ id, label, value: amount, unit: "usd" });
884
955
  }
885
- function asObject5(value) {
956
+ function asObject6(value) {
886
957
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
887
958
  return value;
888
959
  }
@@ -1060,13 +1131,13 @@ function isRecord2(value) {
1060
1131
  var FIVE_HOUR_WINDOW_MINUTES2 = 300;
1061
1132
  var WEEKLY_WINDOW_MINUTES2 = 10080;
1062
1133
  function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt) {
1063
- const data = asObject6(payload.data);
1134
+ const data = asObject7(payload.data);
1064
1135
  if (!data) throw new Error("Z.AI quota response data was not an object.");
1065
1136
  const limits = Array.isArray(data.limits) ? data.limits : [];
1066
1137
  const buckets = [];
1067
1138
  const metrics = [];
1068
1139
  for (const raw of limits) {
1069
- const limit = asObject6(raw);
1140
+ const limit = asObject7(raw);
1070
1141
  if (!limit) continue;
1071
1142
  const type = asString5(limit.type);
1072
1143
  const unit = asNonnegativeNumber4(limit.unit);
@@ -1138,7 +1209,7 @@ function addCountBucket(buckets, limit, id, label, windowMinutes) {
1138
1209
  function addUsageDetailMetrics(metrics, value) {
1139
1210
  if (!Array.isArray(value)) return;
1140
1211
  for (const raw of value) {
1141
- const detail = asObject6(raw);
1212
+ const detail = asObject7(raw);
1142
1213
  if (!detail) continue;
1143
1214
  const label = asString5(detail.modelCode);
1144
1215
  const usage = asNonnegativeNumber4(detail.usage);
@@ -1146,7 +1217,7 @@ function addUsageDetailMetrics(metrics, value) {
1146
1217
  metrics.push({ id: `mcp-${kebabCase(label)}`, label, value: usage, unit: "count" });
1147
1218
  }
1148
1219
  }
1149
- function asObject6(value) {
1220
+ function asObject7(value) {
1150
1221
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
1151
1222
  return value;
1152
1223
  }
@@ -1172,6 +1243,7 @@ function clampPercent3(value) {
1172
1243
 
1173
1244
  // src/query.ts
1174
1245
  var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
1246
+ var DEEPSEEK_BALANCE_URL = "https://api.deepseek.com/user/balance";
1175
1247
  var GITHUB_COPILOT_USAGE_URL = "https://api.github.com/copilot_internal/user";
1176
1248
  var OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
1177
1249
  var OPENCODE_GO_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
@@ -1205,6 +1277,27 @@ var SUPPORTED_ADAPTERS = [
1205
1277
  return normalizeCodexBackendPayload(payload, Date.now());
1206
1278
  }
1207
1279
  },
1280
+ {
1281
+ id: "deepseek",
1282
+ displayName: "DeepSeek",
1283
+ semantics: { kind: "api-key", label: "DeepSeek API balance" },
1284
+ async query(auth, signal, timeoutMs, guard) {
1285
+ if (!guard) throw new Error("DeepSeek API balance requires request-boundary revalidation.");
1286
+ const startedAt = Date.now();
1287
+ await guard();
1288
+ const remainingMs = timeoutMs - (Date.now() - startedAt);
1289
+ if (remainingMs <= 0) throw new Error("Timed out while revalidating DeepSeek runtime auth.");
1290
+ const payload = await fetchProviderJson(
1291
+ DEEPSEEK_BALANCE_URL,
1292
+ auth,
1293
+ signal,
1294
+ remainingMs,
1295
+ "DeepSeek API balance endpoint",
1296
+ { redirect: "error" }
1297
+ );
1298
+ return normalizeDeepSeekBalancePayload(payload, Date.now());
1299
+ }
1300
+ },
1208
1301
  {
1209
1302
  id: "github-copilot",
1210
1303
  displayName: "GitHub Copilot",
@@ -1273,7 +1366,6 @@ var SUPPORTED_ADAPTERS = [
1273
1366
  id: "zai",
1274
1367
  displayName: "Z.AI",
1275
1368
  semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
1276
- publishesStatusline: false,
1277
1369
  async query(auth, signal, timeoutMs) {
1278
1370
  const payload = await fetchProviderJson(
1279
1371
  zaiMonitorUrl(auth.model.baseUrl),
@@ -1289,7 +1381,6 @@ var SUPPORTED_ADAPTERS = [
1289
1381
  id: "zai-coding-cn",
1290
1382
  displayName: "Z.AI Coding CN",
1291
1383
  semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
1292
- publishesStatusline: false,
1293
1384
  async query(auth, signal, timeoutMs) {
1294
1385
  const payload = await fetchProviderJson(
1295
1386
  zaiMonitorUrl(auth.model.baseUrl),
@@ -1351,11 +1442,11 @@ var XAI_ADAPTER = {
1351
1442
  return normalizeXaiBillingPayload(billingPayload, userPayload.subscriptionTier, Date.now());
1352
1443
  }
1353
1444
  };
1354
- function usageAdapters(xaiUsage = true) {
1355
- return xaiUsage ? [...SUPPORTED_ADAPTERS, XAI_ADAPTER] : SUPPORTED_ADAPTERS;
1445
+ function usageAdapters() {
1446
+ return [...SUPPORTED_ADAPTERS, XAI_ADAPTER];
1356
1447
  }
1357
- function adapterForProvider(providerId, xaiUsage = true) {
1358
- return usageAdapters(xaiUsage).find((adapter) => adapter.id === providerId);
1448
+ function adapterForProvider(providerId) {
1449
+ return usageAdapters().find((adapter) => adapter.id === providerId);
1359
1450
  }
1360
1451
  function isStaleExtensionContextError(error) {
1361
1452
  return error instanceof Error && error.message.includes("This extension ctx is stale after session replacement or reload");
@@ -1372,11 +1463,14 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
1372
1463
  if (!model) return void 0;
1373
1464
  const registry = ctx.modelRegistry;
1374
1465
  let modelAuth;
1375
- if (ctx.model?.provider === adapter.id && typeof registry.getApiKeyAndHeaders === "function") {
1376
- const result = await registry.getApiKeyAndHeaders(ctx.model);
1466
+ const currentModel = ctx.model?.provider === adapter.id ? ctx.model : void 0;
1467
+ const resolveCurrentModelAuth = async () => {
1468
+ if (!currentModel || typeof registry.getApiKeyAndHeaders !== "function") return void 0;
1469
+ const result = await registry.getApiKeyAndHeaders(currentModel);
1377
1470
  if (!result.ok) throw new Error(redactUsageError(result.error));
1378
- if (authorizationFrom(result)) modelAuth = result;
1379
- }
1471
+ return authorizationFrom(result) ? result : void 0;
1472
+ };
1473
+ if (adapter.id !== "deepseek") modelAuth = await resolveCurrentModelAuth();
1380
1474
  if (typeof registry.getProviderAuth !== "function") {
1381
1475
  throw new Error("pi-usage requires Pi 0.81.0 or newer to validate resolved provider auth.");
1382
1476
  }
@@ -1386,6 +1480,7 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
1386
1480
  `${adapter.displayName} usage cannot send a proxy-resolved credential to the official usage endpoint.`
1387
1481
  );
1388
1482
  }
1483
+ if (adapter.id === "deepseek") modelAuth = await resolveCurrentModelAuth();
1389
1484
  const auth = modelAuth ?? providerResult?.auth;
1390
1485
  if (!auth) return void 0;
1391
1486
  if (adapter.id === "github-copilot") {
@@ -1406,6 +1501,26 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
1406
1501
  if (!offered.ok) throw new Error("xAI OAuth credential discovery failed closed.");
1407
1502
  return resolveXaiUsageAuth(auth, model, salt, offered.candidates);
1408
1503
  }
1504
+ if (adapter.id === "deepseek") {
1505
+ const resolvedAuthorization = authorizationFrom(auth);
1506
+ const access = bearerToken(resolvedAuthorization);
1507
+ if (!access) throw new Error("DeepSeek API balance requires Bearer authentication.");
1508
+ const authorization2 = `Bearer ${access}`;
1509
+ const headers2 = { Authorization: authorization2 };
1510
+ return {
1511
+ apiKey: access,
1512
+ headers: headers2,
1513
+ fingerprint: fingerprintResolvedAuth({ headers: headers2 }, salt),
1514
+ secrets: [
1515
+ access,
1516
+ auth.apiKey,
1517
+ headerValue(auth.headers, "Authorization"),
1518
+ resolvedAuthorization,
1519
+ authorization2
1520
+ ].filter((value) => Boolean(value)),
1521
+ model
1522
+ };
1523
+ }
1409
1524
  const authorization = authorizationFrom(auth);
1410
1525
  if (!authorization) return void 0;
1411
1526
  const headers = { Authorization: authorization };
@@ -1563,7 +1678,7 @@ function resolveXaiUsageAuth(auth, model, salt, candidates) {
1563
1678
  const matches = [];
1564
1679
  for (const candidate of candidates) {
1565
1680
  try {
1566
- const credential = asObject7(candidate);
1681
+ const credential = asObject8(candidate);
1567
1682
  if (credential?.type !== "oauth") continue;
1568
1683
  sawOAuth = true;
1569
1684
  if (credential.access !== resolvedAccess) continue;
@@ -1617,7 +1732,7 @@ function resolveGitHubCopilotUsageAuth(auth, model, salt, candidates, standalone
1617
1732
  const matches = /* @__PURE__ */ new Map();
1618
1733
  for (const candidate of candidates) {
1619
1734
  try {
1620
- const credential = asObject7(candidate);
1735
+ const credential = asObject8(candidate);
1621
1736
  if (credential?.type !== "oauth") continue;
1622
1737
  sawOAuth = true;
1623
1738
  const storedAccess2 = typeof credential.access === "string" && credential.access ? credential.access : void 0;
@@ -1679,7 +1794,7 @@ function bearerToken(authorization) {
1679
1794
  const match = /^Bearer\s+(.+)$/iu.exec(authorization ?? "");
1680
1795
  return match?.[1];
1681
1796
  }
1682
- function asObject7(value) {
1797
+ function asObject8(value) {
1683
1798
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
1684
1799
  return value;
1685
1800
  }
@@ -1698,6 +1813,7 @@ function hasOfficialUrlOrigin(value, providerId) {
1698
1813
  try {
1699
1814
  const url = new URL(value);
1700
1815
  if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
1816
+ if (providerId === "deepseek") return url.origin === "https://api.deepseek.com";
1701
1817
  if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
1702
1818
  if (providerId === "opencode-go") return url.origin === "https://opencode.ai";
1703
1819
  if (providerId === "kimi-coding") return url.origin === "https://api.kimi.com";
@@ -1885,7 +2001,7 @@ function normalizeCodexResetCreditsPayload(payload) {
1885
2001
  if (rawCredits !== void 0 && !Array.isArray(rawCredits)) {
1886
2002
  throw new Error("Codex reset credits response returned invalid credits.");
1887
2003
  }
1888
- const options = (rawCredits ?? []).map(asObject8).filter((credit) => Boolean(credit)).filter((credit) => credit.status === "available" && credit.reset_type === "codex_rate_limits").map(normalizeResetOption).sort(
2004
+ const options = (rawCredits ?? []).map(asObject9).filter((credit) => Boolean(credit)).filter((credit) => credit.status === "available" && credit.reset_type === "codex_rate_limits").map(normalizeResetOption).sort(
1889
2005
  (left, right) => (left.expiresAt ?? Number.MAX_SAFE_INTEGER) - (right.expiresAt ?? Number.MAX_SAFE_INTEGER)
1890
2006
  ).slice(0, Math.min(availableCount, MAX_RESET_OPTIONS));
1891
2007
  if (availableCount > 0 && options.length === 0) {
@@ -1900,7 +2016,7 @@ function selectCodexResetCredential(candidates, resolvedAccess, resolvedAccountI
1900
2016
  const matches = /* @__PURE__ */ new Map();
1901
2017
  for (const candidate of candidates) {
1902
2018
  try {
1903
- const credential = asObject8(candidate);
2019
+ const credential = asObject9(candidate);
1904
2020
  if (credential?.type !== "oauth") continue;
1905
2021
  sawOAuth = true;
1906
2022
  const storedAccess = asNonemptyString(credential.access);
@@ -1939,7 +2055,7 @@ function codexAccountIdFromAccessToken(access) {
1939
2055
  const parts = access.split(".");
1940
2056
  if (parts.length !== 3 || !parts[1]) return void 0;
1941
2057
  const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
1942
- const claims = asObject8(asObject8(payload)?.["https://api.openai.com/auth"]);
2058
+ const claims = asObject9(asObject9(payload)?.["https://api.openai.com/auth"]);
1943
2059
  return validHeaderValue(claims?.chatgpt_account_id);
1944
2060
  } catch {
1945
2061
  return void 0;
@@ -1971,7 +2087,7 @@ function normalizeResetOption(credit) {
1971
2087
  function isCodexResetOutcomeCode(value) {
1972
2088
  return value === "reset" || value === "nothing_to_reset" || value === "no_credit" || value === "already_redeemed";
1973
2089
  }
1974
- function asObject8(value) {
2090
+ function asObject9(value) {
1975
2091
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
1976
2092
  return value;
1977
2093
  }
@@ -2012,10 +2128,12 @@ var BAR_SEGMENTS = 20;
2012
2128
  var VALUE_COLUMN = 29;
2013
2129
  function formatUsageReport(report, displayState) {
2014
2130
  const stateLabel = displayState === "current" ? "Current" : "Configured";
2015
- const lines = [`${report.providerName} Usage \xB7 ${stateLabel}`];
2131
+ const title = report.providerId === "deepseek" ? "DeepSeek API Balance" : `${report.providerName} Usage`;
2132
+ const lines = [`${title} \xB7 ${stateLabel}`];
2016
2133
  if (report.accountLabel) lines.push(`Account: ${report.accountLabel}`);
2017
2134
  lines.push(`Semantics: ${report.semantics.label}`, "");
2018
2135
  if (report.providerId === "openai-codex") formatCodexReport(lines, report);
2136
+ else if (report.providerId === "deepseek") formatDeepSeekReport(lines, report);
2019
2137
  else if (report.providerId === "github-copilot") formatGitHubCopilotReport(lines, report);
2020
2138
  else if (report.providerId === "openrouter") formatOpenRouterReport(lines, report);
2021
2139
  else if (report.providerId === "opencode-go") formatOpenCodeZenReport(lines, report);
@@ -2029,8 +2147,11 @@ function formatUsageReport(report, displayState) {
2029
2147
  }
2030
2148
  return lines.join("\n").trimEnd();
2031
2149
  }
2032
- function formatUsageStatusline(report, model) {
2033
- if (report.providerId === "openai-codex") return formatCodexStatusline(report, model);
2150
+ function formatUsageStatusline(report, model, now = Date.now(), showCodexResetCountdown = true) {
2151
+ if (report.providerId === "openai-codex") {
2152
+ return formatCodexStatusline(report, model, now, showCodexResetCountdown);
2153
+ }
2154
+ if (report.providerId === "deepseek") return formatDeepSeekStatusline(report);
2034
2155
  if (report.providerId === "github-copilot") return formatGitHubCopilotStatusline(report);
2035
2156
  if (report.providerId === "openrouter") {
2036
2157
  const limit = report.buckets.find((bucket) => bucket.id === "key-limit");
@@ -2040,6 +2161,9 @@ function formatUsageStatusline(report, model) {
2040
2161
  }
2041
2162
  if (report.providerId === "opencode-go") return formatOpenCodeZenStatusline(report);
2042
2163
  if (report.providerId === "kimi-coding") return formatKimiCodingStatusline(report);
2164
+ if (report.providerId === "zai" || report.providerId === "zai-coding-cn") {
2165
+ return formatZaiStatusline(report);
2166
+ }
2043
2167
  return void 0;
2044
2168
  }
2045
2169
  function formatProviderStates(states) {
@@ -2073,6 +2197,31 @@ function formatCodexReport(lines, report) {
2073
2197
  }
2074
2198
  }
2075
2199
  }
2200
+ function formatDeepSeekReport(lines, report) {
2201
+ const availability = report.metrics.find((metric) => metric.id === "api-availability");
2202
+ lines.push(
2203
+ `${"API calls:".padEnd(VALUE_COLUMN)}${availability?.value === "available" ? "Available" : "Unavailable"}`
2204
+ );
2205
+ for (const currency of ["CNY", "USD"]) {
2206
+ const metrics = report.metrics.filter((metric) => metric.currency === currency);
2207
+ if (metrics.length === 0) continue;
2208
+ lines.push("", `${currency} balance:`);
2209
+ for (const metric of metrics) {
2210
+ lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${currency} ${metric.value}`);
2211
+ }
2212
+ }
2213
+ }
2214
+ function formatDeepSeekStatusline(report) {
2215
+ const availability = report.metrics.find((metric) => metric.id === "api-availability");
2216
+ if (availability?.value !== "available") return "deepseek API unavailable";
2217
+ const totals = ["CNY", "USD"].flatMap((currency) => {
2218
+ const metric = report.metrics.find(
2219
+ (candidate) => candidate.id === `${currency.toLowerCase()}-total`
2220
+ );
2221
+ return metric ? [`${currency} ${metric.value}`] : [];
2222
+ });
2223
+ return totals.length > 0 ? `deepseek ${totals.join(" \xB7 ")}` : "deepseek balance unavailable";
2224
+ }
2076
2225
  function formatGitHubCopilotReport(lines, report) {
2077
2226
  const quota = findGitHubCopilotQuota(report);
2078
2227
  if (!quota || quota.limit === void 0 || quota.remaining === void 0) {
@@ -2187,6 +2336,21 @@ function formatKimiCodingStatusline(report) {
2187
2336
  }
2188
2337
  return parts.length > 1 ? parts.join(" ") : void 0;
2189
2338
  }
2339
+ function formatZaiStatusline(report) {
2340
+ const selected = [
2341
+ report.buckets.find((bucket) => bucket.id === "five-hour"),
2342
+ report.buckets.find((bucket) => bucket.id === "weekly")
2343
+ ];
2344
+ const parts = ["zai"];
2345
+ for (const bucket of selected) {
2346
+ if (!bucket?.limit || bucket.remaining === void 0) continue;
2347
+ const fallback = bucket.id === "weekly" ? "weekly" : "5h";
2348
+ parts.push(
2349
+ `${percentRemaining(bucket)}% ${formatWindowLabel(bucket.windowMinutes, fallback, true)}`
2350
+ );
2351
+ }
2352
+ return parts.length > 1 ? parts.join(" ") : void 0;
2353
+ }
2190
2354
  function formatCurrencyMetric(metric) {
2191
2355
  if (typeof metric.value !== "number") return String(metric.value);
2192
2356
  if (!metric.currency) return "unavailable";
@@ -2258,7 +2422,7 @@ function formatGenericReport(lines, report) {
2258
2422
  );
2259
2423
  }
2260
2424
  }
2261
- function formatCodexStatusline(report, model) {
2425
+ function formatCodexStatusline(report, model, now = Date.now(), showResetCountdown = true) {
2262
2426
  const group = selectCodexGroup(report, model);
2263
2427
  if (!group) return formatCodexCreditsStatus(report);
2264
2428
  const buckets = report.buckets.filter((bucket) => (bucket.groupId ?? bucket.id) === group);
@@ -2268,10 +2432,15 @@ function formatCodexStatusline(report, model) {
2268
2432
  ];
2269
2433
  for (const bucket of buckets) {
2270
2434
  if (bucket.remaining === void 0) continue;
2435
+ const percent = `${clampPercent4(bucket.remaining).toFixed(0)}%`;
2271
2436
  const fallback = bucket.id.endsWith(":secondary") ? "weekly" : "5h";
2272
- parts.push(
2273
- `${clampPercent4(bucket.remaining).toFixed(0)}% ${formatWindowLabel(bucket.windowMinutes, fallback, true)}`
2274
- );
2437
+ const window = formatWindowLabel(bucket.windowMinutes, fallback, true);
2438
+ if (!showResetCountdown) {
2439
+ parts.push(`${percent} ${window}`);
2440
+ continue;
2441
+ }
2442
+ const reset = formatResetCountdown(bucket.resetsAt, now);
2443
+ parts.push(`${percent} ${reset ? `\u21BB ${reset}` : window}`);
2275
2444
  }
2276
2445
  return parts.length > 1 ? parts.join(" ") : formatCodexCreditsStatus(report);
2277
2446
  }
@@ -2352,6 +2521,23 @@ function formatWindowLabel(minutes, fallback, compact) {
2352
2521
  if (minutes % 60 === 0) return `${minutes / 60}h`;
2353
2522
  return `${minutes}m`;
2354
2523
  }
2524
+ function formatResetCountdown(resetsAt, now) {
2525
+ if (resetsAt === void 0 || !Number.isFinite(resetsAt) || !Number.isFinite(now))
2526
+ return void 0;
2527
+ const totalMinutes = Math.max(0, Math.ceil((resetsAt * 1e3 - now) / 6e4));
2528
+ const days = Math.floor(totalMinutes / 1440);
2529
+ const hours = Math.floor(totalMinutes % 1440 / 60);
2530
+ const minutes = totalMinutes % 60;
2531
+ if (days > 0) {
2532
+ return [
2533
+ `${String(days)}d`,
2534
+ hours > 0 ? `${String(hours)}h` : minutes > 0 ? `${String(minutes)}m` : ""
2535
+ ].filter(Boolean).join("");
2536
+ }
2537
+ if (hours > 0)
2538
+ return [`${String(hours)}h`, minutes > 0 ? `${String(minutes)}m` : ""].filter(Boolean).join("");
2539
+ return `${String(minutes)}m`;
2540
+ }
2355
2541
  function formatMetricValue(value, unit) {
2356
2542
  if (unit === "usd" && typeof value === "number") return formatUsd(value);
2357
2543
  return String(value);
@@ -2384,7 +2570,7 @@ var USAGE_SETTINGS_FILE = "pi-usage.json";
2384
2570
  var MAX_USAGE_SETTINGS_BYTES = 64 * 1024;
2385
2571
  var DEFAULT_USAGE_SETTINGS = Object.freeze({
2386
2572
  codexFastMode: false,
2387
- xaiUsage: true
2573
+ codexStatusResetCountdown: true
2388
2574
  });
2389
2575
  function usageSettingsPath() {
2390
2576
  return join(getAgentDir(), USAGE_SETTINGS_FILE);
@@ -2394,12 +2580,12 @@ function normalizeUsageSettings(value) {
2394
2580
  if (Object.hasOwn(value, "codexFastMode") && typeof value.codexFastMode !== "boolean") {
2395
2581
  return void 0;
2396
2582
  }
2397
- if (Object.hasOwn(value, "xaiUsage") && typeof value.xaiUsage !== "boolean") {
2583
+ if (Object.hasOwn(value, "codexStatusResetCountdown") && typeof value.codexStatusResetCountdown !== "boolean") {
2398
2584
  return void 0;
2399
2585
  }
2400
2586
  return {
2401
2587
  codexFastMode: typeof value.codexFastMode === "boolean" ? value.codexFastMode : DEFAULT_USAGE_SETTINGS.codexFastMode,
2402
- xaiUsage: typeof value.xaiUsage === "boolean" ? value.xaiUsage : DEFAULT_USAGE_SETTINGS.xaiUsage
2588
+ codexStatusResetCountdown: typeof value.codexStatusResetCountdown === "boolean" ? value.codexStatusResetCountdown : DEFAULT_USAGE_SETTINGS.codexStatusResetCountdown
2403
2589
  };
2404
2590
  }
2405
2591
  async function loadUsageSettings(path = usageSettingsPath(), signal) {
@@ -2684,8 +2870,8 @@ function isAbortError2(error) {
2684
2870
  }
2685
2871
 
2686
2872
  // src/usage-helpers.ts
2687
- function configuredAdapters(ctx, xaiUsage = true) {
2688
- return usageAdapters(xaiUsage).filter(
2873
+ function configuredAdapters(ctx) {
2874
+ return usageAdapters().filter(
2689
2875
  (adapter) => adapter.id === ctx.model?.provider || providerIsConfigured(ctx, adapter.id)
2690
2876
  );
2691
2877
  }
@@ -2750,10 +2936,10 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
2750
2936
  values: [OFF, ON]
2751
2937
  },
2752
2938
  {
2753
- id: "xaiUsage",
2754
- label: "xAI usage",
2755
- description: "Report OAuth subscription allowance and credits.",
2756
- currentValue: state.kind !== "invalid" && state.settings.xaiUsage ? ON : OFF,
2939
+ id: "codexStatusResetCountdown",
2940
+ label: "Codex reset countdown",
2941
+ description: "Show time remaining until each Codex usage limit resets.",
2942
+ currentValue: state.settings.codexStatusResetCountdown ? ON : OFF,
2757
2943
  values: [OFF, ON]
2758
2944
  }
2759
2945
  ];
@@ -2777,8 +2963,7 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
2777
2963
  saveQueue = saveQueue.then(async () => {
2778
2964
  const previous = settingsRuntime.get().settings[settingId];
2779
2965
  if (settingsRuntime.get().kind === "invalid") {
2780
- const effectivePrevious = settingId === "xaiUsage" ? false : previous;
2781
- settingsList.updateValue(id, displayValue(settingId, effectivePrevious));
2966
+ settingsList.updateValue(id, displayValue(previous));
2782
2967
  if (!signal.aborted && isCurrent()) {
2783
2968
  ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
2784
2969
  tui.requestRender();
@@ -2789,17 +2974,17 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
2789
2974
  await settingsRuntime.update({ [settingId]: requested }, signal);
2790
2975
  } catch (error) {
2791
2976
  if (signal.aborted || !isCurrent()) return;
2792
- settingsList.updateValue(id, displayValue(settingId, previous));
2977
+ settingsList.updateValue(id, displayValue(previous));
2793
2978
  ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
2794
2979
  tui.requestRender();
2795
2980
  return;
2796
2981
  }
2797
2982
  if (previous !== requested) {
2798
2983
  changed = true;
2799
- onApplied(settingId, previous, requested);
2984
+ onApplied(settingId);
2800
2985
  }
2801
2986
  if (signal.aborted || !isCurrent()) return;
2802
- settingsList.updateValue(id, displayValue(settingId, requested));
2987
+ settingsList.updateValue(id, displayValue(requested));
2803
2988
  tui.requestRender();
2804
2989
  });
2805
2990
  },
@@ -2823,12 +3008,13 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
2823
3008
  };
2824
3009
  });
2825
3010
  }
2826
- function displayValue(_id, enabled) {
3011
+ function displayValue(enabled) {
2827
3012
  return enabled ? ON : OFF;
2828
3013
  }
2829
3014
 
2830
3015
  // src/usage.ts
2831
3016
  var CACHE_TTL_MS = 5 * 60 * 1e3;
3017
+ var STATUS_COUNTDOWN_REFRESH_MS = 60 * 1e3;
2832
3018
  var DEFAULT_TIMEOUT_MS = 15e3;
2833
3019
  var ALL_PROVIDER_CONCURRENCY = 2;
2834
3020
  var FAILURE_BACKOFF_MS = 3e4;
@@ -2854,19 +3040,22 @@ function usageExtension(pi, dependencies = {}) {
2854
3040
  let sessionActive = false;
2855
3041
  let statusGeneration = 0;
2856
3042
  let sessionGeneration = 0;
2857
- let xaiSettingsGeneration = 0;
2858
3043
  let statusRefreshTimer;
3044
+ let statusCountdownTimer;
2859
3045
  let statusController;
2860
3046
  let fastRuntime;
2861
- const xaiUsageEnabled = () => {
2862
- const state = settingsRuntime.get();
2863
- return state.kind !== "invalid" && state.settings.xaiUsage;
2864
- };
2865
- const activeAdapterForProvider = (providerId) => adapterForProvider(providerId, xaiUsageEnabled());
2866
- const clearStatusTimer = () => {
3047
+ const clearStatusRefreshTimer = () => {
2867
3048
  if (statusRefreshTimer) clearTimeout(statusRefreshTimer);
2868
3049
  statusRefreshTimer = void 0;
2869
3050
  };
3051
+ const clearStatusCountdownTimer = () => {
3052
+ if (statusCountdownTimer) clearTimeout(statusCountdownTimer);
3053
+ statusCountdownTimer = void 0;
3054
+ };
3055
+ const clearStatusTimers = () => {
3056
+ clearStatusRefreshTimer();
3057
+ clearStatusCountdownTimer();
3058
+ };
2870
3059
  const safeSetStatus = (ctx, value) => {
2871
3060
  try {
2872
3061
  ctx.ui.setStatus(STATUS_KEY, value);
@@ -2880,11 +3069,11 @@ function usageExtension(pi, dependencies = {}) {
2880
3069
  statusGeneration += 1;
2881
3070
  statusController?.abort();
2882
3071
  statusController = void 0;
2883
- clearStatusTimer();
3072
+ clearStatusTimers();
2884
3073
  safeSetStatus(ctx, void 0);
2885
3074
  };
2886
3075
  const scheduleStatusRefresh = (ctx, model) => {
2887
- clearStatusTimer();
3076
+ clearStatusRefreshTimer();
2888
3077
  const generation = statusGeneration;
2889
3078
  statusRefreshTimer = setTimeout(() => {
2890
3079
  statusRefreshTimer = void 0;
@@ -2894,13 +3083,14 @@ function usageExtension(pi, dependencies = {}) {
2894
3083
  statusRefreshTimer.unref?.();
2895
3084
  };
2896
3085
  const publishStatus = (ctx, outcome, model, shouldSchedule) => {
2897
- if (activeAdapterForProvider(model.provider)?.publishesStatusline === false) {
2898
- clearStatusTimer();
3086
+ clearStatusCountdownTimer();
3087
+ if (adapterForProvider(model.provider)?.publishesStatusline === false) {
3088
+ clearStatusRefreshTimer();
2899
3089
  safeSetStatus(ctx, void 0);
2900
3090
  return;
2901
3091
  }
2902
3092
  if (outcome.state.status === "unsupported") {
2903
- clearStatusTimer();
3093
+ clearStatusRefreshTimer();
2904
3094
  safeSetStatus(ctx, void 0);
2905
3095
  return;
2906
3096
  }
@@ -2913,10 +3103,30 @@ function usageExtension(pi, dependencies = {}) {
2913
3103
  }
2914
3104
  return;
2915
3105
  }
2916
- const rawValue = formatUsageStatusline(outcome.state.report, model);
3106
+ const showCodexResetCountdown = outcome.state.report.providerId === "openai-codex" && settingsRuntime.get().settings.codexStatusResetCountdown;
3107
+ const now = Date.now();
3108
+ const rawValue = formatUsageStatusline(
3109
+ outcome.state.report,
3110
+ model,
3111
+ now,
3112
+ showCodexResetCountdown
3113
+ );
2917
3114
  const value = rawValue ? fastRuntime.decorateStatus(model, rawValue) : void 0;
2918
3115
  if (!safeSetStatus(ctx, value)) return;
2919
3116
  if (shouldSchedule && sessionActive) scheduleStatusRefresh(ctx, model);
3117
+ if (sessionActive && showCodexResetCountdown && outcome.state.report.buckets.some(
3118
+ (bucket) => bucket.resetsAt !== void 0 && Number.isFinite(bucket.resetsAt) && bucket.resetsAt * 1e3 > now
3119
+ )) {
3120
+ const generation = statusGeneration;
3121
+ statusCountdownTimer = setTimeout(() => {
3122
+ statusCountdownTimer = void 0;
3123
+ if (!sessionActive || generation !== statusGeneration || modelIdentity(ctx.model) !== modelIdentity(model)) {
3124
+ return;
3125
+ }
3126
+ publishStatus(ctx, outcome, model, false);
3127
+ }, STATUS_COUNTDOWN_REFRESH_MS);
3128
+ statusCountdownTimer.unref?.();
3129
+ }
2920
3130
  };
2921
3131
  const invalidateProviderState = (providerId) => {
2922
3132
  cache.clearProvider(providerId);
@@ -2938,10 +3148,8 @@ function usageExtension(pi, dependencies = {}) {
2938
3148
  }
2939
3149
  activeCurrentIdentity = nextIdentity;
2940
3150
  };
2941
- const queryAdapterState = async (ctx, adapter, displayState, force, signal) => {
2942
- const startedAt = Date.now();
3151
+ const queryAdapterState = async (ctx, adapter, displayState, force, signal, authRetry = 0, deadlineAt = Date.now() + DEFAULT_TIMEOUT_MS) => {
2943
3152
  const expectedSessionGeneration = sessionGeneration;
2944
- const expectedXaiSettingsGeneration = xaiSettingsGeneration;
2945
3153
  const expectedSessionId = ctx.sessionManager.getSessionId();
2946
3154
  const expectedModelIdentity = modelIdentity(ctx.model);
2947
3155
  let auth;
@@ -2949,7 +3157,7 @@ function usageExtension(pi, dependencies = {}) {
2949
3157
  auth = await awaitWithDeadline(
2950
3158
  resolveUsageAuth(ctx, adapter, void 0, credentialReader, credentialCandidates),
2951
3159
  signal,
2952
- DEFAULT_TIMEOUT_MS,
3160
+ Math.max(1, deadlineAt - Date.now()),
2953
3161
  `resolving ${adapter.displayName} runtime auth`
2954
3162
  );
2955
3163
  } catch (error) {
@@ -2967,9 +3175,9 @@ function usageExtension(pi, dependencies = {}) {
2967
3175
  }
2968
3176
  };
2969
3177
  }
2970
- if (adapter.id === "xai" && (expectedSessionGeneration !== sessionGeneration || expectedXaiSettingsGeneration !== xaiSettingsGeneration || !xaiUsageEnabled() || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity)) {
2971
- throw abortError();
2972
- }
3178
+ const requiresRequestBoundaryGuard = adapter.id === "deepseek" || adapter.id === "xai";
3179
+ const requestContextChanged = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity;
3180
+ if (requiresRequestBoundaryGuard && requestContextChanged()) throw abortError();
2973
3181
  if (!auth) {
2974
3182
  if (displayState === "current") {
2975
3183
  transitionCurrentIdentity(`${adapter.id}:unavailable`, adapter.id);
@@ -3019,19 +3227,23 @@ function usageExtension(pi, dependencies = {}) {
3019
3227
  querySequence += 1;
3020
3228
  const queryId = querySequence;
3021
3229
  setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
3230
+ let deepSeekAuthChanged = false;
3022
3231
  try {
3023
- const remainingMs = Math.max(1, DEFAULT_TIMEOUT_MS - (Date.now() - startedAt));
3024
- const guard = adapter.id === "xai" ? async () => {
3025
- if (signal.aborted || expectedSessionGeneration !== sessionGeneration || expectedXaiSettingsGeneration !== xaiSettingsGeneration || !xaiUsageEnabled() || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity) {
3026
- throw abortError();
3027
- }
3232
+ const remainingMs = Math.max(1, deadlineAt - Date.now());
3233
+ const guard = requiresRequestBoundaryGuard ? async () => {
3234
+ if (signal.aborted || requestContextChanged()) throw abortError();
3028
3235
  const revalidated = await awaitWithDeadline(
3029
3236
  resolveUsageAuth(ctx, adapter, void 0, credentialReader, credentialCandidates),
3030
3237
  signal,
3031
- Math.max(1, DEFAULT_TIMEOUT_MS - (Date.now() - startedAt)),
3032
- "revalidating xAI runtime auth"
3238
+ Math.max(1, deadlineAt - Date.now()),
3239
+ `revalidating ${adapter.displayName} runtime auth`
3033
3240
  );
3034
- if (signal.aborted || expectedSessionGeneration !== sessionGeneration || expectedXaiSettingsGeneration !== xaiSettingsGeneration || !xaiUsageEnabled() || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity || revalidated?.fingerprint !== auth.fingerprint) {
3241
+ if (signal.aborted || requestContextChanged()) throw abortError();
3242
+ if (revalidated?.fingerprint !== auth.fingerprint) {
3243
+ if (adapter.id === "deepseek") {
3244
+ deepSeekAuthChanged = true;
3245
+ throw new Error("DeepSeek runtime credential changed during the balance query.");
3246
+ }
3035
3247
  throw abortError();
3036
3248
  }
3037
3249
  } : void 0;
@@ -3053,6 +3265,18 @@ function usageExtension(pi, dependencies = {}) {
3053
3265
  };
3054
3266
  } catch (error) {
3055
3267
  if (isStaleExtensionContextError(error) || isAbortError3(error)) throw error;
3268
+ if (deepSeekAuthChanged && authRetry === 0 && !signal.aborted && !requestContextChanged() && Date.now() < deadlineAt) {
3269
+ if (latestQueries.get(failureKey) === queryId) latestQueries.delete(failureKey);
3270
+ return queryAdapterState(
3271
+ ctx,
3272
+ adapter,
3273
+ displayState,
3274
+ true,
3275
+ signal,
3276
+ authRetry + 1,
3277
+ deadlineAt
3278
+ );
3279
+ }
3056
3280
  const message = errorMessage(error);
3057
3281
  const now = Date.now();
3058
3282
  for (const [key, failure] of failureBackoff) {
@@ -3079,7 +3303,7 @@ function usageExtension(pi, dependencies = {}) {
3079
3303
  }
3080
3304
  };
3081
3305
  const queryCurrentState = async (ctx, model, force, signal) => {
3082
- const adapter = activeAdapterForProvider(model?.provider);
3306
+ const adapter = adapterForProvider(model?.provider);
3083
3307
  if (!adapter) {
3084
3308
  const providerId = model?.provider ?? "none";
3085
3309
  transitionCurrentIdentity(`unsupported:${providerId}`, providerId);
@@ -3089,14 +3313,14 @@ function usageExtension(pi, dependencies = {}) {
3089
3313
  providerName: providerDisplayName(ctx, providerId),
3090
3314
  displayState: "current",
3091
3315
  status: "unsupported",
3092
- message: providerId === "xai" && !xaiUsageEnabled() ? "xAI usage is disabled. Open Settings to enable it." : model ? `Usage reporting is not supported for ${providerDisplayName(ctx, providerId)}.` : "No model is selected."
3316
+ message: model ? `Usage reporting is not supported for ${providerDisplayName(ctx, providerId)}.` : "No model is selected."
3093
3317
  }
3094
3318
  };
3095
3319
  }
3096
3320
  return queryAdapterState(ctx, adapter, "current", force, signal);
3097
3321
  };
3098
3322
  const refreshCurrentStatus = async (ctx, model, force) => {
3099
- const adapter = activeAdapterForProvider(model?.provider);
3323
+ const adapter = adapterForProvider(model?.provider);
3100
3324
  if (!adapter || !model) {
3101
3325
  const providerId = model?.provider ?? "none";
3102
3326
  transitionCurrentIdentity(`unsupported:${providerId}`, providerId);
@@ -3109,6 +3333,7 @@ function usageExtension(pi, dependencies = {}) {
3109
3333
  }
3110
3334
  statusGeneration += 1;
3111
3335
  const generation = statusGeneration;
3336
+ clearStatusCountdownTimer();
3112
3337
  statusController?.abort();
3113
3338
  const controller = new AbortController();
3114
3339
  statusController = controller;
@@ -3159,7 +3384,7 @@ function usageExtension(pi, dependencies = {}) {
3159
3384
  if (generation !== statusGeneration || modelIdentity(ctx.model) !== modelIdentity(model)) {
3160
3385
  return false;
3161
3386
  }
3162
- const adapter = activeAdapterForProvider(model?.provider);
3387
+ const adapter = adapterForProvider(model?.provider);
3163
3388
  if (outcome.authState === "unavailable") {
3164
3389
  if (!adapter) return false;
3165
3390
  try {
@@ -3218,7 +3443,7 @@ function usageExtension(pi, dependencies = {}) {
3218
3443
  const menuGeneration = statusGeneration;
3219
3444
  statusController?.abort();
3220
3445
  statusController = void 0;
3221
- clearStatusTimer();
3446
+ clearStatusTimers();
3222
3447
  const controller = new AbortController();
3223
3448
  activeControllers.add(controller);
3224
3449
  try {
@@ -3283,7 +3508,7 @@ function usageExtension(pi, dependencies = {}) {
3283
3508
  providers: () => ({
3284
3509
  kind: "actions",
3285
3510
  title: "Select a configured provider",
3286
- items: configuredAdapters(ctx, xaiUsageEnabled()).filter((adapter) => adapter.id !== ctx.model?.provider).map((adapter) => ({
3511
+ items: configuredAdapters(ctx).filter((adapter) => adapter.id !== ctx.model?.provider).map((adapter) => ({
3287
3512
  id: adapter.id,
3288
3513
  label: adapter.displayName,
3289
3514
  action: "provider"
@@ -3353,14 +3578,9 @@ function usageExtension(pi, dependencies = {}) {
3353
3578
  settingsRuntime,
3354
3579
  controller.signal,
3355
3580
  () => statusGeneration === menuGeneration && !controller.signal.aborted,
3356
- (id, _previous, next) => {
3357
- if (id !== "xaiUsage") return;
3358
- xaiSettingsGeneration += 1;
3359
- invalidateProviderState("xai");
3360
- if (!next) {
3361
- for (const active of activeControllers) {
3362
- if (active !== controller) active.abort();
3363
- }
3581
+ (id) => {
3582
+ if (id === "codexStatusResetCountdown" && stableCurrent && statusGeneration === menuGeneration && !controller.signal.aborted) {
3583
+ publishStableCurrent(ctx, stableCurrent);
3364
3584
  }
3365
3585
  }
3366
3586
  );
@@ -3546,7 +3766,7 @@ function usageExtension(pi, dependencies = {}) {
3546
3766
  return { kind: "stay" };
3547
3767
  },
3548
3768
  another: async () => {
3549
- const others = configuredAdapters(ctx, xaiUsageEnabled()).filter(
3769
+ const others = configuredAdapters(ctx).filter(
3550
3770
  (adapter) => adapter.id !== ctx.model?.provider
3551
3771
  );
3552
3772
  if (others.length === 0) {
@@ -3556,7 +3776,7 @@ function usageExtension(pi, dependencies = {}) {
3556
3776
  return { kind: "to", screen: "providers" };
3557
3777
  },
3558
3778
  provider: async ({ itemId }) => {
3559
- const adapter = configuredAdapters(ctx, xaiUsageEnabled()).find(
3779
+ const adapter = configuredAdapters(ctx).find(
3560
3780
  (candidate) => candidate.id === itemId && candidate.id !== ctx.model?.provider
3561
3781
  );
3562
3782
  if (!adapter) return { kind: "back" };
@@ -3582,7 +3802,7 @@ function usageExtension(pi, dependencies = {}) {
3582
3802
  return { kind: "back" };
3583
3803
  },
3584
3804
  all: async () => {
3585
- const adapters = configuredAdapters(ctx, xaiUsageEnabled());
3805
+ const adapters = configuredAdapters(ctx);
3586
3806
  const currentProviderId = ctx.model?.provider;
3587
3807
  const settled = await runMenuOperation(
3588
3808
  ctx,
@@ -3662,9 +3882,8 @@ function usageExtension(pi, dependencies = {}) {
3662
3882
  });
3663
3883
  pi.on("session_start", (_event, ctx) => {
3664
3884
  sessionGeneration += 1;
3665
- xaiSettingsGeneration += 1;
3666
3885
  statusGeneration += 1;
3667
- clearStatusTimer();
3886
+ clearStatusTimers();
3668
3887
  for (const controller of activeControllers) controller.abort();
3669
3888
  activeControllers.clear();
3670
3889
  statusController = void 0;
@@ -3683,9 +3902,8 @@ function usageExtension(pi, dependencies = {}) {
3683
3902
  pi.on("session_shutdown", (_event, ctx) => {
3684
3903
  sessionActive = false;
3685
3904
  sessionGeneration += 1;
3686
- xaiSettingsGeneration += 1;
3687
3905
  statusGeneration += 1;
3688
- clearStatusTimer();
3906
+ clearStatusTimers();
3689
3907
  for (const controller of activeControllers) controller.abort();
3690
3908
  activeControllers.clear();
3691
3909
  statusController = void 0;
@@ -3730,6 +3948,7 @@ export {
3730
3948
  loadUsageSettings,
3731
3949
  normalizeCodexBackendPayload,
3732
3950
  normalizeCodexResetCreditsPayload,
3951
+ normalizeDeepSeekBalancePayload,
3733
3952
  normalizeGitHubCopilotUsagePayload,
3734
3953
  normalizeKimiCodingUsagePayload,
3735
3954
  normalizeOpenCodeZenPayload,