@narumitw/pi-usage 0.60.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;
@@ -1787,14 +1896,104 @@ function clampPercent3(value) {
1787
1896
  return Math.min(100, Math.max(0, value));
1788
1897
  }
1789
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
+
1790
1992
  // src/query.ts
1791
1993
  var BASETEN_BILLING_USAGE_URL = "https://api.baseten.co/v1/billing/usage_summary";
1792
1994
  var BASETEN_USAGE_WINDOW_DAYS = 30;
1793
1995
  var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
1794
1996
  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
1997
  var GITHUB_COPILOT_USAGE_URL = "https://api.github.com/copilot_internal/user";
1799
1998
  var OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
1800
1999
  var VERCEL_AI_GATEWAY_CREDITS_URL = "https://ai-gateway.vercel.sh/v1/credits";
@@ -1833,7 +2032,7 @@ var SUPPORTED_ADAPTERS = [
1833
2032
  basetenBillingUsageUrl(windowAt),
1834
2033
  auth,
1835
2034
  signal,
1836
- remainingTimeout(timeoutMs, startedAt, "fetching Baseten billing usage"),
2035
+ remainingTimeout2(timeoutMs, startedAt, "fetching Baseten billing usage"),
1837
2036
  "Baseten billing usage endpoint",
1838
2037
  { redirect: "error" }
1839
2038
  );
@@ -1926,7 +2125,7 @@ var SUPPORTED_ADAPTERS = [
1926
2125
  VERCEL_AI_GATEWAY_CREDITS_URL,
1927
2126
  auth,
1928
2127
  signal,
1929
- remainingTimeout(timeoutMs, startedAt, "fetching Vercel AI Gateway credits"),
2128
+ remainingTimeout2(timeoutMs, startedAt, "fetching Vercel AI Gateway credits"),
1930
2129
  "Vercel AI Gateway credits endpoint",
1931
2130
  { redirect: "error" }
1932
2131
  );
@@ -1934,35 +2133,7 @@ var SUPPORTED_ADAPTERS = [
1934
2133
  return normalizeVercelAIGatewayCreditsPayload(payload, Date.now());
1935
2134
  }
1936
2135
  },
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
- },
2136
+ createFireworksAdapter(fetchProviderJson),
1966
2137
  {
1967
2138
  id: "opencode-go",
1968
2139
  displayName: "OpenCode Go",
@@ -2063,7 +2234,7 @@ var XAI_ADAPTER = {
2063
2234
  XAI_USER_URL,
2064
2235
  clientAuth,
2065
2236
  signal,
2066
- remainingTimeout(timeoutMs, startedAt),
2237
+ remainingTimeout2(timeoutMs, startedAt),
2067
2238
  "xAI consumer identity endpoint",
2068
2239
  { redirect: "error", userAgent: false }
2069
2240
  );
@@ -2079,7 +2250,7 @@ var XAI_ADAPTER = {
2079
2250
  XAI_BILLING_URL,
2080
2251
  billingAuth,
2081
2252
  signal,
2082
- remainingTimeout(timeoutMs, startedAt),
2253
+ remainingTimeout2(timeoutMs, startedAt),
2083
2254
  "xAI consumer billing endpoint",
2084
2255
  { redirect: "error", userAgent: false }
2085
2256
  );
@@ -2107,6 +2278,12 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
2107
2278
  );
2108
2279
  if (!model) return void 0;
2109
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
+ }
2110
2287
  let modelAuth;
2111
2288
  const currentModel = ctx.model?.provider === adapter.id ? ctx.model : void 0;
2112
2289
  const resolveCurrentModelAuth = async () => {
@@ -2129,25 +2306,64 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
2129
2306
  );
2130
2307
  }
2131
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
+ }
2132
2314
  const auth = modelAuth ?? providerResult?.auth;
