@narumitw/pi-usage 0.64.3 → 0.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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;
@@ -1289,6 +1284,231 @@ function isProviderId(value) {
1289
1284
  // src/usage.ts
1290
1285
  import { randomUUID as randomUUID2 } from "node:crypto";
1291
1286
 
1287
+ // src/codex-fast-catalog.ts
1288
+ import { VERSION } from "@earendil-works/pi-coding-agent";
1289
+
1290
+ // src/usage-helpers.ts
1291
+ function configuredAdapters(ctx) {
1292
+ return usageAdapters().filter(
1293
+ (adapter) => adapter.id === ctx.model?.provider || providerIsConfigured(ctx, adapter.id)
1294
+ );
1295
+ }
1296
+ function providerDisplayName(ctx, providerId) {
1297
+ try {
1298
+ return sanitizeDisplayText(ctx.modelRegistry.getProviderDisplayName(providerId), 80);
1299
+ } catch {
1300
+ return sanitizeDisplayText(providerId, 80);
1301
+ }
1302
+ }
1303
+ function setBoundedMap(map, key, value, limit) {
1304
+ map.delete(key);
1305
+ while (map.size >= limit) {
1306
+ const oldest = map.keys().next().value;
1307
+ if (oldest === void 0) break;
1308
+ map.delete(oldest);
1309
+ }
1310
+ map.set(key, value);
1311
+ }
1312
+ function modelIdentity(model) {
1313
+ return model ? `${model.provider}/${model.id}` : void 0;
1314
+ }
1315
+ function isAbortError(error) {
1316
+ return error instanceof Error && error.name === "AbortError";
1317
+ }
1318
+ function isTimeoutError(error) {
1319
+ return error instanceof Error && error.name === "TimeoutError";
1320
+ }
1321
+
1322
+ // src/codex-fast-catalog.ts
1323
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
1324
+ var FAILURE_BACKOFF_MS = 3e4;
1325
+ var MAX_ACCOUNT_STATES = 32;
1326
+ var MAX_DIRECTORY_BODY_BYTES = 8 * 1024 * 1024;
1327
+ var DIRECTORY_URL = new URL("https://chatgpt.com/backend-api/codex/models");
1328
+ DIRECTORY_URL.searchParams.set("client_version", VERSION);
1329
+ function createCodexFastCatalog(options = {}) {
1330
+ const accounts = /* @__PURE__ */ new Map();
1331
+ const requests = /* @__PURE__ */ new Map();
1332
+ const latestRequests = /* @__PURE__ */ new Map();
1333
+ const timeoutMs = options.timeoutMs ?? 15e3;
1334
+ let requestSequence = 0;
1335
+ let identitySequence = 0;
1336
+ let generation = 0;
1337
+ let active;
1338
+ let issue = UNKNOWN_CODEX_FAST_CAPABILITY.reason;
1339
+ 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;
1340
+ const capability = (model, key = active?.key) => {
1341
+ const entry = key ? accounts.get(key) : void 0;
1342
+ const supported = model ? entry?.models?.get(model.id) : void 0;
1343
+ if (supported !== void 0) return { kind: supported ? "supported" : "unsupported" };
1344
+ return {
1345
+ kind: "unknown",
1346
+ reason: entry?.issue ?? (entry?.models && model ? `The Codex model directory has no Fast capability information for ${model.id}.` : issue)
1347
+ };
1348
+ };
1349
+ const identify = async (ctx, signal) => {
1350
+ const identity = ++identitySequence;
1351
+ const owner = generation;
1352
+ const model = modelIdentity2(ctx.model);
1353
+ const session = ctx.sessionManager.getSessionId();
1354
+ if (!isOfficialCodexModel(ctx.model)) {
1355
+ active = void 0;
1356
+ return void 0;
1357
+ }
1358
+ try {
1359
+ const auth = await awaitWithDeadline(
1360
+ resolveCodexResetAuth(ctx, void 0, options.credentialReader, options.candidateReader),
1361
+ signal,
1362
+ timeoutMs,
1363
+ "resolving the current Codex model-directory account"
1364
+ );
1365
+ if (signal.aborted || owner !== generation || identity !== identitySequence || modelIdentity2(ctx.model) !== model || ctx.sessionManager.getSessionId() !== session) {
1366
+ return void 0;
1367
+ }
1368
+ const accountId = auth.headers["chatgpt-account-id"];
1369
+ if (!accountId) throw new Error("The active Codex account ID is unavailable.");
1370
+ const scope = {
1371
+ key: fingerprintResolvedAuth({ headers: { "chatgpt-account-id": accountId } }, AUTH_FINGERPRINT_SALT),
1372
+ fingerprint: auth.fingerprint,
1373
+ model,
1374
+ session,
1375
+ generation: owner
1376
+ };
1377
+ const changed = active?.key !== scope.key;
1378
+ active = scope;
1379
+ issue = "Codex Fast capability information has not been fetched for this account.";
1380
+ if (changed) options.onChanged?.(ctx);
1381
+ return { auth, scope };
1382
+ } catch (error) {
1383
+ if (signal.aborted || owner !== generation || identity !== identitySequence) return void 0;
1384
+ active = void 0;
1385
+ issue = `Codex Fast capability is unknown: ${redactUsageError(errorMessage(error))}`;
1386
+ options.onChanged?.(ctx);
1387
+ return void 0;
1388
+ }
1389
+ };
1390
+ const load = async (ctx, auth, scope, signal, force) => {
1391
+ const entry = accounts.get(scope.key);
1392
+ if (!force && (entry?.models && entry.fingerprint === auth.fingerprint && Date.now() - entry.fetchedAt < CACHE_TTL_MS || entry?.retryAt && Date.now() < entry.retryAt)) {
1393
+ return;
1394
+ }
1395
+ const before = capability(ctx.model, scope.key);
1396
+ const existing = requests.get(auth.fingerprint);
1397
+ let promise = existing?.promise;
1398
+ if (!promise) {
1399
+ const sequence = ++requestSequence;
1400
+ latestRequests.set(scope.key, sequence);
1401
+ const controller = new AbortController();
1402
+ const combined = AbortSignal.any([signal, controller.signal]);
1403
+ let status;
1404
+ promise = (async () => {
1405
+ try {
1406
+ const payload = await fetchProviderJson(
1407
+ DIRECTORY_URL.href,
1408
+ auth,
1409
+ combined,
1410
+ timeoutMs,
1411
+ "Codex model directory",
1412
+ {
1413
+ redirect: "error",
1414
+ maxSuccessBodyBytes: MAX_DIRECTORY_BODY_BYTES,
1415
+ responseError: (responseStatus) => {
1416
+ status = responseStatus;
1417
+ return void 0;
1418
+ }
1419
+ }
1420
+ );
1421
+ const models = normalizeDirectory(payload);
1422
+ if (combined.aborted || scope.generation !== generation || latestRequests.get(scope.key) !== sequence) return;
1423
+ setBoundedMap(
1424
+ accounts,
1425
+ scope.key,
1426
+ { models, fetchedAt: Date.now(), fingerprint: auth.fingerprint },
1427
+ MAX_ACCOUNT_STATES
1428
+ );
1429
+ } catch (error) {
1430
+ if (combined.aborted || scope.generation !== generation || latestRequests.get(scope.key) !== sequence) return;
1431
+ const previous = accounts.get(scope.key);
1432
+ const transient = status === void 0 || status >= 500 || status === 429;
1433
+ setBoundedMap(
1434
+ accounts,
1435
+ scope.key,
1436
+ {
1437
+ ...transient && previous?.models ? previous : { fetchedAt: 0 },
1438
+ retryAt: Date.now() + FAILURE_BACKOFF_MS,
1439
+ issue: `Codex Fast capability is unknown: ${redactUsageError(errorMessage(error), auth.secrets)}`
1440
+ },
1441
+ MAX_ACCOUNT_STATES
1442
+ );
1443
+ } finally {
1444
+ if (requests.get(auth.fingerprint)?.controller === controller) requests.delete(auth.fingerprint);
1445
+ if (latestRequests.get(scope.key) === sequence) latestRequests.delete(scope.key);
1446
+ }
1447
+ })();
1448
+ requests.set(auth.fingerprint, { controller, promise });
1449
+ }
1450
+ try {
1451
+ await awaitWithDeadline(promise, signal, timeoutMs, "fetching the Codex model directory");
1452
+ } catch (error) {
1453
+ if (signal.aborted) return;
1454
+ throw error;
1455
+ }
1456
+ const after = capability(ctx.model, scope.key);
1457
+ if (current(scope, ctx, signal) && JSON.stringify(before) !== JSON.stringify(after)) options.onChanged?.(ctx);
1458
+ };
1459
+ return {
1460
+ get(model) {
1461
+ return capability(model);
1462
+ },
1463
+ async read(ctx, signal, refresh = false, force = false) {
1464
+ const resolved = await identify(ctx, signal);
1465
+ if (!resolved) return void 0;
1466
+ const { auth, scope } = resolved;
1467
+ if (refresh) await load(ctx, auth, scope, signal, force || capability(ctx.model, scope.key).kind === "unknown");
1468
+ else void load(ctx, auth, scope, signal, false);
1469
+ return current(scope, ctx, signal) ? capability(ctx.model, scope.key) : void 0;
1470
+ },
1471
+ reset(clearCache = false) {
1472
+ generation += 1;
1473
+ identitySequence += 1;
1474
+ active = void 0;
1475
+ issue = UNKNOWN_CODEX_FAST_CAPABILITY.reason;
1476
+ for (const { controller } of requests.values()) controller.abort();
1477
+ requests.clear();
1478
+ latestRequests.clear();
1479
+ if (clearCache) accounts.clear();
1480
+ }
1481
+ };
1482
+ }
1483
+ function normalizeDirectory(payload) {
1484
+ if (!Array.isArray(payload.models)) throw new Error("The Codex model directory returned invalid models.");
1485
+ const models = /* @__PURE__ */ new Map();
1486
+ for (const item of payload.models) {
1487
+ if (!isRecord3(item) || typeof item.slug !== "string" || !item.slug.trim() || models.has(item.slug)) {
1488
+ throw new Error("The Codex model directory returned an invalid or duplicate model ID.");
1489
+ }
1490
+ const hasTiers = item.service_tiers !== void 0;
1491
+ const hasLegacy = item.additional_speed_tiers !== void 0;
1492
+ 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"))) {
1493
+ throw new Error("The Codex model directory returned invalid service tiers.");
1494
+ }
1495
+ if (!hasTiers && !hasLegacy) {
1496
+ models.set(item.slug, void 0);
1497
+ continue;
1498
+ }
1499
+ const tiers = item.service_tiers ?? [];
1500
+ const legacy = item.additional_speed_tiers ?? [];
1501
+ models.set(item.slug, tiers.some((tier) => tier.id === "priority") || legacy.includes("fast"));
1502
+ }
1503
+ return models;
1504
+ }
1505
+ function modelIdentity2(model) {
1506
+ return model ? `${model.provider}/${model.id}` : "";
1507
+ }
1508
+ function isRecord3(value) {
1509
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1510
+ }
1511
+
1292
1512
  // src/codex-fast-runtime.ts
