@narumitw/pi-usage 0.60.0 → 0.60.2

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;
@@ -1145,6 +1254,26 @@ function normalizeWindow(row, fields) {
1145
1254
  }
1146
1255
  const total = nonnegativeInteger(row[fields.totalField], fields.totalField);
1147
1256
  const count = nonnegativeInteger(row[fields.countField], fields.countField);
1257
+ if (total === 0) {
1258
+ if (count !== 0) {
1259
+ throw new Error(`MiniMax Token Plan ${fields.label} counts were inconsistent.`);
1260
+ }
1261
+ if (percent === void 0) {
1262
+ throw new Error(`MiniMax Token Plan ${fields.label} returned no quota and no percent.`);
1263
+ }
1264
+ return {
1265
+ id: fields.id,
1266
+ label: fields.label,
1267
+ groupId: fields.groupId,
1268
+ groupLabel: fields.groupLabel,
1269
+ remaining: percent,
1270
+ used: 100 - percent,
1271
+ limit: 0,
1272
+ unit: "percent",
1273
+ windowMinutes,
1274
+ resetsAt
1275
+ };
1276
+ }
1148
1277
  const resolved = resolveQuotaCounts(count, total, percent);
1149
1278
  if (!resolved) throw new Error(`MiniMax Token Plan ${fields.label} counts were inconsistent.`);
1150
1279
  return {
@@ -1787,14 +1916,104 @@ function clampPercent3(value) {
1787
1916
  return Math.min(100, Math.max(0, value));
1788
1917
  }
1789
1918
 
1919
+ // src/usage-targets.ts
1920
+ var MAX_TARGETS = 1e3;
1921
+ var MAX_TARGET_ID_CHARS = 256;
1922
+ var MAX_TARGET_LABEL_CHARS = 120;
1923
+ var MAX_TARGET_DESCRIPTION_CHARS = 180;
1924
+ async function resolveUsageTarget(adapter, auth, rememberedTargetId, signal, timeoutMs, guard) {
1925
+ if (!adapter.targets) return { kind: "selected" };
1926
+ if (rememberedTargetId !== void 0 && !isBoundedTargetId(rememberedTargetId)) {
1927
+ throw new Error(`The remembered ${adapter.targets.singularLabel} identifier was invalid.`);
1928
+ }
1929
+ const choices = await listUsageTargets(adapter, auth, signal, timeoutMs, guard);
1930
+ if (rememberedTargetId) {
1931
+ return choices.some((choice) => choice.id === rememberedTargetId) ? { kind: "selected", targetId: rememberedTargetId } : { kind: "selection-required", choices };
1932
+ }
1933
+ if (choices.length === 1) return { kind: "selected", targetId: choices[0]?.id };
1934
+ return { kind: "selection-required", choices };
1935
+ }
1936
+ async function listUsageTargets(adapter, auth, signal, timeoutMs, guard) {
1937
+ if (!adapter.targets) return [];
1938
+ const startedAt = Date.now();
1939
+ await guard();
1940
+ const listed = await adapter.targets.list(
1941
+ auth,
1942
+ signal,
1943
+ remainingTargetTimeout(timeoutMs, startedAt),
1944
+ guard
1945
+ );
1946
+ await guard();
1947
+ remainingTargetTimeout(timeoutMs, startedAt);
1948
+ const choices = normalizeUsageTargets(listed);
1949
+ if (choices.length === 0) {
1950
+ throw new Error(`${adapter.targets.pluralLabel} discovery returned no choices.`);
1951
+ }
1952
+ return choices;
1953
+ }
1954
+ function normalizeUsageTargets(targets) {
1955
+ if (!Array.isArray(targets)) throw new Error("Target discovery did not return a choices array.");
1956
+ if (targets.length > MAX_TARGETS) {
1957
+ throw new Error(`Target discovery exceeded ${MAX_TARGETS} choices.`);
1958
+ }
1959
+ const seen = /* @__PURE__ */ new Set();
1960
+ return targets.map((target) => {
1961
+ if (!target || typeof target !== "object" || Array.isArray(target)) {
1962
+ throw new Error("Target discovery returned an invalid choice.");
1963
+ }
1964
+ const { id, label, description } = target;
1965
+ if (!isBoundedTargetId(id)) throw new Error("Target discovery returned an invalid ID.");
1966
+ if (seen.has(id)) throw new Error(`Target discovery repeated ${id}.`);
1967
+ seen.add(id);
1968
+ if (typeof label !== "string") {
1969
+ throw new Error("Target discovery returned an invalid display label.");
1970
+ }
1971
+ if (description !== void 0 && typeof description !== "string") {
1972
+ throw new Error("Target discovery returned an invalid description.");
1973
+ }
1974
+ const safeLabel2 = sanitizeDisplayText(label, MAX_TARGET_LABEL_CHARS);
1975
+ if (!safeLabel2) throw new Error("Target discovery returned an empty display label.");
1976
+ const safeDescription = description ? sanitizeDisplayText(description, MAX_TARGET_DESCRIPTION_CHARS) : void 0;
1977
+ return { id, label: safeLabel2, ...safeDescription ? { description: safeDescription } : {} };
1978
+ });
1979
+ }
1980
+ function createUsageTargetSelectOptions(targets) {
1981
+ const normalized = normalizeUsageTargets(targets);
1982
+ const ids = /* @__PURE__ */ new Map();
1983
+ const options = normalized.map((target) => {
1984
+ const base = target.description ? `${target.label} \u2014 ${target.description}` : target.label;
1985
+ let option = base;
1986
+ if (ids.has(option)) {
1987
+ const safeId = sanitizeDisplayText(target.id, 80) || "target";
1988
+ option = `${base} \xB7 ${safeId}`;
1989
+ let duplicate = 2;
1990
+ while (ids.has(option)) {
1991
+ option = `${base} \xB7 ${safeId} (${duplicate})`;
1992
+ duplicate += 1;
1993
+ }
1994
+ }
1995
+ ids.set(option, target.id);
1996
+ return option;
1997
+ });
1998
+ return { options, targetIdFor: (option) => ids.get(option) };
1999
+ }
2000
+ function remainingTargetTimeout(timeoutMs, startedAt) {
2001
+ const remaining = timeoutMs - (Date.now() - startedAt);
2002
+ if (remaining <= 0) throw new Error("Timed out while discovering provider targets.");
2003
+ return remaining;
2004
+ }
2005
+ function isBoundedTargetId(value) {
2006
+ return typeof value === "string" && value.length > 0 && value.length <= MAX_TARGET_ID_CHARS && ![...value].some((character) => {
2007
+ const codePoint = character.codePointAt(0) ?? 0;
2008
+ return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
2009
+ });
2010
+ }
2011
+
1790
2012
  // src/query.ts
1791
2013
  var BASETEN_BILLING_USAGE_URL = "https://api.baseten.co/v1/billing/usage_summary";
1792
2014
  var BASETEN_USAGE_WINDOW_DAYS = 30;
1793
2015
  var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
1794
2016
  var DEEPSEEK_BALANCE_URL = "https://api.deepseek.com/user/balance";
1795
- var FIREWORKS_BILLING_SUMMARY_ORIGIN = "https://api.fireworks.ai";
1796
- var FIREWORKS_SPEND_WINDOW_DAYS = 30;
1797
- var FIREWORKS_MAX_ACCOUNT_PAGES = 5;
1798
2017
  var GITHUB_COPILOT_USAGE_URL = "https://api.github.com/copilot_internal/user";
1799
2018
  var OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
1800
2019
  var VERCEL_AI_GATEWAY_CREDITS_URL = "https://ai-gateway.vercel.sh/v1/credits";
@@ -1833,7 +2052,7 @@ var SUPPORTED_ADAPTERS = [
1833
2052
  basetenBillingUsageUrl(windowAt),
1834
2053
  auth,
1835
2054
  signal,
1836
- remainingTimeout(timeoutMs, startedAt, "fetching Baseten billing usage"),
2055
+ remainingTimeout2(timeoutMs, startedAt, "fetching Baseten billing usage"),
1837
2056
  "Baseten billing usage endpoint",
1838
2057
  { redirect: "error" }
1839
2058
  );
@@ -1926,7 +2145,7 @@ var SUPPORTED_ADAPTERS = [
1926
2145
  VERCEL_AI_GATEWAY_CREDITS_URL,
1927
2146
  auth,
1928
2147
  signal,
1929
- remainingTimeout(timeoutMs, startedAt, "fetching Vercel AI Gateway credits"),
2148
+ remainingTimeout2(timeoutMs, startedAt, "fetching Vercel AI Gateway credits"),
1930
2149
  "Vercel AI Gateway credits endpoint",
1931
2150
  { redirect: "error" }
1932
2151
  );
@@ -1934,35 +2153,7 @@ var SUPPORTED_ADAPTERS = [
1934
2153
  return normalizeVercelAIGatewayCreditsPayload(payload, Date.now());
1935
2154
  }
1936
2155
  },