2133
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
+ };
2134
2348
  if (adapter.id === "github-copilot") {
2135
2349
  const offered = candidateReader ? candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
2136
2350
  if (!offered.ok) {
2137
2351
  throw new Error("GitHub Copilot OAuth credential discovery failed closed.");
2138
2352
  }
2139
- return resolveGitHubCopilotUsageAuth(
2140
- auth,
2141
- model,
2142
- salt,
2143
- offered.candidates,
2144
- offered.offeredCount === 0
2353
+ return finalize(
2354
+ resolveGitHubCopilotUsageAuth(
2355
+ auth,
2356
+ model,
2357
+ salt,
2358
+ offered.candidates,
2359
+ offered.offeredCount === 0
2360
+ )
2145
2361
  );
2146
2362
  }
2147
2363
  if (adapter.id === "xai") {
2148
2364
  const offered = candidateReader ? candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
2149
2365
  if (!offered.ok) throw new Error("xAI OAuth credential discovery failed closed.");
2150
- return resolveXaiUsageAuth(auth, model, salt, offered.candidates);
2366
+ return finalize(resolveXaiUsageAuth(auth, model, salt, offered.candidates));
2151
2367
  }
2152
2368
  if (adapter.id === "deepseek") {
2153
2369
  const resolvedAuthorization = authorizationFrom(auth);
@@ -2155,10 +2371,10 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
2155
2371
  if (!access) throw new Error("DeepSeek API balance requires Bearer authentication.");
2156
2372
  const authorization2 = `Bearer ${access}`;
2157
2373
  const headers2 = { Authorization: authorization2 };
2158
- return {
2374
+ return finalize({
2159
2375
  apiKey: access,
2160
2376
  headers: headers2,
2161
- fingerprint: fingerprintResolvedAuth({ headers: headers2 }, salt),
2377
+ fingerprint: "",
2162
2378
  secrets: [
2163
2379
  access,
2164
2380
  auth.apiKey,
@@ -2167,7 +2383,7 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
2167
2383
  authorization2
2168
2384
  ].filter((value) => Boolean(value)),
2169
2385
  model
2170
- };
2386
+ });
2171
2387
  }
2172
2388
  const authorization = authorizationFrom(auth);
2173
2389
  if (!authorization) return void 0;
@@ -2175,17 +2391,41 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
2175
2391
  const secrets = [auth.apiKey, headerValue(auth.headers, "Authorization"), authorization].filter(
2176
2392
  (value) => Boolean(value)
2177
2393
  );
2178
- return {
2394
+ return finalize({
2179
2395
  apiKey: auth.apiKey,
2180
2396
  headers,
2181
- fingerprint: fingerprintResolvedAuth({ headers }, salt),
2397
+ fingerprint: "",
2182
2398
  secrets,
2183
2399
  model
2184
- };
2400
+ });
2185
2401
  }
2186
- 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;
2187
2406
  try {
2188
- 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
+ );
2189
2429
  } catch (error) {
2190
2430
  if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
2191
2431
  throw new Error(redactUsageError(errorMessage(error), auth.secrets));
@@ -2484,7 +2724,7 @@ function hasOfficialUrlOrigin(value, providerId) {
2484
2724
  }
2485
2725
  if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
2486
2726
  if (providerId === "deepseek") return url.origin === "https://api.deepseek.com";
2487
- if (providerId === "fireworks") return url.origin === FIREWORKS_BILLING_SUMMARY_ORIGIN;
2727
+ if (providerId === "fireworks") return url.origin === "https://api.fireworks.ai";
2488
2728
  if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
2489
2729
  if (providerId === "vercel-ai-gateway") return url.origin === "https://ai-gateway.vercel.sh";
2490
2730
  if (providerId === "opencode-go") return url.origin === "https://opencode.ai";
@@ -2540,7 +2780,7 @@ async function queryMiniMaxUsage(providerId, auth, signal, timeoutMs, guard) {
2540
2780
  `${MINIMAX_API_ROOTS[providerId]}${path}`,
2541
2781
  auth,
2542
2782
  signal,
2543
- remainingTimeout(timeoutMs, startedAt, "fetching MiniMax usage"),
2783
+ remainingTimeout2(timeoutMs, startedAt, "fetching MiniMax usage"),
2544
2784
  "MiniMax usage endpoint",
2545
2785
  { redirect: "error" }
2546
2786
  );
@@ -2555,94 +2795,18 @@ async function queryMoonshotBalance(providerId, auth, signal, timeoutMs, guard)
2555
2795
  MOONSHOT_BALANCE_URLS[providerId],
2556
2796
  auth,
2557
2797
  signal,
2558
- remainingTimeout(timeoutMs, startedAt, "fetching Moonshot AI balance"),
2798
+ remainingTimeout2(timeoutMs, startedAt, "fetching Moonshot AI balance"),
2559
2799
  "Moonshot AI balance endpoint",
2560
2800
  { redirect: "error" }
2561
2801
  );
