@agentclientprotocol/codex-acp 1.1.2 → 1.1.4

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 (3) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +702 -88
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -12,6 +12,7 @@ Use [OpenAI Codex](https://github.com/openai/codex) from [Agent Client Protocol]
12
12
  - Model, reasoning effort, fast mode, approval, and sandbox mode configuration.
13
13
  - Text prompts, embedded context, images, resource links, and additional workspace directories.
14
14
  - Shell command, file change, permission request, MCP tool call, terminal output, reasoning, plan, web search, image generation, image view, token usage, and review events.
15
+ - Subagent launches as standard ACP tool calls, with Codex thread identity and activity details in namespaced `_meta.codex.subagent` metadata.
15
16
  - Client-provided MCP servers over command-based stdio config and HTTP transport.
16
17
  - Slash commands: `/status`, `/mcp`, `/skills`, `/review`, `/review-branch`, `/review-commit`, `/compact`, and `/logout`, as well as configured skills.
17
18
 
package/dist/index.js CHANGED
@@ -22592,6 +22592,7 @@ function createTerminalOutputMeta(mode, terminalId, data) {
22592
22592
  }
22593
22593
 
22594
22594
  // src/CodexToolCallMapper.ts
22595
+ var CONTEXT_COMPACTION_META = { contextCompaction: true };
22595
22596
  function toAcpStatus(status) {
22596
22597
  switch (status) {
22597
22598
  case "inProgress":
@@ -22734,6 +22735,35 @@ function createImageGenerationUpdate(item, options) {
22734
22735
  rawOutput: imageGenerationRawOutput(item)
22735
22736
  };
22736
22737
  }
22738
+ function createContextCompactionStartUpdate(item) {
22739
+ return {
22740
+ sessionUpdate: "tool_call",
22741
+ toolCallId: item.id,
22742
+ kind: "other",
22743
+ title: "Context compacting",
22744
+ status: "in_progress",
22745
+ _meta: CONTEXT_COMPACTION_META
22746
+ };
22747
+ }
22748
+ function createContextCompactionCompleteUpdate(item) {
22749
+ return {
22750
+ sessionUpdate: "tool_call_update",
22751
+ toolCallId: item.id,
22752
+ title: "Context compacted",
22753
+ status: "completed",
22754
+ _meta: CONTEXT_COMPACTION_META
22755
+ };
22756
+ }
22757
+ function createCompletedContextCompactionUpdate(item) {
22758
+ return {
22759
+ sessionUpdate: "tool_call",
22760
+ toolCallId: item.id,
22761
+ kind: "other",
22762
+ title: "Context compacted",
22763
+ status: "completed",
22764
+ _meta: CONTEXT_COMPACTION_META
22765
+ };
22766
+ }
22737
22767
  async function createExecuteToolCallUpdate(item, title, rawInput, rawOutput) {
22738
22768
  return {
22739
22769
  sessionUpdate: "tool_call",
@@ -22847,7 +22877,8 @@ function createCollabAgentToolCallUpdate(item) {
22847
22877
  kind: "other",
22848
22878
  title: item.tool,
22849
22879
  status: toAcpStatus(item.status),
22850
- rawInput: createCollabAgentToolCallRawInput(item)
22880
+ rawInput: createCollabAgentToolCallRawInput(item),
22881
+ _meta: createCollabAgentToolCallMeta(item)
22851
22882
  };
22852
22883
  }
22853
22884
  function createCollabAgentToolCallCompleteUpdate(item) {
@@ -22856,7 +22887,8 @@ function createCollabAgentToolCallCompleteUpdate(item) {
22856
22887
  toolCallId: item.id,
22857
22888
  title: item.tool,
22858
22889
  status: toAcpStatus(item.status),
22859
- rawInput: createCollabAgentToolCallRawInput(item)
22890
+ rawInput: createCollabAgentToolCallRawInput(item),
22891
+ _meta: createCollabAgentToolCallMeta(item)
22860
22892
  };
22861
22893
  }
22862
22894
  function createCollabAgentToolCallRawInput(item) {
@@ -22865,9 +22897,66 @@ function createCollabAgentToolCallRawInput(item) {
22865
22897
  senderThreadId: item.senderThreadId,
22866
22898
  receiverThreadIds: item.receiverThreadIds,
22867
22899
  agentsStates: item.agentsStates,
22900
+ model: item.model,
22901
+ reasoningEffort: item.reasoningEffort,
22868
22902
  status: item.status
22869
22903
  };
22870
22904
  }
22905
+ function createCollabAgentToolCallMeta(item) {
22906
+ return {
22907
+ codex: {
22908
+ collaboration: {
22909
+ tool: item.tool,
22910
+ senderThreadId: item.senderThreadId,
22911
+ receiverThreadIds: item.receiverThreadIds
22912
+ }
22913
+ }
22914
+ };
22915
+ }
22916
+ function createSubAgentActivityUpdate(item, status, sessionUpdate) {
22917
+ const name = item.agentPath.split("/").filter(Boolean).at(-1) ?? "subagent";
22918
+ const title = formatSubAgentActivityTitle(item.kind, name);
22919
+ const common = {
22920
+ toolCallId: item.id,
22921
+ status,
22922
+ rawInput: {
22923
+ agentThreadId: item.agentThreadId,
22924
+ agentPath: item.agentPath,
22925
+ activityKind: item.kind
22926
+ },
22927
+ _meta: {
22928
+ codex: {
22929
+ subagent: {
22930
+ threadId: item.agentThreadId,
22931
+ path: item.agentPath,
22932
+ activity: item.kind
22933
+ }
22934
+ }
22935
+ }
22936
+ };
22937
+ if (sessionUpdate === "tool_call") {
22938
+ return {
22939
+ sessionUpdate,
22940
+ title,
22941
+ kind: "other",
22942
+ ...common
22943
+ };
22944
+ }
22945
+ return {
22946
+ sessionUpdate,
22947
+ ...common
22948
+ };
22949
+ }
22950
+ function formatSubAgentActivityTitle(kind, name) {
22951
+ switch (kind) {
22952
+ case "started":
22953
+ return `Start subagent ${name}`;
22954
+ case "interacted":
22955
+ return `Interact with subagent ${name}`;
22956
+ case "interrupted":
22957
+ return `Interrupt subagent ${name}`;
22958
+ }
22959
+ }
22871
22960
  function formatWebSearchTitle(item) {
22872
22961
  const action = item.action;
22873
22962
  if (!action) {
@@ -23273,6 +23362,30 @@ function createAgentTextThoughtChunk(text, messageId, meta3) {
23273
23362
  return createAgentThoughtChunk({ type: "text", text }, messageId, meta3);
23274
23363
  }
23275
23364
 
23365
+ // src/AcpExtensions.ts
23366
+ var LEGACY_SET_SESSION_MODEL_METHOD = "session/set_model";
23367
+ var GOAL_CONTROL_METHOD = "_codex/session/goal_control";
23368
+ 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;
23370
+ }
23371
+
23372
+ // src/ThreadGoalSnapshot.ts
23373
+ function toThreadGoalSnapshot(goal) {
23374
+ return {
23375
+ objective: goal.objective.trim(),
23376
+ status: goal.status,
23377
+ tokenBudget: goal.tokenBudget,
23378
+ timeUsedSeconds: goal.timeUsedSeconds,
23379
+ createdAt: goal.createdAt,
23380
+ controlMethod: GOAL_CONTROL_METHOD
23381
+ };
23382
+ }
23383
+ function sameThreadGoalSnapshot(left, right) {
23384
+ if (left === void 0) return false;
23385
+ if (left === null || right === null) return left === right;
23386
+ return left.objective === right.objective && left.status === right.status && left.tokenBudget === right.tokenBudget && left.createdAt === right.createdAt;
23387
+ }
23388
+
23276
23389
  // src/CodexEventHandler.ts
23277
23390
  var CodexEventHandler = class {
23278
23391
  connection;
@@ -23286,6 +23399,7 @@ var CodexEventHandler = class {
23286
23399
  terminalCommandIds = /* @__PURE__ */ new Set();
23287
23400
  terminalCommandOutputIds = /* @__PURE__ */ new Set();
23288
23401
  agentMessagePhases = /* @__PURE__ */ new Map();
23402
+ activeSubAgentActivities = /* @__PURE__ */ new Set();
23289
23403
  constructor(connection, sessionState) {
23290
23404
  this.connection = connection;
23291
23405
  this.sessionState = sessionState;
@@ -23321,6 +23435,8 @@ var CodexEventHandler = class {
23321
23435
  case "thread/tokenUsage/updated":
23322
23436
  return this.createUsageUpdate(notification.params);
23323
23437
  case "thread/name/updated":
23438
+ this.sessionState.sessionTitle = notification.params.threadName ?? null;
23439
+ this.sessionState.sessionTitleSource = notification.params.threadName == null ? "unset" : "explicit";
23324
23440
  return {
23325
23441
  sessionUpdate: "session_info_update",
23326
23442
  title: notification.params.threadName ?? null
@@ -23451,8 +23567,9 @@ ${event.details}` : "";
23451
23567
  `);
23452
23568
  }
23453
23569
  createThreadGoalUpdatedEvent(event) {
23454
- const goalSnapshot = this.createThreadGoalSnapshot(event);
23455
- if (this.sameThreadGoalSnapshot(this.sessionState.currentGoal, goalSnapshot)) {
23570
+ this.sessionState.goalRevision += 1;
23571
+ const goalSnapshot = toThreadGoalSnapshot(event.goal);
23572
+ if (sameThreadGoalSnapshot(this.sessionState.currentGoal, goalSnapshot)) {
23456
23573
  return null;
23457
23574
  }
23458
23575
  this.sessionState.currentGoal = goalSnapshot;
@@ -23461,6 +23578,7 @@ ${event.details}` : "";
23461
23578
  });
23462
23579
  }
23463
23580
  createThreadGoalClearedEvent(_event) {
23581
+ this.sessionState.goalRevision += 1;
23464
23582
  if (this.sessionState.currentGoal === null) {
23465
23583
  return null;
23466
23584
  }
@@ -23469,16 +23587,6 @@ ${event.details}` : "";
23469
23587
  goal: null
23470
23588
  });
23471
23589
  }
23472
- createThreadGoalSnapshot(event) {
23473
- return {
23474
- objective: event.goal.objective.trim(),
23475
- status: event.goal.status,
23476
- tokenBudget: event.goal.tokenBudget
23477
- };
23478
- }
23479
- sameThreadGoalSnapshot(left, right) {
23480
- return left !== null && left !== void 0 && left.objective === right.objective && left.status === right.status && left.tokenBudget === right.tokenBudget;
23481
- }
23482
23590
  createReasoningDeltaEvent(event) {
23483
23591
  this.seenReasoningDeltaItemIds.add(event.itemId);
23484
23592
  return this.createAgentThoughtEvent(event.delta, event.itemId);
@@ -23520,14 +23628,17 @@ ${event.details}` : "";
23520
23628
  case "agentMessage":
23521
23629
  this.rememberAgentMessagePhase(event.item);
23522
23630
  return null;
23631
+ case "contextCompaction":
23632
+ return createContextCompactionStartUpdate(event.item);
23523
23633
  case "subAgentActivity":
23634
+ this.activeSubAgentActivities.add(event.item.id);
23635
+ return createSubAgentActivityUpdate(event.item, "in_progress", "tool_call");
23524
23636
  case "sleep":
23525
23637
  case "userMessage":
23526
23638
  case "hookPrompt":
23527
23639
  case "reasoning":
23528
23640
  case "enteredReviewMode":
23529
23641
  case "exitedReviewMode":
23530
- case "contextCompaction":
23531
23642
  case "plan":
23532
23643
  return null;
23533
23644
  }
@@ -23576,9 +23687,12 @@ ${event.details}` : "";
23576
23687
  case "exitedReviewMode":
23577
23688
  return this.createExitedReviewModeEvent(event.item);
23578
23689
  case "contextCompaction":
23579
- return this.createContextCompactedEvent();
23690
+ return createContextCompactionCompleteUpdate(event.item);
23580
23691
  //ignored types
23581
- case "subAgentActivity":
23692
+ case "subAgentActivity": {
23693
+ const sessionUpdate = this.activeSubAgentActivities.delete(event.item.id) ? "tool_call_update" : "tool_call";
23694
+ return createSubAgentActivityUpdate(event.item, "completed", sessionUpdate);
23695
+ }
23582
23696
  case "sleep":
23583
23697
  case "userMessage":
23584
23698
  case "hookPrompt":
@@ -23726,7 +23840,15 @@ ${event.stdin}
23726
23840
  }
23727
23841
  async createErrorEvent(params) {
23728
23842
  const error51 = params.error.codexErrorInfo;
23729
- if (error51 === "usageLimitExceeded") {
23843
+ if (params.willRetry) {
23844
+ return this.createCodexSessionInfoUpdate({
23845
+ error: {
23846
+ ...params.error,
23847
+ turnId: params.turnId,
23848
+ willRetry: true
23849
+ }
23850
+ });
23851
+ } else if (error51 === "usageLimitExceeded") {
23730
23852
  this.failure = RequestError.internalError(
23731
23853
  this.createTurnErrorData(params.error)
23732
23854
  );
@@ -24158,6 +24280,7 @@ var ELICITATION_OPTIONS = [
24158
24280
  { optionId: "accept", name: "Accept", kind: "allow_once" },
24159
24281
  { optionId: "decline", name: "Decline", kind: "reject_once" }
24160
24282
  ];
24283
+ var USER_INPUT_OTHER_FIELD_SUFFIX = "__other";
24161
24284
  function parsePersistOptions(meta3) {
24162
24285
  const result = /* @__PURE__ */ new Set();
24163
24286
  if (!meta3 || typeof meta3 !== "object") return result;
@@ -24293,6 +24416,27 @@ function elicitationResponseMeta(response, context, persist = void 0) {
24293
24416
  }
24294
24417
  return Object.keys(meta3).length === 0 ? null : meta3;
24295
24418
  }
24419
+ function userInputOtherFieldId(questionId, questionIds) {
24420
+ const base = `${questionId}${USER_INPUT_OTHER_FIELD_SUFFIX}`;
24421
+ if (!questionIds.has(base)) {
24422
+ return base;
24423
+ }
24424
+ let index = 1;
24425
+ while (questionIds.has(`${base}${index}`)) {
24426
+ index += 1;
24427
+ }
24428
+ return `${base}${index}`;
24429
+ }
24430
+ function userInputResponseValue(content, fieldId) {
24431
+ const value = content[fieldId];
24432
+ if (typeof value === "string" && value.trim() === "") {
24433
+ return void 0;
24434
+ }
24435
+ if (Array.isArray(value) && value.length === 0) {
24436
+ return void 0;
24437
+ }
24438
+ return value;
24439
+ }
24296
24440
  function buildToolApprovalOptions(persistOptions) {
24297
24441
  const options = [
24298
24442
  { optionId: McpApprovalOptionId.AllowOnce, name: "Allow", kind: "allow_once" }
@@ -24389,8 +24533,68 @@ var CodexElicitationHandler = class {
24389
24533
  return { action: "cancel", content: null, _meta: null };
24390
24534
  }
24391
24535
  }
24392
- requestOptions() {
24393
- return this.cancellationSignal ? { cancellationSignal: this.cancellationSignal } : void 0;
24536
+ async handleUserInput(params) {
24537
+ if (!clientSupportsFormElicitation(this.clientCapabilities)) {
24538
+ return { answers: {} };
24539
+ }
24540
+ try {
24541
+ const response = await this.requestUserInputElicitation(params);
24542
+ if (response === null) {
24543
+ return { answers: {} };
24544
+ }
24545
+ return this.convertUserInputResponse(response, params);
24546
+ } catch (error51) {
24547
+ logger.error("Error handling Codex user input request", error51);
24548
+ return { answers: {} };
24549
+ }
24550
+ }
24551
+ requestOptions(cancellationSignal = this.cancellationSignal) {
24552
+ return cancellationSignal ? { cancellationSignal } : void 0;
24553
+ }
24554
+ async requestUserInputElicitation(params) {
24555
+ const request = this.buildUserInputRequest(params);
24556
+ if (params.autoResolutionMs === null) {
24557
+ return await this.connection.request(
24558
+ methods.client.elicitation.create,
24559
+ request,
24560
+ this.requestOptions()
24561
+ );
24562
+ }
24563
+ const abortController = new AbortController();
24564
+ let timeout;
24565
+ let removeAbortListener;
24566
+ const timeoutPromise = new Promise((resolve) => {
24567
+ const resolveWithoutInput = () => {
24568
+ abortController.abort();
24569
+ resolve(null);
24570
+ };
24571
+ timeout = setTimeout(resolveWithoutInput, Math.max(0, params.autoResolutionMs ?? 0));
24572
+ if (this.cancellationSignal?.aborted) {
24573
+ resolveWithoutInput();
24574
+ return;
24575
+ }
24576
+ if (this.cancellationSignal) {
24577
+ this.cancellationSignal.addEventListener("abort", resolveWithoutInput, { once: true });
24578
+ removeAbortListener = () => {
24579
+ this.cancellationSignal?.removeEventListener("abort", resolveWithoutInput);
24580
+ };
24581
+ }
24582
+ });
24583
+ const requestPromise = Promise.resolve(this.connection.request(
24584
+ methods.client.elicitation.create,
24585
+ request,
24586
+ this.requestOptions(abortController.signal)
24587
+ ));
24588
+ void requestPromise.catch(() => {
24589
+ });
24590
+ try {
24591
+ return await Promise.race([requestPromise, timeoutPromise]);
24592
+ } finally {
24593
+ if (timeout) {
24594
+ clearTimeout(timeout);
24595
+ }
24596
+ removeAbortListener?.();
24597
+ }
24394
24598
  }
24395
24599
  createMcpElicitationContext(params) {
24396
24600
  const isToolApproval = isMcpToolCallApproval(params._meta);
@@ -24436,6 +24640,72 @@ var CodexElicitationHandler = class {
24436
24640
  };
24437
24641
  }
24438
24642
  }
24643
+ buildUserInputRequest(params) {
24644
+ const properties = {};
24645
+ const required2 = [];
24646
+ const questionIds = new Set(params.questions.map((question) => question.id));
24647
+ for (const question of params.questions) {
24648
+ const options = question.options ?? [];
24649
+ const hasOptions = options.length > 0;
24650
+ const hasOtherAnswer = question.isOther && hasOptions;
24651
+ const base = {
24652
+ title: question.header || question.id,
24653
+ description: question.question,
24654
+ _meta: {
24655
+ codex: {
24656
+ isOther: question.isOther,
24657
+ isSecret: question.isSecret
24658
+ }
24659
+ }
24660
+ };
24661
+ if (!hasOtherAnswer) {
24662
+ required2.push(question.id);
24663
+ }
24664
+ properties[question.id] = hasOptions ? {
24665
+ ...base,
24666
+ type: "string",
24667
+ oneOf: options.map((option) => ({
24668
+ const: option.label,
24669
+ title: option.label,
24670
+ description: option.description
24671
+ }))
24672
+ } : {
24673
+ ...base,
24674
+ type: "string"
24675
+ };
24676
+ if (hasOtherAnswer) {
24677
+ properties[userInputOtherFieldId(question.id, questionIds)] = {
24678
+ type: "string",
24679
+ title: "Other",
24680
+ description: "Type your own answer instead of choosing an option above.",
24681
+ _meta: {
24682
+ codex: {
24683
+ questionId: question.id,
24684
+ isOtherAnswer: true,
24685
+ isSecret: question.isSecret
24686
+ }
24687
+ }
24688
+ };
24689
+ }
24690
+ }
24691
+ const firstQuestion = params.questions[0];
24692
+ return {
24693
+ sessionId: this.sessionState.sessionId,
24694
+ toolCallId: params.itemId,
24695
+ mode: "form",
24696
+ message: params.questions.length === 1 && firstQuestion ? firstQuestion.question : "Input requested",
24697
+ requestedSchema: {
24698
+ type: "object",
24699
+ properties,
24700
+ required: required2
24701
+ },
24702
+ _meta: {
24703
+ codex: {
24704
+ autoResolutionMs: params.autoResolutionMs
24705
+ }
24706
+ }
24707
+ };
24708
+ }
24439
24709
  buildPermissionRequest(params, context) {
24440
24710
  const sessionId = this.sessionState.sessionId;
24441
24711
  const messageContent = {
@@ -24533,6 +24803,24 @@ var CodexElicitationHandler = class {
24533
24803
  }
24534
24804
  return { action: "cancel", content: null, _meta: null };
24535
24805
  }
24806
+ convertUserInputResponse(response, params) {
24807
+ if (!CreateElicitationResponse.isAccept(response)) {
24808
+ return { answers: {} };
24809
+ }
24810
+ const answers = {};
24811
+ const content = contentRecord(response.content);
24812
+ const questionIds = new Set(params.questions.map((question) => question.id));
24813
+ for (const question of params.questions) {
24814
+ const value = question.isOther && question.options != null && question.options.length > 0 ? userInputResponseValue(content, userInputOtherFieldId(question.id, questionIds)) ?? userInputResponseValue(content, question.id) : userInputResponseValue(content, question.id);
24815
+ if (value === void 0) {
24816
+ continue;
24817
+ }
24818
+ answers[question.id] = {
24819
+ answers: Array.isArray(value) ? value.map(String) : [String(value)]
24820
+ };
24821
+ }
24822
+ return { answers };
24823
+ }
24536
24824
  async publishAcceptedMcpToolApproval(context, accepted) {
24537
24825
  if (!accepted || context.correlatedCallId === void 0) {
24538
24826
  return;
@@ -25399,7 +25687,7 @@ var package_default = {
25399
25687
  publishConfig: {
25400
25688
  access: "public"
25401
25689
  },
25402
- version: "1.1.2",
25690
+ version: "1.1.4",
25403
25691
  description: "",
25404
25692
  main: "dist/index.js",
25405
25693
  bin: {
@@ -25458,7 +25746,7 @@ var package_default = {
25458
25746
  },
25459
25747
  dependencies: {
25460
25748
  "@agentclientprotocol/sdk": "^1.2.1",
25461
- "@openai/codex": "^0.144.0",
25749
+ "@openai/codex": "^0.144.4",
25462
25750
  diff: "^9.0.0",
25463
25751
  open: "^11.0.0",
25464
25752
  "vscode-jsonrpc": "^9.0.1",
@@ -25466,7 +25754,46 @@ var package_default = {
25466
25754
  }
25467
25755
  };
25468
25756
 
25757
+ // src/CollaborationModeConfig.ts
25758
+ var COLLABORATION_MODE_CONFIG_ID = "collaboration_mode";
25759
+ var DEFAULT_COLLABORATION_MODE = "default";
25760
+ var PLAN_COLLABORATION_MODE = "plan";
25761
+ function createCollaborationModeConfigOption(currentValue) {
25762
+ return {
25763
+ id: COLLABORATION_MODE_CONFIG_ID,
25764
+ name: "Collaboration mode",
25765
+ description: "How Codex collaborates for subsequent turns",
25766
+ category: "collaboration_mode",
25767
+ type: "select",
25768
+ currentValue,
25769
+ options: [
25770
+ { value: DEFAULT_COLLABORATION_MODE, name: "Default" },
25771
+ { value: PLAN_COLLABORATION_MODE, name: "Plan", description: "Plan before making changes" }
25772
+ ]
25773
+ };
25774
+ }
25775
+ function parseCollaborationMode(value) {
25776
+ if (value === DEFAULT_COLLABORATION_MODE) return DEFAULT_COLLABORATION_MODE;
25777
+ if (value === PLAN_COLLABORATION_MODE) return PLAN_COLLABORATION_MODE;
25778
+ return null;
25779
+ }
25780
+ function createCodexCollaborationMode(mode, currentModelId) {
25781
+ const modelId = ModelId.fromString(currentModelId);
25782
+ return {
25783
+ mode,
25784
+ settings: {
25785
+ model: modelId.model,
25786
+ reasoning_effort: modelId.effort,
25787
+ developer_instructions: null
25788
+ }
25789
+ };
25790
+ }
25791
+
25469
25792
  // src/CodexAcpClient.ts
25793
+ var CUSTOM_GATEWAY_PROVIDER_ID = "custom-gateway";
25794
+ var SUPPORTED_GATEWAY_PROTOCOLS = {
25795
+ openai: "responses"
25796
+ };
25470
25797
  var CodexAcpClient = class {
25471
25798
  codexClient;
25472
25799
  config;
@@ -25489,7 +25816,10 @@ var CodexAcpClient = class {
25489
25816
  };
25490
25817
  async initialize(request) {
25491
25818
  await this.codexClient.initialize({
25492
- capabilities: null,
25819
+ capabilities: {
25820
+ experimentalApi: true,
25821
+ requestAttestation: false
25822
+ },
25493
25823
  clientInfo: {
25494
25824
  name: request.clientInfo?.name ?? this.defaultClientInfo.name,
25495
25825
  version: request.clientInfo?.version ?? this.defaultClientInfo.version,
@@ -25501,6 +25831,7 @@ var CodexAcpClient = class {
25501
25831
  if (!isCodexAuthRequest(authRequest)) {
25502
25832
  throw RequestError.invalidRequest();
25503
25833
  }
25834
+ this.gatewayConfig = null;
25504
25835
  switch (authRequest.methodId) {
25505
25836
  case "api-key": {
25506
25837
  const apiKey = authRequest._meta?.["api-key"]?.apiKey ?? this.readApiKeyFromEnv();
@@ -25509,7 +25840,6 @@ var CodexAcpClient = class {
25509
25840
  case "chat-gpt": {
25510
25841
  const accountResponse = await this.codexClient.accountRead({ refreshToken: true });
25511
25842
  if (accountResponse.account?.type === "chatgpt") {
25512
- this.gatewayConfig = null;
25513
25843
  return true;
25514
25844
  }
25515
25845
  const loginCompletedPromise = this.awaitNextLoginCompleted();
@@ -25517,7 +25847,6 @@ var CodexAcpClient = class {
25517
25847
  if (loginResponse.type == "chatgpt") {
25518
25848
  await open_default(loginResponse.authUrl);
25519
25849
  }
25520
- this.gatewayConfig = null;
25521
25850
  const result = await loginCompletedPromise;
25522
25851
  return result.success;
25523
25852
  }
@@ -25525,25 +25854,14 @@ var CodexAcpClient = class {
25525
25854
  if (!authRequest._meta) throw RequestError.invalidRequest();
25526
25855
  const gatewaySettings = authRequest._meta["gateway"];
25527
25856
  if (!gatewaySettings) throw RequestError.invalidRequest();
25528
- const baseUrl = gatewaySettings.baseUrl;
25529
- const providerName = typeof gatewaySettings.providerName === "string" && gatewaySettings.providerName.trim().length > 0 ? gatewaySettings.providerName : "User-provided gateway";
25530
- const headers = {
25531
- "X-Client-Feature-ID": "codex",
25532
- ...gatewaySettings.headers
25533
- };
25534
- this.gatewayConfig = {
25535
- modelProvider: "custom-gateway",
25536
- config: {
25537
- name: providerName,
25538
- base_url: baseUrl,
25539
- http_headers: headers,
25540
- wire_api: "responses"
25541
- }
25542
- };
25857
+ this.applyGatewayConfig({
25858
+ baseUrl: gatewaySettings.baseUrl,
25859
+ apiType: GatewayAuthMethod._meta.gateway.protocol,
25860
+ headers: gatewaySettings.headers,
25861
+ providerName: gatewaySettings.providerName
25862
+ });
25543
25863
  return true;
25544
25864
  }
25545
- this.gatewayConfig = null;
25546
- return false;
25547
25865
  }
25548
25866
  async authenticateWithApiKey(apiKey) {
25549
25867
  const loginCompletedPromise = this.awaitNextLoginCompleted();
@@ -25551,7 +25869,6 @@ var CodexAcpClient = class {
25551
25869
  type: "apiKey",
25552
25870
  apiKey
25553
25871
  });
25554
- this.gatewayConfig = null;
25555
25872
  const result = await loginCompletedPromise;
25556
25873
  return result.success;
25557
25874
  }
@@ -25618,8 +25935,83 @@ var CodexAcpClient = class {
25618
25935
  const response = await this.codexClient.accountRead({ refreshToken: false });
25619
25936
  return response.requiresOpenaiAuth && !response.account;
25620
25937
  }
25621
- hasGatewayAuth() {
25622
- return this.gatewayConfig !== null;
25938
+ /**
25939
+ * Validates and stores custom gateway routing. Shared by the `gateway` auth
25940
+ * method and the ACP `providers/set` method. Throws `invalid_params` for an
25941
+ * unsupported protocol or a malformed base URL.
25942
+ */
25943
+ applyGatewayConfig(params) {
25944
+ const apiType = params.apiType;
25945
+ const wireApi = SUPPORTED_GATEWAY_PROTOCOLS[apiType];
25946
+ if (!wireApi) {
25947
+ throw RequestError.invalidParams(
25948
+ { apiType },
25949
+ `Unsupported provider apiType "${apiType}"; supported: ${Object.keys(SUPPORTED_GATEWAY_PROTOCOLS).join(", ")}`
25950
+ );
25951
+ }
25952
+ if (typeof params.baseUrl !== "string" || params.baseUrl.trim().length === 0) {
25953
+ throw RequestError.invalidParams(void 0, "baseUrl must be a non-empty string");
25954
+ }
25955
+ const providerName = typeof params.providerName === "string" && params.providerName.trim().length > 0 ? params.providerName : "User-provided gateway";
25956
+ const headers = {
25957
+ "X-Client-Feature-ID": "codex",
25958
+ ...params.headers
25959
+ };
25960
+ this.gatewayConfig = {
25961
+ modelProvider: CUSTOM_GATEWAY_PROVIDER_ID,
25962
+ config: {
25963
+ name: providerName,
25964
+ base_url: params.baseUrl,
25965
+ http_headers: headers,
25966
+ wire_api: wireApi
25967
+ }
25968
+ };
25969
+ }
25970
+ /**
25971
+ * `providers/list`: returns the single client-configurable custom gateway
25972
+ * provider. `current` carries only non-secret routing (never headers), and is
25973
+ * `null` when the provider is not configured/disabled.
25974
+ */
25975
+ listProviders() {
25976
+ const gatewayConfig = this.gatewayConfig;
25977
+ const current = gatewayConfig ? {
25978
+ apiType: gatewayApiTypeFromConfig(gatewayConfig),
25979
+ baseUrl: gatewayConfig.config.base_url
25980
+ } : null;
25981
+ return [
25982
+ {
25983
+ providerId: CUSTOM_GATEWAY_PROVIDER_ID,
25984
+ supported: Object.keys(SUPPORTED_GATEWAY_PROTOCOLS),
25985
+ required: false,
25986
+ current
25987
+ }
25988
+ ];
25989
+ }
25990
+ /**
25991
+ * `providers/set`: replaces the full configuration for the custom gateway
25992
+ * provider. Rejects unknown provider ids with `invalid_params`.
25993
+ */
25994
+ setProvider(request) {
25995
+ if (request.providerId !== CUSTOM_GATEWAY_PROVIDER_ID) {
25996
+ throw RequestError.invalidParams(
25997
+ { providerId: request.providerId },
25998
+ `Unknown providerId "${request.providerId}"; only "${CUSTOM_GATEWAY_PROVIDER_ID}" is configurable`
25999
+ );
26000
+ }
26001
+ this.applyGatewayConfig({
26002
+ apiType: request.apiType,
26003
+ baseUrl: request.baseUrl,
26004
+ headers: request.headers
26005
+ });
26006
+ }
26007
+ /**
26008
+ * `providers/disable`: disables the custom gateway provider. Disabling an
26009
+ * unknown provider id is idempotent success (RFD behavior §7).
26010
+ */
26011
+ disableProvider(request) {
26012
+ if (request.providerId === CUSTOM_GATEWAY_PROVIDER_ID) {
26013
+ this.gatewayConfig = null;
26014
+ }
25623
26015
  }
25624
26016
  async getAccount() {
25625
26017
  return this.codexClient.accountRead({ refreshToken: false });
@@ -25640,6 +26032,7 @@ var CodexAcpClient = class {
25640
26032
  sessionId: request.sessionId,
25641
26033
  currentModelId,
25642
26034
  models: codexModels,
26035
+ collaborationMode: this.getCollaborationMode(response.thread.id),
25643
26036
  modelProvider: response.modelProvider,
25644
26037
  currentServiceTier: response.serviceTier ?? null,
25645
26038
  additionalDirectories
@@ -25665,6 +26058,7 @@ var CodexAcpClient = class {
25665
26058
  sessionId: request.sessionId,
25666
26059
  currentModelId,
25667
26060
  models: codexModels,
26061
+ collaborationMode: this.getCollaborationMode(response.thread.id),
25668
26062
  modelProvider: response.modelProvider,
25669
26063
  currentServiceTier: response.serviceTier ?? null,
25670
26064
  thread: historyResponse.thread,
@@ -25688,6 +26082,7 @@ var CodexAcpClient = class {
25688
26082
  sessionId: response.thread.id,
25689
26083
  currentModelId,
25690
26084
  models: codexModels,
26085
+ collaborationMode: this.getCollaborationMode(response.thread.id),
25691
26086
  modelProvider: response.modelProvider,
25692
26087
  currentServiceTier: response.serviceTier ?? null,
25693
26088
  additionalDirectories
@@ -25713,6 +26108,10 @@ var CodexAcpClient = class {
25713
26108
  async runCompact(sessionId) {
25714
26109
  await this.codexClient.runCompact({ threadId: sessionId });
25715
26110
  }
26111
+ async getGoal(sessionId) {
26112
+ const response = await this.codexClient.threadGoalGet({ threadId: sessionId });
26113
+ return response?.goal ?? null;
26114
+ }
25716
26115
  async setGoal(sessionId, objective, onTurnStarted) {
25717
26116
  return await this.codexClient.runGoalSet({
25718
26117
  threadId: sessionId,
@@ -25721,10 +26120,17 @@ var CodexAcpClient = class {
25721
26120
  }, onTurnStarted);
25722
26121
  }
25723
26122
  async setGoalStatus(sessionId, status) {
26123
+ let updatedGoal = null;
25724
26124
  await this.codexClient.runGoalSet({
25725
26125
  threadId: sessionId,
25726
26126
  status
26127
+ }, void 0, void 0, (goal) => {
26128
+ updatedGoal = goal;
25727
26129
  });
26130
+ if (updatedGoal === null) {
26131
+ throw new Error(`Goal update for session ${sessionId} returned no goal`);
26132
+ }
26133
+ return updatedGoal;
25728
26134
  }
25729
26135
  async resumeGoal(sessionId, onTurnStarted) {
25730
26136
  return await this.codexClient.runGoalSet({
@@ -25861,6 +26267,10 @@ var CodexAcpClient = class {
25861
26267
  handleElicitation: async (params) => {
25862
26268
  await this.waitForSessionNotifications(sessionId);
25863
26269
  return await elicitationHandler.handleElicitation(params);
26270
+ },
26271
+ handleUserInput: async (params) => {
26272
+ await this.waitForSessionNotifications(sessionId);
26273
+ return await elicitationHandler.handleUserInput(params);
25864
26274
  }
25865
26275
  });
25866
26276
  }
@@ -25906,6 +26316,15 @@ var CodexAcpClient = class {
25906
26316
  serviceTier
25907
26317
  }, onTurnStarted);
25908
26318
  }
26319
+ async setCollaborationMode(sessionId, mode, currentModelId) {
26320
+ await this.codexClient.threadSettingsUpdate({
26321
+ threadId: sessionId,
26322
+ collaborationMode: createCodexCollaborationMode(mode, currentModelId)
26323
+ });
26324
+ }
26325
+ getCollaborationMode(sessionId) {
26326
+ return this.codexClient.getThreadSettings(sessionId)?.collaborationMode.mode ?? "default";
26327
+ }
25909
26328
  resolveTurnInterrupted(params) {
25910
26329
  this.codexClient.resolveTurnInterrupted(params.threadId, params.turnId);
25911
26330
  }
@@ -26023,7 +26442,7 @@ var CodexAcpClient = class {
26023
26442
  const [allProviders, archivedAllProviders, customGateway] = await Promise.all([
26024
26443
  this.codexClient.threadList({}),
26025
26444
  this.codexClient.threadList({ archived: true }),
26026
- this.codexClient.threadList({ modelProviders: ["custom-gateway"] })
26445
+ this.codexClient.threadList({ modelProviders: [CUSTOM_GATEWAY_PROVIDER_ID] })
26027
26446
  ]);
26028
26447
  return {
26029
26448
  allProviders: {
@@ -26176,6 +26595,11 @@ function arraysEqual(left, right) {
26176
26595
  function isJsonObject(value) {
26177
26596
  return value !== null && typeof value === "object" && !Array.isArray(value);
26178
26597
  }
26598
+ function gatewayApiTypeFromConfig(gatewayConfig) {
26599
+ const wireApi = gatewayConfig.config.wire_api;
26600
+ const match = Object.entries(SUPPORTED_GATEWAY_PROTOCOLS).find(([, wire]) => wire === wireApi);
26601
+ return match?.[0] ?? "openai";
26602
+ }
26179
26603
  function mergeGatewayConfig(config2, gatewayConfig) {
26180
26604
  if (gatewayConfig !== null) {
26181
26605
  const newConfig = { ...config2 };
@@ -26194,6 +26618,9 @@ function mergeGatewayConfig(config2, gatewayConfig) {
26194
26618
  // src/ModelConfigOption.ts
26195
26619
  var MODEL_CONFIG_ID = "model";
26196
26620
  var REASONING_EFFORT_CONFIG_ID = "reasoning_effort";
26621
+ function capitalize(value) {
26622
+ return value.charAt(0).toUpperCase() + value.slice(1);
26623
+ }
26197
26624
  function findSupportedEffort(options, effort) {
26198
26625
  if (!effort) return void 0;
26199
26626
  return options.find((o) => o.reasoningEffort === effort)?.reasoningEffort;
@@ -26231,7 +26658,7 @@ function createReasoningEffortConfigOption(supportedReasoningEfforts, currentEff
26231
26658
  currentValue: currentEffort,
26232
26659
  options: supportedReasoningEfforts.map((option) => ({
26233
26660
  value: option.reasoningEffort,
26234
- name: option.reasoningEffort,
26661
+ name: capitalize(option.reasoningEffort),
26235
26662
  description: option.description
26236
26663
  }))
26237
26664
  };
@@ -26295,6 +26722,20 @@ var CodexCommands = class {
26295
26722
  */
26296
26723
  getBuiltinCommands() {
26297
26724
  return [
26725
+ {
26726
+ name: "plan",
26727
+ description: "Turn plan mode on.",
26728
+ input: null,
26729
+ _meta: {
26730
+ commandAction: {
26731
+ kind: "setConfigOption",
26732
+ configId: COLLABORATION_MODE_CONFIG_ID,
26733
+ value: PLAN_COLLABORATION_MODE,
26734
+ resetValue: DEFAULT_COLLABORATION_MODE,
26735
+ presentation: "state"
26736
+ }
26737
+ }
26738
+ },
26298
26739
  {
26299
26740
  name: "mcp",
26300
26741
  description: "List configured Model Context Protocol (MCP) tools.",
@@ -26332,8 +26773,14 @@ var CodexCommands = class {
26332
26773
  },
26333
26774
  {
26334
26775
  name: "goal",
26335
- description: "Set, pause, resume, or clear a task goal.",
26336
- input: { hint: "[<objective>|clear|pause|resume]" }
26776
+ description: "Set a goal to keep pursuing.",
26777
+ input: { hint: "[<objective>|clear|pause|resume]" },
26778
+ _meta: {
26779
+ commandAction: {
26780
+ kind: "prefixPrompt",
26781
+ presentation: "state"
26782
+ }
26783
+ }
26337
26784
  },
26338
26785
  {
26339
26786
  name: "logout",
@@ -26363,6 +26810,15 @@ var CodexCommands = class {
26363
26810
  if (commandName.startsWith("$")) return { handled: false };
26364
26811
  const sessionId = sessionState.sessionId;
26365
26812
  switch (commandName) {
26813
+ case "plan": {
26814
+ if (command.rest.length > 0) {
26815
+ await this.sendCommandUsageMessage(commandName, "no arguments", sessionId);
26816
+ return { handled: true };
26817
+ }
26818
+ const mode = sessionState.collaborationMode === PLAN_COLLABORATION_MODE ? DEFAULT_COLLABORATION_MODE : PLAN_COLLABORATION_MODE;
26819
+ await options.setConfigOption?.(COLLABORATION_MODE_CONFIG_ID, mode);
26820
+ return { handled: options.setConfigOption !== void 0 };
26821
+ }
26366
26822
  case "compact": {
26367
26823
  await this.runWithProcessCheck(() => this.codexAcpClient.runCompact(sessionId));
26368
26824
  return { handled: true };
@@ -26438,8 +26894,7 @@ var CodexCommands = class {
26438
26894
  return { handled: true };
26439
26895
  }
26440
26896
  default:
26441
- await this.sendUnknownCommandMessage(commandName, sessionId);
26442
- return { handled: true };
26897
+ return { handled: false };
26443
26898
  }
26444
26899
  }
26445
26900
  async runReviewCommand(sessionState, target, options) {
@@ -26518,18 +26973,6 @@ var CodexCommands = class {
26518
26973
  const session = new ACPSessionConnection(this.connection, sessionId);
26519
26974
  await session.update(createAgentTextMessageChunk(`Command "/${name}" requires ${inputHint}.`));
26520
26975
  }
26521
- async sendUnknownCommandMessage(name, sessionId) {
26522
- const lines = this.getBuiltinCommands().map((command) => `- /${command.name}: ${command.description}`);
26523
- const text = [
26524
- `Unknown command "/${name}".`,
26525
- "Available commands:"
26526
- ];
26527
- if (lines.length > 0) {
26528
- text.push(...lines);
26529
- }
26530
- const session = new ACPSessionConnection(this.connection, sessionId);
26531
- await session.update(createAgentTextMessageChunk(text.join("\n")));
26532
- }
26533
26976
  buildStatusMessage(sessionState) {
26534
26977
  const agentMode = sessionState.agentMode;
26535
26978
  const accountText = this.formatAccountInfo(sessionState.account);
@@ -26799,6 +27242,7 @@ function toolCallIdFromThreadItem(item) {
26799
27242
  case "webSearch":
26800
27243
  case "imageView":
26801
27244
  case "imageGeneration":
27245
+ case "contextCompaction":
26802
27246
  return item.id;
26803
27247
  case "userMessage":
26804
27248
  case "hookPrompt":
@@ -26808,7 +27252,6 @@ function toolCallIdFromThreadItem(item) {
26808
27252
  case "subAgentActivity":
26809
27253
  case "enteredReviewMode":
26810
27254
  case "exitedReviewMode":
26811
- case "contextCompaction":
26812
27255
  case "sleep":
26813
27256
  return null;
26814
27257
  }
@@ -27625,12 +28068,6 @@ function numberValue(value) {
27625
28068
  return typeof value === "number" && Number.isFinite(value) ? value : null;
27626
28069
  }
27627
28070
 
27628
- // src/AcpExtensions.ts
27629
- var LEGACY_SET_SESSION_MODEL_METHOD = "session/set_model";
27630
- function isExtMethodRequest(request) {
27631
- return request.method === "authentication/status" || request.method === "authentication/logout" || request.method === LEGACY_SET_SESSION_MODEL_METHOD;
27632
- }
27633
-
27634
28071
  // src/FastModeConfig.ts
27635
28072
  var FAST_MODE_CONFIG_ID = "fast-mode";
27636
28073
  var FAST_MODE_CATEGORY = "model_config";
@@ -27756,6 +28193,7 @@ var CodexAcpServer = class _CodexAcpServer {
27756
28193
  auth: {
27757
28194
  logout: {}
27758
28195
  },
28196
+ providers: {},
27759
28197
  loadSession: true,
27760
28198
  promptCapabilities: {
27761
28199
  embeddedContext: true,
@@ -27791,6 +28229,25 @@ var CodexAcpServer = class _CodexAcpServer {
27791
28229
  }
27792
28230
  case LEGACY_SET_SESSION_MODEL_METHOD:
27793
28231
  return await this.unstable_setSessionModel(this.parseLegacySetSessionModelParams(methodRequest.params));
28232
+ case GOAL_CONTROL_METHOD: {
28233
+ const sessionState = this.sessions.get(methodRequest.params.sessionId);
28234
+ if (!sessionState) {
28235
+ throw RequestError.invalidParams(void 0, `Unknown session: ${methodRequest.params.sessionId}`);
28236
+ }
28237
+ const sessionGeneration = this.getSessionGeneration(sessionState.sessionId);
28238
+ if (methodRequest.params.action === "pause") {
28239
+ const goal = await this.runWithProcessCheck(() => this.codexAcpClient.setGoalStatus(sessionState.sessionId, "paused"));
28240
+ if (this.goalPublishIsCurrent(sessionState, sessionGeneration)) {
28241
+ await this.publishGoalSnapshot(sessionState, toThreadGoalSnapshot(goal), false);
28242
+ }
28243
+ } else if (methodRequest.params.action === "clear") {
28244
+ await this.runWithProcessCheck(() => this.codexAcpClient.clearGoal(sessionState.sessionId));
28245
+ if (this.goalPublishIsCurrent(sessionState, sessionGeneration)) {
28246
+ await this.publishGoalSnapshot(sessionState, null, false);
28247
+ }
28248
+ }
28249
+ return {};
28250
+ }
27794
28251
  }
27795
28252
  }
27796
28253
  async checkAuthorization() {
@@ -27932,6 +28389,7 @@ You have been logged out. Please try again.`);
27932
28389
  supportedReasoningEfforts: currentModel?.supportedReasoningEfforts ?? [],
27933
28390
  supportedInputModalities: currentModel?.inputModalities ?? ["text", "image"],
27934
28391
  agentMode: AgentMode.getInitialAgentMode(),
28392
+ collaborationMode: sessionMetadata.collaborationMode,
27935
28393
  currentTurnId: null,
27936
28394
  lastTokenUsage: null,
27937
28395
  totalTokenUsage: null,
@@ -27945,7 +28403,10 @@ You have been logged out. Please try again.`);
27945
28403
  fastModeEnabled: sessionMetadata.currentServiceTier === "fast",
27946
28404
  currentModelSupportsFast,
27947
28405
  sessionMcpServers,
27948
- terminalOutputMode: this.terminalOutputMode
28406
+ terminalOutputMode: this.terminalOutputMode,
28407
+ goalRevision: 0,
28408
+ sessionTitle: null,
28409
+ sessionTitleSource: "sessionId" in request ? "unknown" : "unset"
27949
28410
  };
27950
28411
  this.sessions.set(sessionId, sessionState);
27951
28412
  resumeSubscribed = false;
@@ -27957,6 +28418,9 @@ You have been logged out. Please try again.`);
27957
28418
  this.publishMcpStartupStatusAsync(sessionId);
27958
28419
  }
27959
28420
  this.publishAvailableCommandsAsync(sessionState);
28421
+ if ("sessionId" in request) {
28422
+ this.publishCurrentGoalAsync(sessionState, sessionGeneration);
28423
+ }
27960
28424
  const sessionModelState = this.createModelState(models, currentModelId);
27961
28425
  const sessionModeState = sessionState.agentMode.toSessionModeState();
27962
28426
  return [sessionId, sessionModelState, sessionModeState];
@@ -28126,6 +28590,17 @@ You have been logged out. Please try again.`);
28126
28590
  await this.refreshSessionsAuthState(null);
28127
28591
  logger.log("Logout request completed");
28128
28592
  }
28593
+ listProviders(_params) {
28594
+ return { providers: this.codexAcpClient.listProviders() };
28595
+ }
28596
+ setProvider(params) {
28597
+ this.codexAcpClient.setProvider(params);
28598
+ return {};
28599
+ }
28600
+ disableProvider(params) {
28601
+ this.codexAcpClient.disableProvider(params);
28602
+ return {};
28603
+ }
28129
28604
  async refreshSessionsAuthState(authProvider) {
28130
28605
  if (this.sessions.size === 0) return;
28131
28606
  const sessionsToRefresh = [...this.sessions.values()].filter((sessionState) => this.authProvidersMatch(sessionState.authProvider, authProvider));
@@ -28153,6 +28628,12 @@ You have been logged out. Please try again.`);
28153
28628
  });
28154
28629
  const sessionState = this.sessions.get(params.sessionId);
28155
28630
  if (!sessionState) throw new Error(`Session ${params.sessionId} not found`);
28631
+ await this.applySessionConfigOption(sessionState, params);
28632
+ return {
28633
+ configOptions: this.createSessionConfigOptions(sessionState)
28634
+ };
28635
+ }
28636
+ async applySessionConfigOption(sessionState, params) {
28156
28637
  switch (params.configId) {
28157
28638
  case FAST_MODE_CONFIG_ID:
28158
28639
  this.applyFastModeChange(sessionState, params);
@@ -28160,6 +28641,9 @@ You have been logged out. Please try again.`);
28160
28641
  case MODE_CONFIG_ID:
28161
28642
  this.applyModeChange(sessionState, this.stringConfigValue(params));
28162
28643
  break;
28644
+ case COLLABORATION_MODE_CONFIG_ID:
28645
+ await this.applyCollaborationModeChange(sessionState, this.stringConfigValue(params));
28646
+ break;
28163
28647
  case MODEL_CONFIG_ID:
28164
28648
  this.applyModelChange(sessionState, this.stringConfigValue(params));
28165
28649
  break;
@@ -28169,9 +28653,6 @@ You have been logged out. Please try again.`);
28169
28653
  default:
28170
28654
  throw RequestError.invalidParams();
28171
28655
  }
28172
- return {
28173
- configOptions: this.createSessionConfigOptions(sessionState)
28174
- };
28175
28656
  }
28176
28657
  applyFastModeChange(sessionState, params) {
28177
28658
  const value = params.value;
@@ -28197,6 +28678,14 @@ You have been logged out. Please try again.`);
28197
28678
  }
28198
28679
  sessionState.agentMode = newMode;
28199
28680
  }
28681
+ async applyCollaborationModeChange(sessionState, value) {
28682
+ const mode = parseCollaborationMode(value);
28683
+ if (mode === null) {
28684
+ throw RequestError.invalidParams();
28685
+ }
28686
+ await this.codexAcpClient.setCollaborationMode(sessionState.sessionId, mode, sessionState.currentModelId);
28687
+ sessionState.collaborationMode = mode;
28688
+ }
28200
28689
  applyModelChange(sessionState, value) {
28201
28690
  const model = sessionState.availableModels.find((m) => m.id === value);
28202
28691
  if (!model) {
@@ -28264,6 +28753,7 @@ You have been logged out. Please try again.`);
28264
28753
  const currentModelId = ModelId.fromString(sessionState.currentModelId);
28265
28754
  const configOptions = [
28266
28755
  sessionState.agentMode.toConfigOption(),
28756
+ createCollaborationModeConfigOption(sessionState.collaborationMode),
28267
28757
  createModelConfigOption(sessionState.availableModels, currentModelId.model)
28268
28758
  ];
28269
28759
  if (sessionState.supportedReasoningEfforts.length > 0) {
@@ -28293,6 +28783,46 @@ You have been logged out. Please try again.`);
28293
28783
  publishAvailableCommandsAsync(sessionState) {
28294
28784
  void this.availableCommands.publish(sessionState);
28295
28785
  }
28786
+ publishCurrentGoalAsync(sessionState, sessionGeneration) {
28787
+ void this.publishCurrentGoalBestEffort(sessionState, sessionGeneration, true);
28788
+ }
28789
+ async publishCurrentGoalBestEffort(sessionState, sessionGeneration, force) {
28790
+ try {
28791
+ await this.publishCurrentGoal(sessionState, sessionGeneration, force);
28792
+ } catch (err) {
28793
+ logger.error(`Failed to publish current goal for session ${sessionState.sessionId}`, err);
28794
+ }
28795
+ }
28796
+ async publishCurrentGoal(sessionState, sessionGeneration, force) {
28797
+ const requestRevision = ++sessionState.goalRevision;
28798
+ const goal = await this.runWithProcessCheck(() => this.codexAcpClient.getGoal(sessionState.sessionId));
28799
+ const snapshot = goal === null ? null : toThreadGoalSnapshot(goal);
28800
+ if (!this.goalPublishIsCurrent(sessionState, sessionGeneration) || sessionState.goalRevision !== requestRevision) {
28801
+ return;
28802
+ }
28803
+ await this.publishGoalSnapshot(sessionState, snapshot, force, false);
28804
+ }
28805
+ goalPublishIsCurrent(sessionState, sessionGeneration) {
28806
+ return this.sessions.get(sessionState.sessionId) === sessionState && this.getSessionGeneration(sessionState.sessionId) === sessionGeneration && !this.sessionIsClosing(sessionState.sessionId);
28807
+ }
28808
+ async publishGoalSnapshot(sessionState, snapshot, force, incrementRevision = true) {
28809
+ if (incrementRevision) {
28810
+ sessionState.goalRevision += 1;
28811
+ }
28812
+ if (!force && sameThreadGoalSnapshot(sessionState.currentGoal, snapshot)) {
28813
+ return;
28814
+ }
28815
+ sessionState.currentGoal = snapshot;
28816
+ const session = new ACPSessionConnection(this.connection, sessionState.sessionId);
28817
+ await session.update({
28818
+ sessionUpdate: "session_info_update",
28819
+ _meta: {
28820
+ codex: {
28821
+ goal: snapshot
28822
+ }
28823
+ }
28824
+ });
28825
+ }
28296
28826
  findCurrentModel(models, currentModelId) {
28297
28827
  const modelId = ModelId.fromString(currentModelId);
28298
28828
  return models.find((m) => m.id === modelId.model);
@@ -28358,6 +28888,7 @@ You have been logged out. Please try again.`);
28358
28888
  supportedReasoningEfforts: currentModel?.supportedReasoningEfforts ?? [],
28359
28889
  supportedInputModalities: currentModel?.inputModalities ?? ["text", "image"],
28360
28890
  agentMode: AgentMode.getInitialAgentMode(),
28891
+ collaborationMode: sessionMetadata.collaborationMode,
28361
28892
  currentTurnId: null,
28362
28893
  lastTokenUsage: null,
28363
28894
  totalTokenUsage: null,
@@ -28371,7 +28902,10 @@ You have been logged out. Please try again.`);
28371
28902
  fastModeEnabled: sessionMetadata.currentServiceTier === "fast",
28372
28903
  currentModelSupportsFast,
28373
28904
  sessionMcpServers,
28374
- terminalOutputMode: this.terminalOutputMode
28905
+ terminalOutputMode: this.terminalOutputMode,
28906
+ goalRevision: 0,
28907
+ sessionTitle: null,
28908
+ sessionTitleSource: "unset"
28375
28909
  };
28376
28910
  this.sessions.set(sessionId, sessionState);
28377
28911
  subscribed = false;
@@ -28383,6 +28917,7 @@ You have been logged out. Please try again.`);
28383
28917
  this.publishMcpStartupStatusAsync(sessionId);
28384
28918
  }
28385
28919
  await this.availableCommands.publish(sessionState);
28920
+ await this.publishCurrentGoalBestEffort(sessionState, requestedSessionGeneration, true);
28386
28921
  const sessionModelState = this.createModelState(models, currentModelId);
28387
28922
  const sessionModeState = sessionState.agentMode.toSessionModeState();
28388
28923
  return {
@@ -28395,6 +28930,7 @@ You have been logged out. Please try again.`);
28395
28930
  async streamThreadHistory(sessionId, thread) {
28396
28931
  const session = new ACPSessionConnection(this.connection, sessionId);
28397
28932
  const sessionState = this.getSessionState(sessionId);
28933
+ await this.publishThreadHistoryTitle(session, sessionState, thread);
28398
28934
  const responseItemFallbackUpdates = await createResponseItemHistoryFallbackUpdates(
28399
28935
  thread,
28400
28936
  sessionState.terminalOutputMode
@@ -28411,14 +28947,56 @@ You have been logged out. Please try again.`);
28411
28947
  await session.update(update);
28412
28948
  }
28413
28949
  }
28950
+ async publishThreadHistoryTitle(session, sessionState, thread) {
28951
+ const explicitTitle = this.normalizeSessionTitle(thread.name);
28952
+ if (explicitTitle) {
28953
+ sessionState.sessionTitle = explicitTitle;
28954
+ sessionState.sessionTitleSource = "explicit";
28955
+ await session.update({
28956
+ sessionUpdate: "session_info_update",
28957
+ title: explicitTitle
28958
+ });
28959
+ return;
28960
+ }
28961
+ const historyTitle = this.findFirstUserMessageTitle(thread) ?? this.normalizeSessionTitle(thread.preview);
28962
+ await this.publishFallbackSessionTitle(sessionState, historyTitle);
28963
+ }
28964
+ findFirstUserMessageTitle(thread) {
28965
+ for (const turn of thread.turns) {
28966
+ for (const item of turn.items) {
28967
+ if (item.type !== "userMessage") continue;
28968
+ const title = this.normalizeSessionTitle(item.content.filter((input) => input.type === "text").map((input) => input.text).join(" "));
28969
+ if (title) return title;
28970
+ }
28971
+ }
28972
+ return null;
28973
+ }
28974
+ async publishFallbackSessionTitle(sessionState, title) {
28975
+ if (sessionState.sessionTitleSource !== "unset" || !title) return;
28976
+ sessionState.sessionTitle = title;
28977
+ sessionState.sessionTitleSource = "fallback";
28978
+ const session = new ACPSessionConnection(this.connection, sessionState.sessionId);
28979
+ await session.update({
28980
+ sessionUpdate: "session_info_update",
28981
+ title
28982
+ });
28983
+ }
28984
+ createPromptFallbackTitle(prompt) {
28985
+ return this.normalizeSessionTitle(prompt.filter((block) => block.type === "text").map((block) => block.text).join(" "));
28986
+ }
28987
+ normalizeSessionTitle(title) {
28988
+ const normalized = title?.replace(/\s+/g, " ").trim() ?? "";
28989
+ return normalized.length > 0 ? normalized : null;
28990
+ }
28414
28991
  async createHistoryUpdates(item, sessionState) {
28415
28992
  switch (item.type) {
28416
28993
  case "userMessage":
28417
28994
  return this.createUserMessageUpdates(item);
28418
28995
  case "hookPrompt":
28419
- case "subAgentActivity":
28420
28996
  case "sleep":
28421
28997
  return [];
28998
+ case "subAgentActivity":
28999
+ return [createSubAgentActivityUpdate(item, "completed", "tool_call")];
28422
29000
  case "agentMessage": {
28423
29001
  const meta3 = createCodexMessagePhaseMeta(item.phase);
28424
29002
  return [{
@@ -28457,7 +29035,7 @@ You have been logged out. Please try again.`);
28457
29035
  case "exitedReviewMode":
28458
29036
  return [this.createReviewModeUpdate(item, false)];
28459
29037
  case "contextCompaction":
28460
- return [this.createContextCompactionUpdate()];
29038
+ return [createCompletedContextCompactionUpdate(item)];
28461
29039
  case "plan":
28462
29040
  return [this.createPlanUpdate(item)];
28463
29041
  }
@@ -28500,15 +29078,6 @@ You have been logged out. Please try again.`);
28500
29078
  }
28501
29079
  };
28502
29080
  }
28503
- createContextCompactionUpdate() {
28504
- return {
28505
- sessionUpdate: "agent_message_chunk",
28506
- content: {
28507
- type: "text",
28508
- text: "Context compacted."
28509
- }
28510
- };
28511
- }
28512
29081
  createPlanUpdate(item) {
28513
29082
  return {
28514
29083
  sessionUpdate: "agent_message_chunk",
@@ -28839,6 +29408,18 @@ ${item.text}`
28839
29408
  }
28840
29409
  sessionState.currentTurnId = turnId;
28841
29410
  pendingTurnStart?.resolve(turnId);
29411
+ },
29412
+ setConfigOption: async (configId, value) => {
29413
+ await this.applySessionConfigOption(sessionState, {
29414
+ sessionId: sessionState.sessionId,
29415
+ configId,
29416
+ value
29417
+ });
29418
+ const session = new ACPSessionConnection(this.connection, sessionState.sessionId);
29419
+ await session.update({
29420
+ sessionUpdate: "config_option_update",
29421
+ configOptions: this.createSessionConfigOptions(sessionState)
29422
+ });
28842
29423
  }
28843
29424
  });
28844
29425
  void commandPromise.catch((err) => {
@@ -28936,6 +29517,10 @@ ${item.text}`
28936
29517
  if (error51) {
28937
29518
  throw error51;
28938
29519
  }
29520
+ await this.publishFallbackSessionTitle(
29521
+ sessionState,
29522
+ this.createPromptFallbackTitle(params.prompt)
29523
+ );
28939
29524
  return {
28940
29525
  stopReason: "end_turn",
28941
29526
  usage: this.buildPromptUsage(sessionState.lastTokenUsage),
@@ -29090,6 +29675,7 @@ var CommandExecutionApprovalRequest = new import_node2.RequestType("item/command
29090
29675
  var FileChangeApprovalRequest = new import_node2.RequestType("item/fileChange/requestApproval");
29091
29676
  var PermissionsApprovalRequest = new import_node2.RequestType("item/permissions/requestApproval");
29092
29677
  var McpServerElicitationRequest = new import_node2.RequestType("mcpServer/elicitation/request");
29678
+ var ToolRequestUserInputRequest = new import_node2.RequestType("item/tool/requestUserInput");
29093
29679
  var GOAL_RUNTIME_EFFECTS_GRACE_MS = 1e3;
29094
29680
  var CodexAppServerClient = class {
29095
29681
  connection;
@@ -29105,6 +29691,7 @@ var CodexAppServerClient = class {
29105
29691
  threadStatusCaptures = /* @__PURE__ */ new Map();
29106
29692
  threadGoalUpdateCaptures = /* @__PURE__ */ new Map();
29107
29693
  threadGoalClearedCaptures = /* @__PURE__ */ new Map();
29694
+ threadSettings = /* @__PURE__ */ new Map();
29108
29695
  staleTurnIds = /* @__PURE__ */ new Map();
29109
29696
  constructor(connection) {
29110
29697
  this.connection = connection;
@@ -29134,6 +29721,9 @@ var CodexAppServerClient = class {
29134
29721
  if (isThreadGoalClearedNotification(serverNotification)) {
29135
29722
  this.recordThreadGoalCleared(serverNotification.params);
29136
29723
  }
29724
+ if (serverNotification.method === "thread/settings/updated") {
29725
+ this.threadSettings.set(serverNotification.params.threadId, serverNotification.params.threadSettings);
29726
+ }
29137
29727
  const routing = extractTurnRouting(serverNotification);
29138
29728
  if (this.handleStaleTurnNotification(serverNotification, routing)) {
29139
29729
  return;
@@ -29187,6 +29777,16 @@ var CodexAppServerClient = class {
29187
29777
  }
29188
29778
  return await handler.handleElicitation(params);
29189
29779
  });
29780
+ this.connection.onRequest(ToolRequestUserInputRequest, async (params) => {
29781
+ if (this.isStaleTurn(params.threadId, params.turnId)) {
29782
+ return { answers: {} };
29783
+ }
29784
+ const handler = this.elicitationHandlers.get(params.threadId);
29785
+ if (!handler) {
29786
+ return { answers: {} };
29787
+ }
29788
+ return await handler.handleUserInput(params);
29789
+ });
29190
29790
  }
29191
29791
  onApprovalRequest(threadId, handler) {
29192
29792
  this.approvalHandlers.set(threadId, handler);
@@ -29241,7 +29841,7 @@ var CodexAppServerClient = class {
29241
29841
  releaseCapture();
29242
29842
  }
29243
29843
  }
29244
- async runGoalSet(params, onTurnStarted, runtimeEffectsGraceMs = GOAL_RUNTIME_EFFECTS_GRACE_MS) {
29844
+ async runGoalSet(params, onTurnStarted, runtimeEffectsGraceMs = GOAL_RUNTIME_EFFECTS_GRACE_MS, onGoalSet) {
29245
29845
  let goalTurnId = null;
29246
29846
  const capturedCompletions = [];
29247
29847
  let resolveGoalTurnCompleted = () => {
@@ -29294,6 +29894,7 @@ var CodexAppServerClient = class {
29294
29894
  try {
29295
29895
  const goalSetResponse = await this.threadGoalSet(params);
29296
29896
  expectedGoal = goalSetResponse.goal;
29897
+ onGoalSet?.(expectedGoal);
29297
29898
  if (capturedGoalUpdates.some((event) => goalsMatch(event.goal, expectedGoal))) {
29298
29899
  goalUpdateHandled = true;
29299
29900
  resolveGoalUpdateHandled();
@@ -29431,6 +30032,12 @@ var CodexAppServerClient = class {
29431
30032
  async threadResume(params) {
29432
30033
  return await this.sendRequest({ method: "thread/resume", params });
29433
30034
  }
30035
+ getThreadSettings(threadId) {
30036
+ return this.threadSettings.get(threadId);
30037
+ }
30038
+ async threadSettingsUpdate(params) {
30039
+ await this.connection.sendRequest("thread/settings/update", params);
30040
+ }
29434
30041
  async threadList(params) {
29435
30042
  return await this.sendRequest({ method: "thread/list", params });
29436
30043
  }
@@ -29452,6 +30059,9 @@ var CodexAppServerClient = class {
29452
30059
  async threadGoalSet(params) {
29453
30060
  return await this.sendRequest({ method: "thread/goal/set", params });
29454
30061
  }
30062
+ async threadGoalGet(params) {
30063
+ return await this.sendRequest({ method: "thread/goal/get", params });
30064
+ }
29455
30065
  async threadGoalClear(params) {
29456
30066
  return await this.sendRequest({ method: "thread/goal/clear", params });
29457
30067
  }
@@ -29981,6 +30591,10 @@ var legacySetSessionModelParamsParser = external_exports.object({
29981
30591
  sessionId: external_exports.string(),
29982
30592
  modelId: external_exports.string()
29983
30593
  }).passthrough();
30594
+ var goalControlParamsParser = external_exports.object({
30595
+ sessionId: external_exports.string(),
30596
+ action: external_exports.enum(["pause", "clear"])
30597
+ }).passthrough();
29984
30598
  if (process.argv.includes("--version")) {
29985
30599
  console.log(`${package_default.name} ${package_default.version}`);
29986
30600
  process.exit(0);
@@ -30053,5 +30667,5 @@ function startAcpServer() {
30053
30667
  codexAcpServer = null;
30054
30668
  }
30055
30669
  });
30056
- }).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.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)).connect(acpJsonStream);
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);
30057
30671
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.1.2",
6
+ "version": "1.1.4",
7
7
  "description": "",
8
8
  "main": "dist/index.js",
9
9
  "bin": {
@@ -62,7 +62,7 @@
62
62
  },
63
63
  "dependencies": {
64
64
  "@agentclientprotocol/sdk": "^1.2.1",
65
- "@openai/codex": "^0.144.0",
65
+ "@openai/codex": "^0.144.4",
66
66
  "diff": "^9.0.0",
67
67
  "open": "^11.0.0",
68
68
  "vscode-jsonrpc": "^9.0.1",