@narumitw/pi-usage 0.64.3 → 0.65.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
@@ -47,23 +47,17 @@ import {
47
47
  runWithConcurrency,
48
48
  sanitizeDisplayText,
49
49
  usageAdapters
50
- } from "./chunks/chunk-ACPQJ2CM.ts";
50
+ } from "./chunks/chunk-N2ZD5X4K.ts";
51
51
 
52
52
  // src/codex-fast.ts
53
53
  import { calculateCost, hasApi } from "@earendil-works/pi-ai";
54
54
  var CODEX_FAST_SERVICE_TIER = "priority";
55
55
  var CODEX_STANDARD_SERVICE_TIER = "default";
56
- var CODEX_FAST_MODEL_IDS = /* @__PURE__ */ new Set([
57
- "gpt-5.5",
58
- "gpt-5.6-luna",
59
- "gpt-5.6-sol",
60
- "gpt-5.6-terra",
61
- "gpt-6-astra",
62
- "gpt-6-luna",
63
- "gpt-6-sol",
64
- "gpt-6.1-sol"
65
- ]);
66
- function codexFastAvailability(model, enabled) {
56
+ var UNKNOWN_CODEX_FAST_CAPABILITY = {
57
+ kind: "unknown",
58
+ reason: "Codex Fast capability information has not been fetched."
59
+ };
60
+ function codexFastAvailability(model, enabled, capability = UNKNOWN_CODEX_FAST_CAPABILITY) {
67
61
  if (model?.provider !== "openai-codex") return { kind: "not-codex" };
68
62
  if (!isOfficialCodexModel(model)) {
69
63
  return {
@@ -71,7 +65,8 @@ function codexFastAvailability(model, enabled) {
71
65
  reason: "Fast mode requires the official OpenAI Codex Responses endpoint."
72
66
  };
73
67
  }
74
- if (!CODEX_FAST_MODEL_IDS.has(model.id)) {
68
+ if (capability.kind === "unknown") return capability;
69
+ if (capability.kind === "unsupported") {
75
70
  return {
76
71
  kind: "unavailable",
77
72
  reason: `${model.id} does not advertise Codex Fast support.`
@@ -79,20 +74,20 @@ function codexFastAvailability(model, enabled) {
79
74
  }
80
75
  return { kind: "available", enabled };
81
76
  }
82
- function codexFastIsEffective(model, enabled) {
83
- return codexFastAvailability(model, enabled).kind === "available" && enabled;
77
+ function codexFastIsEffective(model, enabled, capability = UNKNOWN_CODEX_FAST_CAPABILITY) {
78
+ return codexFastAvailability(model, enabled, capability).kind === "available" && enabled;
84
79
  }
85
- function codexFastRequestTier(model, enabled) {
80
+ function codexFastRequestTier(model, enabled, capability = UNKNOWN_CODEX_FAST_CAPABILITY) {
86
81
  if (!isOfficialCodexModel(model)) return void 0;
87
- return enabled && CODEX_FAST_MODEL_IDS.has(model.id) ? CODEX_FAST_SERVICE_TIER : CODEX_STANDARD_SERVICE_TIER;
82
+ return enabled && capability.kind === "supported" ? CODEX_FAST_SERVICE_TIER : CODEX_STANDARD_SERVICE_TIER;
88
83
  }
89
- function rewriteCodexFastPayload(payload, model, enabled) {
90
- const serviceTier = codexFastRequestTier(model, enabled);
84
+ function rewriteCodexFastPayload(payload, model, enabled, capability = UNKNOWN_CODEX_FAST_CAPABILITY) {
85
+ const serviceTier = codexFastRequestTier(model, enabled, capability);
91
86
  if (!serviceTier || !isRecord(payload)) return void 0;
92
87
  return { ...payload, service_tier: serviceTier };
93
88
  }
94
- function correctCodexFastMessageCost(message, model, fastRequested) {
95
- if (!codexFastIsEffective(model, fastRequested) || !isRecord(message) || message.role !== "assistant" || message.provider !== model?.provider || message.model !== model?.id) {
89
+ function correctCodexFastMessageCost(message, model, fastRequested, capability = UNKNOWN_CODEX_FAST_CAPABILITY) {
90
+ if (!codexFastIsEffective(model, fastRequested, capability) || !isRecord(message) || message.role !== "assistant" || message.provider !== model?.provider || message.model !== model?.id) {
96
91
  return void 0;
97
92
  }
98
93
  const usage = isRecord(message.usage) ? message.usage : void 0;
@@ -426,6 +421,14 @@ function formatUsageReport(report, displayState) {
426
421
  return lines.join("\n").trimEnd();
427
422
  }
428
423
  function formatUsageStatusline(report, model, now = Date.now(), showCodexResetCountdown = true, codexStatusPercentage = "remaining") {
424
+ return formatUsageStatuslineWithCountdown(report, model, now, showCodexResetCountdown, codexStatusPercentage).value;
425
+ }
426
+ function formatUsageStatuslineWithCountdown(report, model, now = Date.now(), showCodexResetCountdown = true, codexStatusPercentage = "remaining") {
427
+ const countdown = { now, active: false };
428
+ const value = formatStatusline(report, model, now, showCodexResetCountdown, codexStatusPercentage, countdown);
429
+ return { value, hasCountdown: countdown.active };
430
+ }
431
+ function formatStatusline(report, model, now, showCodexResetCountdown, codexStatusPercentage, countdown) {
429
432
  if (report.providerId === "openai" && report.source === "openai-chatgpt-auth") return "chatgpt usage: web only";
430
433
  if (report.source === "openai-chatgpt-companion") {
431
434
  const windows = report.buckets.filter((bucket) => bucket.groupId === "chatgpt-plan").map((bucket) => {
@@ -442,24 +445,24 @@ function formatUsageStatusline(report, model, now = Date.now(), showCodexResetCo
442
445
  if (report.providerId === "deepseek") return formatDeepSeekStatusline(report);
443
446
  if (report.providerId === "fireworks") return formatFireworksStatusline(report);
444
447
  if (report.providerId === "vercel-ai-gateway") return formatVercelAIGatewayStatusline(report);
445
- if (report.providerId === "github-copilot") return formatGitHubCopilotStatusline(report);
448
+ if (report.providerId === "github-copilot") return formatGitHubCopilotStatusline(report, countdown);
446
449
  if (report.providerId === "openrouter") {
447
450
  const limit = report.buckets.find((bucket) => bucket.id === "key-limit");
448
451
  if (limit?.remaining !== void 0) return `openrouter ${formatUsd(limit.remaining)} left`;
449
452
  const total = report.metrics.find((metric) => metric.id === "usage-total");
450
453
  if (typeof total?.value === "number") return `openrouter ${formatUsd(total.value)} used`;
451
454
  }
452
- if (report.providerId === "opencode-go") return formatOpenCodeZenStatusline(report);
453
- if (report.providerId === "command-code") return formatCommandCodeStatusline(report);
454
- if (report.providerId === "kimi-coding") return formatKimiCodingStatusline(report);
455
+ if (report.providerId === "opencode-go") return formatOpenCodeZenStatusline(report, countdown);
456
+ if (report.providerId === "command-code") return formatCommandCodeStatusline(report, countdown);
457
+ if (report.providerId === "kimi-coding") return formatKimiCodingStatusline(report, countdown);
455
458
  if (report.providerId === "moonshotai" || report.providerId === "moonshotai-cn") {
456
459
  return formatMoonshotStatusline(report);
457
460
  }
458
461
  if (report.providerId === "minimax" || report.providerId === "minimax-cn") {
459
- return formatMiniMaxStatusline(report, model);
462
+ return formatMiniMaxStatusline(report, countdown, model);
460
463
  }
461
464
  if (report.providerId === "zai" || report.providerId === "zai-coding-cn") {
462
- return formatZaiStatusline(report);
465
+ return formatZaiStatusline(report, countdown);
463
466
  }
464
467
  return void 0;
465
468
  }
@@ -577,7 +580,7 @@ function formatGitHubCopilotReport(lines, report) {
577
580
  lines.push(`${"Additional usage:".padEnd(VALUE_COLUMN)}${overage.value} ${quota.label}`);
578
581
  }
579
582
  }
580
- function formatGitHubCopilotStatusline(report) {
583
+ function formatGitHubCopilotStatusline(report, countdown) {
581
584
  const quota = findGitHubCopilotQuota(report);
582
585
  const kind = compactGitHubCopilotQuotaKind(quota);
583
586
  if (!quota || quota.limit === void 0 || quota.remaining === void 0) {
@@ -585,7 +588,8 @@ function formatGitHubCopilotStatusline(report) {
585
588
  }
586
589
  const overage = report.metrics.find((metric) => metric.id === "overage-used");
587
590
  const overageSuffix = typeof overage?.value === "number" && overage.value > 0 ? ` +${overage.value} over` : "";
588
- return `copilot ${kind === "premium" ? "" : `${kind} `}${quota.remaining}/${quota.limit} ${percentRemaining(quota)}%${overageSuffix}`;
591
+ const reset = formatQuotaCountdown(quota, countdown);
592
+ return `copilot ${kind === "premium" ? "" : `${kind} `}${quota.remaining}/${quota.limit} ${percentRemaining(quota)}%${overageSuffix}${reset ? ` \u21BB ${reset}` : ""}`;
589
593
  }
590
594
  function findGitHubCopilotQuota(report) {
591
595
  return report.buckets.find((bucket) => ["ai-credits", "premium-requests", "chat-requests"].includes(bucket.id));
@@ -621,14 +625,14 @@ function formatOpenCodeZenReport(lines, report) {
621
625
  lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${used}% used${reset}`);
622
626
  }
623
627
  }
624
- function formatOpenCodeZenStatusline(report) {
625
- const parts = ["zen"];
628
+ function formatOpenCodeZenStatusline(report, countdown) {
629
+ const parts = [];
626
630
  for (const bucket of report.buckets) {
627
631
  if (bucket.used === void 0) continue;
628
632
  const compact = bucket.id === "rolling" ? "r" : bucket.id === "weekly" ? "w" : "m";
629
- parts.push(`${clampPercent(bucket.used).toFixed(0)}% ${compact}`);
633
+ parts.push(formatQuotaWindow(bucket, `${clampPercent(bucket.used).toFixed(0)}%`, compact, countdown));
630
634
  }
631
- return parts.length > 1 ? parts.join(" ") : void 0;
635
+ return parts.length > 0 ? `zen ${parts.join(" \xB7 ")}` : void 0;
632
636
  }
633
637
  function formatCommandCodeReport(lines, report) {
634
638
  for (const bucket of report.buckets) {
@@ -645,8 +649,8 @@ function formatCommandCodeReport(lines, report) {
645
649
  lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(metric.value, metric.unit)}`);
646
650
  }
647
651
  }
648
- function formatCommandCodeStatusline(report) {
649
- const parts = ["cmd"];
652
+ function formatCommandCodeStatusline(report, countdown) {
653
+ const parts = [];
650
654
  for (const [id, label] of [
651
655
  ["five-hour", "5h"],
652
656
  ["weekly", "wk"],
@@ -654,9 +658,9 @@ function formatCommandCodeStatusline(report) {
654
658
  ]) {
655
659
  const bucket = report.buckets.find((candidate) => candidate.id === id);
656
660
  if (bucket?.unit !== "usd" || bucket.limit === void 0 || bucket.remaining === void 0) continue;
657
- parts.push(`${percentRemaining(bucket)}% ${label}`);
661
+ parts.push(formatQuotaWindow(bucket, `${percentRemaining(bucket)}%`, label, countdown));
658
662
  }
659
- return parts.length > 1 ? parts.join(" ") : void 0;
663
+ return parts.length > 0 ? `cmd ${parts.join(" \xB7 ")}` : void 0;
660
664
  }
661
665
  function formatKimiCodingReport(lines, report) {
662
666
  for (const bucket of report.buckets) {
@@ -692,7 +696,7 @@ function formatKimiCodingReport(lines, report) {
692
696
  lines.push(`${"Monthly limit:".padEnd(VALUE_COLUMN)}${formatCurrencyMetric(monthlyLimit)}`);
693
697
  }
694
698
  }
695
- function formatKimiCodingStatusline(report) {
699
+ function formatKimiCodingStatusline(report, countdown) {
696
700
  const fiveHour = report.buckets.find((bucket) => bucket.id === "five-hour");
697
701
  const weekly = report.buckets.find((bucket) => bucket.id === "weekly");
698
702
  const monthly = report.buckets.find((bucket) => bucket.id === "monthly");
@@ -700,15 +704,15 @@ function formatKimiCodingStatusline(report) {
700
704
  const selected = [subWindow, weekly, monthly].filter(
701
705
  (bucket, index, buckets) => bucket !== void 0 && buckets.indexOf(bucket) === index
702
706
  );
703
- const parts = ["kimi"];
707
+ const parts = [];
704
708
  for (const bucket of selected) {
705
709
  if (bucket.remaining === void 0 || bucket.unit !== "percent" && !bucket.limit) continue;
706
710
  const fallback = bucket.id === "weekly" ? "weekly" : "5h";
707
711
  const window = bucket.id === "monthly" ? "mo" : formatWindowLabel(bucket.windowMinutes, fallback, true);
708
712
  const remaining = bucket.unit === "percent" ? Math.round(clampPercent(bucket.remaining)) : percentRemaining(bucket);
709
- parts.push(`${remaining}% ${window}`);
713
+ parts.push(formatQuotaWindow(bucket, `${remaining}%`, window, countdown));
710
714
  }
711
- return parts.length > 1 ? parts.join(" ") : void 0;
715
+ return parts.length > 0 ? `kimi ${parts.join(" \xB7 ")}` : void 0;
712
716
  }
713
717
  function formatMoonshotReport(lines, report) {
714
718
  for (const metric of report.metrics) {
@@ -736,7 +740,7 @@ function formatMiniMaxReport(lines, report) {
736
740
  lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${value}`);
737
741
  }
738
742
  }
739
- function formatMiniMaxStatusline(report, model) {
743
+ function formatMiniMaxStatusline(report, countdown, model) {
740
744
  const prefix = report.providerId === "minimax-cn" ? "minimax cn" : "minimax";
741
745
  if (report.source === "minimax-account-balance") {
742
746
  const available = report.metrics.find((metric) => metric.id === "available-balance");
@@ -745,22 +749,22 @@ function formatMiniMaxStatusline(report, model) {
745
749
  const selectedGroup = selectMiniMaxGroup(report, model);
746
750
  if (!selectedGroup) return void 0;
747
751
  const selected = report.buckets.filter((bucket) => bucket.groupId === selectedGroup);
748
- const parts = [prefix];
752
+ const parts = [];
749
753
  for (const bucket of selected) {
750
754
  const fallback = bucket.id.endsWith(":weekly") ? "weekly" : "5h";
751
755
  const window = formatWindowLabel(bucket.windowMinutes, fallback, true);
752
756
  if (bucket.period === "unlimited") {
753
- parts.push(`unlimited ${window}`);
757
+ parts.push(`${window}:unlimited`);
754
758
  continue;
755
759
  }
756
760
  if (bucket.unit === "percent" && bucket.remaining !== void 0) {
757
- parts.push(`${bucket.remaining}% ${window}`);
761
+ parts.push(formatQuotaWindow(bucket, `${bucket.remaining}%`, window, countdown));
758
762
  continue;
759
763
  }
760
764
  if (bucket.limit === void 0 || bucket.remaining === void 0) continue;
761
- parts.push(`${percentRemaining(bucket)}% ${window}`);
765
+ parts.push(formatQuotaWindow(bucket, `${percentRemaining(bucket)}%`, window, countdown));
762
766
  }
763
- return parts.length > 1 ? parts.join(" ") : void 0;
767
+ return parts.length > 0 ? `${prefix} ${parts.join(" \xB7 ")}` : void 0;
764
768
  }
765
769
  function selectMiniMaxGroup(report, model) {
766
770
  const groups = [
@@ -802,18 +806,25 @@ function wildcardKeyMatches(pattern, value) {
802
806
  const last = segments.at(-1);
803
807
  return pattern.endsWith("*") || last !== void 0 && value.endsWith(last);
804
808
  }
805
- function formatZaiStatusline(report) {
809
+ function formatZaiStatusline(report, countdown) {
806
810
  const selected = [
807
811
  report.buckets.find((bucket) => bucket.id === "five-hour"),
808
812
  report.buckets.find((bucket) => bucket.id === "weekly")
809
813
  ];
810
- const parts = ["zai"];
814
+ const parts = [];
811
815
  for (const bucket of selected) {
812
816
  if (!bucket?.limit || bucket.remaining === void 0) continue;
813
817
  const fallback = bucket.id === "weekly" ? "weekly" : "5h";
814
- parts.push(`${percentRemaining(bucket)}% ${formatWindowLabel(bucket.windowMinutes, fallback, true)}`);
818
+ parts.push(
819
+ formatQuotaWindow(
820
+ bucket,
821
+ `${percentRemaining(bucket)}%`,
822
+ formatWindowLabel(bucket.windowMinutes, fallback, true),
823
+ countdown
824
+ )
825
+ );
815
826
  }
816
- return parts.length > 1 ? parts.join(" ") : void 0;
827
+ return parts.length > 0 ? `zai ${parts.join(" \xB7 ")}` : void 0;
817
828
  }
818
829
  function formatCurrencyMetric(metric) {
819
830
  if (typeof metric.value !== "number") return String(metric.value);
@@ -896,7 +907,8 @@ function formatCodexStatusline(report, model, now = Date.now(), showResetCountdo
896
907
  if (!group) return formatCodexCreditsStatus(report);
897
908
  const buckets = report.buckets.filter((bucket) => (bucket.groupId ?? bucket.id) === group);
898
909
  const labelBucket = buckets[0];
899
- const parts = [group === "codex" ? "codex" : `codex ${compactLimitLabel(labelBucket?.groupLabel ?? group)}`];
910
+ const prefix = group === "codex" ? "codex" : `codex ${compactLimitLabel(labelBucket?.groupLabel ?? group)}`;
911
+ const parts = [];
900
912
  for (const bucket of buckets) {
901
913
  if (bucket.remaining === void 0) continue;
902
914
  const displayedPercentage = percentage === "used" ? bucket.used ?? 100 - bucket.remaining : bucket.remaining;
@@ -904,13 +916,13 @@ function formatCodexStatusline(report, model, now = Date.now(), showResetCountdo
904
916
  const fallback = bucket.id.endsWith(":secondary") ? "weekly" : "5h";
905
917
  const window = formatWindowLabel(bucket.windowMinutes, fallback, true);
906
918
  if (!showResetCountdown) {
907
- parts.push(`${percent} ${window}`);
919
+ parts.push(`${window}:${percent}`);
908
920
  continue;
909
921
  }
910
922
  const reset = formatResetCountdown(bucket.resetsAt, now);
911
- parts.push(`${percent} ${reset ? `\u21BB ${reset}` : window}`);
923
+ parts.push(reset ? `${percent} \u21BB ${reset}` : `${window}:${percent}`);
912
924
  }
913
- return parts.length > 1 ? parts.join(" ") : formatCodexCreditsStatus(report);
925
+ return parts.length > 0 ? `${prefix} ${parts.join(" \xB7 ")}` : formatCodexCreditsStatus(report);
914
926
  }
915
927
  function formatCodexCreditsStatus(report) {
916
928
  const credits = report.metrics.find((metric) => metric.id === "credits");
@@ -989,6 +1001,17 @@ function formatWindowLabel(minutes, fallback, compact) {
989
1001
  if (minutes % 60 === 0) return `${minutes / 60}h`;
990
1002
  return `${minutes}m`;
991
1003
  }
1004
+ function formatQuotaWindow(bucket, percent, window, countdown) {
1005
+ const reset = formatQuotaCountdown(bucket, countdown);
1006
+ return reset ? `${percent} \u21BB ${reset}` : `${window}:${percent}`;
1007
+ }
1008
+ function formatQuotaCountdown(bucket, countdown) {
1009
+ if (bucket.period === "unlimited" || bucket.resetsAt === void 0 || !Number.isFinite(bucket.resetsAt * 1e3) || !Number.isFinite(countdown.now) || bucket.resetsAt * 1e3 <= countdown.now)
1010
+ return void 0;
1011
+ const reset = formatResetCountdown(bucket.resetsAt, countdown.now);
1012
+ if (reset !== void 0) countdown.active = true;
1013
+ return reset;
1014
+ }
992
1015
  function formatResetCountdown(resetsAt, now) {
993
1016
  if (resetsAt === void 0 || !Number.isFinite(resetsAt) || !Number.isFinite(now)) return void 0;
994
1017
  const totalMinutes = Math.max(0, Math.ceil((resetsAt * 1e3 - now) / 6e4));
@@ -1289,6 +1312,231 @@ function isProviderId(value) {
1289
1312
  // src/usage.ts
1290
1313
  import { randomUUID as randomUUID2 } from "node:crypto";
1291
1314
 
1315
+ // src/codex-fast-catalog.ts
1316
+ import { VERSION } from "@earendil-works/pi-coding-agent";
1317
+
1318
+ // src/usage-helpers.ts
1319
+ function configuredAdapters(ctx) {
1320
+ return usageAdapters().filter(
1321
+ (adapter) => adapter.id === ctx.model?.provider || providerIsConfigured(ctx, adapter.id)
1322
+ );
1323
+ }
1324
+ function providerDisplayName(ctx, providerId) {
1325
+ try {
1326
+ return sanitizeDisplayText(ctx.modelRegistry.getProviderDisplayName(providerId), 80);
1327
+ } catch {
1328
+ return sanitizeDisplayText(providerId, 80);
1329
+ }
1330
+ }
1331
+ function setBoundedMap(map, key, value, limit) {
1332
+ map.delete(key);
1333
+ while (map.size >= limit) {
1334
+ const oldest = map.keys().next().value;
1335
+ if (oldest === void 0) break;
1336
+ map.delete(oldest);
1337
+ }
1338
+ map.set(key, value);
1339
+ }
1340
+ function modelIdentity(model) {
1341
+ return model ? `${model.provider}/${model.id}` : void 0;
1342
+ }
1343
+ function isAbortError(error) {
1344
+ return error instanceof Error && error.name === "AbortError";
1345
+ }
1346
+ function isTimeoutError(error) {
1347
+ return error instanceof Error && error.name === "TimeoutError";
1348
+ }
1349
+
1350
+ // src/codex-fast-catalog.ts
1351
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
1352
+ var FAILURE_BACKOFF_MS = 3e4;
1353
+ var MAX_ACCOUNT_STATES = 32;
1354
+ var MAX_DIRECTORY_BODY_BYTES = 8 * 1024 * 1024;
1355
+ var DIRECTORY_URL = new URL("https://chatgpt.com/backend-api/codex/models");
1356
+ DIRECTORY_URL.searchParams.set("client_version", VERSION);
1357
+ function createCodexFastCatalog(options = {}) {
1358
+ const accounts = /* @__PURE__ */ new Map();
1359
+ const requests = /* @__PURE__ */ new Map();
1360
+ const latestRequests = /* @__PURE__ */ new Map();
1361
+ const timeoutMs = options.timeoutMs ?? 15e3;
1362
+ let requestSequence = 0;
1363
+ let identitySequence = 0;
1364
+ let generation = 0;
1365
+ let active;
1366
+ let issue = UNKNOWN_CODEX_FAST_CAPABILITY.reason;
1367
+ const current = (scope, ctx, signal) => !signal.aborted && generation === scope.generation && active?.key === scope.key && active.fingerprint === scope.fingerprint && active.model === scope.model && active.session === scope.session && modelIdentity2(ctx.model) === scope.model && ctx.sessionManager.getSessionId() === scope.session;
1368
+ const capability = (model, key = active?.key) => {
1369
+ const entry = key ? accounts.get(key) : void 0;
1370
+ const supported = model ? entry?.models?.get(model.id) : void 0;
1371
+ if (supported !== void 0) return { kind: supported ? "supported" : "unsupported" };
1372
+ return {
1373
+ kind: "unknown",
1374
+ reason: entry?.issue ?? (entry?.models && model ? `The Codex model directory has no Fast capability information for ${model.id}.` : issue)
1375
+ };
1376
+ };
1377
+ const identify = async (ctx, signal) => {
1378
+ const identity = ++identitySequence;
1379
+ const owner = generation;
1380
+ const model = modelIdentity2(ctx.model);
1381
+ const session = ctx.sessionManager.getSessionId();
1382
+ if (!isOfficialCodexModel(ctx.model)) {
1383
+ active = void 0;
1384
+ return void 0;
1385
+ }
1386
+ try {
1387
+ const auth = await awaitWithDeadline(
1388
+ resolveCodexResetAuth(ctx, void 0, options.credentialReader, options.candidateReader),
1389
+ signal,
1390
+ timeoutMs,
1391
+ "resolving the current Codex model-directory account"
1392
+ );
1393
+ if (signal.aborted || owner !== generation || identity !== identitySequence || modelIdentity2(ctx.model) !== model || ctx.sessionManager.getSessionId() !== session) {
1394
+ return void 0;
1395
+ }
1396
+ const accountId = auth.headers["chatgpt-account-id"];
1397
+ if (!accountId) throw new Error("The active Codex account ID is unavailable.");
1398
+ const scope = {
1399
+ key: fingerprintResolvedAuth({ headers: { "chatgpt-account-id": accountId } }, AUTH_FINGERPRINT_SALT),
1400
+ fingerprint: auth.fingerprint,
1401
+ model,
1402
+ session,
1403
+ generation: owner
1404
+ };
1405
+ const changed = active?.key !== scope.key;
1406
+ active = scope;
1407
+ issue = "Codex Fast capability information has not been fetched for this account.";
1408
+ if (changed) options.onChanged?.(ctx);
1409
+ return { auth, scope };
1410
+ } catch (error) {
1411
+ if (signal.aborted || owner !== generation || identity !== identitySequence) return void 0;
1412
+ active = void 0;
1413
+ issue = `Codex Fast capability is unknown: ${redactUsageError(errorMessage(error))}`;
1414
+ options.onChanged?.(ctx);
1415
+ return void 0;
1416
+ }
1417
+ };
1418
+ const load = async (ctx, auth, scope, signal, force) => {
1419
+ const entry = accounts.get(scope.key);
1420
+ if (!force && (entry?.models && entry.fingerprint === auth.fingerprint && Date.now() - entry.fetchedAt < CACHE_TTL_MS || entry?.retryAt && Date.now() < entry.retryAt)) {
1421
+ return;
1422
+ }
1423
+ const before = capability(ctx.model, scope.key);
1424
+ const existing = requests.get(auth.fingerprint);
1425
+ let promise = existing?.promise;
1426
+ if (!promise) {
1427
+ const sequence = ++requestSequence;
1428
+ latestRequests.set(scope.key, sequence);
1429
+ const controller = new AbortController();
1430
+ const combined = AbortSignal.any([signal, controller.signal]);
1431
+ let status;
1432
+ promise = (async () => {
1433
+ try {
1434
+ const payload = await fetchProviderJson(
1435
+ DIRECTORY_URL.href,
1436
+ auth,
1437
+ combined,
1438
+ timeoutMs,
1439
+ "Codex model directory",
1440
+ {
1441
+ redirect: "error",
1442
+ maxSuccessBodyBytes: MAX_DIRECTORY_BODY_BYTES,
1443
+ responseError: (responseStatus) => {
1444
+ status = responseStatus;
1445
+ return void 0;
1446
+ }
1447
+ }
1448
+ );
1449
+ const models = normalizeDirectory(payload);
1450
+ if (combined.aborted || scope.generation !== generation || latestRequests.get(scope.key) !== sequence) return;
1451
+ setBoundedMap(
1452
+ accounts,
1453
+ scope.key,
1454
+ { models, fetchedAt: Date.now(), fingerprint: auth.fingerprint },
1455
+ MAX_ACCOUNT_STATES
1456
+ );
1457
+ } catch (error) {
1458
+ if (combined.aborted || scope.generation !== generation || latestRequests.get(scope.key) !== sequence) return;
1459
+ const previous = accounts.get(scope.key);
1460
+ const transient = status === void 0 || status >= 500 || status === 429;
1461
+ setBoundedMap(
1462
+ accounts,
1463
+ scope.key,
1464
+ {
1465
+ ...transient && previous?.models ? previous : { fetchedAt: 0 },
1466
+ retryAt: Date.now() + FAILURE_BACKOFF_MS,
1467
+ issue: `Codex Fast capability is unknown: ${redactUsageError(errorMessage(error), auth.secrets)}`
1468
+ },
1469
+ MAX_ACCOUNT_STATES
1470
+ );
1471
+ } finally {
1472
+ if (requests.get(auth.fingerprint)?.controller === controller) requests.delete(auth.fingerprint);
1473
+ if (latestRequests.get(scope.key) === sequence) latestRequests.delete(scope.key);
1474
+ }
1475
+ })();
1476
+ requests.set(auth.fingerprint, { controller, promise });
1477
+ }
1478
+ try {
1479
+ await awaitWithDeadline(promise, signal, timeoutMs, "fetching the Codex model directory");
1480
+ } catch (error) {
1481
+ if (signal.aborted) return;
1482
+ throw error;
1483
+ }
1484
+ const after = capability(ctx.model, scope.key);
1485
+ if (current(scope, ctx, signal) && JSON.stringify(before) !== JSON.stringify(after)) options.onChanged?.(ctx);
1486
+ };
1487
+ return {
1488
+ get(model) {
1489
+ return capability(model);
1490
+ },
1491
+ async read(ctx, signal, refresh = false, force = false) {
1492
+ const resolved = await identify(ctx, signal);
1493
+ if (!resolved) return void 0;
1494
+ const { auth, scope } = resolved;
1495
+ if (refresh) await load(ctx, auth, scope, signal, force || capability(ctx.model, scope.key).kind === "unknown");
1496
+ else void load(ctx, auth, scope, signal, false);
1497
+ return current(scope, ctx, signal) ? capability(ctx.model, scope.key) : void 0;
1498
+ },
1499
+ reset(clearCache = false) {
1500
+ generation += 1;
1501
+ identitySequence += 1;
1502
+ active = void 0;
1503
+ issue = UNKNOWN_CODEX_FAST_CAPABILITY.reason;
1504
+ for (const { controller } of requests.values()) controller.abort();
1505
+ requests.clear();
1506
+ latestRequests.clear();
1507
+ if (clearCache) accounts.clear();
1508
+ }
1509
+ };
1510
+ }
1511
+ function normalizeDirectory(payload) {
1512
+ if (!Array.isArray(payload.models)) throw new Error("The Codex model directory returned invalid models.");
1513
+ const models = /* @__PURE__ */ new Map();
1514
+ for (const item of payload.models) {
1515
+ if (!isRecord3(item) || typeof item.slug !== "string" || !item.slug.trim() || models.has(item.slug)) {
1516
+ throw new Error("The Codex model directory returned an invalid or duplicate model ID.");
1517
+ }
1518
+ const hasTiers = item.service_tiers !== void 0;
1519
+ const hasLegacy = item.additional_speed_tiers !== void 0;
1520
+ if (hasTiers && (!Array.isArray(item.service_tiers) || item.service_tiers.some((tier) => !isRecord3(tier) || typeof tier.id !== "string")) || hasLegacy && (!Array.isArray(item.additional_speed_tiers) || item.additional_speed_tiers.some((tier) => typeof tier !== "string"))) {
1521
+ throw new Error("The Codex model directory returned invalid service tiers.");
1522
+ }
1523
+ if (!hasTiers && !hasLegacy) {
1524
+ models.set(item.slug, void 0);
1525
+ continue;
1526
+ }
1527
+ const tiers = item.service_tiers ?? [];
1528
+ const legacy = item.additional_speed_tiers ?? [];
1529
+ models.set(item.slug, tiers.some((tier) => tier.id === "priority") || legacy.includes("fast"));
1530
+ }
1531
+ return models;
1532
+ }
1533
+ function modelIdentity2(model) {
1534
+ return model ? `${model.provider}/${model.id}` : "";
1535
+ }
1536
+ function isRecord3(value) {
1537
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1538
+ }
1539
+
1292
1540
  // src/codex-fast-runtime.ts
1293
1541
  var NO_FAST_REQUEST = /* @__PURE__ */ Symbol("no-fast-request");
1294
1542
  var FAST_USAGE_WARNING = "Fast is about 1.5\xD7 faster and uses more of your plan allowance.";
@@ -1296,14 +1544,58 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {})
1296
1544
  let sessionController = new AbortController();
1297
1545
  let generation = 0;
1298
1546
  const pendingFastRequests = /* @__PURE__ */ new Map();
1547
+ const catalog = createCodexFastCatalog({
1548
+ credentialReader: options.credentialReader,
1549
+ candidateReader: options.candidateReader ?? createOAuthCredentialCandidateReader(pi, options.credentialReader),
1550
+ timeoutMs: options.timeoutMs,
1551
+ onChanged(ctx) {
1552
+ if (sessionController.signal.aborted || !settingsRuntime.get().settings.codexFastMode) return;
1553
+ try {
1554
+ refreshStatus(ctx);
1555
+ } catch (error) {
1556
+ if (!isStaleExtensionContextError(error)) throw error;
1557
+ }
1558
+ }
1559
+ });
1299
1560
  const toggle = async (ctx, enabled, callerSignal) => {
1300
1561
  const ownerGeneration = generation;
1301
1562
  const sessionId = ctx.sessionManager.getSessionId();
1302
1563
  const signal = callerSignal ? AbortSignal.any([callerSignal, sessionController.signal]) : sessionController.signal;
1564
+ if (ctx.model?.provider !== "openai-codex") {
1565
+ ctx.ui.notify("/fast is available only for the active OpenAI Codex model.", "warning");
1566
+ return false;
1567
+ }
1568
+ if (!isOfficialCodexModel(ctx.model)) {
1569
+ ctx.ui.notify("Fast mode requires the official OpenAI Codex Responses endpoint.", "warning");
1570
+ return false;
1571
+ }
1572
+ if (settingsRuntime.get().kind === "invalid") {
1573
+ ctx.ui.notify("pi-usage.json is invalid; repair it and run /reload before changing Fast mode.", "error");
1574
+ return false;
1575
+ }
1576
+ if (enabled) {
1577
+ const capability = await catalog.read(ctx, signal, true, catalog.get(ctx.model).kind !== "supported");
1578
+ if (signal.aborted || ownerGeneration !== generation) return false;
1579
+ if (!capability) {
1580
+ const latest = catalog.get(ctx.model);
1581
+ ctx.ui.notify(
1582
+ latest.kind === "unknown" ? latest.reason : "The active Codex account changed while checking Fast capability.",
1583
+ "warning"
1584
+ );
1585
+ return false;
1586
+ }
1587
+ const availability = codexFastAvailability(ctx.model, false, capability);
1588
+ if (availability.kind !== "available") {
1589
+ if (availability.kind === "unknown" || availability.kind === "unavailable") {
1590
+ ctx.ui.notify(availability.reason, "warning");
1591
+ }
1592
+ return false;
1593
+ }
1594
+ }
1303
1595
  try {
1304
1596
  await settingsRuntime.update({ codexFastMode: enabled }, signal);
1305
1597
  } catch (error) {
1306
- if (isAbortError(error) || isStaleExtensionContextError(error)) return false;
1598
+ if (isAbortError2(error) || isStaleExtensionContextError(error)) return false;
1307
1599
  ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
1308
1600
  return false;
1309
1601
  }
@@ -1326,26 +1618,18 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {})
1326
1618
  return;
1327
1619
  }
1328
1620
  if (!ctx.hasUI) throw new Error("/fast requires TUI or RPC mode.");
1329
- const availability = codexFastAvailability(ctx.model, settingsRuntime.get().settings.codexFastMode);
1330
- if (availability.kind === "not-codex") {
1331
- ctx.ui.notify("/fast is available only for the active OpenAI Codex model.", "warning");
1332
- return;
1333
- }
1334
- if (availability.kind === "unavailable") {
1335
- ctx.ui.notify(availability.reason, "warning");
1336
- return;
1337
- }
1338
- if (settingsRuntime.get().kind === "invalid") {
1339
- ctx.ui.notify("pi-usage.json is invalid; repair it and run /reload before changing Fast mode.", "error");
1340
- return;
1341
- }
1342
- await toggle(ctx, !availability.enabled);
1621
+ await toggle(ctx, !settingsRuntime.get().settings.codexFastMode);
1343
1622
  }
1344
1623
  });
1624
+ const refreshCapabilities = (ctx, callerSignal, force = false) => {
1625
+ const signal = callerSignal ? AbortSignal.any([callerSignal, sessionController.signal]) : sessionController.signal;
1626
+ return catalog.read(ctx, signal, true, force);
1627
+ };
1345
1628
  const prepareSession = (ctx) => {
1346
1629
  const sessionId = ctx.sessionManager.getSessionId();
1347
1630
  generation += 1;
1348
1631
  sessionController.abort();
1632
+ catalog.reset();
1349
1633
  pendingFastRequests.clear();
1350
1634
  sessionController = new AbortController();
1351
1635
  const ownerGeneration = generation;
@@ -1367,18 +1651,28 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {})
1367
1651
  ctx.ui.notify(`Invalid pi-usage.json; using defaults without overwriting it. ${state.issue}`, "warning");
1368
1652
  }
1369
1653
  refreshStatus(ctx);
1654
+ if (state.kind !== "invalid" && isOfficialCodexModel(ctx.model)) void refreshCapabilities(ctx);
1370
1655
  })();
1371
1656
  };
1372
1657
  if (options.registerSessionStart !== false) {
1373
1658
  pi.on("session_start", async (_event, ctx) => prepareSession(ctx));
1374
1659
  }
1375
- pi.on("before_provider_request", (event, ctx) => {
1376
- const rewritten = rewriteCodexFastPayload(event.payload, ctx.model, settingsRuntime.get().settings.codexFastMode);
1377
- const key = activeRequestKey(ctx);
1378
- if (key && ctx.model) {
1660
+ pi.on("model_select", (_event, ctx) => {
1661
+ void refreshCapabilities(ctx);
1662
+ });
1663
+ pi.on("before_provider_request", async (event, ctx) => {
1664
+ const ownerGeneration = generation;
1665
+ const model = ctx.model;
1666
+ const enabled = settingsRuntime.get().settings.codexFastMode;
1667
+ const capability = enabled && isOfficialCodexModel(model) ? await catalog.read(ctx, sessionController.signal) ?? UNKNOWN_CODEX_FAST_CAPABILITY : UNKNOWN_CODEX_FAST_CAPABILITY;
1668
+ if (sessionController.signal.aborted || ownerGeneration !== generation) return void 0;
1669
+ const rewritten = rewriteCodexFastPayload(event.payload, model, enabled, capability);
1670
+ const key = activeRequestKey(ctx, model);
1671
+ if (key && model) {
1379
1672
  pendingFastRequests.set(key, {
1380
- fastRequested: isRecord3(rewritten) && rewritten.service_tier === "priority",
1381
- model: ctx.model
1673
+ fastRequested: isRecord4(rewritten) && rewritten.service_tier === "priority",
1674
+ model,
1675
+ capability
1382
1676
  });
1383
1677
  }
1384
1678
  return rewritten;
@@ -1386,32 +1680,43 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {})
1386
1680
  pi.on("message_end", (event, ctx) => {
1387
1681
  const request = consumeFastRequest(ctx, event.message, pendingFastRequests);
1388
1682
  if (request === NO_FAST_REQUEST) return void 0;
1389
- const message = correctCodexFastMessageCost(event.message, request.model, request.fastRequested);
1683
+ const message = correctCodexFastMessageCost(
1684
+ event.message,
1685
+ request.model,
1686
+ request.fastRequested,
1687
+ request.capability
1688
+ );
1390
1689
  return message ? { message } : void 0;
1391
1690
  });
1392
1691
  pi.on("session_shutdown", async () => {
1393
1692
  generation += 1;
1394
1693
  sessionController.abort();
1694
+ catalog.reset(true);
1395
1695
  pendingFastRequests.clear();
1396
1696
  await settingsRuntime.flush();
1397
1697
  });
1398
1698
  return {
1399
1699
  prepareSession,
1700
+ refreshCapabilities,
1400
1701
  availability(model) {
1401
- return codexFastAvailability(model, settingsRuntime.get().settings.codexFastMode);
1702
+ return codexFastAvailability(model, settingsRuntime.get().settings.codexFastMode, catalog.get(model));
1402
1703
  },
1403
1704
  decorateStatus(model, status) {
1404
- return codexFastStatusLabel(status, codexFastIsEffective(model, settingsRuntime.get().settings.codexFastMode));
1705
+ const enabled = settingsRuntime.get().settings.codexFastMode;
1706
+ const capability = catalog.get(model);
1707
+ if (enabled && codexFastAvailability(model, enabled, capability).kind === "unknown" && /^codex(?:\s|$)/u.test(status)) {
1708
+ return `codex (Fast unknown)${status.slice("codex".length)}`;
1709
+ }
1710
+ return codexFastStatusLabel(status, codexFastIsEffective(model, enabled, capability));
1405
1711
  },
1406
1712
  toggle
1407
1713
  };
1408
1714
  }
1409
- function activeRequestKey(ctx) {
1410
- const model = ctx.model;
1715
+ function activeRequestKey(ctx, model = ctx.model) {
1411
1716
  return model ? `${ctx.sessionManager.getSessionId()}:${model.provider}/${model.id}` : void 0;
1412
1717
  }
1413
1718
  function consumeFastRequest(ctx, message, pending) {
1414
- if (!isRecord3(message) || message.role !== "assistant") return NO_FAST_REQUEST;
1719
+ if (!isRecord4(message) || message.role !== "assistant") return NO_FAST_REQUEST;
1415
1720
  const key = messageRequestKey(ctx, message);
1416
1721
  if (!key) return NO_FAST_REQUEST;
1417
1722
  const request = pending.get(key);
@@ -1422,44 +1727,12 @@ function messageRequestKey(ctx, message) {
1422
1727
  if (typeof message.provider !== "string" || typeof message.model !== "string") return void 0;
1423
1728
  return `${ctx.sessionManager.getSessionId()}:${message.provider}/${message.model}`;
1424
1729
  }
1425
- function isRecord3(value) {
1730
+ function isRecord4(value) {
1426
1731
  return typeof value === "object" && value !== null && !Array.isArray(value);
1427
1732
  }
1428
- function isAbortError(error) {
1429
- return error instanceof Error && error.name === "AbortError";
1430
- }
1431
-
1432
- // src/usage-helpers.ts
1433
- function configuredAdapters(ctx) {
1434
- return usageAdapters().filter(
1435
- (adapter) => adapter.id === ctx.model?.provider || providerIsConfigured(ctx, adapter.id)
1436
- );
1437
- }
1438
- function providerDisplayName(ctx, providerId) {
1439
- try {
1440
- return sanitizeDisplayText(ctx.modelRegistry.getProviderDisplayName(providerId), 80);
1441
- } catch {
1442
- return sanitizeDisplayText(providerId, 80);
1443
- }
1444
- }
1445
- function setBoundedMap(map, key, value, limit) {
1446
- map.delete(key);
1447
- while (map.size >= limit) {
1448
- const oldest = map.keys().next().value;
1449
- if (oldest === void 0) break;
1450
- map.delete(oldest);
1451
- }
1452
- map.set(key, value);
1453
- }
1454
- function modelIdentity(model) {
1455
- return model ? `${model.provider}/${model.id}` : void 0;
1456
- }
1457
1733
  function isAbortError2(error) {
1458
1734
  return error instanceof Error && error.name === "AbortError";
1459
1735
  }
1460
- function isTimeoutError(error) {
1461
- return error instanceof Error && error.name === "TimeoutError";
1462
- }
1463
1736
 
1464
1737
  // src/usage-settings-ui.ts
1465
1738
  import { stripVTControlCharacters } from "node:util";
@@ -1599,13 +1872,13 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
1599
1872
  }
1600
1873
 
1601
1874
  // src/usage.ts
1602
- var CACHE_TTL_MS = 5 * 60 * 1e3;
1875
+ var CACHE_TTL_MS2 = 5 * 60 * 1e3;
1603
1876
  var STATUS_COUNTDOWN_REFRESH_MS = 60 * 1e3;
1604
1877
  var DEFAULT_TIMEOUT_MS = 15e3;
1605
1878
  var COMMAND_CODE_REVALIDATION_RESERVE_MS = 1e3;
1606
1879
  var ALL_PROVIDER_CONCURRENCY = 2;
1607
- var FAILURE_BACKOFF_MS = 3e4;
1608
- var MAX_ACCOUNT_STATES = 32;
1880
+ var FAILURE_BACKOFF_MS2 = 3e4;
1881
+ var MAX_ACCOUNT_STATES2 = 32;
1609
1882
  var STATUS_KEY = "usage";
1610
1883
  var REFRESH_CURRENT = "Refresh current usage";
1611
1884
  var VIEW_ANOTHER = "View another configured provider\u2026";
@@ -1622,7 +1895,7 @@ function usageExtension(pi, dependencies = {}) {
1622
1895
  const createRedemptionId = dependencies.createRedemptionId ?? randomUUID2;
1623
1896
  const settingsRuntime = dependencies.settingsRuntime ?? createUsageSettingsRuntime();
1624
1897
  const resolveUsageAuth2 = resolveUsageAuth;
1625
- const cache = new UsageCache(CACHE_TTL_MS);
1898
+ const cache = new UsageCache(CACHE_TTL_MS2);
1626
1899
  const failureBackoff = /* @__PURE__ */ new Map();
1627
1900
  const latestQueries = /* @__PURE__ */ new Map();
1628
1901
  const activeControllers = /* @__PURE__ */ new Set();
@@ -1671,7 +1944,7 @@ function usageExtension(pi, dependencies = {}) {
1671
1944
  statusRefreshTimer = void 0;
1672
1945
  if (!sessionActive || generation !== statusGeneration) return;
1673
1946
  startStatusRefresh(ctx, model, true);
1674
- }, CACHE_TTL_MS);
1947
+ }, CACHE_TTL_MS2);
1675
1948
  statusRefreshTimer.unref?.();
1676
1949
  };
1677
1950
  const publishStatus = (ctx, outcome, model, shouldSchedule) => {
@@ -1697,23 +1970,23 @@ function usageExtension(pi, dependencies = {}) {
1697
1970
  const showCodexResetCountdown = outcome.state.report.providerId === "openai-codex" && settings.codexStatusResetCountdown;
1698
1971
  const isCompanionReport = outcome.state.report.source === "openai-chatgpt-companion";
1699
1972
  const now = Date.now();
1700
- const rawValue = formatUsageStatusline(
1973
+ const status = formatUsageStatuslineWithCountdown(
1701
1974
  outcome.state.report,
1702
1975
  model,
1703
1976
  now,
1704
1977
  showCodexResetCountdown,
1705
1978
  settings.codexStatusPercentage
1706
1979
  );
1707
- const value = rawValue ? fastRuntime.decorateStatus(model, rawValue) : void 0;
1980
+ const value = status.value ? fastRuntime.decorateStatus(model, status.value) : void 0;
1708
1981
  if (!safeSetStatus(ctx, value)) return;
1709
1982
  if (shouldSchedule && sessionActive) scheduleStatusRefresh(ctx, model);
1710
- if (sessionActive && (showCodexResetCountdown || isCompanionReport) && outcome.state.report.buckets.some(
1983
+ if (sessionActive && (status.hasCountdown || (showCodexResetCountdown || isCompanionReport) && outcome.state.report.buckets.some(
1711
1984
  (bucket) => (!isCompanionReport || bucket.groupId === "chatgpt-plan") && bucket.resetsAt !== void 0 && Number.isFinite(bucket.resetsAt) && bucket.resetsAt * 1e3 > now
1712
- )) {
1985
+ ))) {
1713
1986
  const generation = statusGeneration;
1714
1987
  statusCountdownTimer = setTimeout(() => {
1715
- statusCountdownTimer = void 0;
1716
1988
  if (!sessionActive || generation !== statusGeneration) return;
1989
+ statusCountdownTimer = void 0;
1717
1990
  publishStatus(ctx, outcome, model, false);
1718
1991
  }, STATUS_COUNTDOWN_REFRESH_MS);
1719
1992
  statusCountdownTimer.unref?.();
@@ -1754,7 +2027,7 @@ function usageExtension(pi, dependencies = {}) {
1754
2027
  `resolving ${adapter.displayName} runtime auth`
1755
2028
  );
1756
2029
  } catch (error) {
1757
- if (isStaleExtensionContextError(error) || isAbortError2(error)) throw error;
2030
+ if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
1758
2031
  if (adapter.id === "openai") invalidateProviderState(adapter.id);
1759
2032
  const authState = error instanceof UnsupportedOpenAIUsageAuthError ? "unsupported" : adapter.id === "openai" && !isTimeoutError(error) ? "failed" : void 0;
1760
2033
  if (displayState === "current") {
@@ -1903,7 +2176,7 @@ function usageExtension(pi, dependencies = {}) {
1903
2176
  failureBackoff.delete(failureKey);
1904
2177
  querySequence += 1;
1905
2178
  queryId = querySequence;
1906
- setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
2179
+ setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES2);
1907
2180
  const queryTimeoutMs = Math.max(
1908
2181
  1,
1909
2182
  deadlineAt - Date.now() - (adapter.id === "command-code" ? COMMAND_CODE_REVALIDATION_RESERVE_MS : 0)
@@ -1934,7 +2207,7 @@ function usageExtension(pi, dependencies = {}) {
1934
2207
  rememberedTargetId: expectedTargetId
1935
2208
  };
1936
2209
  } catch (error) {
1937
- if (isStaleExtensionContextError(error) || isAbortError2(error)) throw error;
2210
+ if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
1938
2211
  if (retryableAuthChanged && authRetry === 0 && !signal.aborted && !requestContextChanged() && Date.now() < deadlineAt) {
1939
2212
  if (queryId !== void 0 && latestQueries.get(failureKey) === queryId) {
1940
2213
  latestQueries.delete(failureKey);
@@ -1948,7 +2221,7 @@ function usageExtension(pi, dependencies = {}) {
1948
2221
  }
1949
2222
  if (queryId === void 0 || latestQueries.get(failureKey) === queryId) {
1950
2223
  if (adapter.invalidateCacheOnFailure) cache.delete(adapter.id, queryFingerprint);
1951
- setBoundedMap(failureBackoff, failureKey, { until: now + FAILURE_BACKOFF_MS, message }, MAX_ACCOUNT_STATES);
2224
+ setBoundedMap(failureBackoff, failureKey, { until: now + FAILURE_BACKOFF_MS2, message }, MAX_ACCOUNT_STATES2);
1952
2225
  }
1953
2226
  return {
1954
2227
  state: {
@@ -2065,7 +2338,7 @@ function usageExtension(pi, dependencies = {}) {
2065
2338
  };
2066
2339
  const startStatusRefresh = (ctx, model, force) => {
2067
2340
  void refreshCurrentStatus(ctx, model, force).catch((error) => {
2068
- if (isStaleExtensionContextError(error) || isAbortError2(error)) return;
2341
+ if (isStaleExtensionContextError(error) || isAbortError(error)) return;
2069
2342
  const message = errorMessage(error);
2070
2343
  console.error("[pi-usage] refresh failed:", message);
2071
2344
  safeSetStatus(ctx, `usage err: ${message.slice(0, 50)}`);
@@ -2109,7 +2382,7 @@ function usageExtension(pi, dependencies = {}) {
2109
2382
  );
2110
2383
  return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId) && outcome.authState === "unavailable" && auth === void 0;
2111
2384
  } catch (error) {
2112
- if (isAbortError2(error) || isStaleExtensionContextError(error)) throw error;
2385
+ if (isAbortError(error) || isStaleExtensionContextError(error)) throw error;
2113
2386
  const unchangedFailure = outcome.authState === "unsupported" && error instanceof UnsupportedOpenAIUsageAuthError || outcome.authState === "failed" && outcome.state.status === "auth-unavailable" && !(error instanceof UnsupportedOpenAIUsageAuthError) && !isTimeoutError(error) && errorMessage(error) === outcome.state.message;
2114
2387
  return unchangedFailure && generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId);
2115
2388
  }
@@ -2125,7 +2398,7 @@ function usageExtension(pi, dependencies = {}) {
2125
2398
  );
2126
2399
  return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId) && auth?.fingerprint === outcome.fingerprint;
2127
2400
  } catch (error) {
2128
- if (isAbortError2(error) || isStaleExtensionContextError(error)) throw error;
2401
+ if (isAbortError(error) || isStaleExtensionContextError(error)) throw error;
2129
2402
  return false;
2130
2403
  }
2131
2404
  };
@@ -2165,14 +2438,26 @@ function usageExtension(pi, dependencies = {}) {
2165
2438
  };
2166
2439
  const showMenu = async (ctx) => {
2167
2440
  if (!ctx.hasUI) throw new Error("/usage requires TUI or RPC mode.");
2168
- statusGeneration += 1;
2169
- const menuGeneration = statusGeneration;
2170
- statusController?.abort();
2171
- statusController = void 0;
2172
- clearStatusTimers();
2173
2441
  const controller = new AbortController();
2174
2442
  activeControllers.add(controller);
2175
2443
  try {
2444
+ if (isOfficialCodexModel(ctx.model)) {
2445
+ const refreshed = await runMenuOperation(
2446
+ ctx,
2447
+ "Checking Codex Fast capability\u2026",
2448
+ controller.signal,
2449
+ async (signal) => {
2450
+ await fastRuntime.refreshCapabilities(ctx, signal);
2451
+ return true;
2452
+ }
2453
+ );
2454
+ if (!refreshed || controller.signal.aborted) return;
2455
+ }
2456
+ statusGeneration += 1;
2457
+ const menuGeneration = statusGeneration;
2458
+ statusController?.abort();
2459
+ statusController = void 0;
2460
+ clearStatusTimers();
2176
2461
  let stableCurrent = await queryStableCurrent(ctx, false, controller, "Checking current usage\u2026");
2177
2462
  if (!stableCurrent) return;
2178
2463
  publishStableCurrent(ctx, stableCurrent);
@@ -2277,7 +2562,7 @@ function usageExtension(pi, dependencies = {}) {
2277
2562
  const fastAvailability = fastRuntime.availability(ctx.model);
2278
2563
  const targetState = actionableTargetState();
2279
2564
  const targetAdapter = adapterForProvider(targetState?.providerId);
2280
- const fastLines = fastAvailability.kind === "available" ? [`Fast mode: ${fastAvailability.enabled ? "On" : "Off"}`, FAST_USAGE_WARNING] : fastAvailability.kind === "unavailable" ? [`Fast mode: Unavailable \xB7 ${fastAvailability.reason}`] : [];
2565
+ const fastLines = fastAvailability.kind === "available" ? [`Fast mode: ${fastAvailability.enabled ? "On" : "Off"}`, FAST_USAGE_WARNING] : fastAvailability.kind === "unavailable" ? [`Fast mode: Unavailable \xB7 ${fastAvailability.reason}`] : fastAvailability.kind === "unknown" ? [`Fast mode: Unknown \xB7 ${fastAvailability.reason}`] : [];
2281
2566
  return {
2282
2567
  kind: "actions",
2283
2568
  title: "Provider usage",
@@ -2292,10 +2577,10 @@ function usageExtension(pi, dependencies = {}) {
2292
2577
  action: "target"
2293
2578
  }
2294
2579
  ] : [],
2295
- ...fastAvailability.kind === "available" ? [
2580
+ ...fastAvailability.kind === "available" || fastState.settings.codexFastMode && isOfficialCodexModel(ctx.model) ? [
2296
2581
  {
2297
2582
  id: "toggle-fast",
2298
- label: fastAvailability.enabled ? "Turn Fast mode off" : "Turn Fast mode on",
2583
+ label: fastState.settings.codexFastMode ? "Turn Fast mode off" : "Turn Fast mode on",
2299
2584
  description: fastState.kind === "invalid" ? "Repair pi-usage.json and reload before changing Fast mode." : FAST_USAGE_WARNING,
2300
2585
  disabled: fastState.kind === "invalid",
2301
2586
  action: "toggle-fast"
@@ -2424,7 +2709,7 @@ function usageExtension(pi, dependencies = {}) {
2424
2709
  ];
2425
2710
  return { kind: "stay" };
2426
2711
  } catch (error) {
2427
- if (isAbortError2(error) || isStaleExtensionContextError(error)) {
2712
+ if (isAbortError(error) || isStaleExtensionContextError(error)) {
2428
2713
  return { kind: "stay" };
2429
2714
  }
2430
2715
  ctx.ui.notify(`Could not select target: ${errorMessage(error)}`, "error");
@@ -2454,10 +2739,11 @@ function usageExtension(pi, dependencies = {}) {
2454
2739
  },
2455
2740
  "toggle-fast": async () => {
2456
2741
  const availability = fastRuntime.availability(ctx.model);
2457
- if (availability.kind !== "available" || fastState.kind === "invalid") {
2742
+ const enabled = settingsRuntime.get().settings.codexFastMode;
2743
+ if (availability.kind !== "available" && !enabled || fastState.kind === "invalid") {
2458
2744
  return { kind: "rejected" };
2459
2745
  }
2460
- const changed = await fastRuntime.toggle(ctx, !availability.enabled, controller.signal);
2746
+ const changed = await fastRuntime.toggle(ctx, !enabled, controller.signal);
2461
2747
  if (!changed) return { kind: "rejected" };
2462
2748
  fastState = settingsRuntime.get();
2463
2749
  return { kind: "stay" };
@@ -2481,7 +2767,7 @@ function usageExtension(pi, dependencies = {}) {
2481
2767
  try {
2482
2768
  availability = await listCodexResetCredits(auth, signal, DEFAULT_TIMEOUT_MS);
2483
2769
  } catch (error) {
2484
- if (isAbortError2(error) || summaryCount === void 0 || summaryCount <= 0) {
2770
+ if (isAbortError(error) || summaryCount === void 0 || summaryCount <= 0) {
2485
2771
  throw error;
2486
2772
  }
2487
2773
  availability = {
@@ -2514,7 +2800,7 @@ function usageExtension(pi, dependencies = {}) {
2514
2800
  screen: loaded.availability.availableCount > 0 ? "reset-picker" : "reset-result"
2515
2801
  };
2516
2802
  } catch (error) {
2517
- if (isAbortError2(error) || isStaleExtensionContextError(error)) {
2803
+ if (isAbortError(error) || isStaleExtensionContextError(error)) {
2518
2804
  return { kind: "stay" };
2519
2805
  }
2520
2806
  ctx.ui.notify(`Couldn't load usage limit resets: ${errorMessage(error)}`, "error");
@@ -2576,7 +2862,7 @@ function usageExtension(pi, dependencies = {}) {
2576
2862
  }
2577
2863
  return { kind: "to", screen: "reset-result" };
2578
2864
  } catch (error) {
2579
- if (isAbortError2(error) || isStaleExtensionContextError(error)) {
2865
+ if (isAbortError(error) || isStaleExtensionContextError(error)) {
2580
2866
  return { kind: "close" };
2581
2867
  }
2582
2868
  resetFailure = `Couldn't reset usage: ${errorMessage(error)}. Try again with the same request.`;
@@ -2730,7 +3016,7 @@ function usageExtension(pi, dependencies = {}) {
2730
3016
  try {
2731
3017
  await showMenu(ctx);
2732
3018
  } catch (error) {
2733
- if (isStaleExtensionContextError(error) || isAbortError2(error)) return;
3019
+ if (isStaleExtensionContextError(error) || isAbortError(error)) return;
2734
3020
  throw error;
2735
3021
  }
2736
3022
  }
@@ -2776,15 +3062,18 @@ function usageExtension(pi, dependencies = {}) {
2776
3062
  safeSetStatus(ctx, void 0);
2777
3063
  });
2778
3064
  fastRuntime = registerCodexFastMode(pi, settingsRuntime, (ctx) => startStatusRefresh(ctx, ctx.model, false), {
2779
- registerSessionStart: false
3065
+ registerSessionStart: false,
3066
+ credentialReader,
3067
+ candidateReader: credentialCandidates,
3068
+ timeoutMs: DEFAULT_TIMEOUT_MS
2780
3069
  });
2781
3070
  }
2782
3071
  export {
2783
- CODEX_FAST_MODEL_IDS,
2784
3072
  CODEX_FAST_SERVICE_TIER,
2785
3073
  CODEX_STANDARD_SERVICE_TIER,
2786
3074
  DEFAULT_USAGE_SETTINGS,
2787
3075
  SUPPORTED_ADAPTERS,
3076
+ UNKNOWN_CODEX_FAST_CAPABILITY,
2788
3077
  UsageCache,
2789
3078
  XAI_ADAPTER,
2790
3079
  abortError,
@@ -2807,6 +3096,7 @@ export {
2807
3096
  formatUsageReport,
2808
3097
  formatUsageStatusline,
2809
3098
  isBoundedTargetId,
3099
+ isOfficialCodexModel,
2810
3100
  isStaleExtensionContextError,
2811
3101
  listCodexResetCredits,
2812
3102
  listUsageTargets,