@narumitw/pi-usage 0.59.0 → 0.60.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.ts CHANGED
@@ -136,7 +136,20 @@ var UsageCache = class {
136
136
  };
137
137
  function fingerprintResolvedAuth(auth, salt) {
138
138
  const headers = Object.entries(auth.headers ?? {}).map(([name, value]) => [name.toLowerCase(), value]).sort(([left], [right]) => left.localeCompare(right));
139
- const canonical = JSON.stringify({ apiKey: auth.apiKey ?? "", headers });
139
+ const env = Object.entries(auth.env ?? {}).sort(([left], [right]) => left.localeCompare(right));
140
+ const providerHeaders = Object.entries(auth.providerAuth?.headers ?? {}).map(([name, value]) => [name.toLowerCase(), value]).sort(([left], [right]) => left.localeCompare(right));
141
+ const canonical = JSON.stringify({
142
+ apiKey: auth.apiKey ?? "",
143
+ headers,
144
+ baseUrl: auth.baseUrl ?? "",
145
+ env,
146
+ source: auth.source ?? "",
147
+ providerAuth: {
148
+ apiKey: auth.providerAuth?.apiKey ?? "",
149
+ headers: providerHeaders,
150
+ baseUrl: auth.providerAuth?.baseUrl ?? ""
151
+ }
152
+ });
140
153
  return createHmac("sha256", salt).update(canonical).digest("hex");
141
154
  }
142
155
  async function runWithConcurrency(items, limit, worker, signal) {
@@ -572,6 +585,9 @@ var INT64_MIN = -(2n ** 63n);
572
585
  var INT64_MAX = 2n ** 63n - 1n;
573
586
  var MAX_UNITS_CHARS = 20;
574
587
  var MAX_NANOS_CHARS = 11;
588
+ var FIREWORKS_BILLING_SUMMARY_ORIGIN = "https://api.fireworks.ai";
589
+ var FIREWORKS_SPEND_WINDOW_DAYS = 30;
590
+ var FIREWORKS_MAX_ACCOUNT_PAGES = 5;
575
591
  var SERIES_KEYS = ["serverless", "dedicated", "training", "other"];
576
592
  var SERIES_LABELS = {
577
593
  serverless: "Serverless",
@@ -605,6 +621,67 @@ function normalizeFireworksAccountsPayload(payload) {
605
621
  }
606
622
  return accounts;
607
623
  }
624
+ function createFireworksAdapter(fetchProviderJson2) {
625
+ return {
626
+ id: "fireworks",
627
+ displayName: "Fireworks",
628
+ semantics: { kind: "api-key", label: "Fireworks API spend" },
629
+ targets: {
630
+ singularLabel: "account",
631
+ pluralLabel: "accounts",
632
+ async list(auth, signal, timeoutMs, guard) {
633
+ const startedAt = Date.now();
634
+ const accounts = [];
635
+ let pageToken;
636
+ for (let page = 0; page < FIREWORKS_MAX_ACCOUNT_PAGES; page += 1) {
637
+ await guard();
638
+ const payload = await fetchProviderJson2(
639
+ fireworksAccountsUrl(pageToken),
640
+ auth,
641
+ signal,
642
+ remainingTimeout(timeoutMs, startedAt, "fetching Fireworks accounts"),
643
+ "Fireworks accounts endpoint",
644
+ { redirect: "error" }
645
+ );
646
+ await guard();
647
+ for (const accountId of normalizeFireworksAccountsPayload(payload)) {
648
+ if (accounts.includes(accountId)) {
649
+ throw new Error(`Fireworks accounts listing repeated ${accountId}.`);
650
+ }
651
+ accounts.push(accountId);
652
+ }
653
+ pageToken = fireworksNextPageToken(payload.nextPageToken);
654
+ if (!pageToken) break;
655
+ }
656
+ if (pageToken) {
657
+ throw new Error(
658
+ `Fireworks account listing exceeded ${FIREWORKS_MAX_ACCOUNT_PAGES} pages.`
659
+ );
660
+ }
661
+ return accounts.map((id) => ({ id, label: id }));
662
+ }
663
+ },
664
+ async query(auth, signal, timeoutMs, guard, targetId) {
665
+ if (!guard) throw new Error("Fireworks API spend requires request-boundary revalidation.");
666
+ if (!isFireworksAccountId(targetId)) {
667
+ throw new Error("Fireworks billing requires a safe selected account slug.");
668
+ }
669
+ const startedAt = Date.now();
670
+ await guard();
671
+ const billingWindowAt = Date.now();
672
+ const payload = await fetchProviderJson2(
673
+ fireworksBillingSummaryUrl(targetId, billingWindowAt),
674
+ auth,
675
+ signal,
676
+ remainingTimeout(timeoutMs, startedAt, "fetching Fireworks rated spend"),
677
+ "Fireworks billing summary endpoint",
678
+ { redirect: "error" }
679
+ );
680
+ await guard();
681
+ return normalizeFireworksBillingSummaryPayload(payload, targetId, Date.now());
682
+ }
683
+ };
684
+ }
608
685
  function normalizeFireworksBillingSummaryPayload(payload, accountId, capturedAt) {
609
686
  if (!isFireworksAccountId(accountId)) {
610
687
  throw new Error("Fireworks billing summary received an unsafe account identifier.");
@@ -711,6 +788,38 @@ function formatMoneyAmount(amount2) {
711
788
  const nanos = (magnitude % NANOS_PER_UNIT).toString().padStart(9, "0").replace(/0+$/u, "");
712
789
  return `${negative ? "-" : ""}${units.toString()}${nanos ? `.${nanos}` : ""}`;
713
790
  }
791
+ function fireworksAccountsUrl(pageToken) {
792
+ const url = new URL("/v1/accounts", FIREWORKS_BILLING_SUMMARY_ORIGIN);
793
+ url.searchParams.set("pageSize", "200");
794
+ if (pageToken !== void 0) url.searchParams.set("pageToken", pageToken);
795
+ return url.toString();
796
+ }
797
+ function fireworksNextPageToken(value) {
798
+ if (value === void 0 || value === null) return void 0;
799
+ if (typeof value !== "string" || !value || value.length > 512) {
800
+ throw new Error("Fireworks accounts listing returned an invalid page token.");
801
+ }
802
+ return value;
803
+ }
804
+ function fireworksBillingSummaryUrl(accountId, startedAt) {
805
+ const dayMs = 24 * 60 * 60 * 1e3;
806
+ const dayFloor = (time) => `${new Date(time).toISOString().slice(0, 10)}T00:00:00Z`;
807
+ const url = new URL(
808
+ `/v1/accounts/${accountId}/billing/summary`,
809
+ FIREWORKS_BILLING_SUMMARY_ORIGIN
810
+ );
811
+ url.searchParams.set(
812
+ "startTime",
813
+ dayFloor(startedAt - (FIREWORKS_SPEND_WINDOW_DAYS - 1) * dayMs)
814
+ );
815
+ url.searchParams.set("endTime", dayFloor(startedAt + dayMs));
816
+ return url.toString();
817
+ }
818
+ function remainingTimeout(timeoutMs, startedAt, description) {
819
+ const remaining = timeoutMs - (Date.now() - startedAt);
820
+ if (remaining <= 0) throw new Error(`Timed out while ${description}.`);
821
+ return remaining;
822
+ }
714
823
  function asObject4(value) {
715
824
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
716
825
  return value;
@@ -1604,7 +1713,7 @@ function isRecord2(value) {
1604
1713
  // src/providers/zai.ts
1605
1714
  var FIVE_HOUR_WINDOW_MINUTES2 = 300;
1606
1715
  var WEEKLY_WINDOW_MINUTES2 = 10080;
1607
- function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt) {
1716
+ function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt, plan) {
1608
1717
  const data = asObject11(payload.data);
1609
1718
  if (!data) throw new Error("Z.AI quota response data was not an object.");
1610
1719
  const limits = Array.isArray(data.limits) ? data.limits : [];
@@ -1620,14 +1729,20 @@ function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt)
1620
1729
  addCountBucket(buckets, limit, "mcp-monthly", "MCP monthly allowance");
1621
1730
  addUsageDetailMetrics(metrics, limit.usageDetails);
1622
1731
  } else if (isPlanUsage && unit === 3) {
1623
- addPercentBucket(buckets, limit, "five-hour", "5h window", FIVE_HOUR_WINDOW_MINUTES2);
1732
+ addPercentBucket(
1733
+ buckets,
1734
+ limit,
1735
+ "five-hour",
1736
+ sessionWindowLabel(limit),
1737
+ sessionWindowMinutes(limit)
1738
+ );
1624
1739
  } else if (isPlanUsage && unit === 6) {
1625
1740
  const used = asNonnegativeNumber4(limit.currentValue);
1626
1741
  const quota = asNonnegativeNumber4(limit.usage);
1627
1742
  if (used !== void 0 && quota !== void 0) {
1628
- addCountBucket(buckets, limit, "weekly", "Weekly window", WEEKLY_WINDOW_MINUTES2);
1743
+ addCountBucket(buckets, limit, "weekly", "Weekly window", weeklyWindowMinutes(limit));
1629
1744
  } else {
1630
- addPercentBucket(buckets, limit, "weekly", "Weekly window", WEEKLY_WINDOW_MINUTES2);
1745
+ addPercentBucket(buckets, limit, "weekly", "Weekly window", weeklyWindowMinutes(limit));
1631
1746
  }
1632
1747
  }
1633
1748
  }
@@ -1636,7 +1751,12 @@ function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt)
1636
1751
  }
1637
1752
  const notes = [];
1638
1753
  const level = asString5(data.level);
1639
- if (level) notes.push(`Plan: ${level}`);
1754
+ const planLabel = plan?.name ?? level;
1755
+ if (planLabel) {
1756
+ notes.push(
1757
+ plan?.renewsAt ? `Plan: ${planLabel} \xB7 renews ${plan.renewsAt}` : `Plan: ${planLabel}`
1758
+ );
1759
+ }
1640
1760
  return {
1641
1761
  providerId,
1642
1762
  providerName,
@@ -1648,6 +1768,57 @@ function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt)
1648
1768
  ...notes.length > 0 ? { notes } : {}
1649
1769
  };
1650
1770
  }