1937
- {
1938
- id: "fireworks",
1939
- displayName: "Fireworks",
1940
- semantics: { kind: "api-key", label: "Fireworks API spend" },
1941
- async query(auth, signal, timeoutMs, guard, settings) {
1942
- if (!guard) throw new Error("Fireworks API spend requires request-boundary revalidation.");
1943
- const startedAt = Date.now();
1944
- await guard();
1945
- const accountId = await resolveFireworksAccountId(
1946
- auth,
1947
- signal,
1948
- remainingTimeout(timeoutMs, startedAt, "resolving the Fireworks account"),
1949
- guard,
1950
- settings?.fireworksAccountId
1951
- );
1952
- await guard();
1953
- const billingWindowAt = Date.now();
1954
- const payload = await fetchProviderJson(
1955
- fireworksBillingSummaryUrl(accountId, billingWindowAt),
1956
- auth,
1957
- signal,
1958
- remainingTimeout(timeoutMs, startedAt, "fetching Fireworks rated spend"),
1959
- "Fireworks billing summary endpoint",
1960
- { redirect: "error" }
1961
- );
1962
- await guard();
1963
- return normalizeFireworksBillingSummaryPayload(payload, accountId, Date.now());
1964
- }
1965
- },
2156
+ createFireworksAdapter(fetchProviderJson),
1966
2157
  {
1967
2158
  id: "opencode-go",
1968
2159
  displayName: "OpenCode Go",
@@ -2063,7 +2254,7 @@ var XAI_ADAPTER = {
2063
2254
  XAI_USER_URL,
2064
2255
  clientAuth,
2065
2256
  signal,
2066
- remainingTimeout(timeoutMs, startedAt),
2257
+ remainingTimeout2(timeoutMs, startedAt),
2067
2258
  "xAI consumer identity endpoint",
2068
2259
  { redirect: "error", userAgent: false }
2069
2260
  );
@@ -2079,7 +2270,7 @@ var XAI_ADAPTER = {
2079
2270
  XAI_BILLING_URL,
2080
2271
  billingAuth,
2081
2272
  signal,
2082
- remainingTimeout(timeoutMs, startedAt),
2273
+ remainingTimeout2(timeoutMs, startedAt),
2083
2274
  "xAI consumer billing endpoint",
2084
2275
  { redirect: "error", userAgent: false }
2085
2276
  );
@@ -2107,6 +2298,12 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
2107
2298
  );
2108
2299
  if (!model) return void 0;
2109
2300
  const registry = ctx.modelRegistry;
2301
+ const provider = registry.getProvider?.(adapter.id);
2302
+ if (provider?.baseUrl && !hasOfficialUrlOrigin(provider.baseUrl, adapter.id)) {
2303
+ throw new Error(
2304
+ `${adapter.displayName} usage cannot send an overridden provider credential to the official usage endpoint.`
2305
+ );
2306
+ }
2110
2307
  let modelAuth;
2111
2308
  const currentModel = ctx.model?.provider === adapter.id ? ctx.model : void 0;
2112
2309
  const resolveCurrentModelAuth = async () => {
@@ -2129,25 +2326,64 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
2129
2326
  );
2130
2327
  }
2131
2328
  if (resolveSelectedAuthLast) modelAuth = await resolveCurrentModelAuth();
2329
+ if (modelAuth?.baseUrl && !hasOfficialUrlOrigin(modelAuth.baseUrl, adapter.id)) {
2330
+ throw new Error(
2331
+ `${adapter.displayName} usage cannot send model-resolved proxy credentials to the official usage endpoint.`
2332
+ );
2333
+ }
2132
2334
  const auth = modelAuth ?? providerResult?.auth;
2133
2335
  if (!auth) return void 0;
2336
+ const finalize = (resolved) => {
2337
+ const preservedAuth = { ...providerResult?.auth ?? auth };
2338
+ const env = providerResult?.env ?? modelAuth?.env;
2339
+ const source = providerResult?.source;
2340
+ const effectiveBaseUrl = modelAuth?.baseUrl ?? providerResult?.auth.baseUrl ?? provider?.baseUrl ?? model.baseUrl;
2341
+ const redactionInputs = [
2342
+ preservedAuth.apiKey,
2343
+ ...Object.values(preservedAuth.headers ?? {}),
2344
+ ...Object.values(env ?? {}),
2345
+ modelAuth?.apiKey,
2346
+ ...Object.values(modelAuth?.headers ?? {})
2347
+ ].filter((value) => typeof value === "string" && value.length > 0);
2348
+ return {
2349
+ ...resolved,
2350
+ auth: preservedAuth,
2351
+ ...env ? { env: { ...env } } : {},
2352
+ ...source ? { source } : {},
2353
+ effectiveBaseUrl,
2354
+ secrets: [.../* @__PURE__ */ new Set([...resolved.secrets, ...redactionInputs])],
2355
+ fingerprint: fingerprintResolvedAuth(
2356
+ {
2357
+ apiKey: resolved.apiKey,
2358
+ headers: resolved.headers,
2359
+ baseUrl: effectiveBaseUrl,
2360
+ env,
2361
+ source,
2362
+ providerAuth: preservedAuth
2363
+ },
2364
+ salt
2365
+ )
2366
+ };
2367
+ };
2134
2368
  if (adapter.id === "github-copilot") {
2135
2369
  const offered = candidateReader ? candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
2136
2370
  if (!offered.ok) {
2137
2371
  throw new Error("GitHub Copilot OAuth credential discovery failed closed.");
2138
2372
  }
2139
- return resolveGitHubCopilotUsageAuth(
2140
- auth,
2141
- model,
2142
- salt,
2143
- offered.candidates,
2144
- offered.offeredCount === 0
2373
+ return finalize(
2374
+ resolveGitHubCopilotUsageAuth(
2375
+ auth,
2376
+ model,
2377
+ salt,
2378
+ offered.candidates,
2379
+ offered.offeredCount === 0
2380
+ )
2145
2381
  );
2146
2382
  }
2147
2383
  if (adapter.id === "xai") {
2148
2384
  const offered = candidateReader ? candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
2149
2385
  if (!offered.ok) throw new Error("xAI OAuth credential discovery failed closed.");
2150
- return resolveXaiUsageAuth(auth, model, salt, offered.candidates);
2386
+ return finalize(resolveXaiUsageAuth(auth, model, salt, offered.candidates));
2151
2387
  }
2152
2388
  if (adapter.id === "deepseek") {
2153
2389
  const resolvedAuthorization = authorizationFrom(auth);
@@ -2155,10 +2391,10 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
2155
2391
  if (!access) throw new Error("DeepSeek API balance requires Bearer authentication.");
2156
2392
  const authorization2 = `Bearer ${access}`;
2157
2393
  const headers2 = { Authorization: authorization2 };
2158
- return {
2394
+ return finalize({
2159
2395
  apiKey: access,
2160
2396
  headers: headers2,
2161
- fingerprint: fingerprintResolvedAuth({ headers: headers2 }, salt),
2397
+ fingerprint: "",
2162
2398
  secrets: [
2163
2399
  access,
2164
2400
  auth.apiKey,
@@ -2167,7 +2403,7 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
2167
2403
  authorization2
2168
2404
  ].filter((value) => Boolean(value)),
2169
2405
  model
2170
- };
2406
+ });
2171
2407
  }
2172
2408
  const authorization = authorizationFrom(auth);
2173
2409
  if (!authorization) return void 0;
@@ -2175,17 +2411,41 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
2175
2411
  const secrets = [auth.apiKey, headerValue(auth.headers, "Authorization"), authorization].filter(
2176
2412
  (value) => Boolean(value)
2177
2413
  );
2178
- return {
2414
+ return finalize({
2179
2415
  apiKey: auth.apiKey,
2180
2416
  headers,
2181
- fingerprint: fingerprintResolvedAuth({ headers }, salt),
2417
+ fingerprint: "",
2182
2418
  secrets,
2183
2419
  model
2184
- };
2420
+ });
2185
2421
  }
