lody 0.94.0 → 0.96.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.
@@ -1,11 +1,11 @@
1
1
  import { createRequire } from "module";
2
- import Tw__default, { execFile as execFile$1, spawn } from "node:child_process";
2
+ import ew__default, { execFile as execFile$1, spawn } from "node:child_process";
3
3
  import { createRequire as createRequire$1 } from "node:module";
4
4
  import { Writable, Readable } from "node:stream";
5
5
  import { StringDecoder } from "node:string_decoder";
6
6
  import fs__default from "node:fs";
7
7
  import path__default from "node:path";
8
- import Xt__default, { constants, readFile, realpath } from "node:fs/promises";
8
+ import Qt__default, { constants, readFile, realpath } from "node:fs/promises";
9
9
  import { randomUUID, createHash } from "node:crypto";
10
10
  import process$1 from "node:process";
11
11
  import { fileURLToPath } from "node:url";
@@ -4240,9 +4240,9 @@ function $constructor(name, initializer3, params) {
4240
4240
  inst._zod.traits.add(name);
4241
4241
  initializer3(inst, def);
4242
4242
  const proto = _.prototype;
4243
- const keys = Object.keys(proto);
4244
- for (let i = 0; i < keys.length; i++) {
4245
- const k = keys[i];
4243
+ const keys2 = Object.keys(proto);
4244
+ for (let i = 0; i < keys2.length; i++) {
4245
+ const k = keys2[i];
4246
4246
  if (!(k in inst)) {
4247
4247
  inst[k] = proto[k].bind(inst);
4248
4248
  }
@@ -4467,12 +4467,12 @@ function getElementAtPath(obj, path10) {
4467
4467
  return path10.reduce((acc, key) => acc?.[key], obj);
4468
4468
  }
4469
4469
  function promiseAllObject(promisesObj) {
4470
- const keys = Object.keys(promisesObj);
4471
- const promises = keys.map((key) => promisesObj[key]);
4470
+ const keys2 = Object.keys(promisesObj);
4471
+ const promises = keys2.map((key) => promisesObj[key]);
4472
4472
  return Promise.all(promises).then((results) => {
4473
4473
  const resolvedObj = {};
4474
- for (let i = 0; i < keys.length; i++) {
4475
- resolvedObj[keys[i]] = results[i];
4474
+ for (let i = 0; i < keys2.length; i++) {
4475
+ resolvedObj[keys2[i]] = results[i];
4476
4476
  }
4477
4477
  return resolvedObj;
4478
4478
  });
@@ -6583,8 +6583,8 @@ function handlePropertyResult(result, final, key, input, isOptionalOut) {
6583
6583
  }
6584
6584
  }
6585
6585
  function normalizeDef(def) {
6586
- const keys = Object.keys(def.shape);
6587
- for (const k of keys) {
6586
+ const keys2 = Object.keys(def.shape);
6587
+ for (const k of keys2) {
6588
6588
  if (!def.shape?.[k]?._zod?.traits?.has("$ZodType")) {
6589
6589
  throw new Error(`Invalid element at key "${k}": expected a Zod schema`);
6590
6590
  }
@@ -6592,9 +6592,9 @@ function normalizeDef(def) {
6592
6592
  const okeys = optionalKeys(def.shape);
6593
6593
  return {
6594
6594
  ...def,
6595
- keys,
6596
- keySet: new Set(keys),
6597
- numKeys: keys.length,
6595
+ keys: keys2,
6596
+ keySet: new Set(keys2),
6597
+ numKeys: keys2.length,
6598
6598
  optionalKeys: new Set(okeys)
6599
6599
  };
6600
6600
  }
@@ -16411,11 +16411,11 @@ var ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
16411
16411
  inst._zod.processJSONSchema = (ctx, json2, params) => enumProcessor(inst, ctx, json2);
16412
16412
  inst.enum = def.entries;
16413
16413
  inst.options = Object.values(def.entries);
16414
- const keys = new Set(Object.keys(def.entries));
16414
+ const keys2 = new Set(Object.keys(def.entries));
16415
16415
  inst.extract = (values, params) => {
16416
16416
  const newEntries = {};
16417
16417
  for (const value of values) {
16418
- if (keys.has(value)) {
16418
+ if (keys2.has(value)) {
16419
16419
  newEntries[value] = def.entries[value];
16420
16420
  } else
16421
16421
  throw new Error(`Key ${value} not found in enum`);
@@ -16430,7 +16430,7 @@ var ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
16430
16430
  inst.exclude = (values, params) => {
16431
16431
  const newEntries = { ...def.entries };
16432
16432
  for (const value of values) {
16433
- if (keys.has(value)) {
16433
+ if (keys2.has(value)) {
16434
16434
  delete newEntries[value];
16435
16435
  } else
16436
16436
  throw new Error(`Key ${value} not found in enum`);
@@ -21253,8 +21253,7 @@ function toPromptUsage(tokenCount) {
21253
21253
  thoughtTokens: tokenCount.reasoningOutputTokens
21254
21254
  };
21255
21255
  }
21256
- var CODEX_UNATTRIBUTED_MODEL = "codex:unattributed";
21257
- var counters = [
21256
+ var keys = [
21258
21257
  "inputTokens",
21259
21258
  "outputTokens",
21260
21259
  "cacheReadInputTokens",
@@ -21268,39 +21267,51 @@ var empty = () => ({
21268
21267
  cacheCreationInputTokens: 0,
21269
21268
  reasoningOutputTokens: 0
21270
21269
  });
21271
- function normalizeCodexUsage(raw) {
21270
+ function normalize(total) {
21272
21271
  return {
21273
- inputTokens: Math.max(0, raw.inputTokens - raw.cachedInputTokens - raw.cacheWriteInputTokens),
21274
- outputTokens: Math.max(0, raw.outputTokens - raw.reasoningOutputTokens),
21275
- cacheReadInputTokens: raw.cachedInputTokens,
21276
- cacheCreationInputTokens: raw.cacheWriteInputTokens,
21277
- reasoningOutputTokens: raw.reasoningOutputTokens
21278
- };
21279
- }
21280
- var CodexUsageAccounting = class {
21281
- offset = empty();
21282
- total = empty();
21283
- atReset = false;
21284
- update(sessionId, params) {
21285
- const { total: raw, modelContextWindow } = params.tokenUsage;
21286
- const reset = modelContextWindow !== null && modelContextWindow > 0 && raw.totalTokens === modelContextWindow && raw.inputTokens === 0 && raw.outputTokens === 0 && raw.cachedInputTokens === 0 && raw.cacheWriteInputTokens === 0 && raw.reasoningOutputTokens === 0;
21287
- if (reset && !this.atReset) this.offset = { ...this.total };
21288
- this.atReset = reset;
21289
- const normalized = normalizeCodexUsage(raw);
21290
- const next = empty();
21291
- const difference = empty();
21292
- for (const key of counters) {
21293
- next[key] = Math.max(this.total[key] ?? 0, (this.offset[key] ?? 0) + (normalized[key] ?? 0));
21294
- difference[key] = (next[key] ?? 0) - (this.total[key] ?? 0);
21295
- }
21296
- this.total = next;
21297
- const usage = { ...next };
21298
- if (modelContextWindow !== null) usage.contextWindow = modelContextWindow;
21272
+ inputTokens: Math.max(0, total.inputTokens - total.cachedInputTokens - total.cacheWriteInputTokens),
21273
+ outputTokens: Math.max(0, total.outputTokens - total.reasoningOutputTokens),
21274
+ cacheReadInputTokens: total.cachedInputTokens,
21275
+ cacheCreationInputTokens: total.cacheWriteInputTokens,
21276
+ reasoningOutputTokens: total.reasoningOutputTokens
21277
+ };
21278
+ }
21279
+ var CodexTurnUsage = class {
21280
+ previous;
21281
+ turn;
21282
+ requestedModel;
21283
+ constructor(fresh = false, snapshot) {
21284
+ this.previous = snapshot ? normalize(snapshot) : fresh ? empty() : void 0;
21285
+ }
21286
+ prepare(model) {
21287
+ this.requestedModel = model;
21288
+ }
21289
+ start(turnId, model) {
21290
+ if (this.turn?.id === turnId) return;
21291
+ this.turn = { id: turnId, model: this.requestedModel ?? model, usage: empty() };
21292
+ }
21293
+ update(params) {
21294
+ const next = normalize(params.tokenUsage.total);
21295
+ const turn = this.turn;
21296
+ if (!turn) {
21297
+ this.previous = next;
21298
+ return void 0;
21299
+ }
21300
+ if (params.turnId !== turn.id) return void 0;
21301
+ if (!this.previous) {
21302
+ this.previous = next;
21303
+ return void 0;
21304
+ }
21305
+ const delta = empty();
21306
+ for (const key of keys) delta[key] = Math.max(0, (next[key] ?? 0) - (this.previous[key] ?? 0));
21307
+ this.previous = next;
21308
+ if (!keys.some((key) => (delta[key] ?? 0) > 0)) return void 0;
21309
+ for (const key of keys) turn.usage[key] = (turn.usage[key] ?? 0) + (delta[key] ?? 0);
21299
21310
  return {
21300
- sessionId,
21301
- usage,
21302
- modelUsage: { [CODEX_UNATTRIBUTED_MODEL]: { ...next } },
21303
- delta: { usage: { ...difference }, modelUsage: { [CODEX_UNATTRIBUTED_MODEL]: { ...difference } } }
21311
+ sessionId: params.threadId,
21312
+ usage: { ...turn.usage, ...params.tokenUsage.modelContextWindow !== null && { contextWindow: params.tokenUsage.modelContextWindow } },
21313
+ modelUsage: { [turn.model]: { ...turn.usage } },
21314
+ _meta: { codex: { usageTurnId: turn.id } }
21304
21315
  };
21305
21316
  }
21306
21317
  };
@@ -23646,6 +23657,13 @@ var CodexEventHandler = class _CodexEventHandler {
23646
23657
  case "error":
23647
23658
  return await this.createErrorEvent(notification.params);
23648
23659
  case "turn/started":
23660
+ if (notification.params.threadId === this.sessionState.sessionId) {
23661
+ this.sessionState.turnUsage ??= new CodexTurnUsage();
23662
+ this.sessionState.turnUsage.start(
23663
+ notification.params.turn.id,
23664
+ this.sessionState.currentModelId.replace(/\[.*?]$/, "")
23665
+ );
23666
+ }
23649
23667
  this.sessionState.currentTurnId = notification.params.turn.id;
23650
23668
  await this.flushPendingErrors();
23651
23669
  return null;
@@ -23784,25 +23802,16 @@ var CodexEventHandler = class _CodexEventHandler {
23784
23802
  }
23785
23803
  }
23786
23804
  async emitExtNotification(notification) {
23787
- switch (notification.method) {
23788
- case "thread/tokenUsage/updated":
23789
- await this.notifyExt(
23790
- ACP_EXT_SESSION_USAGE_UPDATE_METHOD,
23791
- this.createSessionUsageExtNotification(notification.params)
23792
- );
23793
- return;
23794
- default:
23795
- return;
23805
+ if (notification.method === "thread/tokenUsage/updated" && notification.params.threadId === this.sessionState.sessionId) {
23806
+ this.sessionState.turnUsage ??= new CodexTurnUsage();
23807
+ const update = this.sessionState.turnUsage.update(notification.params);
23808
+ if (update) await this.notifyExt(ACP_EXT_SESSION_USAGE_UPDATE_METHOD, update);
23796
23809
  }
23797
23810
  }
23798
23811
  async notifyExt(method, params) {
23799
23812
  const extMethod = method.startsWith("_") ? method : `_${method}`;
23800
23813
  await this.connection.notify(extMethod, params);
23801
23814
  }
23802
- createSessionUsageExtNotification(params) {
23803
- this.sessionState.usageAccounting ??= new CodexUsageAccounting();
23804
- return this.sessionState.usageAccounting.update(this.sessionState.sessionId, params);
23805
- }
23806
23815
  createSessionRateLimitsExtNotification(rateLimits) {
23807
23816
  return {
23808
23817
  rateLimits: [toLodyRateLimit(rateLimits)],
@@ -25915,7 +25924,7 @@ var isWsl = () => {
25915
25924
  return false;
25916
25925
  };
25917
25926
  var is_wsl_default = process$1.env.__IS_WSL_TEST__ ? isWsl : isWsl();
25918
- var execFile = promisify(Tw__default.execFile);
25927
+ var execFile = promisify(ew__default.execFile);
25919
25928
  var powerShellPath = () => `${process$1.env.SYSTEMROOT || process$1.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
25920
25929
  var executePowerShell = async (command, options = {}) => {
25921
25930
  const {
@@ -25956,7 +25965,7 @@ function parseMountPointFromConfig(content) {
25956
25965
  return match.groups.mountPoint.trim().replaceAll(/^["']|["']$/g, "");
25957
25966
  }
25958
25967
  }
25959
- var execFile2 = promisify(Tw__default.execFile);
25968
+ var execFile2 = promisify(ew__default.execFile);
25960
25969
  var wslDrivesMountPoint = /* @__PURE__ */ (() => {
25961
25970
  const defaultMountPoint = "/mnt/";
25962
25971
  let mountPoint;
@@ -25967,14 +25976,14 @@ var wslDrivesMountPoint = /* @__PURE__ */ (() => {
25967
25976
  const configFilePath = "/etc/wsl.conf";
25968
25977
  let isConfigFileExists = false;
25969
25978
  try {
25970
- await Xt__default.access(configFilePath, constants.F_OK);
25979
+ await Qt__default.access(configFilePath, constants.F_OK);
25971
25980
  isConfigFileExists = true;
25972
25981
  } catch {
25973
25982
  }
25974
25983
  if (!isConfigFileExists) {
25975
25984
  return defaultMountPoint;
25976
25985
  }
25977
- const configContent = await Xt__default.readFile(configFilePath, { encoding: "utf8" });
25986
+ const configContent = await Qt__default.readFile(configFilePath, { encoding: "utf8" });
25978
25987
  const parsedMountPoint = parseMountPointFromConfig(configContent);
25979
25988
  if (parsedMountPoint === void 0) {
25980
25989
  return defaultMountPoint;
@@ -25994,7 +26003,7 @@ var canAccessPowerShell = async () => {
25994
26003
  canAccessPowerShellPromise ??= (async () => {
25995
26004
  try {
25996
26005
  const psPath = await powerShellPath2();
25997
- await Xt__default.access(psPath, constants.X_OK);
26006
+ await Qt__default.access(psPath, constants.X_OK);
25998
26007
  return true;
25999
26008
  } catch {
26000
26009
  return false;
@@ -26036,7 +26045,7 @@ var convertWslPathToWindows = async (paths) => {
26036
26045
  }
26037
26046
  return isBatch ? results : results[0];
26038
26047
  };
26039
- var execFile3 = promisify(Tw__default.execFile);
26048
+ var execFile3 = promisify(ew__default.execFile);
26040
26049
  var powerShellPath3 = () => `${process$1.env.SYSTEMROOT || process$1.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
26041
26050
  var argumentsPrefix = [
26042
26051
  "-NoProfile",
@@ -26333,7 +26342,7 @@ var baseOpen = async (options) => {
26333
26342
  const isBundled = !__dirname$1 || __dirname$1 === "/";
26334
26343
  let exeLocalXdgOpen = false;
26335
26344
  try {
26336
- await Xt__default.access(localXdgOpenPath, constants.X_OK);
26345
+ await Qt__default.access(localXdgOpenPath, constants.X_OK);
26337
26346
  exeLocalXdgOpen = true;
26338
26347
  } catch {
26339
26348
  }
@@ -26354,7 +26363,7 @@ var baseOpen = async (options) => {
26354
26363
  if (options.target) {
26355
26364
  cliArguments.push(options.target);
26356
26365
  }
26357
- const subprocess = Tw__default.spawn(command, cliArguments, childProcessOptions);
26366
+ const subprocess = ew__default.spawn(command, cliArguments, childProcessOptions);
26358
26367
  if (options.wait) {
26359
26368
  return new Promise((resolve, reject) => {
26360
26369
  subprocess.once("error", reject);
@@ -26692,7 +26701,7 @@ var package_default = {
26692
26701
  },
26693
26702
  dependencies: {
26694
26703
  "@agentclientprotocol/sdk": "^1.4.0",
26695
- "@openai/codex": "^0.153.4",
26704
+ "@openai/codex": "^0.154.0",
26696
26705
  "acp-extension-core": "0.1.5",
26697
26706
  diff: "^9.0.0",
26698
26707
  open: "^11.0.1",
@@ -28468,17 +28477,22 @@ var CodexAppServerClient = class {
28468
28477
  threadGoalUpdateCaptures = /* @__PURE__ */ new Map();
28469
28478
  threadGoalClearedCaptures = /* @__PURE__ */ new Map();
28470
28479
  threadSettings = /* @__PURE__ */ new Map();
28480
+ tokenUsage = /* @__PURE__ */ new Map();
28471
28481
  staleTurnIds = /* @__PURE__ */ new Map();
28472
28482
  turnCompletionTerminalError = null;
28473
28483
  constructor(connection) {
28474
28484
  this.connection = connection;
28475
- const failPendingTurns = () => this.rejectAllPendingTurnCompletions(
28476
- new Error("Codex process exited before completing the turn")
28477
- );
28485
+ const failPendingTurns = () => {
28486
+ const error48 = new Error("Codex process exited before completing the turn");
28487
+ this.rejectAllPendingTurnCompletions(error48);
28488
+ };
28478
28489
  this.connection.onClose(failPendingTurns);
28479
28490
  this.connection.onDispose(failPendingTurns);
28480
28491
  this.connection.onUnhandledNotification((data) => {
28481
28492
  const serverNotification = data;
28493
+ if (serverNotification.method === "thread/tokenUsage/updated") {
28494
+ this.tokenUsage.set(serverNotification.params.threadId, serverNotification.params);
28495
+ }
28482
28496
  if (isMcpServerStatusUpdatedNotification(serverNotification)) {
28483
28497
  this.mcpServerStartupVersion += 1;
28484
28498
  this.mcpServerStartupStates.set(serverNotification.params.name, {
@@ -28586,6 +28600,7 @@ var CodexAppServerClient = class {
28586
28600
  this.elicitationHandlers.set(threadId, handler);
28587
28601
  }
28588
28602
  clearThreadHandlers(threadId) {
28603
+ this.tokenUsage.delete(threadId);
28589
28604
  this.notificationHandlers.delete(threadId);
28590
28605
  this.approvalHandlers.delete(threadId);
28591
28606
  this.elicitationHandlers.delete(threadId);
@@ -28846,6 +28861,9 @@ var CodexAppServerClient = class {
28846
28861
  async threadProjectUpdate(params) {
28847
28862
  await this.sendRequest({ method: "thread/metadata/update", params });
28848
28863
  }
28864
+ getThreadTokenUsage(threadId) {
28865
+ return this.tokenUsage.get(threadId);
28866
+ }
28849
28867
  async threadStart(params) {
28850
28868
  return await this.sendRequest({ method: "thread/start", params });
28851
28869
  }
@@ -31902,6 +31920,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
31902
31920
  sessionState.cwd,
31903
31921
  () => sessionState.sessionTitleSource
31904
31922
  );
31923
+ if (operation.kind === "new") sessionState.turnUsage = new CodexTurnUsage(true);
31905
31924
  this.installSessionState(sessionState);
31906
31925
  this.publishRateLimitsAsync(sessionState);
31907
31926
  subscribed = false;
@@ -32649,7 +32668,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32649
32668
  throw error48;
32650
32669
  }
32651
32670
  logger.error(`Steering request for session ${params.sessionId} failed`, error48);
32652
- return { outcome: "failed" };
32671
+ throw error48;
32653
32672
  } finally {
32654
32673
  if (queue.isIdle && this.steeringQueues.get(params.sessionId) === queue) {
32655
32674
  this.steeringQueues.delete(params.sessionId);
@@ -32686,7 +32705,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32686
32705
  this.assertSteerInputSupported(params, sessionState);
32687
32706
  const turnId = await this.getSteerableTurnId(sessionState);
32688
32707
  if (turnId) {
32689
- const injected = await this.injectSteerIntoActiveTurn(params, turnId, sessionState);
32708
+ const injected = await this.injectSteerIntoActiveTurn(params, turnId);
32690
32709
  if (injected) {
32691
32710
  logger.log("Steering session injected", { sessionId: params.sessionId, turnId });
32692
32711
  return { outcome: "injected" };
@@ -32707,15 +32726,10 @@ Check ${configPath} and project .codex directories, especially their config.toml
32707
32726
  /**
32708
32727
  * Attempts to inject the prompt into the given running turn.
32709
32728
  *
32710
- * A failed injection is fatal only when the turn is still the session's
32711
- * current turn and Codex reported something other than "no active turn to
32712
- * steer". Otherwise the turn has already ended underneath us and steering
32713
- * reports a failed delivery.
32714
- *
32715
- * @returns true when the prompt was injected; false when the target turn
32716
- * already ended.
32729
+ * After submission, only an explicit refusal proves non-delivery. A turn
32730
+ * ending (including Stop) does not prove whether it consumed the input.
32717
32731
  */
32718
- async injectSteerIntoActiveTurn(params, turnId, sessionState) {
32732
+ async injectSteerIntoActiveTurn(params, turnId) {
32719
32733
  const activePrompt = this.activePrompts.get(params.sessionId);
32720
32734
  const activeTurn = activePrompt?.currentTurn;
32721
32735
  const firstText = params.prompt[0]?.type === "text" ? params.prompt[0].text : "";
@@ -32729,8 +32743,23 @@ Check ${configPath} and project .codex directories, especially their config.toml
32729
32743
  if (pending.has(params.steerId)) {
32730
32744
  throw RequestError.invalidRequest(`Duplicate Codex steer id: ${params.steerId}`);
32731
32745
  }
32732
- pending.set(params.steerId, { activePrompt, turnId });
32746
+ let resolveApplied = () => {
32747
+ };
32748
+ const applied = new Promise((resolve) => {
32749
+ resolveApplied = resolve;
32750
+ });
32751
+ const steer = {
32752
+ activePrompt,
32753
+ threadId: activeTurn.threadId,
32754
+ turnId,
32755
+ requestPending: true,
32756
+ acknowledgement: null,
32757
+ applied,
32758
+ resolveApplied
32759
+ };
32760
+ pending.set(params.steerId, steer);
32733
32761
  this.pendingSteers.set(params.sessionId, pending);
32762
+ let requestAccepted = false;
32734
32763
  try {
32735
32764
  const response = await this.runWithProcessCheck(() => this.codexAcpClient.steerTurn({
32736
32765
  threadId: activeTurn.threadId,
@@ -32744,18 +32773,48 @@ Check ${configPath} and project .codex directories, especially their config.toml
32744
32773
  `Codex steered unexpected turn ${response.turnId}; expected ${turnId}`
32745
32774
  );
32746
32775
  }
32776
+ requestAccepted = true;
32747
32777
  return true;
32748
32778
  } catch (err) {
32749
- if (pending.get(params.steerId)?.activePrompt === activePrompt) {
32750
- pending.delete(params.steerId);
32751
- if (pending.size === 0) this.pendingSteers.delete(params.sessionId);
32779
+ const refused = this.isNoActiveTurnToSteerError(err);
32780
+ if (await this.reconcileSteer(params, steer, !refused)) {
32781
+ await this.acknowledgeSteer(params.sessionId, params.steerId, steer);
32782
+ return true;
32752
32783
  }
32753
- await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
32754
- const turnStillActive = sessionState.currentTurnId === turnId;
32755
- if (turnStillActive && !this.isNoActiveTurnToSteerError(err)) {
32756
- throw err;
32784
+ if (refused) return false;
32785
+ throw err;
32786
+ } finally {
32787
+ steer.requestPending = false;
32788
+ if (!requestAccepted || this.activePrompts.get(params.sessionId) !== activePrompt) {
32789
+ this.removePendingSteer(params.sessionId, params.steerId, steer);
32757
32790
  }
32758
- return false;
32791
+ }
32792
+ }
32793
+ async reconcileSteer(params, steer, readHistory) {
32794
+ let finished = false;
32795
+ let timeout;
32796
+ try {
32797
+ const historyApplied = (async () => {
32798
+ await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
32799
+ if (steer.acknowledgement !== null) return true;
32800
+ if (finished || !readHistory) return false;
32801
+ const thread = await this.codexAcpClient.readSessionHistory(steer.threadId);
32802
+ return thread.id === steer.threadId && thread.turns.some((turn) => turn.id === steer.turnId && turn.items.some((item) => item.type === "userMessage" && item.clientId === params.steerId));
32803
+ })().catch((error48) => {
32804
+ logger.error("Could not reconcile Codex steer history", error48);
32805
+ return false;
32806
+ });
32807
+ return await Promise.race([
32808
+ historyApplied,
32809
+ steer.applied.then(() => true),
32810
+ new Promise((resolve) => {
32811
+ timeout = setTimeout(() => resolve(false), 5e3);
32812
+ timeout.unref?.();
32813
+ })
32814
+ ]) || steer.acknowledgement !== null;
32815
+ } finally {
32816
+ finished = true;
32817
+ clearTimeout(timeout);
32759
32818
  }
32760
32819
  }
32761
32820
  async startGoalContinuationIfCurrent(sessionState, sessionGeneration, goalControlGeneration, expectedGoal) {
@@ -33600,7 +33659,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33600
33659
  const pending = this.pendingSteers.get(sessionId);
33601
33660
  if (!pending) return;
33602
33661
  for (const [steerId, steer] of pending) {
33603
- if (steer.activePrompt === activePrompt) pending.delete(steerId);
33662
+ if (steer.activePrompt === activePrompt && !steer.requestPending) pending.delete(steerId);
33604
33663
  }
33605
33664
  if (pending.size === 0) this.pendingSteers.delete(sessionId);
33606
33665
  }
@@ -33611,10 +33670,22 @@ Check ${configPath} and project .codex directories, especially their config.toml
33611
33670
  const pending = this.pendingSteers.get(sessionId);
33612
33671
  if (!pending) return;
33613
33672
  const steer = pending.get(steerId);
33614
- if (!steer || steer.activePrompt !== activePrompt || steer.turnId !== event.params.turnId) return;
33673
+ if (!steer || steer.activePrompt !== activePrompt || steer.threadId !== event.params.threadId || steer.turnId !== event.params.turnId) return;
33674
+ await this.acknowledgeSteer(sessionId, steerId, steer);
33675
+ }
33676
+ removePendingSteer(sessionId, steerId, steer) {
33677
+ const pending = this.pendingSteers.get(sessionId);
33678
+ if (pending?.get(steerId) !== steer) return;
33615
33679
  pending.delete(steerId);
33616
33680
  if (pending.size === 0) this.pendingSteers.delete(sessionId);
33617
- await this.connection.notify(CODEX_STEER_APPLIED_METHOD, { sessionId, steerId });
33681
+ }
33682
+ async acknowledgeSteer(sessionId, steerId, steer) {
33683
+ if (steer.acknowledgement === null) {
33684
+ steer.acknowledgement = Promise.resolve().then(() => this.connection.notify(CODEX_STEER_APPLIED_METHOD, { sessionId, steerId }));
33685
+ this.removePendingSteer(sessionId, steerId, steer);
33686
+ steer.resolveApplied();
33687
+ }
33688
+ await steer.acknowledgement;
33618
33689
  }
33619
33690
  cancelBeforeTurnStarted(activePrompt) {
33620
33691
  return activePrompt.cancelSignal.then(() => {
@@ -33803,6 +33874,11 @@ Check ${configPath} and project .codex directories, especially their config.toml
33803
33874
  return this.cancelledPromptResponse(sessionState);
33804
33875
  };
33805
33876
  try {
33877
+ sessionState.turnUsage ??= new CodexTurnUsage(
33878
+ false,
33879
+ this.codexAcpClient.appServerClient.getThreadTokenUsage(params.sessionId)?.tokenUsage.total
33880
+ );
33881
+ sessionState.turnUsage.prepare(ModelId.fromString(sessionState.currentModelId).model);
33806
33882
  const promptEventHandler = new CodexEventHandler(
33807
33883
  this.connection,
33808
33884
  sessionState,
@@ -33991,6 +34067,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33991
34067
  sessionState.lastTokenUsage = null;
33992
34068
  ensurePendingTurnStart();
33993
34069
  goalLifecycle.prepareTurn();
34070
+ sessionState.turnUsage.prepare(modelId.model);
33994
34071
  const sendPromptPromise = this.runWithProcessCheck(
33995
34072
  () => this.codexAcpClient.sendPrompt(
33996
34073
  effectiveParams,
@@ -34093,6 +34170,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
34093
34170
  activePrompt.currentTurn = null;
34094
34171
  sessionState.currentTurnId = null;
34095
34172
  goalLifecycle.prepareTurn();
34173
+ sessionState.turnUsage.prepare(modelId.model);
34096
34174
  const implementationPromise = this.runWithProcessCheck(
34097
34175
  () => this.codexAcpClient.sendPrompt(
34098
34176
  implementationRequest,