codex-relay 1.4.9 → 1.4.10
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/dist/api-schema2.js +1 -0
- package/dist/src.js +510 -113
- package/package.json +2 -1
- package/src/api-schema.ts +1 -0
package/dist/api-schema2.js
CHANGED
|
@@ -250,6 +250,7 @@ const PendingInputRequestQuestionSchema = z.object({
|
|
|
250
250
|
});
|
|
251
251
|
const PendingInputRequestSchema = z.object({
|
|
252
252
|
id: z.string().min(1),
|
|
253
|
+
isBlocking: z.boolean().default(true),
|
|
253
254
|
questions: z.array(PendingInputRequestQuestionSchema),
|
|
254
255
|
threadId: z.string().min(1),
|
|
255
256
|
turnId: z.string().optional()
|
package/dist/src.js
CHANGED
|
@@ -3,7 +3,7 @@ import { a as getConnectUrlCandidates, c as permanentClientSessionExpiresAt, i a
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import qrcode from "qrcode-terminal";
|
|
5
5
|
import { execFile, spawn } from "node:child_process";
|
|
6
|
-
import fs, { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
6
|
+
import fs, { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { access, appendFile, copyFile, mkdir, open, readFile, readdir, realpath, stat, writeFile } from "node:fs/promises";
|
|
8
8
|
import path, { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
9
9
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
@@ -36,6 +36,7 @@ const stdioAppServerArgs = [
|
|
|
36
36
|
];
|
|
37
37
|
const sharedWindowsServerUrl = "ws://127.0.0.1:8788";
|
|
38
38
|
const windowsShellExtensions = new Set([".bat", ".cmd"]);
|
|
39
|
+
const require = createRequire(import.meta.url);
|
|
39
40
|
function resolveCodexAppServerMode(input = {}) {
|
|
40
41
|
const env = input.env ?? process.env;
|
|
41
42
|
const platform$2 = input.platform ?? platform();
|
|
@@ -59,35 +60,46 @@ function resolveCodexAppServerMode(input = {}) {
|
|
|
59
60
|
}
|
|
60
61
|
function resolveCodexAppServerSpawn(input = {}) {
|
|
61
62
|
const platform$3 = input.platform ?? platform();
|
|
62
|
-
const
|
|
63
|
+
const executable = resolveCodexExecutable(input.env ?? process.env);
|
|
63
64
|
const isWindows = platform$3 === "win32";
|
|
64
65
|
return {
|
|
65
|
-
command,
|
|
66
|
-
args: [...stdioAppServerArgs],
|
|
67
|
-
shell: isWindows && shouldUseWindowsShell(command),
|
|
66
|
+
command: executable.command,
|
|
67
|
+
args: [...executable.args, ...stdioAppServerArgs],
|
|
68
|
+
shell: isWindows && executable.explicit && shouldUseWindowsShell(executable.command),
|
|
68
69
|
windowsHide: isWindows
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
function resolveCodexSharedAppServerSpawn(input = {}) {
|
|
72
73
|
const platform$4 = input.platform ?? platform();
|
|
73
|
-
const
|
|
74
|
+
const executable = resolveCodexExecutable(input.env ?? process.env);
|
|
74
75
|
const isWindows = platform$4 === "win32";
|
|
75
76
|
return {
|
|
76
|
-
command,
|
|
77
|
+
command: executable.command,
|
|
77
78
|
args: [
|
|
79
|
+
...executable.args,
|
|
78
80
|
"app-server",
|
|
79
81
|
"--listen",
|
|
80
82
|
resolveCodexSharedAppServerRemoteAddress(platform$4)
|
|
81
83
|
],
|
|
82
|
-
shell: isWindows && shouldUseWindowsShell(command),
|
|
84
|
+
shell: isWindows && executable.explicit && shouldUseWindowsShell(executable.command),
|
|
83
85
|
windowsHide: isWindows
|
|
84
86
|
};
|
|
85
87
|
}
|
|
86
88
|
function resolveCodexSharedAppServerRemoteAddress(platform$1 = platform()) {
|
|
87
89
|
return platform$1 === "win32" ? sharedWindowsServerUrl : "unix://";
|
|
88
90
|
}
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
+
function resolveCodexExecutable(env) {
|
|
92
|
+
const configuredBinary = env.CODEX_BIN?.trim();
|
|
93
|
+
if (configuredBinary) return {
|
|
94
|
+
args: [],
|
|
95
|
+
command: configuredBinary,
|
|
96
|
+
explicit: true
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
args: [join(dirname(require.resolve("@openai/codex/package.json")), "bin", "codex.js")],
|
|
100
|
+
command: process.execPath,
|
|
101
|
+
explicit: false
|
|
102
|
+
};
|
|
91
103
|
}
|
|
92
104
|
function shouldUseWindowsShell(command) {
|
|
93
105
|
const extension = extname(command).toLowerCase();
|
|
@@ -155,6 +167,9 @@ var CodexAppServerClient = class {
|
|
|
155
167
|
sharedReconnectEnabled = false;
|
|
156
168
|
sharedServer;
|
|
157
169
|
socket;
|
|
170
|
+
activeTurnIdsByThreadId = /* @__PURE__ */ new Map();
|
|
171
|
+
terminalTurnIdsByThreadId = /* @__PURE__ */ new Map();
|
|
172
|
+
subscribedThreadIds = /* @__PURE__ */ new Set();
|
|
158
173
|
onStartupFallback;
|
|
159
174
|
startSharedServer;
|
|
160
175
|
constructor(options = {}) {
|
|
@@ -171,13 +186,30 @@ var CodexAppServerClient = class {
|
|
|
171
186
|
return this.ensureInitialized();
|
|
172
187
|
}
|
|
173
188
|
async listThreads(limit = 80) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
189
|
+
const threads = [];
|
|
190
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
191
|
+
let cursor;
|
|
192
|
+
do {
|
|
193
|
+
const response = await this.request("thread/list", {
|
|
194
|
+
archived: false,
|
|
195
|
+
cursor,
|
|
196
|
+
limit,
|
|
197
|
+
sortDirection: "desc",
|
|
198
|
+
sortKey: "recency_at",
|
|
199
|
+
sourceKinds: [
|
|
200
|
+
"cli",
|
|
201
|
+
"vscode",
|
|
202
|
+
"exec",
|
|
203
|
+
"appServer"
|
|
204
|
+
]
|
|
205
|
+
});
|
|
206
|
+
threads.push(...response.data);
|
|
207
|
+
const nextCursor = response.nextCursor ?? void 0;
|
|
208
|
+
if (!nextCursor || seenCursors.has(nextCursor)) break;
|
|
209
|
+
seenCursors.add(nextCursor);
|
|
210
|
+
cursor = nextCursor;
|
|
211
|
+
} while (cursor);
|
|
212
|
+
return threads;
|
|
181
213
|
}
|
|
182
214
|
async readThread(threadId, options = {}) {
|
|
183
215
|
return (await this.request("thread/read", {
|
|
@@ -195,19 +227,39 @@ var CodexAppServerClient = class {
|
|
|
195
227
|
return this.request("account/rateLimits/read", null);
|
|
196
228
|
}
|
|
197
229
|
async startThread(params) {
|
|
198
|
-
|
|
230
|
+
const response = await this.request("thread/start", params);
|
|
231
|
+
this.subscribedThreadIds.add(response.thread.id);
|
|
232
|
+
return response.thread;
|
|
199
233
|
}
|
|
200
234
|
async resumeThread(params) {
|
|
201
|
-
|
|
235
|
+
const response = await this.request("thread/resume", params.excludeTurns ? {
|
|
236
|
+
...params,
|
|
237
|
+
initialTurnsPage: {
|
|
238
|
+
itemsView: "summary",
|
|
239
|
+
limit: 1,
|
|
240
|
+
sortDirection: "desc"
|
|
241
|
+
}
|
|
242
|
+
} : params);
|
|
243
|
+
this.subscribedThreadIds.add(response.thread.id);
|
|
244
|
+
this.rememberActiveTurn({
|
|
245
|
+
...response.thread,
|
|
246
|
+
turns: response.initialTurnsPage?.data ?? response.thread.turns
|
|
247
|
+
});
|
|
248
|
+
return response.thread;
|
|
202
249
|
}
|
|
203
250
|
async startTurn(params) {
|
|
204
|
-
|
|
251
|
+
const response = await this.request("turn/start", params);
|
|
252
|
+
this.rememberTurn(params.threadId, response.turn);
|
|
253
|
+
return response.turn;
|
|
205
254
|
}
|
|
206
255
|
async interruptTurn(params) {
|
|
207
256
|
await this.request("turn/interrupt", params);
|
|
208
257
|
}
|
|
209
258
|
async archiveThread(params) {
|
|
210
259
|
await this.request("thread/archive", params);
|
|
260
|
+
this.subscribedThreadIds.delete(params.threadId);
|
|
261
|
+
this.activeTurnIdsByThreadId.delete(params.threadId);
|
|
262
|
+
this.terminalTurnIdsByThreadId.delete(params.threadId);
|
|
211
263
|
}
|
|
212
264
|
async setThreadName(params) {
|
|
213
265
|
await this.request("thread/name/set", params);
|
|
@@ -215,6 +267,9 @@ var CodexAppServerClient = class {
|
|
|
215
267
|
async rollbackThread(params) {
|
|
216
268
|
return (await this.request("thread/rollback", params)).thread;
|
|
217
269
|
}
|
|
270
|
+
async revertThread(params) {
|
|
271
|
+
return (await this.request("thread/revert", params)).thread;
|
|
272
|
+
}
|
|
218
273
|
async getThreadGoal(params) {
|
|
219
274
|
return (await this.request("thread/goal/get", params)).goal;
|
|
220
275
|
}
|
|
@@ -255,6 +310,9 @@ var CodexAppServerClient = class {
|
|
|
255
310
|
this.stopStdioCodexAppServer();
|
|
256
311
|
this.stopSharedCodexAppServer();
|
|
257
312
|
this.initialized = void 0;
|
|
313
|
+
this.activeTurnIdsByThreadId.clear();
|
|
314
|
+
this.terminalTurnIdsByThreadId.clear();
|
|
315
|
+
this.subscribedThreadIds.clear();
|
|
258
316
|
}
|
|
259
317
|
async request(method, params) {
|
|
260
318
|
await this.ensureInitialized();
|
|
@@ -322,8 +380,90 @@ var CodexAppServerClient = class {
|
|
|
322
380
|
title: "Codex Relay Mobile Server",
|
|
323
381
|
version: "1.2.0"
|
|
324
382
|
},
|
|
325
|
-
capabilities: {
|
|
383
|
+
capabilities: {
|
|
384
|
+
experimentalApi: true,
|
|
385
|
+
requestAttestation: false
|
|
386
|
+
}
|
|
326
387
|
});
|
|
388
|
+
await this.writeJson({ method: "initialized" });
|
|
389
|
+
for (const threadId of this.subscribedThreadIds) try {
|
|
390
|
+
const disconnectedTurnId = this.activeTurnIdsByThreadId.get(threadId);
|
|
391
|
+
const response = await this.requestRaw("thread/resume", {
|
|
392
|
+
excludeTurns: true,
|
|
393
|
+
initialTurnsPage: {
|
|
394
|
+
itemsView: "summary",
|
|
395
|
+
limit: 1,
|
|
396
|
+
sortDirection: "desc"
|
|
397
|
+
},
|
|
398
|
+
threadId
|
|
399
|
+
});
|
|
400
|
+
this.reconcileResumedThread({
|
|
401
|
+
...response.thread,
|
|
402
|
+
turns: response.initialTurnsPage?.data ?? response.thread.turns
|
|
403
|
+
}, disconnectedTurnId);
|
|
404
|
+
} catch (error) {
|
|
405
|
+
relayDebugLog("app_server.thread.resubscribe_failed", {
|
|
406
|
+
message: error instanceof Error ? error.message : String(error),
|
|
407
|
+
threadId
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
reconcileResumedThread(thread, disconnectedTurnId) {
|
|
412
|
+
if (disconnectedTurnId) {
|
|
413
|
+
const disconnectedTurn = thread.turns?.find((turn) => turn.id === disconnectedTurnId);
|
|
414
|
+
if (disconnectedTurn && !isAppServerTurnInProgress(disconnectedTurn)) this.dispatchNotification({
|
|
415
|
+
method: "turn/completed",
|
|
416
|
+
params: {
|
|
417
|
+
threadId: thread.id,
|
|
418
|
+
turn: disconnectedTurn
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
else this.dispatchNotification({
|
|
422
|
+
method: "thread/status/changed",
|
|
423
|
+
params: {
|
|
424
|
+
status: thread.status,
|
|
425
|
+
threadId: thread.id
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
this.rememberActiveTurn(thread);
|
|
430
|
+
}
|
|
431
|
+
rememberActiveTurn(thread) {
|
|
432
|
+
const activeTurn = [...thread.turns ?? []].reverse().find((turn) => isAppServerTurnInProgress(turn));
|
|
433
|
+
if (activeTurn && this.terminalTurnIdsByThreadId.get(thread.id) !== activeTurn.id) {
|
|
434
|
+
this.activeTurnIdsByThreadId.set(thread.id, activeTurn.id);
|
|
435
|
+
this.terminalTurnIdsByThreadId.delete(thread.id);
|
|
436
|
+
} else this.activeTurnIdsByThreadId.delete(thread.id);
|
|
437
|
+
}
|
|
438
|
+
rememberTurn(threadId, turn) {
|
|
439
|
+
if (isAppServerTurnInProgress(turn)) {
|
|
440
|
+
if (this.terminalTurnIdsByThreadId.get(threadId) !== turn.id) {
|
|
441
|
+
this.activeTurnIdsByThreadId.set(threadId, turn.id);
|
|
442
|
+
this.terminalTurnIdsByThreadId.delete(threadId);
|
|
443
|
+
}
|
|
444
|
+
} else if (this.activeTurnIdsByThreadId.get(threadId) === turn.id) this.activeTurnIdsByThreadId.delete(threadId);
|
|
445
|
+
}
|
|
446
|
+
dispatchNotification(notification) {
|
|
447
|
+
const params = notification.params;
|
|
448
|
+
const record = params && typeof params === "object" ? params : {};
|
|
449
|
+
const threadId = typeof record.threadId === "string" ? record.threadId : void 0;
|
|
450
|
+
const turn = record.turn && typeof record.turn === "object" ? record.turn : void 0;
|
|
451
|
+
const turnId = typeof record.turnId === "string" ? record.turnId : typeof turn?.id === "string" ? turn.id : void 0;
|
|
452
|
+
if (notification.method === "turn/started" && threadId && turnId) {
|
|
453
|
+
if (this.terminalTurnIdsByThreadId.get(threadId) !== turnId) {
|
|
454
|
+
this.activeTurnIdsByThreadId.set(threadId, turnId);
|
|
455
|
+
this.terminalTurnIdsByThreadId.delete(threadId);
|
|
456
|
+
}
|
|
457
|
+
} else if ([
|
|
458
|
+
"turn/completed",
|
|
459
|
+
"turn/failed",
|
|
460
|
+
"turn/aborted",
|
|
461
|
+
"turn/cancelled"
|
|
462
|
+
].includes(notification.method) && threadId) {
|
|
463
|
+
if (turnId) this.terminalTurnIdsByThreadId.set(threadId, turnId);
|
|
464
|
+
if (!turnId || this.activeTurnIdsByThreadId.get(threadId) === turnId) this.activeTurnIdsByThreadId.delete(threadId);
|
|
465
|
+
}
|
|
466
|
+
for (const handler of this.notificationHandlers) handler(notification);
|
|
327
467
|
}
|
|
328
468
|
startStdioCodexAppServer() {
|
|
329
469
|
const spawnConfig = resolveCodexAppServerSpawn();
|
|
@@ -528,7 +668,7 @@ var CodexAppServerClient = class {
|
|
|
528
668
|
} catch {
|
|
529
669
|
return;
|
|
530
670
|
}
|
|
531
|
-
if (typeof message.method === "string" && typeof message.id === "number") {
|
|
671
|
+
if (typeof message.method === "string" && (typeof message.id === "number" || typeof message.id === "string")) {
|
|
532
672
|
debugAppServer("server-request", message.method, message.id);
|
|
533
673
|
const request = {
|
|
534
674
|
id: message.id,
|
|
@@ -545,7 +685,7 @@ var CodexAppServerClient = class {
|
|
|
545
685
|
method: message.method,
|
|
546
686
|
params: message.params
|
|
547
687
|
};
|
|
548
|
-
|
|
688
|
+
this.dispatchNotification(notification);
|
|
549
689
|
return;
|
|
550
690
|
}
|
|
551
691
|
if (typeof message.id !== "number") return;
|
|
@@ -644,6 +784,15 @@ function connectRemoteAddress(remoteAddress) {
|
|
|
644
784
|
function sharedCodexAppServerSocketPath() {
|
|
645
785
|
return join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "app-server-control", "app-server-control.sock");
|
|
646
786
|
}
|
|
787
|
+
function isAppServerTurnInProgress(turn) {
|
|
788
|
+
if (turn.completedAt !== null && turn.completedAt !== void 0) return false;
|
|
789
|
+
const status = typeof turn.status === "string" ? turn.status : turn.status && typeof turn.status === "object" && "type" in turn.status ? String(turn.status.type) : "";
|
|
790
|
+
return [
|
|
791
|
+
"active",
|
|
792
|
+
"inprogress",
|
|
793
|
+
"running"
|
|
794
|
+
].includes(status.toLowerCase().replace(/[^a-z0-9]/g, ""));
|
|
795
|
+
}
|
|
647
796
|
function asError(error) {
|
|
648
797
|
return error instanceof Error ? error : new Error(String(error));
|
|
649
798
|
}
|
|
@@ -672,7 +821,8 @@ function extractStreamText(event) {
|
|
|
672
821
|
if (!event || typeof event !== "object") return;
|
|
673
822
|
const record = event;
|
|
674
823
|
const item = record.item && typeof record.item === "object" ? record.item : void 0;
|
|
675
|
-
const
|
|
824
|
+
const error = record.error && typeof record.error === "object" ? record.error : void 0;
|
|
825
|
+
const candidate = record.delta ?? record.text ?? record.message ?? error?.message ?? item?.text ?? item?.message ?? item?.aggregated_output ?? item?.command;
|
|
676
826
|
return typeof candidate === "string" && candidate.length > 0 ? candidate : void 0;
|
|
677
827
|
}
|
|
678
828
|
function classifyStreamEvent(event) {
|
|
@@ -1472,11 +1622,47 @@ function createApp(options = {}) {
|
|
|
1472
1622
|
const appServerMutationTails = /* @__PURE__ */ new Map();
|
|
1473
1623
|
const workspaceTerminalSessions = /* @__PURE__ */ new Map();
|
|
1474
1624
|
const activeAppServerTurnIdsByThreadId = /* @__PURE__ */ new Map();
|
|
1625
|
+
const appServerHistoryGenerationsByThreadId = /* @__PURE__ */ new Map();
|
|
1475
1626
|
const appServerHistoryLoadsByThreadId = /* @__PURE__ */ new Map();
|
|
1627
|
+
const appServerRolloutPathsByThreadId = /* @__PURE__ */ new Map();
|
|
1476
1628
|
const steeringThreads = /* @__PURE__ */ new Set();
|
|
1477
1629
|
const secureSessionsByTokenHash = /* @__PURE__ */ new Map();
|
|
1478
1630
|
const activeStreamControllers = /* @__PURE__ */ new Map();
|
|
1479
|
-
const threadOptions = {
|
|
1631
|
+
const threadOptions = {
|
|
1632
|
+
threadSource: "codex-relay",
|
|
1633
|
+
workingDirectory: workspacePath
|
|
1634
|
+
};
|
|
1635
|
+
const advanceAppServerHistoryGeneration = (threadId) => {
|
|
1636
|
+
appServerHistoryGenerationsByThreadId.set(threadId, (appServerHistoryGenerationsByThreadId.get(threadId) ?? 0) + 1);
|
|
1637
|
+
};
|
|
1638
|
+
const invalidateAppServerHistory = (threadId) => {
|
|
1639
|
+
advanceAppServerHistoryGeneration(threadId);
|
|
1640
|
+
appServerHistoryLoadsByThreadId.delete(threadId);
|
|
1641
|
+
appServerRolloutPathsByThreadId.delete(threadId);
|
|
1642
|
+
messagesByThreadId.delete(threadId);
|
|
1643
|
+
};
|
|
1644
|
+
if (appServer && typeof appServer.onNotification === "function") appServer.onNotification((notification) => {
|
|
1645
|
+
const params = recordParams(notification);
|
|
1646
|
+
const threadId = firstString(params, ["threadId"]);
|
|
1647
|
+
if (!threadId) return;
|
|
1648
|
+
if (notification.method === "thread/reverted") {
|
|
1649
|
+
invalidateAppServerHistory(threadId);
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
if (notification.method === "item/completed") {
|
|
1653
|
+
advanceAppServerHistoryGeneration(threadId);
|
|
1654
|
+
const item = params?.item;
|
|
1655
|
+
if (item && typeof item === "object" && item.type !== "userMessage") upsertAppServerItemMessage(messagesByThreadId, threadId, firstString(params, ["turnId"]), item);
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
if (notification.method === "turn/completed") {
|
|
1659
|
+
advanceAppServerHistoryGeneration(threadId);
|
|
1660
|
+
const turn = appServerTurnFromParams(params);
|
|
1661
|
+
if (turn) {
|
|
1662
|
+
for (const item of turn.items) if (item.type !== "userMessage") upsertAppServerItemMessage(messagesByThreadId, threadId, turn.id, item);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1480
1666
|
function runThreadOperation(threadId, operation) {
|
|
1481
1667
|
const result = (threadOperationTails.get(threadId) ?? Promise.resolve()).then(operation, operation);
|
|
1482
1668
|
const tail = result.then(() => void 0, () => void 0);
|
|
@@ -1503,13 +1689,18 @@ function createApp(options = {}) {
|
|
|
1503
1689
|
const scheduleAppServerHistoryLoad = (threadId, cachedMessages) => {
|
|
1504
1690
|
if (!appServer || appServerHistoryLoadsByThreadId.has(threadId)) return;
|
|
1505
1691
|
const startedAt = Date.now();
|
|
1692
|
+
const historyGeneration = appServerHistoryGenerationsByThreadId.get(threadId) ?? 0;
|
|
1506
1693
|
relayDebugLog("thread.detail.history.background_started", {
|
|
1507
1694
|
cachedMessageCount: cachedMessages.length,
|
|
1508
1695
|
threadId
|
|
1509
1696
|
});
|
|
1510
1697
|
const load = appServer.readThread(threadId, { includeTurns: true }).then((threadWithTurns) => {
|
|
1511
|
-
|
|
1512
|
-
const
|
|
1698
|
+
if ((appServerHistoryGenerationsByThreadId.get(threadId) ?? 0) !== historyGeneration) return;
|
|
1699
|
+
const hasCompleteHistory = appServerThreadHasCompleteHistory(threadWithTurns);
|
|
1700
|
+
const mappedThread = rememberAppServerThread(threads, threadWithTurns, { authoritativeMessageCount: hasCompleteHistory });
|
|
1701
|
+
const appMessages = mapAppServerMessages(threadWithTurns);
|
|
1702
|
+
const localMessages = messagesByThreadId.get(threadId) ?? cachedMessages;
|
|
1703
|
+
const messages = hasCompleteHistory ? mergeAppServerMessagesWithLocalStatus(appMessages, localMessages) : mergeThreadMessagePages(appMessages, localMessages);
|
|
1513
1704
|
messagesByThreadId.set(threadId, messages);
|
|
1514
1705
|
relayDebugLog("thread.detail.history.background_completed", {
|
|
1515
1706
|
durationMs: Date.now() - startedAt,
|
|
@@ -1524,7 +1715,7 @@ function createApp(options = {}) {
|
|
|
1524
1715
|
threadId
|
|
1525
1716
|
});
|
|
1526
1717
|
}).finally(() => {
|
|
1527
|
-
appServerHistoryLoadsByThreadId.delete(threadId);
|
|
1718
|
+
if (appServerHistoryLoadsByThreadId.get(threadId) === load) appServerHistoryLoadsByThreadId.delete(threadId);
|
|
1528
1719
|
});
|
|
1529
1720
|
appServerHistoryLoadsByThreadId.set(threadId, load);
|
|
1530
1721
|
};
|
|
@@ -2203,13 +2394,27 @@ function createApp(options = {}) {
|
|
|
2203
2394
|
const turns = threadBeforeRewind.turns ?? [];
|
|
2204
2395
|
const turnIndex = turns.findIndex((turn) => turn.id === parsed.data.turnId);
|
|
2205
2396
|
if (turnIndex < 0) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("not_found", "The selected message is no longer available to rewind."), 404);
|
|
2206
|
-
|
|
2397
|
+
let rolledBackThread;
|
|
2398
|
+
if (threadBeforeRewind.historyMode === "paginated" && typeof appServer.revertThread === "function") try {
|
|
2399
|
+
rolledBackThread = await appServer.revertThread({
|
|
2400
|
+
beforeTurnId: parsed.data.turnId,
|
|
2401
|
+
threadId
|
|
2402
|
+
});
|
|
2403
|
+
} catch (error) {
|
|
2404
|
+
if (!/method not found|unsupported|-32601/i.test(errorMessage(error))) throw error;
|
|
2405
|
+
rolledBackThread = await appServer.rollbackThread({
|
|
2406
|
+
threadId,
|
|
2407
|
+
numTurns: turns.length - turnIndex
|
|
2408
|
+
});
|
|
2409
|
+
}
|
|
2410
|
+
else rolledBackThread = await appServer.rollbackThread({
|
|
2207
2411
|
threadId,
|
|
2208
2412
|
numTurns: turns.length - turnIndex
|
|
2209
2413
|
});
|
|
2210
2414
|
const threadWithTurns = rolledBackThread.turns === void 0 ? await appServer.readThread(threadId, { includeTurns: true }) : rolledBackThread;
|
|
2211
2415
|
const thread = rememberAppServerThread(threads, threadWithTurns, { authoritativeMessageCount: true });
|
|
2212
2416
|
const messages = mapAppServerMessages(threadWithTurns);
|
|
2417
|
+
invalidateAppServerHistory(threadId);
|
|
2213
2418
|
messagesByThreadId.set(threadId, messages);
|
|
2214
2419
|
liveThreads.delete(threadId);
|
|
2215
2420
|
activeAppServerTurnIdsByThreadId.delete(threadId);
|
|
@@ -2239,18 +2444,29 @@ function createApp(options = {}) {
|
|
|
2239
2444
|
const thread = await appServer.readThread(threadId, { includeTurns: false });
|
|
2240
2445
|
if (isSubagentThread(thread)) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("not_found", `Thread ${threadId} is not known to this server.`), 404);
|
|
2241
2446
|
const mappedThread = rememberAppServerThread(threads, thread);
|
|
2242
|
-
|
|
2447
|
+
let cachedMessages = messagesByThreadId.get(threadId) ?? [];
|
|
2448
|
+
if (thread.path) {
|
|
2449
|
+
const previousRolloutPath = appServerRolloutPathsByThreadId.get(threadId);
|
|
2450
|
+
if (previousRolloutPath && previousRolloutPath !== thread.path) {
|
|
2451
|
+
invalidateAppServerHistory(threadId);
|
|
2452
|
+
cachedMessages = [];
|
|
2453
|
+
}
|
|
2454
|
+
appServerRolloutPathsByThreadId.set(threadId, thread.path);
|
|
2455
|
+
}
|
|
2243
2456
|
let loadedMessages = false;
|
|
2244
2457
|
let messages = cachedMessages;
|
|
2245
2458
|
let responseThread = preserveKnownRunningThreadState(mappedThread, wasKnownRunning);
|
|
2246
2459
|
if (forceRefresh && responseThread.state !== "running") {
|
|
2247
2460
|
const threadWithTurns = await appServer.readThread(threadId, { includeTurns: true });
|
|
2248
|
-
|
|
2249
|
-
|
|
2461
|
+
const hasCompleteHistory = appServerThreadHasCompleteHistory(threadWithTurns);
|
|
2462
|
+
responseThread = rememberAppServerThread(threads, threadWithTurns, { authoritativeMessageCount: hasCompleteHistory });
|
|
2463
|
+
const appMessages = mapAppServerMessages(threadWithTurns);
|
|
2464
|
+
messages = hasCompleteHistory ? mergeAppServerMessagesWithLocalStatus(appMessages, cachedMessages) : mergeThreadMessagePages(appMessages, cachedMessages);
|
|
2250
2465
|
messagesByThreadId.set(threadId, messages);
|
|
2251
2466
|
loadedMessages = true;
|
|
2252
2467
|
} else {
|
|
2253
|
-
const rolloutHistory = readRolloutThreadMessages(threadId, workspacePath);
|
|
2468
|
+
const rolloutHistory = readRolloutThreadMessages(threadId, workspacePath, thread.path ?? void 0);
|
|
2469
|
+
if (rolloutHistory.rolloutPath) appServerRolloutPathsByThreadId.set(threadId, rolloutHistory.rolloutPath);
|
|
2254
2470
|
if (rolloutHistory.messages.length > 0) {
|
|
2255
2471
|
messages = mergeThreadMessagePages(rolloutHistory.messages, cachedMessages);
|
|
2256
2472
|
responseThread = rememberRolloutThreadMessages(threads, responseThread, messages, rolloutHistory.messageCountLowerBound);
|
|
@@ -2265,8 +2481,10 @@ function createApp(options = {}) {
|
|
|
2265
2481
|
}
|
|
2266
2482
|
if (!loadedMessages && responseThread.state !== "running") {
|
|
2267
2483
|
const threadWithTurns = await appServer.readThread(threadId, { includeTurns: true });
|
|
2268
|
-
|
|
2269
|
-
|
|
2484
|
+
const hasCompleteHistory = appServerThreadHasCompleteHistory(threadWithTurns);
|
|
2485
|
+
responseThread = rememberAppServerThread(threads, threadWithTurns, { authoritativeMessageCount: hasCompleteHistory });
|
|
2486
|
+
const appMessages = mapAppServerMessages(threadWithTurns);
|
|
2487
|
+
messages = hasCompleteHistory ? mergeAppServerMessagesWithLocalStatus(appMessages, cachedMessages) : mergeThreadMessagePages(appMessages, cachedMessages);
|
|
2270
2488
|
messagesByThreadId.set(threadId, messages);
|
|
2271
2489
|
loadedMessages = true;
|
|
2272
2490
|
} else if (!loadedMessages) scheduleAppServerHistoryLoad(threadId, cachedMessages);
|
|
@@ -2927,11 +3145,10 @@ async function createAppServerThreadRecord(input) {
|
|
|
2927
3145
|
const thread = await input.appServer.startThread({
|
|
2928
3146
|
approvalPolicy: runtime.approvalPolicy,
|
|
2929
3147
|
cwd: input.workspacePath,
|
|
2930
|
-
experimentalRawEvents: false,
|
|
2931
3148
|
model: input.options.model ?? null,
|
|
2932
|
-
persistExtendedHistory: true,
|
|
2933
3149
|
sandbox: runtime.sandbox,
|
|
2934
|
-
serviceTier: input.options.serviceTier ?? null
|
|
3150
|
+
serviceTier: input.options.serviceTier ?? null,
|
|
3151
|
+
threadSource: "codex-relay"
|
|
2935
3152
|
});
|
|
2936
3153
|
const metadata = ThreadSummarySchema.parse({
|
|
2937
3154
|
...mapAppServerThread({
|
|
@@ -3101,11 +3318,25 @@ async function runPromptStreamed(input) {
|
|
|
3101
3318
|
message: assistantMessage
|
|
3102
3319
|
});
|
|
3103
3320
|
for await (const event of streamed.events) {
|
|
3321
|
+
activeThreadId = replaceLocalThreadId(input.threads, input.messagesByThreadId, input.liveThreads, activeThreadId, getThreadId(thread));
|
|
3104
3322
|
const kind = classifyStreamEvent(event);
|
|
3105
3323
|
const text = extractStreamText(event);
|
|
3106
3324
|
if (kind === "error") throw new Error(text ?? "Codex run failed.");
|
|
3107
|
-
if (!text) continue;
|
|
3108
3325
|
if (kind === "assistant") {
|
|
3326
|
+
if (!text) continue;
|
|
3327
|
+
if (isCompletedAgentMessageEvent(event)) {
|
|
3328
|
+
assistantMessage = updateMessage(input.messagesByThreadId, activeThreadId, assistantMessage.id, { content: text });
|
|
3329
|
+
threadSummary = updateThread(input.threads, input.messagesByThreadId, activeThreadId, {
|
|
3330
|
+
state: "running",
|
|
3331
|
+
lastResult: assistantMessage.content
|
|
3332
|
+
});
|
|
3333
|
+
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3334
|
+
type: "thread.message.created",
|
|
3335
|
+
thread: threadSummary,
|
|
3336
|
+
message: assistantMessage
|
|
3337
|
+
});
|
|
3338
|
+
continue;
|
|
3339
|
+
}
|
|
3109
3340
|
const assistantPatch = appendMessageDelta(input.messagesByThreadId, activeThreadId, assistantMessage.id, text);
|
|
3110
3341
|
assistantMessage = assistantPatch.message;
|
|
3111
3342
|
updateThread(input.threads, input.messagesByThreadId, activeThreadId, {
|
|
@@ -3119,14 +3350,18 @@ async function runPromptStreamed(input) {
|
|
|
3119
3350
|
delta: assistantPatch.delta
|
|
3120
3351
|
});
|
|
3121
3352
|
} else {
|
|
3122
|
-
|
|
3123
|
-
const
|
|
3353
|
+
if (!text && !isRenderableStructuredStreamEvent(event)) continue;
|
|
3354
|
+
const structured = structuredStreamMessage(kind, event, text ?? "Codex activity");
|
|
3355
|
+
const itemId = firstString(eventItem(event), ["id"]);
|
|
3356
|
+
const existingMessage = itemId ? input.messagesByThreadId.get(activeThreadId)?.find((message) => message.id === itemId) : void 0;
|
|
3357
|
+
const message = {
|
|
3124
3358
|
role: kind,
|
|
3125
3359
|
kind: structured.kind,
|
|
3126
3360
|
content: structured.content,
|
|
3127
3361
|
details: structured.details,
|
|
3128
|
-
state:
|
|
3129
|
-
}
|
|
3362
|
+
state: streamItemState(event)
|
|
3363
|
+
};
|
|
3364
|
+
const statusMessage = existingMessage ? updateMessage(input.messagesByThreadId, activeThreadId, existingMessage.id, message) : itemId ? appendMessageWithId(input.messagesByThreadId, activeThreadId, itemId, message) : appendMessage(input.messagesByThreadId, activeThreadId, message);
|
|
3130
3365
|
threadSummary = updateThread(input.threads, input.messagesByThreadId, activeThreadId, { state: "running" });
|
|
3131
3366
|
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3132
3367
|
type: "thread.message.created",
|
|
@@ -3179,6 +3414,7 @@ async function startAppServerTurn(appServer, threadId, input) {
|
|
|
3179
3414
|
const runtime = resolveAppServerRuntime(input.runOptions, input.workspacePath);
|
|
3180
3415
|
const params = {
|
|
3181
3416
|
approvalPolicy: runtime.approvalPolicy,
|
|
3417
|
+
clientUserMessageId: input.id,
|
|
3182
3418
|
collaborationMode: appServerCollaborationMode(input.runOptions),
|
|
3183
3419
|
cwd: input.workspacePath,
|
|
3184
3420
|
effort: input.runOptions.reasoningEffort ?? null,
|
|
@@ -3213,9 +3449,8 @@ async function resumeAppServerThread(appServer, threadId, input, runtime) {
|
|
|
3213
3449
|
await appServer.resumeThread({
|
|
3214
3450
|
approvalPolicy: runtime.approvalPolicy,
|
|
3215
3451
|
cwd: input.workspacePath,
|
|
3216
|
-
excludeTurns:
|
|
3452
|
+
excludeTurns: true,
|
|
3217
3453
|
model: input.runOptions.model ?? null,
|
|
3218
|
-
persistExtendedHistory: true,
|
|
3219
3454
|
sandbox: runtime.sandbox,
|
|
3220
3455
|
serviceTier: input.runOptions.serviceTier ?? null,
|
|
3221
3456
|
threadId
|
|
@@ -3262,10 +3497,12 @@ async function streamRunningAppServerThread(input) {
|
|
|
3262
3497
|
let observedInputRequest = false;
|
|
3263
3498
|
let observedTurnActivity = false;
|
|
3264
3499
|
let producedTurnOutput = false;
|
|
3500
|
+
const asyncAgentMessageIds = /* @__PURE__ */ new Set();
|
|
3265
3501
|
let threadSummary = input.threads.get(input.threadId);
|
|
3266
3502
|
let cleanupNotificationHandler = () => void 0;
|
|
3267
3503
|
let cleanupRequestHandler = () => void 0;
|
|
3268
3504
|
let handlersCleaned = false;
|
|
3505
|
+
let streamFinished = false;
|
|
3269
3506
|
const cleanupHandlers = () => {
|
|
3270
3507
|
if (handlersCleaned) return;
|
|
3271
3508
|
handlersCleaned = true;
|
|
@@ -3286,6 +3523,7 @@ async function streamRunningAppServerThread(input) {
|
|
|
3286
3523
|
if (approval.kind === "structuredUserInput") {
|
|
3287
3524
|
const pending = {
|
|
3288
3525
|
appServer: input.appServer,
|
|
3526
|
+
isBlocking: approval.isBlocking,
|
|
3289
3527
|
kind: approval.kind,
|
|
3290
3528
|
method: request.method,
|
|
3291
3529
|
questions: approval.questions,
|
|
@@ -3330,6 +3568,7 @@ async function streamRunningAppServerThread(input) {
|
|
|
3330
3568
|
const finish = (error) => {
|
|
3331
3569
|
if (settled) return;
|
|
3332
3570
|
settled = true;
|
|
3571
|
+
streamFinished = true;
|
|
3333
3572
|
cleanupHandlers();
|
|
3334
3573
|
if (error === void 0) resolve();
|
|
3335
3574
|
else reject(error);
|
|
@@ -3340,6 +3579,20 @@ async function streamRunningAppServerThread(input) {
|
|
|
3340
3579
|
if (notificationThreadId && notificationThreadId !== input.threadId) return;
|
|
3341
3580
|
try {
|
|
3342
3581
|
switch (notification.method) {
|
|
3582
|
+
case "serverRequest/resolved": {
|
|
3583
|
+
const requestId = appServerRequestIdFromParams(params);
|
|
3584
|
+
if (requestId === void 0) return;
|
|
3585
|
+
const approvalId = appServerApprovalId(input.threadId, requestId);
|
|
3586
|
+
const pending = input.pendingApprovals.get(approvalId);
|
|
3587
|
+
if (!pending || pending.threadId !== input.threadId) return;
|
|
3588
|
+
input.pendingApprovals.delete(approvalId);
|
|
3589
|
+
if (pending.kind === "structuredUserInput") sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3590
|
+
type: "thread.input_request.resolved",
|
|
3591
|
+
requestId: approvalId,
|
|
3592
|
+
threadId: input.threadId
|
|
3593
|
+
});
|
|
3594
|
+
return;
|
|
3595
|
+
}
|
|
3343
3596
|
case "thread/status/changed": {
|
|
3344
3597
|
const state = mapAppServerThreadState(params?.status);
|
|
3345
3598
|
if (state !== "running" && observedTurnActivity) return;
|
|
@@ -3375,11 +3628,13 @@ async function streamRunningAppServerThread(input) {
|
|
|
3375
3628
|
if (!item || typeof item !== "object") return;
|
|
3376
3629
|
const message = upsertAppServerItemMessage(input.messagesByThreadId, input.threadId, firstString(params, ["turnId"]) ?? activeTurnId, item);
|
|
3377
3630
|
if (!message) return;
|
|
3378
|
-
|
|
3379
|
-
if (
|
|
3631
|
+
const isAsyncAgentMessage = isAsyncAppServerAgentMessage(item);
|
|
3632
|
+
if (isAsyncAgentMessage) asyncAgentMessageIds.add(message.id);
|
|
3633
|
+
if (message.role !== "user" && !isAsyncAgentMessage) producedTurnOutput = true;
|
|
3634
|
+
if (message.role === "assistant" && !isAsyncAgentMessage) assistantMessageId = message.id;
|
|
3380
3635
|
threadSummary = updateThread(input.threads, input.messagesByThreadId, input.threadId, {
|
|
3381
3636
|
state: "running",
|
|
3382
|
-
|
|
3637
|
+
...message.role === "assistant" && !isAsyncAgentMessage ? { lastResult: message.content } : {}
|
|
3383
3638
|
});
|
|
3384
3639
|
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3385
3640
|
type: notification.method === "item/completed" && message.role === "assistant" ? "thread.message.completed" : "thread.message.created",
|
|
@@ -3399,13 +3654,16 @@ async function streamRunningAppServerThread(input) {
|
|
|
3399
3654
|
state: "streaming",
|
|
3400
3655
|
turnId: firstString(params, ["turnId"]) ?? activeTurnId
|
|
3401
3656
|
});
|
|
3402
|
-
|
|
3403
|
-
|
|
3657
|
+
const isAsyncAgentMessage = asyncAgentMessageIds.has(itemId);
|
|
3658
|
+
if (!isAsyncAgentMessage) {
|
|
3659
|
+
assistantMessageId = itemId;
|
|
3660
|
+
producedTurnOutput = true;
|
|
3661
|
+
}
|
|
3404
3662
|
const patch = appendMessageDelta(input.messagesByThreadId, input.threadId, itemId, delta);
|
|
3405
3663
|
const message = patch.message;
|
|
3406
3664
|
updateThread(input.threads, input.messagesByThreadId, input.threadId, {
|
|
3407
3665
|
state: "running",
|
|
3408
|
-
lastResult: message.content
|
|
3666
|
+
...isAsyncAgentMessage ? {} : { lastResult: message.content }
|
|
3409
3667
|
});
|
|
3410
3668
|
if (patch.delta) sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3411
3669
|
type: "thread.message.delta",
|
|
@@ -3441,6 +3699,27 @@ async function streamRunningAppServerThread(input) {
|
|
|
3441
3699
|
case "turn/failed": {
|
|
3442
3700
|
observedTurnActivity = true;
|
|
3443
3701
|
const state = terminalTurnState(notification.method, params);
|
|
3702
|
+
const terminalTurn = appServerTurnFromParams(params);
|
|
3703
|
+
if (terminalTurn) {
|
|
3704
|
+
activeTurnId = terminalTurn.id;
|
|
3705
|
+
for (const item of terminalTurn.items) {
|
|
3706
|
+
const message = upsertAppServerItemMessage(input.messagesByThreadId, input.threadId, terminalTurn.id, item);
|
|
3707
|
+
if (!message) continue;
|
|
3708
|
+
const isAsyncAgentMessage = isAsyncAppServerAgentMessage(item);
|
|
3709
|
+
if (isAsyncAgentMessage) asyncAgentMessageIds.add(message.id);
|
|
3710
|
+
if (message.role !== "user" && !isAsyncAgentMessage) producedTurnOutput = true;
|
|
3711
|
+
if (message.role === "assistant" && !isAsyncAgentMessage) assistantMessageId = message.id;
|
|
3712
|
+
threadSummary = updateThread(input.threads, input.messagesByThreadId, input.threadId, {
|
|
3713
|
+
state: "running",
|
|
3714
|
+
...message.role === "assistant" && !isAsyncAgentMessage ? { lastResult: message.content } : {}
|
|
3715
|
+
});
|
|
3716
|
+
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3717
|
+
type: message.role === "assistant" ? "thread.message.completed" : "thread.message.created",
|
|
3718
|
+
thread: threadSummary,
|
|
3719
|
+
message
|
|
3720
|
+
});
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3444
3723
|
relayDebugLog("app_server.turn.terminal", {
|
|
3445
3724
|
method: notification.method,
|
|
3446
3725
|
state,
|
|
@@ -3460,7 +3739,8 @@ async function streamRunningAppServerThread(input) {
|
|
|
3460
3739
|
finish();
|
|
3461
3740
|
return;
|
|
3462
3741
|
}
|
|
3463
|
-
|
|
3742
|
+
const assistantMessage = assistantMessageId ? input.messagesByThreadId.get(input.threadId)?.find((message) => message.id === assistantMessageId) : void 0;
|
|
3743
|
+
if (assistantMessageId && assistantMessage?.state !== "completed") {
|
|
3464
3744
|
const completedMessage = updateMessage(input.messagesByThreadId, input.threadId, assistantMessageId, { state: "completed" });
|
|
3465
3745
|
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3466
3746
|
type: "thread.message.completed",
|
|
@@ -3518,8 +3798,15 @@ async function streamRunningAppServerThread(input) {
|
|
|
3518
3798
|
removeAbortListener = () => input.signal.removeEventListener("abort", onAbort);
|
|
3519
3799
|
});
|
|
3520
3800
|
try {
|
|
3521
|
-
const appServerThread = await Promise.race([input.appServer.
|
|
3801
|
+
const appServerThread = await Promise.race([typeof input.appServer.resumeThread === "function" ? input.appServer.resumeThread({
|
|
3802
|
+
excludeTurns: true,
|
|
3803
|
+
threadId: input.threadId
|
|
3804
|
+
}) : input.appServer.readThread(input.threadId, { includeTurns: false }), aborted.then(() => void 0)]);
|
|
3522
3805
|
if (!appServerThread || input.signal.aborted) return;
|
|
3806
|
+
if (streamFinished) {
|
|
3807
|
+
await completed;
|
|
3808
|
+
return;
|
|
3809
|
+
}
|
|
3523
3810
|
threadSummary = rememberAppServerThread(input.threads, appServerThread);
|
|
3524
3811
|
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3525
3812
|
type: "thread.state.changed",
|
|
@@ -3553,6 +3840,7 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3553
3840
|
let handedOffToQueuedTurn = false;
|
|
3554
3841
|
let observedInputRequest = false;
|
|
3555
3842
|
let producedTurnOutput = false;
|
|
3843
|
+
const asyncAgentMessageIds = /* @__PURE__ */ new Set();
|
|
3556
3844
|
let userMessage = appendMessage(input.messagesByThreadId, activeThreadId, {
|
|
3557
3845
|
role: "user",
|
|
3558
3846
|
content: displayPrompt,
|
|
@@ -3589,6 +3877,7 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3589
3877
|
if (approval.kind === "structuredUserInput") {
|
|
3590
3878
|
const pending = {
|
|
3591
3879
|
appServer: input.appServer,
|
|
3880
|
+
isBlocking: approval.isBlocking,
|
|
3592
3881
|
kind: approval.kind,
|
|
3593
3882
|
method: request.method,
|
|
3594
3883
|
questions: approval.questions,
|
|
@@ -3668,7 +3957,8 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3668
3957
|
resolveCompleted();
|
|
3669
3958
|
return;
|
|
3670
3959
|
}
|
|
3671
|
-
|
|
3960
|
+
const assistantMessage = assistantMessageId ? input.messagesByThreadId.get(activeThreadId)?.find((message) => message.id === assistantMessageId) : void 0;
|
|
3961
|
+
if (assistantMessageId && assistantMessage?.state !== "completed") {
|
|
3672
3962
|
const completedMessage = updateMessage(input.messagesByThreadId, activeThreadId, assistantMessageId, { state: "completed" });
|
|
3673
3963
|
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3674
3964
|
type: "thread.message.completed",
|
|
@@ -3723,8 +4013,7 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3723
4013
|
cleanupNotificationHandler();
|
|
3724
4014
|
resolveCompleted();
|
|
3725
4015
|
}
|
|
3726
|
-
|
|
3727
|
-
if (finalizedTurnIds.has(turn.id)) return;
|
|
4016
|
+
function processTurnItems(turn) {
|
|
3728
4017
|
activeTurnId = turn.id;
|
|
3729
4018
|
input.activeAppServerTurnIdsByThreadId.set(activeThreadId, turn.id);
|
|
3730
4019
|
const turnIsRunning = isAppServerTurnRunning(turn);
|
|
@@ -3750,11 +4039,13 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3750
4039
|
}
|
|
3751
4040
|
const message = upsertAppServerItemMessage(input.messagesByThreadId, activeThreadId, turn.id, item);
|
|
3752
4041
|
if (!message) continue;
|
|
3753
|
-
|
|
3754
|
-
if (
|
|
4042
|
+
const isAsyncAgentMessage = isAsyncAppServerAgentMessage(item);
|
|
4043
|
+
if (isAsyncAgentMessage) asyncAgentMessageIds.add(message.id);
|
|
4044
|
+
if (message.role !== "user" && !isAsyncAgentMessage) producedTurnOutput = true;
|
|
4045
|
+
if (message.role === "assistant" && !isAsyncAgentMessage) assistantMessageId = message.id;
|
|
3755
4046
|
threadSummary = updateThread(input.threads, input.messagesByThreadId, activeThreadId, {
|
|
3756
4047
|
state: "running",
|
|
3757
|
-
|
|
4048
|
+
...message.role === "assistant" && !isAsyncAgentMessage ? { lastResult: message.content } : {}
|
|
3758
4049
|
});
|
|
3759
4050
|
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3760
4051
|
type: message.role === "assistant" ? "thread.message.completed" : "thread.message.created",
|
|
@@ -3762,7 +4053,11 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3762
4053
|
message
|
|
3763
4054
|
});
|
|
3764
4055
|
}
|
|
3765
|
-
|
|
4056
|
+
return turnIsRunning;
|
|
4057
|
+
}
|
|
4058
|
+
async function processReturnedTurn(turn) {
|
|
4059
|
+
if (finalizedTurnIds.has(turn.id)) return;
|
|
4060
|
+
if (processTurnItems(turn)) return;
|
|
3766
4061
|
const params = { turn };
|
|
3767
4062
|
const state = terminalTurnState("turn/completed", params);
|
|
3768
4063
|
await finishTerminalTurn({
|
|
@@ -3788,6 +4083,20 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3788
4083
|
if (threadId && threadId !== activeThreadId) return;
|
|
3789
4084
|
try {
|
|
3790
4085
|
switch (notification.method) {
|
|
4086
|
+
case "serverRequest/resolved": {
|
|
4087
|
+
const requestId = appServerRequestIdFromParams(params);
|
|
4088
|
+
if (requestId === void 0) return;
|
|
4089
|
+
const approvalId = appServerApprovalId(activeThreadId, requestId);
|
|
4090
|
+
const pending = input.pendingApprovals.get(approvalId);
|
|
4091
|
+
if (!pending || pending.threadId !== activeThreadId) return;
|
|
4092
|
+
input.pendingApprovals.delete(approvalId);
|
|
4093
|
+
if (pending.kind === "structuredUserInput") sendSse(input.controller, input.encoder, input.secureSession, {
|
|
4094
|
+
type: "thread.input_request.resolved",
|
|
4095
|
+
requestId: approvalId,
|
|
4096
|
+
threadId: activeThreadId
|
|
4097
|
+
});
|
|
4098
|
+
return;
|
|
4099
|
+
}
|
|
3791
4100
|
case "thread/status/changed": {
|
|
3792
4101
|
const status = params?.status;
|
|
3793
4102
|
const state = mapAppServerThreadState(status);
|
|
@@ -3840,11 +4149,13 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3840
4149
|
const turnId = firstString(params, ["turnId"]) ?? activeTurnId;
|
|
3841
4150
|
const message = upsertAppServerItemMessage(input.messagesByThreadId, activeThreadId, turnId, item);
|
|
3842
4151
|
if (!message) return;
|
|
3843
|
-
|
|
3844
|
-
if (
|
|
4152
|
+
const isAsyncAgentMessage = isAsyncAppServerAgentMessage(item);
|
|
4153
|
+
if (isAsyncAgentMessage) asyncAgentMessageIds.add(message.id);
|
|
4154
|
+
if (message.role !== "user" && !isAsyncAgentMessage) producedTurnOutput = true;
|
|
4155
|
+
if (message.role === "assistant" && !isAsyncAgentMessage) assistantMessageId = message.id;
|
|
3845
4156
|
threadSummary = updateThread(input.threads, input.messagesByThreadId, activeThreadId, {
|
|
3846
4157
|
state: "running",
|
|
3847
|
-
|
|
4158
|
+
...message.role === "assistant" && !isAsyncAgentMessage ? { lastResult: message.content } : {}
|
|
3848
4159
|
});
|
|
3849
4160
|
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3850
4161
|
type: notification.method === "item/completed" && message.role === "assistant" ? "thread.message.completed" : "thread.message.created",
|
|
@@ -3863,13 +4174,14 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3863
4174
|
state: "streaming",
|
|
3864
4175
|
turnId: firstString(params, ["turnId"]) ?? activeTurnId
|
|
3865
4176
|
});
|
|
3866
|
-
|
|
4177
|
+
const isAsyncAgentMessage = asyncAgentMessageIds.has(itemId);
|
|
4178
|
+
if (!isAsyncAgentMessage) assistantMessageId = itemId;
|
|
3867
4179
|
const patch = appendMessageDelta(input.messagesByThreadId, activeThreadId, itemId, delta);
|
|
3868
4180
|
const message = patch.message;
|
|
3869
|
-
producedTurnOutput = true;
|
|
4181
|
+
if (!isAsyncAgentMessage) producedTurnOutput = true;
|
|
3870
4182
|
updateThread(input.threads, input.messagesByThreadId, activeThreadId, {
|
|
3871
4183
|
state: "running",
|
|
3872
|
-
lastResult: message.content
|
|
4184
|
+
...isAsyncAgentMessage ? {} : { lastResult: message.content }
|
|
3873
4185
|
});
|
|
3874
4186
|
if (patch.delta) sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3875
4187
|
type: "thread.message.delta",
|
|
@@ -3903,14 +4215,19 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3903
4215
|
case "turn/completed":
|
|
3904
4216
|
case "turn/failed": {
|
|
3905
4217
|
const state = terminalTurnState(notification.method, params);
|
|
4218
|
+
const terminalTurn = appServerTurnFromParams(params);
|
|
3906
4219
|
const terminalTurnId = firstString(params, ["turnId"]) ?? turnIdFromParams(params) ?? activeTurnId;
|
|
3907
4220
|
input.runThreadOperation(activeThreadId, async () => {
|
|
3908
|
-
await input.runAppServerMutation(activeThreadId, () =>
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
4221
|
+
await input.runAppServerMutation(activeThreadId, async () => {
|
|
4222
|
+
if (terminalTurnId && finalizedTurnIds.has(terminalTurnId) || terminalTurnId && activeTurnId && terminalTurnId !== activeTurnId) return;
|
|
4223
|
+
if (terminalTurn && terminalTurn.id === terminalTurnId) processTurnItems(terminalTurn);
|
|
4224
|
+
await finishTerminalTurn({
|
|
4225
|
+
lastError: state === "failed" ? turnErrorMessage(params) : void 0,
|
|
4226
|
+
method: notification.method,
|
|
4227
|
+
state,
|
|
4228
|
+
turnId: terminalTurnId
|
|
4229
|
+
});
|
|
4230
|
+
});
|
|
3914
4231
|
}).catch((error) => {
|
|
3915
4232
|
cleanupNotificationHandler();
|
|
3916
4233
|
rejectCompleted(error);
|
|
@@ -3926,36 +4243,34 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3926
4243
|
});
|
|
3927
4244
|
try {
|
|
3928
4245
|
debugStream(`wait idle gate first=${(input.messagesByThreadId.get(activeThreadId) ?? []).every((message) => message.id === userMessage.id) ? "1" : "0"} active=${activeTurnId ?? "none"}`, activeThreadId);
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
debugStream("wait idle complete", activeThreadId);
|
|
3958
|
-
}
|
|
4246
|
+
debugStream("wait idle begin", activeThreadId);
|
|
4247
|
+
await waitForAppServerThreadIdle({
|
|
4248
|
+
appServer: input.appServer,
|
|
4249
|
+
onWaiting() {
|
|
4250
|
+
if (waitingForActiveTurnMessageId) return;
|
|
4251
|
+
const message = appendMessage(input.messagesByThreadId, activeThreadId, {
|
|
4252
|
+
role: "status",
|
|
4253
|
+
content: "Waiting for the current Codex turn to finish.",
|
|
4254
|
+
state: "streaming"
|
|
4255
|
+
});
|
|
4256
|
+
waitingForActiveTurnMessageId = message.id;
|
|
4257
|
+
threadSummary = updateThread(input.threads, input.messagesByThreadId, activeThreadId, { state: "running" });
|
|
4258
|
+
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
4259
|
+
type: "thread.message.created",
|
|
4260
|
+
thread: threadSummary,
|
|
4261
|
+
message
|
|
4262
|
+
});
|
|
4263
|
+
},
|
|
4264
|
+
onWaitingHeartbeat() {
|
|
4265
|
+
threadSummary = updateThread(input.threads, input.messagesByThreadId, activeThreadId, { state: "running" });
|
|
4266
|
+
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
4267
|
+
type: "thread.state.changed",
|
|
4268
|
+
thread: threadSummary
|
|
4269
|
+
});
|
|
4270
|
+
},
|
|
4271
|
+
threadId: activeThreadId
|
|
4272
|
+
});
|
|
4273
|
+
debugStream("wait idle complete", activeThreadId);
|
|
3959
4274
|
if (waitingForActiveTurnMessageId) {
|
|
3960
4275
|
const message = updateMessage(input.messagesByThreadId, activeThreadId, waitingForActiveTurnMessageId, {
|
|
3961
4276
|
content: "Current Codex turn finished. Starting your reply.",
|
|
@@ -3973,7 +4288,7 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3973
4288
|
try {
|
|
3974
4289
|
turn = await startAndProcessAppServerTurn(activeThreadId, {
|
|
3975
4290
|
attachments: input.attachments,
|
|
3976
|
-
id:
|
|
4291
|
+
id: userMessage.id,
|
|
3977
4292
|
prompt,
|
|
3978
4293
|
runOptions: input.runOptions,
|
|
3979
4294
|
skills: input.skills,
|
|
@@ -4007,7 +4322,7 @@ async function runAppServerPromptStreamed(input) {
|
|
|
4007
4322
|
});
|
|
4008
4323
|
turn = await startAndProcessAppServerTurn(activeThreadId, {
|
|
4009
4324
|
attachments: input.attachments,
|
|
4010
|
-
id:
|
|
4325
|
+
id: userMessage.id,
|
|
4011
4326
|
prompt,
|
|
4012
4327
|
runOptions: input.runOptions,
|
|
4013
4328
|
skills: input.skills,
|
|
@@ -4120,11 +4435,10 @@ async function recoverMissingAppServerThread(input) {
|
|
|
4120
4435
|
const thread = await input.appServer.startThread({
|
|
4121
4436
|
approvalPolicy: runtime.approvalPolicy,
|
|
4122
4437
|
cwd: input.workspacePath,
|
|
4123
|
-
experimentalRawEvents: false,
|
|
4124
4438
|
model: input.runOptions.model ?? null,
|
|
4125
|
-
persistExtendedHistory: true,
|
|
4126
4439
|
sandbox: runtime.sandbox,
|
|
4127
|
-
serviceTier: input.runOptions.serviceTier ?? null
|
|
4440
|
+
serviceTier: input.runOptions.serviceTier ?? null,
|
|
4441
|
+
threadSource: "codex-relay"
|
|
4128
4442
|
});
|
|
4129
4443
|
const recoveredThread = mapAppServerThread({
|
|
4130
4444
|
...thread,
|
|
@@ -4277,7 +4591,11 @@ function appendMessageWithId(messagesByThreadId, threadId, id, input) {
|
|
|
4277
4591
|
function upsertAppServerItemMessage(messagesByThreadId, threadId, turnId, item) {
|
|
4278
4592
|
const message = mapAppServerItem(threadId, appServerTurnShell(turnId), item);
|
|
4279
4593
|
if (!message) return;
|
|
4280
|
-
|
|
4594
|
+
const existing = messagesByThreadId.get(threadId)?.find((candidate) => candidate.id === item.id);
|
|
4595
|
+
if (existing) return updateMessage(messagesByThreadId, threadId, item.id, {
|
|
4596
|
+
...message,
|
|
4597
|
+
createdAt: existing.createdAt
|
|
4598
|
+
});
|
|
4281
4599
|
return appendMessageWithId(messagesByThreadId, threadId, item.id, {
|
|
4282
4600
|
role: message.role,
|
|
4283
4601
|
kind: message.kind,
|
|
@@ -4295,6 +4613,7 @@ function replaceDuplicateInitialUserMessage(messagesByThreadId, threadId, turnId
|
|
|
4295
4613
|
}
|
|
4296
4614
|
function isDuplicateInitialUserMessage(messagesByThreadId, threadId, item, localMessageId, prompt) {
|
|
4297
4615
|
if (item.type !== "userMessage" || !("content" in item) || !Array.isArray(item.content)) return false;
|
|
4616
|
+
if (item.clientId) return item.clientId === localMessageId;
|
|
4298
4617
|
const localMessage = messagesByThreadId.get(threadId)?.find((message) => message.id === localMessageId);
|
|
4299
4618
|
const skills = appServerUserMessageSkills(item);
|
|
4300
4619
|
const normalizeContent = (content) => stripPromptSkillMentions(normalizeImageMessageContent(content), skills);
|
|
@@ -4966,6 +5285,7 @@ function hasExplicitRunOptions(options) {
|
|
|
4966
5285
|
function mapAppServerThread(thread, fallbackMessageCount) {
|
|
4967
5286
|
const createdAt = fromUnixSeconds(thread.createdAt);
|
|
4968
5287
|
const updatedAt = fromUnixSeconds(thread.updatedAt);
|
|
5288
|
+
const lastActivityAt = fromUnixSeconds(thread.recencyAt ?? thread.updatedAt);
|
|
4969
5289
|
const messageCount = Math.max(thread.turns ? countThreadMessages(thread) : 0, fallbackMessageCount ?? 0);
|
|
4970
5290
|
const mappedState = mapAppServerThreadState(thread.status, thread.turns);
|
|
4971
5291
|
return ThreadSummarySchema.parse({
|
|
@@ -4979,7 +5299,7 @@ function mapAppServerThread(thread, fallbackMessageCount) {
|
|
|
4979
5299
|
source: thread.source,
|
|
4980
5300
|
messageCount,
|
|
4981
5301
|
lastMessagePreview: thread.preview ? preview(thread.preview) : void 0,
|
|
4982
|
-
lastActivityAt
|
|
5302
|
+
lastActivityAt
|
|
4983
5303
|
});
|
|
4984
5304
|
}
|
|
4985
5305
|
function rememberAppServerThread(threads, thread, options = {}) {
|
|
@@ -5032,6 +5352,12 @@ function mapAppServerMessages(thread) {
|
|
|
5032
5352
|
}
|
|
5033
5353
|
return messages;
|
|
5034
5354
|
}
|
|
5355
|
+
function appServerThreadHasCompleteHistory(thread) {
|
|
5356
|
+
return (thread.turns ?? []).every((turn) => turn.itemsView === void 0 || turn.itemsView === "full");
|
|
5357
|
+
}
|
|
5358
|
+
function isAsyncAppServerAgentMessage(item) {
|
|
5359
|
+
return item.type === "agentMessage" && "delivery" in item && item.delivery === "async";
|
|
5360
|
+
}
|
|
5035
5361
|
function mergeAppServerMessagesWithLocalStatus(appMessages, localMessages) {
|
|
5036
5362
|
const appMessageIds = new Set(appMessages.map((message) => message.id));
|
|
5037
5363
|
const localStatusMessages = localMessages.filter((message) => message.role === "status" && !appMessageIds.has(message.id));
|
|
@@ -5162,13 +5488,18 @@ function readSessionIndexThreadTitle(threadId) {
|
|
|
5162
5488
|
} catch {}
|
|
5163
5489
|
}
|
|
5164
5490
|
}
|
|
5165
|
-
function readRolloutThreadMessages(threadId, workspacePath = defaultWorkspacePath) {
|
|
5166
|
-
const rolloutPath = findRolloutFileForThread(threadId);
|
|
5491
|
+
function readRolloutThreadMessages(threadId, workspacePath = defaultWorkspacePath, selectedRolloutPath) {
|
|
5492
|
+
const rolloutPath = selectedRolloutPath && selectedRolloutPath.endsWith(".jsonl") && existsSync(selectedRolloutPath) ? selectedRolloutPath : findRolloutFileForThread(threadId);
|
|
5167
5493
|
if (!rolloutPath) return {
|
|
5168
5494
|
messageCountLowerBound: 0,
|
|
5169
5495
|
messages: [],
|
|
5170
5496
|
rolloutPath
|
|
5171
5497
|
};
|
|
5498
|
+
if (isPaginatedRolloutFile(rolloutPath)) return {
|
|
5499
|
+
messageCountLowerBound: 0,
|
|
5500
|
+
messages: [],
|
|
5501
|
+
rolloutPath
|
|
5502
|
+
};
|
|
5172
5503
|
const collected = [];
|
|
5173
5504
|
const applyPatchInputs = /* @__PURE__ */ new Map();
|
|
5174
5505
|
const handledApplyPatchCallIds = /* @__PURE__ */ new Set();
|
|
@@ -5208,6 +5539,25 @@ function readRolloutThreadMessages(threadId, workspacePath = defaultWorkspacePat
|
|
|
5208
5539
|
rolloutPath
|
|
5209
5540
|
};
|
|
5210
5541
|
}
|
|
5542
|
+
function isPaginatedRolloutFile(rolloutPath) {
|
|
5543
|
+
let descriptor;
|
|
5544
|
+
try {
|
|
5545
|
+
descriptor = openSync(rolloutPath, "r");
|
|
5546
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
5547
|
+
const bytesRead = readSync(descriptor, buffer, 0, buffer.length, 0);
|
|
5548
|
+
const prefix = buffer.toString("utf8", 0, bytesRead);
|
|
5549
|
+
for (const line of prefix.split("\n")) {
|
|
5550
|
+
if (!line.trim()) continue;
|
|
5551
|
+
const record = JSON.parse(line);
|
|
5552
|
+
if (record.type === "session_meta") return record.payload?.history_mode === "paginated";
|
|
5553
|
+
}
|
|
5554
|
+
} catch {
|
|
5555
|
+
return false;
|
|
5556
|
+
} finally {
|
|
5557
|
+
if (descriptor !== void 0) closeSync(descriptor);
|
|
5558
|
+
}
|
|
5559
|
+
return false;
|
|
5560
|
+
}
|
|
5211
5561
|
function findRolloutFileForThread(threadId) {
|
|
5212
5562
|
const sessionsRoot = join(process.env.CODEX_HOME || join(homedir(), ".codex"), "sessions");
|
|
5213
5563
|
if (!existsSync(sessionsRoot)) return;
|
|
@@ -5572,12 +5922,16 @@ function mapAppServerItem(threadId, turn, item) {
|
|
|
5572
5922
|
const agentItem = item;
|
|
5573
5923
|
const planContent = proposedPlanContent(agentItem.text);
|
|
5574
5924
|
const messageParts = appServerAgentMessageParts(agentItem);
|
|
5925
|
+
const details = planContent ? { raw: agentItem.text } : agentItem.delivery || messageParts.details ? {
|
|
5926
|
+
...messageParts.details,
|
|
5927
|
+
delivery: agentItem.delivery ?? void 0
|
|
5928
|
+
} : void 0;
|
|
5575
5929
|
return ChatMessageSchema.parse({
|
|
5576
5930
|
...base,
|
|
5577
5931
|
role: "assistant",
|
|
5578
5932
|
kind: planContent ? "plan" : void 0,
|
|
5579
5933
|
content: planContent ?? messageParts.content,
|
|
5580
|
-
details
|
|
5934
|
+
details
|
|
5581
5935
|
});
|
|
5582
5936
|
}
|
|
5583
5937
|
case "reasoning": {
|
|
@@ -5977,6 +6331,21 @@ function structuredStreamMessage(role, event, fallbackContent) {
|
|
|
5977
6331
|
type
|
|
5978
6332
|
}
|
|
5979
6333
|
};
|
|
6334
|
+
case "todo_list": {
|
|
6335
|
+
const items = Array.isArray(item?.items) ? item.items : [];
|
|
6336
|
+
return {
|
|
6337
|
+
kind: "plan",
|
|
6338
|
+
content: items.flatMap((entry) => {
|
|
6339
|
+
if (!entry || typeof entry !== "object") return [];
|
|
6340
|
+
const todo = entry;
|
|
6341
|
+
return typeof todo.text === "string" ? [`[${todo.completed === true ? "x" : " "}] ${todo.text}`] : [];
|
|
6342
|
+
}).join("\n") || fallbackContent,
|
|
6343
|
+
details: {
|
|
6344
|
+
items,
|
|
6345
|
+
type
|
|
6346
|
+
}
|
|
6347
|
+
};
|
|
6348
|
+
}
|
|
5980
6349
|
default: return {
|
|
5981
6350
|
kind: kindFromProtocolType(type) ?? (role === "tool" ? "toolActivity" : "unknown"),
|
|
5982
6351
|
content: fallbackContent,
|
|
@@ -5984,6 +6353,17 @@ function structuredStreamMessage(role, event, fallbackContent) {
|
|
|
5984
6353
|
};
|
|
5985
6354
|
}
|
|
5986
6355
|
}
|
|
6356
|
+
function isCompletedAgentMessageEvent(event) {
|
|
6357
|
+
if (!event || typeof event !== "object") return false;
|
|
6358
|
+
return event.type === "item.completed" && eventItem(event)?.type === "agent_message";
|
|
6359
|
+
}
|
|
6360
|
+
function isRenderableStructuredStreamEvent(event) {
|
|
6361
|
+
const type = eventItem(event)?.type;
|
|
6362
|
+
return type === "file_change" || type === "mcp_tool_call" || type === "todo_list" || type === "web_search";
|
|
6363
|
+
}
|
|
6364
|
+
function streamItemState(event) {
|
|
6365
|
+
return event?.type === "item.completed" ? "completed" : "streaming";
|
|
6366
|
+
}
|
|
5987
6367
|
function isApprovalServerRequest(method) {
|
|
5988
6368
|
return method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval" || method === "item/permissions/requestApproval" || method === "item/tool/requestUserInput" || method === "mcpServer/elicitation/request" || method === "execCommandApproval" || method === "applyPatchApproval";
|
|
5989
6369
|
}
|
|
@@ -6046,7 +6426,7 @@ function approvalMessageFromRequest(request) {
|
|
|
6046
6426
|
const threadId = firstString(params, ["threadId", "conversationId"]);
|
|
6047
6427
|
const turnId = firstString(params, ["turnId"]) ?? void 0;
|
|
6048
6428
|
if (!threadId) return;
|
|
6049
|
-
const approvalId =
|
|
6429
|
+
const approvalId = appServerApprovalId(threadId, request.id);
|
|
6050
6430
|
switch (request.method) {
|
|
6051
6431
|
case "item/commandExecution/requestApproval": {
|
|
6052
6432
|
const command = firstString(params, ["command"]) ?? "Command execution";
|
|
@@ -6134,6 +6514,7 @@ function approvalMessageFromRequest(request) {
|
|
|
6134
6514
|
questions
|
|
6135
6515
|
},
|
|
6136
6516
|
kind: "structuredUserInput",
|
|
6517
|
+
isBlocking: typeof params?.isBlocking === "boolean" ? params.isBlocking : true,
|
|
6137
6518
|
questions,
|
|
6138
6519
|
threadId,
|
|
6139
6520
|
turnId
|
|
@@ -6188,6 +6569,7 @@ function pendingInputOptions(value) {
|
|
|
6188
6569
|
function pendingInputRequestFromApproval(approval, threadId) {
|
|
6189
6570
|
return {
|
|
6190
6571
|
id: approval.approvalId,
|
|
6572
|
+
isBlocking: approval.isBlocking ?? true,
|
|
6191
6573
|
questions: approval.questions ?? [],
|
|
6192
6574
|
threadId,
|
|
6193
6575
|
turnId: approval.turnId
|
|
@@ -6198,6 +6580,7 @@ function pendingInputRequestsForThread(pendingApprovals, threadId) {
|
|
|
6198
6580
|
if (pending.kind !== "structuredUserInput" || pending.threadId !== threadId) return [];
|
|
6199
6581
|
return [{
|
|
6200
6582
|
id: approvalId,
|
|
6583
|
+
isBlocking: pending.isBlocking ?? true,
|
|
6201
6584
|
questions: pending.questions ?? [],
|
|
6202
6585
|
threadId,
|
|
6203
6586
|
turnId: pending.turnId
|
|
@@ -6293,6 +6676,20 @@ function turnIdFromParams(params) {
|
|
|
6293
6676
|
const turn = params?.turn;
|
|
6294
6677
|
return turn && typeof turn === "object" ? firstString(turn, ["id"]) : void 0;
|
|
6295
6678
|
}
|
|
6679
|
+
function appServerRequestIdFromParams(params) {
|
|
6680
|
+
const requestId = params?.requestId;
|
|
6681
|
+
return typeof requestId === "number" || typeof requestId === "string" ? requestId : void 0;
|
|
6682
|
+
}
|
|
6683
|
+
function appServerApprovalId(threadId, requestId) {
|
|
6684
|
+
return `approval-${createHash("sha256").update(threadId).update("\0").update(String(requestId)).digest("hex").slice(0, 24)}`;
|
|
6685
|
+
}
|
|
6686
|
+
function appServerTurnFromParams(params) {
|
|
6687
|
+
const turn = params?.turn;
|
|
6688
|
+
if (!turn || typeof turn !== "object") return;
|
|
6689
|
+
const record = turn;
|
|
6690
|
+
if (typeof record.id !== "string" || !Array.isArray(record.items)) return;
|
|
6691
|
+
return turn;
|
|
6692
|
+
}
|
|
6296
6693
|
function turnStatus(params) {
|
|
6297
6694
|
if (typeof params?.status === "string") return params.status;
|
|
6298
6695
|
const turn = params?.turn;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-relay",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.10",
|
|
4
4
|
"description": "Local Codex Relay CLI bridge for the Codex Relay mobile app.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"@noble/ciphers": "2.2.0",
|
|
42
42
|
"@noble/curves": "2.2.0",
|
|
43
43
|
"@noble/hashes": "2.2.0",
|
|
44
|
+
"@openai/codex": "0.149.1",
|
|
44
45
|
"@openai/codex-sdk": "0.149.1",
|
|
45
46
|
"base64-js": "1.5.1",
|
|
46
47
|
"commander": "^14.0.3",
|
package/src/api-schema.ts
CHANGED
|
@@ -284,6 +284,7 @@ export const PendingInputRequestQuestionSchema = z.object({
|
|
|
284
284
|
|
|
285
285
|
export const PendingInputRequestSchema = z.object({
|
|
286
286
|
id: z.string().min(1),
|
|
287
|
+
isBlocking: z.boolean().default(true),
|
|
287
288
|
questions: z.array(PendingInputRequestQuestionSchema),
|
|
288
289
|
threadId: z.string().min(1),
|
|
289
290
|
turnId: z.string().optional(),
|