@narumitw/pi-usage 0.54.0 → 0.58.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,234 @@ 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
+
523
+ // src/providers/fireworks.ts
524
+ var NANOS_PER_UNIT = 1000000000n;
525
+ var CURRENCY_PATTERN = /^[A-Z]{3}$/u;
526
+ var ACCOUNT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$/u;
527
+ var INTEGER_PATTERN = /^-?\d+$/u;
528
+ var INT64_MIN = -(2n ** 63n);
529
+ var INT64_MAX = 2n ** 63n - 1n;
530
+ var MAX_UNITS_CHARS = 20;
531
+ var MAX_NANOS_CHARS = 11;
532
+ var SERIES_KEYS = ["serverless", "dedicated", "training", "other"];
533
+ var SERIES_LABELS = {
534
+ serverless: "Serverless",
535
+ dedicated: "Dedicated deployments",
536
+ training: "Training",
537
+ other: "Other"
538
+ };
539
+ function isFireworksAccountId(value) {
540
+ return typeof value === "string" && ACCOUNT_ID_PATTERN.test(value);
541
+ }
542
+ function normalizeFireworksAccountsPayload(payload) {
543
+ if (!Array.isArray(payload.accounts)) {
544
+ throw new Error("Fireworks accounts response did not contain an accounts array.");
545
+ }
546
+ const accounts = [];
547
+ for (const raw of payload.accounts) {
548
+ const account = asObject3(raw);
549
+ if (!account) throw new Error("Fireworks accounts response row was not an object.");
550
+ if (typeof account.name !== "string") {
551
+ throw new Error("Fireworks accounts response omitted the account resource name.");
552
+ }
553
+ const match = /^accounts\/([^/]+)$/u.exec(account.name);
554
+ if (!match || !isFireworksAccountId(match[1])) {
555
+ throw new Error("Fireworks accounts response returned an unsafe account resource name.");
556
+ }
557
+ const accountId = match[1];
558
+ if (accounts.includes(accountId)) {
559
+ throw new Error(`Fireworks accounts response repeated ${accountId}.`);
560
+ }
561
+ accounts.push(accountId);
562
+ }
563
+ return accounts;
564
+ }
565
+ function normalizeFireworksBillingSummaryPayload(payload, accountId, capturedAt) {
566
+ if (!isFireworksAccountId(accountId)) {
567
+ throw new Error("Fireworks billing summary received an unsafe account identifier.");
568
+ }
569
+ if (payload.lineItems !== void 0 && !Array.isArray(payload.lineItems)) {
570
+ throw new Error("Fireworks billing summary lineItems was not an array.");
571
+ }
572
+ const totals = /* @__PURE__ */ new Map();
573
+ for (const raw of payload.lineItems ?? []) {
574
+ const lineItem = asObject3(raw);
575
+ if (!lineItem) throw new Error("Fireworks billing line item was not an object.");
576
+ const cost = moneyAmount(lineItem.totalCost, "line item total cost");
577
+ const series = seriesKey(lineItem.series);
578
+ let amounts = totals.get(cost.currency);
579
+ if (!amounts) {
580
+ amounts = /* @__PURE__ */ new Map();
581
+ totals.set(cost.currency, amounts);
582
+ }
583
+ amounts.set(series, (amounts.get(series) ?? 0n) + cost.amount);
584
+ }
585
+ const metrics = [];
586
+ for (const [currency, amounts] of totals) {
587
+ metrics.push({
588
+ id: `${currency.toLowerCase()}-total`,
589
+ label: "Total spend",
590
+ value: formatMoneyAmount(sumSeries(amounts)),
591
+ unit: "currency",
592
+ currency
593
+ });
594
+ for (const series of SERIES_KEYS) {
595
+ const amount = amounts.get(series);
596
+ if (amount === void 0) continue;
597
+ metrics.push({
598
+ id: `${currency.toLowerCase()}-${series}`,
599
+ label: SERIES_LABELS[series],
600
+ value: formatMoneyAmount(amount),
601
+ unit: "currency",
602
+ currency
603
+ });
604
+ }
605
+ }
606
+ const notes = [
607
+ "Rated line items may differ from the final invoice once credits or adjustments are applied."
608
+ ];
609
+ if (metrics.length === 0) {
610
+ notes.push("Fireworks returned no rated line items for the last 30 days.");
611
+ }
612
+ return {
613
+ providerId: "fireworks",
614
+ providerName: "Fireworks",
615
+ capturedAt,
616
+ source: "fireworks-billing-summary",
617
+ semantics: { kind: "api-key", label: "Fireworks API spend" },
618
+ accountLabel: sanitizeDisplayText(accountId, 80),
619
+ buckets: [],
620
+ metrics,
621
+ notes
622
+ };
623
+ }
624
+ function moneyAmount(value, description) {
625
+ const money = asObject3(value);
626
+ if (!money) throw new Error(`Fireworks billing ${description} was not a money object.`);
627
+ const currency = typeof money.currencyCode === "string" ? money.currencyCode : void 0;
628
+ if (!currency || !CURRENCY_PATTERN.test(currency)) {
629
+ throw new Error(`Fireworks billing ${description} currency was not an ISO 4217 code.`);
630
+ }
631
+ const units = money.units === void 0 ? 0n : integerComponent(money.units, description, "whole units", MAX_UNITS_CHARS);
632
+ if (units < INT64_MIN || units > INT64_MAX) {
633
+ throw new Error(`Fireworks billing ${description} whole units exceeded the int64 range.`);
634
+ }
635
+ const nanos = money.nanos === void 0 ? 0n : integerComponent(money.nanos, description, "nano units", MAX_NANOS_CHARS);
636
+ if (nanos <= -NANOS_PER_UNIT || nanos >= NANOS_PER_UNIT) {
637
+ throw new Error(`Fireworks billing ${description} nano units exceeded the Money range.`);
638
+ }
639
+ if (units > 0n && nanos < 0n || units < 0n && nanos > 0n) {
640
+ throw new Error(`Fireworks billing ${description} mixed unit and nano signs.`);
641
+ }
642
+ return { currency, amount: units * NANOS_PER_UNIT + nanos };
643
+ }
644
+ function integerComponent(value, description, component, maxChars) {
645
+ const text = typeof value === "number" && Number.isSafeInteger(value) ? String(value) : typeof value === "string" && INTEGER_PATTERN.test(value) ? value : void 0;
646
+ if (text === void 0 || text.length > maxChars) {
647
+ throw new Error(`Fireworks billing ${description} ${component} was not a bounded integer.`);
648
+ }
649
+ return BigInt(text);
650
+ }
651
+ function seriesKey(value) {
652
+ if (value === void 0 || value === null) return "other";
653
+ if (typeof value !== "string") throw new Error("Fireworks billing line item series was invalid.");
654
+ if (value === "SERVERLESS") return "serverless";
655
+ if (value === "DEDICATED_DEPLOYMENT") return "dedicated";
656
+ if (value === "TRAINING") return "training";
657
+ return "other";
658
+ }
659
+ function sumSeries(amounts) {
660
+ let total = 0n;
661
+ for (const amount of amounts.values()) total += amount;
662
+ return total;
663
+ }
664
+ function formatMoneyAmount(amount) {
665
+ const negative = amount < 0n;
666
+ const magnitude = negative ? -amount : amount;
667
+ const units = magnitude / NANOS_PER_UNIT;
668
+ const nanos = (magnitude % NANOS_PER_UNIT).toString().padStart(9, "0").replace(/0+$/u, "");
669
+ return `${negative ? "-" : ""}${units.toString()}${nanos ? `.${nanos}` : ""}`;
670
+ }
671
+ function asObject3(value) {
672
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
673
+ return value;
674
+ }
675
+
452
676
  // src/providers/github-copilot.ts
453
677
  function normalizeGitHubCopilotUsagePayload(payload, capturedAt) {
454
- const snapshots = asObject2(payload.quota_snapshots);
455
- const premium = asObject2(snapshots?.premium_interactions);
678
+ const snapshots = asObject4(payload.quota_snapshots);
679
+ const premium = asObject4(snapshots?.premium_interactions);
456
680
  const metrics = [];
457
681
  let semanticsLabel;
458
682
  let bucket;
@@ -493,8 +717,8 @@ function normalizeGitHubCopilotUsagePayload(payload, capturedAt) {
493
717
  };
494
718
  }
