@agentclientprotocol/codex-acp 1.1.4 → 1.1.7

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.
Files changed (2) hide show
  1. package/dist/index.js +634 -133
  2. package/package.json +8 -6
package/dist/index.js CHANGED
@@ -18470,6 +18470,7 @@ var zToolCallUpdate = object({
18470
18470
  kind: defaultOnError(zToolKind.nullish(), () => void 0),
18471
18471
  status: defaultOnError(zToolCallStatus.nullish(), () => void 0),
18472
18472
  title: defaultOnError(string2().nullish(), () => void 0),
18473
+ name: defaultOnError(string2().nullish(), () => void 0),
18473
18474
  content: defaultOnError(vecSkipError(zToolCallContent).nullish(), () => void 0),
18474
18475
  locations: defaultOnError(vecSkipError(zToolCallLocation).nullish(), () => void 0),
18475
18476
  rawInput: defaultOnError(unknown().optional(), () => void 0),
@@ -19235,6 +19236,7 @@ var zContentChunk = object({
19235
19236
  var zToolCall = object({
19236
19237
  toolCallId: zToolCallId,
19237
19238
  title: string2(),
19239
+ name: defaultOnError(string2().nullish(), () => void 0),
19238
19240
  kind: defaultOnError(zToolKind.optional(), () => void 0),
19239
19241
  status: defaultOnError(zToolCallStatus.optional(), () => void 0),
19240
19242
  content: defaultOnError(vecSkipError(zToolCallContent).optional(), () => []),
@@ -19916,66 +19918,71 @@ var zCancelRequestNotification = object({
19916
19918
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => void 0)
19917
19919
  });
19918
19920
 
19919
- // node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.js
19920
- function tagOf(value, key) {
19921
- return typeof value === "object" && value !== null ? value[key] : void 0;
19922
- }
19923
- var zGuardCreateElicitationRequestForm = zElicitationFormMode.and(object({ mode: literal("form") })).and(object({ message: string2() }));
19924
- var zGuardCreateElicitationRequestUrl = zElicitationUrlMode.and(object({ mode: literal("url") })).and(object({ message: string2() }));
19925
- var zGuardCreateElicitationRequestCustom = union([zElicitationSessionScope, zElicitationRequestScope]).and(object({ message: string2() }));
19926
- var zGuardElicitationPropertySchemaString = zStringPropertySchema.and(object({ type: literal("string") }));
19927
- var zGuardElicitationPropertySchemaNumber = zNumberPropertySchema.and(object({ type: literal("number") }));
19928
- var zGuardElicitationPropertySchemaInteger = zIntegerPropertySchema.and(object({ type: literal("integer") }));
19929
- var zGuardElicitationPropertySchemaBoolean = zBooleanPropertySchema.and(object({ type: literal("boolean") }));
19930
- var zGuardElicitationPropertySchemaArray = zMultiSelectPropertySchema.and(object({ type: literal("array") }));
19931
- var zGuardMultiSelectItemsString = zStringMultiSelectItems.and(object({ type: literal("string") }));
19932
- var zGuardCreateElicitationResponseAccept = zElicitationAcceptAction.and(object({ action: literal("accept") }));
19933
- var zGuardCreateElicitationResponseDecline = object({
19934
- action: literal("decline")
19935
- });
19936
- var zGuardCreateElicitationResponseCancel = object({
19937
- action: literal("cancel")
19938
- });
19939
- var CreateElicitationResponse = {
19940
- /** Narrow to the `accept` variant, validating its payload. */
19941
- isAccept(value) {
19942
- return tagOf(value, "action") === "accept" && zGuardCreateElicitationResponseAccept.safeParse(value).success;
19943
- },
19944
- /** Narrow to the `decline` variant, validating its payload. */
19945
- isDecline(value) {
19946
- return tagOf(value, "action") === "decline" && zGuardCreateElicitationResponseDecline.safeParse(value).success;
19947
- },
19948
- /** Narrow to the `cancel` variant, validating its payload. */
19949
- isCancel(value) {
19950
- return tagOf(value, "action") === "cancel" && zGuardCreateElicitationResponseCancel.safeParse(value).success;
19951
- },
19952
- /**
19953
- * Narrow to a custom or future variant: the `action` tag matches no known variant.
19954
- *
19955
- * TypeScript keeps the known variants in the narrowed union (they are
19956
- * structural subtypes of the catch-all), so read vendor payload keys
19957
- * via a widening cast: `(value as Record<string, unknown>).someKey`.
19958
- */
19959
- isCustom(value) {
19960
- const tag = tagOf(value, "action");
19961
- return typeof tag === "string" && !["accept", "cancel", "decline"].includes(tag);
19962
- }
19963
- };
19964
-
19965
19921
  // node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js
19966
19922
  var CANCEL_REQUEST_METHOD = "$/cancel_request";
19923
+ function isRequestMessage(value) {
19924
+ return isJsonRpcEnvelope(value) && "id" in value && typeof value["method"] === "string" && isJsonRpcId(value["id"]);
19925
+ }
19926
+ function isResponseMessage(value) {
19927
+ if (!isJsonRpcEnvelope(value) || "method" in value) {
19928
+ return false;
19929
+ }
19930
+ if (!("id" in value) || !isJsonRpcId(value["id"])) {
19931
+ return false;
19932
+ }
19933
+ const hasResult = Object.hasOwn(value, "result");
19934
+ const hasError = Object.hasOwn(value, "error");
19935
+ if (hasResult === hasError) {
19936
+ return false;
19937
+ }
19938
+ return !hasError || isErrorResponse(value["error"]);
19939
+ }
19940
+ function isNotificationMessage(value) {
19941
+ return isJsonRpcEnvelope(value) && !("id" in value) && typeof value["method"] === "string";
19942
+ }
19967
19943
  function isRecord(value) {
19968
19944
  return typeof value === "object" && value !== null;
19969
19945
  }
19946
+ function isJsonRpcEnvelope(value) {
19947
+ return isRecord(value) && value["jsonrpc"] === "2.0";
19948
+ }
19970
19949
  function isJsonRpcId(value) {
19971
19950
  return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
19972
19951
  }
19952
+ function isResponseShapedMessage(value) {
19953
+ return isRecord(value) && !("method" in value) && ("id" in value || "result" in value || "error" in value);
19954
+ }
19955
+ function isResponseBatch(batch) {
19956
+ let hasValidCall = false;
19957
+ let hasValidResponse = false;
19958
+ let hasCallShape = false;
19959
+ let hasResponseShape = false;
19960
+ for (const entry of batch) {
19961
+ hasValidCall ||= isRequestMessage(entry) || isNotificationMessage(entry);
19962
+ hasValidResponse ||= isResponseMessage(entry);
19963
+ if (!isRecord(entry)) {
19964
+ continue;
19965
+ }
19966
+ hasCallShape ||= "method" in entry;
19967
+ hasResponseShape ||= "result" in entry || "error" in entry;
19968
+ }
19969
+ if (hasValidCall) {
19970
+ return false;
19971
+ }
19972
+ if (hasValidResponse) {
19973
+ return true;
19974
+ }
19975
+ return hasResponseShape && !hasCallShape;
19976
+ }
19973
19977
  function cancelRequestId(params) {
19974
19978
  if (!isRecord(params) || !isJsonRpcId(params["requestId"])) {
19975
19979
  return void 0;
19976
19980
  }
19977
19981
  return params["requestId"];
19978
19982
  }
19983
+ function isErrorResponse(value) {
19984
+ return isRecord(value) && typeof value["code"] === "number" && Number.isInteger(value["code"]) && typeof value["message"] === "string";
19985
+ }
19979
19986
  var Handled = {
19980
19987
  /**
19981
19988
  * Marks a message as handled.
@@ -20135,6 +20142,12 @@ var ConnectionContext = class {
20135
20142
  sendNotification(method, params) {
20136
20143
  return this.connection.sendNotification(method, params);
20137
20144
  }
20145
+ /**
20146
+ * Sends a non-empty JSON-RPC batch in one transport message.
20147
+ */
20148
+ sendBatch(entries) {
20149
+ return this.connection.sendBatch(entries);
20150
+ }
20138
20151
  /**
20139
20152
  * Sends a protocol-level request cancellation notification.
20140
20153
  */
@@ -20173,6 +20186,7 @@ var Connection = class {
20173
20186
  retryQueue = [];
20174
20187
  context = new ConnectionContext(this);
20175
20188
  receiveReader;
20189
+ allowBatches = true;
20176
20190
  constructor(requestHandlerOrStream, notificationHandlerOrHandlers, streamOrOptions, options) {
20177
20191
  if (typeof requestHandlerOrStream === "function") {
20178
20192
  const requestHandler = requestHandlerOrStream;
@@ -20181,16 +20195,13 @@ var Connection = class {
20181
20195
  this.initialize(stream2, [
20182
20196
  ...options?.handlers ?? [],
20183
20197
  this.legacyHandler(requestHandler, notificationHandler)
20184
- ]);
20198
+ ], options);
20185
20199
  return;
20186
20200
  }
20187
20201
  const stream = requestHandlerOrStream;
20188
20202
  const handlers = notificationHandlerOrHandlers;
20189
20203
  const connectionOptions = streamOrOptions;
20190
- this.initialize(stream, [
20191
- ...connectionOptions?.handlers ?? [],
20192
- ...handlers
20193
- ]);
20204
+ this.initialize(stream, [...connectionOptions?.handlers ?? [], ...handlers], connectionOptions);
20194
20205
  }
20195
20206
  /**
20196
20207
  * Creates a builder for configuring a handler-based connection.
@@ -20264,15 +20275,89 @@ var Connection = class {
20264
20275
  if (this.abortController.signal.aborted) {
20265
20276
  return rejectedPromise(this.closedReason());
20266
20277
  }
20278
+ const request = this.prepareRequest(method, params, mapResponse, options);
20279
+ const requestSent = this.sendWireMessage(request.message);
20280
+ void requestSent.catch(() => {
20281
+ });
20282
+ if (options.cancellationSignal?.aborted) {
20283
+ request.cancel();
20284
+ }
20285
+ return request.response;
20286
+ }
20287
+ /**
20288
+ * Sends a non-empty JSON-RPC batch in one transport message.
20289
+ *
20290
+ * Requests and notifications are processed independently by the peer. The
20291
+ * returned tuple preserves the input order: request entries resolve to their
20292
+ * mapped response, while notification entries resolve to `undefined`.
20293
+ */
20294
+ sendBatch(entries) {
20295
+ if (this.abortController.signal.aborted) {
20296
+ return rejectedPromise(this.closedReason());
20297
+ }
20298
+ if (!this.allowBatches) {
20299
+ return rejectedPromise(new TypeError("JSON-RPC batches are not supported on this connection"));
20300
+ }
20301
+ if (entries.length === 0) {
20302
+ return rejectedPromise(new TypeError("JSON-RPC batch must contain at least one entry"));
20303
+ }
20304
+ const messages = [];
20305
+ const cancellations = [];
20306
+ const outputs = [];
20307
+ for (const entry of entries) {
20308
+ if (entry.kind === "notification") {
20309
+ messages.push({
20310
+ jsonrpc: "2.0",
20311
+ method: entry.method,
20312
+ params: entry.params
20313
+ });
20314
+ outputs.push(Promise.resolve(void 0));
20315
+ continue;
20316
+ }
20317
+ const request = this.prepareRequest(entry.method, entry.params, entry.mapResponse, entry.options);
20318
+ messages.push(request.message);
20319
+ outputs.push(request.response);
20320
+ cancellations.push({
20321
+ signal: entry.options?.cancellationSignal,
20322
+ cancel: request.cancel
20323
+ });
20324
+ }
20325
+ const batch = messages;
20326
+ const batchSent = this.sendWireMessage(batch);
20327
+ for (const cancellation of cancellations) {
20328
+ if (cancellation.signal?.aborted) {
20329
+ cancellation.cancel();
20330
+ }
20331
+ }
20332
+ const response = Promise.all([batchSent, ...outputs]).then(([, ...resolved]) => resolved);
20333
+ response.catch(() => {
20334
+ });
20335
+ return response;
20336
+ }
20337
+ /**
20338
+ * Sends a protocol-level request cancellation notification.
20339
+ */
20340
+ sendCancelRequest(requestId) {
20341
+ return this.sendNotification(CANCEL_REQUEST_METHOD, { requestId });
20342
+ }
20343
+ /**
20344
+ * Sends a JSON-RPC notification.
20345
+ */
20346
+ sendNotification(method, params) {
20347
+ if (this.abortController.signal.aborted) {
20348
+ return rejectedPromise(this.closedReason());
20349
+ }
20350
+ return this.sendWireMessage({ jsonrpc: "2.0", method, params });
20351
+ }
20352
+ prepareRequest(method, params, mapResponse, options = {}) {
20267
20353
  const id = this.nextRequestId++;
20268
20354
  let cancel = () => {
20269
20355
  };
20270
- const responsePromise = new Promise((resolve, reject) => {
20356
+ const response = new Promise((resolve, reject) => {
20271
20357
  const pendingResponse = {
20272
- resolve: (response) => {
20358
+ resolve: (value) => {
20273
20359
  try {
20274
- const value = mapResponse ? mapResponse(response) : response;
20275
- resolve(value);
20360
+ resolve(mapResponse ? mapResponse(value) : value);
20276
20361
  } catch (error51) {
20277
20362
  reject(error51);
20278
20363
  }
@@ -20296,35 +20381,13 @@ var Connection = class {
20296
20381
  };
20297
20382
  this.pendingResponses.set(id, pendingResponse);
20298
20383
  });
20299
- responsePromise.catch(() => {
20300
- });
20301
- const requestSent = this.sendMessage({
20302
- jsonrpc: "2.0",
20303
- id,
20304
- method,
20305
- params
20306
- });
20307
- void requestSent.catch(() => {
20384
+ response.catch(() => {
20308
20385
  });
20309
- if (options.cancellationSignal?.aborted) {
20310
- cancel();
20311
- }
20312
- return responsePromise;
20313
- }
20314
- /**
20315
- * Sends a protocol-level request cancellation notification.
20316
- */
20317
- sendCancelRequest(requestId) {
20318
- return this.sendNotification(CANCEL_REQUEST_METHOD, { requestId });
20319
- }
20320
- /**
20321
- * Sends a JSON-RPC notification.
20322
- */
20323
- sendNotification(method, params) {
20324
- if (this.abortController.signal.aborted) {
20325
- return rejectedPromise(this.closedReason());
20326
- }
20327
- return this.sendMessage({ jsonrpc: "2.0", method, params });
20386
+ return {
20387
+ message: { jsonrpc: "2.0", id, method, params },
20388
+ response,
20389
+ cancel: () => cancel()
20390
+ };
20328
20391
  }
20329
20392
  /**
20330
20393
  * Closes the connection and rejects pending requests.
@@ -20347,9 +20410,10 @@ var Connection = class {
20347
20410
  void this.receiveReader?.cancel(closeError).catch(() => {
20348
20411
  });
20349
20412
  }
20350
- initialize(stream, handlers) {
20413
+ initialize(stream, handlers, options) {
20351
20414
  this.stream = stream;
20352
20415
  this.staticHandlers = handlers;
20416
+ this.allowBatches = options?.allowBatches ?? true;
20353
20417
  this.closedPromise = new Promise((resolve) => {
20354
20418
  this.abortController.signal.addEventListener("abort", () => resolve());
20355
20419
  });
@@ -20385,7 +20449,7 @@ var Connection = class {
20385
20449
  if (!message) {
20386
20450
  continue;
20387
20451
  }
20388
- this.receiveMessage(message);
20452
+ this.receiveWireMessage(message);
20389
20453
  }
20390
20454
  } finally {
20391
20455
  if (this.receiveReader === reader) {
@@ -20399,24 +20463,93 @@ var Connection = class {
20399
20463
  this.close(closeError);
20400
20464
  }
20401
20465
  }
20402
- receiveMessage(message) {
20403
- if (this.abortController.signal.aborted) {
20466
+ receiveWireMessage(message) {
20467
+ if (Array.isArray(message)) {
20468
+ if (!this.allowBatches) {
20469
+ this.close(new TypeError("JSON-RPC batches are not supported on this connection"));
20470
+ return;
20471
+ }
20472
+ this.receiveBatch(message);
20404
20473
  return;
20405
20474
  }
20406
20475
  if (!isRecord(message)) {
20407
20476
  console.error("Invalid message", { message });
20408
20477
  return;
20409
20478
  }
20479
+ this.receiveMessage(message);
20480
+ }
20481
+ receiveBatch(batch) {
20482
+ if (batch.length === 0) {
20483
+ void this.sendWireMessage({
20484
+ jsonrpc: "2.0",
20485
+ id: null,
20486
+ error: RequestError.invalidRequest(batch).toErrorResponse()
20487
+ }).catch(() => {
20488
+ });
20489
+ return;
20490
+ }
20491
+ const responseBatch = isResponseBatch(batch);
20492
+ const responseCount = responseBatch ? 0 : batch.reduce((count, message) => count + (isNotificationMessage(message) ? 0 : 1), 0);
20493
+ let remaining = responseCount;
20494
+ let remainingNotifications = batch.reduce((count, message) => count + (isNotificationMessage(message) ? 1 : 0), 0);
20495
+ let responseSent = false;
20496
+ const responses = [];
20497
+ const sendResponsesIfReady = async () => {
20498
+ if (responseSent || remaining !== 0 || remainingNotifications !== 0 || responses.length === 0) {
20499
+ return;
20500
+ }
20501
+ responseSent = true;
20502
+ await this.sendWireMessage(responses);
20503
+ };
20504
+ const collectResponse = async (response) => {
20505
+ responses.push(response);
20506
+ remaining -= 1;
20507
+ await sendResponsesIfReady();
20508
+ };
20509
+ for (const message of batch) {
20510
+ if (responseBatch) {
20511
+ if (isResponseShapedMessage(message)) {
20512
+ this.receiveMessage(message);
20513
+ }
20514
+ continue;
20515
+ }
20516
+ if (!isRequestMessage(message) && !isNotificationMessage(message)) {
20517
+ void collectResponse({
20518
+ jsonrpc: "2.0",
20519
+ id: null,
20520
+ error: RequestError.invalidRequest(message).toErrorResponse()
20521
+ }).catch(() => {
20522
+ });
20523
+ continue;
20524
+ }
20525
+ const processing = this.receiveMessage(message, isRequestMessage(message) ? collectResponse : void 0);
20526
+ if (isNotificationMessage(message)) {
20527
+ void processing.finally(() => {
20528
+ remainingNotifications -= 1;
20529
+ void sendResponsesIfReady().catch((error51) => this.close(error51));
20530
+ });
20531
+ }
20532
+ }
20533
+ }
20534
+ receiveMessage(message, sendResponse) {
20535
+ if (this.abortController.signal.aborted) {
20536
+ return Promise.resolve();
20537
+ }
20538
+ if (!isRecord(message)) {
20539
+ console.error("Invalid message", { message });
20540
+ return Promise.resolve();
20541
+ }
20410
20542
  if ("method" in message) {
20411
20543
  if (!("id" in message)) {
20412
20544
  this.handleProtocolNotification(message);
20413
20545
  }
20414
- void this.processIncomingMessage(this.toIncomingMessage(message)).catch((error51) => this.close(error51));
20546
+ return this.processIncomingMessage(this.toIncomingMessage(message, sendResponse)).catch((error51) => this.close(error51));
20415
20547
  } else if ("id" in message) {
20416
20548
  this.handleResponse(message);
20417
20549
  } else {
20418
20550
  console.error("Invalid message", { message });
20419
20551
  }
20552
+ return Promise.resolve();
20420
20553
  }
20421
20554
  async processIncomingMessage(message) {
20422
20555
  if (this.abortController.signal.aborted) {
@@ -20460,7 +20593,7 @@ var Connection = class {
20460
20593
  }
20461
20594
  }
20462
20595
  }
20463
- toIncomingMessage(message) {
20596
+ toIncomingMessage(message, sendResponse) {
20464
20597
  if ("id" in message) {
20465
20598
  const abortController = new AbortController();
20466
20599
  this.incomingRequests.set(message.id, abortController);
@@ -20475,11 +20608,14 @@ var Connection = class {
20475
20608
  params: message.params,
20476
20609
  raw: message,
20477
20610
  signal: abortController.signal,
20478
- responder: new RequestResponder(message.id, (result) => this.sendMessage({
20479
- jsonrpc: "2.0",
20480
- id: message.id,
20481
- ...result
20482
- }), abortController.signal, finishRequest)
20611
+ responder: new RequestResponder(message.id, (result) => {
20612
+ const response = {
20613
+ jsonrpc: "2.0",
20614
+ id: message.id,
20615
+ ...result
20616
+ };
20617
+ return sendResponse ? sendResponse(response) : this.sendWireMessage(response);
20618
+ }, abortController.signal, finishRequest)
20483
20619
  };
20484
20620
  }
20485
20621
  return {
@@ -20494,13 +20630,13 @@ var Connection = class {
20494
20630
  if (pendingResponse) {
20495
20631
  this.pendingResponses.delete(response.id);
20496
20632
  pendingResponse.cleanup?.();
20497
- if ("result" in response) {
20633
+ if (!isResponseMessage(response)) {
20634
+ pendingResponse.reject(RequestError.invalidRequest(response));
20635
+ } else if ("result" in response) {
20498
20636
  pendingResponse.resolve(response.result);
20499
- } else if ("error" in response && isRecord(response.error)) {
20637
+ } else {
20500
20638
  const { code, message, data } = response.error;
20501
20639
  pendingResponse.reject(new RequestError(code, message, data));
20502
- } else {
20503
- pendingResponse.reject(RequestError.invalidRequest(response));
20504
20640
  }
20505
20641
  } else {
20506
20642
  console.error("Got response to unknown request", response.id);
@@ -20523,7 +20659,7 @@ var Connection = class {
20523
20659
  closedReason() {
20524
20660
  return this.abortController.signal.reason ?? new Error("ACP connection closed");
20525
20661
  }
20526
- async sendMessage(message) {
20662
+ async sendWireMessage(message) {
20527
20663
  if (this.abortController.signal.aborted) {
20528
20664
  return rejectedPromise(this.closedReason());
20529
20665
  }
@@ -20769,7 +20905,7 @@ function ndJsonStream(output, input) {
20769
20905
  if (trimmedLine) {
20770
20906
  try {
20771
20907
  const message = JSON.parse(trimmedLine);
20772
- if (isRecord(message)) {
20908
+ if (isRecord(message) || Array.isArray(message)) {
20773
20909
  controller.enqueue(message);
20774
20910
  } else {
20775
20911
  console.warn("Skipping JSON line that is not an object:", trimmedLine);
@@ -20843,7 +20979,56 @@ function ndJsonStream(output, input) {
20843
20979
  return { readable, writable };
20844
20980
  }
20845
20981
 
20982
+ // node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.js
20983
+ function tagOf(value, key) {
20984
+ return typeof value === "object" && value !== null ? value[key] : void 0;
20985
+ }
20986
+ var zGuardCreateElicitationRequestForm = zElicitationFormMode.and(object({ mode: literal("form") })).and(object({ message: string2() }));
20987
+ var zGuardCreateElicitationRequestUrl = zElicitationUrlMode.and(object({ mode: literal("url") })).and(object({ message: string2() }));
20988
+ var zGuardCreateElicitationRequestCustom = union([zElicitationSessionScope, zElicitationRequestScope]).and(object({ message: string2() }));
20989
+ var zGuardElicitationPropertySchemaString = zStringPropertySchema.and(object({ type: literal("string") }));
20990
+ var zGuardElicitationPropertySchemaNumber = zNumberPropertySchema.and(object({ type: literal("number") }));
20991
+ var zGuardElicitationPropertySchemaInteger = zIntegerPropertySchema.and(object({ type: literal("integer") }));
20992
+ var zGuardElicitationPropertySchemaBoolean = zBooleanPropertySchema.and(object({ type: literal("boolean") }));
20993
+ var zGuardElicitationPropertySchemaArray = zMultiSelectPropertySchema.and(object({ type: literal("array") }));
20994
+ var zGuardMultiSelectItemsString = zStringMultiSelectItems.and(object({ type: literal("string") }));
20995
+ var zGuardCreateElicitationResponseAccept = zElicitationAcceptAction.and(object({ action: literal("accept") }));
20996
+ var zGuardCreateElicitationResponseDecline = object({
20997
+ action: literal("decline")
20998
+ });
20999
+ var zGuardCreateElicitationResponseCancel = object({
21000
+ action: literal("cancel")
21001
+ });
21002
+ var CreateElicitationResponse = {
21003
+ /** Narrow to the `accept` variant, validating its payload. */
21004
+ isAccept(value) {
21005
+ return tagOf(value, "action") === "accept" && zGuardCreateElicitationResponseAccept.safeParse(value).success;
21006
+ },
21007
+ /** Narrow to the `decline` variant, validating its payload. */
21008
+ isDecline(value) {
21009
+ return tagOf(value, "action") === "decline" && zGuardCreateElicitationResponseDecline.safeParse(value).success;
21010
+ },
21011
+ /** Narrow to the `cancel` variant, validating its payload. */
21012
+ isCancel(value) {
21013
+ return tagOf(value, "action") === "cancel" && zGuardCreateElicitationResponseCancel.safeParse(value).success;
21014
+ },
21015
+ /**
21016
+ * Narrow to a custom or future variant: the `action` tag matches no known variant.
21017
+ *
21018
+ * TypeScript keeps the known variants in the narrowed union (they are
21019
+ * structural subtypes of the catch-all), so read vendor payload keys
21020
+ * via a widening cast: `(value as Record<string, unknown>).someKey`.
21021
+ */
21022
+ isCustom(value) {
21023
+ const tag = tagOf(value, "action");
21024
+ return typeof tag === "string" && !["accept", "cancel", "decline"].includes(tag);
21025
+ }
21026
+ };
21027
+
20846
21028
  // node_modules/@agentclientprotocol/sdk/dist/acp.js
21029
+ function ndJsonStream2(output, input) {
21030
+ return ndJsonStream(output, input);
21031
+ }
20847
21032
  function emptyObjectResponse(response) {
20848
21033
  return response ?? {};
20849
21034
  }
@@ -21505,6 +21690,7 @@ function runConnectHandlers(connection, handlers) {
21505
21690
  var appBuilder = /* @__PURE__ */ Symbol("appBuilder");
21506
21691
  var runAgentConnectHandlers = /* @__PURE__ */ Symbol("runAgentConnectHandlers");
21507
21692
  var runClientConnectHandlers = /* @__PURE__ */ Symbol("runClientConnectHandlers");
21693
+ var stableConnectionOptions = { allowBatches: false };
21508
21694
  function agent(options) {
21509
21695
  return new AgentApp(options);
21510
21696
  }
@@ -21578,7 +21764,7 @@ var AgentApp = class {
21578
21764
  return state2;
21579
21765
  }
21580
21766
  const [thisStream, peerStream] = memoryStreamPair();
21581
- const peerRawConnection = target[appBuilder]().connect(peerStream);
21767
+ const peerRawConnection = target[appBuilder]().connect(peerStream, stableConnectionOptions);
21582
21768
  const peerConnection = clientConnection(peerRawConnection);
21583
21769
  const state = this.openStreamConnection(thisStream);
21584
21770
  void state.rawConnection.closed.then(() => peerConnection.close());
@@ -21594,7 +21780,7 @@ var AgentApp = class {
21594
21780
  return state;
21595
21781
  }
21596
21782
  openStreamConnection(stream) {
21597
- const rawConnection = this.builder.connect(stream);
21783
+ const rawConnection = this.builder.connect(stream, stableConnectionOptions);
21598
21784
  return {
21599
21785
  rawConnection,
21600
21786
  connection: agentConnection(rawConnection, this.connectHandlers)
@@ -21673,7 +21859,7 @@ var ClientApp = class {
21673
21859
  return state2;
21674
21860
  }
21675
21861
  const [thisStream, peerStream] = memoryStreamPair();
21676
- const peerRawConnection = target[appBuilder]().connect(peerStream);
21862
+ const peerRawConnection = target[appBuilder]().connect(peerStream, stableConnectionOptions);
21677
21863
  const peerConnection = agentConnection(peerRawConnection);
21678
21864
  const state = this.openStreamConnection(thisStream);
21679
21865
  void state.rawConnection.closed.then(() => peerConnection.close());
@@ -21689,7 +21875,7 @@ var ClientApp = class {
21689
21875
  return state;
21690
21876
  }
21691
21877
  openStreamConnection(stream) {
21692
- const rawConnection = this.builder.connect(stream);
21878
+ const rawConnection = this.builder.connect(stream, stableConnectionOptions);
21693
21879
  return {
21694
21880
  rawConnection,
21695
21881
  connection: clientConnection(rawConnection, this.connectHandlers)
@@ -21811,7 +21997,7 @@ function createJSONRPCReader(readable) {
21811
21997
  function createJsonStream(readable, writable) {
21812
21998
  const input = Writable.toWeb(writable);
21813
21999
  const output = Readable.toWeb(readable);
21814
- return ndJsonStream(input, output);
22000
+ return ndJsonStream2(input, output);
21815
22001
  }
21816
22002
 
21817
22003
  // src/Logger.ts
@@ -22858,7 +23044,7 @@ function createWebSearchStartUpdate(item) {
22858
23044
  kind: "search",
22859
23045
  title: formatWebSearchTitle(item),
22860
23046
  status: "in_progress",
22861
- rawInput: item
23047
+ rawInput: createWebSearchRawInput(item)
22862
23048
  };
22863
23049
  }
22864
23050
  function createWebSearchCompleteUpdate(item) {
@@ -22867,7 +23053,15 @@ function createWebSearchCompleteUpdate(item) {
22867
23053
  toolCallId: item.id,
22868
23054
  title: formatWebSearchTitle(item),
22869
23055
  status: "completed",
22870
- rawInput: item
23056
+ rawInput: createWebSearchRawInput(item)
23057
+ };
23058
+ }
23059
+ function createWebSearchRawInput(item) {
23060
+ return {
23061
+ type: item.type,
23062
+ id: item.id,
23063
+ query: item.query,
23064
+ action: item.action
22871
23065
  };
22872
23066
  }
22873
23067
  function createCollabAgentToolCallUpdate(item) {
@@ -23364,9 +23558,10 @@ function createAgentTextThoughtChunk(text, messageId, meta3) {
23364
23558
 
23365
23559
  // src/AcpExtensions.ts
23366
23560
  var LEGACY_SET_SESSION_MODEL_METHOD = "session/set_model";
23561
+ var SESSION_STEERING_METHOD = "_session/steering";
23367
23562
  var GOAL_CONTROL_METHOD = "_codex/session/goal_control";
23368
23563
  function isExtMethodRequest(request) {
23369
- return request.method === "authentication/status" || request.method === "authentication/logout" || request.method === LEGACY_SET_SESSION_MODEL_METHOD || request.method === GOAL_CONTROL_METHOD;
23564
+ return request.method === "authentication/status" || request.method === "authentication/logout" || request.method === LEGACY_SET_SESSION_MODEL_METHOD || request.method === GOAL_CONTROL_METHOD || request.method === SESSION_STEERING_METHOD;
23370
23565
  }
23371
23566
 
23372
23567
  // src/ThreadGoalSnapshot.ts
@@ -23395,6 +23590,7 @@ var CodexEventHandler = class {
23395
23590
  activeGuardianApprovalReviews = /* @__PURE__ */ new Set();
23396
23591
  activeImageGenerationItems = /* @__PURE__ */ new Set();
23397
23592
  emittedImageViewItems = /* @__PURE__ */ new Set();
23593
+ planDeltaTextByItemId = /* @__PURE__ */ new Map();
23398
23594
  seenReasoningDeltaItemIds = /* @__PURE__ */ new Set();
23399
23595
  terminalCommandIds = /* @__PURE__ */ new Set();
23400
23596
  terminalCommandOutputIds = /* @__PURE__ */ new Set();
@@ -23418,6 +23614,8 @@ var CodexEventHandler = class {
23418
23614
  switch (notification.method) {
23419
23615
  case "item/agentMessage/delta":
23420
23616
  return await this.createTextEvent(notification.params);
23617
+ case "item/plan/delta":
23618
+ return this.createPlanDeltaEvent(notification.params);
23421
23619
  case "item/started":
23422
23620
  return await this.createItemEvent(notification.params);
23423
23621
  case "item/completed":
@@ -23496,6 +23694,8 @@ var CodexEventHandler = class {
23496
23694
  return this.createTerminalInteractionEvent(notification.params);
23497
23695
  // ignored events
23498
23696
  case "thread/deleted":
23697
+ case "thread/environment/connected":
23698
+ case "thread/environment/disconnected":
23499
23699
  case "command/exec/outputDelta":
23500
23700
  case "hook/started":
23501
23701
  case "hook/completed":
@@ -23525,8 +23725,8 @@ var CodexEventHandler = class {
23525
23725
  case "mcpServer/oauthLogin/completed":
23526
23726
  case "externalAgentConfig/import/completed":
23527
23727
  case "rawResponseItem/completed":
23728
+ case "rawResponse/completed":
23528
23729
  case "thread/started":
23529
- case "item/plan/delta":
23530
23730
  case "remoteControl/status/changed":
23531
23731
  case "app/list/updated":
23532
23732
  case "thread/settings/updated":
@@ -23591,6 +23791,14 @@ ${event.details}` : "";
23591
23791
  this.seenReasoningDeltaItemIds.add(event.itemId);
23592
23792
  return this.createAgentThoughtEvent(event.delta, event.itemId);
23593
23793
  }
23794
+ createPlanDeltaEvent(event) {
23795
+ if (event.delta.length === 0) {
23796
+ return null;
23797
+ }
23798
+ const text = this.planDeltaTextByItemId.get(event.itemId) ?? "";
23799
+ this.planDeltaTextByItemId.set(event.itemId, text + event.delta);
23800
+ return null;
23801
+ }
23594
23802
  createReasoningSectionBreakEvent(event) {
23595
23803
  this.seenReasoningDeltaItemIds.add(event.itemId);
23596
23804
  return this.createAgentThoughtEvent("\n\n", event.itemId);
@@ -23684,6 +23892,11 @@ ${event.details}` : "";
23684
23892
  case "agentMessage":
23685
23893
  this.rememberAgentMessagePhase(event.item);
23686
23894
  return null;
23895
+ case "plan": {
23896
+ const deltaText = this.planDeltaTextByItemId.get(event.item.id) ?? "";
23897
+ this.planDeltaTextByItemId.delete(event.item.id);
23898
+ return this.createCompletedPlanEvent(event.item, deltaText);
23899
+ }
23687
23900
  case "exitedReviewMode":
23688
23901
  return this.createExitedReviewModeEvent(event.item);
23689
23902
  case "contextCompaction":
@@ -23697,7 +23910,6 @@ ${event.details}` : "";
23697
23910
  case "userMessage":
23698
23911
  case "hookPrompt":
23699
23912
  case "enteredReviewMode":
23700
- case "plan":
23701
23913
  return null;
23702
23914
  }
23703
23915
  }
@@ -23712,6 +23924,20 @@ ${event.details}` : "";
23712
23924
  }
23713
23925
  return this.createAgentThoughtEvent(text, item.id);
23714
23926
  }
23927
+ createCompletedPlanEvent(item, deltaText) {
23928
+ const text = item.text.length > 0 ? item.text : deltaText;
23929
+ if (text.length === 0) {
23930
+ return null;
23931
+ }
23932
+ return this.createPlanTextEvent(text, item.id);
23933
+ }
23934
+ createPlanTextEvent(text, messageId) {
23935
+ return createAgentTextMessageChunk(
23936
+ text,
23937
+ messageId,
23938
+ createCodexMessagePhaseMeta("final_answer")
23939
+ );
23940
+ }
23715
23941
  createExitedReviewModeEvent(item) {
23716
23942
  const text = item.review.trim();
23717
23943
  if (text.length === 0) {
@@ -25687,7 +25913,7 @@ var package_default = {
25687
25913
  publishConfig: {
25688
25914
  access: "public"
25689
25915
  },
25690
- version: "1.1.4",
25916
+ version: "1.1.7",
25691
25917
  description: "",
25692
25918
  main: "dist/index.js",
25693
25919
  bin: {
@@ -25717,11 +25943,13 @@ var package_default = {
25717
25943
  "package:win-x64": "cd dist/bin && zip codex-acp-x64-windows.zip codex-acp-x64-windows.exe",
25718
25944
  "package:win-arm64": "cd dist/bin && zip codex-acp-arm64-windows.zip codex-acp-arm64-windows.exe",
25719
25945
  start: "node --import tsx src/index.ts",
25946
+ "example:steering": "node --import tsx examples/steering.ts",
25947
+ "example:steering:multistep": "node --import tsx examples/steering.ts",
25720
25948
  "generate-types": "./node_modules/.bin/codex app-server generate-ts --out src/app-server",
25721
25949
  test: "vitest run",
25722
25950
  "test:e2e": "npm run build && RUN_E2E_TESTS=true vitest run src/__tests__/CodexACPAgent/e2e",
25723
25951
  "test:watch": "vitest",
25724
- typecheck: "tsc --noEmit",
25952
+ typecheck: "tsc --noEmit && tsc --noEmit -p examples/tsconfig.json",
25725
25953
  "codex-test": "tsx .claude/skills/run-codex/scripts/run-codex-test.ts"
25726
25954
  },
25727
25955
  homepage: "https://github.com/agentclientprotocol/codex-acp#readme",
@@ -25740,13 +25968,13 @@ var package_default = {
25740
25968
  "@types/node": "^26.1.0",
25741
25969
  esbuild: "^0.28.1",
25742
25970
  "mcp-hello-world": "^1.1.2",
25743
- tsx: "^4.23.0",
25744
- typescript: "^6.0.3",
25971
+ tsx: "^4.23.1",
25972
+ typescript: "^7.0.2",
25745
25973
  vitest: "^4.1.10"
25746
25974
  },
25747
25975
  dependencies: {
25748
- "@agentclientprotocol/sdk": "^1.2.1",
25749
- "@openai/codex": "^0.144.4",
25976
+ "@agentclientprotocol/sdk": "^1.3.0",
25977
+ "@openai/codex": "^0.145.0",
25750
25978
  diff: "^9.0.0",
25751
25979
  open: "^11.0.0",
25752
25980
  "vscode-jsonrpc": "^9.0.1",
@@ -25803,6 +26031,7 @@ var CodexAcpClient = class {
25803
26031
  pendingAccountUpdated = null;
25804
26032
  sessionNotificationQueues = /* @__PURE__ */ new Map();
25805
26033
  skillExtraRoots = [];
26034
+ configPath = null;
25806
26035
  constructor(codexClient, codexConfig, modelProvider) {
25807
26036
  this.codexClient = codexClient;
25808
26037
  this.config = codexConfig ?? {};
@@ -25815,7 +26044,7 @@ var CodexAcpClient = class {
25815
26044
  version: `${package_default.version}`
25816
26045
  };
25817
26046
  async initialize(request) {
25818
- await this.codexClient.initialize({
26047
+ const response = await this.codexClient.initialize({
25819
26048
  capabilities: {
25820
26049
  experimentalApi: true,
25821
26050
  requestAttestation: false
@@ -25826,6 +26055,10 @@ var CodexAcpClient = class {
25826
26055
  title: request.clientInfo?.title ?? this.defaultClientInfo.title
25827
26056
  }
25828
26057
  });
26058
+ this.configPath = response?.codexHome ?? null;
26059
+ }
26060
+ getHomePath() {
26061
+ return this.configPath;
25829
26062
  }
25830
26063
  async authenticate(authRequest) {
25831
26064
  if (!isCodexAuthRequest(authRequest)) {
@@ -26178,11 +26411,16 @@ var CodexAcpClient = class {
26178
26411
  }
26179
26412
  async getConfigMcpServerNames(projectPath) {
26180
26413
  const response = await this.codexClient.configRead({ includeLayers: true, cwd: projectPath });
26181
- const mcpServers = response?.config?.["mcp_servers"];
26182
- if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
26414
+ const effectiveMcpServers = response?.config?.["mcp_servers"];
26415
+ const configLayers = response?.layers ?? [];
26416
+ const layerMcpServers = configLayers.map((layer) => {
26417
+ return isJsonObject(layer.config) ? layer.config["mcp_servers"] : void 0;
26418
+ });
26419
+ const configuredMcpServers = [effectiveMcpServers, ...layerMcpServers].filter(isJsonObject);
26420
+ if (configuredMcpServers.length === 0) {
26183
26421
  return /* @__PURE__ */ new Set();
26184
26422
  }
26185
- return new Set(Object.keys(mcpServers));
26423
+ return new Set(configuredMcpServers.flatMap((server) => Object.keys(server)));
26186
26424
  }
26187
26425
  getModelProvider() {
26188
26426
  return this.gatewayConfig?.modelProvider ?? this.modelProvider;
@@ -26428,6 +26666,13 @@ var CodexAcpClient = class {
26428
26666
  turnId: params.turnId
26429
26667
  });
26430
26668
  }
26669
+ async steerTurn(params) {
26670
+ return await this.codexClient.turnSteer({
26671
+ threadId: params.threadId,
26672
+ expectedTurnId: params.turnId,
26673
+ input: buildPromptItems(params.prompt)
26674
+ });
26675
+ }
26431
26676
  async fetchAvailableModels() {
26432
26677
  const models = [];
26433
26678
  let cursor = null;
@@ -27106,6 +27351,47 @@ var CodexCommands = class {
27106
27351
  }
27107
27352
  };
27108
27353
 
27354
+ // src/SteeringQueue.ts
27355
+ var SteeringQueue = class {
27356
+ constructor(handle) {
27357
+ this.handle = handle;
27358
+ }
27359
+ handle;
27360
+ pending = [];
27361
+ processing = false;
27362
+ enqueue(params) {
27363
+ return new Promise((resolve, reject) => {
27364
+ this.pending.push({ params, resolve, reject });
27365
+ this.startConsumer();
27366
+ });
27367
+ }
27368
+ /** No request is queued and the consumer is not running. */
27369
+ get isIdle() {
27370
+ return !this.processing && this.pending.length === 0;
27371
+ }
27372
+ startConsumer() {
27373
+ if (this.processing) {
27374
+ return;
27375
+ }
27376
+ this.processing = true;
27377
+ void this.consume();
27378
+ }
27379
+ async consume() {
27380
+ try {
27381
+ while (this.pending.length > 0) {
27382
+ const next = this.pending.shift();
27383
+ try {
27384
+ next.resolve(await this.handle(next.params));
27385
+ } catch (error51) {
27386
+ next.reject(error51);
27387
+ }
27388
+ }
27389
+ } finally {
27390
+ this.processing = false;
27391
+ }
27392
+ }
27393
+ };
27394
+
27109
27395
  // src/ResponseItemHistoryFallback.ts
27110
27396
  import { readFile as readFile2 } from "node:fs/promises";
27111
27397
  import path5 from "node:path";
@@ -28148,6 +28434,7 @@ var CodexAcpServer = class _CodexAcpServer {
28148
28434
  pendingMcpStartupSessions;
28149
28435
  pendingTurnStarts;
28150
28436
  activePrompts;
28437
+ steeringQueues;
28151
28438
  closingSessions;
28152
28439
  sessionGenerations;
28153
28440
  sessionOpenGenerations;
@@ -28156,6 +28443,7 @@ var CodexAcpServer = class _CodexAcpServer {
28156
28443
  this.pendingMcpStartupSessions = /* @__PURE__ */ new Map();
28157
28444
  this.pendingTurnStarts = /* @__PURE__ */ new Map();
28158
28445
  this.activePrompts = /* @__PURE__ */ new Map();
28446
+ this.steeringQueues = /* @__PURE__ */ new Map();
28159
28447
  this.closingSessions = /* @__PURE__ */ new Map();
28160
28448
  this.sessionGenerations = /* @__PURE__ */ new Map();
28161
28449
  this.sessionOpenGenerations = /* @__PURE__ */ new Map();
@@ -28212,7 +28500,12 @@ var CodexAcpServer = class _CodexAcpServer {
28212
28500
  sse: false
28213
28501
  }
28214
28502
  },
28215
- authMethods: getCodexAuthMethods(_params.clientCapabilities)
28503
+ authMethods: getCodexAuthMethods(_params.clientCapabilities),
28504
+ _meta: {
28505
+ steering: {
28506
+ supported: true
28507
+ }
28508
+ }
28216
28509
  };
28217
28510
  }
28218
28511
  async extMethod(method, params) {
@@ -28229,6 +28522,8 @@ var CodexAcpServer = class _CodexAcpServer {
28229
28522
  }
28230
28523
  case LEGACY_SET_SESSION_MODEL_METHOD:
28231
28524
  return await this.unstable_setSessionModel(this.parseLegacySetSessionModelParams(methodRequest.params));
28525
+ case SESSION_STEERING_METHOD:
28526
+ return await this.executeOrQueueSteeringRequest(this.parseSessionSteerParams(methodRequest.params));
28232
28527
  case GOAL_CONTROL_METHOD: {
28233
28528
  const sessionState = this.sessions.get(methodRequest.params.sessionId);
28234
28529
  if (!sessionState) {
@@ -28283,6 +28578,12 @@ var CodexAcpServer = class _CodexAcpServer {
28283
28578
 
28284
28579
  You have been logged out. Please try again.`);
28285
28580
  }
28581
+ const configPath = this.codexAcpClient.getHomePath() ?? "global";
28582
+ if (e.message.includes("load config")) {
28583
+ throw RequestError.internalError(`${e.message}
28584
+
28585
+ Check ${configPath} and project .codex directories, especially their config.toml files, or any CODEX_CONFIG override.`);
28586
+ }
28286
28587
  }
28287
28588
  beginSessionOpen(sessionId) {
28288
28589
  const generation = this.getSessionGeneration(sessionId);
@@ -28529,6 +28830,7 @@ You have been logged out. Please try again.`);
28529
28830
  this.pendingMcpStartupSessions.delete(params.sessionId);
28530
28831
  this.pendingTurnStarts.delete(params.sessionId);
28531
28832
  this.activePrompts.delete(params.sessionId);
28833
+ this.steeringQueues.delete(params.sessionId);
28532
28834
  }
28533
28835
  this.endSessionCloseFence(params.sessionId);
28534
28836
  }
@@ -28749,6 +29051,199 @@ You have been logged out. Please try again.`);
28749
29051
  modelId
28750
29052
  };
28751
29053
  }
29054
+ /**
29055
+ * Handles one incoming steering request, serialising it against any other
29056
+ * steer already in flight for the same session.
29057
+ *
29058
+ * Every session gets its own {@link SteeringQueue}: the request is enqueued
29059
+ * and awaited, so concurrent steers for one session run strictly one at a
29060
+ * time, in arrival order, and can never race to inject into — or start —
29061
+ * rival turns. Steers for different sessions use different queues and run
29062
+ * concurrently. Once the queue drains to idle it is removed from the map,
29063
+ * so no per-session entry leaks after the session goes quiet (the identity
29064
+ * check guards against deleting a queue a later request has since reused).
29065
+ *
29066
+ * @param params The target session id and the prompt to steer with.
29067
+ * @returns Whether the prompt joined the active turn ("injected"), started a
29068
+ * new one ("startedNewTurn"), or could not be applied ("failed"); see
29069
+ * {@link performSteeringRequest}.
29070
+ */
29071
+ async executeOrQueueSteeringRequest(params) {
29072
+ const queue = this.getSteeringQueue(params.sessionId);
29073
+ try {
29074
+ return await queue.enqueue(params);
29075
+ } catch (error51) {
29076
+ if (error51 instanceof RequestError) {
29077
+ throw error51;
29078
+ }
29079
+ logger.error(`Steering request for session ${params.sessionId} failed`, error51);
29080
+ return { outcome: "failed" };
29081
+ } finally {
29082
+ if (queue.isIdle && this.steeringQueues.get(params.sessionId) === queue) {
29083
+ this.steeringQueues.delete(params.sessionId);
29084
+ }
29085
+ }
29086
+ }
29087
+ /**
29088
+ * Returns the steering queue for a session, creating and registering it on
29089
+ * first use.
29090
+ *
29091
+ * @param sessionId The session whose steering queue is required.
29092
+ * @returns The session's existing queue, or a freshly created one.
29093
+ */
29094
+ getSteeringQueue(sessionId) {
29095
+ let queue = this.steeringQueues.get(sessionId);
29096
+ if (!queue) {
29097
+ queue = new SteeringQueue((params) => this.performSteeringRequest(params));
29098
+ this.steeringQueues.set(sessionId, queue);
29099
+ }
29100
+ return queue;
29101
+ }
29102
+ /**
29103
+ * Delivers a steering prompt to the session: injects it into the live turn
29104
+ * when there is one, otherwise starts a new turn.
29105
+ *
29106
+ * @param params The target session id and the prompt to steer with.
29107
+ * @returns "injected" when the prompt joined an existing turn, otherwise the
29108
+ * outcome of starting a new turn.
29109
+ */
29110
+ async performSteeringRequest(params) {
29111
+ logger.log("Steering session requested", {
29112
+ sessionId: params.sessionId,
29113
+ prompt: params.prompt
29114
+ });
29115
+ const sessionState = this.getSessionState(params.sessionId);
29116
+ this.assertSteerInputSupported(params, sessionState);
29117
+ const turnId = await this.getSteerableTurnId(sessionState);
29118
+ if (turnId) {
29119
+ const injected = await this.injectSteerIntoActiveTurn(params, turnId, sessionState);
29120
+ if (injected) {
29121
+ logger.log("Steering session injected", { sessionId: params.sessionId, turnId });
29122
+ return { outcome: "injected" };
29123
+ }
29124
+ }
29125
+ return await this.startNewTurnFromSteering(params);
29126
+ }
29127
+ /**
29128
+ * Rejects a steering prompt whose content the active model cannot accept
29129
+ * (currently: image blocks on a text-only model).
29130
+ */
29131
+ assertSteerInputSupported(params, sessionState) {
29132
+ const hasImage = params.prompt.some((block) => block.type === "image");
29133
+ if (hasImage && !sessionState.supportedInputModalities.includes("image")) {
29134
+ throw RequestError.invalidRequest("The current model does not support image input");
29135
+ }
29136
+ }
29137
+ /**
29138
+ * Attempts to inject the prompt into the given running turn.
29139
+ *
29140
+ * A failed injection is fatal only when the turn is still the session's
29141
+ * current turn and Codex reported something other than "no active turn to
29142
+ * steer". Otherwise the turn has already ended underneath us and the caller
29143
+ * should start a new turn instead.
29144
+ *
29145
+ * @returns true when the prompt was injected; false when the caller should
29146
+ * fall back to starting a new turn.
29147
+ */
29148
+ async injectSteerIntoActiveTurn(params, turnId, sessionState) {
29149
+ try {
29150
+ await this.runWithProcessCheck(() => this.codexAcpClient.steerTurn({
29151
+ threadId: params.sessionId,
29152
+ turnId,
29153
+ prompt: params.prompt
29154
+ }));
29155
+ return true;
29156
+ } catch (err) {
29157
+ await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
29158
+ const turnStillActive = sessionState.currentTurnId === turnId;
29159
+ if (turnStillActive && !this.isNoActiveTurnToSteerError(err)) {
29160
+ throw err;
29161
+ }
29162
+ return false;
29163
+ }
29164
+ }
29165
+ /**
29166
+ * Starts a new turn from a steering prompt when there is no live turn to
29167
+ * inject into, and returns as soon as that turn is running.
29168
+ *
29169
+ * Waits for any previous prompt to drain first, then re-checks that the
29170
+ * session is not closing — the await above is a window during which a close
29171
+ * request can arrive.
29172
+ *
29173
+ * @param params The target session id and the prompt to steer with.
29174
+ * @returns "startedNewTurn" once the turn is running; throws if the prompt
29175
+ * fails or is cancelled before the turn starts.
29176
+ */
29177
+ async startNewTurnFromSteering(params) {
29178
+ const previousPrompt = this.activePrompts.get(params.sessionId);
29179
+ await previousPrompt?.completion;
29180
+ if (this.sessionIsClosing(params.sessionId)) {
29181
+ throw RequestError.invalidRequest(`Session ${params.sessionId} is closing`);
29182
+ }
29183
+ return await new Promise((resolve, reject) => {
29184
+ let turnStarted = false;
29185
+ const promptDone = this.prompt(params, void 0, () => {
29186
+ turnStarted = true;
29187
+ logger.log("Steering session started a new turn", { sessionId: params.sessionId });
29188
+ resolve({ outcome: "startedNewTurn" });
29189
+ });
29190
+ promptDone.then(
29191
+ (response) => {
29192
+ if (!turnStarted && response.stopReason === "cancelled") {
29193
+ reject(RequestError.invalidRequest(`Session ${params.sessionId} was cancelled before the steering turn started`));
29194
+ } else {
29195
+ resolve({ outcome: "startedNewTurn" });
29196
+ }
29197
+ },
29198
+ (error51) => {
29199
+ if (turnStarted) {
29200
+ logger.error(`Steering-started prompt for session ${params.sessionId} failed`, error51);
29201
+ } else {
29202
+ reject(error51);
29203
+ }
29204
+ }
29205
+ );
29206
+ });
29207
+ }
29208
+ isNoActiveTurnToSteerError(error51) {
29209
+ const messages = error51 instanceof Error ? [error51.message] : [];
29210
+ if (typeof error51 === "object" && error51 !== null && "data" in error51) {
29211
+ const data = error51.data;
29212
+ if (typeof data === "string") {
29213
+ messages.push(data);
29214
+ } else if (typeof data === "object" && data !== null && "details" in data) {
29215
+ const details = data.details;
29216
+ if (typeof details === "string") {
29217
+ messages.push(details);
29218
+ }
29219
+ }
29220
+ }
29221
+ return messages.some((message) => message.toLowerCase().includes("no active turn to steer"));
29222
+ }
29223
+ async getSteerableTurnId(sessionState) {
29224
+ if (this.sessionIsClosing(sessionState.sessionId)) {
29225
+ return null;
29226
+ }
29227
+ if (sessionState.currentTurnId) {
29228
+ return sessionState.currentTurnId;
29229
+ }
29230
+ const pendingTurnStart = this.pendingTurnStarts.get(sessionState.sessionId);
29231
+ if (!pendingTurnStart) {
29232
+ return null;
29233
+ }
29234
+ return await pendingTurnStart.promise;
29235
+ }
29236
+ parseSessionSteerParams(params) {
29237
+ const sessionId = params["sessionId"];
29238
+ const prompt = params["prompt"];
29239
+ if (typeof sessionId !== "string" || !Array.isArray(prompt)) {
29240
+ throw RequestError.invalidParams();
29241
+ }
29242
+ return {
29243
+ sessionId,
29244
+ prompt
29245
+ };
29246
+ }
28752
29247
  createSessionConfigOptions(sessionState) {
28753
29248
  const currentModelId = ModelId.fromString(sessionState.currentModelId);
28754
29249
  const configOptions = [
@@ -29037,7 +29532,7 @@ You have been logged out. Please try again.`);
29037
29532
  case "contextCompaction":
29038
29533
  return [createCompletedContextCompactionUpdate(item)];
29039
29534
  case "plan":
29040
- return [this.createPlanUpdate(item)];
29535
+ return [this.createPlanMessageUpdate(item)];
29041
29536
  }
29042
29537
  }
29043
29538
  createUserMessageUpdates(item) {
@@ -29078,15 +29573,12 @@ You have been logged out. Please try again.`);
29078
29573
  }
29079
29574
  };
29080
29575
  }
29081
- createPlanUpdate(item) {
29082
- return {
29083
- sessionUpdate: "agent_message_chunk",
29084
- content: {
29085
- type: "text",
29086
- text: `Plan:
29087
- ${item.text}`
29088
- }
29089
- };
29576
+ createPlanMessageUpdate(item) {
29577
+ return createAgentTextMessageChunk(
29578
+ item.text,
29579
+ item.id,
29580
+ createCodexMessagePhaseMeta("final_answer")
29581
+ );
29090
29582
  }
29091
29583
  userInputToContentBlocks(input) {
29092
29584
  switch (input.type) {
@@ -29356,7 +29848,7 @@ ${item.text}`
29356
29848
  }
29357
29849
  return turnId;
29358
29850
  }
29359
- async prompt(params, signal) {
29851
+ async prompt(params, signal, onTurnStarted) {
29360
29852
  logger.log("Prompt received", {
29361
29853
  sessionId: params.sessionId,
29362
29854
  prompt: params.prompt
@@ -29408,6 +29900,7 @@ ${item.text}`
29408
29900
  }
29409
29901
  sessionState.currentTurnId = turnId;
29410
29902
  pendingTurnStart?.resolve(turnId);
29903
+ onTurnStarted?.();
29411
29904
  },
29412
29905
  setConfigOption: async (configId, value) => {
29413
29906
  await this.applySessionConfigOption(sessionState, {
@@ -29491,6 +29984,7 @@ ${item.text}`
29491
29984
  }
29492
29985
  sessionState.currentTurnId = turnId;
29493
29986
  pendingTurnStart?.resolve(turnId);
29987
+ onTurnStarted?.();
29494
29988
  },
29495
29989
  () => this.promptShouldStop(params.sessionId, activePrompt)
29496
29990
  )
@@ -30018,6 +30512,9 @@ var CodexAppServerClient = class {
30018
30512
  async turnInterrupt(params) {
30019
30513
  return await this.sendRequest({ method: "turn/interrupt", params });
30020
30514
  }
30515
+ async turnSteer(params) {
30516
+ return await this.sendRequest({ method: "turn/steer", params });
30517
+ }
30021
30518
  async reviewStart(params) {
30022
30519
  return await this.sendRequest({ method: "review/start", params });
30023
30520
  }
@@ -30591,6 +31088,10 @@ var legacySetSessionModelParamsParser = external_exports.object({
30591
31088
  sessionId: external_exports.string(),
30592
31089
  modelId: external_exports.string()
30593
31090
  }).passthrough();
31091
+ var sessionSteerParamsParser = external_exports.object({
31092
+ sessionId: external_exports.string(),
31093
+ prompt: external_exports.array(external_exports.any())
31094
+ }).passthrough();
30594
31095
  var goalControlParamsParser = external_exports.object({
30595
31096
  sessionId: external_exports.string(),
30596
31097
  action: external_exports.enum(["pause", "clear"])
@@ -30667,5 +31168,5 @@ function startAcpServer() {
30667
31168
  codexAcpServer = null;
30668
31169
  }
30669
31170
  });
30670
- }).onRequest(methods.agent.initialize, (ctx) => getAgent().initialize(ctx.params)).onRequest(methods.agent.session.new, (ctx) => getAgent().newSession(ctx.params)).onRequest(methods.agent.session.load, (ctx) => getAgent().loadSession(ctx.params)).onRequest(methods.agent.session.list, (ctx) => getAgent().listSessions(ctx.params)).onRequest(methods.agent.session.delete, (ctx) => getAgent().deleteSession(ctx.params)).onRequest(methods.agent.session.resume, (ctx) => getAgent().resumeSession(ctx.params)).onRequest(methods.agent.session.close, (ctx) => getAgent().closeSession(ctx.params)).onRequest(methods.agent.session.setMode, (ctx) => getAgent().setSessionMode(ctx.params)).onRequest(methods.agent.session.setConfigOption, (ctx) => getAgent().setSessionConfigOption(ctx.params)).onRequest(methods.agent.authenticate, (ctx) => getAgent().authenticate(ctx.params)).onRequest(methods.agent.logout, (ctx) => getAgent().logout(ctx.params)).onRequest(methods.agent.providers.list, (ctx) => getAgent().listProviders(ctx.params)).onRequest(methods.agent.providers.set, (ctx) => getAgent().setProvider(ctx.params)).onRequest(methods.agent.providers.disable, (ctx) => getAgent().disableProvider(ctx.params)).onRequest(methods.agent.session.prompt, (ctx) => getAgent().prompt(ctx.params, ctx.signal)).onNotification(methods.agent.session.cancel, (ctx) => getAgent().cancel(ctx.params)).onRequest("authentication/status", emptyExtensionParamsParser, (ctx) => getAgent().extMethod("authentication/status", ctx.params)).onRequest("authentication/logout", emptyExtensionParamsParser, (ctx) => getAgent().extMethod("authentication/logout", ctx.params)).onRequest(LEGACY_SET_SESSION_MODEL_METHOD, legacySetSessionModelParamsParser, (ctx) => getAgent().extMethod(LEGACY_SET_SESSION_MODEL_METHOD, ctx.params)).onRequest(GOAL_CONTROL_METHOD, goalControlParamsParser, (ctx) => getAgent().extMethod(GOAL_CONTROL_METHOD, ctx.params)).connect(acpJsonStream);
31171
+ }).onRequest(methods.agent.initialize, (ctx) => getAgent().initialize(ctx.params)).onRequest(methods.agent.session.new, (ctx) => getAgent().newSession(ctx.params)).onRequest(methods.agent.session.load, (ctx) => getAgent().loadSession(ctx.params)).onRequest(methods.agent.session.list, (ctx) => getAgent().listSessions(ctx.params)).onRequest(methods.agent.session.delete, (ctx) => getAgent().deleteSession(ctx.params)).onRequest(methods.agent.session.resume, (ctx) => getAgent().resumeSession(ctx.params)).onRequest(methods.agent.session.close, (ctx) => getAgent().closeSession(ctx.params)).onRequest(methods.agent.session.setMode, (ctx) => getAgent().setSessionMode(ctx.params)).onRequest(methods.agent.session.setConfigOption, (ctx) => getAgent().setSessionConfigOption(ctx.params)).onRequest(methods.agent.authenticate, (ctx) => getAgent().authenticate(ctx.params)).onRequest(methods.agent.logout, (ctx) => getAgent().logout(ctx.params)).onRequest(methods.agent.providers.list, (ctx) => getAgent().listProviders(ctx.params)).onRequest(methods.agent.providers.set, (ctx) => getAgent().setProvider(ctx.params)).onRequest(methods.agent.providers.disable, (ctx) => getAgent().disableProvider(ctx.params)).onRequest(methods.agent.session.prompt, (ctx) => getAgent().prompt(ctx.params, ctx.signal)).onNotification(methods.agent.session.cancel, (ctx) => getAgent().cancel(ctx.params)).onRequest("authentication/status", emptyExtensionParamsParser, (ctx) => getAgent().extMethod("authentication/status", ctx.params)).onRequest("authentication/logout", emptyExtensionParamsParser, (ctx) => getAgent().extMethod("authentication/logout", ctx.params)).onRequest(LEGACY_SET_SESSION_MODEL_METHOD, legacySetSessionModelParamsParser, (ctx) => getAgent().extMethod(LEGACY_SET_SESSION_MODEL_METHOD, ctx.params)).onRequest(SESSION_STEERING_METHOD, sessionSteerParamsParser, (ctx) => getAgent().extMethod(SESSION_STEERING_METHOD, ctx.params)).onRequest(GOAL_CONTROL_METHOD, goalControlParamsParser, (ctx) => getAgent().extMethod(GOAL_CONTROL_METHOD, ctx.params)).connect(acpJsonStream);
30671
31172
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.1.4",
6
+ "version": "1.1.7",
7
7
  "description": "",
8
8
  "main": "dist/index.js",
9
9
  "bin": {
@@ -33,11 +33,13 @@
33
33
  "package:win-x64": "cd dist/bin && zip codex-acp-x64-windows.zip codex-acp-x64-windows.exe",
34
34
  "package:win-arm64": "cd dist/bin && zip codex-acp-arm64-windows.zip codex-acp-arm64-windows.exe",
35
35
  "start": "node --import tsx src/index.ts",
36
+ "example:steering": "node --import tsx examples/steering.ts",
37
+ "example:steering:multistep": "node --import tsx examples/steering.ts",
36
38
  "generate-types": "./node_modules/.bin/codex app-server generate-ts --out src/app-server",
37
39
  "test": "vitest run",
38
40
  "test:e2e": "npm run build && RUN_E2E_TESTS=true vitest run src/__tests__/CodexACPAgent/e2e",
39
41
  "test:watch": "vitest",
40
- "typecheck": "tsc --noEmit",
42
+ "typecheck": "tsc --noEmit && tsc --noEmit -p examples/tsconfig.json",
41
43
  "codex-test": "tsx .claude/skills/run-codex/scripts/run-codex-test.ts"
42
44
  },
43
45
  "homepage": "https://github.com/agentclientprotocol/codex-acp#readme",
@@ -56,13 +58,13 @@
56
58
  "@types/node": "^26.1.0",
57
59
  "esbuild": "^0.28.1",
58
60
  "mcp-hello-world": "^1.1.2",
59
- "tsx": "^4.23.0",
60
- "typescript": "^6.0.3",
61
+ "tsx": "^4.23.1",
62
+ "typescript": "^7.0.2",
61
63
  "vitest": "^4.1.10"
62
64
  },
63
65
  "dependencies": {
64
- "@agentclientprotocol/sdk": "^1.2.1",
65
- "@openai/codex": "^0.144.4",
66
+ "@agentclientprotocol/sdk": "^1.3.0",
67
+ "@openai/codex": "^0.145.0",
66
68
  "diff": "^9.0.0",
67
69
  "open": "^11.0.0",
68
70
  "vscode-jsonrpc": "^9.0.1",