@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/README.md +137 -64
- package/dist/index.ts +783 -173
- package/dist/index.ts.map +4 -4
- package/package.json +10 -10
- package/src/codex-fast-runtime.ts +34 -26
- package/src/format.ts +126 -7
- package/src/index.ts +9 -0
- package/src/providers/deepseek.ts +85 -0
- package/src/providers/fireworks.ts +198 -0
- package/src/query.ts +199 -13
- package/src/settings.ts +26 -6
- package/src/types.ts +19 -0
- package/src/usage-helpers.ts +2 -2
- package/src/usage-settings-ui.ts +130 -39
- package/src/usage.ts +156 -94
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 =
|
|
455
|
-
const premium =
|
|
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 =
|
|
497
|
-
const monthly =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
634
|
-
const 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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
1355
|
-
return
|
|
1630
|
+
function usageAdapters() {
|
|
1631
|
+
return [...SUPPORTED_ADAPTERS, XAI_ADAPTER];
|
|
1356
1632
|
}
|
|
1357
|
-
function adapterForProvider(providerId
|
|
1358
|
-
return usageAdapters(
|
|
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
|
-
|
|
1376
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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")
|
|
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
|
-
|
|
2273
|
-
|
|
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
|
-
|
|
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, "
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
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
|
-
`
|
|
3095
|
+
`Invalid pi-usage.json; using defaults without overwriting it. ${state.issue}`,
|
|
2603
3096
|
"warning"
|
|
2604
3097
|
);
|
|
2605
3098
|
}
|
|
2606
|
-
|
|
2607
|
-
}
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
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
|
|
2688
|
-
return usageAdapters(
|
|
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
|
-
|
|
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: "
|
|
2754
|
-
label: "
|
|
2755
|
-
description: "
|
|
2756
|
-
currentValue: state.
|
|
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
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
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
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
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
|
|
2827
|
-
|
|
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
|
|
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
|
-
|
|
3444
|
+
clearStatusTimers();
|
|
2884
3445
|
safeSetStatus(ctx, void 0);
|
|
2885
3446
|
};
|
|
2886
3447
|
const scheduleStatusRefresh = (ctx, model) => {
|
|
2887
|
-
|
|
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
|
-
|
|
2898
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2971
|
-
|
|
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}:${
|
|
3572
|
+
transitionCurrentIdentity(`${adapter.id}:${queryFingerprint}`, adapter.id);
|
|
2990
3573
|
}
|
|
2991
|
-
const cached = !force ? cache.get(adapter.id,
|
|
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}:${
|
|
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,
|
|
3024
|
-
const guard =
|
|
3025
|
-
if (signal.aborted ||
|
|
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,
|
|
3032
|
-
|
|
3613
|
+
Math.max(1, deadlineAt - Date.now()),
|
|
3614
|
+
`revalidating ${adapter.displayName} runtime auth`
|
|
3033
3615
|
);
|
|
3034
|
-
if (signal.aborted ||
|
|
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(
|
|
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,
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
3357
|
-
if (id
|
|
3358
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|