1293
1513
  var NO_FAST_REQUEST = /* @__PURE__ */ Symbol("no-fast-request");
1294
1514
  var FAST_USAGE_WARNING = "Fast is about 1.5\xD7 faster and uses more of your plan allowance.";
@@ -1296,14 +1516,58 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {})
1296
1516
  let sessionController = new AbortController();
1297
1517
  let generation = 0;
1298
1518
  const pendingFastRequests = /* @__PURE__ */ new Map();
1519
+ const catalog = createCodexFastCatalog({
1520
+ credentialReader: options.credentialReader,
1521
+ candidateReader: options.candidateReader ?? createOAuthCredentialCandidateReader(pi, options.credentialReader),
1522
+ timeoutMs: options.timeoutMs,
1523
+ onChanged(ctx) {
1524
+ if (sessionController.signal.aborted || !settingsRuntime.get().settings.codexFastMode) return;
1525
+ try {
1526
+ refreshStatus(ctx);
1527
+ } catch (error) {
1528
+ if (!isStaleExtensionContextError(error)) throw error;
1529
+ }
1530
+ }
1531
+ });
1299
1532
  const toggle = async (ctx, enabled, callerSignal) => {
1300
1533
  const ownerGeneration = generation;
1301
1534
  const sessionId = ctx.sessionManager.getSessionId();
1302
1535
  const signal = callerSignal ? AbortSignal.any([callerSignal, sessionController.signal]) : sessionController.signal;
1536
+ if (ctx.model?.provider !== "openai-codex") {
1537
+ ctx.ui.notify("/fast is available only for the active OpenAI Codex model.", "warning");
1538
+ return false;
1539
+ }
1540
+ if (!isOfficialCodexModel(ctx.model)) {
1541
+ ctx.ui.notify("Fast mode requires the official OpenAI Codex Responses endpoint.", "warning");
1542
+ return false;
1543
+ }
1544
+ if (settingsRuntime.get().kind === "invalid") {
1545
+ ctx.ui.notify("pi-usage.json is invalid; repair it and run /reload before changing Fast mode.", "error");
1546
+ return false;
1547
+ }
1548
+ if (enabled) {
1549
+ const capability = await catalog.read(ctx, signal, true, catalog.get(ctx.model).kind !== "supported");
1550
+ if (signal.aborted || ownerGeneration !== generation) return false;
1551
+ if (!capability) {
1552
+ const latest = catalog.get(ctx.model);
1553
+ ctx.ui.notify(
1554
+ latest.kind === "unknown" ? latest.reason : "The active Codex account changed while checking Fast capability.",
1555
+ "warning"
1556
+ );
1557
+ return false;
1558
+ }
1559
+ const availability = codexFastAvailability(ctx.model, false, capability);
1560
+ if (availability.kind !== "available") {
1561
+ if (availability.kind === "unknown" || availability.kind === "unavailable") {
1562
+ ctx.ui.notify(availability.reason, "warning");
1563
+ }
1564
+ return false;
1565
+ }
1566
+ }
1303
1567
  try {
1304
1568
  await settingsRuntime.update({ codexFastMode: enabled }, signal);
1305
1569
  } catch (error) {
1306
- if (isAbortError(error) || isStaleExtensionContextError(error)) return false;
1570
+ if (isAbortError2(error) || isStaleExtensionContextError(error)) return false;
1307
1571
  ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
1308
1572
  return false;
1309
1573
  }
@@ -1326,26 +1590,18 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {})
1326
1590
  return;