495
719
  } else {
496
- const limited = asObject2(payload.limited_user_quotas);
497
- const monthly = asObject2(payload.monthly_quotas);
720
+ const limited = asObject4(payload.limited_user_quotas);
721
+ const monthly = asObject4(payload.monthly_quotas);
498
722
  const remaining = asNonnegativeNumber(limited?.chat);
499
723
  const entitlement = asNonnegativeNumber(monthly?.chat);
500
724
  if (remaining === void 0 || entitlement === void 0) {
@@ -533,7 +757,7 @@ function resetTimestamp(payload) {
533
757
  const milliseconds = Date.parse(raw);
534
758
  return Number.isNaN(milliseconds) ? {} : { resetsAt: Math.floor(milliseconds / 1e3) };
535
759
  }
536
- function asObject2(value) {
760
+ function asObject4(value) {
537
761
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
538
762
  return value;
539
763
  }
@@ -556,7 +780,7 @@ var DAILY_WINDOW_MINUTES = 1440;
556
780
  var WEEKLY_WINDOW_MINUTES = 10080;
557
781
  var FIXED_POINT_UNITS_PER_CENT = 1e6;
558
782
  function normalizeKimiCodingUsagePayload(payload, capturedAt) {
559
- const root = asObject3(payload);
783
+ const root = asObject5(payload);
560
784
  if (!root) throw new Error("Kimi Coding usage response was not an object.");
561
785
  const candidates = [];
562
786
  let omittedWindow = false;
@@ -565,7 +789,7 @@ function normalizeKimiCodingUsagePayload(payload, capturedAt) {
565
789
  else if (root.usage !== void 0) omittedWindow = true;
566
790
  if (Array.isArray(root.limits)) {
567
791
  for (const raw of root.limits) {
568
- const item = asObject3(raw);
792
+ const item = asObject5(raw);
569
793
  const windowMinutes = parseWindowMinutes(item?.window);
570
794
  const label = sanitizedLabel(item?.name);
571
795
  const bucket = windowMinutes === void 0 ? void 0 : parseUsageRow(item?.detail, windowMinutes, label ?? defaultWindowLabel(windowMinutes));
@@ -602,7 +826,7 @@ function normalizeKimiCodingUsagePayload(payload, capturedAt) {
602
826
  };
603
827
  }
604
828
  function parseUsageRow(value, windowMinutes, label) {
605
- const row = asObject3(value);
829
+ const row = asObject5(value);
606
830
  if (!row) return void 0;
607
831
  const used = asNonnegativeInteger2(row.used);
608
832
  const limit = asNonnegativeInteger2(row.limit);
@@ -620,7 +844,7 @@ function parseUsageRow(value, windowMinutes, label) {
620
844
  };
621
845
  }
622
846
  function parseWindowMinutes(value) {
623
- const window = asObject3(value);
847
+ const window = asObject5(value);
624
848
  if (!window) return void 0;
625
849
  const duration = asPositiveInteger(window.duration);
626
850
  if (duration === void 0) return void 0;
@@ -630,8 +854,8 @@ function parseWindowMinutes(value) {
630
854
  return Number.isSafeInteger(minutes) ? minutes : void 0;
631
855
  }
632
856
  function parseBoosterWallet(value) {
633
- const wallet = asObject3(value);
634
- const balance = asObject3(wallet?.balance);
857
+ const wallet = asObject5(value);
858
+ const balance = asObject5(wallet?.balance);
635
859
  if (!wallet || !balance || balance.type !== "BOOSTER") return [];
636
860
  const totalRaw = asPositiveInteger(balance.amount);
637
861
  if (totalRaw === void 0) return [];
@@ -682,7 +906,7 @@ function parseBoosterWallet(value) {
682
906
  return metrics;
683
907
  }
684
908
  function parseMoney(value) {
685
- const money = asObject3(value);
909
+ const money = asObject5(value);
686
910
  if (!money) return void 0;
687
911
  const cents = asNonnegativeInteger2(money.priceInCents);
688
912
  if (cents === void 0) return void 0;
@@ -696,7 +920,7 @@ function fixedPointToMajor(value) {
696
920
  const major = roundedCents / 100;
697
921
  return Number.isSafeInteger(roundedCents) && Number.isFinite(major) ? major : void 0;
698
922
  }
699
- function asObject3(value) {
923
+ function asObject5(value) {
700
924
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
701
925
  return value;
702
926
  }
@@ -770,12 +994,12 @@ var ZEN_WINDOWS = [
770
994
  { key: "monthly", label: "Monthly" }
771
995
  ];
772
996
  function normalizeOpenCodeZenPayload(payload, capturedAt) {
773
- const usage = asObject4(payload.usage);
997
+ const usage = asObject6(payload.usage);
774
998
  if (!usage) throw new Error("OpenCode Zen usage response was not an object.");
775
999
  const buckets = [];
776
1000
  const notes = [];
777
1001
  for (const window of ZEN_WINDOWS) {
778
- const raw = asObject4(usage[window.key]);
1002
+ const raw = asObject6(usage[window.key]);
779
1003
  if (!raw) continue;
780
1004
  const status = asString3(raw.status);
781
1005
  if (status !== "ok" && status !== "rate-limited") {
@@ -812,7 +1036,7 @@ function normalizeOpenCodeZenPayload(payload, capturedAt) {
812
1036
  ...notes.length > 0 ? { notes } : {}
813
1037
  };
814
1038
  }
815
- function asObject4(value) {
1039
+ function asObject6(value) {
816
1040
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
817
1041
  return value;
818
1042
  }
@@ -836,7 +1060,7 @@ function clampPercent2(value) {
836
1060
 
837
1061
  // src/providers/openrouter.ts
838
1062
  function normalizeOpenRouterKeyPayload(payload, capturedAt) {
839
- const data = asObject5(payload.data);
1063
+ const data = asObject7(payload.data);
840
1064
  if (!data) throw new Error("OpenRouter key response data was not an object.");
841
1065
  const limit = asNonnegativeNumber3(data.limit);
842
1066
  const remaining = asNonnegativeNumber3(data.limit_remaining);
@@ -882,7 +1106,7 @@ function addUsageMetric(metrics, id, label, value) {
882
1106
  if (amount === void 0) return;
883
1107
  metrics.push({ id, label, value: amount, unit: "usd" });
884
1108
  }
885
- function asObject5(value) {
1109
+ function asObject7(value) {
886
1110
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
887
1111
  return value;
888
1112
  }
@@ -1060,13 +1284,13 @@ function isRecord2(value) {
1060
1284
  var FIVE_HOUR_WINDOW_MINUTES2 = 300;
1061
1285
  var WEEKLY_WINDOW_MINUTES2 = 10080;
1062
1286
  function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt) {
1063
- const data = asObject6(payload.data);
1287
+ const data = asObject8(payload.data);
1064
1288
  if (!data) throw new Error("Z.AI quota response data was not an object.");
1065
1289
  const limits = Array.isArray(data.limits) ? data.limits : [];
1066
1290
  const buckets = [];
1067
1291
  const metrics = [];
1068
1292
  for (const raw of limits) {
1069
- const limit = asObject6(raw);
1293
+ const limit = asObject8(raw);
1070
1294
  if (!limit) continue;
1071
1295
  const type = asString5(limit.type);
1072
1296
  const unit = asNonnegativeNumber4(limit.unit);
@@ -1138,7 +1362,7 @@ function addCountBucket(buckets, limit, id, label, windowMinutes) {
1138
1362
  function addUsageDetailMetrics(metrics, value) {
1139
1363
  if (!Array.isArray(value)) return;
1140
1364
  for (const raw of value) {
1141
- const detail = asObject6(raw);
1365
+ const detail = asObject8(raw);
1142
1366
  if (!detail) continue;
1143
1367
  const label = asString5(detail.modelCode);
1144
1368
  const usage = asNonnegativeNumber4(detail.usage);
@@ -1146,7 +1370,7 @@ function addUsageDetailMetrics(metrics, value) {
1146
1370
  metrics.push({ id: `mcp-${kebabCase(label)}`, label, value: usage, unit: "count" });
1147
1371
  }
1148
1372
  }
1149
- function asObject6(value) {
1373
+ function asObject8(value) {
1150
1374
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
1151
1375
  return value;
1152
1376
  }
@@ -1172,6 +1396,10 @@ function clampPercent3(value) {
1172
1396
 
1173
1397
  // src/query.ts
1174
1398
  var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
1399
+ var DEEPSEEK_BALANCE_URL = "https://api.deepseek.com/user/balance";
1400
+ var FIREWORKS_BILLING_SUMMARY_ORIGIN = "https://api.fireworks.ai";
1401
+ var FIREWORKS_SPEND_WINDOW_DAYS = 30;
1402
+ var FIREWORKS_MAX_ACCOUNT_PAGES = 5;
1175
1403
  var GITHUB_COPILOT_USAGE_URL = "https://api.github.com/copilot_internal/user";
1176
1404
  var OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
1177
1405
  var OPENCODE_GO_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
@@ -1205,6 +1433,27 @@ var SUPPORTED_ADAPTERS = [
1205
1433
  return normalizeCodexBackendPayload(payload, Date.now());
1206
1434
  }
1207
1435
  },
1436
+ {
1437
+ id: "deepseek",
1438
+ displayName: "DeepSeek",
1439
+ semantics: { kind: "api-key", label: "DeepSeek API balance" },
1440
+ async query(auth, signal, timeoutMs, guard) {
1441
+ if (!guard) throw new Error("DeepSeek API balance requires request-boundary revalidation.");
1442
+ const startedAt = Date.now();
1443
+ await guard();
1444
+ const remainingMs = timeoutMs - (Date.now() - startedAt);
1445
+ if (remainingMs <= 0) throw new Error("Timed out while revalidating DeepSeek runtime auth.");
1446
+ const payload = await fetchProviderJson(
1447
+ DEEPSEEK_BALANCE_URL,
1448
+ auth,
1449
+ signal,
1450
+ remainingMs,
1451
+ "DeepSeek API balance endpoint",
1452
+ { redirect: "error" }
1453
+ );
1454
+ return normalizeDeepSeekBalancePayload(payload, Date.now());
1455
+ }
1456
+ },
1208
1457
  {
1209
1458
  id: "github-copilot",
1210
1459
  displayName: "GitHub Copilot",
@@ -1238,6 +1487,35 @@ var SUPPORTED_ADAPTERS = [
1238
1487
  return normalizeOpenRouterKeyPayload(payload, Date.now());
1239
1488
  }
1240
1489
  },
1490
+ {
1491
+ id: "fireworks",
1492
+ displayName: "Fireworks",
1493
+ semantics: { kind: "api-key", label: "Fireworks API spend" },
1494
+ async query(auth, signal, timeoutMs, guard, settings) {
1495
+ if (!guard) throw new Error("Fireworks API spend requires request-boundary revalidation.");
1496
+ const startedAt = Date.now();
1497
+ await guard();
1498
+ const accountId = await resolveFireworksAccountId(
1499
+ auth,
1500
+ signal,
1501
+ remainingTimeout(timeoutMs, startedAt, "resolving the Fireworks account"),
1502
+ guard,
1503
+ settings?.fireworksAccountId
1504
+ );
1505
+ await guard();
1506
+ const billingWindowAt = Date.now();
1507
+ const payload = await fetchProviderJson(
1508
+ fireworksBillingSummaryUrl(accountId, billingWindowAt),
1509
+ auth,
1510
+ signal,
1511
+ remainingTimeout(timeoutMs, startedAt, "fetching Fireworks rated spend"),
1512
+ "Fireworks billing summary endpoint",
1513
+ { redirect: "error" }
1514
+ );
1515
+ await guard();
1516
+ return normalizeFireworksBillingSummaryPayload(payload, accountId, Date.now());
1517
+ }
1518
+ },
1241
1519
  {
1242
1520
  id: "opencode-go",
1243
1521
  displayName: "OpenCode Go",
@@ -1273,7 +1551,6 @@ var SUPPORTED_ADAPTERS = [
1273
1551
  id: "zai",
1274
1552
  displayName: "Z.AI",
1275
1553
  semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
1276
- publishesStatusline: false,
1277
1554
  async query(auth, signal, timeoutMs) {
1278
1555
  const payload = await fetchProviderJson(
1279
1556
  zaiMonitorUrl(auth.model.baseUrl),
@@ -1289,7 +1566,6 @@ var SUPPORTED_ADAPTERS = [
1289
1566
  id: "zai-coding-cn",
1290
1567
  displayName: "Z.AI Coding CN",
1291
1568
  semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
1292
- publishesStatusline: false,
1293
1569
  async query(auth, signal, timeoutMs) {
1294
1570
  const payload = await fetchProviderJson(
1295
1571
  zaiMonitorUrl(auth.model.baseUrl),
@@ -1351,11 +1627,11 @@ var XAI_ADAPTER = {
1351
1627
  return normalizeXaiBillingPayload(billingPayload, userPayload.subscriptionTier, Date.now());
1352
1628
  }
1353
1629
  };
1354
- function usageAdapters(xaiUsage = true) {
1355
- return xaiUsage ? [...SUPPORTED_ADAPTERS, XAI_ADAPTER] : SUPPORTED_ADAPTERS;
1630
+ function usageAdapters() {
1631
+ return [...SUPPORTED_ADAPTERS, XAI_ADAPTER];
1356
1632
  }
1357
- function adapterForProvider(providerId, xaiUsage = true) {
1358
- return usageAdapters(xaiUsage).find((adapter) => adapter.id === providerId);
1633
+ function adapterForProvider(providerId) {
1634
+ return usageAdapters().find((adapter) => adapter.id === providerId);
1359
1635
  }
1360
1636
  function isStaleExtensionContextError(error) {
1361
1637
  return error instanceof Error && error.message.includes("This extension ctx is stale after session replacement or reload");
@@ -1372,11 +1648,14 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
1372
1648
  if (!model) return void 0;
1373
1649
  const registry = ctx.modelRegistry;
1374
1650
  let modelAuth;
1375
- if (ctx.model?.provider === adapter.id && typeof registry.getApiKeyAndHeaders === "function") {
1376
- const result = await registry.getApiKeyAndHeaders(ctx.model);
1651
+ const currentModel = ctx.model?.provider === adapter.id ? ctx.model : void 0;
1652
+ const resolveCurrentModelAuth = async () => {
1653
+ if (!currentModel || typeof registry.getApiKeyAndHeaders !== "function") return void 0;
1654
+ const result = await registry.getApiKeyAndHeaders(currentModel);
1377
1655
  if (!result.ok) throw new Error(redactUsageError(result.error));
1378
- if (authorizationFrom(result)) modelAuth = result;
1379
- }
1656
+ return authorizationFrom(result) ? result : void 0;
1657
+ };
1658
+ if (adapter.id !== "deepseek") modelAuth = await resolveCurrentModelAuth();
1380
1659
  if (typeof registry.getProviderAuth !== "function") {
1381
1660
  throw new Error("pi-usage requires Pi 0.81.0 or newer to validate resolved provider auth.");
1382
1661
  }
@@ -1386,6 +1665,7 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
1386
1665
  `${adapter.displayName} usage cannot send a proxy-resolved credential to the official usage endpoint.`
1387
1666
  );
1388
1667
  }
1668
+ if (adapter.id === "deepseek") modelAuth = await resolveCurrentModelAuth();
1389
1669
  const auth = modelAuth ?? providerResult?.auth;
1390
1670
  if (!auth) return void 0;
1391
1671
  if (adapter.id === "github-copilot") {
@@ -1406,6 +1686,26 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
1406
1686
  if (!offered.ok) throw new Error("xAI OAuth credential discovery failed closed.");
1407
1687
  return resolveXaiUsageAuth(auth, model, salt, offered.candidates);
1408
1688
  }
1689
+ if (adapter.id === "deepseek") {
1690
+ const resolvedAuthorization = authorizationFrom(auth);
1691
+ const access = bearerToken(resolvedAuthorization);
1692
+ if (!access) throw new Error("DeepSeek API balance requires Bearer authentication.");
1693
+ const authorization2 = `Bearer ${access}`;
1694
+ const headers2 = { Authorization: authorization2 };
1695
+ return {
1696
+ apiKey: access,
1697
+ headers: headers2,
1698
+ fingerprint: fingerprintResolvedAuth({ headers: headers2 }, salt),
1699
+ secrets: [
1700
+ access,
1701
+ auth.apiKey,
1702
+ headerValue(auth.headers, "Authorization"),
1703
+ resolvedAuthorization,
1704
+ authorization2
1705
+ ].filter((value) => Boolean(value)),
1706
+ model
1707
+ };
1708
+ }
1409
1709
  const authorization = authorizationFrom(auth);
1410
1710
  if (!authorization) return void 0;
1411
1711
  const headers = { Authorization: authorization };
@@ -1420,9 +1720,9 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
1420
1720
  model
1421
1721
  };
1422
1722
  }
1423
- async function queryProviderUsage(adapter, auth, signal, timeoutMs, guard) {
1723
+ async function queryProviderUsage(adapter, auth, signal, timeoutMs, guard, settings) {
1424
1724
  try {
1425
- return await adapter.query(auth, signal, timeoutMs, guard);
1725
+ return await adapter.query(auth, signal, timeoutMs, guard, settings);
1426
1726
  } catch (error) {
1427
1727
  if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
1428
1728
  throw new Error(redactUsageError(errorMessage(error), auth.secrets));
@@ -1563,7 +1863,7 @@ function resolveXaiUsageAuth(auth, model, salt, candidates) {
1563
1863
  const matches = [];
1564
1864
  for (const candidate of candidates) {
1565
1865
  try {
1566
- const credential = asObject7(candidate);
1866
+ const credential = asObject9(candidate);
1567
1867
  if (credential?.type !== "oauth") continue;
1568
1868
  sawOAuth = true;
1569
1869
  if (credential.access !== resolvedAccess) continue;
@@ -1617,7 +1917,7 @@ function resolveGitHubCopilotUsageAuth(auth, model, salt, candidates, standalone
1617
1917
  const matches = /* @__PURE__ */ new Map();
1618
1918
  for (const candidate of candidates) {
1619
1919
  try {
1620
- const credential = asObject7(candidate);
1920
+ const credential = asObject9(candidate);
1621
1921
  if (credential?.type !== "oauth") continue;
1622
1922
  sawOAuth = true;
1623
1923
  const storedAccess2 = typeof credential.access === "string" && credential.access ? credential.access : void 0;
@@ -1679,7 +1979,7 @@ function bearerToken(authorization) {
1679
1979
  const match = /^Bearer\s+(.+)$/iu.exec(authorization ?? "");
1680
1980
  return match?.[1];
1681
1981
  }
1682
- function asObject7(value) {
1982
+ function asObject9(value) {
1683
1983
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
1684
1984
  return value;
1685
1985
  }
@@ -1698,6 +1998,8 @@ function hasOfficialUrlOrigin(value, providerId) {
1698
1998
  try {
1699
1999
  const url = new URL(value);
1700
2000
  if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
2001
+ if (providerId === "deepseek") return url.origin === "https://api.deepseek.com";
2002
+ if (providerId === "fireworks") return url.origin === FIREWORKS_BILLING_SUMMARY_ORIGIN;
1701
2003
  if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
1702
2004
  if (providerId === "opencode-go") return url.origin === "https://opencode.ai";
1703
2005
  if (providerId === "kimi-coding") return url.origin === "https://api.kimi.com";
@@ -1727,11 +2029,87 @@ function validatedXaiUserId(value) {
1727
2029
  }
1728
2030
  return value;
1729
2031
  }
1730
- function remainingTimeout(timeoutMs, startedAt) {
2032
+ function remainingTimeout(timeoutMs, startedAt, description = "fetching xAI consumer usage") {
1731
2033
  const remaining = timeoutMs - (Date.now() - startedAt);
1732
- if (remaining <= 0) throw new Error("Timed out while fetching xAI consumer usage.");
2034
+ if (remaining <= 0) throw new Error(`Timed out while ${description}.`);
1733
2035
  return remaining;
1734
2036
  }
2037
+ async function resolveFireworksAccountId(auth, signal, timeoutMs, guard, configuredAccountId) {
2038
+ if (configuredAccountId !== void 0 && !isFireworksAccountId(configuredAccountId)) {
2039
+ throw new Error("The Fireworks account setting was not a safe account slug.");
2040
+ }
2041
+ const startedAt = Date.now();
2042
+ const accounts = [];
2043
+ let pageToken;
2044
+ for (let page = 0; page < FIREWORKS_MAX_ACCOUNT_PAGES; page += 1) {
2045
+ await guard();
2046
+ const payload = await fetchProviderJson(
2047
+ fireworksAccountsUrl(pageToken),
2048
+ auth,
2049
+ signal,
2050
+ remainingTimeout(timeoutMs, startedAt, "fetching Fireworks accounts"),
2051
+ "Fireworks accounts endpoint",
2052
+ { redirect: "error" }
2053
+ );
2054
+ for (const accountId of normalizeFireworksAccountsPayload(
2055
+ payload
2056
+ )) {
2057
+ if (accounts.includes(accountId)) {
2058
+ throw new Error(`Fireworks accounts listing repeated ${accountId}.`);
2059
+ }
2060
+ accounts.push(accountId);
2061
+ if (configuredAccountId === accountId) return accountId;
2062
+ }
2063
+ pageToken = fireworksNextPageToken(payload.nextPageToken);
2064
+ if (!pageToken) break;
2065
+ }
2066
+ if (pageToken) {
2067
+ throw new Error(
2068
+ configuredAccountId ? `The configured Fireworks account was not found within the first ${FIREWORKS_MAX_ACCOUNT_PAGES} listing pages.` : `Fireworks account listing exceeded ${FIREWORKS_MAX_ACCOUNT_PAGES} pages; set fireworksAccountId in pi-usage.json to an account returned in those pages.`
2069
+ );
2070
+ }
2071
+ if (accounts.length === 0) {
2072
+ throw new Error("Fireworks account discovery returned no accounts for this API key.");
2073
+ }
2074
+ if (configuredAccountId) {
2075
+ throw new Error(
2076
+ "The configured Fireworks account does not match an account visible to this API key."
2077
+ );
2078
+ }
2079
+ if (accounts.length === 1) return accounts[0];
2080
+ const preview = accounts.slice(0, 8).join(", ");
2081
+ const suffix = accounts.length > 8 ? ` \u2026and ${accounts.length - 8} more` : "";
2082
+ throw new Error(
2083
+ `The Fireworks key can see ${accounts.length} accounts (${preview}${suffix}); set fireworksAccountId in pi-usage.json to one of them.`
2084
+ );
2085
+ }
2086
+ function fireworksAccountsUrl(pageToken) {
2087
+ const url = new URL("/v1/accounts", FIREWORKS_BILLING_SUMMARY_ORIGIN);
2088
+ url.searchParams.set("pageSize", "200");
2089
+ if (pageToken !== void 0) url.searchParams.set("pageToken", pageToken);
2090
+ return url.toString();
2091
+ }
2092
+ function fireworksNextPageToken(value) {
2093
+ if (value === void 0 || value === null) return void 0;
2094
+ if (typeof value !== "string" || !value || value.length > 512) {
2095
+ throw new Error("Fireworks accounts listing returned an invalid page token.");
2096
+ }
2097
+ return value;
2098
+ }
2099
+ function fireworksBillingSummaryUrl(accountId, startedAt) {
2100
+ const dayMs = 24 * 60 * 60 * 1e3;
2101
+ const dayFloor = (time) => `${new Date(time).toISOString().slice(0, 10)}T00:00:00Z`;
2102
+ const url = new URL(
2103
+ `/v1/accounts/${accountId}/billing/summary`,
2104
+ FIREWORKS_BILLING_SUMMARY_ORIGIN
2105
+ );
2106
+ url.searchParams.set(
2107
+ "startTime",
2108
+ dayFloor(startedAt - (FIREWORKS_SPEND_WINDOW_DAYS - 1) * dayMs)
2109
+ );
2110
+ url.searchParams.set("endTime", dayFloor(startedAt + dayMs));
2111
+ return url.toString();
2112
+ }
1735
2113
  function zaiMonitorUrl(baseUrl) {
1736
2114
  const base = baseUrl?.trim();
1737
2115
  if (!base) throw new Error("Z.AI model base URL is unavailable.");
@@ -1885,7 +2263,7 @@ function normalizeCodexResetCreditsPayload(payload) {
1885
2263
  if (rawCredits !== void 0 && !Array.isArray(rawCredits)) {
1886
2264
  throw new Error("Codex reset credits response returned invalid credits.");
1887
2265
  }
1888
- const options = (rawCredits ?? []).map(asObject8).filter((credit) => Boolean(credit)).filter((credit) => credit.status === "available" && credit.reset_type === "codex_rate_limits").map(normalizeResetOption).sort(
2266
+ const options = (rawCredits ?? []).map(asObject10).filter((credit) => Boolean(credit)).filter((credit) => credit.status === "available" && credit.reset_type === "codex_rate_limits").map(normalizeResetOption).sort(
1889
2267
  (left, right) => (left.expiresAt ?? Number.MAX_SAFE_INTEGER) - (right.expiresAt ?? Number.MAX_SAFE_INTEGER)
1890
2268
  ).slice(0, Math.min(availableCount, MAX_RESET_OPTIONS));
1891
2269
  if (availableCount > 0 && options.length === 0) {
@@ -1900,7 +2278,7 @@ function selectCodexResetCredential(candidates, resolvedAccess, resolvedAccountI
1900
2278
  const matches = /* @__PURE__ */ new Map();
1901
2279
  for (const candidate of candidates) {
1902
2280
  try {
1903
- const credential = asObject8(candidate);
2281
+ const credential = asObject10(candidate);
1904
2282
  if (credential?.type !== "oauth") continue;
1905
2283
  sawOAuth = true;
1906
2284
  const storedAccess = asNonemptyString(credential.access);
@@ -1939,7 +2317,7 @@ function codexAccountIdFromAccessToken(access) {
1939
2317
  const parts = access.split(".");
1940
2318
  if (parts.length !== 3 || !parts[1]) return void 0;
1941
2319
  const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
1942
- const claims = asObject8(asObject8(payload)?.["https://api.openai.com/auth"]);
2320
+ const claims = asObject10(asObject10(payload)?.["https://api.openai.com/auth"]);
1943
2321
  return validHeaderValue(claims?.chatgpt_account_id);
1944
2322
  } catch {
1945
2323
  return void 0;
@@ -1971,7 +2349,7 @@ function normalizeResetOption(credit) {
1971
2349
  function isCodexResetOutcomeCode(value) {
1972
2350
  return value === "reset" || value === "nothing_to_reset" || value === "no_credit" || value === "already_redeemed";
1973
2351
  }
1974
- function asObject8(value) {
2352
+ function asObject10(value) {
1975
2353
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
1976
2354
  return value;
1977
2355
  }
@@ -2012,10 +2390,13 @@ var BAR_SEGMENTS = 20;
2012
2390
  var VALUE_COLUMN = 29;
2013
2391
  function formatUsageReport(report, displayState) {
2014
2392
  const stateLabel = displayState === "current" ? "Current" : "Configured";
2015
- const lines = [`${report.providerName} Usage \xB7 ${stateLabel}`];
2393
+ const title = report.providerId === "deepseek" ? "DeepSeek API Balance" : report.providerId === "fireworks" ? "Fireworks API Spend" : `${report.providerName} Usage`;
2394
+ const lines = [`${title} \xB7 ${stateLabel}`];
2016
2395
  if (report.accountLabel) lines.push(`Account: ${report.accountLabel}`);
2017
2396
  lines.push(`Semantics: ${report.semantics.label}`, "");
2018
2397
  if (report.providerId === "openai-codex") formatCodexReport(lines, report);
2398
+ else if (report.providerId === "deepseek") formatDeepSeekReport(lines, report);
2399
+ else if (report.providerId === "fireworks") formatFireworksReport(lines, report);
2019
2400
  else if (report.providerId === "github-copilot") formatGitHubCopilotReport(lines, report);
2020
2401
  else if (report.providerId === "openrouter") formatOpenRouterReport(lines, report);
2021
2402
  else if (report.providerId === "opencode-go") formatOpenCodeZenReport(lines, report);
@@ -2029,8 +2410,12 @@ function formatUsageReport(report, displayState) {
2029
2410
  }
2030
2411
  return lines.join("\n").trimEnd();
2031
2412
  }
2032
- function formatUsageStatusline(report, model) {
2033
- if (report.providerId === "openai-codex") return formatCodexStatusline(report, model);
2413
+ function formatUsageStatusline(report, model, now = Date.now(), showCodexResetCountdown = true) {
2414
+ if (report.providerId === "openai-codex") {
2415
+ return formatCodexStatusline(report, model, now, showCodexResetCountdown);
2416
+ }
2417
+ if (report.providerId === "deepseek") return formatDeepSeekStatusline(report);
2418
+ if (report.providerId === "fireworks") return formatFireworksStatusline(report);
2034
2419
  if (report.providerId === "github-copilot") return formatGitHubCopilotStatusline(report);
2035
2420
  if (report.providerId === "openrouter") {
2036
2421
  const limit = report.buckets.find((bucket) => bucket.id === "key-limit");
@@ -2040,6 +2425,9 @@ function formatUsageStatusline(report, model) {
2040
2425
  }
2041
2426
  if (report.providerId === "opencode-go") return formatOpenCodeZenStatusline(report);
2042
2427
  if (report.providerId === "kimi-coding") return formatKimiCodingStatusline(report);
2428
+ if (report.providerId === "zai" || report.providerId === "zai-coding-cn") {
2429
+ return formatZaiStatusline(report);
2430
+ }
2043
2431
  return void 0;
2044
2432
  }
2045
2433
  function formatProviderStates(states) {
@@ -2073,6 +2461,54 @@ function formatCodexReport(lines, report) {
2073
2461
  }
2074
2462
  }
2075
2463
  }
2464
+ function formatDeepSeekReport(lines, report) {
2465
+ const availability = report.metrics.find((metric) => metric.id === "api-availability");
2466
+ lines.push(
2467
+ `${"API calls:".padEnd(VALUE_COLUMN)}${availability?.value === "available" ? "Available" : "Unavailable"}`
2468
+ );
2469
+ for (const currency of ["CNY", "USD"]) {
2470
+ const metrics = report.metrics.filter((metric) => metric.currency === currency);
2471
+ if (metrics.length === 0) continue;
2472
+ lines.push("", `${currency} balance:`);
2473
+ for (const metric of metrics) {
2474
+ lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${currency} ${metric.value}`);
2475
+ }
2476
+ }
2477
+ }
2478
+ function formatDeepSeekStatusline(report) {
2479
+ const availability = report.metrics.find((metric) => metric.id === "api-availability");
2480
+ if (availability?.value !== "available") return "deepseek API unavailable";
2481
+ const totals = ["CNY", "USD"].flatMap((currency) => {
2482
+ const metric = report.metrics.find(
2483
+ (candidate) => candidate.id === `${currency.toLowerCase()}-total`
2484
+ );
2485
+ return metric ? [`${currency} ${metric.value}`] : [];
2486
+ });
2487
+ return totals.length > 0 ? `deepseek ${totals.join(" \xB7 ")}` : "deepseek balance unavailable";
2488
+ }
2489
+ function formatFireworksReport(lines, report) {
2490
+ lines.push(`${"Spend window:".padEnd(VALUE_COLUMN)}Last 30 days (rated)`);
2491
+ for (const currency of fireworksCurrencies(report)) {
2492
+ lines.push("", `${currency} rated spend:`);
2493
+ for (const metric of report.metrics) {
2494
+ if (metric.currency !== currency) continue;
2495
+ lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${currency} ${metric.value}`);
2496
+ }
2497
+ }
2498
+ }
2499
+ function formatFireworksStatusline(report) {
2500
+ const totals = report.metrics.filter((metric) => metric.id.endsWith("-total"));
2501
+ if (totals.length === 0) return "fireworks no rated usage";
2502
+ return `fireworks ${totals.map((metric) => `${metric.currency} ${metric.value}`).join(" \xB7 ")}`;
2503
+ }
2504
+ function fireworksCurrencies(report) {
2505
+ const currencies = [];
2506
+ for (const metric of report.metrics) {
2507
+ if (!metric.currency || currencies.includes(metric.currency)) continue;
2508
+ currencies.push(metric.currency);
2509
+ }
2510
+ return currencies;
2511
+ }
2076
2512
  function formatGitHubCopilotReport(lines, report) {
2077
2513
  const quota = findGitHubCopilotQuota(report);
2078
2514
  if (!quota || quota.limit === void 0 || quota.remaining === void 0) {
@@ -2187,6 +2623,21 @@ function formatKimiCodingStatusline(report) {
2187
2623
  }
2188
2624
  return parts.length > 1 ? parts.join(" ") : void 0;
2189
2625
  }
2626
+ function formatZaiStatusline(report) {
2627
+ const selected = [
2628
+ report.buckets.find((bucket) => bucket.id === "five-hour"),
2629
+ report.buckets.find((bucket) => bucket.id === "weekly")
2630
+ ];
2631
+ const parts = ["zai"];
2632
+ for (const bucket of selected) {
2633
+ if (!bucket?.limit || bucket.remaining === void 0) continue;
2634
+ const fallback = bucket.id === "weekly" ? "weekly" : "5h";
2635
+ parts.push(
2636
+ `${percentRemaining(bucket)}% ${formatWindowLabel(bucket.windowMinutes, fallback, true)}`
2637
+ );
2638
+ }
2639
+ return parts.length > 1 ? parts.join(" ") : void 0;
2640
+ }
2190
2641
  function formatCurrencyMetric(metric) {
2191
2642
  if (typeof metric.value !== "number") return String(metric.value);
2192
2643
  if (!metric.currency) return "unavailable";
@@ -2258,7 +2709,7 @@ function formatGenericReport(lines, report) {
2258
2709
  );
2259
2710
  }
2260
2711
  }
2261
- function formatCodexStatusline(report, model) {
2712
+ function formatCodexStatusline(report, model, now = Date.now(), showResetCountdown = true) {
2262
2713
  const group = selectCodexGroup(report, model);
2263
2714
  if (!group) return formatCodexCreditsStatus(report);
2264
2715
  const buckets = report.buckets.filter((bucket) => (bucket.groupId ?? bucket.id) === group);
@@ -2268,10 +2719,15 @@ function formatCodexStatusline(report, model) {
2268
2719
  ];
2269
2720
  for (const bucket of buckets) {
2270
2721
  if (bucket.remaining === void 0) continue;
2722
+ const percent = `${clampPercent4(bucket.remaining).toFixed(0)}%`;
2271
2723
  const fallback = bucket.id.endsWith(":secondary") ? "weekly" : "5h";
2272
- parts.push(
2273
- `${clampPercent4(bucket.remaining).toFixed(0)}% ${formatWindowLabel(bucket.windowMinutes, fallback, true)}`
2274
- );
2724
+ const window = formatWindowLabel(bucket.windowMinutes, fallback, true);
2725
+ if (!showResetCountdown) {
2726
+ parts.push(`${percent} ${window}`);
2727
+ continue;
2728
+ }
2729
+ const reset = formatResetCountdown(bucket.resetsAt, now);
2730
+ parts.push(`${percent} ${reset ? `\u21BB ${reset}` : window}`);
2275
2731
  }
2276
2732
  return parts.length > 1 ? parts.join(" ") : formatCodexCreditsStatus(report);
2277
2733
  }
@@ -2352,6 +2808,23 @@ function formatWindowLabel(minutes, fallback, compact) {
2352
2808
  if (minutes % 60 === 0) return `${minutes / 60}h`;
2353
2809
  return `${minutes}m`;
2354
2810
  }
2811
+ function formatResetCountdown(resetsAt, now) {
2812
+ if (resetsAt === void 0 || !Number.isFinite(resetsAt) || !Number.isFinite(now))
2813
+ return void 0;
2814
+ const totalMinutes = Math.max(0, Math.ceil((resetsAt * 1e3 - now) / 6e4));
2815
+ const days = Math.floor(totalMinutes / 1440);
2816
+ const hours = Math.floor(totalMinutes % 1440 / 60);
2817
+ const minutes = totalMinutes % 60;
2818
+ if (days > 0) {
2819
+ return [
2820
+ `${String(days)}d`,
2821
+ hours > 0 ? `${String(hours)}h` : minutes > 0 ? `${String(minutes)}m` : ""
2822
+ ].filter(Boolean).join("");
2823
+ }
2824
+ if (hours > 0)
2825
+ return [`${String(hours)}h`, minutes > 0 ? `${String(minutes)}m` : ""].filter(Boolean).join("");
2826
+ return `${String(minutes)}m`;
2827
+ }
2355
2828
  function formatMetricValue(value, unit) {
2356
2829
  if (unit === "usd" && typeof value === "number") return formatUsd(value);
2357
2830
  return String(value);
@@ -2384,7 +2857,7 @@ var USAGE_SETTINGS_FILE = "pi-usage.json";
2384
2857
  var MAX_USAGE_SETTINGS_BYTES = 64 * 1024;
2385
2858
  var DEFAULT_USAGE_SETTINGS = Object.freeze({
2386
2859
  codexFastMode: false,
2387
- xaiUsage: true
2860
+ codexStatusResetCountdown: true
2388
2861
  });
2389
2862
  function usageSettingsPath() {
2390
2863
  return join(getAgentDir(), USAGE_SETTINGS_FILE);
@@ -2394,12 +2867,16 @@ function normalizeUsageSettings(value) {
2394
2867
  if (Object.hasOwn(value, "codexFastMode") && typeof value.codexFastMode !== "boolean") {
2395
2868
  return void 0;
2396
2869
  }
2397
- if (Object.hasOwn(value, "xaiUsage") && typeof value.xaiUsage !== "boolean") {
2870
+ if (Object.hasOwn(value, "codexStatusResetCountdown") && typeof value.codexStatusResetCountdown !== "boolean") {
2871
+ return void 0;
2872
+ }
2873
+ if (Object.hasOwn(value, "fireworksAccountId") && !isFireworksAccountId(value.fireworksAccountId)) {
2398
2874
  return void 0;
2399
2875
  }
2400
2876
  return {
2401
2877
  codexFastMode: typeof value.codexFastMode === "boolean" ? value.codexFastMode : DEFAULT_USAGE_SETTINGS.codexFastMode,
2402
- xaiUsage: typeof value.xaiUsage === "boolean" ? value.xaiUsage : DEFAULT_USAGE_SETTINGS.xaiUsage
2878
+ codexStatusResetCountdown: typeof value.codexStatusResetCountdown === "boolean" ? value.codexStatusResetCountdown : DEFAULT_USAGE_SETTINGS.codexStatusResetCountdown,
2879
+ ...isFireworksAccountId(value.fireworksAccountId) ? { fireworksAccountId: value.fireworksAccountId } : {}
2403
2880
  };
2404
2881
  }
2405
2882
  async function loadUsageSettings(path = usageSettingsPath(), signal) {
@@ -2483,7 +2960,11 @@ async function saveUsageSettingsPatch(path, patch, operations, signal) {
2483
2960
  if (latest.kind === "invalid") {
2484
2961
  throw new Error("Cannot overwrite an invalid pi-usage.json; repair it and reload first");
2485
2962
  }
2486
- const document = { ...latest.document, ...patch };
2963
+ const document = { ...latest.document };
2964
+ for (const [key, value] of Object.entries(patch)) {
2965
+ if (value === void 0) delete document[key];
2966
+ else document[key] = value;
2967
+ }
2487
2968
  const settings = normalizeUsageSettings(document);
2488
2969
  if (!settings) throw new Error("Refusing to save invalid pi-usage settings");
2489
2970
  const directory = dirname(path);
@@ -2529,7 +3010,7 @@ import { randomUUID as randomUUID2 } from "node:crypto";
2529
3010
  // src/codex-fast-runtime.ts
2530
3011
  var NO_FAST_REQUEST = /* @__PURE__ */ Symbol("no-fast-request");
2531
3012
  var FAST_USAGE_WARNING = "Fast is about 1.5\xD7 faster and uses more of your plan allowance.";
2532
- function registerCodexFastMode(pi, settingsRuntime, refreshStatus) {
3013
+ function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {}) {
2533
3014
  let sessionController = new AbortController();
2534
3015
  let generation = 0;
2535
3016
  const pendingFastRequests = /* @__PURE__ */ new Map();
@@ -2585,37 +3066,42 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus) {
2585
3066
  await toggle(ctx, !availability.enabled);
2586
3067
  }
2587
3068
  });
2588
- pi.on("session_start", async (_event, ctx) => {
3069
+ const prepareSession = (ctx) => {
3070
+ const sessionId = ctx.sessionManager.getSessionId();
2589
3071
  generation += 1;
2590
3072
  sessionController.abort();
2591
3073
  pendingFastRequests.clear();
2592
3074
  sessionController = new AbortController();
2593
3075
  const ownerGeneration = generation;
2594
- const sessionId = ctx.sessionManager.getSessionId();
2595
- let state;
2596
- try {
2597
- state = await settingsRuntime.reload(sessionController.signal);
2598
- } catch (error) {
2599
- if (sessionController.signal.aborted || ownerGeneration !== generation) return;
2600
- if (ctx.hasUI) {
3076
+ return (async () => {
3077
+ let state;
3078
+ try {
3079
+ state = await settingsRuntime.reload(sessionController.signal);
3080
+ } catch (error) {
3081
+ if (sessionController.signal.aborted || ownerGeneration !== generation) return;
3082
+ if (ctx.hasUI) {
3083
+ ctx.ui.notify(
3084
+ `Could not load pi-usage.json; using defaults. ${errorMessage(error)}`,
3085
+ "warning"
3086
+ );
3087
+ }
3088
+ return;
3089
+ }
3090
+ if (sessionController.signal.aborted || ownerGeneration !== generation || ctx.sessionManager.getSessionId() !== sessionId) {
3091
+ return;
3092
+ }
3093
+ if (ctx.hasUI && state.kind === "invalid") {
2601
3094
  ctx.ui.notify(
2602
- `Could not load pi-usage.json; using defaults. ${errorMessage(error)}`,
3095
+ `Invalid pi-usage.json; using defaults without overwriting it. ${state.issue}`,
2603
3096
  "warning"
2604
3097
  );
2605
3098
  }
2606
- return;
2607
- }
2608
- if (sessionController.signal.aborted || ownerGeneration !== generation || ctx.sessionManager.getSessionId() !== sessionId) {
2609
- return;
2610
- }
2611
- if (ctx.hasUI && state.kind === "invalid") {
2612
- ctx.ui.notify(
2613
- `Invalid pi-usage.json; using defaults without overwriting it. ${state.issue}`,
2614
- "warning"
2615
- );
2616
- }
2617
- refreshStatus(ctx);
2618
- });
3099
+ refreshStatus(ctx);
3100
+ })();
3101
+ };
3102
+ if (options.registerSessionStart !== false) {
3103
+ pi.on("session_start", async (_event, ctx) => prepareSession(ctx));
3104
+ }
2619
3105
  pi.on("before_provider_request", (event, ctx) => {
2620
3106
  const rewritten = rewriteCodexFastPayload(
2621
3107
  event.payload,
@@ -2648,6 +3134,7 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus) {
2648
3134
  await settingsRuntime.flush();
2649
3135
  });
2650
3136
  return {
3137
+ prepareSession,
2651
3138
  availability(model) {
2652
3139
  return codexFastAvailability(model, settingsRuntime.get().settings.codexFastMode);
2653
3140
  },
@@ -2684,8 +3171,8 @@ function isAbortError2(error) {
2684
3171
  }
2685
3172
 
2686
3173
  // src/usage-helpers.ts
2687
- function configuredAdapters(ctx, xaiUsage = true) {
2688
- return usageAdapters(xaiUsage).filter(
3174
+ function configuredAdapters(ctx) {
3175
+ return usageAdapters().filter(
2689
3176
  (adapter) => adapter.id === ctx.model?.provider || providerIsConfigured(ctx, adapter.id)
2690
3177
  );
2691
3178
  }
@@ -2726,6 +3213,8 @@ import {
2726
3213
  SettingsList,
2727
3214
  Text
2728
3215
  } from "@earendil-works/pi-tui";
3216
+ var AUTO = "Auto";
3217
+ var EDIT = "Edit\u2026";
2729
3218
  var OFF = "Off";
2730
3219
  var ON = "On";
2731
3220
  async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent, onApplied) {
@@ -2733,7 +3222,23 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
2733
3222
  if (ctx.hasUI) ctx.ui.notify(`Edit settings manually: ${settingsRuntime.get().path}`, "info");
2734
3223
  return false;
2735
3224
  }
2736
- if (parentSignal.aborted || !isCurrent()) return false;
3225
+ let changed = false;
3226
+ while (!parentSignal.aborted && isCurrent()) {
3227
+ const result = await showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, onApplied);
3228
+ if (!result) return changed;
3229
+ changed ||= result.changed;
3230
+ if (!result.editFireworksAccount) return changed;
3231
+ changed ||= await editFireworksAccount(
3232
+ ctx,
3233
+ settingsRuntime,
3234
+ parentSignal,
3235
+ isCurrent,
3236
+ onApplied
3237
+ );
3238
+ }
3239
+ return changed;
3240
+ }
3241
+ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, onApplied) {
2737
3242
  return ctx.ui.custom((tui, theme, _keybindings, done) => {
2738
3243
  const localController = new AbortController();
2739
3244
  const signal = AbortSignal.any([parentSignal, localController.signal]);
@@ -2741,6 +3246,7 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
2741
3246
  let closing = false;
2742
3247
  let saveQueue = Promise.resolve();
2743
3248
  const state = settingsRuntime.get();
3249
+ const fireworksValue = state.settings.fireworksAccountId ?? AUTO;
2744
3250
  const items = [
2745
3251
  {
2746
3252
  id: "codexFastMode",
@@ -2750,11 +3256,18 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
2750
3256
  values: [OFF, ON]
2751
3257
  },
2752
3258
  {
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,
3259
+ id: "codexStatusResetCountdown",
3260
+ label: "Codex reset countdown",
3261
+ description: "Show time remaining until each Codex usage limit resets.",
3262
+ currentValue: state.settings.codexStatusResetCountdown ? ON : OFF,
2757
3263
  values: [OFF, ON]
3264
+ },
3265
+ {
3266
+ id: "fireworksAccountId",
3267
+ label: "Fireworks account",
3268
+ description: "Select Edit to enter a visible account slug, or submit blank to clear it.",
3269
+ currentValue: fireworksValue,
3270
+ values: state.settings.fireworksAccountId ? [state.settings.fireworksAccountId, EDIT] : [AUTO, EDIT]
2758
3271
  }
2759
3272
  ];
2760
3273
  const container = new Container();
@@ -2764,7 +3277,36 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
2764
3277
  if (closing) return;
2765
3278
  closing = true;
2766
3279
  localController.abort();
2767
- done(changed);
3280
+ done({ changed, editFireworksAccount: false });
3281
+ };
3282
+ const queueUpdate = (id, requested, display) => {
3283
+ saveQueue = saveQueue.then(async () => {
3284
+ const previous = settingsRuntime.get().settings[id];
3285
+ if (settingsRuntime.get().kind === "invalid") {
3286
+ settingsList.updateValue(id, displaySetting(id, previous));
3287
+ if (!signal.aborted && isCurrent()) {
3288
+ ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
3289
+ tui.requestRender();
3290
+ }
3291
+ return;
3292
+ }
3293
+ try {
3294
+ await settingsRuntime.update({ [id]: requested }, signal);
3295
+ } catch (error) {
3296
+ if (signal.aborted || !isCurrent()) return;
3297
+ settingsList.updateValue(id, displaySetting(id, previous));
3298
+ ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
3299
+ tui.requestRender();
3300
+ return;
3301
+ }
3302
+ if (previous !== requested) {
3303
+ changed = true;
3304
+ onApplied(id);
3305
+ }
3306
+ if (signal.aborted || !isCurrent()) return;
3307
+ settingsList.updateValue(id, display);
3308
+ tui.requestRender();
3309
+ });
2768
3310
  };
2769
3311
  settingsList = new SettingsList(
2770
3312
  items,
@@ -2772,36 +3314,18 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
2772
3314
  getSettingsListTheme(),
2773
3315
  (id, value) => {
2774
3316
  if (closing || signal.aborted || !isCurrent()) return;
2775
- const settingId = id;
2776
- const requested = value !== OFF;
2777
- saveQueue = saveQueue.then(async () => {
2778
- const previous = settingsRuntime.get().settings[settingId];
2779
- if (settingsRuntime.get().kind === "invalid") {
2780
- const effectivePrevious = settingId === "xaiUsage" ? false : previous;
2781
- settingsList.updateValue(id, displayValue(settingId, effectivePrevious));
2782
- if (!signal.aborted && isCurrent()) {
2783
- ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
2784
- tui.requestRender();
2785
- }
2786
- return;
2787
- }
2788
- try {
2789
- await settingsRuntime.update({ [settingId]: requested }, signal);
2790
- } catch (error) {
2791
- if (signal.aborted || !isCurrent()) return;
2792
- settingsList.updateValue(id, displayValue(settingId, previous));
2793
- ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
2794
- tui.requestRender();
2795
- return;
2796
- }
2797
- if (previous !== requested) {
2798
- changed = true;
2799
- onApplied(settingId, previous, requested);
3317
+ if (id === "fireworksAccountId") {
3318
+ if (value === EDIT) {
3319
+ saveQueue = saveQueue.then(() => {
3320
+ if (closing || signal.aborted || !isCurrent()) return;
3321
+ closing = true;
3322
+ done({ changed, editFireworksAccount: true });
3323
+ });
2800
3324
  }
2801
- if (signal.aborted || !isCurrent()) return;
2802
- settingsList.updateValue(id, displayValue(settingId, requested));
2803
- tui.requestRender();
2804
- });
3325
+ return;
3326
+ }
3327
+ const settingId = id;
3328
+ queueUpdate(settingId, value !== OFF, value);
2805
3329
  },
2806
3330
  cancel
2807
3331
  );
@@ -2823,12 +3347,46 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
2823
3347
  };
2824
3348
  });
2825
3349
  }
2826
- function displayValue(_id, enabled) {
2827
- return enabled ? ON : OFF;
3350
+ async function editFireworksAccount(ctx, settingsRuntime, signal, isCurrent, onApplied) {
3351
+ while (!signal.aborted && isCurrent()) {
3352
+ const state = settingsRuntime.get();
3353
+ if (state.kind === "invalid") {
3354
+ ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
3355
+ return false;
3356
+ }
3357
+ const entered = await ctx.ui.input(
3358
+ "Fireworks account slug \xB7 submit blank for Auto",
3359
+ state.settings.fireworksAccountId ?? "Example: acme",
3360
+ { signal }
3361
+ );
3362
+ if (signal.aborted || !isCurrent() || entered === void 0) return false;
3363
+ const normalized = entered.trim();
3364
+ const requested = normalized || void 0;
3365
+ if (requested !== void 0 && !isFireworksAccountId(requested)) {
3366
+ ctx.ui.notify("Enter a URL-safe Fireworks account slug.", "warning");
3367
+ continue;
3368
+ }
3369
+ if (requested === state.settings.fireworksAccountId) return false;
3370
+ try {
3371
+ await settingsRuntime.update({ fireworksAccountId: requested }, signal);
3372
+ } catch (error) {
3373
+ if (signal.aborted || !isCurrent()) return false;
3374
+ ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
3375
+ return false;
3376
+ }
3377
+ onApplied("fireworksAccountId");
3378
+ return true;
3379
+ }
3380
+ return false;
3381
+ }
3382
+ function displaySetting(id, value) {
3383
+ if (id === "fireworksAccountId") return typeof value === "string" ? value : AUTO;
3384
+ return value ? ON : OFF;
2828
3385
  }
2829
3386
 
2830
3387
  // src/usage.ts
2831
3388
  var CACHE_TTL_MS = 5 * 60 * 1e3;
3389
+ var STATUS_COUNTDOWN_REFRESH_MS = 60 * 1e3;
2832
3390
  var DEFAULT_TIMEOUT_MS = 15e3;
2833
3391
  var ALL_PROVIDER_CONCURRENCY = 2;
2834
3392
  var FAILURE_BACKOFF_MS = 3e4;
@@ -2854,19 +3412,22 @@ function usageExtension(pi, dependencies = {}) {
2854
3412
  let sessionActive = false;
2855
3413
  let statusGeneration = 0;
2856
3414
  let sessionGeneration = 0;
2857
- let xaiSettingsGeneration = 0;
2858
3415
  let statusRefreshTimer;
3416
+ let statusCountdownTimer;
2859
3417
  let statusController;
2860
3418
  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 = () => {
3419
+ const clearStatusRefreshTimer = () => {
2867
3420
  if (statusRefreshTimer) clearTimeout(statusRefreshTimer);
2868
3421
  statusRefreshTimer = void 0;
2869
3422
  };
3423
+ const clearStatusCountdownTimer = () => {
3424
+ if (statusCountdownTimer) clearTimeout(statusCountdownTimer);
3425
+ statusCountdownTimer = void 0;
3426
+ };
3427
+ const clearStatusTimers = () => {
3428
+ clearStatusRefreshTimer();
3429
+ clearStatusCountdownTimer();
3430
+ };
2870
3431
  const safeSetStatus = (ctx, value) => {
2871
3432
  try {
2872
3433
  ctx.ui.setStatus(STATUS_KEY, value);
@@ -2880,11 +3441,11 @@ function usageExtension(pi, dependencies = {}) {
2880
3441
  statusGeneration += 1;
2881
3442
  statusController?.abort();
2882
3443
  statusController = void 0;
2883
- clearStatusTimer();
3444
+ clearStatusTimers();
2884
3445
  safeSetStatus(ctx, void 0);
2885
3446
  };
2886
3447
  const scheduleStatusRefresh = (ctx, model) => {
2887
- clearStatusTimer();
3448
+ clearStatusRefreshTimer();
2888
3449
  const generation = statusGeneration;
2889
3450
  statusRefreshTimer = setTimeout(() => {
2890
3451
  statusRefreshTimer = void 0;
@@ -2894,13 +3455,14 @@ function usageExtension(pi, dependencies = {}) {
2894
3455
  statusRefreshTimer.unref?.();
2895
3456
  };
2896
3457
  const publishStatus = (ctx, outcome, model, shouldSchedule) => {
2897
- if (activeAdapterForProvider(model.provider)?.publishesStatusline === false) {
2898
- clearStatusTimer();
3458
+ clearStatusCountdownTimer();
3459
+ if (adapterForProvider(model.provider)?.publishesStatusline === false) {
3460
+ clearStatusRefreshTimer();
2899
3461
  safeSetStatus(ctx, void 0);
2900
3462
  return;
2901
3463
  }
2902
3464
  if (outcome.state.status === "unsupported") {
2903
- clearStatusTimer();
3465
+ clearStatusRefreshTimer();
2904
3466
  safeSetStatus(ctx, void 0);
2905
3467
  return;
2906
3468
  }
@@ -2913,10 +3475,30 @@ function usageExtension(pi, dependencies = {}) {
2913
3475
  }
2914
3476
  return;
2915
3477
  }
2916
- const rawValue = formatUsageStatusline(outcome.state.report, model);
3478
+ const showCodexResetCountdown = outcome.state.report.providerId === "openai-codex" && settingsRuntime.get().settings.codexStatusResetCountdown;
3479
+ const now = Date.now();
3480
+ const rawValue = formatUsageStatusline(
3481
+ outcome.state.report,
3482
+ model,
3483
+ now,
3484
+ showCodexResetCountdown
3485
+ );
2917
3486
  const value = rawValue ? fastRuntime.decorateStatus(model, rawValue) : void 0;
2918
3487
  if (!safeSetStatus(ctx, value)) return;
2919
3488
  if (shouldSchedule && sessionActive) scheduleStatusRefresh(ctx, model);
3489
+ if (sessionActive && showCodexResetCountdown && outcome.state.report.buckets.some(
3490
+ (bucket) => bucket.resetsAt !== void 0 && Number.isFinite(bucket.resetsAt) && bucket.resetsAt * 1e3 > now
3491
+ )) {
3492
+ const generation = statusGeneration;
3493
+ statusCountdownTimer = setTimeout(() => {
3494
+ statusCountdownTimer = void 0;
3495
+ if (!sessionActive || generation !== statusGeneration || modelIdentity(ctx.model) !== modelIdentity(model)) {
3496
+ return;
3497
+ }
3498
+ publishStatus(ctx, outcome, model, false);
3499
+ }, STATUS_COUNTDOWN_REFRESH_MS);
3500
+ statusCountdownTimer.unref?.();
3501
+ }
2920
3502
  };
2921
3503
  const invalidateProviderState = (providerId) => {
2922
3504
  cache.clearProvider(providerId);
@@ -2938,18 +3520,18 @@ function usageExtension(pi, dependencies = {}) {
2938
3520
  }
2939
3521
  activeCurrentIdentity = nextIdentity;
2940
3522
  };
2941
- const queryAdapterState = async (ctx, adapter, displayState, force, signal) => {
2942
- const startedAt = Date.now();
3523
+ const queryAdapterState = async (ctx, adapter, displayState, force, signal, authRetry = 0, deadlineAt = Date.now() + DEFAULT_TIMEOUT_MS) => {
2943
3524
  const expectedSessionGeneration = sessionGeneration;
2944
- const expectedXaiSettingsGeneration = xaiSettingsGeneration;
2945
3525
  const expectedSessionId = ctx.sessionManager.getSessionId();
2946
3526
  const expectedModelIdentity = modelIdentity(ctx.model);
3527
+ const expectedFireworksAccountId = adapter.id === "fireworks" ? settingsRuntime.get().settings.fireworksAccountId : void 0;
3528
+ const querySettings = adapter.id === "fireworks" ? { fireworksAccountId: expectedFireworksAccountId } : void 0;
2947
3529
  let auth;
2948
3530
  try {
2949
3531
  auth = await awaitWithDeadline(
2950
3532
  resolveUsageAuth(ctx, adapter, void 0, credentialReader, credentialCandidates),
2951
3533
  signal,
2952
- DEFAULT_TIMEOUT_MS,
3534
+ Math.max(1, deadlineAt - Date.now()),
2953
3535
  `resolving ${adapter.displayName} runtime auth`
2954
3536
  );
2955
3537
  } catch (error) {
@@ -2967,9 +3549,9 @@ function usageExtension(pi, dependencies = {}) {
2967
3549
  }
2968
3550
  };
2969
3551
  }
2970
- if (adapter.id === "xai" && (expectedSessionGeneration !== sessionGeneration || expectedXaiSettingsGeneration !== xaiSettingsGeneration || !xaiUsageEnabled() || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity)) {
2971
- throw abortError();
2972
- }
3552
+ const requiresRequestBoundaryGuard = ["deepseek", "fireworks", "xai"].includes(adapter.id);
3553
+ const requestContextChanged = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity || adapter.id === "fireworks" && settingsRuntime.get().settings.fireworksAccountId !== expectedFireworksAccountId;
3554
+ if (requiresRequestBoundaryGuard && requestContextChanged()) throw abortError();
2973
3555
  if (!auth) {
2974
3556
  if (displayState === "current") {
2975
3557
  transitionCurrentIdentity(`${adapter.id}:unavailable`, adapter.id);
@@ -2985,10 +3567,11 @@ function usageExtension(pi, dependencies = {}) {
2985
3567
  authState: "unavailable"
2986
3568
  };
2987
3569
  }
3570
+ const queryFingerprint = adapter.id === "fireworks" ? `${auth.fingerprint}:account:${expectedFireworksAccountId ?? "auto"}` : auth.fingerprint;
2988
3571
  if (displayState === "current") {
2989
- transitionCurrentIdentity(`${adapter.id}:${auth.fingerprint}`, adapter.id);
3572
+ transitionCurrentIdentity(`${adapter.id}:${queryFingerprint}`, adapter.id);
2990
3573
  }
2991
- const cached = !force ? cache.get(adapter.id, auth.fingerprint) : void 0;
3574
+ const cached = !force ? cache.get(adapter.id, queryFingerprint) : void 0;
2992
3575
  if (cached) {
2993
3576
  return {
2994
3577
  state: {
@@ -3001,7 +3584,7 @@ function usageExtension(pi, dependencies = {}) {
3001
3584
  fingerprint: auth.fingerprint
3002
3585
  };
3003
3586
  }
3004
- const failureKey = `${adapter.id}:${auth.fingerprint}`;
3587
+ const failureKey = `${adapter.id}:${queryFingerprint}`;
3005
3588
  const previousFailure = failureBackoff.get(failureKey);
3006
3589
  if (!force && previousFailure && previousFailure.until > Date.now()) {
3007
3590
  return {
@@ -3019,26 +3602,37 @@ function usageExtension(pi, dependencies = {}) {
3019
3602
  querySequence += 1;
3020
3603
  const queryId = querySequence;
3021
3604
  setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
3605
+ let deepSeekAuthChanged = false;
3022
3606
  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
- }
3607
+ const remainingMs = Math.max(1, deadlineAt - Date.now());
3608
+ const guard = requiresRequestBoundaryGuard ? async () => {
3609
+ if (signal.aborted || requestContextChanged()) throw abortError();
3028
3610
  const revalidated = await awaitWithDeadline(
3029
3611
  resolveUsageAuth(ctx, adapter, void 0, credentialReader, credentialCandidates),
3030
3612
  signal,
3031
- Math.max(1, DEFAULT_TIMEOUT_MS - (Date.now() - startedAt)),
3032
- "revalidating xAI runtime auth"
3613
+ Math.max(1, deadlineAt - Date.now()),
3614
+ `revalidating ${adapter.displayName} runtime auth`
3033
3615
  );
3034
- if (signal.aborted || expectedSessionGeneration !== sessionGeneration || expectedXaiSettingsGeneration !== xaiSettingsGeneration || !xaiUsageEnabled() || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity || revalidated?.fingerprint !== auth.fingerprint) {
3616
+ if (signal.aborted || requestContextChanged()) throw abortError();
3617
+ if (revalidated?.fingerprint !== auth.fingerprint) {
3618
+ if (adapter.id === "deepseek") {
3619
+ deepSeekAuthChanged = true;
3620
+ throw new Error("DeepSeek runtime credential changed during the balance query.");
3621
+ }
3035
3622
  throw abortError();
3036
3623
  }
3037
3624
  } : void 0;
3038
- const report = await queryProviderUsage(adapter, auth, signal, remainingMs, guard);
3625
+ const report = await queryProviderUsage(
3626
+ adapter,
3627
+ auth,
3628
+ signal,
3629
+ remainingMs,
3630
+ guard,
3631
+ querySettings
3632
+ );
3039
3633
  if (guard) await guard();
3040
3634
  if (latestQueries.get(failureKey) === queryId) {
3041
- cache.set(adapter.id, auth.fingerprint, report);
3635
+ cache.set(adapter.id, queryFingerprint, report);
3042
3636
  failureBackoff.delete(failureKey);
3043
3637
  }
3044
3638
  return {
@@ -3053,6 +3647,18 @@ function usageExtension(pi, dependencies = {}) {
3053
3647
  };
3054
3648
  } catch (error) {
3055
3649
  if (isStaleExtensionContextError(error) || isAbortError3(error)) throw error;
3650
+ if (deepSeekAuthChanged && authRetry === 0 && !signal.aborted && !requestContextChanged() && Date.now() < deadlineAt) {
3651
+ if (latestQueries.get(failureKey) === queryId) latestQueries.delete(failureKey);
3652
+ return queryAdapterState(
3653
+ ctx,
3654
+ adapter,
3655
+ displayState,
3656
+ true,
3657
+ signal,
3658
+ authRetry + 1,
3659
+ deadlineAt
3660
+ );
3661
+ }
3056
3662
  const message = errorMessage(error);
3057
3663
  const now = Date.now();
3058
3664
  for (const [key, failure] of failureBackoff) {
@@ -3079,7 +3685,7 @@ function usageExtension(pi, dependencies = {}) {
3079
3685
  }
3080
3686
  };
3081
3687
  const queryCurrentState = async (ctx, model, force, signal) => {
3082
- const adapter = activeAdapterForProvider(model?.provider);
3688
+ const adapter = adapterForProvider(model?.provider);
3083
3689
  if (!adapter) {
3084
3690
  const providerId = model?.provider ?? "none";
3085
3691
  transitionCurrentIdentity(`unsupported:${providerId}`, providerId);
@@ -3089,14 +3695,14 @@ function usageExtension(pi, dependencies = {}) {
3089
3695
  providerName: providerDisplayName(ctx, providerId),
3090
3696
  displayState: "current",
3091
3697
  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."
3698
+ message: model ? `Usage reporting is not supported for ${providerDisplayName(ctx, providerId)}.` : "No model is selected."
3093
3699
  }
3094
3700
  };
3095
3701
  }
3096
3702
  return queryAdapterState(ctx, adapter, "current", force, signal);
3097
3703
  };
3098
3704
  const refreshCurrentStatus = async (ctx, model, force) => {
3099
- const adapter = activeAdapterForProvider(model?.provider);
3705
+ const adapter = adapterForProvider(model?.provider);
3100
3706
  if (!adapter || !model) {
3101
3707
  const providerId = model?.provider ?? "none";
3102
3708
  transitionCurrentIdentity(`unsupported:${providerId}`, providerId);
@@ -3109,6 +3715,7 @@ function usageExtension(pi, dependencies = {}) {
3109
3715
  }
3110
3716
  statusGeneration += 1;
3111
3717
  const generation = statusGeneration;
3718
+ clearStatusCountdownTimer();
3112
3719
  statusController?.abort();
3113
3720
  const controller = new AbortController();
3114
3721
  statusController = controller;
@@ -3159,7 +3766,7 @@ function usageExtension(pi, dependencies = {}) {
3159
3766
  if (generation !== statusGeneration || modelIdentity(ctx.model) !== modelIdentity(model)) {
3160
3767
  return false;
3161
3768
  }
3162
- const adapter = activeAdapterForProvider(model?.provider);
3769
+ const adapter = adapterForProvider(model?.provider);
3163
3770
  if (outcome.authState === "unavailable") {
3164
3771
  if (!adapter) return false;
3165
3772
  try {
@@ -3218,7 +3825,7 @@ function usageExtension(pi, dependencies = {}) {
3218
3825
  const menuGeneration = statusGeneration;
3219
3826
  statusController?.abort();
3220
3827
  statusController = void 0;
3221
- clearStatusTimer();
3828
+ clearStatusTimers();
3222
3829
  const controller = new AbortController();
3223
3830
  activeControllers.add(controller);
3224
3831
  try {
@@ -3283,7 +3890,7 @@ function usageExtension(pi, dependencies = {}) {
3283
3890
  providers: () => ({
3284
3891
  kind: "actions",
3285
3892
  title: "Select a configured provider",
3286
- items: configuredAdapters(ctx, xaiUsageEnabled()).filter((adapter) => adapter.id !== ctx.model?.provider).map((adapter) => ({
3893
+ items: configuredAdapters(ctx).filter((adapter) => adapter.id !== ctx.model?.provider).map((adapter) => ({
3287
3894
  id: adapter.id,
3288
3895
  label: adapter.displayName,
3289
3896
  action: "provider"
@@ -3353,14 +3960,9 @@ function usageExtension(pi, dependencies = {}) {
3353
3960
  settingsRuntime,
3354
3961
  controller.signal,
3355
3962
  () => 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
- }
3963
+ (id) => {
3964
+ if (id === "codexStatusResetCountdown" && stableCurrent && statusGeneration === menuGeneration && !controller.signal.aborted) {
3965
+ publishStableCurrent(ctx, stableCurrent);
3364
3966
  }
3365
3967
  }
3366
3968
  );
@@ -3546,7 +4148,7 @@ function usageExtension(pi, dependencies = {}) {
3546
4148
  return { kind: "stay" };
3547
4149
  },
3548
4150
  another: async () => {
3549
- const others = configuredAdapters(ctx, xaiUsageEnabled()).filter(
4151
+ const others = configuredAdapters(ctx).filter(
3550
4152
  (adapter) => adapter.id !== ctx.model?.provider
3551
4153
  );
3552
4154
  if (others.length === 0) {
@@ -3556,7 +4158,7 @@ function usageExtension(pi, dependencies = {}) {
3556
4158
  return { kind: "to", screen: "providers" };
3557
4159
  },
3558
4160
  provider: async ({ itemId }) => {
3559
- const adapter = configuredAdapters(ctx, xaiUsageEnabled()).find(
4161
+ const adapter = configuredAdapters(ctx).find(
3560
4162
  (candidate) => candidate.id === itemId && candidate.id !== ctx.model?.provider
3561
4163
  );
3562
4164
  if (!adapter) return { kind: "back" };
@@ -3582,7 +4184,7 @@ function usageExtension(pi, dependencies = {}) {
3582
4184
  return { kind: "back" };
3583
4185
  },
3584
4186
  all: async () => {
3585
- const adapters = configuredAdapters(ctx, xaiUsageEnabled());
4187
+ const adapters = configuredAdapters(ctx);
3586
4188
  const currentProviderId = ctx.model?.provider;
3587
4189
  const settled = await runMenuOperation(
3588
4190
  ctx,
@@ -3660,16 +4262,21 @@ function usageExtension(pi, dependencies = {}) {
3660
4262
  }
3661
4263
  }
3662
4264
  });
3663
- pi.on("session_start", (_event, ctx) => {
4265
+ pi.on("session_start", async (_event, ctx) => {
3664
4266
  sessionGeneration += 1;
3665
- xaiSettingsGeneration += 1;
3666
4267
  statusGeneration += 1;
3667
- clearStatusTimer();
4268
+ clearStatusTimers();
3668
4269
  for (const controller of activeControllers) controller.abort();
3669
4270
  activeControllers.clear();
3670
4271
  statusController = void 0;
3671
4272
  sessionActive = true;
3672
- startStatusRefresh(ctx, ctx.model, false);
4273
+ const ownerGeneration = sessionGeneration;
4274
+ try {
4275
+ await fastRuntime.prepareSession(ctx);
4276
+ } catch (error) {
4277
+ if (isStaleExtensionContextError(error) || ownerGeneration !== sessionGeneration) return;
4278
+ throw error;
4279
+ }
3673
4280
  });
3674
4281
  pi.on("session_tree", (_event, ctx) => {
3675
4282
  startStatusRefresh(ctx, ctx.model, false);
@@ -3683,9 +4290,8 @@ function usageExtension(pi, dependencies = {}) {
3683
4290
  pi.on("session_shutdown", (_event, ctx) => {
3684
4291
  sessionActive = false;
3685
4292
  sessionGeneration += 1;
3686
- xaiSettingsGeneration += 1;
3687
4293
  statusGeneration += 1;
3688
- clearStatusTimer();
4294
+ clearStatusTimers();
3689
4295
  for (const controller of activeControllers) controller.abort();
3690
4296
  activeControllers.clear();
3691
4297
  statusController = void 0;
@@ -3698,7 +4304,8 @@ function usageExtension(pi, dependencies = {}) {
3698
4304
  fastRuntime = registerCodexFastMode(
3699
4305
  pi,
3700
4306
  settingsRuntime,
3701
- (ctx) => startStatusRefresh(ctx, ctx.model, false)
4307
+ (ctx) => startStatusRefresh(ctx, ctx.model, false),
4308
+ { registerSessionStart: false }
3702
4309
  );
3703
4310
  }
3704
4311
  export {
@@ -3730,6 +4337,9 @@ export {
3730
4337
  loadUsageSettings,
3731
4338
  normalizeCodexBackendPayload,
3732
4339
  normalizeCodexResetCreditsPayload,
4340
+ normalizeDeepSeekBalancePayload,
4341
+ normalizeFireworksAccountsPayload,
4342
+ normalizeFireworksBillingSummaryPayload,
3733
4343
  normalizeGitHubCopilotUsagePayload,
3734
4344
  normalizeKimiCodingUsagePayload,
3735
4345
  normalizeOpenCodeZenPayload,