@narumitw/pi-usage 0.62.0 → 0.63.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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/openai-companion-auth.ts"],
4
+ "sourcesContent": ["import type { ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport { fingerprintResolvedAuth } from \"./core.js\";\nimport { fallbackOAuthCredentialCandidates, type OAuthCredentialCandidateReader } from \"./oauth-credential-source.js\";\nimport { openAIAuthAccessToken } from \"./providers/openai-chatgpt.js\";\nimport { adapterForProvider, resolveUsageAuth } from \"./query.js\";\nimport type { ResolvedUsageAuth } from \"./types.js\";\n\n// A native app registration is routing metadata, not cryptographic same-account proof.\n// Keep this read-only boundary separate from legacy Codex querying and reset mutation policy.\nexport async function resolveOpenAICompanionAuth(\n ctx: ExtensionContext,\n native: ResolvedUsageAuth,\n salt: Uint8Array,\n credentialReader: (providerId: string) => unknown,\n candidateReader?: OAuthCredentialCandidateReader,\n): Promise<ResolvedUsageAuth> {\n const unavailable = () => ({\n ...native,\n fingerprint: fingerprintResolvedAuth(\n { apiKey: JSON.stringify([native.fingerprint, native.openaiClientId, null]) },\n salt,\n ),\n });\n if (candidateReader?.waitUntilReady && !(await candidateReader.waitUntilReady(ctx, \"openai-codex\"))) {\n throw new Error(\"Companion Codex OAuth credential readiness failed closed.\");\n }\n const adapter = adapterForProvider(\"openai-codex\");\n if (!adapter) throw new Error(\"Companion Codex usage support is unavailable.\");\n const companion = await resolveUsageAuth(ctx, adapter, salt, credentialReader);\n if (!companion) return unavailable();\n if (companion.source !== \"OAuth\")\n throw new Error(\"Companion usage requires /login openai-codex OAuth, not an API key.\");\n // Legacy querying is more permissive. Enforce explicit Authorization removals and duplicates\n // against preserved fresh provider auth before using its normalized request headers.\n const access = openAIAuthAccessToken(companion.auth);\n if (ctx.model?.provider === \"openai-codex\") {\n const selected = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);\n if (\n !selected.ok ||\n (selected.baseUrl && new URL(selected.baseUrl).origin !== \"https://chatgpt.com\") ||\n openAIAuthAccessToken(selected) !== access\n ) {\n throw new Error(\"Selected Codex model authorization does not match the companion OAuth credential.\");\n }\n }\n if (!access || access !== openAIAuthAccessToken(companion)) {\n throw new Error(\"Companion Codex runtime authorization did not match its OAuth credential.\");\n }\n const accountId = codexAccountId(access);\n if (!accountId) throw new Error(\"Companion Codex token did not contain a valid account ID.\");\n const offered = candidateReader\n ? await candidateReader(ctx, \"openai-codex\")\n : fallbackOAuthCredentialCandidates(\"openai-codex\", credentialReader);\n if (!offered.ok) throw new Error(\"Companion Codex OAuth credential discovery failed closed.\");\n const matches = new Set<string>();\n for (const candidate of offered.candidates) {\n const credential = object(candidate);\n if (credential?.type !== \"oauth\" || credential.access !== access) continue;\n if (\n credential.accountId !== accountId ||\n typeof credential.refresh !== \"string\" ||\n !credential.refresh ||\n typeof credential.expires !== \"number\" ||\n !Number.isFinite(credential.expires)\n ) {\n throw new Error(\"The matching companion Codex OAuth credential was incomplete or had a different account ID.\");\n }\n matches.add(credential.refresh);\n }\n if (matches.size !== 1) throw new Error(\"Companion Codex OAuth must uniquely match the fresh runtime credential.\");\n const headers = { Authorization: `Bearer ${access}`, \"ChatGPT-Account-Id\": accountId };\n return {\n ...native,\n openaiCompanion: { headers },\n fingerprint: fingerprintResolvedAuth(\n { apiKey: JSON.stringify([native.fingerprint, native.openaiClientId, companion.fingerprint, headers]) },\n salt,\n ),\n secrets: [\n ...new Set([...native.secrets, ...companion.secrets, ...matches, access, accountId, headers.Authorization]),\n ],\n };\n}\n\nfunction codexAccountId(access: string): string | undefined {\n try {\n const parts = access.split(\".\");\n if (parts.length !== 3 || !parts[1]) return undefined;\n const payload = object(JSON.parse(Buffer.from(parts[1], \"base64url\").toString(\"utf8\")));\n const account = object(payload?.[\"https://api.openai.com/auth\"])?.chatgpt_account_id;\n return typeof account === \"string\" && account.length > 0 && account.length <= 512 && /^[\\x21-\\x7e]+$/u.test(account)\n ? account\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction object(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -3,7 +3,7 @@
3
3
  import {
4
4
  CHATGPT_USAGE_SETTINGS_URL,
5
5
  fetchProviderJson
6
- } from "./chunk-WEXCFYZX.ts";
6
+ } from "./chunk-ENCC7IBP.ts";
7
7
 