1327
1591
  }
1328
1592
  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);
1593
+ await toggle(ctx, !settingsRuntime.get().settings.codexFastMode);
1343
1594
  }
1344
1595
  });
1596
+ const refreshCapabilities = (ctx, callerSignal, force = false) => {
1597
+ const signal = callerSignal ? AbortSignal.any([callerSignal, sessionController.signal]) : sessionController.signal;
1598
+ return catalog.read(ctx, signal, true, force);
1599
+ };
1345
1600
  const prepareSession = (ctx) => {
1346
1601
  const sessionId = ctx.sessionManager.getSessionId();
1347
1602
  generation += 1;
1348
1603
  sessionController.abort();
1604
+ catalog.reset();
1349
1605
  pendingFastRequests.clear();
1350
1606
  sessionController = new AbortController();
1351
1607
  const ownerGeneration = generation;
@@ -1367,18 +1623,28 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {})
1367
1623
  ctx.ui.notify(`Invalid pi-usage.json; using defaults without overwriting it. ${state.issue}`, "warning");
1368
1624
  }
1369
1625
  refreshStatus(ctx);
1626
+ if (state.kind !== "invalid" && isOfficialCodexModel(ctx.model)) void refreshCapabilities(ctx);
1370
1627
  })();
1371
1628
  };
