@narumitw/pi-usage 0.57.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 +36 -2
- package/dist/index.ts +492 -101
- package/dist/index.ts.map +4 -4
- package/package.json +2 -1
- package/src/codex-fast-runtime.ts +34 -26
- package/src/format.ts +33 -1
- package/src/index.ts +7 -0
- package/src/providers/fireworks.ts +198 -0
- package/src/query.ts +141 -3
- package/src/settings.ts +16 -1
- package/src/types.ts +14 -0
- package/src/usage-settings-ui.ts +125 -33
- package/src/usage.ts +37 -11
package/dist/index.ts
CHANGED
|
@@ -520,10 +520,163 @@ function decimalAmount(value) {
|
|
|
520
520
|
return typeof value === "string" && value.length <= 64 && /^(?:0|[1-9]\d*)(?:\.\d+)?$/u.test(value);
|
|
521
521
|
}
|
|
522
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
|
+
|
|
523
676
|
// src/providers/github-copilot.ts
|
|
524
677
|
function normalizeGitHubCopilotUsagePayload(payload, capturedAt) {
|
|
525
|
-
const snapshots =
|
|
526
|
-
const premium =
|
|
678
|
+
const snapshots = asObject4(payload.quota_snapshots);
|
|
679
|
+
const premium = asObject4(snapshots?.premium_interactions);
|
|
527
680
|
const metrics = [];
|
|
528
681
|
let semanticsLabel;
|
|
529
682
|
let bucket;
|
|
@@ -564,8 +717,8 @@ function normalizeGitHubCopilotUsagePayload(payload, capturedAt) {
|
|
|
564
717
|
};
|
|
565
718
|
}
|
|
566
719
|
} else {
|
|
567
|
-
const limited =
|
|
568
|
-
const monthly =
|
|
720
|
+
const limited = asObject4(payload.limited_user_quotas);
|
|
721
|
+
const monthly = asObject4(payload.monthly_quotas);
|
|
569
722
|
const remaining = asNonnegativeNumber(limited?.chat);
|
|
570
723
|
const entitlement = asNonnegativeNumber(monthly?.chat);
|
|
571
724
|
if (remaining === void 0 || entitlement === void 0) {
|
|
@@ -604,7 +757,7 @@ function resetTimestamp(payload) {
|
|
|
604
757
|
const milliseconds = Date.parse(raw);
|
|
605
758
|
return Number.isNaN(milliseconds) ? {} : { resetsAt: Math.floor(milliseconds / 1e3) };
|
|
606
759
|
}
|
|
607
|
-
function
|
|
760
|
+
function asObject4(value) {
|
|
608
761
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
609
762
|
return value;
|
|
610
763
|
}
|
|
@@ -627,7 +780,7 @@ var DAILY_WINDOW_MINUTES = 1440;
|
|
|
627
780
|
var WEEKLY_WINDOW_MINUTES = 10080;
|
|
628
781
|
var FIXED_POINT_UNITS_PER_CENT = 1e6;
|
|
629
782
|
function normalizeKimiCodingUsagePayload(payload, capturedAt) {
|
|
630
|
-
const root =
|
|
783
|
+
const root = asObject5(payload);
|
|
631
784
|
if (!root) throw new Error("Kimi Coding usage response was not an object.");
|
|
632
785
|
const candidates = [];
|
|
633
786
|
let omittedWindow = false;
|
|
@@ -636,7 +789,7 @@ function normalizeKimiCodingUsagePayload(payload, capturedAt) {
|
|
|
636
789
|
else if (root.usage !== void 0) omittedWindow = true;
|
|
637
790
|
if (Array.isArray(root.limits)) {
|
|
638
791
|
for (const raw of root.limits) {
|
|
639
|
-
const item =
|
|
792
|
+
const item = asObject5(raw);
|
|
640
793
|
const windowMinutes = parseWindowMinutes(item?.window);
|
|
641
794
|
const label = sanitizedLabel(item?.name);
|
|
642
795
|
const bucket = windowMinutes === void 0 ? void 0 : parseUsageRow(item?.detail, windowMinutes, label ?? defaultWindowLabel(windowMinutes));
|
|
@@ -673,7 +826,7 @@ function normalizeKimiCodingUsagePayload(payload, capturedAt) {
|
|
|
673
826
|
};
|
|
674
827
|
}
|
|
675
828
|
function parseUsageRow(value, windowMinutes, label) {
|
|
676
|
-
const row =
|
|
829
|
+
const row = asObject5(value);
|
|
677
830
|
if (!row) return void 0;
|
|
678
831
|
const used = asNonnegativeInteger2(row.used);
|
|
679
832
|
const limit = asNonnegativeInteger2(row.limit);
|
|
@@ -691,7 +844,7 @@ function parseUsageRow(value, windowMinutes, label) {
|
|
|
691
844
|
};
|
|
692
845
|
}
|
|
693
846
|
function parseWindowMinutes(value) {
|
|
694
|
-
const window =
|
|
847
|
+
const window = asObject5(value);
|
|
695
848
|
if (!window) return void 0;
|
|
696
849
|
const duration = asPositiveInteger(window.duration);
|
|
697
850
|
if (duration === void 0) return void 0;
|
|
@@ -701,8 +854,8 @@ function parseWindowMinutes(value) {
|
|
|
701
854
|
return Number.isSafeInteger(minutes) ? minutes : void 0;
|
|
702
855
|
}
|
|
703
856
|
function parseBoosterWallet(value) {
|
|
704
|
-
const wallet =
|
|
705
|
-
const balance =
|
|
857
|
+
const wallet = asObject5(value);
|
|
858
|
+
const balance = asObject5(wallet?.balance);
|
|
706
859
|
if (!wallet || !balance || balance.type !== "BOOSTER") return [];
|
|
707
860
|
const totalRaw = asPositiveInteger(balance.amount);
|
|
708
861
|
if (totalRaw === void 0) return [];
|
|
@@ -753,7 +906,7 @@ function parseBoosterWallet(value) {
|
|
|
753
906
|
return metrics;
|
|
754
907
|
}
|
|
755
908
|
function parseMoney(value) {
|
|
756
|
-
const money =
|
|
909
|
+
const money = asObject5(value);
|
|
757
910
|
if (!money) return void 0;
|
|
758
911
|
const cents = asNonnegativeInteger2(money.priceInCents);
|
|
759
912
|
if (cents === void 0) return void 0;
|
|
@@ -767,7 +920,7 @@ function fixedPointToMajor(value) {
|
|
|
767
920
|
const major = roundedCents / 100;
|
|
768
921
|
return Number.isSafeInteger(roundedCents) && Number.isFinite(major) ? major : void 0;
|
|
769
922
|
}
|
|
770
|
-
function
|
|
923
|
+
function asObject5(value) {
|
|
771
924
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
772
925
|
return value;
|
|
773
926
|
}
|
|
@@ -841,12 +994,12 @@ var ZEN_WINDOWS = [
|
|
|
841
994
|
{ key: "monthly", label: "Monthly" }
|
|
842
995
|
];
|
|
843
996
|
function normalizeOpenCodeZenPayload(payload, capturedAt) {
|
|
844
|
-
const usage =
|
|
997
|
+
const usage = asObject6(payload.usage);
|
|
845
998
|
if (!usage) throw new Error("OpenCode Zen usage response was not an object.");
|
|
846
999
|
const buckets = [];
|
|
847
1000
|
const notes = [];
|
|
848
1001
|
for (const window of ZEN_WINDOWS) {
|
|
849
|
-
const raw =
|
|
1002
|
+
const raw = asObject6(usage[window.key]);
|
|
850
1003
|
if (!raw) continue;
|
|
851
1004
|
const status = asString3(raw.status);
|
|
852
1005
|
if (status !== "ok" && status !== "rate-limited") {
|
|
@@ -883,7 +1036,7 @@ function normalizeOpenCodeZenPayload(payload, capturedAt) {
|
|
|
883
1036
|
...notes.length > 0 ? { notes } : {}
|
|
884
1037
|
};
|
|
885
1038
|
}
|
|
886
|
-
function
|
|
1039
|
+
function asObject6(value) {
|
|
887
1040
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
888
1041
|
return value;
|
|
889
1042
|
}
|
|
@@ -907,7 +1060,7 @@ function clampPercent2(value) {
|
|
|
907
1060
|
|
|
908
1061
|
// src/providers/openrouter.ts
|
|
909
1062
|
function normalizeOpenRouterKeyPayload(payload, capturedAt) {
|
|
910
|
-
const data =
|
|
1063
|
+
const data = asObject7(payload.data);
|
|
911
1064
|
if (!data) throw new Error("OpenRouter key response data was not an object.");
|
|
912
1065
|
const limit = asNonnegativeNumber3(data.limit);
|
|
913
1066
|
const remaining = asNonnegativeNumber3(data.limit_remaining);
|
|
@@ -953,7 +1106,7 @@ function addUsageMetric(metrics, id, label, value) {
|
|
|
953
1106
|
if (amount === void 0) return;
|
|
954
1107
|
metrics.push({ id, label, value: amount, unit: "usd" });
|
|
955
1108
|
}
|
|
956
|
-
function
|
|
1109
|
+
function asObject7(value) {
|
|
957
1110
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
958
1111
|
return value;
|
|
959
1112
|
}
|
|
@@ -1131,13 +1284,13 @@ function isRecord2(value) {
|
|
|
1131
1284
|
var FIVE_HOUR_WINDOW_MINUTES2 = 300;
|
|
1132
1285
|
var WEEKLY_WINDOW_MINUTES2 = 10080;
|
|
1133
1286
|
function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt) {
|
|
1134
|
-
const data =
|
|
1287
|
+
const data = asObject8(payload.data);
|
|
1135
1288
|
if (!data) throw new Error("Z.AI quota response data was not an object.");
|
|
1136
1289
|
const limits = Array.isArray(data.limits) ? data.limits : [];
|
|
1137
1290
|
const buckets = [];
|
|
1138
1291
|
const metrics = [];
|
|
1139
1292
|
for (const raw of limits) {
|
|
1140
|
-
const limit =
|
|
1293
|
+
const limit = asObject8(raw);
|
|
1141
1294
|
if (!limit) continue;
|
|
1142
1295
|
const type = asString5(limit.type);
|
|
1143
1296
|
const unit = asNonnegativeNumber4(limit.unit);
|
|
@@ -1209,7 +1362,7 @@ function addCountBucket(buckets, limit, id, label, windowMinutes) {
|
|
|
1209
1362
|
function addUsageDetailMetrics(metrics, value) {
|
|
1210
1363
|
if (!Array.isArray(value)) return;
|
|
1211
1364
|
for (const raw of value) {
|
|
1212
|
-
const detail =
|
|
1365
|
+
const detail = asObject8(raw);
|
|
1213
1366
|
if (!detail) continue;
|
|
1214
1367
|
const label = asString5(detail.modelCode);
|
|
1215
1368
|
const usage = asNonnegativeNumber4(detail.usage);
|
|
@@ -1217,7 +1370,7 @@ function addUsageDetailMetrics(metrics, value) {
|
|
|
1217
1370
|
metrics.push({ id: `mcp-${kebabCase(label)}`, label, value: usage, unit: "count" });
|
|
1218
1371
|
}
|
|
1219
1372
|
}
|
|
1220
|
-
function
|
|
1373
|
+
function asObject8(value) {
|
|
1221
1374
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1222
1375
|
return value;
|
|
1223
1376
|
}
|
|
@@ -1244,6 +1397,9 @@ function clampPercent3(value) {
|
|
|
1244
1397
|
// src/query.ts
|
|
1245
1398
|
var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
1246
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;
|
|
1247
1403
|
var GITHUB_COPILOT_USAGE_URL = "https://api.github.com/copilot_internal/user";
|
|
1248
1404
|
var OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
|
|
1249
1405
|
var OPENCODE_GO_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
|
|
@@ -1331,6 +1487,35 @@ var SUPPORTED_ADAPTERS = [
|
|
|
1331
1487
|
return normalizeOpenRouterKeyPayload(payload, Date.now());
|
|
1332
1488
|
}
|
|
1333
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
|
+
},
|
|
1334
1519
|
{
|
|
1335
1520
|
id: "opencode-go",
|
|
1336
1521
|
displayName: "OpenCode Go",
|
|
@@ -1535,9 +1720,9 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
|
|
|
1535
1720
|
model
|
|
1536
1721
|
};
|
|
1537
1722
|
}
|
|
1538
|
-
async function queryProviderUsage(adapter, auth, signal, timeoutMs, guard) {
|
|
1723
|
+
async function queryProviderUsage(adapter, auth, signal, timeoutMs, guard, settings) {
|
|
1539
1724
|
try {
|
|
1540
|
-
return await adapter.query(auth, signal, timeoutMs, guard);
|
|
1725
|
+
return await adapter.query(auth, signal, timeoutMs, guard, settings);
|
|
1541
1726
|
} catch (error) {
|
|
1542
1727
|
if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
|
|
1543
1728
|
throw new Error(redactUsageError(errorMessage(error), auth.secrets));
|
|
@@ -1678,7 +1863,7 @@ function resolveXaiUsageAuth(auth, model, salt, candidates) {
|
|
|
1678
1863
|
const matches = [];
|
|
1679
1864
|
for (const candidate of candidates) {
|
|
1680
1865
|
try {
|
|
1681
|
-
const credential =
|
|
1866
|
+
const credential = asObject9(candidate);
|
|
1682
1867
|
if (credential?.type !== "oauth") continue;
|
|
1683
1868
|
sawOAuth = true;
|
|
1684
1869
|
if (credential.access !== resolvedAccess) continue;
|
|
@@ -1732,7 +1917,7 @@ function resolveGitHubCopilotUsageAuth(auth, model, salt, candidates, standalone
|
|
|
1732
1917
|
const matches = /* @__PURE__ */ new Map();
|
|
1733
1918
|
for (const candidate of candidates) {
|
|
1734
1919
|
try {
|
|
1735
|
-
const credential =
|
|
1920
|
+
const credential = asObject9(candidate);
|
|
1736
1921
|
if (credential?.type !== "oauth") continue;
|
|
1737
1922
|
sawOAuth = true;
|
|
1738
1923
|
const storedAccess2 = typeof credential.access === "string" && credential.access ? credential.access : void 0;
|
|
@@ -1794,7 +1979,7 @@ function bearerToken(authorization) {
|
|
|
1794
1979
|
const match = /^Bearer\s+(.+)$/iu.exec(authorization ?? "");
|
|
1795
1980
|
return match?.[1];
|
|
1796
1981
|
}
|
|
1797
|
-
function
|
|
1982
|
+
function asObject9(value) {
|
|
1798
1983
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1799
1984
|
return value;
|
|
1800
1985
|
}
|
|
@@ -1814,6 +1999,7 @@ function hasOfficialUrlOrigin(value, providerId) {
|
|
|
1814
1999
|
const url = new URL(value);
|
|
1815
2000
|
if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
|
|
1816
2001
|
if (providerId === "deepseek") return url.origin === "https://api.deepseek.com";
|
|
2002
|
+
if (providerId === "fireworks") return url.origin === FIREWORKS_BILLING_SUMMARY_ORIGIN;
|
|
1817
2003
|
if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
|
|
1818
2004
|
if (providerId === "opencode-go") return url.origin === "https://opencode.ai";
|
|
1819
2005
|
if (providerId === "kimi-coding") return url.origin === "https://api.kimi.com";
|
|
@@ -1843,11 +2029,87 @@ function validatedXaiUserId(value) {
|
|
|
1843
2029
|
}
|
|
1844
2030
|
return value;
|
|
1845
2031
|
}
|
|
1846
|
-
function remainingTimeout(timeoutMs, startedAt) {
|
|
2032
|
+
function remainingTimeout(timeoutMs, startedAt, description = "fetching xAI consumer usage") {
|
|
1847
2033
|
const remaining = timeoutMs - (Date.now() - startedAt);
|
|
1848
|
-
if (remaining <= 0) throw new Error(
|
|
2034
|
+
if (remaining <= 0) throw new Error(`Timed out while ${description}.`);
|
|
1849
2035
|
return remaining;
|
|
1850
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
|
+
}
|
|
1851
2113
|
function zaiMonitorUrl(baseUrl) {
|
|
1852
2114
|
const base = baseUrl?.trim();
|
|
1853
2115
|
if (!base) throw new Error("Z.AI model base URL is unavailable.");
|
|
@@ -2001,7 +2263,7 @@ function normalizeCodexResetCreditsPayload(payload) {
|
|
|
2001
2263
|
if (rawCredits !== void 0 && !Array.isArray(rawCredits)) {
|
|
2002
2264
|
throw new Error("Codex reset credits response returned invalid credits.");
|
|
2003
2265
|
}
|
|
2004
|
-
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(
|
|
2005
2267
|
(left, right) => (left.expiresAt ?? Number.MAX_SAFE_INTEGER) - (right.expiresAt ?? Number.MAX_SAFE_INTEGER)
|
|
2006
2268
|
).slice(0, Math.min(availableCount, MAX_RESET_OPTIONS));
|
|
2007
2269
|
if (availableCount > 0 && options.length === 0) {
|
|
@@ -2016,7 +2278,7 @@ function selectCodexResetCredential(candidates, resolvedAccess, resolvedAccountI
|
|
|
2016
2278
|
const matches = /* @__PURE__ */ new Map();
|
|
2017
2279
|
for (const candidate of candidates) {
|
|
2018
2280
|
try {
|
|
2019
|
-
const credential =
|
|
2281
|
+
const credential = asObject10(candidate);
|
|
2020
2282
|
if (credential?.type !== "oauth") continue;
|
|
2021
2283
|
sawOAuth = true;
|
|
2022
2284
|
const storedAccess = asNonemptyString(credential.access);
|
|
@@ -2055,7 +2317,7 @@ function codexAccountIdFromAccessToken(access) {
|
|
|
2055
2317
|
const parts = access.split(".");
|
|
2056
2318
|
if (parts.length !== 3 || !parts[1]) return void 0;
|
|
2057
2319
|
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
2058
|
-
const claims =
|
|
2320
|
+
const claims = asObject10(asObject10(payload)?.["https://api.openai.com/auth"]);
|
|
2059
2321
|
return validHeaderValue(claims?.chatgpt_account_id);
|
|
2060
2322
|
} catch {
|
|
2061
2323
|
return void 0;
|
|
@@ -2087,7 +2349,7 @@ function normalizeResetOption(credit) {
|
|
|
2087
2349
|
function isCodexResetOutcomeCode(value) {
|
|
2088
2350
|
return value === "reset" || value === "nothing_to_reset" || value === "no_credit" || value === "already_redeemed";
|
|
2089
2351
|
}
|
|
2090
|
-
function
|
|
2352
|
+
function asObject10(value) {
|
|
2091
2353
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
2092
2354
|
return value;
|
|
2093
2355
|
}
|
|
@@ -2128,12 +2390,13 @@ var BAR_SEGMENTS = 20;
|
|
|
2128
2390
|
var VALUE_COLUMN = 29;
|
|
2129
2391
|
function formatUsageReport(report, displayState) {
|
|
2130
2392
|
const stateLabel = displayState === "current" ? "Current" : "Configured";
|
|
2131
|
-
const title = report.providerId === "deepseek" ? "DeepSeek API Balance" : `${report.providerName} Usage`;
|
|
2393
|
+
const title = report.providerId === "deepseek" ? "DeepSeek API Balance" : report.providerId === "fireworks" ? "Fireworks API Spend" : `${report.providerName} Usage`;
|
|
2132
2394
|
const lines = [`${title} \xB7 ${stateLabel}`];
|
|
2133
2395
|
if (report.accountLabel) lines.push(`Account: ${report.accountLabel}`);
|
|
2134
2396
|
lines.push(`Semantics: ${report.semantics.label}`, "");
|
|
2135
2397
|
if (report.providerId === "openai-codex") formatCodexReport(lines, report);
|
|
2136
2398
|
else if (report.providerId === "deepseek") formatDeepSeekReport(lines, report);
|
|
2399
|
+
else if (report.providerId === "fireworks") formatFireworksReport(lines, report);
|
|
2137
2400
|
else if (report.providerId === "github-copilot") formatGitHubCopilotReport(lines, report);
|
|
2138
2401
|
else if (report.providerId === "openrouter") formatOpenRouterReport(lines, report);
|
|
2139
2402
|
else if (report.providerId === "opencode-go") formatOpenCodeZenReport(lines, report);
|
|
@@ -2152,6 +2415,7 @@ function formatUsageStatusline(report, model, now = Date.now(), showCodexResetCo
|
|
|
2152
2415
|
return formatCodexStatusline(report, model, now, showCodexResetCountdown);
|
|
2153
2416
|
}
|
|
2154
2417
|
if (report.providerId === "deepseek") return formatDeepSeekStatusline(report);
|
|
2418
|
+
if (report.providerId === "fireworks") return formatFireworksStatusline(report);
|
|
2155
2419
|
if (report.providerId === "github-copilot") return formatGitHubCopilotStatusline(report);
|
|
2156
2420
|
if (report.providerId === "openrouter") {
|
|
2157
2421
|
const limit = report.buckets.find((bucket) => bucket.id === "key-limit");
|
|
@@ -2222,6 +2486,29 @@ function formatDeepSeekStatusline(report) {
|
|
|
2222
2486
|
});
|
|
2223
2487
|
return totals.length > 0 ? `deepseek ${totals.join(" \xB7 ")}` : "deepseek balance unavailable";
|
|
2224
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
|
+
}
|
|
2225
2512
|
function formatGitHubCopilotReport(lines, report) {
|
|
2226
2513
|
const quota = findGitHubCopilotQuota(report);
|
|
2227
2514
|
if (!quota || quota.limit === void 0 || quota.remaining === void 0) {
|
|
@@ -2583,9 +2870,13 @@ function normalizeUsageSettings(value) {
|
|
|
2583
2870
|
if (Object.hasOwn(value, "codexStatusResetCountdown") && typeof value.codexStatusResetCountdown !== "boolean") {
|
|
2584
2871
|
return void 0;
|
|
2585
2872
|
}
|
|
2873
|
+
if (Object.hasOwn(value, "fireworksAccountId") && !isFireworksAccountId(value.fireworksAccountId)) {
|
|
2874
|
+
return void 0;
|
|
2875
|
+
}
|
|
2586
2876
|
return {
|
|
2587
2877
|
codexFastMode: typeof value.codexFastMode === "boolean" ? value.codexFastMode : DEFAULT_USAGE_SETTINGS.codexFastMode,
|
|
2588
|
-
codexStatusResetCountdown: typeof value.codexStatusResetCountdown === "boolean" ? value.codexStatusResetCountdown : DEFAULT_USAGE_SETTINGS.codexStatusResetCountdown
|
|
2878
|
+
codexStatusResetCountdown: typeof value.codexStatusResetCountdown === "boolean" ? value.codexStatusResetCountdown : DEFAULT_USAGE_SETTINGS.codexStatusResetCountdown,
|
|
2879
|
+
...isFireworksAccountId(value.fireworksAccountId) ? { fireworksAccountId: value.fireworksAccountId } : {}
|
|
2589
2880
|
};
|
|
2590
2881
|
}
|
|
2591
2882
|
async function loadUsageSettings(path = usageSettingsPath(), signal) {
|
|
@@ -2669,7 +2960,11 @@ async function saveUsageSettingsPatch(path, patch, operations, signal) {
|
|
|
2669
2960
|
if (latest.kind === "invalid") {
|
|
2670
2961
|
throw new Error("Cannot overwrite an invalid pi-usage.json; repair it and reload first");
|
|
2671
2962
|
}
|
|
2672
|
-
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
|
+
}
|
|
2673
2968
|
const settings = normalizeUsageSettings(document);
|
|
2674
2969
|
if (!settings) throw new Error("Refusing to save invalid pi-usage settings");
|
|
2675
2970
|
const directory = dirname(path);
|
|
@@ -2715,7 +3010,7 @@ import { randomUUID as randomUUID2 } from "node:crypto";
|
|
|
2715
3010
|
// src/codex-fast-runtime.ts
|
|
2716
3011
|
var NO_FAST_REQUEST = /* @__PURE__ */ Symbol("no-fast-request");
|
|
2717
3012
|
var FAST_USAGE_WARNING = "Fast is about 1.5\xD7 faster and uses more of your plan allowance.";
|
|
2718
|
-
function registerCodexFastMode(pi, settingsRuntime, refreshStatus) {
|
|
3013
|
+
function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {}) {
|
|
2719
3014
|
let sessionController = new AbortController();
|
|
2720
3015
|
let generation = 0;
|
|
2721
3016
|
const pendingFastRequests = /* @__PURE__ */ new Map();
|
|
@@ -2771,37 +3066,42 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus) {
|
|
|
2771
3066
|
await toggle(ctx, !availability.enabled);
|
|
2772
3067
|
}
|
|
2773
3068
|
});
|
|
2774
|
-
|
|
3069
|
+
const prepareSession = (ctx) => {
|
|
3070
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
2775
3071
|
generation += 1;
|
|
2776
3072
|
sessionController.abort();
|
|
2777
3073
|
pendingFastRequests.clear();
|
|
2778
3074
|
sessionController = new AbortController();
|
|
2779
3075
|
const ownerGeneration = generation;
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
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") {
|
|
2787
3094
|
ctx.ui.notify(
|
|
2788
|
-
`
|
|
3095
|
+
`Invalid pi-usage.json; using defaults without overwriting it. ${state.issue}`,
|
|
2789
3096
|
"warning"
|
|
2790
3097
|
);
|
|
2791
3098
|
}
|
|
2792
|
-
|
|
2793
|
-
}
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
ctx.ui.notify(
|
|
2799
|
-
`Invalid pi-usage.json; using defaults without overwriting it. ${state.issue}`,
|
|
2800
|
-
"warning"
|
|
2801
|
-
);
|
|
2802
|
-
}
|
|
2803
|
-
refreshStatus(ctx);
|
|
2804
|
-
});
|
|
3099
|
+
refreshStatus(ctx);
|
|
3100
|
+
})();
|
|
3101
|
+
};
|
|
3102
|
+
if (options.registerSessionStart !== false) {
|
|
3103
|
+
pi.on("session_start", async (_event, ctx) => prepareSession(ctx));
|
|
3104
|
+
}
|
|
2805
3105
|
pi.on("before_provider_request", (event, ctx) => {
|
|
2806
3106
|
const rewritten = rewriteCodexFastPayload(
|
|
2807
3107
|
event.payload,
|
|
@@ -2834,6 +3134,7 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus) {
|
|
|
2834
3134
|
await settingsRuntime.flush();
|
|
2835
3135
|
});
|
|
2836
3136
|
return {
|
|
3137
|
+
prepareSession,
|
|
2837
3138
|
availability(model) {
|
|
2838
3139
|
return codexFastAvailability(model, settingsRuntime.get().settings.codexFastMode);
|
|
2839
3140
|
},
|
|
@@ -2912,6 +3213,8 @@ import {
|
|
|
2912
3213
|
SettingsList,
|
|
2913
3214
|
Text
|
|
2914
3215
|
} from "@earendil-works/pi-tui";
|
|
3216
|
+
var AUTO = "Auto";
|
|
3217
|
+
var EDIT = "Edit\u2026";
|
|
2915
3218
|
var OFF = "Off";
|
|
2916
3219
|
var ON = "On";
|
|
2917
3220
|
async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent, onApplied) {
|
|
@@ -2919,7 +3222,23 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
|
|
|
2919
3222
|
if (ctx.hasUI) ctx.ui.notify(`Edit settings manually: ${settingsRuntime.get().path}`, "info");
|
|
2920
3223
|
return false;
|
|
2921
3224
|
}
|
|
2922
|
-
|
|
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) {
|
|
2923
3242
|
return ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
2924
3243
|
const localController = new AbortController();
|
|
2925
3244
|
const signal = AbortSignal.any([parentSignal, localController.signal]);
|
|
@@ -2927,6 +3246,7 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
|
|
|
2927
3246
|
let closing = false;
|
|
2928
3247
|
let saveQueue = Promise.resolve();
|
|
2929
3248
|
const state = settingsRuntime.get();
|
|
3249
|
+
const fireworksValue = state.settings.fireworksAccountId ?? AUTO;
|
|
2930
3250
|
const items = [
|
|
2931
3251
|
{
|
|
2932
3252
|
id: "codexFastMode",
|
|
@@ -2941,6 +3261,13 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
|
|
|
2941
3261
|
description: "Show time remaining until each Codex usage limit resets.",
|
|
2942
3262
|
currentValue: state.settings.codexStatusResetCountdown ? ON : OFF,
|
|
2943
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]
|
|
2944
3271
|
}
|
|
2945
3272
|
];
|
|
2946
3273
|
const container = new Container();
|
|
@@ -2950,7 +3277,36 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
|
|
|
2950
3277
|
if (closing) return;
|
|
2951
3278
|
closing = true;
|
|
2952
3279
|
localController.abort();
|
|
2953
|
-
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
|
+
});
|
|
2954
3310
|
};
|
|
2955
3311
|
settingsList = new SettingsList(
|
|
2956
3312
|
items,
|
|
@@ -2958,35 +3314,18 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
|
|
|
2958
3314
|
getSettingsListTheme(),
|
|
2959
3315
|
(id, value) => {
|
|
2960
3316
|
if (closing || signal.aborted || !isCurrent()) return;
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
|
|
2969
|
-
tui.requestRender();
|
|
2970
|
-
}
|
|
2971
|
-
return;
|
|
2972
|
-
}
|
|
2973
|
-
try {
|
|
2974
|
-
await settingsRuntime.update({ [settingId]: requested }, signal);
|
|
2975
|
-
} catch (error) {
|
|
2976
|
-
if (signal.aborted || !isCurrent()) return;
|
|
2977
|
-
settingsList.updateValue(id, displayValue(previous));
|
|
2978
|
-
ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
|
|
2979
|
-
tui.requestRender();
|
|
2980
|
-
return;
|
|
2981
|
-
}
|
|
2982
|
-
if (previous !== requested) {
|
|
2983
|
-
changed = true;
|
|
2984
|
-
onApplied(settingId);
|
|
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
|
+
});
|
|
2985
3324
|
}
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
3325
|
+
return;
|
|
3326
|
+
}
|
|
3327
|
+
const settingId = id;
|
|
3328
|
+
queueUpdate(settingId, value !== OFF, value);
|
|
2990
3329
|
},
|
|
2991
3330
|
cancel
|
|
2992
3331
|
);
|
|
@@ -3008,8 +3347,41 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
|
|
|
3008
3347
|
};
|
|
3009
3348
|
});
|
|
3010
3349
|
}
|
|
3011
|
-
function
|
|
3012
|
-
|
|
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;
|
|
3013
3385
|
}
|
|
3014
3386
|
|
|
3015
3387
|
// src/usage.ts
|
|
@@ -3152,6 +3524,8 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3152
3524
|
const expectedSessionGeneration = sessionGeneration;
|
|
3153
3525
|
const expectedSessionId = ctx.sessionManager.getSessionId();
|
|
3154
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;
|
|
3155
3529
|
let auth;
|
|
3156
3530
|
try {
|
|
3157
3531
|
auth = await awaitWithDeadline(
|
|
@@ -3175,8 +3549,8 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3175
3549
|
}
|
|
3176
3550
|
};
|
|
3177
3551
|
}
|
|
3178
|
-
const requiresRequestBoundaryGuard =
|
|
3179
|
-
const requestContextChanged = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity;
|
|
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;
|
|
3180
3554
|
if (requiresRequestBoundaryGuard && requestContextChanged()) throw abortError();
|
|
3181
3555
|
if (!auth) {
|
|
3182
3556
|
if (displayState === "current") {
|
|
@@ -3193,10 +3567,11 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3193
3567
|
authState: "unavailable"
|
|
3194
3568
|
};
|
|
3195
3569
|
}
|
|
3570
|
+
const queryFingerprint = adapter.id === "fireworks" ? `${auth.fingerprint}:account:${expectedFireworksAccountId ?? "auto"}` : auth.fingerprint;
|
|
3196
3571
|
if (displayState === "current") {
|
|
3197
|
-
transitionCurrentIdentity(`${adapter.id}:${
|
|
3572
|
+
transitionCurrentIdentity(`${adapter.id}:${queryFingerprint}`, adapter.id);
|
|
3198
3573
|
}
|
|
3199
|
-
const cached = !force ? cache.get(adapter.id,
|
|
3574
|
+
const cached = !force ? cache.get(adapter.id, queryFingerprint) : void 0;
|
|
3200
3575
|
if (cached) {
|
|
3201
3576
|
return {
|
|
3202
3577
|
state: {
|
|
@@ -3209,7 +3584,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3209
3584
|
fingerprint: auth.fingerprint
|
|
3210
3585
|
};
|
|
3211
3586
|
}
|
|
3212
|
-
const failureKey = `${adapter.id}:${
|
|
3587
|
+
const failureKey = `${adapter.id}:${queryFingerprint}`;
|
|
3213
3588
|
const previousFailure = failureBackoff.get(failureKey);
|
|
3214
3589
|
if (!force && previousFailure && previousFailure.until > Date.now()) {
|
|
3215
3590
|
return {
|
|
@@ -3247,10 +3622,17 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3247
3622
|
throw abortError();
|
|
3248
3623
|
}
|
|
3249
3624
|
} : void 0;
|
|
3250
|
-
const report = await queryProviderUsage(
|
|
3625
|
+
const report = await queryProviderUsage(
|
|
3626
|
+
adapter,
|
|
3627
|
+
auth,
|
|
3628
|
+
signal,
|
|
3629
|
+
remainingMs,
|
|
3630
|
+
guard,
|
|
3631
|
+
querySettings
|
|
3632
|
+
);
|
|
3251
3633
|
if (guard) await guard();
|
|
3252
3634
|
if (latestQueries.get(failureKey) === queryId) {
|
|
3253
|
-
cache.set(adapter.id,
|
|
3635
|
+
cache.set(adapter.id, queryFingerprint, report);
|
|
3254
3636
|
failureBackoff.delete(failureKey);
|
|
3255
3637
|
}
|
|
3256
3638
|
return {
|
|
@@ -3880,7 +4262,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3880
4262
|
}
|
|
3881
4263
|
}
|
|
3882
4264
|
});
|
|
3883
|
-
pi.on("session_start", (_event, ctx) => {
|
|
4265
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
3884
4266
|
sessionGeneration += 1;
|
|
3885
4267
|
statusGeneration += 1;
|
|
3886
4268
|
clearStatusTimers();
|
|
@@ -3888,7 +4270,13 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3888
4270
|
activeControllers.clear();
|
|
3889
4271
|
statusController = void 0;
|
|
3890
4272
|
sessionActive = true;
|
|
3891
|
-
|
|
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
|
+
}
|
|
3892
4280
|
});
|
|
3893
4281
|
pi.on("session_tree", (_event, ctx) => {
|
|
3894
4282
|
startStatusRefresh(ctx, ctx.model, false);
|
|
@@ -3916,7 +4304,8 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3916
4304
|
fastRuntime = registerCodexFastMode(
|
|
3917
4305
|
pi,
|
|
3918
4306
|
settingsRuntime,
|
|
3919
|
-
(ctx) => startStatusRefresh(ctx, ctx.model, false)
|
|
4307
|
+
(ctx) => startStatusRefresh(ctx, ctx.model, false),
|
|
4308
|
+
{ registerSessionStart: false }
|
|
3920
4309
|
);
|
|
3921
4310
|
}
|
|
3922
4311
|
export {
|
|
@@ -3949,6 +4338,8 @@ export {
|
|
|
3949
4338
|
normalizeCodexBackendPayload,
|
|
3950
4339
|
normalizeCodexResetCreditsPayload,
|
|
3951
4340
|
normalizeDeepSeekBalancePayload,
|
|
4341
|
+
normalizeFireworksAccountsPayload,
|
|
4342
|
+
normalizeFireworksBillingSummaryPayload,
|
|
3952
4343
|
normalizeGitHubCopilotUsagePayload,
|
|
3953
4344
|
normalizeKimiCodingUsagePayload,
|
|
3954
4345
|
normalizeOpenCodeZenPayload,
|