2186
- async function queryProviderUsage(adapter, auth, signal, timeoutMs, guard, settings) {
2422
+ async function queryProviderUsage(adapter, auth, signal, timeoutMs, guard, targetOrSettings) {
2423
+ const startedAt = Date.now();
2424
+ let targetId = typeof targetOrSettings === "string" ? targetOrSettings : adapter.id === "fireworks" ? targetOrSettings?.fireworksAccountId : void 0;
2425
+ let resolvedLegacyFireworksTarget = false;
2187
2426
  try {
2188
- return await adapter.query(auth, signal, timeoutMs, guard, settings);
2427
+ if (adapter.id === "fireworks" && typeof targetOrSettings !== "string" && adapter.targets && guard) {
2428
+ const target = await resolveUsageTarget(
2429
+ adapter,
2430
+ auth,
2431
+ targetId,
2432
+ signal,
2433
+ remainingTimeout2(timeoutMs, startedAt, "resolving the Fireworks account"),
2434
+ guard
2435
+ );
2436
+ if (target.kind === "selection-required") {
2437
+ throw new Error("Fireworks account selection is required.");
2438
+ }
2439
+ targetId = target.targetId;
2440
+ resolvedLegacyFireworksTarget = true;
2441
+ }
2442
+ return await adapter.query(
2443
+ auth,
2444
+ signal,
2445
+ resolvedLegacyFireworksTarget ? remainingTimeout2(timeoutMs, startedAt, `querying ${adapter.displayName} usage`) : timeoutMs,
2446
+ guard,
2447
+ targetId
2448
+ );
2189
2449
  } catch (error) {
2190
2450
  if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
2191
2451
  throw new Error(redactUsageError(errorMessage(error), auth.secrets));
@@ -2484,7 +2744,7 @@ function hasOfficialUrlOrigin(value, providerId) {
2484
2744
  }
2485
2745
  if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
2486
2746
  if (providerId === "deepseek") return url.origin === "https://api.deepseek.com";
2487
- if (providerId === "fireworks") return url.origin === FIREWORKS_BILLING_SUMMARY_ORIGIN;
2747
+ if (providerId === "fireworks") return url.origin === "https://api.fireworks.ai";
2488
2748
  if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
2489
2749
  if (providerId === "vercel-ai-gateway") return url.origin === "https://ai-gateway.vercel.sh";
2490
2750
  if (providerId === "opencode-go") return url.origin === "https://opencode.ai";
@@ -2540,7 +2800,7 @@ async function queryMiniMaxUsage(providerId, auth, signal, timeoutMs, guard) {
2540
2800
  `${MINIMAX_API_ROOTS[providerId]}${path}`,
2541
2801
  auth,
2542
2802
  signal,
2543
- remainingTimeout(timeoutMs, startedAt, "fetching MiniMax usage"),
2803
+ remainingTimeout2(timeoutMs, startedAt, "fetching MiniMax usage"),
2544
2804
  "MiniMax usage endpoint",
2545
2805
  { redirect: "error" }
2546
2806
  );
@@ -2555,94 +2815,18 @@ async function queryMoonshotBalance(providerId, auth, signal, timeoutMs, guard)
2555
2815
  MOONSHOT_BALANCE_URLS[providerId],
2556
2816
  auth,
2557
2817
  signal,
2558
- remainingTimeout(timeoutMs, startedAt, "fetching Moonshot AI balance"),
2818
+ remainingTimeout2(timeoutMs, startedAt, "fetching Moonshot AI balance"),
2559
2819
  "Moonshot AI balance endpoint",
2560
2820
  { redirect: "error" }
2561
2821
  );
2562
2822
  await guard();
2563
2823
  return normalizeMoonshotBalancePayload(providerId, payload, Date.now());
2564
2824
  }
2565
- function remainingTimeout(timeoutMs, startedAt, description = "fetching xAI consumer usage") {
2825
+ function remainingTimeout2(timeoutMs, startedAt, description = "fetching xAI consumer usage") {
2566
2826
  const remaining = timeoutMs - (Date.now() - startedAt);
2567
2827
  if (remaining <= 0) throw new Error(`Timed out while ${description}.`);
2568
2828
  return remaining;
2569
2829
  }
2570
- async function resolveFireworksAccountId(auth, signal, timeoutMs, guard, configuredAccountId) {
2571
- if (configuredAccountId !== void 0 && !isFireworksAccountId(configuredAccountId)) {
2572
- throw new Error("The Fireworks account setting was not a safe account slug.");
2573
- }
2574
- const startedAt = Date.now();
2575
- const accounts = [];
2576
- let pageToken;
2577
- for (let page = 0; page < FIREWORKS_MAX_ACCOUNT_PAGES; page += 1) {
2578
- await guard();
2579
- const payload = await fetchProviderJson(
2580
- fireworksAccountsUrl(pageToken),
2581
- auth,
2582
- signal,
2583
- remainingTimeout(timeoutMs, startedAt, "fetching Fireworks accounts"),
2584
- "Fireworks accounts endpoint",
2585
- { redirect: "error" }
2586
- );
2587
- for (const accountId of normalizeFireworksAccountsPayload(
2588
- payload
2589
- )) {
2590
- if (accounts.includes(accountId)) {
2591
- throw new Error(`Fireworks accounts listing repeated ${accountId}.`);
2592
- }
2593
- accounts.push(accountId);
2594
- if (configuredAccountId === accountId) return accountId;
2595
- }
2596
- pageToken = fireworksNextPageToken(payload.nextPageToken);
2597
- if (!pageToken) break;
2598
- }
2599
- if (pageToken) {
2600
- throw new Error(
2601
- 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.`
2602
- );
2603
- }
2604
- if (accounts.length === 0) {
2605
- throw new Error("Fireworks account discovery returned no accounts for this API key.");
2606
- }
2607
- if (configuredAccountId) {
2608
- throw new Error(
2609
- "The configured Fireworks account does not match an account visible to this API key."
2610
- );
2611
- }
2612
- if (accounts.length === 1) return accounts[0];
2613
- const preview = accounts.slice(0, 8).join(", ");
2614
- const suffix = accounts.length > 8 ? ` \u2026and ${accounts.length - 8} more` : "";
2615
- throw new Error(
2616
- `The Fireworks key can see ${accounts.length} accounts (${preview}${suffix}); set fireworksAccountId in pi-usage.json to one of them.`
2617
- );
2618
- }
2619
- function fireworksAccountsUrl(pageToken) {
2620
- const url = new URL("/v1/accounts", FIREWORKS_BILLING_SUMMARY_ORIGIN);
2621
- url.searchParams.set("pageSize", "200");
2622
- if (pageToken !== void 0) url.searchParams.set("pageToken", pageToken);
2623
- return url.toString();
2624
- }
2625
- function fireworksNextPageToken(value) {
2626
- if (value === void 0 || value === null) return void 0;
2627
- if (typeof value !== "string" || !value || value.length > 512) {
2628
- throw new Error("Fireworks accounts listing returned an invalid page token.");
2629
- }
2630
- return value;
2631
- }
2632
- function fireworksBillingSummaryUrl(accountId, startedAt) {
2633
- const dayMs = 24 * 60 * 60 * 1e3;
2634
- const dayFloor = (time) => `${new Date(time).toISOString().slice(0, 10)}T00:00:00Z`;
2635
- const url = new URL(
2636
- `/v1/accounts/${accountId}/billing/summary`,
2637
- FIREWORKS_BILLING_SUMMARY_ORIGIN
2638
- );
2639
- url.searchParams.set(
2640
- "startTime",
2641
- dayFloor(startedAt - (FIREWORKS_SPEND_WINDOW_DAYS - 1) * dayMs)
2642
- );
2643
- url.searchParams.set("endTime", dayFloor(startedAt + dayMs));
2644
- return url.toString();
2645
- }
2646
2830
  function zaiOrigin(baseUrl) {
2647
2831
  const base = baseUrl?.trim();
2648
2832
  if (!base) throw new Error("Z.AI model base URL is unavailable.");
@@ -2665,7 +2849,7 @@ async function queryZaiUsage(providerId, providerName, auth, signal, timeoutMs,
2665
2849
  zaiMonitorUrl(auth.model.baseUrl),
2666
2850
  zaiMonitorAuth(auth),
2667
2851
  signal,
2668
- remainingTimeout(timeoutMs, startedAt, `fetching ${providerName} quota`),
2852
+ remainingTimeout2(timeoutMs, startedAt, `fetching ${providerName} quota`),
2669
2853
  `${providerName} quota endpoint`
2670
2854
  );
2671
2855
  await guard();
@@ -3016,6 +3200,10 @@ function formatProviderStates(states) {
3016
3200
  return states.map((state) => {
3017
3201
  if (state.status === "ready") return formatUsageReport(state.report, state.displayState);
3018
3202
  const label = state.displayState === "current" ? "Current" : "Configured";
3203
+ if (state.status === "selection-required") {
3204
+ return `${state.providerName} \xB7 ${label}
3205
+ Selection required: choose this provider's ${state.singularLabel} by viewing it individually.`;
3206
+ }
3019
3207
  const status = state.status === "auth-unavailable" ? "Authentication unavailable" : state.status === "unsupported" ? "Unsupported" : "Query failed";
3020
3208
  return `${state.providerName} \xB7 ${label}
3021
3209
  ${status}: ${state.message}`;
@@ -3250,7 +3438,7 @@ function formatMiniMaxReport(lines, report2) {
3250
3438
  if (bucket.groupId !== previousGroup) lines.push(`${bucket.groupLabel ?? "Token Plan"}:`);
3251
3439
  previousGroup = bucket.groupId;
3252
3440
  const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
3253
- const value = bucket.period === "unlimited" ? "unlimited" : bucket.limit && bucket.remaining !== void 0 ? `${bucket.remaining} of ${bucket.limit} left \xB7 ${percentRemaining(bucket)}%${reset}` : "unavailable";
3441
+ const value = bucket.period === "unlimited" ? "unlimited" : bucket.unit === "percent" && bucket.remaining !== void 0 ? `${bucket.remaining}% remaining${reset}` : bucket.limit !== void 0 && bucket.remaining !== void 0 ? `${bucket.remaining} of ${bucket.limit} left \xB7 ${percentRemaining(bucket)}%${reset}` : "unavailable";
3254
3442
  lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${value}`);
3255
3443
  }
3256
3444
  }
@@ -3271,7 +3459,11 @@ function formatMiniMaxStatusline(report2, model) {
3271
3459
  parts.push(`unlimited ${window}`);
3272
3460
  continue;
3273
3461
  }
3274
- if (!bucket.limit || bucket.remaining === void 0) continue;
3462
+ if (bucket.unit === "percent" && bucket.remaining !== void 0) {
3463
+ parts.push(`${bucket.remaining}% ${window}`);
3464
+ continue;
3465
+ }
3466
+ if (bucket.limit === void 0 || bucket.remaining === void 0) continue;
3275
3467
  parts.push(`${percentRemaining(bucket)}% ${window}`);
3276
3468
  }
3277
3469
  return parts.length > 1 ? parts.join(" ") : void 0;
@@ -3283,22 +3475,26 @@ function selectMiniMaxGroup(report2, model) {
3283
3475
  )
3284
3476
  ];
3285
3477
  if (groups.length <= 1) return groups[0];
3286
- if (model?.provider !== report2.providerId) return void 0;
3287
- const modelKeys = [model.id, model.name].map(normalizeMiniMaxModelKey).filter((key) => key !== void 0);
3288
- const candidates = groups.map((group) => {
3289
- const bucket = report2.buckets.find((candidate) => candidate.groupId === group);
3290
- const patterns = [bucket?.groupLabel, ...bucket?.modelKeys ?? [], group].map(normalizeMiniMaxModelKey).filter((key) => key !== void 0);
3291
- return { group, patterns };
3292
- });
3293
- const exact = candidates.find(
3294
- ({ patterns }) => patterns.some((pattern) => !pattern.includes("*") && modelKeys.includes(pattern))
3295
- );
3296
- if (exact) return exact.group;
3297
- return candidates.find(
3298
- ({ patterns }) => patterns.some(
3299
- (pattern) => pattern.includes("*") && modelKeys.some((key) => wildcardKeyMatches(pattern, key))
3300
- )
3301
- )?.group;
3478
+ if (model && model.provider !== report2.providerId) return void 0;
3479
+ if (model) {
3480
+ const modelKeys = [model.id, model.name].map(normalizeMiniMaxModelKey).filter((key) => key !== void 0);
3481
+ const candidates = groups.map((group) => {
3482
+ const bucket = report2.buckets.find((candidate) => candidate.groupId === group);
3483
+ const patterns = [bucket?.groupLabel, ...bucket?.modelKeys ?? [], group].map(normalizeMiniMaxModelKey).filter((key) => key !== void 0);
3484
+ return { group, patterns };
3485
+ });
3486
+ const exact = candidates.find(
3487
+ ({ patterns }) => patterns.some((pattern) => !pattern.includes("*") && modelKeys.includes(pattern))
3488
+ );
3489
+ if (exact) return exact.group;
3490
+ const wildcard = candidates.find(
3491
+ ({ patterns }) => patterns.some(
3492
+ (pattern) => pattern.includes("*") && modelKeys.some((key) => wildcardKeyMatches(pattern, key))
3493
+ )
3494
+ );
3495
+ if (wildcard) return wildcard.group;
3496
+ }
3497
+ return groups.find((group) => group === "general");
3302
3498
  }
3303
3499
  function normalizeMiniMaxModelKey(value) {
3304
3500
  const key = value?.toLowerCase().replace(/[^a-z0-9*]+/gu, "");
@@ -3552,7 +3748,8 @@ var USAGE_SETTINGS_FILE = "pi-usage.json";
3552
3748
  var MAX_USAGE_SETTINGS_BYTES = 64 * 1024;
3553
3749
  var DEFAULT_USAGE_SETTINGS = Object.freeze({
3554
3750
  codexFastMode: false,
3555
- codexStatusResetCountdown: true
3751
+ codexStatusResetCountdown: true,
3752
+ selectedTargets: Object.freeze({})
3556
3753
  });
3557
3754
  function usageSettingsPath() {
3558
3755
  return join(getAgentDir(), USAGE_SETTINGS_FILE);
@@ -3568,10 +3765,16 @@ function normalizeUsageSettings(value) {
3568
3765
  if (Object.hasOwn(value, "fireworksAccountId") && !isFireworksAccountId(value.fireworksAccountId)) {
3569
3766
  return void 0;
3570
3767
  }
3768
+ const selectedTargets = normalizeSelectedTargets(value.selectedTargets);
3769
+ if (Object.hasOwn(value, "selectedTargets") && !selectedTargets) return void 0;
3770
+ const effectiveTargets = { ...selectedTargets ?? {} };
3771
+ if (!effectiveTargets.fireworks && isFireworksAccountId(value.fireworksAccountId)) {
3772
+ effectiveTargets.fireworks = value.fireworksAccountId;
3773
+ }
3571
3774
  return {
3572
3775
  codexFastMode: typeof value.codexFastMode === "boolean" ? value.codexFastMode : DEFAULT_USAGE_SETTINGS.codexFastMode,
3573
3776
  codexStatusResetCountdown: typeof value.codexStatusResetCountdown === "boolean" ? value.codexStatusResetCountdown : DEFAULT_USAGE_SETTINGS.codexStatusResetCountdown,
3574
- ...isFireworksAccountId(value.fireworksAccountId) ? { fireworksAccountId: value.fireworksAccountId } : {}
3777
+ selectedTargets: effectiveTargets
3575
3778
  };
3576
3779
  }
3577
3780
  async function loadUsageSettings(path = usageSettingsPath(), signal) {
@@ -3647,19 +3850,84 @@ function createUsageSettingsRuntime(options = {}) {
3647
3850
  state = saved;
3648
3851
  return structuredClone(state);
3649
3852
  }),
3853
+ updateSelectedTarget: (providerId, targetId, signal, checkPublishedSelection) => enqueue(async () => {
3854
+ const transaction = await saveUsageTargetSelection(
3855
+ path,
3856
+ providerId,
3857
+ targetId,
3858
+ operations,
3859
+ signal
3860
+ );
3861
+ try {
3862
+ await checkPublishedSelection?.();
3863
+ throwIfAborted(signal);
3864
+ } catch (error) {
3865
+ try {
3866
+ await restoreUsageSettingsState(
3867
+ path,
3868
+ transaction.saved,
3869
+ transaction.previous,
3870
+ operations
3871
+ );
3872
+ state = transaction.previous;
3873
+ } catch (rollbackError) {
3874
+ state = await loadUsageSettings(path);
3875
+ throw new AggregateError(
3876
+ [error, rollbackError],
3877
+ "Target selection changed after publication and pi-usage.json rollback failed"
3878
+ );
3879
+ }
3880
+ throw error;
3881
+ }
3882
+ state = transaction.saved;
3883
+ return structuredClone(state);
3884
+ }),
3650
3885
  flush: () => queue
3651
3886
  };
3652
3887
  }
3653
3888
  async function saveUsageSettingsPatch(path, patch, operations, signal) {
3889
+ return saveUsageSettingsDocument(
3890
+ path,
3891
+ (document) => {
3892
+ for (const [key, value] of Object.entries(patch)) {
3893
+ if (value === void 0) delete document[key];
3894
+ else document[key] = value;
3895
+ }
3896
+ },
3897
+ operations,
3898
+ signal
3899
+ );
3900
+ }
3901
+ async function saveUsageTargetSelection(path, providerId, targetId, operations, signal) {
3902
+ if (!isProviderId(providerId) || !isBoundedTargetId(targetId)) {
3903
+ throw new Error("Refusing to save an invalid usage target selection");
3904
+ }
3905
+ const previous = await loadUsageSettings(path, signal);
3906
+ const saved = await saveUsageSettingsDocument(
3907
+ path,
3908
+ (document) => {
3909
+ document.selectedTargets = {
3910
+ ...normalizeSelectedTargets(document.selectedTargets) ?? {},
3911
+ [providerId]: targetId
3912
+ };
3913
+ if (providerId === "fireworks") delete document.fireworksAccountId;
3914
+ },
3915
+ operations,
3916
+ signal,
3917
+ previous
3918
+ );
3919
+ return { saved, previous };
3920
+ }
3921
+ async function saveUsageSettingsDocument(path, mutate, operations, signal, expected) {
3654
3922
  const latest = await loadUsageSettings(path, signal);
3655
3923
  if (latest.kind === "invalid") {
3656
3924
  throw new Error("Cannot overwrite an invalid pi-usage.json; repair it and reload first");
3657
3925
  }
3658
- const document = { ...latest.document };
3659
- for (const [key, value] of Object.entries(patch)) {
3660
- if (value === void 0) delete document[key];
3661
- else document[key] = value;
3926
+ if (expected && !sameUsageSettingsDocument(latest, expected)) {
3927
+ throw new Error("pi-usage.json changed while saving; retry the action");
3662
3928
  }
3929
+ const document = { ...latest.document };
3930
+ mutate(document);
3663
3931
  const settings = normalizeUsageSettings(document);
3664
3932
  if (!settings) throw new Error("Refusing to save invalid pi-usage settings");
3665
3933
  const directory = dirname(path);
@@ -3686,6 +3954,32 @@ async function saveUsageSettingsPatch(path, patch, operations, signal) {
3686
3954
  }
3687
3955
  return { kind: "loaded", path, settings, document };
3688
3956
  }
3957
+ async function restoreUsageSettingsState(path, published, previous, operations) {
3958
+ if (previous.kind === "missing") {
3959
+ const current = await loadUsageSettings(path);
3960
+ if (!sameUsageSettingsDocument(current, published)) {
3961
+ throw new Error("pi-usage.json changed before target selection rollback");
3962
+ }
3963
+ await rm(path);
3964
+ return;
3965
+ }
3966
+ if (previous.kind !== "loaded" || !previous.document) {
3967
+ throw new Error("Cannot restore invalid prior pi-usage.json settings");
3968
+ }
3969
+ await saveUsageSettingsDocument(
3970
+ path,
3971
+ (document) => {
3972
+ for (const key of Object.keys(document)) delete document[key];
3973
+ Object.assign(document, previous.document);
3974
+ },
3975
+ operations,
3976
+ void 0,
3977
+ published
3978
+ );
3979
+ }
3980
+ function sameUsageSettingsDocument(left, right) {
3981
+ return left.kind === right.kind && JSON.stringify(left.document) === JSON.stringify(right.document);
3982
+ }
3689
3983
  async function chmodPrivate(path) {
3690
3984
  await chmod(path, 384);
3691
3985
  }
@@ -3698,6 +3992,19 @@ function isRecord3(value) {
3698
3992
  function isNodeError(error) {
3699
3993
  return error instanceof Error && "code" in error;
3700
3994
  }
3995
+ function normalizeSelectedTargets(value) {
3996
+ if (value === void 0) return {};
3997
+ if (!isRecord3(value)) return void 0;
3998
+ const targets = {};
3999
+ for (const [providerId, targetId] of Object.entries(value)) {
4000
+ if (!isProviderId(providerId) || !isBoundedTargetId(targetId)) return void 0;
4001
+ targets[providerId] = targetId;
4002
+ }
4003
+ return targets;
4004
+ }
4005
+ function isProviderId(value) {
4006
+ return /^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$/u.test(value);
4007
+ }
3701
4008
 
3702
4009
  // src/usage.ts
3703
4010
  import { randomUUID as randomUUID2 } from "node:crypto";
@@ -3908,8 +4215,6 @@ import {
3908
4215
  SettingsList,
3909
4216
  Text
3910
4217
  } from "@earendil-works/pi-tui";
3911
- var AUTO = "Auto";
3912
- var EDIT = "Edit\u2026";
3913
4218
  var OFF = "Off";
3914
4219
  var ON = "On";
3915
4220
  async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent, onApplied) {
@@ -3917,31 +4222,13 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
3917
4222
  if (ctx.hasUI) ctx.ui.notify(`Edit settings manually: ${settingsRuntime.get().path}`, "info");
3918
4223
  return false;
3919
4224
  }
3920
- let changed = false;
3921
- while (!parentSignal.aborted && isCurrent()) {
3922
- const result = await showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, onApplied);
3923
- if (!result) return changed;
3924
- changed ||= result.changed;
3925
- if (!result.editFireworksAccount) return changed;
3926
- changed ||= await editFireworksAccount(
3927
- ctx,
3928
- settingsRuntime,
3929
- parentSignal,
3930
- isCurrent,
3931
- onApplied
3932
- );
3933
- }
3934
- return changed;
3935
- }
3936
- async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, onApplied) {
3937
- return ctx.ui.custom((tui, theme, _keybindings, done) => {
4225
+ return await ctx.ui.custom((tui, theme, _keybindings, done) => {
3938
4226
  const localController = new AbortController();
3939
4227
  const signal = AbortSignal.any([parentSignal, localController.signal]);
3940
4228
  let changed = false;
3941
4229
  let closing = false;
3942
4230
  let saveQueue = Promise.resolve();
3943
4231
  const state = settingsRuntime.get();
3944
- const fireworksValue = state.settings.fireworksAccountId ?? AUTO;
3945
4232
  const items = [
3946
4233
  {
3947
4234
  id: "codexFastMode",
@@ -3956,13 +4243,6 @@ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, o
3956
4243
  description: "Show time remaining until each Codex usage limit resets.",
3957
4244
  currentValue: state.settings.codexStatusResetCountdown ? ON : OFF,
3958
4245
  values: [OFF, ON]
3959
- },
3960
- {
3961
- id: "fireworksAccountId",
3962
- label: "Fireworks account",
3963
- description: "Select Edit to enter a visible account slug, or submit blank to clear it.",
3964
- currentValue: fireworksValue,
3965
- values: state.settings.fireworksAccountId ? [state.settings.fireworksAccountId, EDIT] : [AUTO, EDIT]
3966
4246
  }
3967
4247
  ];
3968
4248
  const container = new Container();
@@ -3972,13 +4252,13 @@ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, o
3972
4252
  if (closing) return;
3973
4253
  closing = true;
3974
4254
  localController.abort();
3975
- done({ changed, editFireworksAccount: false });
4255
+ done(changed);
3976
4256
  };
3977
4257
  const queueUpdate = (id, requested, display) => {
3978
4258
  saveQueue = saveQueue.then(async () => {
3979
4259
  const previous = settingsRuntime.get().settings[id];
3980
4260
  if (settingsRuntime.get().kind === "invalid") {
3981
- settingsList.updateValue(id, displaySetting(id, previous));
4261
+ settingsList.updateValue(id, previous ? ON : OFF);
3982
4262
  if (!signal.aborted && isCurrent()) {
3983
4263
  ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
3984
4264
  tui.requestRender();
@@ -3989,7 +4269,7 @@ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, o
3989
4269
  await settingsRuntime.update({ [id]: requested }, signal);
3990
4270
  } catch (error) {
3991
4271
  if (signal.aborted || !isCurrent()) return;
3992
- settingsList.updateValue(id, displaySetting(id, previous));
4272
+ settingsList.updateValue(id, previous ? ON : OFF);
3993
4273
  ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
3994
4274
  tui.requestRender();
3995
4275
  return;
@@ -4009,18 +4289,7 @@ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, o
4009
4289
  getSettingsListTheme(),
4010
4290
  (id, value) => {
4011
4291
  if (closing || signal.aborted || !isCurrent()) return;
4012
- if (id === "fireworksAccountId") {
4013
- if (value === EDIT) {
4014
- saveQueue = saveQueue.then(() => {
4015
- if (closing || signal.aborted || !isCurrent()) return;
4016
- closing = true;
4017
- done({ changed, editFireworksAccount: true });
4018
- });
4019
- }
4020
- return;
4021
- }
4022
- const settingId = id;
4023
- queueUpdate(settingId, value !== OFF, value);
4292
+ queueUpdate(id, value !== OFF, value);
4024
4293
  },
4025
4294
  cancel
4026
4295
  );
@@ -4040,43 +4309,7 @@ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, o
4040
4309
  parentSignal.removeEventListener("abort", cancel);
4041
4310
  }
4042
4311
  };
4043
- });
4044
- }
4045
- async function editFireworksAccount(ctx, settingsRuntime, signal, isCurrent, onApplied) {
4046
- while (!signal.aborted && isCurrent()) {
4047
- const state = settingsRuntime.get();
4048
- if (state.kind === "invalid") {
4049
- ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
4050
- return false;
4051
- }
4052
- const entered = await ctx.ui.input(
4053
- "Fireworks account slug \xB7 submit blank for Auto",
4054
- state.settings.fireworksAccountId ?? "Example: acme",
4055
- { signal }
4056
- );
4057
- if (signal.aborted || !isCurrent() || entered === void 0) return false;
4058
- const normalized = entered.trim();
4059
- const requested = normalized || void 0;
4060
- if (requested !== void 0 && !isFireworksAccountId(requested)) {
4061
- ctx.ui.notify("Enter a URL-safe Fireworks account slug.", "warning");
4062
- continue;
4063
- }
4064
- if (requested === state.settings.fireworksAccountId) return false;
4065
- try {
4066
- await settingsRuntime.update({ fireworksAccountId: requested }, signal);
4067
- } catch (error) {
4068
- if (signal.aborted || !isCurrent()) return false;
4069
- ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
4070
- return false;
4071
- }
4072
- onApplied("fireworksAccountId");
4073
- return true;
4074
- }
4075
- return false;
4076
- }
4077
- function displaySetting(id, value) {
4078
- if (id === "fireworksAccountId") return typeof value === "string" ? value : AUTO;
4079
- return value ? ON : OFF;
4312
+ }) ?? false;
4080
4313
  }
4081
4314
 
4082
4315
  // src/usage.ts
@@ -4093,6 +4326,9 @@ var VIEW_ALL = "View all configured providers\u2026";
4093
4326
  var CLOSE = "Close";
4094
4327
  var SETTINGS = "Settings";
4095
4328
  var REDEEM_CODEX_RESET = "Redeem usage limit reset\u2026";
4329
+ var UsageTargetSelectionChangedError = class extends Error {
4330
+ name = "UsageTargetSelectionChangedError";
4331
+ };
4096
4332
  function usageExtension(pi, dependencies = {}) {
4097
4333
  const credentialReader = dependencies.credentialReader;
4098
4334
  const credentialCandidates = createOAuthCredentialCandidateReader(pi, credentialReader);
@@ -4162,10 +4398,8 @@ function usageExtension(pi, dependencies = {}) {
4162
4398
  return;
4163
4399
  }
4164
4400
  if (outcome.state.status !== "ready") {
4165
- if (safeSetStatus(
4166
- ctx,
4167
- outcome.state.status === "auth-unavailable" ? "auth unavailable" : "usage error"
4168
- )) {
4401
+ const chip = outcome.state.status === "auth-unavailable" ? "auth unavailable" : outcome.state.status === "selection-required" ? "selection required" : `usage err: ${outcome.state.message.slice(0, 50)}`;
4402
+ if (safeSetStatus(ctx, chip)) {
4169
4403
  if (shouldSchedule && sessionActive) scheduleStatusRefresh(ctx, model);
4170
4404
  }
4171
4405
  return;
@@ -4217,8 +4451,8 @@ function usageExtension(pi, dependencies = {}) {
4217
4451
  const expectedSessionGeneration = sessionGeneration;
4218
4452
  const expectedSessionId = ctx.sessionManager.getSessionId();
4219
4453
  const expectedModelIdentity = modelIdentity(ctx.model);
4220
- const expectedFireworksAccountId = adapter.id === "fireworks" ? settingsRuntime.get().settings.fireworksAccountId : void 0;
4221
- const querySettings = adapter.id === "fireworks" ? { fireworksAccountId: expectedFireworksAccountId } : void 0;
4454
+ const expectedTargetId = adapter.targets ? settingsRuntime.get().settings.selectedTargets[adapter.id] : void 0;
4455
+ const providerName = providerDisplayName(ctx, adapter.id);
4222
4456
  let auth;
4223
4457
  try {
4224
4458
  auth = await awaitWithDeadline(
@@ -4235,17 +4469,16 @@ function usageExtension(pi, dependencies = {}) {
4235
4469
  return {
4236
4470
  state: {
4237
4471
  providerId: adapter.id,
4238
- providerName: adapter.displayName,
4472
+ providerName,
4239
4473
  displayState,
4240
4474
  status: isTimeoutError(error) ? "query-failed" : "auth-unavailable",
4241
4475
  message: errorMessage(error)
4242
4476
  }
4243
4477
  };
4244
4478
  }
4245
- const requiresRequestBoundaryGuard = [
4479
+ const requiresRequestBoundaryGuard = adapter.targets !== void 0 || [
4246
4480
  "baseten",
4247
4481
  "deepseek",
4248
- "fireworks",
4249
4482
  "minimax",
4250
4483
  "minimax-cn",
4251
4484
  "moonshotai",
@@ -4255,7 +4488,7 @@ function usageExtension(pi, dependencies = {}) {
4255
4488
  "zai",
4256
4489
  "zai-coding-cn"
4257
4490
  ].includes(adapter.id);
4258
- const requestContextChanged = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity || adapter.id === "fireworks" && settingsRuntime.get().settings.fireworksAccountId !== expectedFireworksAccountId;
4491
+ const requestContextChanged = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity || adapter.targets !== void 0 && settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedTargetId;
4259
4492
  if (requiresRequestBoundaryGuard && requestContextChanged()) throw abortError();
4260
4493
  if (!auth) {
4261
4494
  if (displayState === "current") {
@@ -4264,98 +4497,145 @@ function usageExtension(pi, dependencies = {}) {
4264
4497
  return {
4265
4498
  state: {
4266
4499
  providerId: adapter.id,
4267
- providerName: adapter.displayName,
4500
+ providerName,
4268
4501
  displayState,
4269
4502
  status: "auth-unavailable",
4270
- message: `No runtime credential is configured for ${adapter.displayName}.`
4503
+ message: `No runtime credential is configured for ${providerName}.`
4271
4504
  },
4272
4505
  authState: "unavailable"
4273
4506
  };
4274
4507
  }
4275
- const queryFingerprint = adapter.id === "fireworks" ? `${auth.fingerprint}:account:${expectedFireworksAccountId ?? "auto"}` : auth.fingerprint;
4276
- if (displayState === "current") {
4277
- transitionCurrentIdentity(`${adapter.id}:${queryFingerprint}`, adapter.id);
4278
- }
4279
- const cached = !force ? cache.get(adapter.id, queryFingerprint) : void 0;
4280
- if (cached) {
4281
- return {
4282
- state: {
4283
- providerId: adapter.id,
4284
- providerName: adapter.displayName,
4285
- displayState,
4286
- status: "ready",
4287
- report: cached
4288
- },
4289
- fingerprint: auth.fingerprint
4290
- };
4291
- }
4292
- const failureKey = `${adapter.id}:${queryFingerprint}`;
4293
- const previousFailure = failureBackoff.get(failureKey);
4294
- if (!force && previousFailure && previousFailure.until > Date.now()) {
4295
- return {
4296
- state: {
4297
- providerId: adapter.id,
4298
- providerName: adapter.displayName,
4299
- displayState,
4300
- status: "query-failed",
4301
- message: previousFailure.message
4302
- },
4303
- fingerprint: auth.fingerprint
4304
- };
4305
- }
4306
- failureBackoff.delete(failureKey);
4307
- querySequence += 1;
4308
- const queryId = querySequence;
4309
- setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
4310
4508
  let retryableAuthChanged = false;
4509
+ const guard = async () => {
4510
+ if (signal.aborted || requestContextChanged()) throw abortError();
4511
+ if (!requiresRequestBoundaryGuard) return;
4512
+ const revalidated = await awaitWithDeadline(
4513
+ resolveUsageAuth(ctx, adapter, void 0, credentialReader, credentialCandidates),
4514
+ signal,
4515
+ Math.max(1, deadlineAt - Date.now()),
4516
+ `revalidating ${providerName} runtime auth`
4517
+ );
4518
+ if (signal.aborted || requestContextChanged()) throw abortError();
4519
+ if (revalidated?.fingerprint !== auth.fingerprint) {
4520
+ if (["deepseek", "minimax", "minimax-cn"].includes(adapter.id)) {
4521
+ retryableAuthChanged = true;
4522
+ throw new Error(`${providerName} runtime credential changed during the usage query.`);
4523
+ }
4524
+ throw abortError();
4525
+ }
4526
+ };
4527
+ let queryFingerprint = adapter.targets ? `${auth.fingerprint}:target:${expectedTargetId ?? "unresolved"}` : auth.fingerprint;
4528
+ let failureKey = `${adapter.id}:${queryFingerprint}`;
4529
+ let queryId;
4311
4530
  try {
4312
- const remainingMs = Math.max(1, deadlineAt - Date.now());
4313
- const guard = requiresRequestBoundaryGuard ? async () => {
4314
- if (signal.aborted || requestContextChanged()) throw abortError();
4315
- const revalidated = await awaitWithDeadline(
4316
- resolveUsageAuth(ctx, adapter, void 0, credentialReader, credentialCandidates),
4317
- signal,
4318
- Math.max(1, deadlineAt - Date.now()),
4319
- `revalidating ${adapter.displayName} runtime auth`
4320
- );
4321
- if (signal.aborted || requestContextChanged()) throw abortError();
4322
- if (revalidated?.fingerprint !== auth.fingerprint) {
4323
- if (["deepseek", "minimax", "minimax-cn"].includes(adapter.id)) {
4324
- retryableAuthChanged = true;
4325
- throw new Error(
4326
- `${adapter.displayName} runtime credential changed during the usage query.`
4327
- );
4328
- }
4329
- throw abortError();
4531
+ const previousDiscoveryFailure = failureBackoff.get(failureKey);
4532
+ if (!force && previousDiscoveryFailure && previousDiscoveryFailure.until > Date.now()) {
4533
+ return {
4534
+ state: {
4535
+ providerId: adapter.id,
4536
+ providerName,
4537
+ displayState,
4538
+ status: "query-failed",
4539
+ message: previousDiscoveryFailure.message
4540
+ },
4541
+ fingerprint: auth.fingerprint,
4542
+ rememberedTargetId: expectedTargetId
4543
+ };
4544
+ }
4545
+ const target = await resolveUsageTarget(
4546
+ adapter,
4547
+ auth,
4548
+ expectedTargetId,
4549
+ signal,
4550
+ Math.max(1, deadlineAt - Date.now()),
4551
+ guard
4552
+ );
4553
+ if (target.kind === "selection-required") {
4554
+ if (displayState === "current") {
4555
+ transitionCurrentIdentity(`${adapter.id}:${queryFingerprint}`, adapter.id);
4330
4556
  }
4331
- } : void 0;
4557
+ return {
4558
+ state: {
4559
+ providerId: adapter.id,
4560
+ providerName,
4561
+ displayState,
4562
+ status: "selection-required",
4563
+ singularLabel: adapter.targets?.singularLabel ?? "target",
4564
+ pluralLabel: adapter.targets?.pluralLabel ?? "targets",
4565
+ choices: target.choices
4566
+ },
4567
+ fingerprint: auth.fingerprint,
4568
+ rememberedTargetId: expectedTargetId
4569
+ };
4570
+ }
4571
+ queryFingerprint = adapter.targets ? `${auth.fingerprint}:target:${target.targetId ?? "none"}` : auth.fingerprint;
4572
+ failureKey = `${adapter.id}:${queryFingerprint}`;
4573
+ if (displayState === "current") {
4574
+ transitionCurrentIdentity(`${adapter.id}:${queryFingerprint}`, adapter.id);
4575
+ }
4576
+ const cached = !force ? cache.get(adapter.id, queryFingerprint) : void 0;
4577
+ if (cached) {
4578
+ return {
4579
+ state: {
4580
+ providerId: adapter.id,
4581
+ providerName,
4582
+ displayState,
4583
+ status: "ready",
4584
+ report: cached
4585
+ },
4586
+ fingerprint: auth.fingerprint,
4587
+ rememberedTargetId: expectedTargetId
4588
+ };
4589
+ }
4590
+ const previousFailure = failureBackoff.get(failureKey);
4591
+ if (!force && previousFailure && previousFailure.until > Date.now()) {
4592
+ return {
4593
+ state: {
4594
+ providerId: adapter.id,
4595
+ providerName,
4596
+ displayState,
4597
+ status: "query-failed",
4598
+ message: previousFailure.message
4599
+ },
4600
+ fingerprint: auth.fingerprint,
4601
+ rememberedTargetId: expectedTargetId
4602
+ };
4603
+ }
4604
+ failureBackoff.delete(failureKey);
4605
+ querySequence += 1;
4606
+ queryId = querySequence;
4607
+ setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
4332
4608
  const report2 = await queryProviderUsage(
4333
4609
  adapter,
4334
4610
  auth,
4335
4611
  signal,
4336
- remainingMs,
4337
- guard,
4338
- querySettings
4612
+ Math.max(1, deadlineAt - Date.now()),
4613
+ requiresRequestBoundaryGuard ? guard : void 0,
4614
+ target.targetId
4339
4615
  );
4340
- if (guard) await guard();
4616
+ if (requiresRequestBoundaryGuard) await guard();
4617
+ const effectiveReport = { ...report2, providerName };
4341
4618
  if (latestQueries.get(failureKey) === queryId) {
4342
- cache.set(adapter.id, queryFingerprint, report2);
4619
+ cache.set(adapter.id, queryFingerprint, effectiveReport);
4343
4620
  failureBackoff.delete(failureKey);
4344
4621
  }
4345
4622
  return {
4346
4623
  state: {
4347
4624
  providerId: adapter.id,
4348
- providerName: adapter.displayName,
4625
+ providerName,
4349
4626
  displayState,
4350
4627
  status: "ready",
4351
- report: report2
4628
+ report: effectiveReport
4352
4629
  },
4353
- fingerprint: auth.fingerprint
4630
+ fingerprint: auth.fingerprint,
4631
+ rememberedTargetId: expectedTargetId
4354
4632
  };
4355
4633
  } catch (error) {
4356
4634
  if (isStaleExtensionContextError(error) || isAbortError3(error)) throw error;
4357
4635
  if (retryableAuthChanged && authRetry === 0 && !signal.aborted && !requestContextChanged() && Date.now() < deadlineAt) {
4358
- if (latestQueries.get(failureKey) === queryId) latestQueries.delete(failureKey);
4636
+ if (queryId !== void 0 && latestQueries.get(failureKey) === queryId) {
4637
+ latestQueries.delete(failureKey);
4638
+ }
4359
4639
  return queryAdapterState(
4360
4640
  ctx,
4361
4641
  adapter,
@@ -4371,7 +4651,7 @@ function usageExtension(pi, dependencies = {}) {
4371
4651
  for (const [key, failure] of failureBackoff) {
4372
4652
  if (failure.until <= now) failureBackoff.delete(key);
4373
4653
  }
4374
- if (latestQueries.get(failureKey) === queryId) {
4654
+ if (queryId === void 0 || latestQueries.get(failureKey) === queryId) {
4375
4655
  setBoundedMap(
4376
4656
  failureBackoff,
4377
4657
  failureKey,
@@ -4382,15 +4662,52 @@ function usageExtension(pi, dependencies = {}) {
4382
4662
  return {
4383
4663
  state: {
4384
4664
  providerId: adapter.id,
4385
- providerName: adapter.displayName,
4665
+ providerName,
4386
4666
  displayState,
4387
4667
  status: "query-failed",
4388
4668
  message
4389
4669
  },
4390
- fingerprint: auth.fingerprint
4670
+ fingerprint: auth.fingerprint,
4671
+ rememberedTargetId: expectedTargetId
4391
4672
  };
4392
4673
  }
4393
4674
  };
4675
+ const loadTargetChoices = async (ctx, adapter, signal) => {
4676
+ if (!adapter.targets) throw new Error("Provider does not support usage targets.");
4677
+ const expectedSessionGeneration = sessionGeneration;
4678
+ const expectedSessionId = ctx.sessionManager.getSessionId();
4679
+ const expectedModel = modelIdentity(ctx.model);
4680
+ const expectedTargetId = settingsRuntime.get().settings.selectedTargets[adapter.id];
4681
+ const deadlineAt = Date.now() + DEFAULT_TIMEOUT_MS;
4682
+ const changed = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModel || settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedTargetId;
4683
+ const auth = await awaitWithDeadline(
4684
+ resolveUsageAuth(ctx, adapter, void 0, credentialReader, credentialCandidates),
4685
+ signal,
4686
+ Math.max(1, deadlineAt - Date.now()),
4687
+ `resolving ${providerDisplayName(ctx, adapter.id)} runtime auth`
4688
+ );
4689
+ if (!auth || signal.aborted || changed()) throw abortError();
4690
+ const guard = async () => {
4691
+ if (signal.aborted || changed()) throw abortError();
4692
+ const revalidated = await awaitWithDeadline(
4693
+ resolveUsageAuth(ctx, adapter, void 0, credentialReader, credentialCandidates),
4694
+ signal,
4695
+ Math.max(1, deadlineAt - Date.now()),
4696
+ `revalidating ${providerDisplayName(ctx, adapter.id)} runtime auth`
4697
+ );
4698
+ if (signal.aborted || changed() || revalidated?.fingerprint !== auth.fingerprint) {
4699
+ throw abortError();
4700
+ }
4701
+ };
4702
+ const choices = await listUsageTargets(
4703
+ adapter,
4704
+ auth,
4705
+ signal,
4706
+ Math.max(1, deadlineAt - Date.now()),
4707
+ guard
4708
+ );
4709
+ return { choices, fingerprint: auth.fingerprint };
4710
+ };
4394
4711
  const queryCurrentState = async (ctx, model, force, signal) => {
4395
4712
  const adapter = adapterForProvider(model?.provider);
4396
4713
  if (!adapter) {
@@ -4446,7 +4763,9 @@ function usageExtension(pi, dependencies = {}) {
4446
4763
  const startStatusRefresh = (ctx, model, force) => {
4447
4764
  void refreshCurrentStatus(ctx, model, force).catch((error) => {
4448
4765
  if (isStaleExtensionContextError(error) || isAbortError3(error)) return;
4449
- safeSetStatus(ctx, "usage error");
4766
+ const message = errorMessage(error);
4767
+ console.error("[pi-usage] refresh failed:", message);
4768
+ safeSetStatus(ctx, `usage err: ${message.slice(0, 50)}`);
4450
4769
  });
4451
4770
  };
4452
4771
  const runMenuOperation = async (ctx, label, parentSignal, operation, cancellable = true) => {
@@ -4474,6 +4793,8 @@ function usageExtension(pi, dependencies = {}) {
4474
4793
  return false;
4475
4794
  }
4476
4795
  const adapter = adapterForProvider(model?.provider);
4796
+ const selectionStillCurrent = !adapter?.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId;
4797
+ if (!selectionStillCurrent) return false;
4477
4798
  if (outcome.authState === "unavailable") {
4478
4799
  if (!adapter) return false;
4479
4800
  try {
@@ -4483,7 +4804,7 @@ function usageExtension(pi, dependencies = {}) {
4483
4804
  DEFAULT_TIMEOUT_MS,
4484
4805
  `revalidating ${adapter.displayName} runtime auth`
4485
4806
  );
4486
- return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && auth === void 0;
4807
+ return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId) && auth === void 0;
4487
4808
  } catch (error) {
4488
4809
  if (isAbortError3(error) || isStaleExtensionContextError(error)) throw error;
4489
4810
  return false;
@@ -4498,7 +4819,7 @@ function usageExtension(pi, dependencies = {}) {
4498
4819
  DEFAULT_TIMEOUT_MS,
4499
4820
  `revalidating ${adapter.displayName} runtime auth`
4500
4821
  );
4501
- return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && auth?.fingerprint === outcome.fingerprint;
4822
+ return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId) && auth?.fingerprint === outcome.fingerprint;
4502
4823
  } catch (error) {
4503
4824
  if (isAbortError3(error) || isStaleExtensionContextError(error)) throw error;
4504
4825
  return false;
@@ -4554,6 +4875,88 @@ function usageExtension(pi, dependencies = {}) {
4554
4875
  let redemptionId;
4555
4876
  let resetOutcome;
4556
4877
  let resetFailure;
4878
+ const actionableTargetState = () => {
4879
+ if (visibleStates.length !== 1) return void 0;
4880
+ const state = visibleStates[0];
4881
+ return state && (state.status === "ready" || state.status === "selection-required") && adapterForProvider(state.providerId)?.targets ? state : void 0;
4882
+ };
4883
+ const promptForTarget = async (state, stateFingerprint) => {
4884
+ const adapter = adapterForProvider(state.providerId);
4885
+ if (!adapter?.targets) return false;
4886
+ const expectedRememberedTargetId = settingsRuntime.get().settings.selectedTargets[adapter.id];
4887
+ const snapshot = state.status === "selection-required" && stateFingerprint ? { choices: state.choices, fingerprint: stateFingerprint } : await runMenuOperation(
4888
+ ctx,
4889
+ `Loading ${adapter.targets.pluralLabel}\u2026`,
4890
+ controller.signal,
4891
+ (signal) => loadTargetChoices(ctx, adapter, signal)
4892
+ );
4893
+ if (!snapshot || controller.signal.aborted || statusGeneration !== menuGeneration) {
4894
+ return false;
4895
+ }
4896
+ const selectOptions = createUsageTargetSelectOptions(snapshot.choices);
4897
+ const selected = await ctx.ui.select(
4898
+ `Select ${adapter.targets.singularLabel} for ${providerDisplayName(ctx, adapter.id)}`,
4899
+ [...selectOptions.options],
4900
+ { signal: controller.signal }
4901
+ );
4902
+ if (selected === void 0 || controller.signal.aborted || statusGeneration !== menuGeneration || settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedRememberedTargetId) {
4903
+ return false;
4904
+ }
4905
+ const targetId = selectOptions.targetIdFor(selected);
4906
+ if (!targetId) return false;
4907
+ const revalidated = await runMenuOperation(
4908
+ ctx,
4909
+ `Revalidating ${adapter.targets.singularLabel}\u2026`,
4910
+ controller.signal,
4911
+ (signal) => loadTargetChoices(ctx, adapter, signal)
4912
+ );
4913
+ if (!revalidated || controller.signal.aborted || statusGeneration !== menuGeneration || settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedRememberedTargetId) {
4914
+ return false;
4915
+ }
4916
+ if (revalidated.fingerprint !== snapshot.fingerprint || !revalidated.choices.some((choice) => choice.id === targetId)) {
4917
+ ctx.ui.notify(
4918
+ `${providerDisplayName(ctx, adapter.id)} ${adapter.targets.pluralLabel} changed; choose again.`,
4919
+ "warning"
4920
+ );
4921
+ return false;
4922
+ }
4923
+ let saved;
4924
+ try {
4925
+ saved = await runMenuOperation(
4926
+ ctx,
4927
+ `Saving ${adapter.targets.singularLabel}\u2026`,
4928
+ controller.signal,
4929
+ (signal) => settingsRuntime.updateSelectedTarget(adapter.id, targetId, signal, async () => {
4930
+ let published;
4931
+ try {
4932
+ published = await loadTargetChoices(ctx, adapter, signal);
4933
+ } catch (error) {
4934
+ if (signal.aborted || controller.signal.aborted || statusGeneration !== menuGeneration || isStaleExtensionContextError(error)) {
4935
+ throw error;
4936
+ }
4937
+ throw new UsageTargetSelectionChangedError();
4938
+ }
4939
+ if (published.fingerprint !== snapshot.fingerprint || !published.choices.some((choice) => choice.id === targetId)) {
4940
+ throw new UsageTargetSelectionChangedError();
4941
+ }
4942
+ })
4943
+ );
4944
+ } catch (error) {
4945
+ if (error instanceof UsageTargetSelectionChangedError) {
4946
+ ctx.ui.notify(
4947
+ `${providerDisplayName(ctx, adapter.id)} ${adapter.targets.pluralLabel} changed; choose again.`,
4948
+ "warning"
4949
+ );
4950
+ return false;
4951
+ }
4952
+ throw error;
4953
+ }
4954
+ if (!saved || controller.signal.aborted || statusGeneration !== menuGeneration) {
4955
+ return false;
4956
+ }
4957
+ invalidateProviderState(adapter.id);
4958
+ return true;
4959
+ };
4557
4960
  const { defineMenu, runMenu } = await import("@narumitw/pi-tui-kit");
4558
4961
  if (controller.signal.aborted || statusGeneration !== menuGeneration) return;
4559
4962
  const menu = defineMenu({
@@ -4561,6 +4964,8 @@ function usageExtension(pi, dependencies = {}) {
4561
4964
  screens: {
4562
4965
  main: () => {
4563
4966
  const fastAvailability = fastRuntime.availability(ctx.model);
4967
+ const targetState = actionableTargetState();
4968
+ const targetAdapter = adapterForProvider(targetState?.providerId);
4564
4969
  const fastLines = fastAvailability.kind === "available" ? [`Fast mode: ${fastAvailability.enabled ? "On" : "Off"}`, FAST_USAGE_WARNING] : fastAvailability.kind === "unavailable" ? [`Fast mode: Unavailable \xB7 ${fastAvailability.reason}`] : [];
4565
4970
  return {
4566
4971
  kind: "actions",
@@ -4569,6 +4974,13 @@ function usageExtension(pi, dependencies = {}) {
4569
4974
  items: [
4570
4975
  { id: "refresh", label: REFRESH_CURRENT, action: "refresh" },
4571
4976
  { id: "settings", label: SETTINGS, action: "settings" },
4977
+ ...targetState && targetAdapter?.targets ? [
4978
+ {
4979
+ id: "target",
4980
+ label: `${targetState.status === "selection-required" ? "Select" : "Change"} ${targetAdapter.targets.singularLabel}\u2026`,
4981
+ action: "target"
4982
+ }
4983
+ ] : [],
4572
4984
  ...fastAvailability.kind === "available" ? [
4573
4985
  {
4574
4986
  id: "toggle-fast",
@@ -4599,7 +5011,7 @@ function usageExtension(pi, dependencies = {}) {
4599
5011
  title: "Select a configured provider",
4600
5012
  items: configuredAdapters(ctx).filter((adapter) => adapter.id !== ctx.model?.provider).map((adapter) => ({
4601
5013
  id: adapter.id,
4602
- label: adapter.displayName,
5014
+ label: providerDisplayName(ctx, adapter.id),
4603
5015
  action: "provider"
4604
5016
  })),
4605
5017
  hint: "back"
@@ -4661,6 +5073,58 @@ function usageExtension(pi, dependencies = {}) {
4661
5073
  })
4662
5074
  },
4663
5075
  actions: {
5076
+ target: async () => {
5077
+ const targetState = actionableTargetState();
5078
+ if (!targetState) return { kind: "rejected" };
5079
+ try {
5080
+ const stateFingerprint = targetState === current.state ? current.fingerprint : void 0;
5081
+ if (!await promptForTarget(targetState, stateFingerprint)) {
5082
+ return { kind: "stay" };
5083
+ }
5084
+ const adapter = adapterForProvider(targetState.providerId);
5085
+ if (!adapter) return { kind: "rejected" };
5086
+ if (targetState.providerId === ctx.model?.provider) {
5087
+ const refreshed = await queryStableCurrent(
5088
+ ctx,
5089
+ true,
5090
+ controller,
5091
+ `Checking ${providerDisplayName(ctx, adapter.id)} usage\u2026`
5092
+ );
5093
+ if (!refreshed) return { kind: "stay" };
5094
+ stableCurrent = refreshed;
5095
+ current = refreshed.outcome;
5096
+ visibleStates = [current.state];
5097
+ publishStableCurrent(ctx, refreshed);
5098
+ return { kind: "stay" };
5099
+ }
5100
+ const outcome = await runMenuOperation(
5101
+ ctx,
5102
+ `Checking ${providerDisplayName(ctx, adapter.id)} usage\u2026`,
5103
+ controller.signal,
5104
+ (signal) => queryAdapterState(ctx, adapter, "configured", true, signal)
5105
+ );
5106
+ if (!outcome) return { kind: "stay" };
5107
+ const revalidated = await queryStableCurrent(
5108
+ ctx,
5109
+ false,
5110
+ controller,
5111
+ "Revalidating current usage\u2026"
5112
+ );
5113
+ if (!revalidated) return { kind: "stay" };
5114
+ stableCurrent = revalidated;
5115
+ current = revalidated.outcome;
5116
+ visibleStates = [
5117
+ outcome.state.providerId === current.state.providerId ? current.state : { ...outcome.state, displayState: "configured" }
5118
+ ];
5119
+ return { kind: "stay" };
5120
+ } catch (error) {
5121
+ if (isAbortError3(error) || isStaleExtensionContextError(error)) {
5122
+ return { kind: "stay" };
5123
+ }
5124
+ ctx.ui.notify(`Could not select target: ${errorMessage(error)}`, "error");
5125
+ return { kind: "stay" };
5126
+ }
5127
+ },
4664
5128
  settings: async () => {
4665
5129
  await showUsageSettings(
4666
5130
  ctx,
@@ -4869,13 +5333,25 @@ function usageExtension(pi, dependencies = {}) {
4869
5333
  (candidate) => candidate.id === itemId && candidate.id !== ctx.model?.provider
4870
5334
  );
4871
5335
  if (!adapter) return { kind: "back" };
4872
- const outcome = await runMenuOperation(
5336
+ let outcome = await runMenuOperation(
4873
5337
  ctx,
4874
- `Checking ${adapter.displayName} usage\u2026`,
5338
+ `Checking ${providerDisplayName(ctx, adapter.id)} usage\u2026`,
4875
5339
  controller.signal,
4876
5340
  (signal) => queryAdapterState(ctx, adapter, "configured", false, signal)
4877
5341
  );
4878
5342
  if (!outcome) return { kind: "back" };
5343
+ if (outcome.state.status === "selection-required") {
5344
+ if (!await promptForTarget(outcome.state, outcome.fingerprint)) {
5345
+ return { kind: "back" };
5346
+ }
5347
+ outcome = await runMenuOperation(
5348
+ ctx,
5349
+ `Checking ${providerDisplayName(ctx, adapter.id)} usage\u2026`,
5350
+ controller.signal,
5351
+ (signal) => queryAdapterState(ctx, adapter, "configured", true, signal)
5352
+ );
5353
+ if (!outcome) return { kind: "back" };
5354
+ }
4879
5355
  const revalidated = await queryStableCurrent(
4880
5356
  ctx,
4881
5357
  false,
@@ -4918,7 +5394,7 @@ function usageExtension(pi, dependencies = {}) {
4918
5394
  const adapter = adapters[index];
4919
5395
  return {
4920
5396
  providerId: adapter.id,
4921
- providerName: adapter.displayName,
5397
+ providerName: providerDisplayName(ctx, adapter.id),
4922
5398
  displayState: "configured",
4923
5399
  status: "query-failed",
4924
5400
  message: errorMessage(result.reason)
@@ -5032,15 +5508,19 @@ export {
5032
5508
  codexFastStatusLabel,
5033
5509
  consumeCodexResetCredit,
5034
5510
  correctCodexFastMessageCost,
5511
+ createFireworksAdapter,
5035
5512
  createUsageSettingsRuntime,
5513
+ createUsageTargetSelectOptions,
5036
5514
  usageExtension as default,
5037
5515
  errorMessage,
5038
5516
  fingerprintResolvedAuth,
5039
5517
  formatProviderStates,
5040
5518
  formatUsageReport,
5041
5519
  formatUsageStatusline,
5520
+ isBoundedTargetId,
5042
5521
  isStaleExtensionContextError,
5043
5522
  listCodexResetCredits,
5523
+ listUsageTargets,
5044
5524
  loadUsageSettings,
5045
5525
  miniMaxUsageKind,
5046
5526
  normalizeBasetenBillingUsagePayload,
@@ -5056,6 +5536,7 @@ export {
5056
5536
  normalizeOpenCodeZenPayload,
5057
5537
  normalizeOpenRouterKeyPayload,
5058
5538
  normalizeUsageSettings,
5539
+ normalizeUsageTargets,
5059
5540
  normalizeVercelAIGatewayCreditsPayload,
5060
5541
  normalizeXaiBillingPayload,
5061
5542
  normalizeZaiQuotaPayload,
@@ -5065,6 +5546,7 @@ export {
5065
5546
  redactUsageError,
5066
5547
  resolveCodexResetAuth,
5067
5548
  resolveUsageAuth,
5549
+ resolveUsageTarget,
5068
5550
  rewriteCodexFastPayload,
5069
5551
  runWithConcurrency,
5070
5552
  sanitizeDisplayText,