@narumitw/pi-usage 0.52.3 → 0.54.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 +111 -10
- package/dist/index.ts +1038 -43
- package/dist/index.ts.map +4 -4
- package/package.json +12 -4
- package/src/format.ts +117 -1
- package/src/index.ts +8 -0
- package/src/providers/kimi-coding.ts +276 -0
- package/src/providers/xai.ts +186 -0
- package/src/providers/zai.ts +150 -0
- package/src/query.ts +251 -3
- package/src/settings.ts +7 -0
- package/src/types.ts +28 -2
- package/src/usage-helpers.ts +3 -3
- package/src/usage-settings-ui.ts +128 -0
- package/src/usage.ts +127 -12
package/dist/index.ts
CHANGED
|
@@ -550,6 +550,219 @@ function asNonnegativeNumber(value) {
|
|
|
550
550
|
return number === void 0 || number < 0 ? void 0 : number;
|
|
551
551
|
}
|
|
552
552
|
|
|
553
|
+
// src/providers/kimi-coding.ts
|
|
554
|
+
var FIVE_HOUR_WINDOW_MINUTES = 300;
|
|
555
|
+
var DAILY_WINDOW_MINUTES = 1440;
|
|
556
|
+
var WEEKLY_WINDOW_MINUTES = 10080;
|
|
557
|
+
var FIXED_POINT_UNITS_PER_CENT = 1e6;
|
|
558
|
+
function normalizeKimiCodingUsagePayload(payload, capturedAt) {
|
|
559
|
+
const root = asObject3(payload);
|
|
560
|
+
if (!root) throw new Error("Kimi Coding usage response was not an object.");
|
|
561
|
+
const candidates = [];
|
|
562
|
+
let omittedWindow = false;
|
|
563
|
+
const summary = parseUsageRow(root.usage, WEEKLY_WINDOW_MINUTES, "Weekly window");
|
|
564
|
+
if (summary) candidates.push(summary);
|
|
565
|
+
else if (root.usage !== void 0) omittedWindow = true;
|
|
566
|
+
if (Array.isArray(root.limits)) {
|
|
567
|
+
for (const raw of root.limits) {
|
|
568
|
+
const item = asObject3(raw);
|
|
569
|
+
const windowMinutes = parseWindowMinutes(item?.window);
|
|
570
|
+
const label = sanitizedLabel(item?.name);
|
|
571
|
+
const bucket = windowMinutes === void 0 ? void 0 : parseUsageRow(item?.detail, windowMinutes, label ?? defaultWindowLabel(windowMinutes));
|
|
572
|
+
if (bucket) candidates.push(bucket);
|
|
573
|
+
else omittedWindow = true;
|
|
574
|
+
}
|
|
575
|
+
} else if (root.limits !== void 0) {
|
|
576
|
+
omittedWindow = true;
|
|
577
|
+
}
|
|
578
|
+
const buckets = [];
|
|
579
|
+
const byWindow = /* @__PURE__ */ new Map();
|
|
580
|
+
for (const bucket of candidates) {
|
|
581
|
+
const windowMinutes = bucket.windowMinutes;
|
|
582
|
+
byWindow.set(windowMinutes, [...byWindow.get(windowMinutes) ?? [], bucket]);
|
|
583
|
+
}
|
|
584
|
+
for (const rows of byWindow.values()) {
|
|
585
|
+
if (rows.length === 1) buckets.push(rows[0]);
|
|
586
|
+
else omittedWindow = true;
|
|
587
|
+
}
|
|
588
|
+
buckets.sort((left, right) => (left.windowMinutes ?? 0) - (right.windowMinutes ?? 0));
|
|
589
|
+
const metrics = parseBoosterWallet(root.boosterWallet);
|
|
590
|
+
if (buckets.length === 0 && metrics.length === 0) {
|
|
591
|
+
throw new Error("Kimi Coding usage endpoint returned no displayable usage data.");
|
|
592
|
+
}
|
|
593
|
+
return {
|
|
594
|
+
providerId: "kimi-coding",
|
|
595
|
+
providerName: "Kimi For Coding",
|
|
596
|
+
capturedAt,
|
|
597
|
+
source: "kimi-managed-usage",
|
|
598
|
+
semantics: { kind: "consumer-subscription", label: "Kimi Coding Plan usage" },
|
|
599
|
+
buckets,
|
|
600
|
+
metrics,
|
|
601
|
+
...omittedWindow ? { notes: ["Unsupported, malformed, or duplicate plan windows were unavailable."] } : {}
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function parseUsageRow(value, windowMinutes, label) {
|
|
605
|
+
const row = asObject3(value);
|
|
606
|
+
if (!row) return void 0;
|
|
607
|
+
const used = asNonnegativeInteger2(row.used);
|
|
608
|
+
const limit = asNonnegativeInteger2(row.limit);
|
|
609
|
+
if (used === void 0 || limit === void 0 || limit === 0) return void 0;
|
|
610
|
+
const resetsAt = asIsoEpochSeconds(row.resetTime);
|
|
611
|
+
return {
|
|
612
|
+
id: windowId(windowMinutes),
|
|
613
|
+
label,
|
|
614
|
+
used,
|
|
615
|
+
remaining: Math.max(0, limit - used),
|
|
616
|
+
limit,
|
|
617
|
+
unit: "count",
|
|
618
|
+
windowMinutes,
|
|
619
|
+
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
function parseWindowMinutes(value) {
|
|
623
|
+
const window = asObject3(value);
|
|
624
|
+
if (!window) return void 0;
|
|
625
|
+
const duration = asPositiveInteger(window.duration);
|
|
626
|
+
if (duration === void 0) return void 0;
|
|
627
|
+
const multiplier = window.timeUnit === "TIME_UNIT_MINUTE" ? 1 : window.timeUnit === "TIME_UNIT_HOUR" ? 60 : window.timeUnit === "TIME_UNIT_DAY" ? 1440 : window.timeUnit === "TIME_UNIT_WEEK" ? 10080 : void 0;
|
|
628
|
+
if (multiplier === void 0) return void 0;
|
|
629
|
+
const minutes = duration * multiplier;
|
|
630
|
+
return Number.isSafeInteger(minutes) ? minutes : void 0;
|
|
631
|
+
}
|
|
632
|
+
function parseBoosterWallet(value) {
|
|
633
|
+
const wallet = asObject3(value);
|
|
634
|
+
const balance = asObject3(wallet?.balance);
|
|
635
|
+
if (!wallet || !balance || balance.type !== "BOOSTER") return [];
|
|
636
|
+
const totalRaw = asPositiveInteger(balance.amount);
|
|
637
|
+
if (totalRaw === void 0) return [];
|
|
638
|
+
const leftRaw = asNonnegativeInteger2(balance.amountLeft) ?? 0;
|
|
639
|
+
const monthlyLimit = parseMoney(wallet.monthlyChargeLimit);
|
|
640
|
+
const monthlyUsed = parseMoney(wallet.monthlyUsed);
|
|
641
|
+
const currencies = new Set(
|
|
642
|
+
[monthlyLimit?.currency, monthlyUsed?.currency].filter(
|
|
643
|
+
(currency2) => currency2 !== void 0
|
|
644
|
+
)
|
|
645
|
+
);
|
|
646
|
+
if (currencies.size !== 1) return [];
|
|
647
|
+
const currency = currencies.values().next().value;
|
|
648
|
+
if (!currency) return [];
|
|
649
|
+
const total = fixedPointToMajor(totalRaw);
|
|
650
|
+
const left = fixedPointToMajor(leftRaw);
|
|
651
|
+
if (total === void 0 || left === void 0) return [];
|
|
652
|
+
const metrics = [
|
|
653
|
+
{ id: "booster-balance", label: "Balance", value: left, unit: "currency", currency },
|
|
654
|
+
{ id: "booster-total", label: "Total balance", value: total, unit: "currency", currency }
|
|
655
|
+
];
|
|
656
|
+
if (monthlyUsed) {
|
|
657
|
+
metrics.push({
|
|
658
|
+
id: "booster-monthly-used",
|
|
659
|
+
label: "Used this month",
|
|
660
|
+
value: monthlyUsed.cents / 100,
|
|
661
|
+
unit: "currency",
|
|
662
|
+
currency
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
if (wallet.monthlyChargeLimitEnabled === false) {
|
|
666
|
+
metrics.push({
|
|
667
|
+
id: "booster-monthly-limit",
|
|
668
|
+
label: "Monthly limit",
|
|
669
|
+
value: "unlimited",
|
|
670
|
+
unit: "currency",
|
|
671
|
+
currency
|
|
672
|
+
});
|
|
673
|
+
} else if (wallet.monthlyChargeLimitEnabled === true && monthlyLimit) {
|
|
674
|
+
metrics.push({
|
|
675
|
+
id: "booster-monthly-limit",
|
|
676
|
+
label: "Monthly limit",
|
|
677
|
+
value: monthlyLimit.cents / 100,
|
|
678
|
+
unit: "currency",
|
|
679
|
+
currency
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
return metrics;
|
|
683
|
+
}
|
|
684
|
+
function parseMoney(value) {
|
|
685
|
+
const money = asObject3(value);
|
|
686
|
+
if (!money) return void 0;
|
|
687
|
+
const cents = asNonnegativeInteger2(money.priceInCents);
|
|
688
|
+
if (cents === void 0) return void 0;
|
|
689
|
+
const currency = asCurrency(money.currency);
|
|
690
|
+
if (!currency) return void 0;
|
|
691
|
+
return { cents, currency };
|
|
692
|
+
}
|
|
693
|
+
function fixedPointToMajor(value) {
|
|
694
|
+
const cents = value / FIXED_POINT_UNITS_PER_CENT;
|
|
695
|
+
const roundedCents = cents > 0 && cents < 1 ? 1 : Math.round(cents);
|
|
696
|
+
const major = roundedCents / 100;
|
|
697
|
+
return Number.isSafeInteger(roundedCents) && Number.isFinite(major) ? major : void 0;
|
|
698
|
+
}
|
|
699
|
+
function asObject3(value) {
|
|
700
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
701
|
+
return value;
|
|
702
|
+
}
|
|
703
|
+
function asNonnegativeInteger2(value) {
|
|
704
|
+
if (typeof value === "string" && !/^\d+$/u.test(value)) return void 0;
|
|
705
|
+
const number = typeof value === "string" ? Number(value) : value;
|
|
706
|
+
if (typeof number !== "number" || !Number.isSafeInteger(number) || number < 0) return void 0;
|
|
707
|
+
return number;
|
|
708
|
+
}
|
|
709
|
+
function asPositiveInteger(value) {
|
|
710
|
+
const number = asNonnegativeInteger2(value);
|
|
711
|
+
return number !== void 0 && number > 0 ? number : void 0;
|
|
712
|
+
}
|
|
713
|
+
function asIsoEpochSeconds(value) {
|
|
714
|
+
if (typeof value !== "string") return void 0;
|
|
715
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|([+-])(\d{2}):(\d{2}))$/u.exec(
|
|
716
|
+
value
|
|
717
|
+
);
|
|
718
|
+
if (!match) return void 0;
|
|
719
|
+
const [
|
|
720
|
+
,
|
|
721
|
+
yearText,
|
|
722
|
+
monthText,
|
|
723
|
+
dayText,
|
|
724
|
+
hourText,
|
|
725
|
+
minuteText,
|
|
726
|
+
secondText,
|
|
727
|
+
,
|
|
728
|
+
offsetHour,
|
|
729
|
+
offsetMinute
|
|
730
|
+
] = match;
|
|
731
|
+
const year = Number(yearText);
|
|
732
|
+
const month = Number(monthText);
|
|
733
|
+
const day = Number(dayText);
|
|
734
|
+
const hour = Number(hourText);
|
|
735
|
+
const minute = Number(minuteText);
|
|
736
|
+
const second = Number(secondText);
|
|
737
|
+
if (month < 1 || month > 12 || day < 1 || day > new Date(Date.UTC(year, month, 0)).getUTCDate() || hour > 23 || minute > 59 || second > 59 || offsetHour !== void 0 && Number(offsetHour) > 23 || offsetMinute !== void 0 && Number(offsetMinute) > 59) {
|
|
738
|
+
return void 0;
|
|
739
|
+
}
|
|
740
|
+
const millis = Date.parse(value);
|
|
741
|
+
return Number.isFinite(millis) && millis >= 0 ? Math.floor(millis / 1e3) : void 0;
|
|
742
|
+
}
|
|
743
|
+
function sanitizedLabel(value) {
|
|
744
|
+
if (typeof value !== "string") return void 0;
|
|
745
|
+
return sanitizeDisplayText(value, 80) || void 0;
|
|
746
|
+
}
|
|
747
|
+
function asCurrency(value) {
|
|
748
|
+
if (typeof value !== "string") return void 0;
|
|
749
|
+
const currency = sanitizeDisplayText(value, 3).toUpperCase();
|
|
750
|
+
return /^[A-Z]{3}$/u.test(currency) ? currency : void 0;
|
|
751
|
+
}
|
|
752
|
+
function windowId(minutes) {
|
|
753
|
+
if (minutes === FIVE_HOUR_WINDOW_MINUTES) return "five-hour";
|
|
754
|
+
if (minutes === DAILY_WINDOW_MINUTES) return "daily";
|
|
755
|
+
if (minutes === WEEKLY_WINDOW_MINUTES) return "weekly";
|
|
756
|
+
return `window-${minutes}-minutes`;
|
|
757
|
+
}
|
|
758
|
+
function defaultWindowLabel(minutes) {
|
|
759
|
+
if (minutes === WEEKLY_WINDOW_MINUTES) return "Weekly window";
|
|
760
|
+
if (minutes % 10080 === 0) return `${minutes / 10080}w window`;
|
|
761
|
+
if (minutes % 1440 === 0) return `${minutes / 1440}d window`;
|
|
762
|
+
if (minutes % 60 === 0) return `${minutes / 60}h window`;
|
|
763
|
+
return `${minutes}m window`;
|
|
764
|
+
}
|
|
765
|
+
|
|
553
766
|
// src/providers/opencode-zen.ts
|
|
554
767
|
var ZEN_WINDOWS = [
|
|
555
768
|
{ key: "rolling", label: "Rolling" },
|
|
@@ -557,12 +770,12 @@ var ZEN_WINDOWS = [
|
|
|
557
770
|
{ key: "monthly", label: "Monthly" }
|
|
558
771
|
];
|
|
559
772
|
function normalizeOpenCodeZenPayload(payload, capturedAt) {
|
|
560
|
-
const usage =
|
|
773
|
+
const usage = asObject4(payload.usage);
|
|
561
774
|
if (!usage) throw new Error("OpenCode Zen usage response was not an object.");
|
|
562
775
|
const buckets = [];
|
|
563
776
|
const notes = [];
|
|
564
777
|
for (const window of ZEN_WINDOWS) {
|
|
565
|
-
const raw =
|
|
778
|
+
const raw = asObject4(usage[window.key]);
|
|
566
779
|
if (!raw) continue;
|
|
567
780
|
const status = asString3(raw.status);
|
|
568
781
|
if (status !== "ok" && status !== "rate-limited") {
|
|
@@ -599,7 +812,7 @@ function normalizeOpenCodeZenPayload(payload, capturedAt) {
|
|
|
599
812
|
...notes.length > 0 ? { notes } : {}
|
|
600
813
|
};
|
|
601
814
|
}
|
|
602
|
-
function
|
|
815
|
+
function asObject4(value) {
|
|
603
816
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
604
817
|
return value;
|
|
605
818
|
}
|
|
@@ -623,7 +836,7 @@ function clampPercent2(value) {
|
|
|
623
836
|
|
|
624
837
|
// src/providers/openrouter.ts
|
|
625
838
|
function normalizeOpenRouterKeyPayload(payload, capturedAt) {
|
|
626
|
-
const data =
|
|
839
|
+
const data = asObject5(payload.data);
|
|
627
840
|
if (!data) throw new Error("OpenRouter key response data was not an object.");
|
|
628
841
|
const limit = asNonnegativeNumber3(data.limit);
|
|
629
842
|
const remaining = asNonnegativeNumber3(data.limit_remaining);
|
|
@@ -669,7 +882,7 @@ function addUsageMetric(metrics, id, label, value) {
|
|
|
669
882
|
if (amount === void 0) return;
|
|
670
883
|
metrics.push({ id, label, value: amount, unit: "usd" });
|
|
671
884
|
}
|
|
672
|
-
function
|
|
885
|
+
function asObject5(value) {
|
|
673
886
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
674
887
|
return value;
|
|
675
888
|
}
|
|
@@ -682,11 +895,294 @@ function asNonnegativeNumber3(value) {
|
|
|
682
895
|
return value;
|
|
683
896
|
}
|
|
684
897
|
|
|
898
|
+
// src/providers/xai.ts
|
|
899
|
+
var MAX_SAFE_CENTS = Number.MAX_SAFE_INTEGER;
|
|
900
|
+
function normalizeXaiBillingPayload(payload, subscriptionTier, capturedAt) {
|
|
901
|
+
const configValue = payload.config;
|
|
902
|
+
if (configValue !== null && configValue !== void 0 && !isRecord2(configValue)) {
|
|
903
|
+
throw new Error("xAI billing response config was not an object or null.");
|
|
904
|
+
}
|
|
905
|
+
const config = isRecord2(configValue) ? configValue : void 0;
|
|
906
|
+
const buckets = [];
|
|
907
|
+
const metrics = [];
|
|
908
|
+
const notes = [];
|
|
909
|
+
if (config) {
|
|
910
|
+
const period = normalizePeriod(
|
|
911
|
+
config.currentPeriod,
|
|
912
|
+
config.billingPeriodStart,
|
|
913
|
+
config.billingPeriodEnd
|
|
914
|
+
);
|
|
915
|
+
const preferredPercent = optionalPercent(config.creditUsagePercent, "creditUsagePercent");
|
|
916
|
+
if (preferredPercent !== void 0) {
|
|
917
|
+
buckets.push({
|
|
918
|
+
id: "included-allowance",
|
|
919
|
+
label: "Included allowance",
|
|
920
|
+
used: preferredPercent,
|
|
921
|
+
remaining: 100 - preferredPercent,
|
|
922
|
+
unit: "percent",
|
|
923
|
+
...period
|
|
924
|
+
});
|
|
925
|
+
} else {
|
|
926
|
+
const limit = optionalUsd(config.monthlyLimit, "monthlyLimit");
|
|
927
|
+
const used = optionalUsd(config.used, "used");
|
|
928
|
+
if (limit !== void 0 || used !== void 0) {
|
|
929
|
+
buckets.push({
|
|
930
|
+
id: "included-allowance",
|
|
931
|
+
label: "Included allowance",
|
|
932
|
+
...limit !== void 0 ? { limit } : {},
|
|
933
|
+
...used !== void 0 ? { used } : {},
|
|
934
|
+
...limit !== void 0 && used !== void 0 ? { remaining: limit - used } : {},
|
|
935
|
+
unit: "usd",
|
|
936
|
+
...period
|
|
937
|
+
});
|
|
938
|
+
} else if (period.period || period.resetsAt !== void 0) {
|
|
939
|
+
buckets.push({
|
|
940
|
+
id: "included-allowance",
|
|
941
|
+
label: "Included allowance",
|
|
942
|
+
unit: "percent",
|
|
943
|
+
...period
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
const onDemandCap = optionalUsd(config.onDemandCap, "onDemandCap");
|
|
948
|
+
const onDemandUsed = optionalUsd(config.onDemandUsed, "onDemandUsed");
|
|
949
|
+
if (onDemandCap !== void 0 || onDemandUsed !== void 0) {
|
|
950
|
+
buckets.push({
|
|
951
|
+
id: "on-demand",
|
|
952
|
+
label: "On-demand usage",
|
|
953
|
+
...onDemandCap !== void 0 ? { limit: onDemandCap } : {},
|
|
954
|
+
...onDemandUsed !== void 0 ? { used: onDemandUsed } : {},
|
|
955
|
+
...onDemandCap !== void 0 && onDemandUsed !== void 0 ? { remaining: onDemandCap - onDemandUsed } : {},
|
|
956
|
+
unit: "usd"
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
const prepaidBalance = optionalUsd(config.prepaidBalance, "prepaidBalance");
|
|
960
|
+
if (prepaidBalance !== void 0) {
|
|
961
|
+
metrics.push({
|
|
962
|
+
id: "prepaid-balance",
|
|
963
|
+
label: "Prepaid balance",
|
|
964
|
+
value: prepaidBalance,
|
|
965
|
+
unit: "usd"
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
const tier = optionalTier(subscriptionTier);
|
|
970
|
+
if (tier) metrics.push({ id: "subscription-tier", label: "Plan tier", value: tier });
|
|
971
|
+
if (!config) notes.push("No xAI consumer billing configuration is available for this account.");
|
|
972
|
+
else if (buckets.length === 0 && metrics.length === 0) {
|
|
973
|
+
notes.push("The xAI consumer billing response contained no displayable usage fields.");
|
|
974
|
+
}
|
|
975
|
+
return {
|
|
976
|
+
providerId: "xai",
|
|
977
|
+
providerName: "xAI",
|
|
978
|
+
capturedAt,
|
|
979
|
+
source: "cli-chat-proxy.grok.com consumer billing",
|
|
980
|
+
semantics: {
|
|
981
|
+
kind: "consumer-subscription",
|
|
982
|
+
label: "xAI consumer subscription usage"
|
|
983
|
+
},
|
|
984
|
+
buckets,
|
|
985
|
+
metrics,
|
|
986
|
+
...notes.length > 0 ? { notes } : {}
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
function normalizePeriod(currentPeriod, legacyStart, legacyEnd) {
|
|
990
|
+
if (currentPeriod !== void 0 && currentPeriod !== null && !isRecord2(currentPeriod)) {
|
|
991
|
+
throw new Error("xAI billing currentPeriod was not an object or null.");
|
|
992
|
+
}
|
|
993
|
+
if (isRecord2(currentPeriod)) {
|
|
994
|
+
const type = optionalString(currentPeriod.type, "currentPeriod.type");
|
|
995
|
+
const start2 = optionalTimestamp(currentPeriod.start, "currentPeriod.start");
|
|
996
|
+
const end2 = optionalTimestamp(currentPeriod.end, "currentPeriod.end");
|
|
997
|
+
return {
|
|
998
|
+
...periodLabel(type, start2) ? { period: periodLabel(type, start2) } : {},
|
|
999
|
+
...end2 !== void 0 ? { resetsAt: end2 } : {}
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
const start = optionalTimestamp(legacyStart, "billingPeriodStart");
|
|
1003
|
+
const end = optionalTimestamp(legacyEnd, "billingPeriodEnd");
|
|
1004
|
+
return {
|
|
1005
|
+
...start !== void 0 ? { period: "Monthly" } : {},
|
|
1006
|
+
...end !== void 0 ? { resetsAt: end } : {}
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
function periodLabel(type, start) {
|
|
1010
|
+
if (type === "USAGE_PERIOD_TYPE_WEEKLY") return "Weekly";
|
|
1011
|
+
if (type === "USAGE_PERIOD_TYPE_MONTHLY") return "Monthly";
|
|
1012
|
+
if (type)
|
|
1013
|
+
return sanitizeDisplayText(type.replace(/^USAGE_PERIOD_TYPE_/u, "").replaceAll("_", " "), 40);
|
|
1014
|
+
return start === void 0 ? void 0 : "Current period";
|
|
1015
|
+
}
|
|
1016
|
+
function optionalUsd(value, field) {
|
|
1017
|
+
if (value === void 0 || value === null) return void 0;
|
|
1018
|
+
if (!isRecord2(value)) throw new Error(`xAI billing ${field} was not a cent wrapper.`);
|
|
1019
|
+
const cents = value.val === void 0 ? 0 : value.val;
|
|
1020
|
+
if (!Number.isSafeInteger(cents) || Math.abs(cents) > MAX_SAFE_CENTS) {
|
|
1021
|
+
throw new Error(`xAI billing ${field}.val was not a safe signed integer.`);
|
|
1022
|
+
}
|
|
1023
|
+
return cents / 100;
|
|
1024
|
+
}
|
|
1025
|
+
function optionalPercent(value, field) {
|
|
1026
|
+
if (value === void 0 || value === null) return void 0;
|
|
1027
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 100) {
|
|
1028
|
+
throw new Error(`xAI billing ${field} was outside 0\u2013100.`);
|
|
1029
|
+
}
|
|
1030
|
+
return value;
|
|
1031
|
+
}
|
|
1032
|
+
function optionalTimestamp(value, field) {
|
|
1033
|
+
if (value === void 0 || value === null) return void 0;
|
|
1034
|
+
if (typeof value !== "string" || value.length > 80) {
|
|
1035
|
+
throw new Error(`xAI billing ${field} was not a bounded timestamp.`);
|
|
1036
|
+
}
|
|
1037
|
+
const milliseconds = Date.parse(value);
|
|
1038
|
+
if (!Number.isFinite(milliseconds)) throw new Error(`xAI billing ${field} was invalid.`);
|
|
1039
|
+
return Math.floor(milliseconds / 1e3);
|
|
1040
|
+
}
|
|
1041
|
+
function optionalString(value, field) {
|
|
1042
|
+
if (value === void 0 || value === null) return void 0;
|
|
1043
|
+
if (typeof value !== "string" || value.length > 80) {
|
|
1044
|
+
throw new Error(`xAI billing ${field} was not a bounded string.`);
|
|
1045
|
+
}
|
|
1046
|
+
return value;
|
|
1047
|
+
}
|
|
1048
|
+
function optionalTier(value) {
|
|
1049
|
+
if (value === void 0 || value === null) return void 0;
|
|
1050
|
+
if (typeof value !== "string" || value.length > 160) {
|
|
1051
|
+
throw new Error("xAI subscription tier was not a bounded string or null.");
|
|
1052
|
+
}
|
|
1053
|
+
return sanitizeDisplayText(value, 80) || void 0;
|
|
1054
|
+
}
|
|
1055
|
+
function isRecord2(value) {
|
|
1056
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// src/providers/zai.ts
|
|
1060
|
+
var FIVE_HOUR_WINDOW_MINUTES2 = 300;
|
|
1061
|
+
var WEEKLY_WINDOW_MINUTES2 = 10080;
|
|
1062
|
+
function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt) {
|
|
1063
|
+
const data = asObject6(payload.data);
|
|
1064
|
+
if (!data) throw new Error("Z.AI quota response data was not an object.");
|
|
1065
|
+
const limits = Array.isArray(data.limits) ? data.limits : [];
|
|
1066
|
+
const buckets = [];
|
|
1067
|
+
const metrics = [];
|
|
1068
|
+
for (const raw of limits) {
|
|
1069
|
+
const limit = asObject6(raw);
|
|
1070
|
+
if (!limit) continue;
|
|
1071
|
+
const type = asString5(limit.type);
|
|
1072
|
+
const unit = asNonnegativeNumber4(limit.unit);
|
|
1073
|
+
const isPlanUsage = type === "TOKENS_LIMIT" || type === "CREDIT_LIMIT";
|
|
1074
|
+
if (type === "TIME_LIMIT") {
|
|
1075
|
+
addCountBucket(buckets, limit, "mcp-monthly", "MCP monthly allowance");
|
|
1076
|
+
addUsageDetailMetrics(metrics, limit.usageDetails);
|
|
1077
|
+
} else if (isPlanUsage && unit === 3) {
|
|
1078
|
+
addPercentBucket(buckets, limit, "five-hour", "5h window", FIVE_HOUR_WINDOW_MINUTES2);
|
|
1079
|
+
} else if (isPlanUsage && unit === 6) {
|
|
1080
|
+
const used = asNonnegativeNumber4(limit.currentValue);
|
|
1081
|
+
const quota = asNonnegativeNumber4(limit.usage);
|
|
1082
|
+
if (used !== void 0 && quota !== void 0) {
|
|
1083
|
+
addCountBucket(buckets, limit, "weekly", "Weekly window", WEEKLY_WINDOW_MINUTES2);
|
|
1084
|
+
} else {
|
|
1085
|
+
addPercentBucket(buckets, limit, "weekly", "Weekly window", WEEKLY_WINDOW_MINUTES2);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
if (buckets.length === 0) {
|
|
1090
|
+
throw new Error("Z.AI quota endpoint returned no displayable usage data.");
|
|
1091
|
+
}
|
|
1092
|
+
const notes = [];
|
|
1093
|
+
const level = asString5(data.level);
|
|
1094
|
+
if (level) notes.push(`Plan: ${level}`);
|
|
1095
|
+
return {
|
|
1096
|
+
providerId,
|
|
1097
|
+
providerName,
|
|
1098
|
+
capturedAt,
|
|
1099
|
+
source: "zai-quota",
|
|
1100
|
+
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
1101
|
+
buckets,
|
|
1102
|
+
metrics,
|
|
1103
|
+
...notes.length > 0 ? { notes } : {}
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
function addPercentBucket(buckets, limit, id, label, windowMinutes) {
|
|
1107
|
+
const used = asNonnegativeNumber4(limit.percentage);
|
|
1108
|
+
if (used === void 0) return;
|
|
1109
|
+
const percent = clampPercent3(used);
|
|
1110
|
+
const resetsAt = asEpochSeconds2(limit.nextResetTime);
|
|
1111
|
+
buckets.push({
|
|
1112
|
+
id,
|
|
1113
|
+
label,
|
|
1114
|
+
used: percent,
|
|
1115
|
+
remaining: 100 - percent,
|
|
1116
|
+
limit: 100,
|
|
1117
|
+
unit: "percent",
|
|
1118
|
+
windowMinutes,
|
|
1119
|
+
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
function addCountBucket(buckets, limit, id, label, windowMinutes) {
|
|
1123
|
+
const used = asNonnegativeNumber4(limit.currentValue);
|
|
1124
|
+
const quota = asNonnegativeNumber4(limit.usage);
|
|
1125
|
+
if (used === void 0 || quota === void 0) return;
|
|
1126
|
+
const resetsAt = asEpochSeconds2(limit.nextResetTime);
|
|
1127
|
+
buckets.push({
|
|
1128
|
+
id,
|
|
1129
|
+
label,
|
|
1130
|
+
used,
|
|
1131
|
+
remaining: Math.max(0, quota - used),
|
|
1132
|
+
limit: quota,
|
|
1133
|
+
unit: "count",
|
|
1134
|
+
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
1135
|
+
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
function addUsageDetailMetrics(metrics, value) {
|
|
1139
|
+
if (!Array.isArray(value)) return;
|
|
1140
|
+
for (const raw of value) {
|
|
1141
|
+
const detail = asObject6(raw);
|
|
1142
|
+
if (!detail) continue;
|
|
1143
|
+
const label = asString5(detail.modelCode);
|
|
1144
|
+
const usage = asNonnegativeNumber4(detail.usage);
|
|
1145
|
+
if (!label || usage === void 0) continue;
|
|
1146
|
+
metrics.push({ id: `mcp-${kebabCase(label)}`, label, value: usage, unit: "count" });
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
function asObject6(value) {
|
|
1150
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1151
|
+
return value;
|
|
1152
|
+
}
|
|
1153
|
+
function asString5(value) {
|
|
1154
|
+
if (typeof value !== "string") return void 0;
|
|
1155
|
+
return sanitizeDisplayText(value, 80) || void 0;
|
|
1156
|
+
}
|
|
1157
|
+
function asNonnegativeNumber4(value) {
|
|
1158
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
1159
|
+
return value;
|
|
1160
|
+
}
|
|
1161
|
+
function asEpochSeconds2(value) {
|
|
1162
|
+
const millis = asNonnegativeNumber4(value);
|
|
1163
|
+
if (millis === void 0) return void 0;
|
|
1164
|
+
return Math.floor(millis / 1e3);
|
|
1165
|
+
}
|
|
1166
|
+
function kebabCase(label) {
|
|
1167
|
+
return label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || "tool";
|
|
1168
|
+
}
|
|
1169
|
+
function clampPercent3(value) {
|
|
1170
|
+
return Math.min(100, Math.max(0, value));
|
|
1171
|
+
}
|
|
1172
|
+
|
|
685
1173
|
// src/query.ts
|
|
686
1174
|
var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
687
1175
|
var GITHUB_COPILOT_USAGE_URL = "https://api.github.com/copilot_internal/user";
|
|
688
1176
|
var OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
|
|
689
1177
|
var OPENCODE_GO_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
|
|
1178
|
+
var KIMI_CODING_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
|
|
1179
|
+
var XAI_USER_URL = "https://cli-chat-proxy.grok.com/v1/user?include=subscription";
|
|
1180
|
+
var XAI_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
|
|
1181
|
+
var XAI_CLIENT_HEADERS = Object.freeze({
|
|
1182
|
+
"X-XAI-Token-Auth": "xai-grok-cli",
|
|
1183
|
+
"x-grok-client-version": "1.0.10",
|
|
1184
|
+
"x-grok-client-mode": "interactive"
|
|
1185
|
+
});
|
|
690
1186
|
var MAX_SUCCESS_BODY_BYTES = 64 * 1024;
|
|
691
1187
|
var MAX_ERROR_BODY_BYTES = 4 * 1024;
|
|
692
1188
|
var AUTH_FINGERPRINT_SALT = randomBytes(32);
|
|
@@ -756,10 +1252,110 @@ var SUPPORTED_ADAPTERS = [
|
|
|
756
1252
|
);
|
|
757
1253
|
return normalizeOpenCodeZenPayload(payload, Date.now());
|
|
758
1254
|
}
|
|
1255
|
+
},
|
|
1256
|
+
{
|
|
1257
|
+
id: "kimi-coding",
|
|
1258
|
+
displayName: "Kimi For Coding",
|
|
1259
|
+
semantics: { kind: "consumer-subscription", label: "Kimi Coding Plan usage" },
|
|
1260
|
+
async query(auth, signal, timeoutMs) {
|
|
1261
|
+
const payload = await fetchProviderJson(
|
|
1262
|
+
KIMI_CODING_USAGE_URL,
|
|
1263
|
+
auth,
|
|
1264
|
+
signal,
|
|
1265
|
+
timeoutMs,
|
|
1266
|
+
"Kimi Coding usage endpoint",
|
|
1267
|
+
{ redirect: "error" }
|
|
1268
|
+
);
|
|
1269
|
+
return normalizeKimiCodingUsagePayload(payload, Date.now());
|
|
1270
|
+
}
|
|
1271
|
+
},
|
|
1272
|
+
{
|
|
1273
|
+
id: "zai",
|
|
1274
|
+
displayName: "Z.AI",
|
|
1275
|
+
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
1276
|
+
publishesStatusline: false,
|
|
1277
|
+
async query(auth, signal, timeoutMs) {
|
|
1278
|
+
const payload = await fetchProviderJson(
|
|
1279
|
+
zaiMonitorUrl(auth.model.baseUrl),
|
|
1280
|
+
zaiMonitorAuth(auth),
|
|
1281
|
+
signal,
|
|
1282
|
+
timeoutMs,
|
|
1283
|
+
"Z.AI quota endpoint"
|
|
1284
|
+
);
|
|
1285
|
+
return normalizeZaiQuotaPayload("zai", "Z.AI", payload, Date.now());
|
|
1286
|
+
}
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
id: "zai-coding-cn",
|
|
1290
|
+
displayName: "Z.AI Coding CN",
|
|
1291
|
+
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
1292
|
+
publishesStatusline: false,
|
|
1293
|
+
async query(auth, signal, timeoutMs) {
|
|
1294
|
+
const payload = await fetchProviderJson(
|
|
1295
|
+
zaiMonitorUrl(auth.model.baseUrl),
|
|
1296
|
+
zaiMonitorAuth(auth),
|
|
1297
|
+
signal,
|
|
1298
|
+
timeoutMs,
|
|
1299
|
+
"Z.AI Coding CN quota endpoint"
|
|
1300
|
+
);
|
|
1301
|
+
return normalizeZaiQuotaPayload(
|
|
1302
|
+
"zai-coding-cn",
|
|
1303
|
+
"Z.AI Coding CN",
|
|
1304
|
+
payload,
|
|
1305
|
+
Date.now()
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
759
1308
|
}
|
|
760
1309
|
];
|
|
761
|
-
|
|
762
|
-
|
|
1310
|
+
var XAI_ADAPTER = {
|
|
1311
|
+
id: "xai",
|
|
1312
|
+
displayName: "xAI",
|
|
1313
|
+
semantics: {
|
|
1314
|
+
kind: "consumer-subscription",
|
|
1315
|
+
label: "xAI consumer subscription usage"
|
|
1316
|
+
},
|
|
1317
|
+
publishesStatusline: false,
|
|
1318
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
1319
|
+
if (!guard) throw new Error("xAI usage requires request-boundary revalidation.");
|
|
1320
|
+
const startedAt = Date.now();
|
|
1321
|
+
const clientAuth = {
|
|
1322
|
+
...auth,
|
|
1323
|
+
headers: { ...auth.headers, ...XAI_CLIENT_HEADERS }
|
|
1324
|
+
};
|
|
1325
|
+
await guard();
|
|
1326
|
+
const userPayload = await fetchProviderJson(
|
|
1327
|
+
XAI_USER_URL,
|
|
1328
|
+
clientAuth,
|
|
1329
|
+
signal,
|
|
1330
|
+
remainingTimeout(timeoutMs, startedAt),
|
|
1331
|
+
"xAI consumer identity endpoint",
|
|
1332
|
+
{ redirect: "error", userAgent: false }
|
|
1333
|
+
);
|
|
1334
|
+
await guard();
|
|
1335
|
+
const userId = validatedXaiUserId(userPayload.userId);
|
|
1336
|
+
const billingAuth = {
|
|
1337
|
+
...clientAuth,
|
|
1338
|
+
headers: { ...clientAuth.headers, "x-userid": userId },
|
|
1339
|
+
secrets: [...clientAuth.secrets, userId]
|
|
1340
|
+
};
|
|
1341
|
+
await guard();
|
|
1342
|
+
const billingPayload = await fetchProviderJson(
|
|
1343
|
+
XAI_BILLING_URL,
|
|
1344
|
+
billingAuth,
|
|
1345
|
+
signal,
|
|
1346
|
+
remainingTimeout(timeoutMs, startedAt),
|
|
1347
|
+
"xAI consumer billing endpoint",
|
|
1348
|
+
{ redirect: "error", userAgent: false }
|
|
1349
|
+
);
|
|
1350
|
+
await guard();
|
|
1351
|
+
return normalizeXaiBillingPayload(billingPayload, userPayload.subscriptionTier, Date.now());
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
function usageAdapters(xaiUsage = true) {
|
|
1355
|
+
return xaiUsage ? [...SUPPORTED_ADAPTERS, XAI_ADAPTER] : SUPPORTED_ADAPTERS;
|
|
1356
|
+
}
|
|
1357
|
+
function adapterForProvider(providerId, xaiUsage = true) {
|
|
1358
|
+
return usageAdapters(xaiUsage).find((adapter) => adapter.id === providerId);
|
|
763
1359
|
}
|
|
764
1360
|
function isStaleExtensionContextError(error) {
|
|
765
1361
|
return error instanceof Error && error.message.includes("This extension ctx is stale after session replacement or reload");
|
|
@@ -805,6 +1401,11 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
|
|
|
805
1401
|
offered.offeredCount === 0
|
|
806
1402
|
);
|
|
807
1403
|
}
|
|
1404
|
+
if (adapter.id === "xai") {
|
|
1405
|
+
const offered = candidateReader ? candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
|
|
1406
|
+
if (!offered.ok) throw new Error("xAI OAuth credential discovery failed closed.");
|
|
1407
|
+
return resolveXaiUsageAuth(auth, model, salt, offered.candidates);
|
|
1408
|
+
}
|
|
808
1409
|
const authorization = authorizationFrom(auth);
|
|
809
1410
|
if (!authorization) return void 0;
|
|
810
1411
|
const headers = { Authorization: authorization };
|
|
@@ -819,9 +1420,9 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
|
|
|
819
1420
|
model
|
|
820
1421
|
};
|
|
821
1422
|
}
|
|
822
|
-
async function queryProviderUsage(adapter, auth, signal, timeoutMs) {
|
|
1423
|
+
async function queryProviderUsage(adapter, auth, signal, timeoutMs, guard) {
|
|
823
1424
|
try {
|
|
824
|
-
return await adapter.query(auth, signal, timeoutMs);
|
|
1425
|
+
return await adapter.query(auth, signal, timeoutMs, guard);
|
|
825
1426
|
} catch (error) {
|
|
826
1427
|
if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
|
|
827
1428
|
throw new Error(redactUsageError(errorMessage(error), auth.secrets));
|
|
@@ -861,7 +1462,9 @@ async function fetchProviderJson(url, auth, signal, timeoutMs, description, requ
|
|
|
861
1462
|
}, timeoutMs);
|
|
862
1463
|
try {
|
|
863
1464
|
const headers = { ...auth.headers };
|
|
864
|
-
if (!hasHeader(headers, "User-Agent"))
|
|
1465
|
+
if (request.userAgent !== false && !hasHeader(headers, "User-Agent")) {
|
|
1466
|
+
headers["User-Agent"] = "pi-usage";
|
|
1467
|
+
}
|
|
865
1468
|
if (request.body && !hasHeader(headers, "Content-Type")) {
|
|
866
1469
|
headers["Content-Type"] = "application/json";
|
|
867
1470
|
}
|
|
@@ -869,15 +1472,18 @@ async function fetchProviderJson(url, auth, signal, timeoutMs, description, requ
|
|
|
869
1472
|
method: request.method ?? "GET",
|
|
870
1473
|
headers,
|
|
871
1474
|
...request.body ? { body: JSON.stringify(request.body) } : {},
|
|
1475
|
+
...request.redirect ? { redirect: request.redirect } : {},
|
|
872
1476
|
signal: controller.signal
|
|
873
1477
|
});
|
|
1478
|
+
if (response.redirected) throw new Error(`${description} refused a redirected response.`);
|
|
874
1479
|
if (controller.signal.aborted)
|
|
875
1480
|
throw Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
876
1481
|
const text = await readBoundedResponse(
|
|
877
1482
|
response,
|
|
878
1483
|
response.ok ? MAX_SUCCESS_BODY_BYTES : MAX_ERROR_BODY_BYTES,
|
|
879
1484
|
!response.ok,
|
|
880
|
-
description
|
|
1485
|
+
description,
|
|
1486
|
+
controller.signal
|
|
881
1487
|
);
|
|
882
1488
|
if (controller.signal.aborted)
|
|
883
1489
|
throw Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
@@ -908,12 +1514,15 @@ async function fetchProviderJson(url, auth, signal, timeoutMs, description, requ
|
|
|
908
1514
|
signal.removeEventListener("abort", abortFromCaller);
|
|
909
1515
|
}
|
|
910
1516
|
}
|
|
911
|
-
async function readBoundedResponse(response, maxBytes, truncateOverflow, description) {
|
|
1517
|
+
async function readBoundedResponse(response, maxBytes, truncateOverflow, description, signal) {
|
|
912
1518
|
if (!response.body) return "";
|
|
913
1519
|
const reader = response.body.getReader();
|
|
914
1520
|
const chunks = [];
|
|
915
1521
|
let total = 0;
|
|
916
1522
|
let truncated = false;
|
|
1523
|
+
const abort = () => void reader.cancel().catch(() => void 0);
|
|
1524
|
+
if (signal.aborted) abort();
|
|
1525
|
+
else signal.addEventListener("abort", abort, { once: true });
|
|
917
1526
|
try {
|
|
918
1527
|
while (true) {
|
|
919
1528
|
const { done, value } = await reader.read();
|
|
@@ -930,6 +1539,7 @@ async function readBoundedResponse(response, maxBytes, truncateOverflow, descrip
|
|
|
930
1539
|
total += value.byteLength;
|
|
931
1540
|
}
|
|
932
1541
|
} finally {
|
|
1542
|
+
signal.removeEventListener("abort", abort);
|
|
933
1543
|
reader.releaseLock();
|
|
934
1544
|
}
|
|
935
1545
|
if (truncated && !truncateOverflow) {
|
|
@@ -944,6 +1554,59 @@ async function readBoundedResponse(response, maxBytes, truncateOverflow, descrip
|
|
|
944
1554
|
const text = new TextDecoder().decode(body);
|
|
945
1555
|
return truncated ? `${text}\u2026` : text;
|
|
946
1556
|
}
|
|
1557
|
+
function resolveXaiUsageAuth(auth, model, salt, candidates) {
|
|
1558
|
+
const resolvedAccess = bearerToken(headerValue(auth.headers, "Authorization")) ?? auth.apiKey;
|
|
1559
|
+
if (!resolvedAccess) throw new Error("xAI runtime authentication was incomplete.");
|
|
1560
|
+
let sawOAuth = false;
|
|
1561
|
+
let sawMatchingAccess = false;
|
|
1562
|
+
let sawIncompleteMatch = false;
|
|
1563
|
+
const matches = [];
|
|
1564
|
+
for (const candidate of candidates) {
|
|
1565
|
+
try {
|
|
1566
|
+
const credential = asObject7(candidate);
|
|
1567
|
+
if (credential?.type !== "oauth") continue;
|
|
1568
|
+
sawOAuth = true;
|
|
1569
|
+
if (credential.access !== resolvedAccess) continue;
|
|
1570
|
+
sawMatchingAccess = true;
|
|
1571
|
+
if (typeof credential.access !== "string" || !credential.access || typeof credential.refresh !== "string" || !credential.refresh || typeof credential.expires !== "number" || !Number.isFinite(credential.expires)) {
|
|
1572
|
+
sawIncompleteMatch = true;
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1575
|
+
matches.push({ access: credential.access, refresh: credential.refresh });
|
|
1576
|
+
} catch {
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
if (sawIncompleteMatch) throw new Error("The matching xAI OAuth credential was incomplete.");
|
|
1580
|
+
if (matches.length > 1) {
|
|
1581
|
+
throw new Error("Multiple OAuth credentials match the active xAI runtime account.");
|
|
1582
|
+
}
|
|
1583
|
+
const match = matches[0];
|
|
1584
|
+
if (!match) {
|
|
1585
|
+
if (!sawOAuth) {
|
|
1586
|
+
throw new Error(
|
|
1587
|
+
"xAI consumer usage requires the OAuth subscription account configured through Pi /login; XAI_API_KEY users can review API spend at console.x.ai."
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
if (sawMatchingAccess) throw new Error("The matching xAI OAuth credential was incomplete.");
|
|
1591
|
+
throw new Error("The active xAI runtime account does not match Pi's stored OAuth account.");
|
|
1592
|
+
}
|
|
1593
|
+
const authorization = `Bearer ${match.access}`;
|
|
1594
|
+
const headers = { Authorization: authorization };
|
|
1595
|
+
return {
|
|
1596
|
+
apiKey: match.access,
|
|
1597
|
+
headers,
|
|
1598
|
+
fingerprint: fingerprintResolvedAuth({ headers }, salt),
|
|
1599
|
+
secrets: [
|
|
1600
|
+
match.access,
|
|
1601
|
+
match.refresh,
|
|
1602
|
+
resolvedAccess,
|
|
1603
|
+
auth.apiKey,
|
|
1604
|
+
headerValue(auth.headers, "Authorization"),
|
|
1605
|
+
authorization
|
|
1606
|
+
].filter((value) => Boolean(value)),
|
|
1607
|
+
model
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
947
1610
|
function resolveGitHubCopilotUsageAuth(auth, model, salt, candidates, standaloneFallback) {
|
|
948
1611
|
const resolvedAccess = bearerToken(headerValue(auth.headers, "Authorization")) ?? auth.apiKey;
|
|
949
1612
|
if (!resolvedAccess) throw new Error("GitHub Copilot OAuth credentials were incomplete.");
|
|
@@ -954,7 +1617,7 @@ function resolveGitHubCopilotUsageAuth(auth, model, salt, candidates, standalone
|
|
|
954
1617
|
const matches = /* @__PURE__ */ new Map();
|
|
955
1618
|
for (const candidate of candidates) {
|
|
956
1619
|
try {
|
|
957
|
-
const credential =
|
|
1620
|
+
const credential = asObject7(candidate);
|
|
958
1621
|
if (credential?.type !== "oauth") continue;
|
|
959
1622
|
sawOAuth = true;
|
|
960
1623
|
const storedAccess2 = typeof credential.access === "string" && credential.access ? credential.access : void 0;
|
|
@@ -1016,7 +1679,7 @@ function bearerToken(authorization) {
|
|
|
1016
1679
|
const match = /^Bearer\s+(.+)$/iu.exec(authorization ?? "");
|
|
1017
1680
|
return match?.[1];
|
|
1018
1681
|
}
|
|
1019
|
-
function
|
|
1682
|
+
function asObject7(value) {
|
|
1020
1683
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1021
1684
|
return value;
|
|
1022
1685
|
}
|
|
@@ -1037,6 +1700,10 @@ function hasOfficialUrlOrigin(value, providerId) {
|
|
|
1037
1700
|
if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
|
|
1038
1701
|
if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
|
|
1039
1702
|
if (providerId === "opencode-go") return url.origin === "https://opencode.ai";
|
|
1703
|
+
if (providerId === "kimi-coding") return url.origin === "https://api.kimi.com";
|
|
1704
|
+
if (providerId === "xai") return url.origin === "https://api.x.ai";
|
|
1705
|
+
if (providerId === "zai") return url.origin === "https://api.z.ai";
|
|
1706
|
+
if (providerId === "zai-coding-cn") return url.origin === "https://open.bigmodel.cn";
|
|
1040
1707
|
if (providerId === "github-copilot") {
|
|
1041
1708
|
return url.protocol === "https:" && /^api\.[a-z0-9-]+\.githubcopilot\.com$/u.test(url.hostname);
|
|
1042
1709
|
}
|
|
@@ -1054,6 +1721,28 @@ function headerValue(headers, name) {
|
|
|
1054
1721
|
function hasHeader(headers, name) {
|
|
1055
1722
|
return Object.keys(headers).some((key) => key.toLowerCase() === name.toLowerCase());
|
|
1056
1723
|
}
|
|
1724
|
+
function validatedXaiUserId(value) {
|
|
1725
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9._~-]{1,128}$/u.test(value)) {
|
|
1726
|
+
throw new Error("xAI consumer identity returned an unsafe canonical user ID.");
|
|
1727
|
+
}
|
|
1728
|
+
return value;
|
|
1729
|
+
}
|
|
1730
|
+
function remainingTimeout(timeoutMs, startedAt) {
|
|
1731
|
+
const remaining = timeoutMs - (Date.now() - startedAt);
|
|
1732
|
+
if (remaining <= 0) throw new Error("Timed out while fetching xAI consumer usage.");
|
|
1733
|
+
return remaining;
|
|
1734
|
+
}
|
|
1735
|
+
function zaiMonitorUrl(baseUrl) {
|
|
1736
|
+
const base = baseUrl?.trim();
|
|
1737
|
+
if (!base) throw new Error("Z.AI model base URL is unavailable.");
|
|
1738
|
+
return `${new URL(base).origin}/api/monitor/usage/quota/limit`;
|
|
1739
|
+
}
|
|
1740
|
+
function zaiMonitorAuth(auth) {
|
|
1741
|
+
const authorization = headerValue(auth.headers, "Authorization");
|
|
1742
|
+
const token = authorization === void 0 ? void 0 : bearerToken(authorization) ?? authorization;
|
|
1743
|
+
if (token === void 0 || token === authorization) return auth;
|
|
1744
|
+
return { ...auth, headers: { ...auth.headers, Authorization: token } };
|
|
1745
|
+
}
|
|
1057
1746
|
function isAbortError(error) {
|
|
1058
1747
|
return error instanceof Error && error.name === "AbortError";
|
|
1059
1748
|
}
|
|
@@ -1196,7 +1885,7 @@ function normalizeCodexResetCreditsPayload(payload) {
|
|
|
1196
1885
|
if (rawCredits !== void 0 && !Array.isArray(rawCredits)) {
|
|
1197
1886
|
throw new Error("Codex reset credits response returned invalid credits.");
|
|
1198
1887
|
}
|
|
1199
|
-
const options = (rawCredits ?? []).map(
|
|
1888
|
+
const options = (rawCredits ?? []).map(asObject8).filter((credit) => Boolean(credit)).filter((credit) => credit.status === "available" && credit.reset_type === "codex_rate_limits").map(normalizeResetOption).sort(
|
|
1200
1889
|
(left, right) => (left.expiresAt ?? Number.MAX_SAFE_INTEGER) - (right.expiresAt ?? Number.MAX_SAFE_INTEGER)
|
|
1201
1890
|
).slice(0, Math.min(availableCount, MAX_RESET_OPTIONS));
|
|
1202
1891
|
if (availableCount > 0 && options.length === 0) {
|
|
@@ -1211,7 +1900,7 @@ function selectCodexResetCredential(candidates, resolvedAccess, resolvedAccountI
|
|
|
1211
1900
|
const matches = /* @__PURE__ */ new Map();
|
|
1212
1901
|
for (const candidate of candidates) {
|
|
1213
1902
|
try {
|
|
1214
|
-
const credential =
|
|
1903
|
+
const credential = asObject8(candidate);
|
|
1215
1904
|
if (credential?.type !== "oauth") continue;
|
|
1216
1905
|
sawOAuth = true;
|
|
1217
1906
|
const storedAccess = asNonemptyString(credential.access);
|
|
@@ -1250,7 +1939,7 @@ function codexAccountIdFromAccessToken(access) {
|
|
|
1250
1939
|
const parts = access.split(".");
|
|
1251
1940
|
if (parts.length !== 3 || !parts[1]) return void 0;
|
|
1252
1941
|
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
1253
|
-
const claims =
|
|
1942
|
+
const claims = asObject8(asObject8(payload)?.["https://api.openai.com/auth"]);
|
|
1254
1943
|
return validHeaderValue(claims?.chatgpt_account_id);
|
|
1255
1944
|
} catch {
|
|
1256
1945
|
return void 0;
|
|
@@ -1282,7 +1971,7 @@ function normalizeResetOption(credit) {
|
|
|
1282
1971
|
function isCodexResetOutcomeCode(value) {
|
|
1283
1972
|
return value === "reset" || value === "nothing_to_reset" || value === "no_credit" || value === "already_redeemed";
|
|
1284
1973
|
}
|
|
1285
|
-
function
|
|
1974
|
+
function asObject8(value) {
|
|
1286
1975
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1287
1976
|
return value;
|
|
1288
1977
|
}
|
|
@@ -1330,7 +2019,11 @@ function formatUsageReport(report, displayState) {
|
|
|
1330
2019
|
else if (report.providerId === "github-copilot") formatGitHubCopilotReport(lines, report);
|
|
1331
2020
|
else if (report.providerId === "openrouter") formatOpenRouterReport(lines, report);
|
|
1332
2021
|
else if (report.providerId === "opencode-go") formatOpenCodeZenReport(lines, report);
|
|
1333
|
-
else
|
|
2022
|
+
else if (report.providerId === "kimi-coding") formatKimiCodingReport(lines, report);
|
|
2023
|
+
else if (report.providerId === "xai") formatXaiReport(lines, report);
|
|
2024
|
+
else if (report.providerId === "zai" || report.providerId === "zai-coding-cn") {
|
|
2025
|
+
formatZaiReport(lines, report);
|
|
2026
|
+
} else formatGenericReport(lines, report);
|
|
1334
2027
|
if (report.notes) {
|
|
1335
2028
|
for (const note of report.notes) lines.push(note);
|
|
1336
2029
|
}
|
|
@@ -1346,6 +2039,7 @@ function formatUsageStatusline(report, model) {
|
|
|
1346
2039
|
if (typeof total?.value === "number") return `openrouter ${formatUsd(total.value)} used`;
|
|
1347
2040
|
}
|
|
1348
2041
|
if (report.providerId === "opencode-go") return formatOpenCodeZenStatusline(report);
|
|
2042
|
+
if (report.providerId === "kimi-coding") return formatKimiCodingStatusline(report);
|
|
1349
2043
|
return void 0;
|
|
1350
2044
|
}
|
|
1351
2045
|
function formatProviderStates(states) {
|
|
@@ -1417,7 +2111,7 @@ function compactGitHubCopilotQuotaKind(bucket) {
|
|
|
1417
2111
|
}
|
|
1418
2112
|
function percentRemaining(bucket) {
|
|
1419
2113
|
if (!bucket.limit || bucket.remaining === void 0) return 0;
|
|
1420
|
-
return Math.round(
|
|
2114
|
+
return Math.round(clampPercent4(bucket.remaining / bucket.limit * 100));
|
|
1421
2115
|
}
|
|
1422
2116
|
function formatOpenRouterReport(lines, report) {
|
|
1423
2117
|
const limit = report.buckets.find((bucket) => bucket.id === "key-limit");
|
|
@@ -1444,10 +2138,114 @@ function formatOpenCodeZenStatusline(report) {
|
|
|
1444
2138
|
for (const bucket of report.buckets) {
|
|
1445
2139
|
if (bucket.used === void 0) continue;
|
|
1446
2140
|
const compact = bucket.id === "rolling" ? "r" : bucket.id === "weekly" ? "w" : "m";
|
|
1447
|
-
parts.push(`${
|
|
2141
|
+
parts.push(`${clampPercent4(bucket.used).toFixed(0)}% ${compact}`);
|
|
2142
|
+
}
|
|
2143
|
+
return parts.length > 1 ? parts.join(" ") : void 0;
|
|
2144
|
+
}
|
|
2145
|
+
function formatKimiCodingReport(lines, report) {
|
|
2146
|
+
for (const bucket of report.buckets) {
|
|
2147
|
+
const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
|
|
2148
|
+
if (bucket.used === void 0 || bucket.limit === void 0) {
|
|
2149
|
+
lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}unavailable${reset}`);
|
|
2150
|
+
continue;
|
|
2151
|
+
}
|
|
2152
|
+
lines.push(
|
|
2153
|
+
`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${bucket.used} of ${bucket.limit} used \xB7 ${percentRemaining(bucket)}% left${reset}`
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
const balance = report.metrics.find((metric) => metric.id === "booster-balance");
|
|
2157
|
+
const total = report.metrics.find((metric) => metric.id === "booster-total");
|
|
2158
|
+
const monthlyUsed = report.metrics.find((metric) => metric.id === "booster-monthly-used");
|
|
2159
|
+
const monthlyLimit = report.metrics.find((metric) => metric.id === "booster-monthly-limit");
|
|
2160
|
+
if (!balance && !monthlyUsed && !monthlyLimit) return;
|
|
2161
|
+
lines.push("", "Extra usage wallet:");
|
|
2162
|
+
if (balance) {
|
|
2163
|
+
const totalSuffix = total ? ` of ${formatCurrencyMetric(total)}` : "";
|
|
2164
|
+
lines.push(`${"Balance:".padEnd(VALUE_COLUMN)}${formatCurrencyMetric(balance)}${totalSuffix}`);
|
|
2165
|
+
}
|
|
2166
|
+
if (monthlyUsed) {
|
|
2167
|
+
lines.push(`${"Used this month:".padEnd(VALUE_COLUMN)}${formatCurrencyMetric(monthlyUsed)}`);
|
|
2168
|
+
}
|
|
2169
|
+
if (monthlyLimit) {
|
|
2170
|
+
lines.push(`${"Monthly limit:".padEnd(VALUE_COLUMN)}${formatCurrencyMetric(monthlyLimit)}`);
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
function formatKimiCodingStatusline(report) {
|
|
2174
|
+
const fiveHour = report.buckets.find((bucket) => bucket.id === "five-hour");
|
|
2175
|
+
const weekly = report.buckets.find((bucket) => bucket.id === "weekly");
|
|
2176
|
+
const subWindow = fiveHour ?? report.buckets.find((bucket) => bucket.id !== "weekly");
|
|
2177
|
+
const selected = [subWindow, weekly].filter(
|
|
2178
|
+
(bucket, index, buckets) => bucket !== void 0 && buckets.indexOf(bucket) === index
|
|
2179
|
+
);
|
|
2180
|
+
const parts = ["kimi"];
|
|
2181
|
+
for (const bucket of selected) {
|
|
2182
|
+
if (!bucket.limit || bucket.remaining === void 0) continue;
|
|
2183
|
+
const fallback = bucket.id === "weekly" ? "weekly" : "5h";
|
|
2184
|
+
parts.push(
|
|
2185
|
+
`${percentRemaining(bucket)}% ${formatWindowLabel(bucket.windowMinutes, fallback, true)}`
|
|
2186
|
+
);
|
|
1448
2187
|
}
|
|
1449
2188
|
return parts.length > 1 ? parts.join(" ") : void 0;
|
|
1450
2189
|
}
|
|
2190
|
+
function formatCurrencyMetric(metric) {
|
|
2191
|
+
if (typeof metric.value !== "number") return String(metric.value);
|
|
2192
|
+
if (!metric.currency) return "unavailable";
|
|
2193
|
+
if (metric.currency === "USD") return `$${metric.value.toFixed(2)}`;
|
|
2194
|
+
if (metric.currency === "CNY") return `\xA5${metric.value.toFixed(2)}`;
|
|
2195
|
+
return `${metric.value.toFixed(2)} ${metric.currency}`;
|
|
2196
|
+
}
|
|
2197
|
+
function formatXaiReport(lines, report) {
|
|
2198
|
+
const included = report.buckets.find((bucket) => bucket.id === "included-allowance");
|
|
2199
|
+
if (included) {
|
|
2200
|
+
let value = "unavailable";
|
|
2201
|
+
if (included.unit === "percent" && included.used !== void 0) {
|
|
2202
|
+
value = `${included.used}% used`;
|
|
2203
|
+
if (included.remaining !== void 0) value += ` \xB7 ${included.remaining}% left`;
|
|
2204
|
+
} else if (included.used !== void 0) {
|
|
2205
|
+
value = `${formatUsd(included.used)} used`;
|
|
2206
|
+
if (included.limit !== void 0) value += ` of ${formatUsd(included.limit)}`;
|
|
2207
|
+
} else if (included.limit !== void 0) {
|
|
2208
|
+
value = `usage unavailable \xB7 ${formatUsd(included.limit)} limit`;
|
|
2209
|
+
}
|
|
2210
|
+
const period = included.period ? ` \xB7 ${included.period}` : "";
|
|
2211
|
+
const reset = included.resetsAt ? ` (resets ${formatReset(included.resetsAt)})` : "";
|
|
2212
|
+
lines.push(`${"Included allowance:".padEnd(VALUE_COLUMN)}${value}${period}${reset}`);
|
|
2213
|
+
}
|
|
2214
|
+
const onDemand = report.buckets.find((bucket) => bucket.id === "on-demand");
|
|
2215
|
+
if (onDemand) {
|
|
2216
|
+
let value = onDemand.used === void 0 ? "usage unavailable" : `${formatUsd(onDemand.used)} used`;
|
|
2217
|
+
if (onDemand.limit !== void 0) value += ` of ${formatUsd(onDemand.limit)} cap`;
|
|
2218
|
+
lines.push(`${"On-demand usage:".padEnd(VALUE_COLUMN)}${value}`);
|
|
2219
|
+
}
|
|
2220
|
+
for (const metric of report.metrics) {
|
|
2221
|
+
lines.push(
|
|
2222
|
+
`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(metric.value, metric.unit)}`
|
|
2223
|
+
);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
function formatZaiReport(lines, report) {
|
|
2227
|
+
for (const bucket of report.buckets) {
|
|
2228
|
+
const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
|
|
2229
|
+
let value = "unavailable";
|
|
2230
|
+
if (bucket.unit === "percent" && bucket.used !== void 0) {
|
|
2231
|
+
value = `${bucket.used}% used`;
|
|
2232
|
+
if (bucket.remaining !== void 0) value += ` \xB7 ${bucket.remaining}% left`;
|
|
2233
|
+
} else if (bucket.used !== void 0 && bucket.limit !== void 0) {
|
|
2234
|
+
value = `${bucket.used} of ${bucket.limit} used`;
|
|
2235
|
+
if (bucket.remaining !== void 0) value += ` \xB7 ${bucket.remaining} left`;
|
|
2236
|
+
} else if (bucket.used !== void 0) {
|
|
2237
|
+
value = `${bucket.used} used`;
|
|
2238
|
+
} else if (bucket.remaining !== void 0) {
|
|
2239
|
+
value = `${bucket.remaining} left`;
|
|
2240
|
+
}
|
|
2241
|
+
lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${value}${reset}`);
|
|
2242
|
+
}
|
|
2243
|
+
for (const metric of report.metrics) {
|
|
2244
|
+
lines.push(
|
|
2245
|
+
`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(metric.value, metric.unit)}`
|
|
2246
|
+
);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
1451
2249
|
function formatGenericReport(lines, report) {
|
|
1452
2250
|
for (const bucket of report.buckets) {
|
|
1453
2251
|
lines.push(
|
|
@@ -1472,7 +2270,7 @@ function formatCodexStatusline(report, model) {
|
|
|
1472
2270
|
if (bucket.remaining === void 0) continue;
|
|
1473
2271
|
const fallback = bucket.id.endsWith(":secondary") ? "weekly" : "5h";
|
|
1474
2272
|
parts.push(
|
|
1475
|
-
`${
|
|
2273
|
+
`${clampPercent4(bucket.remaining).toFixed(0)}% ${formatWindowLabel(bucket.windowMinutes, fallback, true)}`
|
|
1476
2274
|
);
|
|
1477
2275
|
}
|
|
1478
2276
|
return parts.length > 1 ? parts.join(" ") : formatCodexCreditsStatus(report);
|
|
@@ -1539,7 +2337,7 @@ function compactLimitLabel(label) {
|
|
|
1539
2337
|
return (suffix || normalized).toLowerCase().replace(/\s+/g, " ");
|
|
1540
2338
|
}
|
|
1541
2339
|
function formatPercentBucket(bucket) {
|
|
1542
|
-
const remaining =
|
|
2340
|
+
const remaining = clampPercent4(bucket.remaining ?? 0);
|
|
1543
2341
|
const filled = Math.round(remaining / 100 * BAR_SEGMENTS);
|
|
1544
2342
|
const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
|
|
1545
2343
|
return `[${"\u2588".repeat(filled)}${"\u2591".repeat(BAR_SEGMENTS - filled)}] ${remaining.toFixed(0)}% left${reset}`;
|
|
@@ -1572,7 +2370,7 @@ function formatReset(epochSeconds) {
|
|
|
1572
2370
|
function capitalize(value) {
|
|
1573
2371
|
return `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}`;
|
|
1574
2372
|
}
|
|
1575
|
-
function
|
|
2373
|
+
function clampPercent4(value) {
|
|
1576
2374
|
return Math.min(100, Math.max(0, value));
|
|
1577
2375
|
}
|
|
1578
2376
|
|
|
@@ -1585,18 +2383,23 @@ import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
|
1585
2383
|
var USAGE_SETTINGS_FILE = "pi-usage.json";
|
|
1586
2384
|
var MAX_USAGE_SETTINGS_BYTES = 64 * 1024;
|
|
1587
2385
|
var DEFAULT_USAGE_SETTINGS = Object.freeze({
|
|
1588
|
-
codexFastMode: false
|
|
2386
|
+
codexFastMode: false,
|
|
2387
|
+
xaiUsage: true
|
|
1589
2388
|
});
|
|
1590
2389
|
function usageSettingsPath() {
|
|
1591
2390
|
return join(getAgentDir(), USAGE_SETTINGS_FILE);
|
|
1592
2391
|
}
|
|
1593
2392
|
function normalizeUsageSettings(value) {
|
|
1594
|
-
if (!
|
|
2393
|
+
if (!isRecord3(value)) return void 0;
|
|
1595
2394
|
if (Object.hasOwn(value, "codexFastMode") && typeof value.codexFastMode !== "boolean") {
|
|
1596
2395
|
return void 0;
|
|
1597
2396
|
}
|
|
2397
|
+
if (Object.hasOwn(value, "xaiUsage") && typeof value.xaiUsage !== "boolean") {
|
|
2398
|
+
return void 0;
|
|
2399
|
+
}
|
|
1598
2400
|
return {
|
|
1599
|
-
codexFastMode: typeof value.codexFastMode === "boolean" ? value.codexFastMode : DEFAULT_USAGE_SETTINGS.codexFastMode
|
|
2401
|
+
codexFastMode: typeof value.codexFastMode === "boolean" ? value.codexFastMode : DEFAULT_USAGE_SETTINGS.codexFastMode,
|
|
2402
|
+
xaiUsage: typeof value.xaiUsage === "boolean" ? value.xaiUsage : DEFAULT_USAGE_SETTINGS.xaiUsage
|
|
1600
2403
|
};
|
|
1601
2404
|
}
|
|
1602
2405
|
async function loadUsageSettings(path = usageSettingsPath(), signal) {
|
|
@@ -1618,7 +2421,7 @@ async function loadUsageSettings(path = usageSettingsPath(), signal) {
|
|
|
1618
2421
|
throwIfAborted(signal);
|
|
1619
2422
|
const document = JSON.parse(text);
|
|
1620
2423
|
const settings = normalizeUsageSettings(document);
|
|
1621
|
-
if (!settings || !
|
|
2424
|
+
if (!settings || !isRecord3(document)) throw new Error("invalid settings shape");
|
|
1622
2425
|
return { kind: "loaded", path, settings, document };
|
|
1623
2426
|
} catch (error) {
|
|
1624
2427
|
if (signal?.aborted) throw error;
|
|
@@ -1713,7 +2516,7 @@ async function chmodPrivate(path) {
|
|
|
1713
2516
|
function throwIfAborted(signal) {
|
|
1714
2517
|
if (signal?.aborted) throw new DOMException("Settings operation aborted", "AbortError");
|
|
1715
2518
|
}
|
|
1716
|
-
function
|
|
2519
|
+
function isRecord3(value) {
|
|
1717
2520
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1718
2521
|
}
|
|
1719
2522
|
function isNodeError(error) {
|
|
@@ -1822,7 +2625,7 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus) {
|
|
|
1822
2625
|
const key = activeRequestKey(ctx);
|
|
1823
2626
|
if (key && ctx.model) {
|
|
1824
2627
|
pendingFastRequests.set(key, {
|
|
1825
|
-
fastRequested:
|
|
2628
|
+
fastRequested: isRecord4(rewritten) && rewritten.service_tier === "priority",
|
|
1826
2629
|
model: ctx.model
|
|
1827
2630
|
});
|
|
1828
2631
|
}
|
|
@@ -1862,7 +2665,7 @@ function activeRequestKey(ctx) {
|
|
|
1862
2665
|
return model ? `${ctx.sessionManager.getSessionId()}:${model.provider}/${model.id}` : void 0;
|
|
1863
2666
|
}
|
|
1864
2667
|
function consumeFastRequest(ctx, message, pending) {
|
|
1865
|
-
if (!
|
|
2668
|
+
if (!isRecord4(message) || message.role !== "assistant") return NO_FAST_REQUEST;
|
|
1866
2669
|
const key = messageRequestKey(ctx, message);
|
|
1867
2670
|
if (!key) return NO_FAST_REQUEST;
|
|
1868
2671
|
const request = pending.get(key);
|
|
@@ -1873,7 +2676,7 @@ function messageRequestKey(ctx, message) {
|
|
|
1873
2676
|
if (typeof message.provider !== "string" || typeof message.model !== "string") return void 0;
|
|
1874
2677
|
return `${ctx.sessionManager.getSessionId()}:${message.provider}/${message.model}`;
|
|
1875
2678
|
}
|
|
1876
|
-
function
|
|
2679
|
+
function isRecord4(value) {
|
|
1877
2680
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1878
2681
|
}
|
|
1879
2682
|
function isAbortError2(error) {
|
|
@@ -1881,8 +2684,8 @@ function isAbortError2(error) {
|
|
|
1881
2684
|
}
|
|
1882
2685
|
|
|
1883
2686
|
// src/usage-helpers.ts
|
|
1884
|
-
function configuredAdapters(ctx) {
|
|
1885
|
-
return
|
|
2687
|
+
function configuredAdapters(ctx, xaiUsage = true) {
|
|
2688
|
+
return usageAdapters(xaiUsage).filter(
|
|
1886
2689
|
(adapter) => adapter.id === ctx.model?.provider || providerIsConfigured(ctx, adapter.id)
|
|
1887
2690
|
);
|
|
1888
2691
|
}
|
|
@@ -1912,6 +2715,118 @@ function isTimeoutError(error) {
|
|
|
1912
2715
|
return error instanceof Error && error.name === "TimeoutError";
|
|
1913
2716
|
}
|
|
1914
2717
|
|
|
2718
|
+
// src/usage-settings-ui.ts
|
|
2719
|
+
import {
|
|
2720
|
+
getSettingsListTheme
|
|
2721
|
+
} from "@earendil-works/pi-coding-agent";
|
|
2722
|
+
import {
|
|
2723
|
+
Container,
|
|
2724
|
+
Key,
|
|
2725
|
+
matchesKey,
|
|
2726
|
+
SettingsList,
|
|
2727
|
+
Text
|
|
2728
|
+
} from "@earendil-works/pi-tui";
|
|
2729
|
+
var OFF = "Off";
|
|
2730
|
+
var ON = "On";
|
|
2731
|
+
async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent, onApplied) {
|
|
2732
|
+
if (ctx.mode !== "tui") {
|
|
2733
|
+
if (ctx.hasUI) ctx.ui.notify(`Edit settings manually: ${settingsRuntime.get().path}`, "info");
|
|
2734
|
+
return false;
|
|
2735
|
+
}
|
|
2736
|
+
if (parentSignal.aborted || !isCurrent()) return false;
|
|
2737
|
+
return ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
2738
|
+
const localController = new AbortController();
|
|
2739
|
+
const signal = AbortSignal.any([parentSignal, localController.signal]);
|
|
2740
|
+
let changed = false;
|
|
2741
|
+
let closing = false;
|
|
2742
|
+
let saveQueue = Promise.resolve();
|
|
2743
|
+
const state = settingsRuntime.get();
|
|
2744
|
+
const items = [
|
|
2745
|
+
{
|
|
2746
|
+
id: "codexFastMode",
|
|
2747
|
+
label: "Codex Fast mode",
|
|
2748
|
+
description: "Use faster Codex routing at increased plan allowance consumption.",
|
|
2749
|
+
currentValue: state.settings.codexFastMode ? ON : OFF,
|
|
2750
|
+
values: [OFF, ON]
|
|
2751
|
+
},
|
|
2752
|
+
{
|
|
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,
|
|
2757
|
+
values: [OFF, ON]
|
|
2758
|
+
}
|
|
2759
|
+
];
|
|
2760
|
+
const container = new Container();
|
|
2761
|
+
container.addChild(new Text(theme.fg("accent", theme.bold("pi-usage Settings")), 1, 1));
|
|
2762
|
+
let settingsList;
|
|
2763
|
+
const cancel = () => {
|
|
2764
|
+
if (closing) return;
|
|
2765
|
+
closing = true;
|
|
2766
|
+
localController.abort();
|
|
2767
|
+
done(changed);
|
|
2768
|
+
};
|
|
2769
|
+
settingsList = new SettingsList(
|
|
2770
|
+
items,
|
|
2771
|
+
items.length + 2,
|
|
2772
|
+
getSettingsListTheme(),
|
|
2773
|
+
(id, value) => {
|
|
2774
|
+
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);
|
|
2800
|
+
}
|
|
2801
|
+
if (signal.aborted || !isCurrent()) return;
|
|
2802
|
+
settingsList.updateValue(id, displayValue(settingId, requested));
|
|
2803
|
+
tui.requestRender();
|
|
2804
|
+
});
|
|
2805
|
+
},
|
|
2806
|
+
cancel
|
|
2807
|
+
);
|
|
2808
|
+
container.addChild(settingsList);
|
|
2809
|
+
parentSignal.addEventListener("abort", cancel, { once: true });
|
|
2810
|
+
return {
|
|
2811
|
+
render: (width) => container.render(width),
|
|
2812
|
+
invalidate: () => container.invalidate(),
|
|
2813
|
+
handleInput(data) {
|
|
2814
|
+
if (closing) return;
|
|
2815
|
+
if (matchesKey(data, Key.ctrl("c"))) cancel();
|
|
2816
|
+
else settingsList.handleInput(data);
|
|
2817
|
+
tui.requestRender();
|
|
2818
|
+
},
|
|
2819
|
+
dispose() {
|
|
2820
|
+
localController.abort();
|
|
2821
|
+
parentSignal.removeEventListener("abort", cancel);
|
|
2822
|
+
}
|
|
2823
|
+
};
|
|
2824
|
+
});
|
|
2825
|
+
}
|
|
2826
|
+
function displayValue(_id, enabled) {
|
|
2827
|
+
return enabled ? ON : OFF;
|
|
2828
|
+
}
|
|
2829
|
+
|
|
1915
2830
|
// src/usage.ts
|
|
1916
2831
|
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1917
2832
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
@@ -1923,6 +2838,7 @@ var REFRESH_CURRENT = "Refresh current usage";
|
|
|
1923
2838
|
var VIEW_ANOTHER = "View another configured provider\u2026";
|
|
1924
2839
|
var VIEW_ALL = "View all configured providers\u2026";
|
|
1925
2840
|
var CLOSE = "Close";
|
|
2841
|
+
var SETTINGS = "Settings";
|
|
1926
2842
|
var REDEEM_CODEX_RESET = "Redeem usage limit reset\u2026";
|
|
1927
2843
|
function usageExtension(pi, dependencies = {}) {
|
|
1928
2844
|
const credentialReader = dependencies.credentialReader;
|
|
@@ -1937,9 +2853,16 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
1937
2853
|
let activeCurrentIdentity;
|
|
1938
2854
|
let sessionActive = false;
|
|
1939
2855
|
let statusGeneration = 0;
|
|
2856
|
+
let sessionGeneration = 0;
|
|
2857
|
+
let xaiSettingsGeneration = 0;
|
|
1940
2858
|
let statusRefreshTimer;
|
|
1941
2859
|
let statusController;
|
|
1942
2860
|
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());
|
|
1943
2866
|
const clearStatusTimer = () => {
|
|
1944
2867
|
if (statusRefreshTimer) clearTimeout(statusRefreshTimer);
|
|
1945
2868
|
statusRefreshTimer = void 0;
|
|
@@ -1971,6 +2894,11 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
1971
2894
|
statusRefreshTimer.unref?.();
|
|
1972
2895
|
};
|
|
1973
2896
|
const publishStatus = (ctx, outcome, model, shouldSchedule) => {
|
|
2897
|
+
if (activeAdapterForProvider(model.provider)?.publishesStatusline === false) {
|
|
2898
|
+
clearStatusTimer();
|
|
2899
|
+
safeSetStatus(ctx, void 0);
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
1974
2902
|
if (outcome.state.status === "unsupported") {
|
|
1975
2903
|
clearStatusTimer();
|
|
1976
2904
|
safeSetStatus(ctx, void 0);
|
|
@@ -2012,6 +2940,10 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2012
2940
|
};
|
|
2013
2941
|
const queryAdapterState = async (ctx, adapter, displayState, force, signal) => {
|
|
2014
2942
|
const startedAt = Date.now();
|
|
2943
|
+
const expectedSessionGeneration = sessionGeneration;
|
|
2944
|
+
const expectedXaiSettingsGeneration = xaiSettingsGeneration;
|
|
2945
|
+
const expectedSessionId = ctx.sessionManager.getSessionId();
|
|
2946
|
+
const expectedModelIdentity = modelIdentity(ctx.model);
|
|
2015
2947
|
let auth;
|
|
2016
2948
|
try {
|
|
2017
2949
|
auth = await awaitWithDeadline(
|
|
@@ -2035,6 +2967,9 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2035
2967
|
}
|
|
2036
2968
|
};
|
|
2037
2969
|
}
|
|
2970
|
+
if (adapter.id === "xai" && (expectedSessionGeneration !== sessionGeneration || expectedXaiSettingsGeneration !== xaiSettingsGeneration || !xaiUsageEnabled() || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity)) {
|
|
2971
|
+
throw abortError();
|
|
2972
|
+
}
|
|
2038
2973
|
if (!auth) {
|
|
2039
2974
|
if (displayState === "current") {
|
|
2040
2975
|
transitionCurrentIdentity(`${adapter.id}:unavailable`, adapter.id);
|
|
@@ -2086,7 +3021,22 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2086
3021
|
setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
|
|
2087
3022
|
try {
|
|
2088
3023
|
const remainingMs = Math.max(1, DEFAULT_TIMEOUT_MS - (Date.now() - startedAt));
|
|
2089
|
-
const
|
|
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
|
+
}
|
|
3028
|
+
const revalidated = await awaitWithDeadline(
|
|
3029
|
+
resolveUsageAuth(ctx, adapter, void 0, credentialReader, credentialCandidates),
|
|
3030
|
+
signal,
|
|
3031
|
+
Math.max(1, DEFAULT_TIMEOUT_MS - (Date.now() - startedAt)),
|
|
3032
|
+
"revalidating xAI runtime auth"
|
|
3033
|
+
);
|
|
3034
|
+
if (signal.aborted || expectedSessionGeneration !== sessionGeneration || expectedXaiSettingsGeneration !== xaiSettingsGeneration || !xaiUsageEnabled() || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity || revalidated?.fingerprint !== auth.fingerprint) {
|
|
3035
|
+
throw abortError();
|
|
3036
|
+
}
|
|
3037
|
+
} : void 0;
|
|
3038
|
+
const report = await queryProviderUsage(adapter, auth, signal, remainingMs, guard);
|
|
3039
|
+
if (guard) await guard();
|
|
2090
3040
|
if (latestQueries.get(failureKey) === queryId) {
|
|
2091
3041
|
cache.set(adapter.id, auth.fingerprint, report);
|
|
2092
3042
|
failureBackoff.delete(failureKey);
|
|
@@ -2129,7 +3079,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2129
3079
|
}
|
|
2130
3080
|
};
|
|
2131
3081
|
const queryCurrentState = async (ctx, model, force, signal) => {
|
|
2132
|
-
const adapter =
|
|
3082
|
+
const adapter = activeAdapterForProvider(model?.provider);
|
|
2133
3083
|
if (!adapter) {
|
|
2134
3084
|
const providerId = model?.provider ?? "none";
|
|
2135
3085
|
transitionCurrentIdentity(`unsupported:${providerId}`, providerId);
|
|
@@ -2139,20 +3089,24 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2139
3089
|
providerName: providerDisplayName(ctx, providerId),
|
|
2140
3090
|
displayState: "current",
|
|
2141
3091
|
status: "unsupported",
|
|
2142
|
-
message: model ? `Usage reporting is not supported for ${providerDisplayName(ctx, providerId)}.` : "No model is selected."
|
|
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."
|
|
2143
3093
|
}
|
|
2144
3094
|
};
|
|
2145
3095
|
}
|
|
2146
3096
|
return queryAdapterState(ctx, adapter, "current", force, signal);
|
|
2147
3097
|
};
|
|
2148
3098
|
const refreshCurrentStatus = async (ctx, model, force) => {
|
|
2149
|
-
const adapter =
|
|
3099
|
+
const adapter = activeAdapterForProvider(model?.provider);
|
|
2150
3100
|
if (!adapter || !model) {
|
|
2151
3101
|
const providerId = model?.provider ?? "none";
|
|
2152
3102
|
transitionCurrentIdentity(`unsupported:${providerId}`, providerId);
|
|
2153
3103
|
clearStatus(ctx);
|
|
2154
3104
|
return;
|
|
2155
3105
|
}
|
|
3106
|
+
if (adapter.publishesStatusline === false) {
|
|
3107
|
+
clearStatus(ctx);
|
|
3108
|
+
return;
|
|
3109
|
+
}
|
|
2156
3110
|
statusGeneration += 1;
|
|
2157
3111
|
const generation = statusGeneration;
|
|
2158
3112
|
statusController?.abort();
|
|
@@ -2205,7 +3159,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2205
3159
|
if (generation !== statusGeneration || modelIdentity(ctx.model) !== modelIdentity(model)) {
|
|
2206
3160
|
return false;
|
|
2207
3161
|
}
|
|
2208
|
-
const adapter =
|
|
3162
|
+
const adapter = activeAdapterForProvider(model?.provider);
|
|
2209
3163
|
if (outcome.authState === "unavailable") {
|
|
2210
3164
|
if (!adapter) return false;
|
|
2211
3165
|
try {
|
|
@@ -2300,6 +3254,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2300
3254
|
lines: [...formatProviderStates(visibleStates).split("\n"), ...fastLines],
|
|
2301
3255
|
items: [
|
|
2302
3256
|
{ id: "refresh", label: REFRESH_CURRENT, action: "refresh" },
|
|
3257
|
+
{ id: "settings", label: SETTINGS, action: "settings" },
|
|
2303
3258
|
...fastAvailability.kind === "available" ? [
|
|
2304
3259
|
{
|
|
2305
3260
|
id: "toggle-fast",
|
|
@@ -2328,7 +3283,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2328
3283
|
providers: () => ({
|
|
2329
3284
|
kind: "actions",
|
|
2330
3285
|
title: "Select a configured provider",
|
|
2331
|
-
items: configuredAdapters(ctx).filter((adapter) => adapter.id !== ctx.model?.provider).map((adapter) => ({
|
|
3286
|
+
items: configuredAdapters(ctx, xaiUsageEnabled()).filter((adapter) => adapter.id !== ctx.model?.provider).map((adapter) => ({
|
|
2332
3287
|
id: adapter.id,
|
|
2333
3288
|
label: adapter.displayName,
|
|
2334
3289
|
action: "provider"
|
|
@@ -2392,6 +3347,37 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2392
3347
|
})
|
|
2393
3348
|
},
|
|
2394
3349
|
actions: {
|
|
3350
|
+
settings: async () => {
|
|
3351
|
+
await showUsageSettings(
|
|
3352
|
+
ctx,
|
|
3353
|
+
settingsRuntime,
|
|
3354
|
+
controller.signal,
|
|
3355
|
+
() => 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
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
);
|
|
3367
|
+
fastState = settingsRuntime.get();
|
|
3368
|
+
const revalidated = await queryStableCurrent(
|
|
3369
|
+
ctx,
|
|
3370
|
+
false,
|
|
3371
|
+
controller,
|
|
3372
|
+
"Applying usage settings\u2026"
|
|
3373
|
+
);
|
|
3374
|
+
if (!revalidated) return { kind: "stay" };
|
|
3375
|
+
stableCurrent = revalidated;
|
|
3376
|
+
current = revalidated.outcome;
|
|
3377
|
+
visibleStates = [current.state];
|
|
3378
|
+
publishStableCurrent(ctx, revalidated);
|
|
3379
|
+
return { kind: "stay" };
|
|
3380
|
+
},
|
|
2395
3381
|
"toggle-fast": async () => {
|
|
2396
3382
|
const availability = fastRuntime.availability(ctx.model);
|
|
2397
3383
|
if (availability.kind !== "available" || fastState.kind === "invalid") {
|
|
@@ -2560,7 +3546,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2560
3546
|
return { kind: "stay" };
|
|
2561
3547
|
},
|
|
2562
3548
|
another: async () => {
|
|
2563
|
-
const others = configuredAdapters(ctx).filter(
|
|
3549
|
+
const others = configuredAdapters(ctx, xaiUsageEnabled()).filter(
|
|
2564
3550
|
(adapter) => adapter.id !== ctx.model?.provider
|
|
2565
3551
|
);
|
|
2566
3552
|
if (others.length === 0) {
|
|
@@ -2570,7 +3556,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2570
3556
|
return { kind: "to", screen: "providers" };
|
|
2571
3557
|
},
|
|
2572
3558
|
provider: async ({ itemId }) => {
|
|
2573
|
-
const adapter = configuredAdapters(ctx).find(
|
|
3559
|
+
const adapter = configuredAdapters(ctx, xaiUsageEnabled()).find(
|
|
2574
3560
|
(candidate) => candidate.id === itemId && candidate.id !== ctx.model?.provider
|
|
2575
3561
|
);
|
|
2576
3562
|
if (!adapter) return { kind: "back" };
|
|
@@ -2596,7 +3582,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2596
3582
|
return { kind: "back" };
|
|
2597
3583
|
},
|
|
2598
3584
|
all: async () => {
|
|
2599
|
-
const adapters = configuredAdapters(ctx);
|
|
3585
|
+
const adapters = configuredAdapters(ctx, xaiUsageEnabled());
|
|
2600
3586
|
const currentProviderId = ctx.model?.provider;
|
|
2601
3587
|
const settled = await runMenuOperation(
|
|
2602
3588
|
ctx,
|
|
@@ -2675,6 +3661,8 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2675
3661
|
}
|
|
2676
3662
|
});
|
|
2677
3663
|
pi.on("session_start", (_event, ctx) => {
|
|
3664
|
+
sessionGeneration += 1;
|
|
3665
|
+
xaiSettingsGeneration += 1;
|
|
2678
3666
|
statusGeneration += 1;
|
|
2679
3667
|
clearStatusTimer();
|
|
2680
3668
|
for (const controller of activeControllers) controller.abort();
|
|
@@ -2694,6 +3682,8 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2694
3682
|
});
|
|
2695
3683
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
2696
3684
|
sessionActive = false;
|
|
3685
|
+
sessionGeneration += 1;
|
|
3686
|
+
xaiSettingsGeneration += 1;
|
|
2697
3687
|
statusGeneration += 1;
|
|
2698
3688
|
clearStatusTimer();
|
|
2699
3689
|
for (const controller of activeControllers) controller.abort();
|
|
@@ -2718,6 +3708,7 @@ export {
|
|
|
2718
3708
|
DEFAULT_USAGE_SETTINGS,
|
|
2719
3709
|
SUPPORTED_ADAPTERS,
|
|
2720
3710
|
UsageCache,
|
|
3711
|
+
XAI_ADAPTER,
|
|
2721
3712
|
abortError,
|
|
2722
3713
|
adapterForProvider,
|
|
2723
3714
|
awaitWithDeadline,
|
|
@@ -2740,9 +3731,12 @@ export {
|
|
|
2740
3731
|
normalizeCodexBackendPayload,
|
|
2741
3732
|
normalizeCodexResetCreditsPayload,
|
|
2742
3733
|
normalizeGitHubCopilotUsagePayload,
|
|
3734
|
+
normalizeKimiCodingUsagePayload,
|
|
2743
3735
|
normalizeOpenCodeZenPayload,
|
|
2744
3736
|
normalizeOpenRouterKeyPayload,
|
|
2745
3737
|
normalizeUsageSettings,
|
|
3738
|
+
normalizeXaiBillingPayload,
|
|
3739
|
+
normalizeZaiQuotaPayload,
|
|
2746
3740
|
providerIsConfigured,
|
|
2747
3741
|
queryProviderUsage,
|
|
2748
3742
|
redactUsageError,
|
|
@@ -2751,6 +3745,7 @@ export {
|
|
|
2751
3745
|
rewriteCodexFastPayload,
|
|
2752
3746
|
runWithConcurrency,
|
|
2753
3747
|
sanitizeDisplayText,
|
|
3748
|
+
usageAdapters,
|
|
2754
3749
|
usageSettingsPath
|
|
2755
3750
|
};
|
|
2756
3751
|
//# sourceMappingURL=index.ts.map
|