1372
1629
  if (options.registerSessionStart !== false) {
1373
1630
  pi.on("session_start", async (_event, ctx) => prepareSession(ctx));
1374
1631
  }
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) {
1632
+ pi.on("model_select", (_event, ctx) => {
1633
+ void refreshCapabilities(ctx);
1634
+ });
1635
+ pi.on("before_provider_request", async (event, ctx) => {
1636
+ const ownerGeneration = generation;
1637
+ const model = ctx.model;
1638
+ const enabled = settingsRuntime.get().settings.codexFastMode;
1639
+ const capability = enabled && isOfficialCodexModel(model) ? await catalog.read(ctx, sessionController.signal) ?? UNKNOWN_CODEX_FAST_CAPABILITY : UNKNOWN_CODEX_FAST_CAPABILITY;
1640
+ if (sessionController.signal.aborted || ownerGeneration !== generation) return void 0;
1641
+ const rewritten = rewriteCodexFastPayload(event.payload, model, enabled, capability);
1642
+ const key = activeRequestKey(ctx, model);
1643
+ if (key && model) {
1379
1644
  pendingFastRequests.set(key, {
1380
- fastRequested: isRecord3(rewritten) && rewritten.service_tier === "priority",
1381
- model: ctx.model
1645
+ fastRequested: isRecord4(rewritten) && rewritten.service_tier === "priority",
1646
+ model,
1647
+ capability
1382
1648
  });
1383
1649
  }
1384
1650
  return rewritten;
@@ -1386,32 +1652,43 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {})
1386
1652
  pi.on("message_end", (event, ctx) => {
1387
1653
  const request = consumeFastRequest(ctx, event.message, pendingFastRequests);
1388
1654
  if (request === NO_FAST_REQUEST) return void 0;
1389
- const message = correctCodexFastMessageCost(event.message, request.model, request.fastRequested);
1655
+ const message = correctCodexFastMessageCost(
1656
+ event.message,
1657
+ request.model,
1658
+ request.fastRequested,
1659
+ request.capability
1660
+ );
1390
1661
  return message ? { message } : void 0;
1391
1662
  });
1392
1663
  pi.on("session_shutdown", async () => {
1393
1664
  generation += 1;
1394
1665
  sessionController.abort();
1666
+ catalog.reset(true);
1395
1667
  pendingFastRequests.clear();
1396
1668
  await settingsRuntime.flush();
1397
1669
  });
1398
1670
  return {
1399
1671
  prepareSession,
1672
+ refreshCapabilities,
1400
1673
  availability(model) {
1401
- return codexFastAvailability(model, settingsRuntime.get().settings.codexFastMode);
1674
+ return codexFastAvailability(model, settingsRuntime.get().settings.codexFastMode, catalog.get(model));
1402
1675
  },
1403
1676
  decorateStatus(model, status) {
1404
- return codexFastStatusLabel(status, codexFastIsEffective(model, settingsRuntime.get().settings.codexFastMode));
1677
+ const enabled = settingsRuntime.get().settings.codexFastMode;
1678
+ const capability = catalog.get(model);
1679
+ if (enabled && codexFastAvailability(model, enabled, capability).kind === "unknown" && /^codex(?:\s|$)/u.test(status)) {
1680
+ return `codex (Fast unknown)${status.slice("codex".length)}`;
1681
+ }
1682
+ return codexFastStatusLabel(status, codexFastIsEffective(model, enabled, capability));
1405
1683
  },
1406
1684
  toggle
1407
1685
  };
1408
1686
  }
1409
- function activeRequestKey(ctx) {
1410
- const model = ctx.model;
1687
+ function activeRequestKey(ctx, model = ctx.model) {
1411
1688
  return model ? `${ctx.sessionManager.getSessionId()}:${model.provider}/${model.id}` : void 0;
1412
1689
  }
1413
1690
  function consumeFastRequest(ctx, message, pending) {
1414
- if (!isRecord3(message) || message.role !== "assistant") return NO_FAST_REQUEST;
1691
+ if (!isRecord4(message) || message.role !== "assistant") return NO_FAST_REQUEST;
1415
1692
  const key = messageRequestKey(ctx, message);
1416
1693
  if (!key) return NO_FAST_REQUEST;
1417
1694
  const request = pending.get(key);
@@ -1422,44 +1699,12 @@ function messageRequestKey(ctx, message) {
1422
1699
  if (typeof message.provider !== "string" || typeof message.model !== "string") return void 0;
1423
1700
  return `${ctx.sessionManager.getSessionId()}:${message.provider}/${message.model}`;
1424
1701
  }
1425
- function isRecord3(value) {
1702
+ function isRecord4(value) {
1426
1703
  return typeof value === "object" && value !== null && !Array.isArray(value);
1427
1704
  }
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
1705
  function isAbortError2(error) {
1458
1706
  return error instanceof Error && error.name === "AbortError";
1459
1707
  }
1460
- function isTimeoutError(error) {
1461
- return error instanceof Error && error.name === "TimeoutError";
1462
- }
1463
1708
 
1464
1709
  // src/usage-settings-ui.ts
1465
1710
  import { stripVTControlCharacters } from "node:util";
@@ -1599,13 +1844,13 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
1599
1844
  }
1600
1845
 
1601
1846
  // src/usage.ts
1602
- var CACHE_TTL_MS = 5 * 60 * 1e3;
1847
+ var CACHE_TTL_MS2 = 5 * 60 * 1e3;
1603
1848
  var STATUS_COUNTDOWN_REFRESH_MS = 60 * 1e3;
1604
1849
  var DEFAULT_TIMEOUT_MS = 15e3;
1605
1850
  var COMMAND_CODE_REVALIDATION_RESERVE_MS = 1e3;
1606
1851
  var ALL_PROVIDER_CONCURRENCY = 2;
1607
- var FAILURE_BACKOFF_MS = 3e4;
1608
- var MAX_ACCOUNT_STATES = 32;
1852
+ var FAILURE_BACKOFF_MS2 = 3e4;
1853
+ var MAX_ACCOUNT_STATES2 = 32;
1609
1854
  var STATUS_KEY = "usage";
1610
1855
  var REFRESH_CURRENT = "Refresh current usage";
1611
1856
  var VIEW_ANOTHER = "View another configured provider\u2026";
@@ -1622,7 +1867,7 @@ function usageExtension(pi, dependencies = {}) {
1622
1867
  const createRedemptionId = dependencies.createRedemptionId ?? randomUUID2;
1623
1868
  const settingsRuntime = dependencies.settingsRuntime ?? createUsageSettingsRuntime();
1624
1869
  const resolveUsageAuth2 = resolveUsageAuth;
1625
- const cache = new UsageCache(CACHE_TTL_MS);
1870
+ const cache = new UsageCache(CACHE_TTL_MS2);
1626
1871
  const failureBackoff = /* @__PURE__ */ new Map();
1627
1872
  const latestQueries = /* @__PURE__ */ new Map();
1628
1873
  const activeControllers = /* @__PURE__ */ new Set();
@@ -1671,7 +1916,7 @@ function usageExtension(pi, dependencies = {}) {
1671
1916
  statusRefreshTimer = void 0;
1672
1917
  if (!sessionActive || generation !== statusGeneration) return;
1673
1918
  startStatusRefresh(ctx, model, true);
1674
- }, CACHE_TTL_MS);
1919
+ }, CACHE_TTL_MS2);
1675
1920
  statusRefreshTimer.unref?.();
1676
1921
  };
