@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.
- package/README.md +4 -0
- package/dist/codex-process.d.ts +30 -0
- package/dist/codex-process.js +750 -83
- package/dist/codex-process.js.map +1 -1
- package/dist/parser.d.ts +16 -0
- package/dist/parser.js +15 -0
- package/dist/parser.js.map +1 -1
- package/dist/push-i18n.d.ts +1 -1
- package/dist/push-i18n.js +18 -1
- package/dist/push-i18n.js.map +1 -1
- package/dist/session.d.ts +4 -0
- package/dist/session.js +147 -69
- package/dist/session.js.map +1 -1
- package/dist/sessions-index.d.ts +2 -0
- package/dist/sessions-index.js +10 -0
- package/dist/sessions-index.js.map +1 -1
- package/dist/websocket.d.ts +7 -0
- package/dist/websocket.js +404 -106
- package/dist/websocket.js.map +1 -1
- package/package.json +1 -1
package/dist/codex-process.js
CHANGED
|
@@ -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({
|
|
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({
|
|
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 &&
|
|
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 &&
|
|
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 ??
|
|
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.
|
|
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
|
-
|
|
398
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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 ??
|
|
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({
|
|
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
|
-
|
|
535
|
-
|
|
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:
|
|
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 &&
|
|
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 &&
|
|
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"
|
|
732
|
+
...(typeof params.command === "string"
|
|
733
|
+
? { command: params.command }
|
|
734
|
+
: {}),
|
|
639
735
|
...(typeof params.cwd === "string" ? { cwd: params.cwd } : {}),
|
|
640
|
-
...(params.commandActions
|
|
641
|
-
|
|
642
|
-
|
|
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.
|
|
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:
|
|
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
|
|
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 &&
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
1248
|
+
model: this.getMessageModel(),
|
|
1007
1249
|
},
|
|
1008
1250
|
});
|
|
1009
1251
|
this.emitMessage({
|
|
1010
1252
|
type: "tool_result",
|
|
1011
1253
|
toolUseId: itemId,
|
|
1012
|
-
content:
|
|
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:
|
|
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({
|
|
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)
|
|
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"
|
|
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)
|