codex-relay 1.4.9 → 1.4.11
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 +513 -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,33 @@ 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
|
+
limit,
|
|
196
|
+
sortDirection: "desc",
|
|
197
|
+
sortKey: "recency_at",
|
|
198
|
+
sourceKinds: [
|
|
199
|
+
"cli",
|
|
200
|
+
"vscode",
|
|
201
|
+
"exec",
|
|
202
|
+
"appServer"
|
|
203
|
+
],
|
|
204
|
+
...cursor ? {
|
|
205
|
+
cursor,
|
|
206
|
+
useStateDbOnly: true
|
|
207
|
+
} : {}
|
|
208
|
+
});
|
|
209
|
+
threads.push(...response.data);
|
|
210
|
+
const nextCursor = response.nextCursor ?? void 0;
|
|
211
|
+
if (!nextCursor || seenCursors.has(nextCursor)) break;
|
|
212
|
+
seenCursors.add(nextCursor);
|
|
213
|
+
cursor = nextCursor;
|
|
214
|
+
} while (cursor);
|
|
215
|
+
return threads;
|
|
181
216
|
}
|
|
182
217
|
async readThread(threadId, options = {}) {
|
|
183
218
|
return (await this.request("thread/read", {
|
|
@@ -195,19 +230,39 @@ var CodexAppServerClient = class {
|
|
|
195
230
|
return this.request("account/rateLimits/read", null);
|
|
196
231
|
}
|
|
197
232
|
async startThread(params) {
|
|
198
|
-
|
|
233
|
+
const response = await this.request("thread/start", params);
|
|
234
|
+
this.subscribedThreadIds.add(response.thread.id);
|
|
235
|
+
return response.thread;
|
|
199
236
|
}
|
|
200
237
|
async resumeThread(params) {
|
|
201
|
-
|
|
238
|
+
const response = await this.request("thread/resume", params.excludeTurns ? {
|
|
239
|
+
...params,
|
|
240
|
+
initialTurnsPage: {
|
|
241
|
+
itemsView: "summary",
|
|
242
|
+
limit: 1,
|
|
243
|
+
sortDirection: "desc"
|
|
244
|
+
}
|
|
245
|
+
} : params);
|
|
246
|
+
this.subscribedThreadIds.add(response.thread.id);
|
|
247
|
+
this.rememberActiveTurn({
|
|
248
|
+
...response.thread,
|
|
249
|
+
turns: response.initialTurnsPage?.data ?? response.thread.turns
|
|
250
|
+
});
|
|
251
|
+
return response.thread;
|
|
202
252
|
}
|
|
203
253
|
async startTurn(params) {
|
|
204
|
-
|
|
254
|
+
const response = await this.request("turn/start", params);
|
|
255
|
+
this.rememberTurn(params.threadId, response.turn);
|
|
256
|
+
return response.turn;
|
|
205
257
|
}
|
|
206
258
|
async interruptTurn(params) {
|
|
207
259
|
await this.request("turn/interrupt", params);
|
|
208
260
|
}
|
|
209
261
|
async archiveThread(params) {
|
|
210
262
|
await this.request("thread/archive", params);
|
|
263
|
+
this.subscribedThreadIds.delete(params.threadId);
|
|
264
|
+
this.activeTurnIdsByThreadId.delete(params.threadId);
|
|
265
|
+
this.terminalTurnIdsByThreadId.delete(params.threadId);
|
|
211
266
|
}
|
|
212
267
|
async setThreadName(params) {
|
|
213
268
|
await this.request("thread/name/set", params);
|
|
@@ -215,6 +270,9 @@ var CodexAppServerClient = class {
|
|
|
215
270
|
async rollbackThread(params) {
|
|
216
271
|
return (await this.request("thread/rollback", params)).thread;
|
|
217
272
|
}
|
|
273
|
+
async revertThread(params) {
|
|
274
|
+
return (await this.request("thread/revert", params)).thread;
|
|
275
|
+
}
|
|
218
276
|
async getThreadGoal(params) {
|
|
219
277
|
return (await this.request("thread/goal/get", params)).goal;
|
|
220
278
|
}
|
|
@@ -255,6 +313,9 @@ var CodexAppServerClient = class {
|
|
|
255
313
|
this.stopStdioCodexAppServer();
|
|
256
314
|
this.stopSharedCodexAppServer();
|
|
257
315
|
this.initialized = void 0;
|
|
316
|
+
this.activeTurnIdsByThreadId.clear();
|
|
317
|
+
this.terminalTurnIdsByThreadId.clear();
|
|
318
|
+
this.subscribedThreadIds.clear();
|
|
258
319
|
}
|
|
259
320
|
async request(method, params) {
|
|
260
321
|
await this.ensureInitialized();
|
|
@@ -322,8 +383,90 @@ var CodexAppServerClient = class {
|
|
|
322
383
|
title: "Codex Relay Mobile Server",
|
|
323
384
|
version: "1.2.0"
|
|
324
385
|
},
|
|
325
|
-
capabilities: {
|
|
386
|
+
capabilities: {
|
|
387
|
+
experimentalApi: true,
|
|
388
|
+
requestAttestation: false
|
|
389
|
+
}
|
|
326
390
|
});
|
|
391
|
+
await this.writeJson({ method: "initialized" });
|
|
392
|
+
for (const threadId of this.subscribedThreadIds) try {
|
|
393
|
+
const disconnectedTurnId = this.activeTurnIdsByThreadId.get(threadId);
|
|
394
|
+
const response = await this.requestRaw("thread/resume", {
|
|
395
|
+
excludeTurns: true,
|
|
396
|
+
initialTurnsPage: {
|
|
397
|
+
itemsView: "summary",
|
|
398
|
+
limit: 1,
|
|
399
|
+
sortDirection: "desc"
|
|
400
|
+
},
|
|
401
|
+
threadId
|
|
402
|
+
});
|
|
403
|
+
this.reconcileResumedThread({
|
|
404
|
+
...response.thread,
|
|
405
|
+
turns: response.initialTurnsPage?.data ?? response.thread.turns
|
|
406
|
+
}, disconnectedTurnId);
|
|
407
|
+
} catch (error) {
|
|
408
|
+
relayDebugLog("app_server.thread.resubscribe_failed", {
|
|
409
|
+
message: error instanceof Error ? error.message : String(error),
|
|
410
|
+
threadId
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
reconcileResumedThread(thread, disconnectedTurnId) {
|
|
415
|
+
if (disconnectedTurnId) {
|
|
416
|
+
const disconnectedTurn = thread.turns?.find((turn) => turn.id === disconnectedTurnId);
|
|
417
|
+
if (disconnectedTurn && !isAppServerTurnInProgress(disconnectedTurn)) this.dispatchNotification({
|
|
418
|
+
method: "turn/completed",
|
|
419
|
+
params: {
|
|
420
|
+
threadId: thread.id,
|
|
421
|
+
turn: disconnectedTurn
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
else this.dispatchNotification({
|
|
425
|
+
method: "thread/status/changed",
|
|
426
|
+
params: {
|
|
427
|
+
status: thread.status,
|
|
428
|
+
threadId: thread.id
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
this.rememberActiveTurn(thread);
|
|
433
|
+
}
|
|
434
|
+
rememberActiveTurn(thread) {
|
|
435
|
+
const activeTurn = [...thread.turns ?? []].reverse().find((turn) => isAppServerTurnInProgress(turn));
|
|
436
|
+
if (activeTurn && this.terminalTurnIdsByThreadId.get(thread.id) !== activeTurn.id) {
|
|
437
|
+
this.activeTurnIdsByThreadId.set(thread.id, activeTurn.id);
|
|
438
|
+
this.terminalTurnIdsByThreadId.delete(thread.id);
|
|
439
|
+
} else this.activeTurnIdsByThreadId.delete(thread.id);
|
|
440
|
+
}
|
|
441
|
+
rememberTurn(threadId, turn) {
|
|
442
|
+
if (isAppServerTurnInProgress(turn)) {
|
|
443
|
+
if (this.terminalTurnIdsByThreadId.get(threadId) !== turn.id) {
|
|
444
|
+
this.activeTurnIdsByThreadId.set(threadId, turn.id);
|
|
445
|
+
this.terminalTurnIdsByThreadId.delete(threadId);
|
|
446
|
+
}
|
|
447
|
+
} else if (this.activeTurnIdsByThreadId.get(threadId) === turn.id) this.activeTurnIdsByThreadId.delete(threadId);
|
|
448
|
+
}
|
|
449
|
+
dispatchNotification(notification) {
|
|
450
|
+
const params = notification.params;
|
|
451
|
+
const record = params && typeof params === "object" ? params : {};
|
|
452
|
+
const threadId = typeof record.threadId === "string" ? record.threadId : void 0;
|
|
453
|
+
const turn = record.turn && typeof record.turn === "object" ? record.turn : void 0;
|
|
454
|
+
const turnId = typeof record.turnId === "string" ? record.turnId : typeof turn?.id === "string" ? turn.id : void 0;
|
|
455
|
+
if (notification.method === "turn/started" && threadId && turnId) {
|
|
456
|
+
if (this.terminalTurnIdsByThreadId.get(threadId) !== turnId) {
|
|
457
|
+
this.activeTurnIdsByThreadId.set(threadId, turnId);
|
|
458
|
+
this.terminalTurnIdsByThreadId.delete(threadId);
|
|
459
|
+
}
|
|
460
|
+
} else if ([
|
|
461
|
+
"turn/completed",
|
|
462
|
+
"turn/failed",
|
|
463
|
+
"turn/aborted",
|
|
464
|
+
"turn/cancelled"
|
|
465
|
+
].includes(notification.method) && threadId) {
|
|
466
|
+
if (turnId) this.terminalTurnIdsByThreadId.set(threadId, turnId);
|
|
467
|
+
if (!turnId || this.activeTurnIdsByThreadId.get(threadId) === turnId) this.activeTurnIdsByThreadId.delete(threadId);
|
|
468
|
+
}
|
|
469
|
+
for (const handler of this.notificationHandlers) handler(notification);
|
|
327
470
|
}
|
|
328
471
|
startStdioCodexAppServer() {
|
|
329
472
|
const spawnConfig = resolveCodexAppServerSpawn();
|
|
@@ -528,7 +671,7 @@ var CodexAppServerClient = class {
|
|
|
528
671
|
} catch {
|
|
529
672
|
return;
|
|
530
673
|
}
|
|
531
|
-
if (typeof message.method === "string" && typeof message.id === "number") {
|
|
674
|
+
if (typeof message.method === "string" && (typeof message.id === "number" || typeof message.id === "string")) {
|
|
532
675
|
debugAppServer("server-request", message.method, message.id);
|
|
533
676
|
const request = {
|
|
534
677
|
id: message.id,
|
|
@@ -545,7 +688,7 @@ var CodexAppServerClient = class {
|
|
|
545
688
|
method: message.method,
|
|
546
689
|
params: message.params
|
|
547
690
|
};
|
|
548
|
-
|
|
691
|
+
this.dispatchNotification(notification);
|
|
549
692
|
return;
|
|
550
693
|
}
|
|
551
694
|
if (typeof message.id !== "number") return;
|
|
@@ -644,6 +787,15 @@ function connectRemoteAddress(remoteAddress) {
|
|
|
644
787
|
function sharedCodexAppServerSocketPath() {
|
|
645
788
|
return join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "app-server-control", "app-server-control.sock");
|
|
646
789
|
}
|
|
790
|
+
function isAppServerTurnInProgress(turn) {
|
|
791
|
+
if (turn.completedAt !== null && turn.completedAt !== void 0) return false;
|
|
792
|
+
const status = typeof turn.status === "string" ? turn.status : turn.status && typeof turn.status === "object" && "type" in turn.status ? String(turn.status.type) : "";
|
|
793
|
+
return [
|
|
794
|
+
"active",
|
|
795
|
+
"inprogress",
|
|
796
|
+
"running"
|
|
797
|
+
].includes(status.toLowerCase().replace(/[^a-z0-9]/g, ""));
|
|
798
|
+
}
|
|
647
799
|
function asError(error) {
|
|
648
800
|
return error instanceof Error ? error : new Error(String(error));
|
|
649
801
|
}
|
|
@@ -672,7 +824,8 @@ function extractStreamText(event) {
|
|
|
672
824
|
if (!event || typeof event !== "object") return;
|
|
673
825
|
const record = event;
|
|
674
826
|
const item = record.item && typeof record.item === "object" ? record.item : void 0;
|
|
675
|
-
const
|
|
827
|
+
const error = record.error && typeof record.error === "object" ? record.error : void 0;
|
|
828
|
+
const candidate = record.delta ?? record.text ?? record.message ?? error?.message ?? item?.text ?? item?.message ?? item?.aggregated_output ?? item?.command;
|
|
676
829
|
return typeof candidate === "string" && candidate.length > 0 ? candidate : void 0;
|
|
677
830
|
}
|
|
678
831
|
function classifyStreamEvent(event) {
|
|
@@ -1472,11 +1625,47 @@ function createApp(options = {}) {
|
|
|
1472
1625
|
const appServerMutationTails = /* @__PURE__ */ new Map();
|
|
1473
1626
|
const workspaceTerminalSessions = /* @__PURE__ */ new Map();
|
|
1474
1627
|
const activeAppServerTurnIdsByThreadId = /* @__PURE__ */ new Map();
|
|
1628
|
+
const appServerHistoryGenerationsByThreadId = /* @__PURE__ */ new Map();
|
|
1475
1629
|
const appServerHistoryLoadsByThreadId = /* @__PURE__ */ new Map();
|
|
1630
|
+
const appServerRolloutPathsByThreadId = /* @__PURE__ */ new Map();
|
|
1476
1631
|
const steeringThreads = /* @__PURE__ */ new Set();
|
|
1477
1632
|
const secureSessionsByTokenHash = /* @__PURE__ */ new Map();
|
|
1478
1633
|
const activeStreamControllers = /* @__PURE__ */ new Map();
|
|
1479
|
-
const threadOptions = {
|
|
1634
|
+
const threadOptions = {
|
|
1635
|
+
threadSource: "codex-relay",
|
|
1636
|
+
workingDirectory: workspacePath
|
|
1637
|
+
};
|
|
1638
|
+
const advanceAppServerHistoryGeneration = (threadId) => {
|
|
1639
|
+
appServerHistoryGenerationsByThreadId.set(threadId, (appServerHistoryGenerationsByThreadId.get(threadId) ?? 0) + 1);
|
|
1640
|
+
};
|
|
1641
|
+
const invalidateAppServerHistory = (threadId) => {
|
|
1642
|
+
advanceAppServerHistoryGeneration(threadId);
|
|
1643
|
+
appServerHistoryLoadsByThreadId.delete(threadId);
|
|
1644
|
+
appServerRolloutPathsByThreadId.delete(threadId);
|
|
1645
|
+
messagesByThreadId.delete(threadId);
|
|
1646
|
+
};
|
|
1647
|
+
if (appServer && typeof appServer.onNotification === "function") appServer.onNotification((notification) => {
|
|
1648
|
+
const params = recordParams(notification);
|
|
1649
|
+
const threadId = firstString(params, ["threadId"]);
|
|
1650
|
+
if (!threadId) return;
|
|
1651
|
+
if (notification.method === "thread/reverted") {
|
|
1652
|
+
invalidateAppServerHistory(threadId);
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
if (notification.method === "item/completed") {
|
|
1656
|
+
advanceAppServerHistoryGeneration(threadId);
|
|
1657
|
+
const item = params?.item;
|
|
1658
|
+
if (item && typeof item === "object" && item.type !== "userMessage") upsertAppServerItemMessage(messagesByThreadId, threadId, firstString(params, ["turnId"]), item);
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
if (notification.method === "turn/completed") {
|
|
1662
|
+
advanceAppServerHistoryGeneration(threadId);
|
|
1663
|
+
const turn = appServerTurnFromParams(params);
|
|
1664
|
+
if (turn) {
|
|
1665
|
+
for (const item of turn.items) if (item.type !== "userMessage") upsertAppServerItemMessage(messagesByThreadId, threadId, turn.id, item);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1480
1669
|
function runThreadOperation(threadId, operation) {
|
|
1481
1670
|
const result = (threadOperationTails.get(threadId) ?? Promise.resolve()).then(operation, operation);
|
|
1482
1671
|
const tail = result.then(() => void 0, () => void 0);
|
|
@@ -1503,13 +1692,18 @@ function createApp(options = {}) {
|
|
|
1503
1692
|
const scheduleAppServerHistoryLoad = (threadId, cachedMessages) => {
|
|
1504
1693
|
if (!appServer || appServerHistoryLoadsByThreadId.has(threadId)) return;
|
|
1505
1694
|
const startedAt = Date.now();
|
|
1695
|
+
const historyGeneration = appServerHistoryGenerationsByThreadId.get(threadId) ?? 0;
|
|
1506
1696
|
relayDebugLog("thread.detail.history.background_started", {
|
|
1507
1697
|
cachedMessageCount: cachedMessages.length,
|
|
1508
1698
|
threadId
|
|
1509
1699
|
});
|
|
1510
1700
|
const load = appServer.readThread(threadId, { includeTurns: true }).then((threadWithTurns) => {
|
|
1511
|
-
|
|
1512
|
-
const
|
|
1701
|
+
if ((appServerHistoryGenerationsByThreadId.get(threadId) ?? 0) !== historyGeneration) return;
|
|
1702
|
+
const hasCompleteHistory = appServerThreadHasCompleteHistory(threadWithTurns);
|
|
1703
|
+
const mappedThread = rememberAppServerThread(threads, threadWithTurns, { authoritativeMessageCount: hasCompleteHistory });
|
|
1704
|
+
const appMessages = mapAppServerMessages(threadWithTurns);
|
|
1705
|
+
const localMessages = messagesByThreadId.get(threadId) ?? cachedMessages;
|
|
1706
|
+
const messages = hasCompleteHistory ? mergeAppServerMessagesWithLocalStatus(appMessages, localMessages) : mergeThreadMessagePages(appMessages, localMessages);
|
|
1513
1707
|
messagesByThreadId.set(threadId, messages);
|
|
1514
1708
|
relayDebugLog("thread.detail.history.background_completed", {
|
|
1515
1709
|
durationMs: Date.now() - startedAt,
|
|
@@ -1524,7 +1718,7 @@ function createApp(options = {}) {
|
|
|
1524
1718
|
threadId
|
|
1525
1719
|
});
|
|
1526
1720
|
}).finally(() => {
|
|
1527
|
-
appServerHistoryLoadsByThreadId.delete(threadId);
|
|
1721
|
+
if (appServerHistoryLoadsByThreadId.get(threadId) === load) appServerHistoryLoadsByThreadId.delete(threadId);
|
|
1528
1722
|
});
|
|
1529
1723
|
appServerHistoryLoadsByThreadId.set(threadId, load);
|
|
1530
1724
|
};
|
|
@@ -2203,13 +2397,27 @@ function createApp(options = {}) {
|
|
|
2203
2397
|
const turns = threadBeforeRewind.turns ?? [];
|
|
2204
2398
|
const turnIndex = turns.findIndex((turn) => turn.id === parsed.data.turnId);
|
|
2205
2399
|
if (turnIndex < 0) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("not_found", "The selected message is no longer available to rewind."), 404);
|
|
2206
|
-
|
|
2400
|
+
let rolledBackThread;
|
|
2401
|
+
if (threadBeforeRewind.historyMode === "paginated" && typeof appServer.revertThread === "function") try {
|
|
2402
|
+
rolledBackThread = await appServer.revertThread({
|
|
2403
|
+
beforeTurnId: parsed.data.turnId,
|
|
2404
|
+
threadId
|
|
2405
|
+
});
|
|
2406
|
+
} catch (error) {
|
|
2407
|
+
if (!/method not found|unsupported|-32601/i.test(errorMessage(error))) throw error;
|
|
2408
|
+
rolledBackThread = await appServer.rollbackThread({
|
|
2409
|
+
threadId,
|
|
2410
|
+
numTurns: turns.length - turnIndex
|
|
2411
|
+
});
|
|
2412
|
+
}
|
|
2413
|
+
else rolledBackThread = await appServer.rollbackThread({
|
|
2207
2414
|
threadId,
|
|
2208
2415
|
numTurns: turns.length - turnIndex
|
|
2209
2416
|
});
|
|
2210
2417
|
const threadWithTurns = rolledBackThread.turns === void 0 ? await appServer.readThread(threadId, { includeTurns: true }) : rolledBackThread;
|
|
2211
2418
|
const thread = rememberAppServerThread(threads, threadWithTurns, { authoritativeMessageCount: true });
|
|
2212
2419
|
const messages = mapAppServerMessages(threadWithTurns);
|
|
2420
|
+
invalidateAppServerHistory(threadId);
|
|
2213
2421
|
messagesByThreadId.set(threadId, messages);
|
|
2214
2422
|
liveThreads.delete(threadId);
|
|
2215
2423
|
activeAppServerTurnIdsByThreadId.delete(threadId);
|
|
@@ -2239,18 +2447,29 @@ function createApp(options = {}) {
|
|
|
2239
2447
|
const thread = await appServer.readThread(threadId, { includeTurns: false });
|
|
2240
2448
|
if (isSubagentThread(thread)) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("not_found", `Thread ${threadId} is not known to this server.`), 404);
|
|
2241
2449
|
const mappedThread = rememberAppServerThread(threads, thread);
|
|
2242
|
-
|
|
2450
|
+
let cachedMessages = messagesByThreadId.get(threadId) ?? [];
|
|
2451
|
+
if (thread.path) {
|
|
2452
|
+
const previousRolloutPath = appServerRolloutPathsByThreadId.get(threadId);
|
|
2453
|
+
if (previousRolloutPath && previousRolloutPath !== thread.path) {
|
|
2454
|
+
invalidateAppServerHistory(threadId);
|
|
2455
|
+
cachedMessages = [];
|
|
2456
|
+
}
|
|
2457
|
+
appServerRolloutPathsByThreadId.set(threadId, thread.path);
|
|
2458
|
+
}
|
|
2243
2459
|
let loadedMessages = false;
|
|
2244
2460
|
let messages = cachedMessages;
|
|
2245
2461
|
let responseThread = preserveKnownRunningThreadState(mappedThread, wasKnownRunning);
|
|
2246
2462
|
if (forceRefresh && responseThread.state !== "running") {
|
|
2247
2463
|
const threadWithTurns = await appServer.readThread(threadId, { includeTurns: true });
|
|
2248
|
-
|
|
2249
|
-
|
|
2464
|
+
const hasCompleteHistory = appServerThreadHasCompleteHistory(threadWithTurns);
|
|
2465
|
+
responseThread = rememberAppServerThread(threads, threadWithTurns, { authoritativeMessageCount: hasCompleteHistory });
|
|
2466
|
+
const appMessages = mapAppServerMessages(threadWithTurns);
|
|
2467
|
+
messages = hasCompleteHistory ? mergeAppServerMessagesWithLocalStatus(appMessages, cachedMessages) : mergeThreadMessagePages(appMessages, cachedMessages);
|
|
2250
2468
|
messagesByThreadId.set(threadId, messages);
|
|
2251
2469
|
loadedMessages = true;
|
|
2252
2470
|
} else {
|
|
2253
|
-
const rolloutHistory = readRolloutThreadMessages(threadId, workspacePath);
|
|
2471
|
+
const rolloutHistory = readRolloutThreadMessages(threadId, workspacePath, thread.path ?? void 0);
|
|
2472
|
+
if (rolloutHistory.rolloutPath) appServerRolloutPathsByThreadId.set(threadId, rolloutHistory.rolloutPath);
|
|
2254
2473
|
if (rolloutHistory.messages.length > 0) {
|
|
2255
2474
|
messages = mergeThreadMessagePages(rolloutHistory.messages, cachedMessages);
|
|
2256
2475
|
responseThread = rememberRolloutThreadMessages(threads, responseThread, messages, rolloutHistory.messageCountLowerBound);
|
|
@@ -2265,8 +2484,10 @@ function createApp(options = {}) {
|
|
|
2265
2484
|
}
|
|
2266
2485
|
if (!loadedMessages && responseThread.state !== "running") {
|
|
2267
2486
|
const threadWithTurns = await appServer.readThread(threadId, { includeTurns: true });
|
|
2268
|
-
|
|
2269
|
-
|
|
2487
|
+
const hasCompleteHistory = appServerThreadHasCompleteHistory(threadWithTurns);
|
|
2488
|
+
responseThread = rememberAppServerThread(threads, threadWithTurns, { authoritativeMessageCount: hasCompleteHistory });
|
|
2489
|
+
const appMessages = mapAppServerMessages(threadWithTurns);
|
|
2490
|
+
messages = hasCompleteHistory ? mergeAppServerMessagesWithLocalStatus(appMessages, cachedMessages) : mergeThreadMessagePages(appMessages, cachedMessages);
|
|
2270
2491
|
messagesByThreadId.set(threadId, messages);
|
|
2271
2492
|
loadedMessages = true;
|
|
2272
2493
|
} else if (!loadedMessages) scheduleAppServerHistoryLoad(threadId, cachedMessages);
|
|
@@ -2927,11 +3148,10 @@ async function createAppServerThreadRecord(input) {
|
|
|
2927
3148
|
const thread = await input.appServer.startThread({
|
|
2928
3149
|
approvalPolicy: runtime.approvalPolicy,
|
|
2929
3150
|
cwd: input.workspacePath,
|
|
2930
|
-
experimentalRawEvents: false,
|
|
2931
3151
|
model: input.options.model ?? null,
|
|
2932
|
-
persistExtendedHistory: true,
|
|
2933
3152
|
sandbox: runtime.sandbox,
|
|
2934
|
-
serviceTier: input.options.serviceTier ?? null
|
|
3153
|
+
serviceTier: input.options.serviceTier ?? null,
|
|
3154
|
+
threadSource: "codex-relay"
|
|
2935
3155
|
});
|
|
2936
3156
|
const metadata = ThreadSummarySchema.parse({
|
|
2937
3157
|
...mapAppServerThread({
|
|
@@ -3101,11 +3321,25 @@ async function runPromptStreamed(input) {
|
|
|
3101
3321
|
message: assistantMessage
|
|
3102
3322
|
});
|
|
3103
3323
|
for await (const event of streamed.events) {
|
|
3324
|
+
activeThreadId = replaceLocalThreadId(input.threads, input.messagesByThreadId, input.liveThreads, activeThreadId, getThreadId(thread));
|
|
3104
3325
|
const kind = classifyStreamEvent(event);
|
|
3105
3326
|
const text = extractStreamText(event);
|
|
3106
3327
|
if (kind === "error") throw new Error(text ?? "Codex run failed.");
|
|
3107
|
-
if (!text) continue;
|
|
3108
3328
|
if (kind === "assistant") {
|
|
3329
|
+
if (!text) continue;
|
|
3330
|
+
if (isCompletedAgentMessageEvent(event)) {
|
|
3331
|
+
assistantMessage = updateMessage(input.messagesByThreadId, activeThreadId, assistantMessage.id, { content: text });
|
|
3332
|
+
threadSummary = updateThread(input.threads, input.messagesByThreadId, activeThreadId, {
|
|
3333
|
+
state: "running",
|
|
3334
|
+
lastResult: assistantMessage.content
|
|
3335
|
+
});
|
|
3336
|
+
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3337
|
+
type: "thread.message.created",
|
|
3338
|
+
thread: threadSummary,
|
|
3339
|
+
message: assistantMessage
|
|
3340
|
+
});
|
|
3341
|
+
continue;
|
|
3342
|
+
}
|
|
3109
3343
|
const assistantPatch = appendMessageDelta(input.messagesByThreadId, activeThreadId, assistantMessage.id, text);
|
|
3110
3344
|
assistantMessage = assistantPatch.message;
|
|
3111
3345
|
updateThread(input.threads, input.messagesByThreadId, activeThreadId, {
|
|
@@ -3119,14 +3353,18 @@ async function runPromptStreamed(input) {
|
|
|
3119
3353
|
delta: assistantPatch.delta
|
|
3120
3354
|
});
|
|
3121
3355
|
} else {
|
|
3122
|
-
|
|
3123
|
-
const
|
|
3356
|
+
if (!text && !isRenderableStructuredStreamEvent(event)) continue;
|
|
3357
|
+
const structured = structuredStreamMessage(kind, event, text ?? "Codex activity");
|
|
3358
|
+
const itemId = firstString(eventItem(event), ["id"]);
|
|
3359
|
+
const existingMessage = itemId ? input.messagesByThreadId.get(activeThreadId)?.find((message) => message.id === itemId) : void 0;
|
|
3360
|
+
const message = {
|
|
3124
3361
|
role: kind,
|
|
3125
3362
|
kind: structured.kind,
|
|
3126
3363
|
content: structured.content,
|
|
3127
3364
|
details: structured.details,
|
|
3128
|
-
state:
|
|
3129
|
-
}
|
|
3365
|
+
state: streamItemState(event)
|
|
3366
|
+
};
|
|
3367
|
+
const statusMessage = existingMessage ? updateMessage(input.messagesByThreadId, activeThreadId, existingMessage.id, message) : itemId ? appendMessageWithId(input.messagesByThreadId, activeThreadId, itemId, message) : appendMessage(input.messagesByThreadId, activeThreadId, message);
|
|
3130
3368
|
threadSummary = updateThread(input.threads, input.messagesByThreadId, activeThreadId, { state: "running" });
|
|
3131
3369
|
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3132
3370
|
type: "thread.message.created",
|
|
@@ -3179,6 +3417,7 @@ async function startAppServerTurn(appServer, threadId, input) {
|
|
|
3179
3417
|
const runtime = resolveAppServerRuntime(input.runOptions, input.workspacePath);
|
|
3180
3418
|
const params = {
|
|
3181
3419
|
approvalPolicy: runtime.approvalPolicy,
|
|
3420
|
+
clientUserMessageId: input.id,
|
|
3182
3421
|
collaborationMode: appServerCollaborationMode(input.runOptions),
|
|
3183
3422
|
cwd: input.workspacePath,
|
|
3184
3423
|
effort: input.runOptions.reasoningEffort ?? null,
|
|
@@ -3213,9 +3452,8 @@ async function resumeAppServerThread(appServer, threadId, input, runtime) {
|
|
|
3213
3452
|
await appServer.resumeThread({
|
|
3214
3453
|
approvalPolicy: runtime.approvalPolicy,
|
|
3215
3454
|
cwd: input.workspacePath,
|
|
3216
|
-
excludeTurns:
|
|
3455
|
+
excludeTurns: true,
|
|
3217
3456
|
model: input.runOptions.model ?? null,
|
|
3218
|
-
persistExtendedHistory: true,
|
|
3219
3457
|
sandbox: runtime.sandbox,
|
|
3220
3458
|
serviceTier: input.runOptions.serviceTier ?? null,
|
|
3221
3459
|
threadId
|
|
@@ -3262,10 +3500,12 @@ async function streamRunningAppServerThread(input) {
|
|
|
3262
3500
|
let observedInputRequest = false;
|
|
3263
3501
|
let observedTurnActivity = false;
|
|
3264
3502
|
let producedTurnOutput = false;
|
|
3503
|
+
const asyncAgentMessageIds = /* @__PURE__ */ new Set();
|
|
3265
3504
|
let threadSummary = input.threads.get(input.threadId);
|
|
3266
3505
|
let cleanupNotificationHandler = () => void 0;
|
|
3267
3506
|
let cleanupRequestHandler = () => void 0;
|
|
3268
3507
|
let handlersCleaned = false;
|
|
3508
|
+
let streamFinished = false;
|
|
3269
3509
|
const cleanupHandlers = () => {
|
|
3270
3510
|
if (handlersCleaned) return;
|
|
3271
3511
|
handlersCleaned = true;
|
|
@@ -3286,6 +3526,7 @@ async function streamRunningAppServerThread(input) {
|
|
|
3286
3526
|
if (approval.kind === "structuredUserInput") {
|
|
3287
3527
|
const pending = {
|
|
3288
3528
|
appServer: input.appServer,
|
|
3529
|
+
isBlocking: approval.isBlocking,
|
|
3289
3530
|
kind: approval.kind,
|
|
3290
3531
|
method: request.method,
|
|
3291
3532
|
questions: approval.questions,
|
|
@@ -3330,6 +3571,7 @@ async function streamRunningAppServerThread(input) {
|
|
|
3330
3571
|
const finish = (error) => {
|
|
3331
3572
|
if (settled) return;
|
|
3332
3573
|
settled = true;
|
|
3574
|
+
streamFinished = true;
|
|
3333
3575
|
cleanupHandlers();
|
|
3334
3576
|
if (error === void 0) resolve();
|
|
3335
3577
|
else reject(error);
|
|
@@ -3340,6 +3582,20 @@ async function streamRunningAppServerThread(input) {
|
|
|
3340
3582
|
if (notificationThreadId && notificationThreadId !== input.threadId) return;
|
|
3341
3583
|
try {
|
|
3342
3584
|
switch (notification.method) {
|
|
3585
|
+
case "serverRequest/resolved": {
|
|
3586
|
+
const requestId = appServerRequestIdFromParams(params);
|
|
3587
|
+
if (requestId === void 0) return;
|
|
3588
|
+
const approvalId = appServerApprovalId(input.threadId, requestId);
|
|
3589
|
+
const pending = input.pendingApprovals.get(approvalId);
|
|
3590
|
+
if (!pending || pending.threadId !== input.threadId) return;
|
|
3591
|
+
input.pendingApprovals.delete(approvalId);
|
|
3592
|
+
if (pending.kind === "structuredUserInput") sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3593
|
+
type: "thread.input_request.resolved",
|
|
3594
|
+
requestId: approvalId,
|
|
3595
|
+
threadId: input.threadId
|
|
3596
|
+
});
|
|
3597
|
+
return;
|
|
3598
|
+
}
|
|
3343
3599
|
case "thread/status/changed": {
|
|
3344
3600
|
const state = mapAppServerThreadState(params?.status);
|
|
3345
3601
|
if (state !== "running" && observedTurnActivity) return;
|
|
@@ -3375,11 +3631,13 @@ async function streamRunningAppServerThread(input) {
|
|
|
3375
3631
|
if (!item || typeof item !== "object") return;
|
|
3376
3632
|
const message = upsertAppServerItemMessage(input.messagesByThreadId, input.threadId, firstString(params, ["turnId"]) ?? activeTurnId, item);
|
|
3377
3633
|
if (!message) return;
|
|
3378
|
-
|
|
3379
|
-
if (
|
|
3634
|
+
const isAsyncAgentMessage = isAsyncAppServerAgentMessage(item);
|
|
3635
|
+
if (isAsyncAgentMessage) asyncAgentMessageIds.add(message.id);
|
|
3636
|
+
if (message.role !== "user" && !isAsyncAgentMessage) producedTurnOutput = true;
|
|
3637
|
+
if (message.role === "assistant" && !isAsyncAgentMessage) assistantMessageId = message.id;
|
|
3380
3638
|
threadSummary = updateThread(input.threads, input.messagesByThreadId, input.threadId, {
|
|
3381
3639
|
state: "running",
|
|
3382
|
-
|
|
3640
|
+
...message.role === "assistant" && !isAsyncAgentMessage ? { lastResult: message.content } : {}
|
|
3383
3641
|
});
|
|
3384
3642
|
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3385
3643
|
type: notification.method === "item/completed" && message.role === "assistant" ? "thread.message.completed" : "thread.message.created",
|
|
@@ -3399,13 +3657,16 @@ async function streamRunningAppServerThread(input) {
|
|
|
3399
3657
|
state: "streaming",
|
|
3400
3658
|
turnId: firstString(params, ["turnId"]) ?? activeTurnId
|
|
3401
3659
|
});
|
|
3402
|
-
|
|
3403
|
-
|
|
3660
|
+
const isAsyncAgentMessage = asyncAgentMessageIds.has(itemId);
|
|
3661
|
+
if (!isAsyncAgentMessage) {
|
|
3662
|
+
assistantMessageId = itemId;
|
|
3663
|
+
producedTurnOutput = true;
|
|
3664
|
+
}
|
|
3404
3665
|
const patch = appendMessageDelta(input.messagesByThreadId, input.threadId, itemId, delta);
|
|
3405
3666
|
const message = patch.message;
|
|
3406
3667
|
updateThread(input.threads, input.messagesByThreadId, input.threadId, {
|
|
3407
3668
|
state: "running",
|
|
3408
|
-
lastResult: message.content
|
|
3669
|
+
...isAsyncAgentMessage ? {} : { lastResult: message.content }
|
|
3409
3670
|
});
|
|
3410
3671
|
if (patch.delta) sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3411
3672
|
type: "thread.message.delta",
|
|
@@ -3441,6 +3702,27 @@ async function streamRunningAppServerThread(input) {
|
|
|
3441
3702
|
case "turn/failed": {
|
|
3442
3703
|
observedTurnActivity = true;
|
|
3443
3704
|
const state = terminalTurnState(notification.method, params);
|
|
3705
|
+
const terminalTurn = appServerTurnFromParams(params);
|
|
3706
|
+
if (terminalTurn) {
|
|
3707
|
+
activeTurnId = terminalTurn.id;
|
|
3708
|
+
for (const item of terminalTurn.items) {
|
|
3709
|
+
const message = upsertAppServerItemMessage(input.messagesByThreadId, input.threadId, terminalTurn.id, item);
|
|
3710
|
+
if (!message) continue;
|
|
3711
|
+
const isAsyncAgentMessage = isAsyncAppServerAgentMessage(item);
|
|
3712
|
+
if (isAsyncAgentMessage) asyncAgentMessageIds.add(message.id);
|
|
3713
|
+
if (message.role !== "user" && !isAsyncAgentMessage) producedTurnOutput = true;
|
|
3714
|
+
if (message.role === "assistant" && !isAsyncAgentMessage) assistantMessageId = message.id;
|
|
3715
|
+
threadSummary = updateThread(input.threads, input.messagesByThreadId, input.threadId, {
|
|
3716
|
+
state: "running",
|
|
3717
|
+
...message.role === "assistant" && !isAsyncAgentMessage ? { lastResult: message.content } : {}
|
|
3718
|
+
});
|
|
3719
|
+
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3720
|
+
type: message.role === "assistant" ? "thread.message.completed" : "thread.message.created",
|
|
3721
|
+
thread: threadSummary,
|
|
3722
|
+
message
|
|
3723
|
+
});
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3444
3726
|
relayDebugLog("app_server.turn.terminal", {
|
|
3445
3727
|
method: notification.method,
|
|
3446
3728
|
state,
|
|
@@ -3460,7 +3742,8 @@ async function streamRunningAppServerThread(input) {
|
|
|
3460
3742
|
finish();
|
|
3461
3743
|
return;
|
|
3462
3744
|
}
|
|
3463
|
-
|
|
3745
|
+
const assistantMessage = assistantMessageId ? input.messagesByThreadId.get(input.threadId)?.find((message) => message.id === assistantMessageId) : void 0;
|
|
3746
|
+
if (assistantMessageId && assistantMessage?.state !== "completed") {
|
|
3464
3747
|
const completedMessage = updateMessage(input.messagesByThreadId, input.threadId, assistantMessageId, { state: "completed" });
|
|
3465
3748
|
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3466
3749
|
type: "thread.message.completed",
|
|
@@ -3518,8 +3801,15 @@ async function streamRunningAppServerThread(input) {
|
|
|
3518
3801
|
removeAbortListener = () => input.signal.removeEventListener("abort", onAbort);
|
|
3519
3802
|
});
|
|
3520
3803
|
try {
|
|
3521
|
-
const appServerThread = await Promise.race([input.appServer.
|
|
3804
|
+
const appServerThread = await Promise.race([typeof input.appServer.resumeThread === "function" ? input.appServer.resumeThread({
|
|
3805
|
+
excludeTurns: true,
|
|
3806
|
+
threadId: input.threadId
|
|
3807
|
+
}) : input.appServer.readThread(input.threadId, { includeTurns: false }), aborted.then(() => void 0)]);
|
|
3522
3808
|
if (!appServerThread || input.signal.aborted) return;
|
|
3809
|
+
if (streamFinished) {
|
|
3810
|
+
await completed;
|
|
3811
|
+
return;
|
|
3812
|
+
}
|
|
3523
3813
|
threadSummary = rememberAppServerThread(input.threads, appServerThread);
|
|
3524
3814
|
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3525
3815
|
type: "thread.state.changed",
|
|
@@ -3553,6 +3843,7 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3553
3843
|
let handedOffToQueuedTurn = false;
|
|
3554
3844
|
let observedInputRequest = false;
|
|
3555
3845
|
let producedTurnOutput = false;
|
|
3846
|
+
const asyncAgentMessageIds = /* @__PURE__ */ new Set();
|
|
3556
3847
|
let userMessage = appendMessage(input.messagesByThreadId, activeThreadId, {
|
|
3557
3848
|
role: "user",
|
|
3558
3849
|
content: displayPrompt,
|
|
@@ -3589,6 +3880,7 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3589
3880
|
if (approval.kind === "structuredUserInput") {
|
|
3590
3881
|
const pending = {
|
|
3591
3882
|
appServer: input.appServer,
|
|
3883
|
+
isBlocking: approval.isBlocking,
|
|
3592
3884
|
kind: approval.kind,
|
|
3593
3885
|
method: request.method,
|
|
3594
3886
|
questions: approval.questions,
|
|
@@ -3668,7 +3960,8 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3668
3960
|
resolveCompleted();
|
|
3669
3961
|
return;
|
|
3670
3962
|
}
|
|
3671
|
-
|
|
3963
|
+
const assistantMessage = assistantMessageId ? input.messagesByThreadId.get(activeThreadId)?.find((message) => message.id === assistantMessageId) : void 0;
|
|
3964
|
+
if (assistantMessageId && assistantMessage?.state !== "completed") {
|
|
3672
3965
|
const completedMessage = updateMessage(input.messagesByThreadId, activeThreadId, assistantMessageId, { state: "completed" });
|
|
3673
3966
|
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3674
3967
|
type: "thread.message.completed",
|
|
@@ -3723,8 +4016,7 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3723
4016
|
cleanupNotificationHandler();
|
|
3724
4017
|
resolveCompleted();
|
|
3725
4018
|
}
|
|
3726
|
-
|
|
3727
|
-
if (finalizedTurnIds.has(turn.id)) return;
|
|
4019
|
+
function processTurnItems(turn) {
|
|
3728
4020
|
activeTurnId = turn.id;
|
|
3729
4021
|
input.activeAppServerTurnIdsByThreadId.set(activeThreadId, turn.id);
|
|
3730
4022
|
const turnIsRunning = isAppServerTurnRunning(turn);
|
|
@@ -3750,11 +4042,13 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3750
4042
|
}
|
|
3751
4043
|
const message = upsertAppServerItemMessage(input.messagesByThreadId, activeThreadId, turn.id, item);
|
|
3752
4044
|
if (!message) continue;
|
|
3753
|
-
|
|
3754
|
-
if (
|
|
4045
|
+
const isAsyncAgentMessage = isAsyncAppServerAgentMessage(item);
|
|
4046
|
+
if (isAsyncAgentMessage) asyncAgentMessageIds.add(message.id);
|
|
4047
|
+
if (message.role !== "user" && !isAsyncAgentMessage) producedTurnOutput = true;
|
|
4048
|
+
if (message.role === "assistant" && !isAsyncAgentMessage) assistantMessageId = message.id;
|
|
3755
4049
|
threadSummary = updateThread(input.threads, input.messagesByThreadId, activeThreadId, {
|
|
3756
4050
|
state: "running",
|
|
3757
|
-
|
|
4051
|
+
...message.role === "assistant" && !isAsyncAgentMessage ? { lastResult: message.content } : {}
|
|
3758
4052
|
});
|
|
3759
4053
|
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3760
4054
|
type: message.role === "assistant" ? "thread.message.completed" : "thread.message.created",
|
|
@@ -3762,7 +4056,11 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3762
4056
|
message
|
|
3763
4057
|
});
|
|
3764
4058
|
}
|
|
3765
|
-
|
|
4059
|
+
return turnIsRunning;
|
|
4060
|
+
}
|
|
4061
|
+
async function processReturnedTurn(turn) {
|
|
4062
|
+
if (finalizedTurnIds.has(turn.id)) return;
|
|
4063
|
+
if (processTurnItems(turn)) return;
|
|
3766
4064
|
const params = { turn };
|
|
3767
4065
|
const state = terminalTurnState("turn/completed", params);
|
|
3768
4066
|
await finishTerminalTurn({
|
|
@@ -3788,6 +4086,20 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3788
4086
|
if (threadId && threadId !== activeThreadId) return;
|
|
3789
4087
|
try {
|
|
3790
4088
|
switch (notification.method) {
|
|
4089
|
+
case "serverRequest/resolved": {
|
|
4090
|
+
const requestId = appServerRequestIdFromParams(params);
|
|
4091
|
+
if (requestId === void 0) return;
|
|
4092
|
+
const approvalId = appServerApprovalId(activeThreadId, requestId);
|
|
4093
|
+
const pending = input.pendingApprovals.get(approvalId);
|
|
4094
|
+
if (!pending || pending.threadId !== activeThreadId) return;
|
|
4095
|
+
input.pendingApprovals.delete(approvalId);
|
|
4096
|
+
if (pending.kind === "structuredUserInput") sendSse(input.controller, input.encoder, input.secureSession, {
|
|
4097
|
+
type: "thread.input_request.resolved",
|
|
4098
|
+
requestId: approvalId,
|
|
4099
|
+
threadId: activeThreadId
|
|
4100
|
+
});
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
3791
4103
|
case "thread/status/changed": {
|
|
3792
4104
|
const status = params?.status;
|
|
3793
4105
|
const state = mapAppServerThreadState(status);
|
|
@@ -3840,11 +4152,13 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3840
4152
|
const turnId = firstString(params, ["turnId"]) ?? activeTurnId;
|
|
3841
4153
|
const message = upsertAppServerItemMessage(input.messagesByThreadId, activeThreadId, turnId, item);
|
|
3842
4154
|
if (!message) return;
|
|
3843
|
-
|
|
3844
|
-
if (
|
|
4155
|
+
const isAsyncAgentMessage = isAsyncAppServerAgentMessage(item);
|
|
4156
|
+
if (isAsyncAgentMessage) asyncAgentMessageIds.add(message.id);
|
|
4157
|
+
if (message.role !== "user" && !isAsyncAgentMessage) producedTurnOutput = true;
|
|
4158
|
+
if (message.role === "assistant" && !isAsyncAgentMessage) assistantMessageId = message.id;
|
|
3845
4159
|
threadSummary = updateThread(input.threads, input.messagesByThreadId, activeThreadId, {
|
|
3846
4160
|
state: "running",
|
|
3847
|
-
|
|
4161
|
+
...message.role === "assistant" && !isAsyncAgentMessage ? { lastResult: message.content } : {}
|
|
3848
4162
|
});
|
|
3849
4163
|
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3850
4164
|
type: notification.method === "item/completed" && message.role === "assistant" ? "thread.message.completed" : "thread.message.created",
|
|
@@ -3863,13 +4177,14 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3863
4177
|
state: "streaming",
|
|
3864
4178
|
turnId: firstString(params, ["turnId"]) ?? activeTurnId
|
|
3865
4179
|
});
|
|
3866
|
-
|
|
4180
|
+
const isAsyncAgentMessage = asyncAgentMessageIds.has(itemId);
|
|
4181
|
+
if (!isAsyncAgentMessage) assistantMessageId = itemId;
|
|
3867
4182
|
const patch = appendMessageDelta(input.messagesByThreadId, activeThreadId, itemId, delta);
|
|
3868
4183
|
const message = patch.message;
|
|
3869
|
-
producedTurnOutput = true;
|
|
4184
|
+
if (!isAsyncAgentMessage) producedTurnOutput = true;
|
|
3870
4185
|
updateThread(input.threads, input.messagesByThreadId, activeThreadId, {
|
|
3871
4186
|
state: "running",
|
|
3872
|
-
lastResult: message.content
|
|
4187
|
+
...isAsyncAgentMessage ? {} : { lastResult: message.content }
|
|
3873
4188
|
});
|
|
3874
4189
|
if (patch.delta) sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3875
4190
|
type: "thread.message.delta",
|
|
@@ -3903,14 +4218,19 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3903
4218
|
case "turn/completed":
|
|
3904
4219
|
case "turn/failed": {
|
|
3905
4220
|
const state = terminalTurnState(notification.method, params);
|
|
4221
|
+
const terminalTurn = appServerTurnFromParams(params);
|
|
3906
4222
|
const terminalTurnId = firstString(params, ["turnId"]) ?? turnIdFromParams(params) ?? activeTurnId;
|
|
3907
4223
|
input.runThreadOperation(activeThreadId, async () => {
|
|
3908
|
-
await input.runAppServerMutation(activeThreadId, () =>
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
4224
|
+
await input.runAppServerMutation(activeThreadId, async () => {
|
|
4225
|
+
if (terminalTurnId && finalizedTurnIds.has(terminalTurnId) || terminalTurnId && activeTurnId && terminalTurnId !== activeTurnId) return;
|
|
4226
|
+
if (terminalTurn && terminalTurn.id === terminalTurnId) processTurnItems(terminalTurn);
|
|
4227
|
+
await finishTerminalTurn({
|
|
4228
|
+
lastError: state === "failed" ? turnErrorMessage(params) : void 0,
|
|
4229
|
+
method: notification.method,
|
|
4230
|
+
state,
|
|
4231
|
+
turnId: terminalTurnId
|
|
4232
|
+
});
|
|
4233
|
+
});
|
|
3914
4234
|
}).catch((error) => {
|
|
3915
4235
|
cleanupNotificationHandler();
|
|
3916
4236
|
rejectCompleted(error);
|
|
@@ -3926,36 +4246,34 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3926
4246
|
});
|
|
3927
4247
|
try {
|
|
3928
4248
|
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
|
-
}
|
|
4249
|
+
debugStream("wait idle begin", activeThreadId);
|
|
4250
|
+
await waitForAppServerThreadIdle({
|
|
4251
|
+
appServer: input.appServer,
|
|
4252
|
+
onWaiting() {
|
|
4253
|
+
if (waitingForActiveTurnMessageId) return;
|
|
4254
|
+
const message = appendMessage(input.messagesByThreadId, activeThreadId, {
|
|
4255
|
+
role: "status",
|
|
4256
|
+
content: "Waiting for the current Codex turn to finish.",
|
|
4257
|
+
state: "streaming"
|
|
4258
|
+
});
|
|
4259
|
+
waitingForActiveTurnMessageId = message.id;
|
|
4260
|
+
threadSummary = updateThread(input.threads, input.messagesByThreadId, activeThreadId, { state: "running" });
|
|
4261
|
+
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
4262
|
+
type: "thread.message.created",
|
|
4263
|
+
thread: threadSummary,
|
|
4264
|
+
message
|
|
4265
|
+
});
|
|
4266
|
+
},
|
|
4267
|
+
onWaitingHeartbeat() {
|
|
4268
|
+
threadSummary = updateThread(input.threads, input.messagesByThreadId, activeThreadId, { state: "running" });
|
|
4269
|
+
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
4270
|
+
type: "thread.state.changed",
|
|
4271
|
+
thread: threadSummary
|
|
4272
|
+
});
|
|
4273
|
+
},
|
|
4274
|
+
threadId: activeThreadId
|
|
4275
|
+
});
|
|
4276
|
+
debugStream("wait idle complete", activeThreadId);
|
|
3959
4277
|
if (waitingForActiveTurnMessageId) {
|
|
3960
4278
|
const message = updateMessage(input.messagesByThreadId, activeThreadId, waitingForActiveTurnMessageId, {
|
|
3961
4279
|
content: "Current Codex turn finished. Starting your reply.",
|
|
@@ -3973,7 +4291,7 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3973
4291
|
try {
|
|
3974
4292
|
turn = await startAndProcessAppServerTurn(activeThreadId, {
|
|
3975
4293
|
attachments: input.attachments,
|
|
3976
|
-
id:
|
|
4294
|
+
id: userMessage.id,
|
|
3977
4295
|
prompt,
|
|
3978
4296
|
runOptions: input.runOptions,
|
|
3979
4297
|
skills: input.skills,
|
|
@@ -4007,7 +4325,7 @@ async function runAppServerPromptStreamed(input) {
|
|
|
4007
4325
|
});
|
|
4008
4326
|
turn = await startAndProcessAppServerTurn(activeThreadId, {
|
|
4009
4327
|
attachments: input.attachments,
|
|
4010
|
-
id:
|
|
4328
|
+
id: userMessage.id,
|
|
4011
4329
|
prompt,
|
|
4012
4330
|
runOptions: input.runOptions,
|
|
4013
4331
|
skills: input.skills,
|
|
@@ -4120,11 +4438,10 @@ async function recoverMissingAppServerThread(input) {
|
|
|
4120
4438
|
const thread = await input.appServer.startThread({
|
|
4121
4439
|
approvalPolicy: runtime.approvalPolicy,
|
|
4122
4440
|
cwd: input.workspacePath,
|
|
4123
|
-
experimentalRawEvents: false,
|
|
4124
4441
|
model: input.runOptions.model ?? null,
|
|
4125
|
-
persistExtendedHistory: true,
|
|
4126
4442
|
sandbox: runtime.sandbox,
|
|
4127
|
-
serviceTier: input.runOptions.serviceTier ?? null
|
|
4443
|
+
serviceTier: input.runOptions.serviceTier ?? null,
|
|
4444
|
+
threadSource: "codex-relay"
|
|
4128
4445
|
});
|
|
4129
4446
|
const recoveredThread = mapAppServerThread({
|
|
4130
4447
|
...thread,
|
|
@@ -4277,7 +4594,11 @@ function appendMessageWithId(messagesByThreadId, threadId, id, input) {
|
|
|
4277
4594
|
function upsertAppServerItemMessage(messagesByThreadId, threadId, turnId, item) {
|
|
4278
4595
|
const message = mapAppServerItem(threadId, appServerTurnShell(turnId), item);
|
|
4279
4596
|
if (!message) return;
|
|
4280
|
-
|
|
4597
|
+
const existing = messagesByThreadId.get(threadId)?.find((candidate) => candidate.id === item.id);
|
|
4598
|
+
if (existing) return updateMessage(messagesByThreadId, threadId, item.id, {
|
|
4599
|
+
...message,
|
|
4600
|
+
createdAt: existing.createdAt
|
|
4601
|
+
});
|
|
4281
4602
|
return appendMessageWithId(messagesByThreadId, threadId, item.id, {
|
|
4282
4603
|
role: message.role,
|
|
4283
4604
|
kind: message.kind,
|
|
@@ -4295,6 +4616,7 @@ function replaceDuplicateInitialUserMessage(messagesByThreadId, threadId, turnId
|
|
|
4295
4616
|
}
|
|
4296
4617
|
function isDuplicateInitialUserMessage(messagesByThreadId, threadId, item, localMessageId, prompt) {
|
|
4297
4618
|
if (item.type !== "userMessage" || !("content" in item) || !Array.isArray(item.content)) return false;
|
|
4619
|
+
if (item.clientId) return item.clientId === localMessageId;
|
|
4298
4620
|
const localMessage = messagesByThreadId.get(threadId)?.find((message) => message.id === localMessageId);
|
|
4299
4621
|
const skills = appServerUserMessageSkills(item);
|
|
4300
4622
|
const normalizeContent = (content) => stripPromptSkillMentions(normalizeImageMessageContent(content), skills);
|
|
@@ -4966,6 +5288,7 @@ function hasExplicitRunOptions(options) {
|
|
|
4966
5288
|
function mapAppServerThread(thread, fallbackMessageCount) {
|
|
4967
5289
|
const createdAt = fromUnixSeconds(thread.createdAt);
|
|
4968
5290
|
const updatedAt = fromUnixSeconds(thread.updatedAt);
|
|
5291
|
+
const lastActivityAt = fromUnixSeconds(thread.recencyAt ?? thread.updatedAt);
|
|
4969
5292
|
const messageCount = Math.max(thread.turns ? countThreadMessages(thread) : 0, fallbackMessageCount ?? 0);
|
|
4970
5293
|
const mappedState = mapAppServerThreadState(thread.status, thread.turns);
|
|
4971
5294
|
return ThreadSummarySchema.parse({
|
|
@@ -4979,7 +5302,7 @@ function mapAppServerThread(thread, fallbackMessageCount) {
|
|
|
4979
5302
|
source: thread.source,
|
|
4980
5303
|
messageCount,
|
|
4981
5304
|
lastMessagePreview: thread.preview ? preview(thread.preview) : void 0,
|
|
4982
|
-
lastActivityAt
|
|
5305
|
+
lastActivityAt
|
|
4983
5306
|
});
|
|
4984
5307
|
}
|
|
4985
5308
|
function rememberAppServerThread(threads, thread, options = {}) {
|
|
@@ -5032,6 +5355,12 @@ function mapAppServerMessages(thread) {
|
|
|
5032
5355
|
}
|
|
5033
5356
|
return messages;
|
|
5034
5357
|
}
|
|
5358
|
+
function appServerThreadHasCompleteHistory(thread) {
|
|
5359
|
+
return (thread.turns ?? []).every((turn) => turn.itemsView === void 0 || turn.itemsView === "full");
|
|
5360
|
+
}
|
|
5361
|
+
function isAsyncAppServerAgentMessage(item) {
|
|
5362
|
+
return item.type === "agentMessage" && "delivery" in item && item.delivery === "async";
|
|
5363
|
+
}
|
|
5035
5364
|
function mergeAppServerMessagesWithLocalStatus(appMessages, localMessages) {
|
|
5036
5365
|
const appMessageIds = new Set(appMessages.map((message) => message.id));
|
|
5037
5366
|
const localStatusMessages = localMessages.filter((message) => message.role === "status" && !appMessageIds.has(message.id));
|
|
@@ -5162,13 +5491,18 @@ function readSessionIndexThreadTitle(threadId) {
|
|
|
5162
5491
|
} catch {}
|
|
5163
5492
|
}
|
|
5164
5493
|
}
|
|
5165
|
-
function readRolloutThreadMessages(threadId, workspacePath = defaultWorkspacePath) {
|
|
5166
|
-
const rolloutPath = findRolloutFileForThread(threadId);
|
|
5494
|
+
function readRolloutThreadMessages(threadId, workspacePath = defaultWorkspacePath, selectedRolloutPath) {
|
|
5495
|
+
const rolloutPath = selectedRolloutPath && selectedRolloutPath.endsWith(".jsonl") && existsSync(selectedRolloutPath) ? selectedRolloutPath : findRolloutFileForThread(threadId);
|
|
5167
5496
|
if (!rolloutPath) return {
|
|
5168
5497
|
messageCountLowerBound: 0,
|
|
5169
5498
|
messages: [],
|
|
5170
5499
|
rolloutPath
|
|
5171
5500
|
};
|
|
5501
|
+
if (isPaginatedRolloutFile(rolloutPath)) return {
|
|
5502
|
+
messageCountLowerBound: 0,
|
|
5503
|
+
messages: [],
|
|
5504
|
+
rolloutPath
|
|
5505
|
+
};
|
|
5172
5506
|
const collected = [];
|
|
5173
5507
|
const applyPatchInputs = /* @__PURE__ */ new Map();
|
|
5174
5508
|
const handledApplyPatchCallIds = /* @__PURE__ */ new Set();
|
|
@@ -5208,6 +5542,25 @@ function readRolloutThreadMessages(threadId, workspacePath = defaultWorkspacePat
|
|
|
5208
5542
|
rolloutPath
|
|
5209
5543
|
};
|
|
5210
5544
|
}
|
|
5545
|
+
function isPaginatedRolloutFile(rolloutPath) {
|
|
5546
|
+
let descriptor;
|
|
5547
|
+
try {
|
|
5548
|
+
descriptor = openSync(rolloutPath, "r");
|
|
5549
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
5550
|
+
const bytesRead = readSync(descriptor, buffer, 0, buffer.length, 0);
|
|
5551
|
+
const prefix = buffer.toString("utf8", 0, bytesRead);
|
|
5552
|
+
for (const line of prefix.split("\n")) {
|
|
5553
|
+
if (!line.trim()) continue;
|
|
5554
|
+
const record = JSON.parse(line);
|
|
5555
|
+
if (record.type === "session_meta") return record.payload?.history_mode === "paginated";
|
|
5556
|
+
}
|
|
5557
|
+
} catch {
|
|
5558
|
+
return false;
|
|
5559
|
+
} finally {
|
|
5560
|
+
if (descriptor !== void 0) closeSync(descriptor);
|
|
5561
|
+
}
|
|
5562
|
+
return false;
|
|
5563
|
+
}
|
|
5211
5564
|
function findRolloutFileForThread(threadId) {
|
|
5212
5565
|
const sessionsRoot = join(process.env.CODEX_HOME || join(homedir(), ".codex"), "sessions");
|
|
5213
5566
|
if (!existsSync(sessionsRoot)) return;
|
|
@@ -5572,12 +5925,16 @@ function mapAppServerItem(threadId, turn, item) {
|
|
|
5572
5925
|
const agentItem = item;
|
|
5573
5926
|
const planContent = proposedPlanContent(agentItem.text);
|
|
5574
5927
|
const messageParts = appServerAgentMessageParts(agentItem);
|
|
5928
|
+
const details = planContent ? { raw: agentItem.text } : agentItem.delivery || messageParts.details ? {
|
|
5929
|
+
...messageParts.details,
|
|
5930
|
+
delivery: agentItem.delivery ?? void 0
|
|
5931
|
+
} : void 0;
|
|
5575
5932
|
return ChatMessageSchema.parse({
|
|
5576
5933
|
...base,
|
|
5577
5934
|
role: "assistant",
|
|
5578
5935
|
kind: planContent ? "plan" : void 0,
|
|
5579
5936
|
content: planContent ?? messageParts.content,
|
|
5580
|
-
details
|
|
5937
|
+
details
|
|
5581
5938
|
});
|
|
5582
5939
|
}
|
|
5583
5940
|
case "reasoning": {
|
|
@@ -5977,6 +6334,21 @@ function structuredStreamMessage(role, event, fallbackContent) {
|
|
|
5977
6334
|
type
|
|
5978
6335
|
}
|
|
5979
6336
|
};
|
|
6337
|
+
case "todo_list": {
|
|
6338
|
+
const items = Array.isArray(item?.items) ? item.items : [];
|
|
6339
|
+
return {
|
|
6340
|
+
kind: "plan",
|
|
6341
|
+
content: items.flatMap((entry) => {
|
|
6342
|
+
if (!entry || typeof entry !== "object") return [];
|
|
6343
|
+
const todo = entry;
|
|
6344
|
+
return typeof todo.text === "string" ? [`[${todo.completed === true ? "x" : " "}] ${todo.text}`] : [];
|
|
6345
|
+
}).join("\n") || fallbackContent,
|
|
6346
|
+
details: {
|
|
6347
|
+
items,
|
|
6348
|
+
type
|
|
6349
|
+
}
|
|
6350
|
+
};
|
|
6351
|
+
}
|
|
5980
6352
|
default: return {
|
|
5981
6353
|
kind: kindFromProtocolType(type) ?? (role === "tool" ? "toolActivity" : "unknown"),
|
|
5982
6354
|
content: fallbackContent,
|
|
@@ -5984,6 +6356,17 @@ function structuredStreamMessage(role, event, fallbackContent) {
|
|
|
5984
6356
|
};
|
|
5985
6357
|
}
|
|
5986
6358
|
}
|
|
6359
|
+
function isCompletedAgentMessageEvent(event) {
|
|
6360
|
+
if (!event || typeof event !== "object") return false;
|
|
6361
|
+
return event.type === "item.completed" && eventItem(event)?.type === "agent_message";
|
|
6362
|
+
}
|
|
6363
|
+
function isRenderableStructuredStreamEvent(event) {
|
|
6364
|
+
const type = eventItem(event)?.type;
|
|
6365
|
+
return type === "file_change" || type === "mcp_tool_call" || type === "todo_list" || type === "web_search";
|
|
6366
|
+
}
|
|
6367
|
+
function streamItemState(event) {
|
|
6368
|
+
return event?.type === "item.completed" ? "completed" : "streaming";
|
|
6369
|
+
}
|
|
5987
6370
|
function isApprovalServerRequest(method) {
|
|
5988
6371
|
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
6372
|
}
|
|
@@ -6046,7 +6429,7 @@ function approvalMessageFromRequest(request) {
|
|
|
6046
6429
|
const threadId = firstString(params, ["threadId", "conversationId"]);
|
|
6047
6430
|
const turnId = firstString(params, ["turnId"]) ?? void 0;
|
|
6048
6431
|
if (!threadId) return;
|
|
6049
|
-
const approvalId =
|
|
6432
|
+
const approvalId = appServerApprovalId(threadId, request.id);
|
|
6050
6433
|
switch (request.method) {
|
|
6051
6434
|
case "item/commandExecution/requestApproval": {
|
|
6052
6435
|
const command = firstString(params, ["command"]) ?? "Command execution";
|
|
@@ -6134,6 +6517,7 @@ function approvalMessageFromRequest(request) {
|
|
|
6134
6517
|
questions
|
|
6135
6518
|
},
|
|
6136
6519
|
kind: "structuredUserInput",
|
|
6520
|
+
isBlocking: typeof params?.isBlocking === "boolean" ? params.isBlocking : true,
|
|
6137
6521
|
questions,
|
|
6138
6522
|
threadId,
|
|
6139
6523
|
turnId
|
|
@@ -6188,6 +6572,7 @@ function pendingInputOptions(value) {
|
|
|
6188
6572
|
function pendingInputRequestFromApproval(approval, threadId) {
|
|
6189
6573
|
return {
|
|
6190
6574
|
id: approval.approvalId,
|
|
6575
|
+
isBlocking: approval.isBlocking ?? true,
|
|
6191
6576
|
questions: approval.questions ?? [],
|
|
6192
6577
|
threadId,
|
|
6193
6578
|
turnId: approval.turnId
|
|
@@ -6198,6 +6583,7 @@ function pendingInputRequestsForThread(pendingApprovals, threadId) {
|
|
|
6198
6583
|
if (pending.kind !== "structuredUserInput" || pending.threadId !== threadId) return [];
|
|
6199
6584
|
return [{
|
|
6200
6585
|
id: approvalId,
|
|
6586
|
+
isBlocking: pending.isBlocking ?? true,
|
|
6201
6587
|
questions: pending.questions ?? [],
|
|
6202
6588
|
threadId,
|
|
6203
6589
|
turnId: pending.turnId
|
|
@@ -6293,6 +6679,20 @@ function turnIdFromParams(params) {
|
|
|
6293
6679
|
const turn = params?.turn;
|
|
6294
6680
|
return turn && typeof turn === "object" ? firstString(turn, ["id"]) : void 0;
|
|
6295
6681
|
}
|
|
6682
|
+
function appServerRequestIdFromParams(params) {
|
|
6683
|
+
const requestId = params?.requestId;
|
|
6684
|
+
return typeof requestId === "number" || typeof requestId === "string" ? requestId : void 0;
|
|
6685
|
+
}
|
|
6686
|
+
function appServerApprovalId(threadId, requestId) {
|
|
6687
|
+
return `approval-${createHash("sha256").update(threadId).update("\0").update(String(requestId)).digest("hex").slice(0, 24)}`;
|
|
6688
|
+
}
|
|
6689
|
+
function appServerTurnFromParams(params) {
|
|
6690
|
+
const turn = params?.turn;
|
|
6691
|
+
if (!turn || typeof turn !== "object") return;
|
|
6692
|
+
const record = turn;
|
|
6693
|
+
if (typeof record.id !== "string" || !Array.isArray(record.items)) return;
|
|
6694
|
+
return turn;
|
|
6695
|
+
}
|
|
6296
6696
|
function turnStatus(params) {
|
|
6297
6697
|
if (typeof params?.status === "string") return params.status;
|
|
6298
6698
|
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.11",
|
|
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(),
|