1677
1922
  const publishStatus = (ctx, outcome, model, shouldSchedule) => {
@@ -1754,7 +1999,7 @@ function usageExtension(pi, dependencies = {}) {
1754
1999
  `resolving ${adapter.displayName} runtime auth`
1755
2000
  );
1756
2001
  } catch (error) {
1757
- if (isStaleExtensionContextError(error) || isAbortError2(error)) throw error;
2002
+ if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
1758
2003
  if (adapter.id === "openai") invalidateProviderState(adapter.id);
1759
2004
  const authState = error instanceof UnsupportedOpenAIUsageAuthError ? "unsupported" : adapter.id === "openai" && !isTimeoutError(error) ? "failed" : void 0;
1760
2005
  if (displayState === "current") {
@@ -1903,7 +2148,7 @@ function usageExtension(pi, dependencies = {}) {
1903
2148
  failureBackoff.delete(failureKey);
1904
2149
  querySequence += 1;
1905
2150
  queryId = querySequence;
1906
- setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
2151
+ setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES2);
1907
2152
  const queryTimeoutMs = Math.max(
1908
2153
  1,
1909
2154
  deadlineAt - Date.now() - (adapter.id === "command-code" ? COMMAND_CODE_REVALIDATION_RESERVE_MS : 0)
@@ -1934,7 +2179,7 @@ function usageExtension(pi, dependencies = {}) {
1934
2179
  rememberedTargetId: expectedTargetId
1935
2180
  };
1936
2181
  } catch (error) {
1937
- if (isStaleExtensionContextError(error) || isAbortError2(error)) throw error;
2182
+ if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
1938
2183
  if (retryableAuthChanged && authRetry === 0 && !signal.aborted && !requestContextChanged() && Date.now() < deadlineAt) {
1939
2184
  if (queryId !== void 0 && latestQueries.get(failureKey) === queryId) {
1940
2185
  latestQueries.delete(failureKey);
@@ -1948,7 +2193,7 @@ function usageExtension(pi, dependencies = {}) {
1948
2193
  }
1949
2194
  if (queryId === void 0 || latestQueries.get(failureKey) === queryId) {
1950
2195
  if (adapter.invalidateCacheOnFailure) cache.delete(adapter.id, queryFingerprint);
1951
- setBoundedMap(failureBackoff, failureKey, { until: now + FAILURE_BACKOFF_MS, message }, MAX_ACCOUNT_STATES);
2196
+ setBoundedMap(failureBackoff, failureKey, { until: now + FAILURE_BACKOFF_MS2, message }, MAX_ACCOUNT_STATES2);
1952
2197
  }
1953
2198
  return {
1954
2199
  state: {
@@ -2065,7 +2310,7 @@ function usageExtension(pi, dependencies = {}) {
2065
2310
  };
2066
2311
  const startStatusRefresh = (ctx, model, force) => {
2067
2312
  void refreshCurrentStatus(ctx, model, force).catch((error) => {
2068
- if (isStaleExtensionContextError(error) || isAbortError2(error)) return;
2313
+ if (isStaleExtensionContextError(error) || isAbortError(error)) return;
2069
2314
  const message = errorMessage(error);
2070
2315
  console.error("[pi-usage] refresh failed:", message);
2071
2316
  safeSetStatus(ctx, `usage err: ${message.slice(0, 50)}`);
@@ -2109,7 +2354,7 @@ function usageExtension(pi, dependencies = {}) {
2109
2354
  );
2110
2355
  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
2356
  } catch (error) {
2112
- if (isAbortError2(error) || isStaleExtensionContextError(error)) throw error;
2357
+ if (isAbortError(error) || isStaleExtensionContextError(error)) throw error;
2113
2358
  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
2359
  return unchangedFailure && generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId);
2115
2360
  }
@@ -2125,7 +2370,7 @@ function usageExtension(pi, dependencies = {}) {
2125
2370
  );
2126
2371
  return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId) && auth?.fingerprint === outcome.fingerprint;
2127
2372
  } catch (error) {
2128
- if (isAbortError2(error) || isStaleExtensionContextError(error)) throw error;
2373
+ if (isAbortError(error) || isStaleExtensionContextError(error)) throw error;
2129
2374
  return false;
2130
2375
  }
2131
2376
  };
@@ -2165,14 +2410,26 @@ function usageExtension(pi, dependencies = {}) {
2165
2410
  };
2166
2411
  const showMenu = async (ctx) => {
2167
2412
  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
2413
  const controller = new AbortController();
2174
2414
  activeControllers.add(controller);
2175
2415
  try {
2416
+ if (isOfficialCodexModel(ctx.model)) {
2417
+ const refreshed = await runMenuOperation(
2418
+ ctx,
2419
+ "Checking Codex Fast capability\u2026",
2420
+ controller.signal,
2421
+ async (signal) => {
2422
+ await fastRuntime.refreshCapabilities(ctx, signal);
2423
+ return true;
2424
+ }
2425
+ );
2426
+ if (!refreshed || controller.signal.aborted) return;
2427
+ }
2428
+ statusGeneration += 1;
2429
+ const menuGeneration = statusGeneration;
2430
+ statusController?.abort();
2431
+ statusController = void 0;
2432
+ clearStatusTimers();
2176
2433
  let stableCurrent = await queryStableCurrent(ctx, false, controller, "Checking current usage\u2026");
2177
2434
  if (!stableCurrent) return;
2178
2435
  publishStableCurrent(ctx, stableCurrent);
@@ -2277,7 +2534,7 @@ function usageExtension(pi, dependencies = {}) {
2277
2534
  const fastAvailability = fastRuntime.availability(ctx.model);
2278
2535
  const targetState = actionableTargetState();
2279
2536
  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}`] : [];
2537
+ 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
2538
  return {
2282
2539
  kind: "actions",
2283
2540
  title: "Provider usage",
@@ -2292,10 +2549,10 @@ function usageExtension(pi, dependencies = {}) {
2292
2549
  action: "target"
2293
2550
  }
2294
2551
  ] : [],
2295
- ...fastAvailability.kind === "available" ? [
2552
+ ...fastAvailability.kind === "available" || fastState.settings.codexFastMode && isOfficialCodexModel(ctx.model) ? [
2296
2553
  {
2297
2554
  id: "toggle-fast",
2298
- label: fastAvailability.enabled ? "Turn Fast mode off" : "Turn Fast mode on",
2555
+ label: fastState.settings.codexFastMode ? "Turn Fast mode off" : "Turn Fast mode on",
2299
2556
  description: fastState.kind === "invalid" ? "Repair pi-usage.json and reload before changing Fast mode." : FAST_USAGE_WARNING,
2300
2557
  disabled: fastState.kind === "invalid",
2301
2558
  action: "toggle-fast"
@@ -2424,7 +2681,7 @@ function usageExtension(pi, dependencies = {}) {
2424
2681
  ];
2425
2682
  return { kind: "stay" };
2426
2683
  } catch (error) {
2427
- if (isAbortError2(error) || isStaleExtensionContextError(error)) {
2684
+ if (isAbortError(error) || isStaleExtensionContextError(error)) {
2428
2685
  return { kind: "stay" };
2429
2686
  }
2430
2687
  ctx.ui.notify(`Could not select target: ${errorMessage(error)}`, "error");
@@ -2454,10 +2711,11 @@ function usageExtension(pi, dependencies = {}) {
2454
2711
  },
2455
2712
  "toggle-fast": async () => {
2456
2713
  const availability = fastRuntime.availability(ctx.model);
2457
- if (availability.kind !== "available" || fastState.kind === "invalid") {
2714
+ const enabled = settingsRuntime.get().settings.codexFastMode;
2715
+ if (availability.kind !== "available" && !enabled || fastState.kind === "invalid") {
2458
2716
  return { kind: "rejected" };
2459
2717
  }
2460
- const changed = await fastRuntime.toggle(ctx, !availability.enabled, controller.signal);
2718
+ const changed = await fastRuntime.toggle(ctx, !enabled, controller.signal);
2461
2719
  if (!changed) return { kind: "rejected" };
2462
2720
  fastState = settingsRuntime.get();
2463
2721
  return { kind: "stay" };
@@ -2481,7 +2739,7 @@ function usageExtension(pi, dependencies = {}) {
2481
2739
  try {
2482
2740
  availability = await listCodexResetCredits(auth, signal, DEFAULT_TIMEOUT_MS);
2483
2741
  } catch (error) {
2484
- if (isAbortError2(error) || summaryCount === void 0 || summaryCount <= 0) {
2742
+ if (isAbortError(error) || summaryCount === void 0 || summaryCount <= 0) {
2485
2743
  throw error;
2486
2744
  }
2487
2745
  availability = {
@@ -2514,7 +2772,7 @@ function usageExtension(pi, dependencies = {}) {
2514
2772
  screen: loaded.availability.availableCount > 0 ? "reset-picker" : "reset-result"
2515
2773
  };
2516
2774
  } catch (error) {
2517
- if (isAbortError2(error) || isStaleExtensionContextError(error)) {
2775
+ if (isAbortError(error) || isStaleExtensionContextError(error)) {
2518
2776
  return { kind: "stay" };
2519
2777
  }
2520
2778
  ctx.ui.notify(`Couldn't load usage limit resets: ${errorMessage(error)}`, "error");
@@ -2576,7 +2834,7 @@ function usageExtension(pi, dependencies = {}) {
2576
2834
  }
2577
2835
  return { kind: "to", screen: "reset-result" };
2578
2836
  } catch (error) {
2579
- if (isAbortError2(error) || isStaleExtensionContextError(error)) {
2837
+ if (isAbortError(error) || isStaleExtensionContextError(error)) {
2580
2838
  return { kind: "close" };
2581
2839
  }
2582
2840
  resetFailure = `Couldn't reset usage: ${errorMessage(error)}. Try again with the same request.`;
@@ -2730,7 +2988,7 @@ function usageExtension(pi, dependencies = {}) {
2730
2988
  try {
2731
2989
  await showMenu(ctx);
2732
2990
  } catch (error) {
2733
- if (isStaleExtensionContextError(error) || isAbortError2(error)) return;
2991
+ if (isStaleExtensionContextError(error) || isAbortError(error)) return;
2734
2992
  throw error;
2735
2993
  }
2736
2994
  }
@@ -2776,15 +3034,18 @@ function usageExtension(pi, dependencies = {}) {
2776
3034
  safeSetStatus(ctx, void 0);
2777
3035
  });
2778
3036
  fastRuntime = registerCodexFastMode(pi, settingsRuntime, (ctx) => startStatusRefresh(ctx, ctx.model, false), {
2779
- registerSessionStart: false
3037
+ registerSessionStart: false,
3038
+ credentialReader,
3039
+ candidateReader: credentialCandidates,
3040
+ timeoutMs: DEFAULT_TIMEOUT_MS
2780
3041
  });
2781
3042
  }
2782
3043
  export {
2783
- CODEX_FAST_MODEL_IDS,
2784
3044
  CODEX_FAST_SERVICE_TIER,
2785
3045
  CODEX_STANDARD_SERVICE_TIER,
2786
3046
  DEFAULT_USAGE_SETTINGS,
2787
3047
  SUPPORTED_ADAPTERS,
3048
+ UNKNOWN_CODEX_FAST_CAPABILITY,
2788
3049
  UsageCache,
2789
3050
  XAI_ADAPTER,
2790
3051
  abortError,
@@ -2807,6 +3068,7 @@ export {
2807
3068
  formatUsageReport,
2808
3069
  formatUsageStatusline,
2809
3070
  isBoundedTargetId,
3071
+ isOfficialCodexModel,
2810
3072
  isStaleExtensionContextError,
2811
3073
  listCodexResetCredits,
2812
3074
  listUsageTargets,