8
8
  // src/providers/openai-companion-usage.ts
9
9
  var USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
@@ -117,4 +117,4 @@ export {
117
117
  matchingOpenAIAppBuckets,
118
118
  queryOpenAICompanionUsage
119
119
  };
120
- //# sourceMappingURL=openai-companion-usage-Y3WTMUB2.ts.map
120
+ //# sourceMappingURL=openai-companion-usage-4NOBHGP7.ts.map
package/dist/index.ts CHANGED
@@ -45,7 +45,7 @@ import {
45
45
  runWithConcurrency,
46
46
  sanitizeDisplayText,
47
47
  usageAdapters
48
- } from "./chunks/chunk-WEXCFYZX.ts";
48
+ } from "./chunks/chunk-ENCC7IBP.ts";
49
49
 
50
50
  // src/codex-fast.ts
51
51
  import { calculateCost, hasApi } from "@earendil-works/pi-ai";
@@ -422,13 +422,12 @@ function formatUsageReport(report, displayState) {
422
422
  function formatUsageStatusline(report, model, now = Date.now(), showCodexResetCountdown = true, codexStatusPercentage = "remaining") {
423
423
  if (report.providerId === "openai" && report.source === "openai-chatgpt-auth") return "chatgpt usage: web only";
424
424
  if (report.source === "openai-chatgpt-companion") {
425
- return `chatgpt ${["chatgpt-plan", "chatgpt-app"].map((group) => {
426
- const windows = report.buckets.filter((bucket) => bucket.groupId === group).map((bucket) => {
427
- const reset = formatResetCountdown(bucket.resetsAt, now);
428
- return `${bucket.remaining === void 0 ? "unavailable" : `${bucket.remaining.toFixed(0)}%`} ${reset ? `\u21BB ${reset}` : formatWindowLabel(bucket.windowMinutes, "weekly", true)}`;
429
- });
430
- return `${group === "chatgpt-plan" ? "plan" : "app"} ${windows.join(" ")}`;
431
- }).join(" \xB7 ")}`;
425
+ const windows = report.buckets.filter((bucket) => bucket.groupId === "chatgpt-plan").map((bucket) => {
426
+ const reset = formatResetCountdown(bucket.resetsAt, now);
427
+ const window = reset ? `\u21BB ${reset}` : formatWindowLabel(bucket.windowMinutes, "weekly", true);
428
+ return `${bucket.remaining === void 0 ? "unavailable" : `${bucket.remaining.toFixed(0)}%`} ${window}`;
429
+ });
430
+ return `chatgpt plan ${windows.join(" ")}`;
432
431
  }
433
432
  if (report.providerId === "baseten") return formatBasetenStatusline(report);
434
433
  if (report.providerId === "openai-codex") {
@@ -840,9 +839,8 @@ function formatOpenAICompanionReport(lines, report) {
840
839
  for (const group of ["chatgpt-plan", "chatgpt-app"]) {
841
840
  lines.push(group === "chatgpt-plan" ? "Plan limits:" : "App limits:");
842
841
  for (const bucket of report.buckets.filter((bucket2) => bucket2.groupId === group)) {
843
- lines.push(
844
- `${formatWindowLabel(bucket.windowMinutes, "weekly", false).padEnd(VALUE_COLUMN)}${formatPercentBucket(bucket)}`
845
- );
842
+ const value = group === "chatgpt-app" ? bucket.resetsAt === void 0 ? "Reset time unavailable" : `resets ${formatReset(bucket.resetsAt)}` : formatPercentBucket(bucket);
843
+ lines.push(`${formatWindowLabel(bucket.windowMinutes, "weekly", false).padEnd(VALUE_COLUMN)}${value}`);
846
844
  }
847
845
  }
848
846
  for (const metric of report.metrics)
@@ -1000,7 +998,6 @@ import { getAgentDir } from "@earendil-works/pi-coding-agent";
1000
998
  var USAGE_SETTINGS_FILE = "pi-usage.json";
1001
999
  var MAX_USAGE_SETTINGS_BYTES = 64 * 1024;
1002
1000
  var DEFAULT_USAGE_SETTINGS = Object.freeze({
1003
- openaiCompanionUsage: false,
1004
1001
  codexFastMode: false,
1005
1002
  codexStatusResetCountdown: true,
1006
1003
  codexStatusPercentage: "remaining",
@@ -1011,7 +1008,6 @@ function usageSettingsPath() {
1011
1008
  }
1012
1009
  function normalizeUsageSettings(value) {
1013
1010
  if (!isRecord2(value)) return void 0;
1014
- if (Object.hasOwn(value, "openaiCompanionUsage") && typeof value.openaiCompanionUsage !== "boolean") return void 0;
1015
1011
  if (Object.hasOwn(value, "codexFastMode") && typeof value.codexFastMode !== "boolean") {
1016
1012
  return void 0;
1017
1013
  }
@@ -1031,7 +1027,6 @@ function normalizeUsageSettings(value) {
1031
1027
  effectiveTargets.fireworks = value.fireworksAccountId;
1032
1028
  }
1033
1029
  return {
1034
- openaiCompanionUsage: value.openaiCompanionUsage === true,
1035
1030
  codexFastMode: typeof value.codexFastMode === "boolean" ? value.codexFastMode : DEFAULT_USAGE_SETTINGS.codexFastMode,
1036
1031
  codexStatusResetCountdown: typeof value.codexStatusResetCountdown === "boolean" ? value.codexStatusResetCountdown : DEFAULT_USAGE_SETTINGS.codexStatusResetCountdown,
1037
1032
  codexStatusPercentage: value.codexStatusPercentage === "used" ? "used" : DEFAULT_USAGE_SETTINGS.codexStatusPercentage,
@@ -1451,156 +1446,121 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
1451
1446
  const { HorizontalRule, renderBoundedFrame } = await import("@narumitw/pi-tui-kit");
1452
1447
  if (parentSignal.aborted || !isCurrent()) return false;
1453
1448
  let changed = false;
1454
- let selectedId;
1455
- for (; ; ) {
1456
- const result = await ctx.ui.custom((tui, theme, _keybindings, done) => {
1457
- const localController = new AbortController();
1458
- const signal = AbortSignal.any([parentSignal, localController.signal]);
1459
- let closing = false;
1460
- let saveQueue = Promise.resolve();
1461
- const state = settingsRuntime.get();
1462
- const items = [
1463
- {
1464
- id: "codexFastMode",
1465
- label: "Codex Fast mode",
1466
- description: "Use faster Codex routing at increased plan allowance consumption.",
1467
- currentValue: state.settings.codexFastMode ? ON : OFF,
1468
- values: [OFF, ON]
1469
- },
1470
- {
1471
- id: "codexStatusResetCountdown",
1472
- label: "Codex reset countdown",
1473
- description: "Show time remaining until each Codex usage limit resets.",
1474
- currentValue: state.settings.codexStatusResetCountdown ? ON : OFF,
1475
- values: [OFF, ON]
1476
- },
1477
- {
1478
- id: "codexStatusPercentage",
1479
- label: "Codex percentage",
1480
- description: "Show remaining or used Codex quota in the statusline.",
1481
- currentValue: settingValueLabel("codexStatusPercentage", state.settings.codexStatusPercentage),
1482
- values: [REMAINING, USED]
1483
- },
1484
- {
1485
- id: "openaiCompanionUsage",
1486
- label: "Experimental ChatGPT companion usage",
1487
- description: "Read undocumented plan/app usage using a same-account Codex login; matching registration does not prove identity.",
1488
- currentValue: state.settings.openaiCompanionUsage ? ON : OFF,
1489
- values: [OFF, ON]
1490
- }
1491
- ];
1492
- const rule = new HorizontalRule({ ruleStyle: (text) => theme.fg("border", text) });
1493
- let settingsList;
1494
- const close = (result2) => {
1495
- if (closing) return;
1496
- closing = true;
1497
- localController.abort();
1498
- done(result2);
1499
- };
1500
- const cancel = () => close(changed);
1501
- const queueUpdate = (id, requested, display) => {
1502
- saveQueue = saveQueue.then(async () => {
1503
- if (signal.aborted || !isCurrent()) return;
1504
- const previous = settingsRuntime.get().settings[id];
1505
- if (settingsRuntime.get().kind === "invalid") {
1506
- settingsList.updateValue(id, settingValueLabel(id, previous));
1507
- if (!signal.aborted && isCurrent()) {
1508
- ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
1509
- tui.requestRender();
1510
- }
1511
- return;
1512
- }
1513
- try {
1514
- if (id === "openaiCompanionUsage" && requested === true && previous !== true) {
1515
- selectedId = id;
1516
- close("consent");
1517
- return;
1518
- }
1519
- const patch = {};
1520
- patch[id] = requested;
1521
- await settingsRuntime.update(patch, signal);
1522
- } catch (error) {
1523
- if (signal.aborted || !isCurrent()) return;
1524
- settingsList.updateValue(id, settingValueLabel(id, previous));
1525
- ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
1449
+ return ctx.ui.custom((tui, theme, _keybindings, done) => {
1450
+ const localController = new AbortController();
1451
+ const signal = AbortSignal.any([parentSignal, localController.signal]);
1452
+ let closing = false;
1453
+ let saveQueue = Promise.resolve();
1454
+ const state = settingsRuntime.get();
1455
+ const items = [
1456
+ {
1457
+ id: "codexFastMode",
1458
+ label: "Codex Fast mode",
1459
+ description: "Use faster Codex routing at increased plan allowance consumption.",
1460
+ currentValue: state.settings.codexFastMode ? ON : OFF,
1461
+ values: [OFF, ON]
1462
+ },
1463
+ {
1464
+ id: "codexStatusResetCountdown",
1465
+ label: "Codex reset countdown",
1466
+ description: "Show time remaining until each Codex usage limit resets.",
1467
+ currentValue: state.settings.codexStatusResetCountdown ? ON : OFF,
1468
+ values: [OFF, ON]
1469
+ },
1470
+ {
1471
+ id: "codexStatusPercentage",
1472
+ label: "Codex percentage",
1473
+ description: "Show remaining or used Codex quota in the statusline.",
1474
+ currentValue: settingValueLabel("codexStatusPercentage", state.settings.codexStatusPercentage),
1475
+ values: [REMAINING, USED]
1476
+ }
1477
+ ];
1478
+ const rule = new HorizontalRule({ ruleStyle: (text) => theme.fg("border", text) });
1479
+ let settingsList;
1480
+ const close = (result) => {
1481
+ if (closing) return;
1482
+ closing = true;
1483
+ localController.abort();
1484
+ done(result);
1485
+ };
1486
+ const cancel = () => close(changed);
1487
+ const queueUpdate = (id, requested, display) => {
1488
+ saveQueue = saveQueue.then(async () => {
1489
+ if (signal.aborted || !isCurrent()) return;
1490
+ const previous = settingsRuntime.get().settings[id];
1491
+ if (settingsRuntime.get().kind === "invalid") {
1492
+ settingsList.updateValue(id, settingValueLabel(id, previous));
1493
+ if (!signal.aborted && isCurrent()) {
1494
+ ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
1526
1495
  tui.requestRender();
1527
- return;
1528
- }
1529
- if (previous !== requested) {
1530
- changed = true;
1531
- onApplied(id);
1532
1496
  }
1497
+ return;
1498
+ }
1499
+ try {
1500
+ const patch = {};
1501
+ patch[id] = requested;
1502
+ await settingsRuntime.update(patch, signal);
1503
+ } catch (error) {
1533
1504
  if (signal.aborted || !isCurrent()) return;
1534
- settingsList.updateValue(id, display);
1535
- tui.requestRender();
1536
- });
1537
- };
1538
- settingsList = new SettingsList(
1539
- items,
1540
- items.length + 2,
1541
- getSettingsListTheme(),
1542
- (id, value) => {
1543
- if (closing || signal.aborted || !isCurrent()) return;
1544
- selectedId = id;
1545
- if (id === "codexStatusPercentage") {
1546
- queueUpdate(id, value === USED ? "used" : "remaining", value);
1547
- } else if (id === "codexFastMode" || id === "codexStatusResetCountdown" || id === "openaiCompanionUsage") {
1548
- queueUpdate(id, value === ON, value);
1549
- }
1550
- },
1551
- cancel
1552
- );
1553
- if (selectedId) settingsList.selectItem(selectedId);
1554
- parentSignal.addEventListener("abort", cancel, { once: true });
1555
- return {
1556
- render(width) {
1557
- const title = new Text(theme.fg("accent", theme.bold("pi-usage Settings")), 1, 0).render(width);
1558
- const content = settingsList.render(width);
1559
- const focusedRow = content.findIndex((line) => /^[→›]\s/u.test(stripVTControlCharacters(line)));
1560
- const terminalRows = Number.isFinite(tui.terminal?.rows) ? Math.floor(tui.terminal.rows) : 24;
1561
- const [ruleLine = ""] = rule.render(width);
1562
- return renderBoundedFrame({
1563
- width,
1564
- maxRows: Math.max(1, terminalRows - 3),
1565
- rule: ruleLine,
1566
- title,
1567
- content,
1568
- priorityRows: focusedRow < 0 ? [] : [focusedRow],
1569
- focusedRow
1570
- });
1571
- },
1572
- invalidate: () => settingsList.invalidate(),
1573
- handleInput(data) {
1574
- if (closing) return;
1575
- if (matchesKey(data, Key.ctrl("c"))) cancel();
1576
- else settingsList.handleInput(data);
1505
+ settingsList.updateValue(id, settingValueLabel(id, previous));
1506
+ ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
1577
1507
  tui.requestRender();
1578
- },
1579
- dispose() {
1580
- localController.abort();
1581
- parentSignal.removeEventListener("abort", cancel);
1508
+ return;
1582
1509
  }
1583
- };
1584
- });
1585
- if (result !== "consent" || parentSignal.aborted || !isCurrent()) return changed;
1586
- try {
1587
- const accepted = await ctx.ui.confirm(
1588
- "Enable experimental ChatGPT companion usage?",
1589
- "Requires /login openai-codex with the same ChatGPT account/workspace as native OpenAI. Only the companion token and its matching account ID are sent to undocumented ChatGPT usage endpoints. Registration matching is not independent proof of the same user/workspace. No reset or allowance mutations are performed.",
1590
- { signal: parentSignal }
1591
- );
1592
- if (parentSignal.aborted || !isCurrent()) return changed;
1593
- if (accepted && !settingsRuntime.get().settings.openaiCompanionUsage) {
1594
- await settingsRuntime.update({ openaiCompanionUsage: true }, parentSignal);
1595
- changed = true;
1596
- onApplied("openaiCompanionUsage");
1510
+ if (previous !== requested) {
1511
+ changed = true;
1512
+ onApplied(id);
1513
+ }
1514
+ if (signal.aborted || !isCurrent()) return;
1515
+ settingsList.updateValue(id, display);
1516
+ tui.requestRender();
1517
+ });
1518
+ };
1519
+ settingsList = new SettingsList(
1520
+ items,
1521
+ items.length + 2,
1522
+ getSettingsListTheme(),
1523
+ (id, value) => {
1524
+ if (closing || signal.aborted || !isCurrent()) return;
1525
+ if (id === "codexStatusPercentage") {
1526
+ queueUpdate(id, value === USED ? "used" : "remaining", value);
1527
+ } else if (id === "codexFastMode" || id === "codexStatusResetCountdown") {
1528
+ queueUpdate(id, value === ON, value);
1529
+ }
1530
+ },
1531
+ cancel
1532
+ );
1533
+ parentSignal.addEventListener("abort", cancel, { once: true });
1534
+ return {
1535
+ render(width) {
1536
+ const title = new Text(theme.fg("accent", theme.bold("pi-usage Settings")), 1, 0).render(width);
1537
+ const content = settingsList.render(width);
1538
+ const focusedRow = content.findIndex((line) => /^[→›]\s/u.test(stripVTControlCharacters(line)));
1539
+ const terminalRows = Number.isFinite(tui.terminal?.rows) ? Math.floor(tui.terminal.rows) : 24;
1540
+ const [ruleLine = ""] = rule.render(width);
1541
+ return renderBoundedFrame({
1542
+ width,
1543
+ maxRows: Math.max(1, terminalRows - 3),
1544
+ rule: ruleLine,
1545
+ title,
1546
+ content,
1547
+ priorityRows: focusedRow < 0 ? [] : [focusedRow],
1548
+ focusedRow
1549
+ });
1550
+ },
1551
+ invalidate: () => settingsList.invalidate(),
1552
+ handleInput(data) {
1553
+ if (closing) return;
1554
+ if (matchesKey(data, Key.ctrl("c"))) cancel();
1555
+ else settingsList.handleInput(data);
1556
+ tui.requestRender();
1557
+ },
1558
+ dispose() {
1559
+ localController.abort();
1560
+ parentSignal.removeEventListener("abort", cancel);
1597
1561
  }
1598
- } catch (error) {
1599
- if (parentSignal.aborted || !isCurrent()) return changed;
1600
- ctx.ui.notify(`Could not enable ChatGPT companion usage: ${errorMessage(error)}`, "error");
1601
- }
1602
- if (parentSignal.aborted || !isCurrent()) return changed;
1603
- }
1562
+ };
1563
+ });
1604
1564
  }
1605
1565
 
1606
1566
  // src/usage.ts
@@ -1625,14 +1585,7 @@ function usageExtension(pi, dependencies = {}) {
1625
1585
  const credentialCandidates = createOAuthCredentialCandidateReader(pi, credentialReader);
1626
1586
  const createRedemptionId = dependencies.createRedemptionId ?? randomUUID2;
1627
1587
  const settingsRuntime = dependencies.settingsRuntime ?? createUsageSettingsRuntime();
1628
- const resolveUsageAuth2 = (ctx, adapter, salt, reader, candidates) => resolveUsageAuth(
1629
- ctx,
1630
- adapter,
1631
- salt,
1632
- reader,
1633
- candidates,
1634
- settingsRuntime.get().settings.openaiCompanionUsage
1635
- );
1588
+ const resolveUsageAuth2 = resolveUsageAuth;
1636
1589
  const cache = new UsageCache(CACHE_TTL_MS);
1637
1590
  const failureBackoff = /* @__PURE__ */ new Map();
1638
1591
  const latestQueries = /* @__PURE__ */ new Map();
@@ -1754,7 +1707,6 @@ function usageExtension(pi, dependencies = {}) {
1754
1707
  const expectedSessionId = ctx.sessionManager.getSessionId();
1755
1708
  const expectedModelIdentity = modelIdentity(ctx.model);
1756
1709
  const expectedTargetId = adapter.targets ? settingsRuntime.get().settings.selectedTargets[adapter.id] : void 0;
1757
- const expectedCompanionUsage = settingsRuntime.get().settings.openaiCompanionUsage;
1758
1710
  const providerName = providerDisplayName(ctx, adapter.id);
1759
1711
  let auth;
1760
1712
  try {
@@ -1795,7 +1747,7 @@ function usageExtension(pi, dependencies = {}) {
1795
1747
  "zai",
1796
1748
  "zai-coding-cn"
1797
1749
  ].includes(adapter.id);
1798
- const requestContextChanged = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity || adapter.id === "openai" && settingsRuntime.get().settings.openaiCompanionUsage !== expectedCompanionUsage || adapter.targets !== void 0 && settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedTargetId;
1750
+ const requestContextChanged = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity || adapter.targets !== void 0 && settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedTargetId;
1799
1751
  if (requiresRequestBoundaryGuard && requestContextChanged()) throw abortError();
1800
1752
  if (!auth) {
1801
1753
  if (adapter.id === "openai") invalidateProviderState(adapter.id);
@@ -2444,16 +2396,6 @@ function usageExtension(pi, dependencies = {}) {
2444
2396
  controller.signal,
2445
2397
  () => statusGeneration === menuGeneration && !controller.signal.aborted,
2446
2398
  (id) => {
2447
- if (id === "openaiCompanionUsage" && statusGeneration === menuGeneration && !controller.signal.aborted) {
2448
- for (const active of nativeQueryControllers) active.abort();
2449
- invalidateProviderState("openai");
2450
- if (ctx.model?.provider === "openai") {
2451
- statusController?.abort();
2452
- statusController = void 0;
2453
- clearStatusTimers();
2454
- safeSetStatus(ctx, void 0);
2455
- }
2456
- }
2457
2399
  if ((id === "codexStatusResetCountdown" || id === "codexStatusPercentage") && stableCurrent && statusGeneration === menuGeneration && !controller.signal.aborted) {
2458
2400
  publishStableCurrent(ctx, stableCurrent);
2459
2401
  }