@ccpocket/bridge 1.25.0 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,8 @@ export class CodexProcess extends EventEmitter {
8
8
  child = null;
9
9
  _status = "starting";
10
10
  _threadId = null;
11
+ _agentNickname = null;
12
+ _agentRole = null;
11
13
  stopped = false;
12
14
  startModel;
13
15
  inputResolve = null;
@@ -40,9 +42,18 @@ export class CodexProcess extends EventEmitter {
40
42
  get isWaitingForInput() {
41
43
  return this.inputResolve !== null;
42
44
  }
45
+ getMessageModel() {
46
+ return sanitizeCodexModel(this.startModel) ?? "";
47
+ }
43
48
  get sessionId() {
44
49
  return this._threadId;
45
50
  }
51
+ get agentNickname() {
52
+ return this._agentNickname;
53
+ }
54
+ get agentRole() {
55
+ return this._agentRole;
56
+ }
46
57
  get isRunning() {
47
58
  return this.child !== null;
48
59
  }
@@ -89,23 +100,75 @@ export class CodexProcess extends EventEmitter {
89
100
  async archiveThread(threadId) {
90
101
  await this.request("thread/archive", { threadId });
91
102
  }
103
+ async listThreads(params = {}) {
104
+ const result = (await this.request("thread/list", {
105
+ sortKey: "updated_at",
106
+ archived: false,
107
+ ...(params.limit != null ? { limit: params.limit } : {}),
108
+ ...(params.cursor !== undefined ? { cursor: params.cursor } : {}),
109
+ ...(params.cwd ? { cwd: params.cwd } : {}),
110
+ ...(params.searchTerm ? { searchTerm: params.searchTerm } : {}),
111
+ }));
112
+ const data = Array.isArray(result.data)
113
+ ? result.data.map((entry) => toCodexThreadSummary(entry))
114
+ : [];
115
+ return {
116
+ data,
117
+ nextCursor: typeof result.nextCursor === "string" ? result.nextCursor : null,
118
+ };
119
+ }
92
120
  start(projectPath, options) {
93
121
  if (this.child) {
94
122
  this.stop();
95
123
  }
124
+ this.prepareLaunch(projectPath, options);
125
+ this.launchAppServer(projectPath, options);
126
+ void this.bootstrap(projectPath, options);
127
+ }
128
+ async initializeOnly(projectPath) {
129
+ if (this.child) {
130
+ this.stop();
131
+ }
132
+ this.prepareLaunch(projectPath);
133
+ this.launchAppServer(projectPath);
134
+ await this.initializeRpcConnection();
135
+ this.setStatus("idle");
136
+ }
137
+ stop() {
138
+ this.stopped = true;
139
+ if (this.inputResolve) {
140
+ this.inputResolve({ text: "" });
141
+ this.inputResolve = null;
142
+ }
143
+ this.pendingApprovals.clear();
144
+ this.pendingUserInputs.clear();
145
+ this.rejectAllPending(new Error("stopped"));
146
+ if (this.child) {
147
+ this.child.kill("SIGTERM");
148
+ this.child = null;
149
+ }
150
+ this.setStatus("idle");
151
+ console.log("[codex-process] Stopped");
152
+ }
153
+ prepareLaunch(projectPath, options) {
96
154
  this.stopped = false;
97
155
  this._threadId = null;
156
+ this._agentNickname = null;
157
+ this._agentRole = null;
98
158
  this.pendingTurnId = null;
99
159
  this.pendingTurnCompletion = null;
100
160
  this.pendingApprovals.clear();
101
161
  this.pendingUserInputs.clear();
102
162
  this.lastTokenUsage = null;
103
- this.startModel = options?.model;
163
+ this.startModel = sanitizeCodexModel(options?.model);
104
164
  this._approvalPolicy = options?.approvalPolicy ?? "never";
105
165
  this._collaborationMode = options?.collaborationMode ?? "default";
106
166
  this.lastPlanItemText = null;
107
167
  this.pendingPlanCompletion = null;
108
168
  this._pendingPlanInput = null;
169
+ this._projectPath = projectPath;
170
+ }
171
+ launchAppServer(projectPath, options) {
109
172
  console.log(`[codex-process] Starting app-server (cwd: ${projectPath}, sandbox: ${options?.sandboxMode ?? "workspace-write"}, approval: ${options?.approvalPolicy ?? "never"}, model: ${options?.model ?? "default"}, collaboration: ${this._collaborationMode})`);
110
173
  const child = spawn("codex", ["app-server", "--listen", "stdio://"], {
111
174
  cwd: projectPath,
@@ -128,7 +191,10 @@ export class CodexProcess extends EventEmitter {
128
191
  if (this.stopped)
129
192
  return;
130
193
  console.error("[codex-process] app-server process error:", err);
131
- this.emitMessage({ type: "error", message: `Failed to start codex app-server: ${err.message}` });
194
+ this.emitMessage({
195
+ type: "error",
196
+ message: `Failed to start codex app-server: ${err.message}`,
197
+ });
132
198
  this.setStatus("idle");
133
199
  this.emit("exit", 1);
134
200
  });
@@ -137,28 +203,14 @@ export class CodexProcess extends EventEmitter {
137
203
  this.child = null;
138
204
  this.rejectAllPending(new Error("codex app-server exited"));
139
205
  if (!this.stopped && exitCode !== 0) {
140
- this.emitMessage({ type: "error", message: `codex app-server exited with code ${exitCode}` });
206
+ this.emitMessage({
207
+ type: "error",
208
+ message: `codex app-server exited with code ${exitCode}`,
209
+ });
141
210
  }
142
211
  this.setStatus("idle");
143
212
  this.emit("exit", code);
144
213
  });
145
- void this.bootstrap(projectPath, options);
146
- }
147
- stop() {
148
- this.stopped = true;
149
- if (this.inputResolve) {
150
- this.inputResolve({ text: "" });
151
- this.inputResolve = null;
152
- }
153
- this.pendingApprovals.clear();
154
- this.pendingUserInputs.clear();
155
- this.rejectAllPending(new Error("stopped"));
156
- if (this.child) {
157
- this.child.kill("SIGTERM");
158
- this.child = null;
159
- }
160
- this.setStatus("idle");
161
- console.log("[codex-process] Stopped");
162
214
  }
163
215
  interrupt() {
164
216
  if (!this._threadId || !this.pendingTurnId)
@@ -201,7 +253,8 @@ export class CodexProcess extends EventEmitter {
201
253
  }
202
254
  approve(toolUseId, _updatedInput) {
203
255
  // Check if this is a plan completion approval
204
- if (this.pendingPlanCompletion && toolUseId === this.pendingPlanCompletion.toolUseId) {
256
+ if (this.pendingPlanCompletion &&
257
+ toolUseId === this.pendingPlanCompletion.toolUseId) {
205
258
  this.handlePlanApproved(_updatedInput);
206
259
  return;
207
260
  }
@@ -211,9 +264,7 @@ export class CodexProcess extends EventEmitter {
211
264
  return;
212
265
  }
213
266
  this.pendingApprovals.delete(pending.toolUseId);
214
- this.respondToServerRequest(pending.requestId, {
215
- decision: "accept",
216
- });
267
+ this.respondToServerRequest(pending.requestId, buildApprovalResponse(pending, "accept"));
217
268
  this.emitToolResult(pending.toolUseId, "Approved");
218
269
  if (this.pendingApprovals.size === 0) {
219
270
  this.setStatus("running");
@@ -226,12 +277,7 @@ export class CodexProcess extends EventEmitter {
226
277
  return;
227
278
  }
228
279
  this.pendingApprovals.delete(pending.toolUseId);
229
- this.respondToServerRequest(pending.requestId, {
230
- decision: "accept",
231
- acceptSettings: {
232
- forSession: true,
233
- },
234
- });
280
+ this.respondToServerRequest(pending.requestId, buildApprovalResponse(pending, "acceptForSession"));
235
281
  this.emitToolResult(pending.toolUseId, "Approved (always)");
236
282
  if (this.pendingApprovals.size === 0) {
237
283
  this.setStatus("running");
@@ -239,7 +285,8 @@ export class CodexProcess extends EventEmitter {
239
285
  }
240
286
  reject(toolUseId, _message) {
241
287
  // Check if this is a plan completion rejection
242
- if (this.pendingPlanCompletion && toolUseId === this.pendingPlanCompletion.toolUseId) {
288
+ if (this.pendingPlanCompletion &&
289
+ toolUseId === this.pendingPlanCompletion.toolUseId) {
243
290
  this.handlePlanRejected(_message);
244
291
  return;
245
292
  }
@@ -249,9 +296,7 @@ export class CodexProcess extends EventEmitter {
249
296
  return;
250
297
  }
251
298
  this.pendingApprovals.delete(pending.toolUseId);
252
- this.respondToServerRequest(pending.requestId, {
253
- decision: "decline",
254
- });
299
+ this.respondToServerRequest(pending.requestId, buildApprovalResponse(pending, "decline"));
255
300
  this.emitToolResult(pending.toolUseId, "Rejected");
256
301
  if (this.pendingApprovals.size === 0) {
257
302
  this.setStatus("running");
@@ -264,9 +309,7 @@ export class CodexProcess extends EventEmitter {
264
309
  return;
265
310
  }
266
311
  this.pendingUserInputs.delete(pending.toolUseId);
267
- this.respondToServerRequest(pending.requestId, {
268
- answers: buildUserInputAnswers(pending.questions, result),
269
- });
312
+ this.respondToServerRequest(pending.requestId, buildUserInputResponse(pending, result));
270
313
  this.emitToolResult(pending.toolUseId, "Answered");
271
314
  if (this.pendingApprovals.size === 0 && this.pendingUserInputs.size === 0) {
272
315
  this.setStatus("running");
@@ -327,7 +370,9 @@ export class CodexProcess extends EventEmitter {
327
370
  * Plan approved → switch to Default mode and auto-start execution.
328
371
  */
329
372
  handlePlanApproved(updatedInput) {
330
- const planText = updatedInput?.plan ?? this.pendingPlanCompletion?.planText ?? "";
373
+ const planText = updatedInput?.plan ??
374
+ this.pendingPlanCompletion?.planText ??
375
+ "";
331
376
  const resolvedToolUseId = this.pendingPlanCompletion?.toolUseId;
332
377
  this.pendingPlanCompletion = null;
333
378
  this._collaborationMode = "default";
@@ -378,24 +423,17 @@ export class CodexProcess extends EventEmitter {
378
423
  }
379
424
  async bootstrap(projectPath, options) {
380
425
  try {
381
- await this.request("initialize", {
382
- clientInfo: {
383
- name: "ccpocket_bridge",
384
- version: "1.0.0",
385
- title: "ccpocket bridge",
386
- },
387
- capabilities: {
388
- experimentalApi: true,
389
- },
390
- });
391
- this.notify("initialized", {});
426
+ await this.initializeRpcConnection();
392
427
  const threadParams = {
393
428
  cwd: projectPath,
394
429
  approvalPolicy: normalizeApprovalPolicy(options?.approvalPolicy ?? "never"),
395
430
  sandbox: normalizeSandboxMode(options?.sandboxMode ?? "workspace-write"),
431
+ experimentalRawEvents: false,
432
+ persistExtendedHistory: true,
396
433
  };
397
- if (options?.model)
398
- threadParams.model = options.model;
434
+ const requestedModel = sanitizeCodexModel(options?.model);
435
+ if (requestedModel)
436
+ threadParams.model = requestedModel;
399
437
  if (options?.modelReasoningEffort) {
400
438
  threadParams.effort = normalizeReasoningEffort(options.modelReasoningEffort);
401
439
  }
@@ -412,11 +450,15 @@ export class CodexProcess extends EventEmitter {
412
450
  if (options?.threadId) {
413
451
  threadParams.threadId = options.threadId;
414
452
  }
415
- const response = await this.request(method, threadParams);
453
+ else {
454
+ threadParams.experimentalRawEvents = false;
455
+ }
456
+ if (options?.threadId) {
457
+ threadParams.persistExtendedHistory = true;
458
+ }
459
+ const response = (await this.request(method, threadParams));
416
460
  const thread = response.thread;
417
- const threadId = typeof thread?.id === "string"
418
- ? thread.id
419
- : options?.threadId;
461
+ const threadId = typeof thread?.id === "string" ? thread.id : options?.threadId;
420
462
  if (!threadId) {
421
463
  throw new Error(`${method} returned no thread id`);
422
464
  }
@@ -424,12 +466,34 @@ export class CodexProcess extends EventEmitter {
424
466
  if (typeof thread?.model === "string" && thread.model) {
425
467
  this.startModel = thread.model;
426
468
  }
469
+ const resolvedSettings = extractResolvedSettingsFromThreadResponse(response);
470
+ if (resolvedSettings.model) {
471
+ this.startModel = resolvedSettings.model;
472
+ }
427
473
  this._threadId = threadId;
428
474
  this.emitMessage({
429
475
  type: "system",
430
476
  subtype: "init",
431
477
  sessionId: threadId,
432
- model: this.startModel ?? "codex",
478
+ provider: "codex",
479
+ ...(sanitizeCodexModel(this.startModel)
480
+ ? { model: sanitizeCodexModel(this.startModel) }
481
+ : {}),
482
+ ...(resolvedSettings.approvalPolicy
483
+ ? { approvalPolicy: resolvedSettings.approvalPolicy }
484
+ : {}),
485
+ ...(resolvedSettings.sandboxMode
486
+ ? { sandboxMode: resolvedSettings.sandboxMode }
487
+ : {}),
488
+ ...(resolvedSettings.modelReasoningEffort
489
+ ? { modelReasoningEffort: resolvedSettings.modelReasoningEffort }
490
+ : {}),
491
+ ...(resolvedSettings.networkAccessEnabled !== undefined
492
+ ? { networkAccessEnabled: resolvedSettings.networkAccessEnabled }
493
+ : {}),
494
+ ...(resolvedSettings.webSearchMode
495
+ ? { webSearchMode: resolvedSettings.webSearchMode }
496
+ : {}),
433
497
  });
434
498
  this.setStatus("idle");
435
499
  // Fetch skills in background (non-blocking)
@@ -442,12 +506,30 @@ export class CodexProcess extends EventEmitter {
442
506
  const message = err instanceof Error ? err.message : String(err);
443
507
  console.error("[codex-process] bootstrap error:", err);
444
508
  this.emitMessage({ type: "error", message: `Codex error: ${message}` });
445
- this.emitMessage({ type: "result", subtype: "error", error: message, sessionId: this._threadId ?? undefined });
509
+ this.emitMessage({
510
+ type: "result",
511
+ subtype: "error",
512
+ error: message,
513
+ sessionId: this._threadId ?? undefined,
514
+ });
446
515
  }
447
516
  this.setStatus("idle");
448
517
  this.emit("exit", 1);
449
518
  }
450
519
  }
520
+ async initializeRpcConnection() {
521
+ await this.request("initialize", {
522
+ clientInfo: {
523
+ name: "ccpocket_bridge",
524
+ version: "1.0.0",
525
+ title: "ccpocket bridge",
526
+ },
527
+ capabilities: {
528
+ experimentalApi: true,
529
+ },
530
+ });
531
+ this.notify("initialized", {});
532
+ }
451
533
  /**
452
534
  * Fetch skills from Codex app-server via `skills/list` RPC and emit them
453
535
  * as a `supported_commands` system message so the Flutter client can display
@@ -456,10 +538,10 @@ export class CodexProcess extends EventEmitter {
456
538
  async fetchSkills(projectPath) {
457
539
  const TIMEOUT_MS = 10_000;
458
540
  try {
459
- const result = await Promise.race([
541
+ const result = (await Promise.race([
460
542
  this.request("skills/list", { cwds: [projectPath] }),
461
543
  new Promise((resolve) => setTimeout(() => resolve(null), TIMEOUT_MS)),
462
- ]);
544
+ ]));
463
545
  if (this.stopped || !result?.data)
464
546
  return;
465
547
  const skills = [];
@@ -474,7 +556,9 @@ export class CodexProcess extends EventEmitter {
474
556
  name: skill.name,
475
557
  path: skill.path,
476
558
  description: skill.description,
477
- shortDescription: skill.shortDescription ?? skill.interface?.shortDescription ?? undefined,
559
+ shortDescription: skill.shortDescription ??
560
+ skill.interface?.shortDescription ??
561
+ undefined,
478
562
  enabled: skill.enabled,
479
563
  scope: skill.scope,
480
564
  displayName: skill.interface?.displayName ?? undefined,
@@ -515,7 +599,10 @@ export class CodexProcess extends EventEmitter {
515
599
  if (this.stopped || !pendingInput.text)
516
600
  break;
517
601
  if (!this._threadId) {
518
- this.emitMessage({ type: "error", message: "Codex thread is not initialized" });
602
+ this.emitMessage({
603
+ type: "error",
604
+ message: "Codex thread is not initialized",
605
+ });
519
606
  continue;
520
607
  }
521
608
  const { input, tempPaths } = await this.toRpcInput(pendingInput);
@@ -531,15 +618,18 @@ export class CodexProcess extends EventEmitter {
531
618
  input,
532
619
  approvalPolicy: normalizeApprovalPolicy(this._approvalPolicy),
533
620
  };
534
- if (options?.model)
535
- params.model = options.model;
621
+ const requestedModel = sanitizeCodexModel(options?.model);
622
+ if (requestedModel)
623
+ params.model = requestedModel;
536
624
  if (options?.modelReasoningEffort) {
537
625
  params.effort = normalizeReasoningEffort(options.modelReasoningEffort);
538
626
  }
539
627
  // Always send collaborationMode so the server switches modes correctly.
540
628
  // Omitting it causes the server to persist the previous turn's mode.
541
629
  const modeSettings = {
542
- model: options?.model || this.startModel || "gpt-5.4",
630
+ model: requestedModel
631
+ || sanitizeCodexModel(this.startModel)
632
+ || "gpt-5.4",
543
633
  };
544
634
  if (this._collaborationMode === "plan") {
545
635
  modeSettings.reasoning_effort = "medium";
@@ -603,11 +693,15 @@ export class CodexProcess extends EventEmitter {
603
693
  }
604
694
  }
605
695
  handleRpcEnvelope(envelope) {
606
- if (envelope.id != null && envelope.method && envelope.result === undefined && envelope.error === undefined) {
696
+ if (envelope.id != null &&
697
+ envelope.method &&
698
+ envelope.result === undefined &&
699
+ envelope.error === undefined) {
607
700
  this.handleServerRequest(envelope.id, envelope.method, envelope.params ?? {});
608
701
  return;
609
702
  }
610
- if (envelope.id != null && (envelope.result !== undefined || envelope.error)) {
703
+ if (envelope.id != null &&
704
+ (envelope.result !== undefined || envelope.error)) {
611
705
  this.handleRpcResponse(envelope);
612
706
  return;
613
707
  }
@@ -635,17 +729,45 @@ export class CodexProcess extends EventEmitter {
635
729
  case "item/commandExecution/requestApproval": {
636
730
  const toolUseId = this.extractToolUseId(params, id);
637
731
  const input = {
638
- ...(typeof params.command === "string" ? { command: params.command } : {}),
732
+ ...(typeof params.command === "string"
733
+ ? { command: params.command }
734
+ : {}),
639
735
  ...(typeof params.cwd === "string" ? { cwd: params.cwd } : {}),
640
- ...(params.commandActions ? { commandActions: params.commandActions } : {}),
641
- ...(params.networkApprovalContext ? { networkApprovalContext: params.networkApprovalContext } : {}),
642
- ...(typeof params.reason === "string" ? { reason: params.reason } : {}),
736
+ ...(params.commandActions
737
+ ? { commandActions: params.commandActions }
738
+ : {}),
739
+ ...(params.networkApprovalContext
740
+ ? { networkApprovalContext: params.networkApprovalContext }
741
+ : {}),
742
+ ...(params.additionalPermissions
743
+ ? { additionalPermissions: params.additionalPermissions }
744
+ : {}),
745
+ ...(params.skillMetadata
746
+ ? { skillMetadata: params.skillMetadata }
747
+ : {}),
748
+ ...(params.proposedExecpolicyAmendment
749
+ ? {
750
+ proposedExecpolicyAmendment: params.proposedExecpolicyAmendment,
751
+ }
752
+ : {}),
753
+ ...(params.proposedNetworkPolicyAmendments
754
+ ? {
755
+ proposedNetworkPolicyAmendments: params.proposedNetworkPolicyAmendments,
756
+ }
757
+ : {}),
758
+ ...(params.availableDecisions
759
+ ? { availableDecisions: params.availableDecisions }
760
+ : {}),
761
+ ...(typeof params.reason === "string"
762
+ ? { reason: params.reason }
763
+ : {}),
643
764
  };
644
765
  this.pendingApprovals.set(toolUseId, {
645
766
  requestId: id,
646
767
  toolUseId,
647
768
  toolName: "Bash",
648
769
  input,
770
+ kind: "command",
649
771
  });
650
772
  this.emitMessage({
651
773
  type: "permission_request",
@@ -660,13 +782,19 @@ export class CodexProcess extends EventEmitter {
660
782
  const toolUseId = this.extractToolUseId(params, id);
661
783
  const input = {
662
784
  ...(Array.isArray(params.changes) ? { changes: params.changes } : {}),
663
- ...(typeof params.reason === "string" ? { reason: params.reason } : {}),
785
+ ...(typeof params.grantRoot === "string"
786
+ ? { grantRoot: params.grantRoot }
787
+ : {}),
788
+ ...(typeof params.reason === "string"
789
+ ? { reason: params.reason }
790
+ : {}),
664
791
  };
665
792
  this.pendingApprovals.set(toolUseId, {
666
793
  requestId: id,
667
794
  toolUseId,
668
795
  toolName: "FileChange",
669
796
  input,
797
+ kind: "file",
670
798
  });
671
799
  this.emitMessage({
672
800
  type: "permission_request",
@@ -694,11 +822,13 @@ export class CodexProcess extends EventEmitter {
694
822
  this.pendingUserInputs.set(toolUseId, {
695
823
  requestId: id,
696
824
  toolUseId,
825
+ toolName: "AskUserQuestion",
697
826
  questions: questions.map((q) => ({
698
827
  id: q.id,
699
828
  question: q.question,
700
829
  })),
701
830
  input,
831
+ kind: "questions",
702
832
  });
703
833
  this.emitMessage({
704
834
  type: "permission_request",
@@ -709,6 +839,52 @@ export class CodexProcess extends EventEmitter {
709
839
  this.setStatus("waiting_approval");
710
840
  break;
711
841
  }
842
+ case "item/permissions/requestApproval": {
843
+ const toolUseId = this.extractToolUseId(params, id);
844
+ const requestedPermissions = asRecord(params.permissions) ?? {};
845
+ const input = {
846
+ permissions: requestedPermissions,
847
+ ...(typeof params.reason === "string"
848
+ ? { reason: params.reason }
849
+ : {}),
850
+ };
851
+ this.pendingApprovals.set(toolUseId, {
852
+ requestId: id,
853
+ toolUseId,
854
+ toolName: "Permissions",
855
+ input,
856
+ kind: "permissions",
857
+ requestedPermissions,
858
+ });
859
+ this.emitMessage({
860
+ type: "permission_request",
861
+ toolUseId,
862
+ toolName: "Permissions",
863
+ input,
864
+ });
865
+ this.setStatus("waiting_approval");
866
+ break;
867
+ }
868
+ case "mcpServer/elicitation/request": {
869
+ const toolUseId = this.extractToolUseId(params, id);
870
+ const elicitation = createElicitationInput(params);
871
+ this.pendingUserInputs.set(toolUseId, {
872
+ requestId: id,
873
+ toolUseId,
874
+ toolName: "McpElicitation",
875
+ questions: elicitation.questions,
876
+ input: elicitation.input,
877
+ kind: elicitation.kind,
878
+ });
879
+ this.emitMessage({
880
+ type: "permission_request",
881
+ toolUseId,
882
+ toolName: "McpElicitation",
883
+ input: elicitation.input,
884
+ });
885
+ this.setStatus("waiting_approval");
886
+ break;
887
+ }
712
888
  default:
713
889
  this.respondToServerRequest(id, {});
714
890
  break;
@@ -721,6 +897,8 @@ export class CodexProcess extends EventEmitter {
721
897
  if (typeof thread?.id === "string") {
722
898
  this._threadId = thread.id;
723
899
  }
900
+ this._agentNickname = stringOrNull(thread?.agentNickname);
901
+ this._agentRole = stringOrNull(thread?.agentRole);
724
902
  break;
725
903
  }
726
904
  case "turn/started": {
@@ -806,11 +984,15 @@ export class CodexProcess extends EventEmitter {
806
984
  id: randomUUID(),
807
985
  role: "assistant",
808
986
  content: [{ type: "text", text }],
809
- model: "codex",
987
+ model: this.getMessageModel(),
810
988
  },
811
989
  });
812
990
  break;
813
991
  }
992
+ case "serverRequest/resolved": {
993
+ this.handleServerRequestResolved(params);
994
+ break;
995
+ }
814
996
  default:
815
997
  break;
816
998
  }
@@ -844,7 +1026,9 @@ export class CodexProcess extends EventEmitter {
844
1026
  subtype: "success",
845
1027
  sessionId: this._threadId ?? undefined,
846
1028
  ...(usage?.input != null ? { inputTokens: usage.input } : {}),
847
- ...(usage?.cachedInput != null ? { cachedInputTokens: usage.cachedInput } : {}),
1029
+ ...(usage?.cachedInput != null
1030
+ ? { cachedInputTokens: usage.cachedInput }
1031
+ : {}),
848
1032
  ...(usage?.output != null ? { outputTokens: usage.output } : {}),
849
1033
  });
850
1034
  }
@@ -868,7 +1052,8 @@ export class CodexProcess extends EventEmitter {
868
1052
  }
869
1053
  else {
870
1054
  this.lastPlanItemText = null;
871
- if (this.pendingApprovals.size === 0 && this.pendingUserInputs.size === 0) {
1055
+ if (this.pendingApprovals.size === 0 &&
1056
+ this.pendingUserInputs.size === 0) {
872
1057
  this.setStatus("idle");
873
1058
  }
874
1059
  }
@@ -902,7 +1087,7 @@ export class CodexProcess extends EventEmitter {
902
1087
  input: { command: commandText },
903
1088
  },
904
1089
  ],
905
- model: "codex",
1090
+ model: this.getMessageModel(),
906
1091
  },
907
1092
  });
908
1093
  break;
@@ -923,7 +1108,63 @@ export class CodexProcess extends EventEmitter {
923
1108
  },
924
1109
  },
925
1110
  ],
926
- model: "codex",
1111
+ model: this.getMessageModel(),
1112
+ },
1113
+ });
1114
+ break;
1115
+ }
1116
+ case "dynamictoolcall": {
1117
+ const tool = typeof item.tool === "string" ? item.tool : "DynamicTool";
1118
+ this.emitMessage({
1119
+ type: "assistant",
1120
+ message: {
1121
+ id: itemId,
1122
+ role: "assistant",
1123
+ content: [
1124
+ {
1125
+ type: "tool_use",
1126
+ id: itemId,
1127
+ name: tool,
1128
+ input: toToolUseInput(item.arguments),
1129
+ },
1130
+ ],
1131
+ model: this.getMessageModel(),
1132
+ },
1133
+ });
1134
+ break;
1135
+ }
1136
+ case "collabagenttoolcall": {
1137
+ const tool = typeof item.tool === "string" ? item.tool : "subagent";
1138
+ const toolName = "SubAgent";
1139
+ const input = {
1140
+ tool,
1141
+ ...(typeof item.prompt === "string" ? { prompt: item.prompt } : {}),
1142
+ ...(typeof item.senderThreadId === "string"
1143
+ ? { senderThreadId: item.senderThreadId }
1144
+ : {}),
1145
+ ...(Array.isArray(item.receiverThreadIds)
1146
+ ? { receiverThreadIds: item.receiverThreadIds }
1147
+ : {}),
1148
+ ...(typeof item.model === "string" ? { model: item.model } : {}),
1149
+ ...(typeof item.reasoningEffort === "string"
1150
+ ? { reasoningEffort: item.reasoningEffort }
1151
+ : {}),
1152
+ ...(item.agentsStates ? { agentsStates: item.agentsStates } : {}),
1153
+ };
1154
+ this.emitMessage({
1155
+ type: "assistant",
1156
+ message: {
1157
+ id: itemId,
1158
+ role: "assistant",
1159
+ content: [
1160
+ {
1161
+ type: "tool_use",
1162
+ id: itemId,
1163
+ name: toolName,
1164
+ input,
1165
+ },
1166
+ ],
1167
+ model: this.getMessageModel(),
927
1168
  },
928
1169
  });
929
1170
  break;
@@ -948,7 +1189,7 @@ export class CodexProcess extends EventEmitter {
948
1189
  id: itemId,
949
1190
  role: "assistant",
950
1191
  content: [{ type: "text", text }],
951
- model: "codex",
1192
+ model: this.getMessageModel(),
952
1193
  },
953
1194
  });
954
1195
  break;
@@ -990,6 +1231,7 @@ export class CodexProcess extends EventEmitter {
990
1231
  const tool = typeof item.tool === "string" ? item.tool : "unknown";
991
1232
  const toolName = `mcp:${server}/${tool}`;
992
1233
  const result = item.result ?? item.error ?? "MCP call completed";
1234
+ const normalized = normalizeMcpToolResult(result);
993
1235
  this.emitMessage({
994
1236
  type: "assistant",
995
1237
  message: {
@@ -1003,14 +1245,28 @@ export class CodexProcess extends EventEmitter {
1003
1245
  input: item.arguments ?? {},
1004
1246
  },
1005
1247
  ],
1006
- model: "codex",
1248
+ model: this.getMessageModel(),
1007
1249
  },
1008
1250
  });
1009
1251
  this.emitMessage({
1010
1252
  type: "tool_result",
1011
1253
  toolUseId: itemId,
1012
- content: typeof result === "string" ? result : JSON.stringify(result),
1254
+ content: normalized.content,
1013
1255
  toolName,
1256
+ ...(normalized.rawContentBlocks.length > 0
1257
+ ? { rawContentBlocks: normalized.rawContentBlocks }
1258
+ : {}),
1259
+ });
1260
+ break;
1261
+ }
1262
+ case "dynamictoolcall": {
1263
+ const tool = typeof item.tool === "string" ? item.tool : "DynamicTool";
1264
+ const content = formatDynamicToolResult(item);
1265
+ this.emitMessage({
1266
+ type: "tool_result",
1267
+ toolUseId: itemId,
1268
+ content,
1269
+ toolName: tool,
1014
1270
  });
1015
1271
  break;
1016
1272
  }
@@ -1029,7 +1285,7 @@ export class CodexProcess extends EventEmitter {
1029
1285
  input: { query },
1030
1286
  },
1031
1287
  ],
1032
- model: "codex",
1288
+ model: this.getMessageModel(),
1033
1289
  },
1034
1290
  });
1035
1291
  this.emitMessage({
@@ -1040,6 +1296,27 @@ export class CodexProcess extends EventEmitter {
1040
1296
  });
1041
1297
  break;
1042
1298
  }
1299
+ case "collabagenttoolcall": {
1300
+ const tool = typeof item.tool === "string" ? item.tool : "subagent";
1301
+ const status = typeof item.status === "string" ? item.status : "completed";
1302
+ const receiverThreadIds = Array.isArray(item.receiverThreadIds)
1303
+ ? item.receiverThreadIds.map((entry) => String(entry))
1304
+ : [];
1305
+ const content = [
1306
+ `tool: ${tool}`,
1307
+ `status: ${status}`,
1308
+ ...(receiverThreadIds.length > 0
1309
+ ? [`agents: ${receiverThreadIds.join(", ")}`]
1310
+ : []),
1311
+ ].join("\n");
1312
+ this.emitMessage({
1313
+ type: "tool_result",
1314
+ toolUseId: itemId,
1315
+ content,
1316
+ toolName: "SubAgent",
1317
+ });
1318
+ break;
1319
+ }
1043
1320
  case "plan": {
1044
1321
  // Plan item completed — save text for plan approval emission in handleTurnCompleted()
1045
1322
  const planText = typeof item.text === "string" ? item.text : "";
@@ -1060,7 +1337,11 @@ export class CodexProcess extends EventEmitter {
1060
1337
  const tempPaths = [];
1061
1338
  // Prepend SkillUserInput if a skill reference is attached
1062
1339
  if (pendingInput.skill) {
1063
- input.push({ type: "skill", name: pendingInput.skill.name, path: pendingInput.skill.path });
1340
+ input.push({
1341
+ type: "skill",
1342
+ name: pendingInput.skill.name,
1343
+ path: pendingInput.skill.path,
1344
+ });
1064
1345
  }
1065
1346
  input.push({ type: "text", text: pendingInput.text });
1066
1347
  if (!pendingInput.images || pendingInput.images.length === 0) {
@@ -1150,12 +1431,59 @@ export class CodexProcess extends EventEmitter {
1150
1431
  extractToolUseId(params, requestId) {
1151
1432
  if (typeof params.approvalId === "string")
1152
1433
  return params.approvalId;
1434
+ if (typeof params.elicitationId === "string")
1435
+ return params.elicitationId;
1153
1436
  if (typeof params.itemId === "string")
1154
1437
  return params.itemId;
1155
1438
  if (typeof requestId === "string")
1156
1439
  return requestId;
1157
1440
  return `approval-${requestId}`;
1158
1441
  }
1442
+ handleServerRequestResolved(params) {
1443
+ const requestId = params.requestId;
1444
+ if (requestId === undefined || requestId === null)
1445
+ return;
1446
+ const approval = [...this.pendingApprovals.values()].find((entry) => entry.requestId === requestId);
1447
+ if (approval) {
1448
+ this.pendingApprovals.delete(approval.toolUseId);
1449
+ this.emitMessage({
1450
+ type: "permission_resolved",
1451
+ toolUseId: approval.toolUseId,
1452
+ });
1453
+ }
1454
+ const inputRequest = [...this.pendingUserInputs.values()].find((entry) => entry.requestId === requestId);
1455
+ if (inputRequest) {
1456
+ this.pendingUserInputs.delete(inputRequest.toolUseId);
1457
+ this.emitMessage({
1458
+ type: "permission_resolved",
1459
+ toolUseId: inputRequest.toolUseId,
1460
+ });
1461
+ }
1462
+ if (!this.pendingPlanCompletion &&
1463
+ this.pendingApprovals.size === 0 &&
1464
+ this.pendingUserInputs.size === 0) {
1465
+ this.setStatus(this.pendingTurnId ? "running" : "idle");
1466
+ }
1467
+ }
1468
+ }
1469
+ function buildApprovalResponse(pending, decision) {
1470
+ if (pending.kind === "permissions") {
1471
+ return {
1472
+ scope: decision === "acceptForSession" ? "session" : "turn",
1473
+ permissions: decision === "decline" ? {} : (pending.requestedPermissions ?? {}),
1474
+ };
1475
+ }
1476
+ return {
1477
+ decision,
1478
+ };
1479
+ }
1480
+ function buildUserInputResponse(pending, rawResult) {
1481
+ if (pending.kind === "questions") {
1482
+ return {
1483
+ answers: buildUserInputAnswers(pending.questions, rawResult),
1484
+ };
1485
+ }
1486
+ return buildElicitationResponse(pending, rawResult);
1159
1487
  }
1160
1488
  function normalizeApprovalPolicy(value) {
1161
1489
  switch (value) {
@@ -1189,13 +1517,59 @@ function normalizeReasoningEffort(value) {
1189
1517
  return value;
1190
1518
  }
1191
1519
  }
1520
+ function sanitizeCodexModel(value) {
1521
+ if (typeof value !== "string")
1522
+ return undefined;
1523
+ const normalized = value.trim();
1524
+ if (!normalized || normalized === "codex")
1525
+ return undefined;
1526
+ return normalized;
1527
+ }
1528
+ function extractResolvedSettingsFromThreadResponse(response) {
1529
+ const thread = response.thread;
1530
+ const sandbox = response.sandbox;
1531
+ return {
1532
+ model: sanitizeCodexModel(response.model)
1533
+ ?? sanitizeCodexModel(thread?.model),
1534
+ approvalPolicy: typeof response.approvalPolicy === "string"
1535
+ ? response.approvalPolicy
1536
+ : undefined,
1537
+ sandboxMode: normalizeSandboxModeFromRpc(sandbox?.type),
1538
+ modelReasoningEffort: typeof response.reasoningEffort === "string"
1539
+ ? response.reasoningEffort
1540
+ : undefined,
1541
+ networkAccessEnabled: typeof sandbox?.networkAccess === "boolean"
1542
+ ? sandbox.networkAccess
1543
+ : undefined,
1544
+ webSearchMode: typeof response.webSearchMode === "string"
1545
+ ? response.webSearchMode
1546
+ : undefined,
1547
+ };
1548
+ }
1549
+ function normalizeSandboxModeFromRpc(value) {
1550
+ switch (value) {
1551
+ case "dangerFullAccess":
1552
+ return "danger-full-access";
1553
+ case "workspaceWrite":
1554
+ return "workspace-write";
1555
+ case "readOnly":
1556
+ return "read-only";
1557
+ default:
1558
+ return typeof value === "string" && value.length > 0 ? value : undefined;
1559
+ }
1560
+ }
1192
1561
  function normalizeItemType(raw) {
1193
1562
  if (typeof raw !== "string")
1194
1563
  return "";
1195
1564
  return raw.replace(/[_\s-]/g, "").toLowerCase();
1196
1565
  }
1197
1566
  function numberOrUndefined(value) {
1198
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
1567
+ return typeof value === "number" && Number.isFinite(value)
1568
+ ? value
1569
+ : undefined;
1570
+ }
1571
+ function stringOrNull(value) {
1572
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
1199
1573
  }
1200
1574
  function summarizeFileChanges(changes) {
1201
1575
  if (!Array.isArray(changes) || changes.length === 0) {
@@ -1212,6 +1586,131 @@ function summarizeFileChanges(changes) {
1212
1586
  })
1213
1587
  .join("\n");
1214
1588
  }
1589
+ function toToolUseInput(value) {
1590
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1591
+ return value;
1592
+ }
1593
+ if (Array.isArray(value)) {
1594
+ return { items: value };
1595
+ }
1596
+ if (value === undefined || value === null) {
1597
+ return {};
1598
+ }
1599
+ return { value };
1600
+ }
1601
+ function formatDynamicToolResult(item) {
1602
+ const status = typeof item.status === "string" ? item.status : "completed";
1603
+ const success = typeof item.success === "boolean" ? item.success : null;
1604
+ const contentItems = Array.isArray(item.contentItems)
1605
+ ? item.contentItems
1606
+ : null;
1607
+ const parts = [
1608
+ `status: ${status}`,
1609
+ ...(success != null ? [`success: ${success}`] : []),
1610
+ ];
1611
+ if (contentItems && contentItems.length > 0) {
1612
+ for (const entry of contentItems) {
1613
+ if (!entry || typeof entry !== "object")
1614
+ continue;
1615
+ const record = entry;
1616
+ const type = typeof record.type === "string" ? record.type : "item";
1617
+ if (type === "inputText" && typeof record.text === "string") {
1618
+ parts.push(record.text);
1619
+ continue;
1620
+ }
1621
+ if (type === "inputImage" && typeof record.imageUrl === "string") {
1622
+ parts.push(`image: ${record.imageUrl}`);
1623
+ continue;
1624
+ }
1625
+ parts.push(JSON.stringify(record));
1626
+ }
1627
+ }
1628
+ return parts.join("\n");
1629
+ }
1630
+ function normalizeMcpToolResult(result) {
1631
+ if (typeof result === "string") {
1632
+ return { content: result, rawContentBlocks: [] };
1633
+ }
1634
+ const record = result && typeof result === "object" && !Array.isArray(result)
1635
+ ? result
1636
+ : null;
1637
+ const contentItems = Array.isArray(record?.content) ? record.content : null;
1638
+ if (!contentItems) {
1639
+ return {
1640
+ content: result == null ? "MCP call completed" : JSON.stringify(result),
1641
+ rawContentBlocks: [],
1642
+ };
1643
+ }
1644
+ const textParts = [];
1645
+ const rawContentBlocks = [];
1646
+ for (const entry of contentItems) {
1647
+ if (!entry || typeof entry !== "object")
1648
+ continue;
1649
+ const item = entry;
1650
+ const type = typeof item.type === "string" ? item.type : "";
1651
+ if (type === "text" && typeof item.text === "string") {
1652
+ textParts.push(item.text);
1653
+ rawContentBlocks.push({ type: "text", text: item.text });
1654
+ continue;
1655
+ }
1656
+ if (type === "image" && typeof item.data === "string") {
1657
+ const mimeType = typeof item.mimeType === "string"
1658
+ ? item.mimeType
1659
+ : typeof item.mediaType === "string"
1660
+ ? item.mediaType
1661
+ : typeof item.media_type === "string"
1662
+ ? item.media_type
1663
+ : "image/png";
1664
+ rawContentBlocks.push({
1665
+ type: "image",
1666
+ source: {
1667
+ type: "base64",
1668
+ data: item.data,
1669
+ media_type: mimeType,
1670
+ },
1671
+ });
1672
+ continue;
1673
+ }
1674
+ rawContentBlocks.push(item);
1675
+ textParts.push(JSON.stringify(item));
1676
+ }
1677
+ const content = textParts.join("\n").trim();
1678
+ if (content.length > 0) {
1679
+ return { content, rawContentBlocks };
1680
+ }
1681
+ const imageCount = rawContentBlocks.filter((entry) => entry.type === "image").length;
1682
+ if (imageCount > 0) {
1683
+ return {
1684
+ content: imageCount === 1
1685
+ ? "Generated 1 image"
1686
+ : `Generated ${imageCount} images`,
1687
+ rawContentBlocks,
1688
+ };
1689
+ }
1690
+ return {
1691
+ content: result == null ? "MCP call completed" : JSON.stringify(result),
1692
+ rawContentBlocks,
1693
+ };
1694
+ }
1695
+ function toCodexThreadSummary(entry) {
1696
+ const record = entry && typeof entry === "object"
1697
+ ? entry
1698
+ : {};
1699
+ const gitInfo = record.gitInfo && typeof record.gitInfo === "object"
1700
+ ? record.gitInfo
1701
+ : {};
1702
+ return {
1703
+ id: typeof record.id === "string" ? record.id : "",
1704
+ preview: typeof record.preview === "string" ? record.preview : "",
1705
+ createdAt: numberOrUndefined(record.createdAt) ?? 0,
1706
+ updatedAt: numberOrUndefined(record.updatedAt) ?? 0,
1707
+ cwd: typeof record.cwd === "string" ? record.cwd : "",
1708
+ agentNickname: stringOrNull(record.agentNickname),
1709
+ agentRole: stringOrNull(record.agentRole),
1710
+ gitBranch: stringOrNull(gitInfo.branch),
1711
+ name: stringOrNull(record.name),
1712
+ };
1713
+ }
1215
1714
  /**
1216
1715
  * Format file changes including unified diff content for display in chat.
1217
1716
  * Falls back to `kind: path` summary when no diff is available.
@@ -1287,7 +1786,9 @@ function normalizeUserInputQuestions(raw) {
1287
1786
  .map((entry, index) => {
1288
1787
  const id = typeof entry.id === "string" ? entry.id : `question_${index + 1}`;
1289
1788
  const question = typeof entry.question === "string" ? entry.question : "";
1290
- const header = typeof entry.header === "string" ? entry.header : `Question ${index + 1}`;
1789
+ const header = typeof entry.header === "string"
1790
+ ? entry.header
1791
+ : `Question ${index + 1}`;
1291
1792
  const optionsRaw = Array.isArray(entry.options) ? entry.options : [];
1292
1793
  const options = optionsRaw
1293
1794
  .filter((option) => !!option && typeof option === "object")
@@ -1367,6 +1868,172 @@ function normalizeAnswerValues(value) {
1367
1868
  const normalized = String(value).trim();
1368
1869
  return normalized ? [normalized] : [];
1369
1870
  }
1871
+ function buildElicitationResponse(pending, rawResult) {
1872
+ if (pending.kind === "elicitation_url") {
1873
+ const action = parseElicitationAction(rawResult);
1874
+ return {
1875
+ action,
1876
+ content: null,
1877
+ _meta: null,
1878
+ };
1879
+ }
1880
+ const parsed = parseResultObject(rawResult);
1881
+ const content = {};
1882
+ for (const question of pending.questions) {
1883
+ const candidate = parsed.byId[question.id] ?? parsed.byQuestion[question.question];
1884
+ const answers = normalizeAnswerValues(candidate);
1885
+ if (answers.length === 1) {
1886
+ content[question.id] = answers[0];
1887
+ }
1888
+ else if (answers.length > 1) {
1889
+ content[question.id] = answers;
1890
+ }
1891
+ }
1892
+ if (Object.keys(content).length === 0 && pending.questions.length === 1) {
1893
+ const answers = normalizeAnswerValues(rawResult);
1894
+ if (answers.length === 1) {
1895
+ content[pending.questions[0].id] = answers[0];
1896
+ }
1897
+ else if (answers.length > 1) {
1898
+ content[pending.questions[0].id] = answers;
1899
+ }
1900
+ }
1901
+ return {
1902
+ action: "accept",
1903
+ content: Object.keys(content).length > 0 ? content : null,
1904
+ _meta: null,
1905
+ };
1906
+ }
1907
+ function parseElicitationAction(rawResult) {
1908
+ const normalized = rawResult.trim().toLowerCase();
1909
+ if (normalized.includes("cancel"))
1910
+ return "cancel";
1911
+ if (normalized.includes("decline") || normalized.includes("deny"))
1912
+ return "decline";
1913
+ try {
1914
+ const parsed = JSON.parse(rawResult);
1915
+ const answers = parsed.answers;
1916
+ if (answers && typeof answers === "object" && !Array.isArray(answers)) {
1917
+ const first = Object.values(answers)[0];
1918
+ const answer = normalizeAnswerValues(first).join(" ").toLowerCase();
1919
+ if (answer.includes("cancel"))
1920
+ return "cancel";
1921
+ if (answer.includes("decline") || answer.includes("deny"))
1922
+ return "decline";
1923
+ }
1924
+ }
1925
+ catch {
1926
+ // Fall through to accept.
1927
+ }
1928
+ return "accept";
1929
+ }
1930
+ function createElicitationInput(params) {
1931
+ const serverName = typeof params.serverName === "string" ? params.serverName : "MCP";
1932
+ const message = typeof params.message === "string" ? params.message : "Provide input";
1933
+ if (params.mode === "url") {
1934
+ const url = typeof params.url === "string" ? params.url : "";
1935
+ const question = url ? `${message}\n${url}` : message;
1936
+ return {
1937
+ kind: "elicitation_url",
1938
+ questions: [{ id: "elicitation_action", question }],
1939
+ input: {
1940
+ mode: "url",
1941
+ serverName,
1942
+ url,
1943
+ message,
1944
+ questions: [
1945
+ {
1946
+ id: "elicitation_action",
1947
+ header: serverName,
1948
+ question,
1949
+ options: [
1950
+ { label: "Accept", description: "Continue with this request" },
1951
+ { label: "Decline", description: "Reject this request" },
1952
+ { label: "Cancel", description: "Cancel without accepting" },
1953
+ ],
1954
+ multiSelect: false,
1955
+ isOther: false,
1956
+ isSecret: false,
1957
+ },
1958
+ ],
1959
+ },
1960
+ };
1961
+ }
1962
+ const schema = asRecord(params.requestedSchema);
1963
+ const properties = asRecord(schema?.properties) ?? {};
1964
+ const requiredFields = new Set(Array.isArray(schema?.required)
1965
+ ? schema.required.map((entry) => String(entry))
1966
+ : []);
1967
+ const questions = Object.entries(properties)
1968
+ .filter(([, value]) => value && typeof value === "object")
1969
+ .map(([key, value]) => {
1970
+ const field = value;
1971
+ const title = typeof field.title === "string" ? field.title : key;
1972
+ const description = typeof field.description === "string" ? field.description : message;
1973
+ const enumValues = Array.isArray(field.enum)
1974
+ ? field.enum.map((entry) => String(entry))
1975
+ : [];
1976
+ const type = typeof field.type === "string" ? field.type : "";
1977
+ const options = enumValues.length > 0
1978
+ ? enumValues.map((entry, index) => ({
1979
+ label: entry,
1980
+ description: index === 0 ? description : "",
1981
+ }))
1982
+ : type === "boolean"
1983
+ ? [
1984
+ { label: "true", description: description },
1985
+ { label: "false", description: "" },
1986
+ ]
1987
+ : [];
1988
+ return {
1989
+ id: key,
1990
+ question: requiredFields.has(key) ? `${title} (required)` : title,
1991
+ header: serverName,
1992
+ options,
1993
+ isOther: options.length === 0,
1994
+ isSecret: false,
1995
+ };
1996
+ });
1997
+ const normalizedQuestions = questions.length > 0
1998
+ ? questions
1999
+ : [
2000
+ {
2001
+ id: "value",
2002
+ question: message,
2003
+ header: serverName,
2004
+ options: [],
2005
+ isOther: true,
2006
+ isSecret: false,
2007
+ },
2008
+ ];
2009
+ return {
2010
+ kind: "elicitation_form",
2011
+ questions: normalizedQuestions.map((question) => ({
2012
+ id: question.id,
2013
+ question: question.question,
2014
+ })),
2015
+ input: {
2016
+ mode: "form",
2017
+ serverName,
2018
+ message,
2019
+ requestedSchema: schema,
2020
+ questions: normalizedQuestions.map((question) => ({
2021
+ id: question.id,
2022
+ header: question.header,
2023
+ question: question.question,
2024
+ options: question.options,
2025
+ multiSelect: false,
2026
+ isOther: question.isOther,
2027
+ isSecret: question.isSecret,
2028
+ })),
2029
+ },
2030
+ };
2031
+ }
2032
+ function asRecord(value) {
2033
+ return value && typeof value === "object" && !Array.isArray(value)
2034
+ ? value
2035
+ : undefined;
2036
+ }
1370
2037
  function formatPlanUpdateText(params) {
1371
2038
  const stepsRaw = params.plan;
1372
2039
  if (!Array.isArray(stepsRaw) || stepsRaw.length === 0)