1771
+ function normalizeZaiSubscriptionPayload(payload) {
1772
+ if (payload.success === false) return void 0;
1773
+ if (typeof payload.code === "number" && payload.code !== 0 && payload.code !== 200) {
1774
+ return void 0;
1775
+ }
1776
+ if (!Array.isArray(payload.data)) return void 0;
1777
+ const candidates = [];
1778
+ for (const raw of payload.data) {
1779
+ const entry = asObject11(raw);
1780
+ if (!entry) continue;
1781
+ const name = asString5(entry.productName);
1782
+ if (!name) continue;
1783
+ const renewsAt = planRenewalDate(entry.nextRenewTime);
1784
+ const status = asString5(entry.status)?.toUpperCase();
1785
+ const inCurrentPeriod = asBoolean(entry.inCurrentPeriod);
1786
+ candidates.push({
1787
+ plan: { name, ...renewsAt !== void 0 ? { renewsAt } : {} },
1788
+ ...status !== void 0 ? { status } : {},
1789
+ ...inCurrentPeriod !== void 0 ? { inCurrentPeriod } : {}
1790
+ });
1791
+ }
1792
+ const hasStateMetadata = candidates.some(
1793
+ (candidate) => candidate.status !== void 0 || candidate.inCurrentPeriod !== void 0
1794
+ );
1795
+ if (!hasStateMetadata) return candidates[0]?.plan;
1796
+ return candidates.find(
1797
+ (candidate) => candidate.inCurrentPeriod === true && candidate.status === "VALID"
1798
+ )?.plan ?? candidates.find(
1799
+ (candidate) => candidate.inCurrentPeriod === true && candidate.status === void 0
1800
+ )?.plan ?? candidates.find(
1801
+ (candidate) => candidate.status === "VALID" && candidate.inCurrentPeriod === void 0
1802
+ )?.plan;
1803
+ }
1804
+ function planRenewalDate(value) {
1805
+ if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}/u.test(value)) return value.slice(0, 10);
1806
+ const millis = asNonnegativeNumber4(value);
1807
+ if (millis === void 0 || millis === 0) return void 0;
1808
+ return new Date(millis).toISOString().slice(0, 10);
1809
+ }
1810
+ function sessionWindowMinutes(limit) {
1811
+ const hours = asPositiveNumber(limit.number);
1812
+ return hours === void 0 ? FIVE_HOUR_WINDOW_MINUTES2 : Math.round(hours * 60);
1813
+ }
1814
+ function sessionWindowLabel(limit) {
1815
+ const minutes = sessionWindowMinutes(limit);
1816
+ return minutes === FIVE_HOUR_WINDOW_MINUTES2 ? "5h window" : `${Math.round(minutes / 60)}h window`;
1817
+ }
1818
+ function weeklyWindowMinutes(limit) {
1819
+ const weeks = asPositiveNumber(limit.number);
1820
+ return weeks === void 0 ? WEEKLY_WINDOW_MINUTES2 : Math.round(weeks * WEEKLY_WINDOW_MINUTES2);
1821
+ }
1651
1822
  function addPercentBucket(buckets, limit, id, label, windowMinutes) {
1652
1823
  const used = asNonnegativeNumber4(limit.percentage);
1653
1824
  if (used === void 0) return;
@@ -1703,6 +1874,16 @@ function asNonnegativeNumber4(value) {
1703
1874
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
1704
1875
  return value;
1705
1876
  }
1877
+ function asPositiveNumber(value) {
1878
+ const number = asNonnegativeNumber4(value);
1879
+ return number !== void 0 && number > 0 ? number : void 0;
1880
+ }
1881
+ function asBoolean(value) {
1882
+ if (typeof value === "boolean") return value;
1883
+ if (value === 1) return true;
1884
+ if (value === 0) return false;
1885
+ return void 0;
1886
+ }
1706
1887
  function asEpochSeconds2(value) {
1707
1888
  const millis = asNonnegativeNumber4(value);
1708
1889
  if (millis === void 0) return void 0;
@@ -1715,14 +1896,104 @@ function clampPercent3(value) {
1715
1896
  return Math.min(100, Math.max(0, value));
1716
1897
  }
1717
1898
 
1899
+ // src/usage-targets.ts
1900
+ var MAX_TARGETS = 1e3;
1901
+ var MAX_TARGET_ID_CHARS = 256;
1902
+ var MAX_TARGET_LABEL_CHARS = 120;
1903
+ var MAX_TARGET_DESCRIPTION_CHARS = 180;
1904
+ async function resolveUsageTarget(adapter, auth, rememberedTargetId, signal, timeoutMs, guard) {
1905
+ if (!adapter.targets) return { kind: "selected" };
1906
+ if (rememberedTargetId !== void 0 && !isBoundedTargetId(rememberedTargetId)) {
1907
+ throw new Error(`The remembered ${adapter.targets.singularLabel} identifier was invalid.`);
1908
+ }
1909
+ const choices = await listUsageTargets(adapter, auth, signal, timeoutMs, guard);
1910
+ if (rememberedTargetId) {
1911
+ return choices.some((choice) => choice.id === rememberedTargetId) ? { kind: "selected", targetId: rememberedTargetId } : { kind: "selection-required", choices };
1912
+ }
1913
+ if (choices.length === 1) return { kind: "selected", targetId: choices[0]?.id };
1914
+ return { kind: "selection-required", choices };
1915
+ }
1916
+ async function listUsageTargets(adapter, auth, signal, timeoutMs, guard) {
1917
+ if (!adapter.targets) return [];
1918
+ const startedAt = Date.now();
1919
+ await guard();
1920
+ const listed = await adapter.targets.list(
1921
+ auth,
1922
+ signal,
1923
+ remainingTargetTimeout(timeoutMs, startedAt),
1924
+ guard
1925
+ );
1926
+ await guard();
1927
+ remainingTargetTimeout(timeoutMs, startedAt);
1928
+ const choices = normalizeUsageTargets(listed);
1929
+ if (choices.length === 0) {
1930
+ throw new Error(`${adapter.targets.pluralLabel} discovery returned no choices.`);
1931
+ }
1932
+ return choices;
1933
+ }
1934
+ function normalizeUsageTargets(targets) {
1935
+ if (!Array.isArray(targets)) throw new Error("Target discovery did not return a choices array.");
1936
+ if (targets.length > MAX_TARGETS) {
1937
+ throw new Error(`Target discovery exceeded ${MAX_TARGETS} choices.`);
1938
+ }
1939
+ const seen = /* @__PURE__ */ new Set();
1940
+ return targets.map((target) => {
1941
+ if (!target || typeof target !== "object" || Array.isArray(target)) {
1942
+ throw new Error("Target discovery returned an invalid choice.");
1943
+ }
1944
+ const { id, label, description } = target;
1945
+ if (!isBoundedTargetId(id)) throw new Error("Target discovery returned an invalid ID.");
1946
+ if (seen.has(id)) throw new Error(`Target discovery repeated ${id}.`);
1947
+ seen.add(id);
1948
+ if (typeof label !== "string") {
1949
+ throw new Error("Target discovery returned an invalid display label.");
1950
+ }
1951
+ if (description !== void 0 && typeof description !== "string") {
1952
+ throw new Error("Target discovery returned an invalid description.");
1953
+ }
1954
+ const safeLabel2 = sanitizeDisplayText(label, MAX_TARGET_LABEL_CHARS);
1955
+ if (!safeLabel2) throw new Error("Target discovery returned an empty display label.");
1956
+ const safeDescription = description ? sanitizeDisplayText(description, MAX_TARGET_DESCRIPTION_CHARS) : void 0;
1957
+ return { id, label: safeLabel2, ...safeDescription ? { description: safeDescription } : {} };
1958
+ });
1959
+ }
1960
+ function createUsageTargetSelectOptions(targets) {
1961
+ const normalized = normalizeUsageTargets(targets);
1962
+ const ids = /* @__PURE__ */ new Map();
1963
+ const options = normalized.map((target) => {
1964
+ const base = target.description ? `${target.label} \u2014 ${target.description}` : target.label;
1965
+ let option = base;
1966
+ if (ids.has(option)) {
1967
+ const safeId = sanitizeDisplayText(target.id, 80) || "target";
1968
+ option = `${base} \xB7 ${safeId}`;
1969
+ let duplicate = 2;
1970
+ while (ids.has(option)) {
1971
+ option = `${base} \xB7 ${safeId} (${duplicate})`;
1972
+ duplicate += 1;
1973
+ }
1974
+ }
1975
+ ids.set(option, target.id);
1976
+ return option;
1977
+ });
1978
+ return { options, targetIdFor: (option) => ids.get(option) };
1979
+ }
1980
+ function remainingTargetTimeout(timeoutMs, startedAt) {
1981
+ const remaining = timeoutMs - (Date.now() - startedAt);
1982
+ if (remaining <= 0) throw new Error("Timed out while discovering provider targets.");
1983
+ return remaining;
1984
+ }
1985
+ function isBoundedTargetId(value) {
1986
+ return typeof value === "string" && value.length > 0 && value.length <= MAX_TARGET_ID_CHARS && ![...value].some((character) => {
1987
+ const codePoint = character.codePointAt(0) ?? 0;
1988
+ return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
1989
+ });
1990
+ }
1991
+
1718
1992
  // src/query.ts
1719
1993
  var BASETEN_BILLING_USAGE_URL = "https://api.baseten.co/v1/billing/usage_summary";
1720
1994
  var BASETEN_USAGE_WINDOW_DAYS = 30;
1721
1995
  var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
1722
1996
  var DEEPSEEK_BALANCE_URL = "https://api.deepseek.com/user/balance";
1723
- var FIREWORKS_BILLING_SUMMARY_ORIGIN = "https://api.fireworks.ai";
1724
- var FIREWORKS_SPEND_WINDOW_DAYS = 30;
1725
- var FIREWORKS_MAX_ACCOUNT_PAGES = 5;
1726
1997
  var GITHUB_COPILOT_USAGE_URL = "https://api.github.com/copilot_internal/user";
1727
1998
  var OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
1728
1999
  var VERCEL_AI_GATEWAY_CREDITS_URL = "https://ai-gateway.vercel.sh/v1/credits";
@@ -1761,7 +2032,7 @@ var SUPPORTED_ADAPTERS = [
1761
2032
  basetenBillingUsageUrl(windowAt),
1762
2033
  auth,
1763
2034
  signal,
1764
- remainingTimeout(timeoutMs, startedAt, "fetching Baseten billing usage"),
2035
+ remainingTimeout2(timeoutMs, startedAt, "fetching Baseten billing usage"),
1765
2036
  "Baseten billing usage endpoint",
1766
2037
  { redirect: "error" }
1767
2038
  );
@@ -1854,7 +2125,7 @@ var SUPPORTED_ADAPTERS = [
1854
2125
  VERCEL_AI_GATEWAY_CREDITS_URL,
1855
2126
  auth,
1856
2127
  signal,
1857
- remainingTimeout(timeoutMs, startedAt, "fetching Vercel AI Gateway credits"),
2128
+ remainingTimeout2(timeoutMs, startedAt, "fetching Vercel AI Gateway credits"),
1858
2129
  "Vercel AI Gateway credits endpoint",
1859
2130
  { redirect: "error" }
1860
2131
  );
@@ -1862,35 +2133,7 @@ var SUPPORTED_ADAPTERS = [
1862
2133
  return normalizeVercelAIGatewayCreditsPayload(payload, Date.now());
1863
2134
  }
1864
2135
  },
1865
- {
1866
- id: "fireworks",
1867
- displayName: "Fireworks",
1868
- semantics: { kind: "api-key", label: "Fireworks API spend" },
1869
- async query(auth, signal, timeoutMs, guard, settings) {
1870
- if (!guard) throw new Error("Fireworks API spend requires request-boundary revalidation.");
1871
- const startedAt = Date.now();
1872
- await guard();
1873
- const accountId = await resolveFireworksAccountId(
1874
- auth,
1875
- signal,
1876
- remainingTimeout(timeoutMs, startedAt, "resolving the Fireworks account"),
1877
- guard,
1878
- settings?.fireworksAccountId
1879
- );
1880
- await guard();
1881
- const billingWindowAt = Date.now();
1882
- const payload = await fetchProviderJson(
1883
- fireworksBillingSummaryUrl(accountId, billingWindowAt),
1884
- auth,
1885
- signal,
1886
- remainingTimeout(timeoutMs, startedAt, "fetching Fireworks rated spend"),
1887
- "Fireworks billing summary endpoint",
1888
- { redirect: "error" }
1889
- );
1890
- await guard();
1891
- return normalizeFireworksBillingSummaryPayload(payload, accountId, Date.now());
1892
- }
1893
- },
2136
+ createFireworksAdapter(fetchProviderJson),
1894
2137
  {
1895
2138
  id: "opencode-go",
1896
2139
  displayName: "OpenCode Go",
@@ -1958,35 +2201,16 @@ var SUPPORTED_ADAPTERS = [
1958
2201
  id: "zai",
1959
2202
  displayName: "Z.AI",
1960
2203
  semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
1961
- async query(auth, signal, timeoutMs) {
1962
- const payload = await fetchProviderJson(
1963
- zaiMonitorUrl(auth.model.baseUrl),
1964
- zaiMonitorAuth(auth),
1965
- signal,
1966
- timeoutMs,
1967
- "Z.AI quota endpoint"
1968
- );
1969
- return normalizeZaiQuotaPayload("zai", "Z.AI", payload, Date.now());
2204
+ async query(auth, signal, timeoutMs, guard) {
2205
+ return queryZaiUsage("zai", "Z.AI", auth, signal, timeoutMs, guard);
1970
2206
  }
1971
2207
  },
1972
2208
  {
1973
2209
  id: "zai-coding-cn",
1974
2210
  displayName: "Z.AI Coding CN",
1975
2211
  semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
1976
- async query(auth, signal, timeoutMs) {
1977
- const payload = await fetchProviderJson(
1978
- zaiMonitorUrl(auth.model.baseUrl),
1979
- zaiMonitorAuth(auth),
1980
- signal,
1981
- timeoutMs,
1982
- "Z.AI Coding CN quota endpoint"
1983
- );
1984
- return normalizeZaiQuotaPayload(
1985
- "zai-coding-cn",
1986
- "Z.AI Coding CN",
1987
- payload,
1988
- Date.now()
1989
- );
2212
+ async query(auth, signal, timeoutMs, guard) {
2213
+ return queryZaiUsage("zai-coding-cn", "Z.AI Coding CN", auth, signal, timeoutMs, guard);
1990
2214
  }
1991
2215
  }
1992
2216
  ];
@@ -2010,7 +2234,7 @@ var XAI_ADAPTER = {
2010
2234
  XAI_USER_URL,
2011
2235
  clientAuth,
2012
2236
  signal,
2013
- remainingTimeout(timeoutMs, startedAt),
2237
+ remainingTimeout2(timeoutMs, startedAt),
2014
2238
  "xAI consumer identity endpoint",
2015
2239
  { redirect: "error", userAgent: false }
2016
2240
  );
@@ -2026,7 +2250,7 @@ var XAI_ADAPTER = {
2026
2250
  XAI_BILLING_URL,
2027
2251
  billingAuth,
2028
2252
  signal,
2029
- remainingTimeout(timeoutMs, startedAt),
2253
+ remainingTimeout2(timeoutMs, startedAt),
2030
2254
  "xAI consumer billing endpoint",
2031
2255
  { redirect: "error", userAgent: false }
2032
2256
  );
@@ -2054,6 +2278,12 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
2054
2278
  );
2055
2279
  if (!model) return void 0;
2056
2280
  const registry = ctx.modelRegistry;
2281
+ const provider = registry.getProvider?.(adapter.id);
2282
+ if (provider?.baseUrl && !hasOfficialUrlOrigin(provider.baseUrl, adapter.id)) {
2283
+ throw new Error(
2284
+ `${adapter.displayName} usage cannot send an overridden provider credential to the official usage endpoint.`
2285
+ );
2286
+ }
2057
2287
  let modelAuth;
2058
2288
  const currentModel = ctx.model?.provider === adapter.id ? ctx.model : void 0;
2059
2289
  const resolveCurrentModelAuth = async () => {
@@ -2076,25 +2306,64 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
2076
2306
  );
2077
2307
  }
2078
2308
  if (resolveSelectedAuthLast) modelAuth = await resolveCurrentModelAuth();
2309
+ if (modelAuth?.baseUrl && !hasOfficialUrlOrigin(modelAuth.baseUrl, adapter.id)) {
2310
+ throw new Error(
2311
+ `${adapter.displayName} usage cannot send model-resolved proxy credentials to the official usage endpoint.`
2312
+ );
2313
+ }
2079
2314
  const auth = modelAuth ?? providerResult?.auth;
2080
2315
  if (!auth) return void 0;
2316
+ const finalize = (resolved) => {
2317
+ const preservedAuth = { ...providerResult?.auth ?? auth };
2318
+ const env = providerResult?.env ?? modelAuth?.env;
2319
+ const source = providerResult?.source;
2320
+ const effectiveBaseUrl = modelAuth?.baseUrl ?? providerResult?.auth.baseUrl ?? provider?.baseUrl ?? model.baseUrl;
2321
+ const redactionInputs = [
2322
+ preservedAuth.apiKey,
2323
+ ...Object.values(preservedAuth.headers ?? {}),
2324
+ ...Object.values(env ?? {}),
2325
+ modelAuth?.apiKey,
2326
+ ...Object.values(modelAuth?.headers ?? {})
2327
+ ].filter((value) => typeof value === "string" && value.length > 0);
2328
+ return {
2329
+ ...resolved,
2330
+ auth: preservedAuth,
2331
+ ...env ? { env: { ...env } } : {},
2332
+ ...source ? { source } : {},
2333
+ effectiveBaseUrl,
2334
+ secrets: [.../* @__PURE__ */ new Set([...resolved.secrets, ...redactionInputs])],
2335
+ fingerprint: fingerprintResolvedAuth(
2336
+ {
2337
+ apiKey: resolved.apiKey,
2338
+ headers: resolved.headers,
2339
+ baseUrl: effectiveBaseUrl,
2340
+ env,
2341
+ source,
2342
+ providerAuth: preservedAuth
2343
+ },
2344
+ salt
2345
+ )
2346
+ };
2347
+ };
2081
2348
  if (adapter.id === "github-copilot") {
2082
2349
  const offered = candidateReader ? candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
2083
2350
  if (!offered.ok) {
2084
2351
  throw new Error("GitHub Copilot OAuth credential discovery failed closed.");
2085
2352
  }
2086
- return resolveGitHubCopilotUsageAuth(
2087
- auth,
2088
- model,
2089
- salt,
2090
- offered.candidates,
2091
- offered.offeredCount === 0
2353
+ return finalize(
2354
+ resolveGitHubCopilotUsageAuth(
2355
+ auth,
2356
+ model,
2357
+ salt,
2358
+ offered.candidates,
2359
+ offered.offeredCount === 0
2360
+ )
2092
2361
  );
2093
2362
  }
2094
2363
  if (adapter.id === "xai") {
2095
2364
  const offered = candidateReader ? candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
2096
2365
  if (!offered.ok) throw new Error("xAI OAuth credential discovery failed closed.");
2097
- return resolveXaiUsageAuth(auth, model, salt, offered.candidates);
2366
+ return finalize(resolveXaiUsageAuth(auth, model, salt, offered.candidates));
2098
2367
  }
2099
2368
  if (adapter.id === "deepseek") {
2100
2369
  const resolvedAuthorization = authorizationFrom(auth);
@@ -2102,10 +2371,10 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
2102
2371
  if (!access) throw new Error("DeepSeek API balance requires Bearer authentication.");
2103
2372
  const authorization2 = `Bearer ${access}`;
2104
2373
  const headers2 = { Authorization: authorization2 };
2105
- return {
2374
+ return finalize({
2106
2375
  apiKey: access,
2107
2376
  headers: headers2,
2108
- fingerprint: fingerprintResolvedAuth({ headers: headers2 }, salt),
2377
+ fingerprint: "",
2109
2378
  secrets: [
2110
2379
  access,
2111
2380
  auth.apiKey,
@@ -2114,7 +2383,7 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
2114
2383
  authorization2
2115
2384
  ].filter((value) => Boolean(value)),
2116
2385
  model
2117
- };
2386
+ });
2118
2387
  }
2119
2388
  const authorization = authorizationFrom(auth);
2120
2389
  if (!authorization) return void 0;
@@ -2122,17 +2391,41 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
2122
2391
  const secrets = [auth.apiKey, headerValue(auth.headers, "Authorization"), authorization].filter(
2123
2392
  (value) => Boolean(value)
2124
2393
  );
2125
- return {
2394
+ return finalize({
2126
2395
  apiKey: auth.apiKey,
2127
2396
  headers,
2128
- fingerprint: fingerprintResolvedAuth({ headers }, salt),
2397
+ fingerprint: "",
2129
2398
  secrets,
2130
2399
  model
2131
- };
2400
+ });
2132
2401
  }
2133
- async function queryProviderUsage(adapter, auth, signal, timeoutMs, guard, settings) {
2402
+ async function queryProviderUsage(adapter, auth, signal, timeoutMs, guard, targetOrSettings) {
2403
+ const startedAt = Date.now();
2404
+ let targetId = typeof targetOrSettings === "string" ? targetOrSettings : adapter.id === "fireworks" ? targetOrSettings?.fireworksAccountId : void 0;
2405
+ let resolvedLegacyFireworksTarget = false;
2134
2406
  try {
2135
- return await adapter.query(auth, signal, timeoutMs, guard, settings);
2407
+ if (adapter.id === "fireworks" && typeof targetOrSettings !== "string" && adapter.targets && guard) {
2408
+ const target = await resolveUsageTarget(
2409
+ adapter,
2410
+ auth,
2411
+ targetId,
2412
+ signal,
2413
+ remainingTimeout2(timeoutMs, startedAt, "resolving the Fireworks account"),
2414
+ guard
2415
+ );
2416
+ if (target.kind === "selection-required") {
2417
+ throw new Error("Fireworks account selection is required.");
2418
+ }
2419
+ targetId = target.targetId;
2420
+ resolvedLegacyFireworksTarget = true;
2421
+ }
2422
+ return await adapter.query(
2423
+ auth,
2424
+ signal,
2425
+ resolvedLegacyFireworksTarget ? remainingTimeout2(timeoutMs, startedAt, `querying ${adapter.displayName} usage`) : timeoutMs,
2426
+ guard,
2427
+ targetId
2428
+ );
2136
2429
  } catch (error) {
2137
2430
  if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
2138
2431
  throw new Error(redactUsageError(errorMessage(error), auth.secrets));
@@ -2431,7 +2724,7 @@ function hasOfficialUrlOrigin(value, providerId) {
2431
2724
  }
2432
2725
  if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
2433
2726
  if (providerId === "deepseek") return url.origin === "https://api.deepseek.com";
2434
- if (providerId === "fireworks") return url.origin === FIREWORKS_BILLING_SUMMARY_ORIGIN;
2727
+ if (providerId === "fireworks") return url.origin === "https://api.fireworks.ai";
2435
2728
  if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
2436
2729
  if (providerId === "vercel-ai-gateway") return url.origin === "https://ai-gateway.vercel.sh";
2437
2730
  if (providerId === "opencode-go") return url.origin === "https://opencode.ai";
@@ -2487,7 +2780,7 @@ async function queryMiniMaxUsage(providerId, auth, signal, timeoutMs, guard) {
2487
2780
  `${MINIMAX_API_ROOTS[providerId]}${path}`,
2488
2781
  auth,
2489
2782
  signal,
2490
- remainingTimeout(timeoutMs, startedAt, "fetching MiniMax usage"),
2783
+ remainingTimeout2(timeoutMs, startedAt, "fetching MiniMax usage"),
2491
2784
  "MiniMax usage endpoint",
2492
2785
  { redirect: "error" }
2493
2786
  );
@@ -2502,98 +2795,25 @@ async function queryMoonshotBalance(providerId, auth, signal, timeoutMs, guard)
2502
2795
  MOONSHOT_BALANCE_URLS[providerId],
2503
2796
  auth,
2504
2797
  signal,
2505
- remainingTimeout(timeoutMs, startedAt, "fetching Moonshot AI balance"),
2798
+ remainingTimeout2(timeoutMs, startedAt, "fetching Moonshot AI balance"),
2506
2799
  "Moonshot AI balance endpoint",
2507
2800
  { redirect: "error" }
2508
2801
  );
2509
2802
  await guard();
2510
2803
  return normalizeMoonshotBalancePayload(providerId, payload, Date.now());
2511
2804
  }
2512
- function remainingTimeout(timeoutMs, startedAt, description = "fetching xAI consumer usage") {
2805
+ function remainingTimeout2(timeoutMs, startedAt, description = "fetching xAI consumer usage") {
2513
2806
  const remaining = timeoutMs - (Date.now() - startedAt);
2514
2807
  if (remaining <= 0) throw new Error(`Timed out while ${description}.`);
2515
2808
  return remaining;
2516
2809
  }
2517
- async function resolveFireworksAccountId(auth, signal, timeoutMs, guard, configuredAccountId) {
2518
- if (configuredAccountId !== void 0 && !isFireworksAccountId(configuredAccountId)) {
2519
- throw new Error("The Fireworks account setting was not a safe account slug.");
2520
- }
2521
- const startedAt = Date.now();
2522
- const accounts = [];
2523
- let pageToken;
2524
- for (let page = 0; page < FIREWORKS_MAX_ACCOUNT_PAGES; page += 1) {
2525
- await guard();
2526
- const payload = await fetchProviderJson(
2527
- fireworksAccountsUrl(pageToken),
2528
- auth,
2529
- signal,
2530
- remainingTimeout(timeoutMs, startedAt, "fetching Fireworks accounts"),
2531
- "Fireworks accounts endpoint",
2532
- { redirect: "error" }
2533
- );
2534
- for (const accountId of normalizeFireworksAccountsPayload(
2535
- payload
2536
- )) {
2537
- if (accounts.includes(accountId)) {
2538
- throw new Error(`Fireworks accounts listing repeated ${accountId}.`);
2539
- }
2540
- accounts.push(accountId);
2541
- if (configuredAccountId === accountId) return accountId;
2542
- }
2543
- pageToken = fireworksNextPageToken(payload.nextPageToken);
2544
- if (!pageToken) break;
2545
- }
2546
- if (pageToken) {
2547
- throw new Error(
2548
- 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.`
2549
- );
2550
- }
2551
- if (accounts.length === 0) {
2552
- throw new Error("Fireworks account discovery returned no accounts for this API key.");
2553
- }
2554
- if (configuredAccountId) {
2555
- throw new Error(
2556
- "The configured Fireworks account does not match an account visible to this API key."
2557
- );
2558
- }
2559
- if (accounts.length === 1) return accounts[0];
2560
- const preview = accounts.slice(0, 8).join(", ");
2561
- const suffix = accounts.length > 8 ? ` \u2026and ${accounts.length - 8} more` : "";
2562
- throw new Error(
2563
- `The Fireworks key can see ${accounts.length} accounts (${preview}${suffix}); set fireworksAccountId in pi-usage.json to one of them.`
2564
- );
2565
- }
2566
- function fireworksAccountsUrl(pageToken) {
2567
- const url = new URL("/v1/accounts", FIREWORKS_BILLING_SUMMARY_ORIGIN);
2568
- url.searchParams.set("pageSize", "200");
2569
- if (pageToken !== void 0) url.searchParams.set("pageToken", pageToken);
2570
- return url.toString();
2571
- }
2572
- function fireworksNextPageToken(value) {
2573
- if (value === void 0 || value === null) return void 0;
2574
- if (typeof value !== "string" || !value || value.length > 512) {
2575
- throw new Error("Fireworks accounts listing returned an invalid page token.");
2576
- }
2577
- return value;
2578
- }
2579
- function fireworksBillingSummaryUrl(accountId, startedAt) {
2580
- const dayMs = 24 * 60 * 60 * 1e3;
2581
- const dayFloor = (time) => `${new Date(time).toISOString().slice(0, 10)}T00:00:00Z`;
2582
- const url = new URL(
2583
- `/v1/accounts/${accountId}/billing/summary`,
2584
- FIREWORKS_BILLING_SUMMARY_ORIGIN
2585
- );
2586
- url.searchParams.set(
2587
- "startTime",
2588
- dayFloor(startedAt - (FIREWORKS_SPEND_WINDOW_DAYS - 1) * dayMs)
2589
- );
2590
- url.searchParams.set("endTime", dayFloor(startedAt + dayMs));
2591
- return url.toString();
2592
- }
2593
- function zaiMonitorUrl(baseUrl) {
2810
+ function zaiOrigin(baseUrl) {
2594
2811
  const base = baseUrl?.trim();
2595
2812
  if (!base) throw new Error("Z.AI model base URL is unavailable.");
2596
- return `${new URL(base).origin}/api/monitor/usage/quota/limit`;
2813
+ return new URL(base).origin;
2814
+ }
2815
+ function zaiMonitorUrl(baseUrl) {
2816
+ return `${zaiOrigin(baseUrl)}/api/monitor/usage/quota/limit`;
2597
2817
  }
2598
2818
  function zaiMonitorAuth(auth) {
2599
2819
  const authorization = headerValue(auth.headers, "Authorization");
@@ -2601,6 +2821,38 @@ function zaiMonitorAuth(auth) {
2601
2821
  if (token === void 0 || token === authorization) return auth;
2602
2822
  return { ...auth, headers: { ...auth.headers, Authorization: token } };
2603
2823
  }
2824
+ async function queryZaiUsage(providerId, providerName, auth, signal, timeoutMs, guard) {
2825
+ if (!guard) throw new Error("Z.AI usage requires request-boundary revalidation.");
2826
+ const startedAt = Date.now();
2827
+ await guard();
2828
+ const payload = await fetchProviderJson(
2829
+ zaiMonitorUrl(auth.model.baseUrl),
2830
+ zaiMonitorAuth(auth),
2831
+ signal,
2832
+ remainingTimeout2(timeoutMs, startedAt, `fetching ${providerName} quota`),
2833
+ `${providerName} quota endpoint`
2834
+ );
2835
+ await guard();
2836
+ const planTimeoutMs = timeoutMs - (Date.now() - startedAt);
2837
+ const plan = await fetchZaiPlan(providerName, auth, signal, planTimeoutMs);
2838
+ return normalizeZaiQuotaPayload(providerId, providerName, payload, Date.now(), plan);
2839
+ }
2840
+ async function fetchZaiPlan(providerName, auth, signal, timeoutMs) {
2841
+ if (timeoutMs <= 0 || signal.aborted) return void 0;
2842
+ try {
2843
+ const payload = await fetchProviderJson(
2844
+ `${zaiOrigin(auth.model.baseUrl)}/api/biz/subscription/list`,
2845
+ zaiMonitorAuth(auth),
2846
+ signal,
2847
+ timeoutMs,
2848
+ `${providerName} plan endpoint`
2849
+ );
2850
+ return normalizeZaiSubscriptionPayload(payload);
2851
+ } catch (error) {
2852
+ if (isAbortError(error)) throw error;
2853
+ return void 0;
2854
+ }
2855
+ }
2604
2856
  function isAbortError(error) {
2605
2857
  return error instanceof Error && error.name === "AbortError";
2606
2858
  }
@@ -2928,6 +3180,10 @@ function formatProviderStates(states) {
2928
3180
  return states.map((state) => {
2929
3181
  if (state.status === "ready") return formatUsageReport(state.report, state.displayState);
2930
3182
  const label = state.displayState === "current" ? "Current" : "Configured";
3183
+ if (state.status === "selection-required") {
3184
+ return `${state.providerName} \xB7 ${label}
3185
+ Selection required: choose this provider's ${state.singularLabel} by viewing it individually.`;
3186
+ }
2931
3187
  const status = state.status === "auth-unavailable" ? "Authentication unavailable" : state.status === "unsupported" ? "Unsupported" : "Query failed";
2932
3188
  return `${state.providerName} \xB7 ${label}
2933
3189
  ${status}: ${state.message}`;
@@ -3077,6 +3333,10 @@ function formatOpenRouterReport(lines, report2) {
3077
3333
  }
3078
3334
  function formatOpenCodeZenReport(lines, report2) {
3079
3335
  for (const bucket of report2.buckets) {
3336
+ if (bucket.unit === "percent" && bucket.used !== void 0) {
3337
+ lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${formatPercentBucket(bucket)}`);
3338
+ continue;
3339
+ }
3080
3340
  const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
3081
3341
  const used = bucket.used ?? "unavailable";
3082
3342
  lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${used}% used${reset}`);
@@ -3251,8 +3511,7 @@ function formatXaiReport(lines, report2) {
3251
3511
  if (included) {
3252
3512
  let value = "unavailable";
3253
3513
  if (included.unit === "percent" && included.used !== void 0) {
3254
- value = `${included.used}% used`;
3255
- if (included.remaining !== void 0) value += ` \xB7 ${included.remaining}% left`;
3514
+ value = formatPercentBar(included);
3256
3515
  } else if (included.used !== void 0) {
3257
3516
  value = `${formatUsd(included.used)} used`;
3258
3517
  if (included.limit !== void 0) value += ` of ${formatUsd(included.limit)}`;
@@ -3277,12 +3536,13 @@ function formatXaiReport(lines, report2) {
3277
3536
  }
3278
3537
  function formatZaiReport(lines, report2) {
3279
3538
  for (const bucket of report2.buckets) {
3539
+ if (bucket.unit === "percent" && bucket.used !== void 0) {
3540
+ lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${formatPercentBucket(bucket)}`);
3541
+ continue;
3542
+ }
3280
3543
  const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
3281
3544
  let value = "unavailable";
3282
- if (bucket.unit === "percent" && bucket.used !== void 0) {
3283
- value = `${bucket.used}% used`;
3284
- if (bucket.remaining !== void 0) value += ` \xB7 ${bucket.remaining}% left`;
3285
- } else if (bucket.used !== void 0 && bucket.limit !== void 0) {
3545
+ if (bucket.used !== void 0 && bucket.limit !== void 0) {
3286
3546
  value = `${bucket.used} of ${bucket.limit} used`;
3287
3547
  if (bucket.remaining !== void 0) value += ` \xB7 ${bucket.remaining} left`;
3288
3548
  } else if (bucket.used !== void 0) {
@@ -3394,10 +3654,12 @@ function compactLimitLabel(label) {
3394
3654
  return (suffix || normalized).toLowerCase().replace(/\s+/g, " ");
3395
3655
  }
3396
3656
  function formatPercentBucket(bucket) {
3657
+ return `${formatPercentBar(bucket)}${bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : ""}`;
3658
+ }
3659
+ function formatPercentBar(bucket) {
3397
3660
  const remaining = clampPercent4(bucket.remaining ?? 0);
3398
3661
  const filled = Math.round(remaining / 100 * BAR_SEGMENTS);
3399
- const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
3400
- return `[${"\u2588".repeat(filled)}${"\u2591".repeat(BAR_SEGMENTS - filled)}] ${remaining.toFixed(0)}% left${reset}`;
3662
+ return `[${"\u2588".repeat(filled)}${"\u2591".repeat(BAR_SEGMENTS - filled)}] ${remaining.toFixed(0)}% left`;
3401
3663
  }
3402
3664
  function formatWindowLabel(minutes, fallback, compact) {
3403
3665
  if (!minutes || !Number.isFinite(minutes) || minutes <= 0) {
@@ -3458,7 +3720,8 @@ var USAGE_SETTINGS_FILE = "pi-usage.json";
3458
3720
  var MAX_USAGE_SETTINGS_BYTES = 64 * 1024;
3459
3721
  var DEFAULT_USAGE_SETTINGS = Object.freeze({
3460
3722
  codexFastMode: false,
3461
- codexStatusResetCountdown: true
3723
+ codexStatusResetCountdown: true,
3724
+ selectedTargets: Object.freeze({})
3462
3725
  });
3463
3726
  function usageSettingsPath() {
3464
3727
  return join(getAgentDir(), USAGE_SETTINGS_FILE);
@@ -3474,10 +3737,16 @@ function normalizeUsageSettings(value) {
3474
3737
  if (Object.hasOwn(value, "fireworksAccountId") && !isFireworksAccountId(value.fireworksAccountId)) {
3475
3738
  return void 0;
3476
3739
  }
3740
+ const selectedTargets = normalizeSelectedTargets(value.selectedTargets);
3741
+ if (Object.hasOwn(value, "selectedTargets") && !selectedTargets) return void 0;
3742
+ const effectiveTargets = { ...selectedTargets ?? {} };
3743
+ if (!effectiveTargets.fireworks && isFireworksAccountId(value.fireworksAccountId)) {
3744
+ effectiveTargets.fireworks = value.fireworksAccountId;
3745
+ }
3477
3746
  return {
3478
3747
  codexFastMode: typeof value.codexFastMode === "boolean" ? value.codexFastMode : DEFAULT_USAGE_SETTINGS.codexFastMode,
3479
3748
  codexStatusResetCountdown: typeof value.codexStatusResetCountdown === "boolean" ? value.codexStatusResetCountdown : DEFAULT_USAGE_SETTINGS.codexStatusResetCountdown,
3480
- ...isFireworksAccountId(value.fireworksAccountId) ? { fireworksAccountId: value.fireworksAccountId } : {}
3749
+ selectedTargets: effectiveTargets
3481
3750
  };
3482
3751
  }
3483
3752
  async function loadUsageSettings(path = usageSettingsPath(), signal) {
@@ -3553,19 +3822,84 @@ function createUsageSettingsRuntime(options = {}) {
3553
3822
  state = saved;
3554
3823
  return structuredClone(state);
3555
3824
  }),
3825
+ updateSelectedTarget: (providerId, targetId, signal, checkPublishedSelection) => enqueue(async () => {
3826
+ const transaction = await saveUsageTargetSelection(
3827
+ path,
3828
+ providerId,
3829
+ targetId,
3830
+ operations,
3831
+ signal
3832
+ );
3833
+ try {
3834
+ await checkPublishedSelection?.();
3835
+ throwIfAborted(signal);
3836
+ } catch (error) {
3837
+ try {
3838
+ await restoreUsageSettingsState(
3839
+ path,
3840
+ transaction.saved,
3841
+ transaction.previous,
3842
+ operations
3843
+ );
3844
+ state = transaction.previous;
3845
+ } catch (rollbackError) {
3846
+ state = await loadUsageSettings(path);
3847
+ throw new AggregateError(
3848
+ [error, rollbackError],
3849
+ "Target selection changed after publication and pi-usage.json rollback failed"
3850
+ );
3851
+ }
3852
+ throw error;
3853
+ }
3854
+ state = transaction.saved;
3855
+ return structuredClone(state);
3856
+ }),
3556
3857
  flush: () => queue
3557
3858
  };
3558
3859
  }
3559
3860
  async function saveUsageSettingsPatch(path, patch, operations, signal) {
3861
+ return saveUsageSettingsDocument(
3862
+ path,
3863
+ (document) => {
3864
+ for (const [key, value] of Object.entries(patch)) {
3865
+ if (value === void 0) delete document[key];
3866
+ else document[key] = value;
3867
+ }
3868
+ },
3869
+ operations,
3870
+ signal
3871
+ );
3872
+ }
3873
+ async function saveUsageTargetSelection(path, providerId, targetId, operations, signal) {
3874
+ if (!isProviderId(providerId) || !isBoundedTargetId(targetId)) {
3875
+ throw new Error("Refusing to save an invalid usage target selection");
3876
+ }
3877
+ const previous = await loadUsageSettings(path, signal);
3878
+ const saved = await saveUsageSettingsDocument(
3879
+ path,
3880
+ (document) => {
3881
+ document.selectedTargets = {
3882
+ ...normalizeSelectedTargets(document.selectedTargets) ?? {},
3883
+ [providerId]: targetId
3884
+ };
3885
+ if (providerId === "fireworks") delete document.fireworksAccountId;
3886
+ },
3887
+ operations,
3888
+ signal,
3889
+ previous
3890
+ );
3891
+ return { saved, previous };
3892
+ }
3893
+ async function saveUsageSettingsDocument(path, mutate, operations, signal, expected) {
3560
3894
  const latest = await loadUsageSettings(path, signal);
3561
3895
  if (latest.kind === "invalid") {
3562
3896
  throw new Error("Cannot overwrite an invalid pi-usage.json; repair it and reload first");
3563
3897
  }
3564
- const document = { ...latest.document };
3565
- for (const [key, value] of Object.entries(patch)) {
3566
- if (value === void 0) delete document[key];
3567
- else document[key] = value;
3898
+ if (expected && !sameUsageSettingsDocument(latest, expected)) {
3899
+ throw new Error("pi-usage.json changed while saving; retry the action");
3568
3900
  }
3901
+ const document = { ...latest.document };
3902
+ mutate(document);
3569
3903
  const settings = normalizeUsageSettings(document);
3570
3904
  if (!settings) throw new Error("Refusing to save invalid pi-usage settings");
3571
3905
  const directory = dirname(path);
@@ -3592,6 +3926,32 @@ async function saveUsageSettingsPatch(path, patch, operations, signal) {
3592
3926
  }
3593
3927
  return { kind: "loaded", path, settings, document };
3594
3928
  }
3929
+ async function restoreUsageSettingsState(path, published, previous, operations) {
3930
+ if (previous.kind === "missing") {
3931
+ const current = await loadUsageSettings(path);
3932
+ if (!sameUsageSettingsDocument(current, published)) {
3933
+ throw new Error("pi-usage.json changed before target selection rollback");
3934
+ }
3935
+ await rm(path);
3936
+ return;
3937
+ }
3938
+ if (previous.kind !== "loaded" || !previous.document) {
3939
+ throw new Error("Cannot restore invalid prior pi-usage.json settings");
3940
+ }
3941
+ await saveUsageSettingsDocument(
3942
+ path,
3943
+ (document) => {
3944
+ for (const key of Object.keys(document)) delete document[key];
3945
+ Object.assign(document, previous.document);
3946
+ },
3947
+ operations,
3948
+ void 0,
3949
+ published
3950
+ );
3951
+ }
3952
+ function sameUsageSettingsDocument(left, right) {
3953
+ return left.kind === right.kind && JSON.stringify(left.document) === JSON.stringify(right.document);
3954
+ }
3595
3955
  async function chmodPrivate(path) {
3596
3956
  await chmod(path, 384);
3597
3957
  }
@@ -3604,6 +3964,19 @@ function isRecord3(value) {
3604
3964
  function isNodeError(error) {
3605
3965
  return error instanceof Error && "code" in error;
3606
3966
  }
3967
+ function normalizeSelectedTargets(value) {
3968
+ if (value === void 0) return {};
3969
+ if (!isRecord3(value)) return void 0;
3970
+ const targets = {};
3971
+ for (const [providerId, targetId] of Object.entries(value)) {
3972
+ if (!isProviderId(providerId) || !isBoundedTargetId(targetId)) return void 0;
3973
+ targets[providerId] = targetId;
3974
+ }
3975
+ return targets;
3976
+ }
3977
+ function isProviderId(value) {
3978
+ return /^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$/u.test(value);
3979
+ }
3607
3980
 
3608
3981
  // src/usage.ts
3609
3982
  import { randomUUID as randomUUID2 } from "node:crypto";
@@ -3814,8 +4187,6 @@ import {
3814
4187
  SettingsList,
3815
4188
  Text
3816
4189
  } from "@earendil-works/pi-tui";
3817
- var AUTO = "Auto";
3818
- var EDIT = "Edit\u2026";
3819
4190
  var OFF = "Off";
3820
4191
  var ON = "On";
3821
4192
  async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent, onApplied) {
@@ -3823,31 +4194,13 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
3823
4194
  if (ctx.hasUI) ctx.ui.notify(`Edit settings manually: ${settingsRuntime.get().path}`, "info");
3824
4195
  return false;
3825
4196
  }
3826
- let changed = false;
3827
- while (!parentSignal.aborted && isCurrent()) {
3828
- const result = await showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, onApplied);
3829
- if (!result) return changed;
3830
- changed ||= result.changed;
3831
- if (!result.editFireworksAccount) return changed;
3832
- changed ||= await editFireworksAccount(
3833
- ctx,
3834
- settingsRuntime,
3835
- parentSignal,
3836
- isCurrent,
3837
- onApplied
3838
- );
3839
- }
3840
- return changed;
3841
- }
3842
- async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, onApplied) {
3843
- return ctx.ui.custom((tui, theme, _keybindings, done) => {
4197
+ return await ctx.ui.custom((tui, theme, _keybindings, done) => {
3844
4198
  const localController = new AbortController();
3845
4199
  const signal = AbortSignal.any([parentSignal, localController.signal]);
3846
4200
  let changed = false;
3847
4201
  let closing = false;
3848
4202
  let saveQueue = Promise.resolve();
3849
4203
  const state = settingsRuntime.get();
3850
- const fireworksValue = state.settings.fireworksAccountId ?? AUTO;
3851
4204
  const items = [
3852
4205
  {
3853
4206
  id: "codexFastMode",
@@ -3862,13 +4215,6 @@ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, o
3862
4215
  description: "Show time remaining until each Codex usage limit resets.",
3863
4216
  currentValue: state.settings.codexStatusResetCountdown ? ON : OFF,
3864
4217
  values: [OFF, ON]
3865
- },
3866
- {
3867
- id: "fireworksAccountId",
3868
- label: "Fireworks account",
3869
- description: "Select Edit to enter a visible account slug, or submit blank to clear it.",
3870
- currentValue: fireworksValue,
3871
- values: state.settings.fireworksAccountId ? [state.settings.fireworksAccountId, EDIT] : [AUTO, EDIT]
3872
4218
  }
3873
4219
  ];
3874
4220
  const container = new Container();
@@ -3878,13 +4224,13 @@ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, o
3878
4224
  if (closing) return;
3879
4225
  closing = true;
3880
4226
  localController.abort();
3881
- done({ changed, editFireworksAccount: false });
4227
+ done(changed);
3882
4228
  };
3883
4229
  const queueUpdate = (id, requested, display) => {
3884
4230
  saveQueue = saveQueue.then(async () => {
3885
4231
  const previous = settingsRuntime.get().settings[id];
3886
4232
  if (settingsRuntime.get().kind === "invalid") {
3887
- settingsList.updateValue(id, displaySetting(id, previous));
4233
+ settingsList.updateValue(id, previous ? ON : OFF);
3888
4234
  if (!signal.aborted && isCurrent()) {
3889
4235
  ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
3890
4236
  tui.requestRender();
@@ -3895,7 +4241,7 @@ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, o
3895
4241
  await settingsRuntime.update({ [id]: requested }, signal);
3896
4242
  } catch (error) {
3897
4243
  if (signal.aborted || !isCurrent()) return;
3898
- settingsList.updateValue(id, displaySetting(id, previous));
4244
+ settingsList.updateValue(id, previous ? ON : OFF);
3899
4245
  ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
3900
4246
  tui.requestRender();
3901
4247
  return;
@@ -3915,18 +4261,7 @@ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, o
3915
4261
  getSettingsListTheme(),
3916
4262
  (id, value) => {
3917
4263
  if (closing || signal.aborted || !isCurrent()) return;
3918
- if (id === "fireworksAccountId") {
3919
- if (value === EDIT) {
3920
- saveQueue = saveQueue.then(() => {
3921
- if (closing || signal.aborted || !isCurrent()) return;
3922
- closing = true;
3923
- done({ changed, editFireworksAccount: true });
3924
- });
3925
- }
3926
- return;
3927
- }
3928
- const settingId = id;
3929
- queueUpdate(settingId, value !== OFF, value);
4264
+ queueUpdate(id, value !== OFF, value);
3930
4265
  },
3931
4266
  cancel
3932
4267
  );
@@ -3946,43 +4281,7 @@ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, o
3946
4281
  parentSignal.removeEventListener("abort", cancel);
3947
4282
  }
3948
4283
  };
3949
- });
3950
- }
3951
- async function editFireworksAccount(ctx, settingsRuntime, signal, isCurrent, onApplied) {
3952
- while (!signal.aborted && isCurrent()) {
3953
- const state = settingsRuntime.get();
3954
- if (state.kind === "invalid") {
3955
- ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
3956
- return false;
3957
- }
3958
- const entered = await ctx.ui.input(
3959
- "Fireworks account slug \xB7 submit blank for Auto",
3960
- state.settings.fireworksAccountId ?? "Example: acme",
3961
- { signal }
3962
- );
3963
- if (signal.aborted || !isCurrent() || entered === void 0) return false;
3964
- const normalized = entered.trim();
3965
- const requested = normalized || void 0;
3966
- if (requested !== void 0 && !isFireworksAccountId(requested)) {
3967
- ctx.ui.notify("Enter a URL-safe Fireworks account slug.", "warning");
3968
- continue;
3969
- }
3970
- if (requested === state.settings.fireworksAccountId) return false;
3971
- try {
3972
- await settingsRuntime.update({ fireworksAccountId: requested }, signal);
3973
- } catch (error) {
3974
- if (signal.aborted || !isCurrent()) return false;
3975
- ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
3976
- return false;
3977
- }
3978
- onApplied("fireworksAccountId");
3979
- return true;
3980
- }
3981
- return false;
3982
- }
3983
- function displaySetting(id, value) {
3984
- if (id === "fireworksAccountId") return typeof value === "string" ? value : AUTO;
3985
- return value ? ON : OFF;
4284
+ }) ?? false;
3986
4285
  }
3987
4286
 
3988
4287
  // src/usage.ts
@@ -3999,6 +4298,9 @@ var VIEW_ALL = "View all configured providers\u2026";
3999
4298
  var CLOSE = "Close";
4000
4299
  var SETTINGS = "Settings";
4001
4300
  var REDEEM_CODEX_RESET = "Redeem usage limit reset\u2026";
4301
+ var UsageTargetSelectionChangedError = class extends Error {
4302
+ name = "UsageTargetSelectionChangedError";
4303
+ };
4002
4304
  function usageExtension(pi, dependencies = {}) {
4003
4305
  const credentialReader = dependencies.credentialReader;
4004
4306
  const credentialCandidates = createOAuthCredentialCandidateReader(pi, credentialReader);
@@ -4070,7 +4372,7 @@ function usageExtension(pi, dependencies = {}) {
4070
4372
  if (outcome.state.status !== "ready") {
4071
4373
  if (safeSetStatus(
4072
4374
  ctx,
4073
- outcome.state.status === "auth-unavailable" ? "auth unavailable" : "usage error"
4375
+ outcome.state.status === "auth-unavailable" ? "auth unavailable" : outcome.state.status === "selection-required" ? "selection required" : "usage error"
4074
4376
  )) {
4075
4377
  if (shouldSchedule && sessionActive) scheduleStatusRefresh(ctx, model);
4076
4378
  }
@@ -4123,8 +4425,8 @@ function usageExtension(pi, dependencies = {}) {
4123
4425
  const expectedSessionGeneration = sessionGeneration;
4124
4426
  const expectedSessionId = ctx.sessionManager.getSessionId();
4125
4427
  const expectedModelIdentity = modelIdentity(ctx.model);
4126
- const expectedFireworksAccountId = adapter.id === "fireworks" ? settingsRuntime.get().settings.fireworksAccountId : void 0;
4127
- const querySettings = adapter.id === "fireworks" ? { fireworksAccountId: expectedFireworksAccountId } : void 0;
4428
+ const expectedTargetId = adapter.targets ? settingsRuntime.get().settings.selectedTargets[adapter.id] : void 0;
4429
+ const providerName = providerDisplayName(ctx, adapter.id);
4128
4430
  let auth;
4129
4431
  try {
4130
4432
  auth = await awaitWithDeadline(
@@ -4141,25 +4443,26 @@ function usageExtension(pi, dependencies = {}) {
4141
4443
  return {
4142
4444
  state: {
4143
4445
  providerId: adapter.id,
4144
- providerName: adapter.displayName,
4446
+ providerName,
4145
4447
  displayState,
4146
4448
  status: isTimeoutError(error) ? "query-failed" : "auth-unavailable",
4147
4449
  message: errorMessage(error)
4148
4450
  }
4149
4451
  };
4150
4452
  }
4151
- const requiresRequestBoundaryGuard = [
4453
+ const requiresRequestBoundaryGuard = adapter.targets !== void 0 || [
4152
4454
  "baseten",
4153
4455
  "deepseek",
4154
- "fireworks",
4155
4456
  "minimax",
4156
4457
  "minimax-cn",
4157
4458
  "moonshotai",
4158
4459
  "moonshotai-cn",
4159
4460
  "vercel-ai-gateway",
4160
- "xai"
4461
+ "xai",
4462
+ "zai",
4463
+ "zai-coding-cn"
4161
4464
  ].includes(adapter.id);
4162
- const requestContextChanged = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity || adapter.id === "fireworks" && settingsRuntime.get().settings.fireworksAccountId !== expectedFireworksAccountId;
4465
+ const requestContextChanged = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity || adapter.targets !== void 0 && settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedTargetId;
4163
4466
  if (requiresRequestBoundaryGuard && requestContextChanged()) throw abortError();
4164
4467
  if (!auth) {
4165
4468
  if (displayState === "current") {
@@ -4168,98 +4471,145 @@ function usageExtension(pi, dependencies = {}) {
4168
4471
  return {
4169
4472
  state: {
4170
4473
  providerId: adapter.id,
4171
- providerName: adapter.displayName,
4474
+ providerName,
4172
4475
  displayState,
4173
4476
  status: "auth-unavailable",
4174
- message: `No runtime credential is configured for ${adapter.displayName}.`
4477
+ message: `No runtime credential is configured for ${providerName}.`
4175
4478
  },
4176
4479
  authState: "unavailable"
4177
4480
  };
4178
4481
  }
4179
- const queryFingerprint = adapter.id === "fireworks" ? `${auth.fingerprint}:account:${expectedFireworksAccountId ?? "auto"}` : auth.fingerprint;
4180
- if (displayState === "current") {
4181
- transitionCurrentIdentity(`${adapter.id}:${queryFingerprint}`, adapter.id);
4182
- }
4183
- const cached = !force ? cache.get(adapter.id, queryFingerprint) : void 0;
4184
- if (cached) {
4185
- return {
4186
- state: {
4187
- providerId: adapter.id,
4188
- providerName: adapter.displayName,
4189
- displayState,
4190
- status: "ready",
4191
- report: cached
4192
- },
4193
- fingerprint: auth.fingerprint
4194
- };
4195
- }
4196
- const failureKey = `${adapter.id}:${queryFingerprint}`;
4197
- const previousFailure = failureBackoff.get(failureKey);
4198
- if (!force && previousFailure && previousFailure.until > Date.now()) {
4199
- return {
4200
- state: {
4201
- providerId: adapter.id,
4202
- providerName: adapter.displayName,
4203
- displayState,
4204
- status: "query-failed",
4205
- message: previousFailure.message
4206
- },
4207
- fingerprint: auth.fingerprint
4208
- };
4209
- }
4210
- failureBackoff.delete(failureKey);
4211
- querySequence += 1;
4212
- const queryId = querySequence;
4213
- setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
4214
4482
  let retryableAuthChanged = false;
4483
+ const guard = async () => {
4484
+ if (signal.aborted || requestContextChanged()) throw abortError();
4485
+ if (!requiresRequestBoundaryGuard) return;
4486
+ const revalidated = await awaitWithDeadline(
4487
+ resolveUsageAuth(ctx, adapter, void 0, credentialReader, credentialCandidates),
4488
+ signal,
4489
+ Math.max(1, deadlineAt - Date.now()),
4490
+ `revalidating ${providerName} runtime auth`
4491
+ );
4492
+ if (signal.aborted || requestContextChanged()) throw abortError();
4493
+ if (revalidated?.fingerprint !== auth.fingerprint) {
4494
+ if (["deepseek", "minimax", "minimax-cn"].includes(adapter.id)) {
4495
+ retryableAuthChanged = true;
4496
+ throw new Error(`${providerName} runtime credential changed during the usage query.`);
4497
+ }
4498
+ throw abortError();
4499
+ }
4500
+ };
4501
+ let queryFingerprint = adapter.targets ? `${auth.fingerprint}:target:${expectedTargetId ?? "unresolved"}` : auth.fingerprint;
4502
+ let failureKey = `${adapter.id}:${queryFingerprint}`;
4503
+ let queryId;
4215
4504
  try {
4216
- const remainingMs = Math.max(1, deadlineAt - Date.now());
4217
- const guard = requiresRequestBoundaryGuard ? async () => {
4218
- if (signal.aborted || requestContextChanged()) throw abortError();
4219
- const revalidated = await awaitWithDeadline(
4220
- resolveUsageAuth(ctx, adapter, void 0, credentialReader, credentialCandidates),
4221
- signal,
4222
- Math.max(1, deadlineAt - Date.now()),
4223
- `revalidating ${adapter.displayName} runtime auth`
4224
- );
4225
- if (signal.aborted || requestContextChanged()) throw abortError();
4226
- if (revalidated?.fingerprint !== auth.fingerprint) {
4227
- if (["deepseek", "minimax", "minimax-cn"].includes(adapter.id)) {
4228
- retryableAuthChanged = true;
4229
- throw new Error(
4230
- `${adapter.displayName} runtime credential changed during the usage query.`
4231
- );
4232
- }
4233
- throw abortError();
4505
+ const previousDiscoveryFailure = failureBackoff.get(failureKey);
4506
+ if (!force && previousDiscoveryFailure && previousDiscoveryFailure.until > Date.now()) {
4507
+ return {
4508
+ state: {
4509
+ providerId: adapter.id,
4510
+ providerName,
4511
+ displayState,
4512
+ status: "query-failed",
4513
+ message: previousDiscoveryFailure.message
4514
+ },
4515
+ fingerprint: auth.fingerprint,
4516
+ rememberedTargetId: expectedTargetId
4517
+ };
4518
+ }
4519
+ const target = await resolveUsageTarget(
4520
+ adapter,
4521
+ auth,
4522
+ expectedTargetId,
4523
+ signal,
4524
+ Math.max(1, deadlineAt - Date.now()),
4525
+ guard
4526
+ );
4527
+ if (target.kind === "selection-required") {
4528
+ if (displayState === "current") {
4529
+ transitionCurrentIdentity(`${adapter.id}:${queryFingerprint}`, adapter.id);
4234
4530
  }
4235
- } : void 0;
4531
+ return {
4532
+ state: {
4533
+ providerId: adapter.id,
4534
+ providerName,
4535
+ displayState,
4536
+ status: "selection-required",
4537
+ singularLabel: adapter.targets?.singularLabel ?? "target",
4538
+ pluralLabel: adapter.targets?.pluralLabel ?? "targets",
4539
+ choices: target.choices
4540
+ },
4541
+ fingerprint: auth.fingerprint,
4542
+ rememberedTargetId: expectedTargetId
4543
+ };
4544
+ }
4545
+ queryFingerprint = adapter.targets ? `${auth.fingerprint}:target:${target.targetId ?? "none"}` : auth.fingerprint;
4546
+ failureKey = `${adapter.id}:${queryFingerprint}`;
4547
+ if (displayState === "current") {
4548
+ transitionCurrentIdentity(`${adapter.id}:${queryFingerprint}`, adapter.id);
4549
+ }
4550
+ const cached = !force ? cache.get(adapter.id, queryFingerprint) : void 0;
4551
+ if (cached) {
4552
+ return {
4553
+ state: {
4554
+ providerId: adapter.id,
4555
+ providerName,
4556
+ displayState,
4557
+ status: "ready",
4558
+ report: cached
4559
+ },
4560
+ fingerprint: auth.fingerprint,
4561
+ rememberedTargetId: expectedTargetId
4562
+ };
4563
+ }
4564
+ const previousFailure = failureBackoff.get(failureKey);
4565
+ if (!force && previousFailure && previousFailure.until > Date.now()) {
4566
+ return {
4567
+ state: {
4568
+ providerId: adapter.id,
4569
+ providerName,
4570
+ displayState,
4571
+ status: "query-failed",
4572
+ message: previousFailure.message
4573
+ },
4574
+ fingerprint: auth.fingerprint,
4575
+ rememberedTargetId: expectedTargetId
4576
+ };
4577
+ }
4578
+ failureBackoff.delete(failureKey);
4579
+ querySequence += 1;
4580
+ queryId = querySequence;
4581
+ setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
4236
4582
  const report2 = await queryProviderUsage(
4237
4583
  adapter,
4238
4584
  auth,
4239
4585
  signal,
4240
- remainingMs,
4241
- guard,
4242
- querySettings
4586
+ Math.max(1, deadlineAt - Date.now()),
4587
+ requiresRequestBoundaryGuard ? guard : void 0,
4588
+ target.targetId
4243
4589
  );
4244
- if (guard) await guard();
4590
+ if (requiresRequestBoundaryGuard) await guard();
4591
+ const effectiveReport = { ...report2, providerName };
4245
4592
  if (latestQueries.get(failureKey) === queryId) {
4246
- cache.set(adapter.id, queryFingerprint, report2);
4593
+ cache.set(adapter.id, queryFingerprint, effectiveReport);
4247
4594
  failureBackoff.delete(failureKey);
4248
4595
  }
4249
4596
  return {
4250
4597
  state: {
4251
4598
  providerId: adapter.id,
4252
- providerName: adapter.displayName,
4599
+ providerName,
4253
4600
  displayState,
4254
4601
  status: "ready",
4255
- report: report2
4602
+ report: effectiveReport
4256
4603
  },
4257
- fingerprint: auth.fingerprint
4604
+ fingerprint: auth.fingerprint,
4605
+ rememberedTargetId: expectedTargetId
4258
4606
  };
4259
4607
  } catch (error) {
4260
4608
  if (isStaleExtensionContextError(error) || isAbortError3(error)) throw error;
4261
4609
  if (retryableAuthChanged && authRetry === 0 && !signal.aborted && !requestContextChanged() && Date.now() < deadlineAt) {
4262
- if (latestQueries.get(failureKey) === queryId) latestQueries.delete(failureKey);
4610
+ if (queryId !== void 0 && latestQueries.get(failureKey) === queryId) {
4611
+ latestQueries.delete(failureKey);
4612
+ }
4263
4613
  return queryAdapterState(
4264
4614
  ctx,
4265
4615
  adapter,
@@ -4275,7 +4625,7 @@ function usageExtension(pi, dependencies = {}) {
4275
4625
  for (const [key, failure] of failureBackoff) {
4276
4626
  if (failure.until <= now) failureBackoff.delete(key);
4277
4627
  }
4278
- if (latestQueries.get(failureKey) === queryId) {
4628
+ if (queryId === void 0 || latestQueries.get(failureKey) === queryId) {
4279
4629
  setBoundedMap(
4280
4630
  failureBackoff,
4281
4631
  failureKey,
@@ -4286,15 +4636,52 @@ function usageExtension(pi, dependencies = {}) {
4286
4636
  return {
4287
4637
  state: {
4288
4638
  providerId: adapter.id,
4289
- providerName: adapter.displayName,
4639
+ providerName,
4290
4640
  displayState,
4291
4641
  status: "query-failed",
4292
4642
  message
4293
4643
  },
4294
- fingerprint: auth.fingerprint
4644
+ fingerprint: auth.fingerprint,
4645
+ rememberedTargetId: expectedTargetId
4295
4646
  };
4296
4647
  }
4297
4648
  };
4649
+ const loadTargetChoices = async (ctx, adapter, signal) => {
4650
+ if (!adapter.targets) throw new Error("Provider does not support usage targets.");
4651
+ const expectedSessionGeneration = sessionGeneration;
4652
+ const expectedSessionId = ctx.sessionManager.getSessionId();
4653
+ const expectedModel = modelIdentity(ctx.model);
4654
+ const expectedTargetId = settingsRuntime.get().settings.selectedTargets[adapter.id];
4655
+ const deadlineAt = Date.now() + DEFAULT_TIMEOUT_MS;
4656
+ const changed = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModel || settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedTargetId;
4657
+ const auth = await awaitWithDeadline(
4658
+ resolveUsageAuth(ctx, adapter, void 0, credentialReader, credentialCandidates),
4659
+ signal,
4660
+ Math.max(1, deadlineAt - Date.now()),
4661
+ `resolving ${providerDisplayName(ctx, adapter.id)} runtime auth`
4662
+ );
4663
+ if (!auth || signal.aborted || changed()) throw abortError();
4664
+ const guard = async () => {
4665
+ if (signal.aborted || changed()) throw abortError();
4666
+ const revalidated = await awaitWithDeadline(
4667
+ resolveUsageAuth(ctx, adapter, void 0, credentialReader, credentialCandidates),
4668
+ signal,
4669
+ Math.max(1, deadlineAt - Date.now()),
4670
+ `revalidating ${providerDisplayName(ctx, adapter.id)} runtime auth`
4671
+ );
4672
+ if (signal.aborted || changed() || revalidated?.fingerprint !== auth.fingerprint) {
4673
+ throw abortError();
4674
+ }
4675
+ };
4676
+ const choices = await listUsageTargets(
4677
+ adapter,
4678
+ auth,
4679
+ signal,
4680
+ Math.max(1, deadlineAt - Date.now()),
4681
+ guard
4682
+ );
4683
+ return { choices, fingerprint: auth.fingerprint };
4684
+ };
4298
4685
  const queryCurrentState = async (ctx, model, force, signal) => {
4299
4686
  const adapter = adapterForProvider(model?.provider);
4300
4687
  if (!adapter) {
@@ -4378,6 +4765,8 @@ function usageExtension(pi, dependencies = {}) {
4378
4765
  return false;
4379
4766
  }
4380
4767
  const adapter = adapterForProvider(model?.provider);
4768
+ const selectionStillCurrent = !adapter?.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId;
4769
+ if (!selectionStillCurrent) return false;
4381
4770
  if (outcome.authState === "unavailable") {
4382
4771
  if (!adapter) return false;
4383
4772
  try {
@@ -4387,7 +4776,7 @@ function usageExtension(pi, dependencies = {}) {
4387
4776
  DEFAULT_TIMEOUT_MS,
4388
4777
  `revalidating ${adapter.displayName} runtime auth`
4389
4778
  );
4390
- return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && auth === void 0;
4779
+ return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId) && auth === void 0;
4391
4780
  } catch (error) {
4392
4781
  if (isAbortError3(error) || isStaleExtensionContextError(error)) throw error;
4393
4782
  return false;
@@ -4402,7 +4791,7 @@ function usageExtension(pi, dependencies = {}) {
4402
4791
  DEFAULT_TIMEOUT_MS,
4403
4792
  `revalidating ${adapter.displayName} runtime auth`
4404
4793
  );
4405
- return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && auth?.fingerprint === outcome.fingerprint;
4794
+ return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId) && auth?.fingerprint === outcome.fingerprint;
4406
4795
  } catch (error) {
4407
4796
  if (isAbortError3(error) || isStaleExtensionContextError(error)) throw error;
4408
4797
  return false;
@@ -4458,6 +4847,88 @@ function usageExtension(pi, dependencies = {}) {
4458
4847
  let redemptionId;
4459
4848
  let resetOutcome;
4460
4849
  let resetFailure;
4850
+ const actionableTargetState = () => {
4851
+ if (visibleStates.length !== 1) return void 0;
4852
+ const state = visibleStates[0];
4853
+ return state && (state.status === "ready" || state.status === "selection-required") && adapterForProvider(state.providerId)?.targets ? state : void 0;
4854
+ };
4855
+ const promptForTarget = async (state, stateFingerprint) => {
4856
+ const adapter = adapterForProvider(state.providerId);
4857
+ if (!adapter?.targets) return false;
4858
+ const expectedRememberedTargetId = settingsRuntime.get().settings.selectedTargets[adapter.id];
4859
+ const snapshot = state.status === "selection-required" && stateFingerprint ? { choices: state.choices, fingerprint: stateFingerprint } : await runMenuOperation(
4860
+ ctx,
4861
+ `Loading ${adapter.targets.pluralLabel}\u2026`,
4862
+ controller.signal,
4863
+ (signal) => loadTargetChoices(ctx, adapter, signal)
4864
+ );
4865
+ if (!snapshot || controller.signal.aborted || statusGeneration !== menuGeneration) {
4866
+ return false;
4867
+ }
4868
+ const selectOptions = createUsageTargetSelectOptions(snapshot.choices);
4869
+ const selected = await ctx.ui.select(
4870
+ `Select ${adapter.targets.singularLabel} for ${providerDisplayName(ctx, adapter.id)}`,
4871
+ [...selectOptions.options],
4872
+ { signal: controller.signal }
4873
+ );
4874
+ if (selected === void 0 || controller.signal.aborted || statusGeneration !== menuGeneration || settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedRememberedTargetId) {
4875
+ return false;
4876
+ }
4877
+ const targetId = selectOptions.targetIdFor(selected);
4878
+ if (!targetId) return false;
4879
+ const revalidated = await runMenuOperation(
4880
+ ctx,
4881
+ `Revalidating ${adapter.targets.singularLabel}\u2026`,
4882
+ controller.signal,
4883
+ (signal) => loadTargetChoices(ctx, adapter, signal)
4884
+ );
4885
+ if (!revalidated || controller.signal.aborted || statusGeneration !== menuGeneration || settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedRememberedTargetId) {
4886
+ return false;
4887
+ }
4888
+ if (revalidated.fingerprint !== snapshot.fingerprint || !revalidated.choices.some((choice) => choice.id === targetId)) {
4889
+ ctx.ui.notify(
4890
+ `${providerDisplayName(ctx, adapter.id)} ${adapter.targets.pluralLabel} changed; choose again.`,
4891
+ "warning"
4892
+ );
4893
+ return false;
4894
+ }
4895
+ let saved;
4896
+ try {
4897
+ saved = await runMenuOperation(
4898
+ ctx,
4899
+ `Saving ${adapter.targets.singularLabel}\u2026`,
4900
+ controller.signal,
4901
+ (signal) => settingsRuntime.updateSelectedTarget(adapter.id, targetId, signal, async () => {
4902
+ let published;
4903
+ try {
4904
+ published = await loadTargetChoices(ctx, adapter, signal);
4905
+ } catch (error) {
4906
+ if (signal.aborted || controller.signal.aborted || statusGeneration !== menuGeneration || isStaleExtensionContextError(error)) {
4907
+ throw error;
4908
+ }
4909
+ throw new UsageTargetSelectionChangedError();
4910
+ }
4911
+ if (published.fingerprint !== snapshot.fingerprint || !published.choices.some((choice) => choice.id === targetId)) {
4912
+ throw new UsageTargetSelectionChangedError();
4913
+ }
4914
+ })
4915
+ );
4916
+ } catch (error) {
4917
+ if (error instanceof UsageTargetSelectionChangedError) {
4918
+ ctx.ui.notify(
4919
+ `${providerDisplayName(ctx, adapter.id)} ${adapter.targets.pluralLabel} changed; choose again.`,
4920
+ "warning"
4921
+ );
4922
+ return false;
4923
+ }
4924
+ throw error;
4925
+ }
4926
+ if (!saved || controller.signal.aborted || statusGeneration !== menuGeneration) {
4927
+ return false;
4928
+ }
4929
+ invalidateProviderState(adapter.id);
4930
+ return true;
4931
+ };
4461
4932
  const { defineMenu, runMenu } = await import("@narumitw/pi-tui-kit");
4462
4933
  if (controller.signal.aborted || statusGeneration !== menuGeneration) return;
4463
4934
  const menu = defineMenu({
@@ -4465,6 +4936,8 @@ function usageExtension(pi, dependencies = {}) {
4465
4936
  screens: {
4466
4937
  main: () => {
4467
4938
  const fastAvailability = fastRuntime.availability(ctx.model);
4939
+ const targetState = actionableTargetState();
4940
+ const targetAdapter = adapterForProvider(targetState?.providerId);
4468
4941
  const fastLines = fastAvailability.kind === "available" ? [`Fast mode: ${fastAvailability.enabled ? "On" : "Off"}`, FAST_USAGE_WARNING] : fastAvailability.kind === "unavailable" ? [`Fast mode: Unavailable \xB7 ${fastAvailability.reason}`] : [];
4469
4942
  return {
4470
4943
  kind: "actions",
@@ -4473,6 +4946,13 @@ function usageExtension(pi, dependencies = {}) {
4473
4946
  items: [
4474
4947
  { id: "refresh", label: REFRESH_CURRENT, action: "refresh" },
4475
4948
  { id: "settings", label: SETTINGS, action: "settings" },
4949
+ ...targetState && targetAdapter?.targets ? [
4950
+ {
4951
+ id: "target",
4952
+ label: `${targetState.status === "selection-required" ? "Select" : "Change"} ${targetAdapter.targets.singularLabel}\u2026`,
4953
+ action: "target"
4954
+ }
4955
+ ] : [],
4476
4956
  ...fastAvailability.kind === "available" ? [
4477
4957
  {
4478
4958
  id: "toggle-fast",
@@ -4503,7 +4983,7 @@ function usageExtension(pi, dependencies = {}) {
4503
4983
  title: "Select a configured provider",
4504
4984
  items: configuredAdapters(ctx).filter((adapter) => adapter.id !== ctx.model?.provider).map((adapter) => ({
4505
4985
  id: adapter.id,
4506
- label: adapter.displayName,
4986
+ label: providerDisplayName(ctx, adapter.id),
4507
4987
  action: "provider"
4508
4988
  })),
4509
4989
  hint: "back"
@@ -4565,6 +5045,58 @@ function usageExtension(pi, dependencies = {}) {
4565
5045
  })
4566
5046
  },
4567
5047
  actions: {
5048
+ target: async () => {
5049
+ const targetState = actionableTargetState();
5050
+ if (!targetState) return { kind: "rejected" };
5051
+ try {
5052
+ const stateFingerprint = targetState === current.state ? current.fingerprint : void 0;
5053
+ if (!await promptForTarget(targetState, stateFingerprint)) {
5054
+ return { kind: "stay" };
5055
+ }
5056
+ const adapter = adapterForProvider(targetState.providerId);
5057
+ if (!adapter) return { kind: "rejected" };
5058
+ if (targetState.providerId === ctx.model?.provider) {
5059
+ const refreshed = await queryStableCurrent(
5060
+ ctx,
5061
+ true,
5062
+ controller,
5063
+ `Checking ${providerDisplayName(ctx, adapter.id)} usage\u2026`
5064
+ );
5065
+ if (!refreshed) return { kind: "stay" };
5066
+ stableCurrent = refreshed;
5067
+ current = refreshed.outcome;
5068
+ visibleStates = [current.state];
5069
+ publishStableCurrent(ctx, refreshed);
5070
+ return { kind: "stay" };
5071
+ }
5072
+ const outcome = await runMenuOperation(
5073
+ ctx,
5074
+ `Checking ${providerDisplayName(ctx, adapter.id)} usage\u2026`,
5075
+ controller.signal,
5076
+ (signal) => queryAdapterState(ctx, adapter, "configured", true, signal)
5077
+ );
5078
+ if (!outcome) return { kind: "stay" };
5079
+ const revalidated = await queryStableCurrent(
5080
+ ctx,
5081
+ false,
5082
+ controller,
5083
+ "Revalidating current usage\u2026"
5084
+ );
5085
+ if (!revalidated) return { kind: "stay" };
5086
+ stableCurrent = revalidated;
5087
+ current = revalidated.outcome;
5088
+ visibleStates = [
5089
+ outcome.state.providerId === current.state.providerId ? current.state : { ...outcome.state, displayState: "configured" }
5090
+ ];
5091
+ return { kind: "stay" };
5092
+ } catch (error) {
5093
+ if (isAbortError3(error) || isStaleExtensionContextError(error)) {
5094
+ return { kind: "stay" };
5095
+ }
5096
+ ctx.ui.notify(`Could not select target: ${errorMessage(error)}`, "error");
5097
+ return { kind: "stay" };
5098
+ }
5099
+ },
4568
5100
  settings: async () => {
4569
5101
  await showUsageSettings(
4570
5102
  ctx,
@@ -4773,13 +5305,25 @@ function usageExtension(pi, dependencies = {}) {
4773
5305
  (candidate) => candidate.id === itemId && candidate.id !== ctx.model?.provider
4774
5306
  );
4775
5307
  if (!adapter) return { kind: "back" };
4776
- const outcome = await runMenuOperation(
5308
+ let outcome = await runMenuOperation(
4777
5309
  ctx,
4778
- `Checking ${adapter.displayName} usage\u2026`,
5310
+ `Checking ${providerDisplayName(ctx, adapter.id)} usage\u2026`,
4779
5311
  controller.signal,
4780
5312
  (signal) => queryAdapterState(ctx, adapter, "configured", false, signal)
4781
5313
  );
4782
5314
  if (!outcome) return { kind: "back" };
5315
+ if (outcome.state.status === "selection-required") {
5316
+ if (!await promptForTarget(outcome.state, outcome.fingerprint)) {
5317
+ return { kind: "back" };
5318
+ }
5319
+ outcome = await runMenuOperation(
5320
+ ctx,
5321
+ `Checking ${providerDisplayName(ctx, adapter.id)} usage\u2026`,
5322
+ controller.signal,
5323
+ (signal) => queryAdapterState(ctx, adapter, "configured", true, signal)
5324
+ );
5325
+ if (!outcome) return { kind: "back" };
5326
+ }
4783
5327
  const revalidated = await queryStableCurrent(
4784
5328
  ctx,
4785
5329
  false,
@@ -4822,7 +5366,7 @@ function usageExtension(pi, dependencies = {}) {
4822
5366
  const adapter = adapters[index];
4823
5367
  return {
4824
5368
  providerId: adapter.id,
4825
- providerName: adapter.displayName,
5369
+ providerName: providerDisplayName(ctx, adapter.id),
4826
5370
  displayState: "configured",
4827
5371
  status: "query-failed",
4828
5372
  message: errorMessage(result.reason)
@@ -4936,15 +5480,19 @@ export {
4936
5480
  codexFastStatusLabel,
4937
5481
  consumeCodexResetCredit,
4938
5482
  correctCodexFastMessageCost,
5483
+ createFireworksAdapter,
4939
5484
  createUsageSettingsRuntime,
5485
+ createUsageTargetSelectOptions,
4940
5486
  usageExtension as default,
4941
5487
  errorMessage,
4942
5488
  fingerprintResolvedAuth,
4943
5489
  formatProviderStates,
4944
5490
  formatUsageReport,
4945
5491
  formatUsageStatusline,
5492
+ isBoundedTargetId,
4946
5493
  isStaleExtensionContextError,
4947
5494
  listCodexResetCredits,
5495
+ listUsageTargets,
4948
5496
  loadUsageSettings,
4949
5497
  miniMaxUsageKind,
4950
5498
  normalizeBasetenBillingUsagePayload,
@@ -4960,14 +5508,17 @@ export {
4960
5508
  normalizeOpenCodeZenPayload,
4961
5509
  normalizeOpenRouterKeyPayload,
4962
5510
  normalizeUsageSettings,
5511
+ normalizeUsageTargets,
4963
5512
  normalizeVercelAIGatewayCreditsPayload,
4964
5513
  normalizeXaiBillingPayload,
4965
5514
  normalizeZaiQuotaPayload,
5515
+ normalizeZaiSubscriptionPayload,
4966
5516
  providerIsConfigured,
4967
5517
  queryProviderUsage,
4968
5518
  redactUsageError,
4969
5519
  resolveCodexResetAuth,
4970
5520
  resolveUsageAuth,
5521
+ resolveUsageTarget,
4971
5522
  rewriteCodexFastPayload,
4972
5523
  runWithConcurrency,
4973
5524
  sanitizeDisplayText,