2562
2802
  await guard();
2563
2803
  return normalizeMoonshotBalancePayload(providerId, payload, Date.now());
2564
2804
  }
2565
- function remainingTimeout(timeoutMs, startedAt, description = "fetching xAI consumer usage") {
2805
+ function remainingTimeout2(timeoutMs, startedAt, description = "fetching xAI consumer usage") {
2566
2806
  const remaining = timeoutMs - (Date.now() - startedAt);
2567
2807
  if (remaining <= 0) throw new Error(`Timed out while ${description}.`);
2568
2808
  return remaining;
2569
2809
  }
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
2810
  function zaiOrigin(baseUrl) {
2647
2811
  const base = baseUrl?.trim();
2648
2812
  if (!base) throw new Error("Z.AI model base URL is unavailable.");
@@ -2665,7 +2829,7 @@ async function queryZaiUsage(providerId, providerName, auth, signal, timeoutMs,
2665
2829
  zaiMonitorUrl(auth.model.baseUrl),
2666
2830
  zaiMonitorAuth(auth),
2667
2831
  signal,
2668
- remainingTimeout(timeoutMs, startedAt, `fetching ${providerName} quota`),
2832
+ remainingTimeout2(timeoutMs, startedAt, `fetching ${providerName} quota`),
2669
2833
  `${providerName} quota endpoint`
2670
2834
  );
2671
2835
  await guard();
@@ -3016,6 +3180,10 @@ function formatProviderStates(states) {
3016
3180
  return states.map((state) => {
3017
3181
  if (state.status === "ready") return formatUsageReport(state.report, state.displayState);
3018
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
+ }
3019
3187
  const status = state.status === "auth-unavailable" ? "Authentication unavailable" : state.status === "unsupported" ? "Unsupported" : "Query failed";
3020
3188
  return `${state.providerName} \xB7 ${label}
3021
3189
  ${status}: ${state.message}`;
@@ -3552,7 +3720,8 @@ var USAGE_SETTINGS_FILE = "pi-usage.json";
3552
3720
  var MAX_USAGE_SETTINGS_BYTES = 64 * 1024;
3553
3721
  var DEFAULT_USAGE_SETTINGS = Object.freeze({
3554
3722
  codexFastMode: false,
3555
- codexStatusResetCountdown: true
3723
+ codexStatusResetCountdown: true,
3724
+ selectedTargets: Object.freeze({})
3556
3725
  });
3557
3726
  function usageSettingsPath() {
3558
3727
  return join(getAgentDir(), USAGE_SETTINGS_FILE);
@@ -3568,10 +3737,16 @@ function normalizeUsageSettings(value) {
3568
3737
  if (Object.hasOwn(value, "fireworksAccountId") && !isFireworksAccountId(value.fireworksAccountId)) {
3569
3738
  return void 0;
3570
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
+ }
3571
3746
  return {
3572
3747
  codexFastMode: typeof value.codexFastMode === "boolean" ? value.codexFastMode : DEFAULT_USAGE_SETTINGS.codexFastMode,
3573
3748
  codexStatusResetCountdown: typeof value.codexStatusResetCountdown === "boolean" ? value.codexStatusResetCountdown : DEFAULT_USAGE_SETTINGS.codexStatusResetCountdown,
3574
- ...isFireworksAccountId(value.fireworksAccountId) ? { fireworksAccountId: value.fireworksAccountId } : {}
3749
+ selectedTargets: effectiveTargets
3575
3750
  };
3576
3751
  }
3577
3752
  async function loadUsageSettings(path = usageSettingsPath(), signal) {
@@ -3647,19 +3822,84 @@ function createUsageSettingsRuntime(options = {}) {
3647
3822
  state = saved;
3648
3823
  return structuredClone(state);
3649
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
+ }),
3650
3857
  flush: () => queue
3651
3858
  };
3652
3859
  }
3653
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) {
3654
3894
  const latest = await loadUsageSettings(path, signal);
3655
3895
  if (latest.kind === "invalid") {
3656
3896
  throw new Error("Cannot overwrite an invalid pi-usage.json; repair it and reload first");
3657
3897
  }
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;
3898
+ if (expected && !sameUsageSettingsDocument(latest, expected)) {
3899
+ throw new Error("pi-usage.json changed while saving; retry the action");
3662
3900
  }
3901
+ const document = { ...latest.document };
3902
+ mutate(document);
3663
3903
  const settings = normalizeUsageSettings(document);
3664
3904
  if (!settings) throw new Error("Refusing to save invalid pi-usage settings");
3665
3905
  const directory = dirname(path);
@@ -3686,6 +3926,32 @@ async function saveUsageSettingsPatch(path, patch, operations, signal) {
3686
3926
  }
3687
3927
  return { kind: "loaded", path, settings, document };
3688
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
+ }
3689
3955
  async function chmodPrivate(path) {
3690
3956
  await chmod(path, 384);
3691
3957
  }
@@ -3698,6 +3964,19 @@ function isRecord3(value) {
3698
3964
  function isNodeError(error) {
3699
3965
  return error instanceof Error && "code" in error;
3700
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
+ }
3701
3980
 
3702
3981
  // src/usage.ts
3703
3982
  import { randomUUID as randomUUID2 } from "node:crypto";
@@ -3908,8 +4187,6 @@ import {
3908
4187
  SettingsList,
3909
4188
  Text
3910
4189
  } from "@earendil-works/pi-tui";
3911
- var AUTO = "Auto";
3912
- var EDIT = "Edit\u2026";
3913
4190
  var OFF = "Off";
3914
4191
  var ON = "On";
3915
4192
  async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent, onApplied) {
@@ -3917,31 +4194,13 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
3917
4194
  if (ctx.hasUI) ctx.ui.notify(`Edit settings manually: ${settingsRuntime.get().path}`, "info");
3918
4195
  return false;
3919
4196
  }
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) => {
4197
+ return await ctx.ui.custom((tui, theme, _keybindings, done) => {
3938
4198
  const localController = new AbortController();
3939
4199
  const signal = AbortSignal.any([parentSignal, localController.signal]);
3940
4200
  let changed = false;
3941
4201
  let closing = false;
3942
4202
  let saveQueue = Promise.resolve();
3943
4203
  const state = settingsRuntime.get();
3944
- const fireworksValue = state.settings.fireworksAccountId ?? AUTO;
3945
4204
  const items = [
3946
4205
  {
3947
4206
  id: "codexFastMode",
@@ -3956,13 +4215,6 @@ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, o
3956
4215
  description: "Show time remaining until each Codex usage limit resets.",
3957
4216
  currentValue: state.settings.codexStatusResetCountdown ? ON : OFF,
3958
4217
  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
4218
  }
3967
4219
  ];
3968
4220
  const container = new Container();
@@ -3972,13 +4224,13 @@ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, o
3972
4224
  if (closing) return;
3973
4225
  closing = true;
3974
4226
  localController.abort();
3975
- done({ changed, editFireworksAccount: false });
4227
+ done(changed);
3976
4228
  };
3977
4229
  const queueUpdate = (id, requested, display) => {
3978
4230
  saveQueue = saveQueue.then(async () => {
3979
4231
  const previous = settingsRuntime.get().settings[id];
3980
4232
  if (settingsRuntime.get().kind === "invalid") {
3981
- settingsList.updateValue(id, displaySetting(id, previous));
4233
+ settingsList.updateValue(id, previous ? ON : OFF);
3982
4234
  if (!signal.aborted && isCurrent()) {
3983
4235
  ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
3984
4236
  tui.requestRender();
@@ -3989,7 +4241,7 @@ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, o
3989
4241
  await settingsRuntime.update({ [id]: requested }, signal);
3990
4242
  } catch (error) {
3991
4243
  if (signal.aborted || !isCurrent()) return;
3992
- settingsList.updateValue(id, displaySetting(id, previous));
4244
+ settingsList.updateValue(id, previous ? ON : OFF);
3993
4245
  ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
3994
4246
  tui.requestRender();
3995
4247
  return;
@@ -4009,18 +4261,7 @@ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, o
4009
4261
  getSettingsListTheme(),
4010
4262
  (id, value) => {
4011
4263
  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);
4264
+ queueUpdate(id, value !== OFF, value);
4024
4265
  },
4025
4266
  cancel
4026
4267
  );
@@ -4040,43 +4281,7 @@ async function showSettingsList(ctx, settingsRuntime, parentSignal, isCurrent, o
4040
4281
  parentSignal.removeEventListener("abort", cancel);
4041
4282
  }
4042
4283
  };
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;
4284
+ }) ?? false;
4080
4285
  }
4081
4286
 
4082
4287
  // src/usage.ts
@@ -4093,6 +4298,9 @@ var VIEW_ALL = "View all configured providers\u2026";
4093
4298
  var CLOSE = "Close";
4094
4299
  var SETTINGS = "Settings";
4095
4300
  var REDEEM_CODEX_RESET = "Redeem usage limit reset\u2026";
4301
+ var UsageTargetSelectionChangedError = class extends Error {
4302
+ name = "UsageTargetSelectionChangedError";
4303
+ };
4096
4304
  function usageExtension(pi, dependencies = {}) {
4097
4305
  const credentialReader = dependencies.credentialReader;
4098
4306
  const credentialCandidates = createOAuthCredentialCandidateReader(pi, credentialReader);
@@ -4164,7 +4372,7 @@ function usageExtension(pi, dependencies = {}) {
4164
4372
  if (outcome.state.status !== "ready") {
4165
4373
  if (safeSetStatus(
4166
4374
  ctx,
4167
- 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"
4168
4376
  )) {
4169
4377
  if (shouldSchedule && sessionActive) scheduleStatusRefresh(ctx, model);
4170
4378
  }
@@ -4217,8 +4425,8 @@ function usageExtension(pi, dependencies = {}) {
4217
4425
  const expectedSessionGeneration = sessionGeneration;
4218
4426
  const expectedSessionId = ctx.sessionManager.getSessionId();
4219
4427
  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;
4428
+ const expectedTargetId = adapter.targets ? settingsRuntime.get().settings.selectedTargets[adapter.id] : void 0;
4429
+ const providerName = providerDisplayName(ctx, adapter.id);
4222
4430
  let auth;
4223
4431
  try {
4224
4432
  auth = await awaitWithDeadline(
@@ -4235,17 +4443,16 @@ function usageExtension(pi, dependencies = {}) {
4235
4443
  return {
4236
4444
  state: {
4237
4445
  providerId: adapter.id,
4238
- providerName: adapter.displayName,
4446
+ providerName,
4239
4447
  displayState,
4240
4448
  status: isTimeoutError(error) ? "query-failed" : "auth-unavailable",
4241
4449
  message: errorMessage(error)
4242
4450
  }
4243
4451
  };
4244
4452
  }
4245
- const requiresRequestBoundaryGuard = [
4453
+ const requiresRequestBoundaryGuard = adapter.targets !== void 0 || [
4246
4454
  "baseten",
4247
4455
  "deepseek",
4248
- "fireworks",
4249
4456
  "minimax",
4250
4457
  "minimax-cn",
4251
4458
  "moonshotai",
@@ -4255,7 +4462,7 @@ function usageExtension(pi, dependencies = {}) {
4255
4462
  "zai",
4256
4463
  "zai-coding-cn"
4257
4464
  ].includes(adapter.id);
4258
- 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;
4259
4466
  if (requiresRequestBoundaryGuard && requestContextChanged()) throw abortError();
4260
4467
  if (!auth) {
4261
4468
  if (displayState === "current") {
@@ -4264,98 +4471,145 @@ function usageExtension(pi, dependencies = {}) {
4264
4471
  return {
4265
4472
  state: {
4266
4473
  providerId: adapter.id,
4267
- providerName: adapter.displayName,
4474
+ providerName,
4268
4475
  displayState,
4269
4476
  status: "auth-unavailable",
4270
- message: `No runtime credential is configured for ${adapter.displayName}.`
4477
+ message: `No runtime credential is configured for ${providerName}.`
4271
4478
  },
4272
4479
  authState: "unavailable"
4273
4480
  };
4274
4481
  }
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
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;
4311
4504
  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();
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);
4330
4530
  }
4331
- } : 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);
4332
4582
  const report2 = await queryProviderUsage(
4333
4583
  adapter,
4334
4584
  auth,
4335
4585
  signal,
4336
- remainingMs,
4337
- guard,
4338
- querySettings
4586
+ Math.max(1, deadlineAt - Date.now()),
4587
+ requiresRequestBoundaryGuard ? guard : void 0,
4588
+ target.targetId
4339
4589
  );
4340
- if (guard) await guard();
4590
+ if (requiresRequestBoundaryGuard) await guard();
4591
+ const effectiveReport = { ...report2, providerName };
4341
4592
  if (latestQueries.get(failureKey) === queryId) {
4342
- cache.set(adapter.id, queryFingerprint, report2);
4593
+ cache.set(adapter.id, queryFingerprint, effectiveReport);
4343
4594
  failureBackoff.delete(failureKey);
4344
4595
  }
4345
4596
  return {
4346
4597
  state: {
4347
4598
  providerId: adapter.id,
4348
- providerName: adapter.displayName,
4599
+ providerName,
4349
4600
  displayState,
4350
4601
  status: "ready",
4351
- report: report2
4602
+ report: effectiveReport
4352
4603
  },
4353
- fingerprint: auth.fingerprint
4604
+ fingerprint: auth.fingerprint,
4605
+ rememberedTargetId: expectedTargetId
4354
4606
  };
4355
4607
  } catch (error) {
4356
4608
  if (isStaleExtensionContextError(error) || isAbortError3(error)) throw error;
4357
4609
  if (retryableAuthChanged && authRetry === 0 && !signal.aborted && !requestContextChanged() && Date.now() < deadlineAt) {
4358
- if (latestQueries.get(failureKey) === queryId) latestQueries.delete(failureKey);
4610
+ if (queryId !== void 0 && latestQueries.get(failureKey) === queryId) {
4611
+ latestQueries.delete(failureKey);
4612
+ }
4359
4613
  return queryAdapterState(
4360
4614
  ctx,
4361
4615
  adapter,
@@ -4371,7 +4625,7 @@ function usageExtension(pi, dependencies = {}) {
4371
4625
  for (const [key, failure] of failureBackoff) {
4372
4626
  if (failure.until <= now) failureBackoff.delete(key);
4373
4627
  }
4374
- if (latestQueries.get(failureKey) === queryId) {
4628
+ if (queryId === void 0 || latestQueries.get(failureKey) === queryId) {
4375
4629
  setBoundedMap(
4376
4630
  failureBackoff,
4377
4631
  failureKey,
@@ -4382,15 +4636,52 @@ function usageExtension(pi, dependencies = {}) {
4382
4636
  return {
4383
4637
  state: {
4384
4638
  providerId: adapter.id,
4385
- providerName: adapter.displayName,
4639
+ providerName,
4386
4640
  displayState,
4387
4641
  status: "query-failed",
4388
4642
  message
4389
4643
  },
4390
- fingerprint: auth.fingerprint
4644
+ fingerprint: auth.fingerprint,
4645
+ rememberedTargetId: expectedTargetId
4391
4646
  };
4392
4647
  }
4393
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
+ };
4394
4685
  const queryCurrentState = async (ctx, model, force, signal) => {
4395
4686
  const adapter = adapterForProvider(model?.provider);
4396
4687
  if (!adapter) {
@@ -4474,6 +4765,8 @@ function usageExtension(pi, dependencies = {}) {
4474
4765
  return false;
4475
4766
  }
4476
4767
  const adapter = adapterForProvider(model?.provider);
4768
+ const selectionStillCurrent = !adapter?.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId;
4769
+ if (!selectionStillCurrent) return false;
4477
4770
  if (outcome.authState === "unavailable") {
4478
4771
  if (!adapter) return false;
4479
4772
  try {
@@ -4483,7 +4776,7 @@ function usageExtension(pi, dependencies = {}) {
4483
4776
  DEFAULT_TIMEOUT_MS,
4484
4777
  `revalidating ${adapter.displayName} runtime auth`
4485
4778
  );
4486
- 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;
4487
4780
  } catch (error) {
4488
4781
  if (isAbortError3(error) || isStaleExtensionContextError(error)) throw error;
4489
4782
  return false;
@@ -4498,7 +4791,7 @@ function usageExtension(pi, dependencies = {}) {
4498
4791
  DEFAULT_TIMEOUT_MS,
4499
4792
  `revalidating ${adapter.displayName} runtime auth`
4500
4793
  );
4501
- 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;
4502
4795
  } catch (error) {
4503
4796
  if (isAbortError3(error) || isStaleExtensionContextError(error)) throw error;
4504
4797
  return false;
@@ -4554,6 +4847,88 @@ function usageExtension(pi, dependencies = {}) {
4554
4847
  let redemptionId;
4555
4848
  let resetOutcome;
4556
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
+ };
4557
4932
  const { defineMenu, runMenu } = await import("@narumitw/pi-tui-kit");
4558
4933
  if (controller.signal.aborted || statusGeneration !== menuGeneration) return;
4559
4934
  const menu = defineMenu({
@@ -4561,6 +4936,8 @@ function usageExtension(pi, dependencies = {}) {
4561
4936
  screens: {
4562
4937
  main: () => {
4563
4938
  const fastAvailability = fastRuntime.availability(ctx.model);
4939
+ const targetState = actionableTargetState();
4940
+ const targetAdapter = adapterForProvider(targetState?.providerId);
4564
4941
  const fastLines = fastAvailability.kind === "available" ? [`Fast mode: ${fastAvailability.enabled ? "On" : "Off"}`, FAST_USAGE_WARNING] : fastAvailability.kind === "unavailable" ? [`Fast mode: Unavailable \xB7 ${fastAvailability.reason}`] : [];
4565
4942
  return {
4566
4943
  kind: "actions",
@@ -4569,6 +4946,13 @@ function usageExtension(pi, dependencies = {}) {
4569
4946
  items: [
4570
4947
  { id: "refresh", label: REFRESH_CURRENT, action: "refresh" },
4571
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
+ ] : [],
4572
4956
  ...fastAvailability.kind === "available" ? [
4573
4957
  {
4574
4958
  id: "toggle-fast",
@@ -4599,7 +4983,7 @@ function usageExtension(pi, dependencies = {}) {
4599
4983
  title: "Select a configured provider",
4600
4984
  items: configuredAdapters(ctx).filter((adapter) => adapter.id !== ctx.model?.provider).map((adapter) => ({
4601
4985
  id: adapter.id,
4602
- label: adapter.displayName,
4986
+ label: providerDisplayName(ctx, adapter.id),
4603
4987
  action: "provider"
4604
4988
  })),
4605
4989
  hint: "back"
@@ -4661,6 +5045,58 @@ function usageExtension(pi, dependencies = {}) {
4661
5045
  })
4662
5046
  },
4663
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
+ },
4664
5100
  settings: async () => {
4665
5101
  await showUsageSettings(
4666
5102
  ctx,
@@ -4869,13 +5305,25 @@ function usageExtension(pi, dependencies = {}) {
4869
5305
  (candidate) => candidate.id === itemId && candidate.id !== ctx.model?.provider
4870
5306
  );
4871
5307
  if (!adapter) return { kind: "back" };
4872
- const outcome = await runMenuOperation(
5308
+ let outcome = await runMenuOperation(
4873
5309
  ctx,
4874
- `Checking ${adapter.displayName} usage\u2026`,
5310
+ `Checking ${providerDisplayName(ctx, adapter.id)} usage\u2026`,
4875
5311
  controller.signal,
4876
5312
  (signal) => queryAdapterState(ctx, adapter, "configured", false, signal)
4877
5313
  );
4878
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
+ }
4879
5327
  const revalidated = await queryStableCurrent(
4880
5328
  ctx,
4881
5329
  false,
@@ -4918,7 +5366,7 @@ function usageExtension(pi, dependencies = {}) {
4918
5366
  const adapter = adapters[index];
4919
5367
  return {
4920
5368
  providerId: adapter.id,
4921
- providerName: adapter.displayName,
5369
+ providerName: providerDisplayName(ctx, adapter.id),
4922
5370
  displayState: "configured",
4923
5371
  status: "query-failed",
4924
5372
  message: errorMessage(result.reason)
@@ -5032,15 +5480,19 @@ export {
5032
5480
  codexFastStatusLabel,
5033
5481
  consumeCodexResetCredit,
5034
5482
  correctCodexFastMessageCost,
5483
+ createFireworksAdapter,
5035
5484
  createUsageSettingsRuntime,
5485
+ createUsageTargetSelectOptions,
5036
5486
  usageExtension as default,
5037
5487
  errorMessage,
5038
5488
  fingerprintResolvedAuth,
5039
5489
  formatProviderStates,
5040
5490
  formatUsageReport,
5041
5491
  formatUsageStatusline,
5492
+ isBoundedTargetId,
5042
5493
  isStaleExtensionContextError,
5043
5494
  listCodexResetCredits,
5495
+ listUsageTargets,
5044
5496
  loadUsageSettings,
5045
5497
  miniMaxUsageKind,
5046
5498
  normalizeBasetenBillingUsagePayload,
@@ -5056,6 +5508,7 @@ export {
5056
5508
  normalizeOpenCodeZenPayload,
5057
5509
  normalizeOpenRouterKeyPayload,
5058
5510
  normalizeUsageSettings,
5511
+ normalizeUsageTargets,
5059
5512
  normalizeVercelAIGatewayCreditsPayload,
5060
5513
  normalizeXaiBillingPayload,
5061
5514
  normalizeZaiQuotaPayload,
@@ -5065,6 +5518,7 @@ export {
5065
5518
  redactUsageError,
5066
5519
  resolveCodexResetAuth,
5067
5520
  resolveUsageAuth,
5521
+ resolveUsageTarget,
5068
5522
  rewriteCodexFastPayload,
5069
5523
  runWithConcurrency,
5070
5524
  sanitizeDisplayText,