@ai-sdk/harness-codex 1.0.134 → 1.0.136

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.
@@ -1,2300 +1,2089 @@
1
- // ../harness/dist/bridge/index.js
2
- import { appendFile, mkdir, writeFile } from "fs/promises";
3
- import { existsSync, readFileSync } from "fs";
4
- import { randomUUID } from "crypto";
5
- import { env as procEnv, pid, stdout } from "process";
1
+ import { appendFile, mkdir, writeFile } from "node:fs/promises";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { createHash, randomUUID } from "node:crypto";
4
+ import { argv, env, pid, stdout } from "node:process";
6
5
  import { WebSocketServer } from "ws";
7
- var DEBUG_LEVEL_WEIGHT = {
8
- error: 0,
9
- warn: 1,
10
- info: 2,
11
- debug: 3,
12
- trace: 4
6
+ import { fileURLToPath } from "node:url";
7
+ import { spawn } from "node:child_process";
8
+ import { isDeepStrictEqual } from "node:util";
9
+ import path from "node:path";
10
+ //#region ../harness/dist/bridge/index.js
11
+ const DEBUG_LEVEL_WEIGHT = {
12
+ error: 0,
13
+ warn: 1,
14
+ info: 2,
15
+ debug: 3,
16
+ trace: 4
13
17
  };
18
+ /** Exact-or-dotted-prefix subsystem match (`'bridge'` matches `'bridge.turn'`). */
14
19
  function subsystemMatches(filters, subsystem) {
15
- if (!filters || filters.length === 0) return true;
16
- return filters.some(
17
- (filter) => subsystem === filter || subsystem.startsWith(`${filter}.`)
18
- );
20
+ if (!filters || filters.length === 0) return true;
21
+ return filters.some((filter) => subsystem === filter || subsystem.startsWith(`${filter}.`));
19
22
  }
20
23
  function formatBridgeError(err) {
21
- if (err instanceof Error) {
22
- return { name: err.name, message: err.message, stack: err.stack };
23
- }
24
- if (typeof err === "string") {
25
- return { message: err };
26
- }
27
- if (err !== null && typeof err === "object") {
28
- try {
29
- return { message: JSON.stringify(err) };
30
- } catch {
31
- }
32
- }
33
- return { message: String(err) };
24
+ if (err instanceof Error) return {
25
+ name: err.name,
26
+ message: err.message,
27
+ stack: err.stack
28
+ };
29
+ if (typeof err === "string") return { message: err };
30
+ if (err !== null && typeof err === "object") try {
31
+ return { message: JSON.stringify(err) };
32
+ } catch {}
33
+ return { message: String(err) };
34
34
  }
35
35
  function createBridgeUserMessageQueue(options) {
36
- const messages = [];
37
- const waiters = [];
38
- const entries = /* @__PURE__ */ new Map();
39
- let closed = false;
40
- let pendingCount = 0;
41
- const enqueue = (input) => {
42
- const existing = entries.get(input.messageId);
43
- if (existing != null) {
44
- if (existing.response != null) {
45
- options.respond(existing.response);
46
- }
47
- return;
48
- }
49
- let settled = false;
50
- const settle = (response) => {
51
- if (settled) return;
52
- settled = true;
53
- pendingCount--;
54
- const entry = entries.get(input.messageId);
55
- if (entry != null) entry.response = response;
56
- options.respond(response);
57
- };
58
- const message = {
59
- messageId: input.messageId,
60
- text: input.text,
61
- accept: () => {
62
- settle({
63
- type: "user-message-response",
64
- messageId: input.messageId,
65
- accepted: true
66
- });
67
- },
68
- reject: (error) => {
69
- settle({
70
- type: "user-message-response",
71
- messageId: input.messageId,
72
- accepted: false,
73
- error: { message: formatBridgeError(error).message }
74
- });
75
- }
76
- };
77
- entries.set(input.messageId, {
78
- reject: message.reject
79
- });
80
- pendingCount++;
81
- if (closed) {
82
- message.reject(
83
- new Error("The bridge turn is no longer accepting user messages.")
84
- );
85
- return;
86
- }
87
- const waiter = waiters.shift();
88
- if (waiter != null) {
89
- waiter({ done: false, value: message });
90
- } else {
91
- messages.push(message);
92
- }
93
- };
94
- const close = (error) => {
95
- if (closed) return;
96
- closed = true;
97
- const reason = error ?? new Error("The bridge turn ended before accepting the user message.");
98
- for (const entry of entries.values()) {
99
- if (entry.response == null) entry.reject(reason);
100
- }
101
- messages.length = 0;
102
- while (waiters.length > 0) {
103
- waiters.shift()({ done: true, value: void 0 });
104
- }
105
- };
106
- return {
107
- get pendingCount() {
108
- return pendingCount;
109
- },
110
- enqueue,
111
- close,
112
- [Symbol.asyncIterator]() {
113
- return {
114
- next: () => {
115
- const message = messages.shift();
116
- if (message != null) {
117
- return Promise.resolve({ done: false, value: message });
118
- }
119
- if (closed) {
120
- return Promise.resolve({
121
- done: true,
122
- value: void 0
123
- });
124
- }
125
- return new Promise(
126
- (resolve) => {
127
- waiters.push(resolve);
128
- }
129
- );
130
- }
131
- };
132
- }
133
- };
36
+ const messages = [];
37
+ const waiters = [];
38
+ const entries = /* @__PURE__ */ new Map();
39
+ let closed = false;
40
+ let pendingCount = 0;
41
+ const enqueue = (input) => {
42
+ const existing = entries.get(input.messageId);
43
+ if (existing != null) {
44
+ if (existing.response != null) options.respond(existing.response);
45
+ return;
46
+ }
47
+ let settled = false;
48
+ const settle = (response) => {
49
+ if (settled) return;
50
+ settled = true;
51
+ pendingCount--;
52
+ const entry = entries.get(input.messageId);
53
+ if (entry != null) entry.response = response;
54
+ options.respond(response);
55
+ };
56
+ const message = {
57
+ messageId: input.messageId,
58
+ text: input.text,
59
+ accept: () => {
60
+ settle({
61
+ type: "user-message-response",
62
+ messageId: input.messageId,
63
+ accepted: true
64
+ });
65
+ },
66
+ reject: (error) => {
67
+ settle({
68
+ type: "user-message-response",
69
+ messageId: input.messageId,
70
+ accepted: false,
71
+ error: { message: formatBridgeError(error).message }
72
+ });
73
+ }
74
+ };
75
+ entries.set(input.messageId, { reject: message.reject });
76
+ pendingCount++;
77
+ if (closed) {
78
+ message.reject(/* @__PURE__ */ new Error("The bridge turn is no longer accepting user messages."));
79
+ return;
80
+ }
81
+ const waiter = waiters.shift();
82
+ if (waiter != null) waiter({
83
+ done: false,
84
+ value: message
85
+ });
86
+ else messages.push(message);
87
+ };
88
+ const close = (error) => {
89
+ if (closed) return;
90
+ closed = true;
91
+ const reason = error ?? /* @__PURE__ */ new Error("The bridge turn ended before accepting the user message.");
92
+ for (const entry of entries.values()) if (entry.response == null) entry.reject(reason);
93
+ messages.length = 0;
94
+ while (waiters.length > 0) waiters.shift()({
95
+ done: true,
96
+ value: void 0
97
+ });
98
+ };
99
+ return {
100
+ get pendingCount() {
101
+ return pendingCount;
102
+ },
103
+ enqueue,
104
+ close,
105
+ [Symbol.asyncIterator]() {
106
+ return { next: () => {
107
+ const message = messages.shift();
108
+ if (message != null) return Promise.resolve({
109
+ done: false,
110
+ value: message
111
+ });
112
+ if (closed) return Promise.resolve({
113
+ done: true,
114
+ value: void 0
115
+ });
116
+ return new Promise((resolve) => {
117
+ waiters.push(resolve);
118
+ });
119
+ } };
120
+ }
121
+ };
134
122
  }
135
123
  function parseEnvList(value) {
136
- if (!value) return void 0;
137
- const items = value.split(",").map((item) => item.trim()).filter(Boolean);
138
- return items.length > 0 ? items : void 0;
124
+ if (!value) return void 0;
125
+ const items = value.split(",").map((item) => item.trim()).filter(Boolean);
126
+ return items.length > 0 ? items : void 0;
139
127
  }
140
- var ENV_TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
141
- var WS_OPEN = 1;
128
+ const ENV_TRUTHY = /* @__PURE__ */ new Set([
129
+ "1",
130
+ "true",
131
+ "yes",
132
+ "on"
133
+ ]);
134
+ const WS_OPEN = 1;
142
135
  async function runBridge(options) {
143
- const { bridgeType, bridgeStateDir: bridgeStateDir2, onStart, onStop, onDestroy } = options;
144
- const teardownGraceMs = options.turnTeardownGraceMs ?? 1e4;
145
- const expectedToken = options.token ?? procEnv.BRIDGE_CHANNEL_TOKEN ?? "";
146
- const bridgeWsPort = options.port ?? parseInt(procEnv.BRIDGE_WS_PORT ?? "0", 10);
147
- const bridgeMetaPath = `${bridgeStateDir2}/bridge-meta.json`;
148
- const startConfigPath = `${bridgeStateDir2}/start-config.json`;
149
- const rerunStartConfigPath = `${bridgeStateDir2}/rerun-start-config.json`;
150
- const eventLogPath = `${bridgeStateDir2}/event-log.ndjson`;
151
- try {
152
- await mkdir(bridgeStateDir2, { recursive: true });
153
- } catch {
154
- }
155
- let currentBoundPort = 0;
156
- let currentTurnState = "init";
157
- let activeSocket;
158
- let isFirstTurn = true;
159
- let turnAbort;
160
- let currentUserMessages;
161
- let activeTurn;
162
- let debugConfig;
163
- let consoleCaptureInstalled = false;
164
- const envDebugEnabled = ENV_TRUTHY.has(
165
- (procEnv.HARNESS_DEBUG ?? "").toLowerCase()
166
- );
167
- let seqCounter = 0;
168
- let eventLog = [];
169
- let diskBuffer = "";
170
- let flushPromise = null;
171
- const flushEventsToDisk = async () => {
172
- while (diskBuffer.length > 0) {
173
- const buf = diskBuffer;
174
- diskBuffer = "";
175
- await appendFile(eventLogPath, buf).catch(() => {
176
- });
177
- }
178
- };
179
- const scheduleEventFlush = () => {
180
- if (flushPromise) return;
181
- flushPromise = new Promise((resolve) => {
182
- setImmediate(() => {
183
- void flushEventsToDisk().finally(resolve);
184
- });
185
- }).finally(() => {
186
- flushPromise = null;
187
- if (diskBuffer.length > 0) {
188
- scheduleEventFlush();
189
- }
190
- });
191
- };
192
- const flushPendingEventsToDisk = async () => {
193
- if (diskBuffer.length > 0 && !flushPromise) {
194
- scheduleEventFlush();
195
- }
196
- let inFlight = flushPromise;
197
- while (inFlight) {
198
- await inFlight;
199
- inFlight = flushPromise;
200
- }
201
- };
202
- const replayFromDisk = procEnv.BRIDGE_REPLAY_FROM_DISK === "1";
203
- if (replayFromDisk && existsSync(eventLogPath)) {
204
- try {
205
- const lines = readFileSync(eventLogPath, "utf8").split("\n").map((line) => line.trim()).filter(Boolean);
206
- eventLog = lines.map((line) => ({
207
- seq: JSON.parse(line).seq,
208
- line
209
- }));
210
- seqCounter = eventLog.at(-1)?.seq ?? 0;
211
- } catch {
212
- eventLog = [];
213
- seqCounter = 0;
214
- }
215
- }
216
- const pendingToolResults = /* @__PURE__ */ new Map();
217
- const bufferedToolResults = [];
218
- const pendingToolApprovals = /* @__PURE__ */ new Map();
219
- const writeBridgeMeta = async (state) => {
220
- try {
221
- await writeFile(
222
- bridgeMetaPath,
223
- JSON.stringify({
224
- type: bridgeType,
225
- port: currentBoundPort,
226
- state,
227
- pid
228
- })
229
- );
230
- } catch {
231
- }
232
- };
233
- const writeStartConfig = async (start) => {
234
- try {
235
- const serialized = JSON.stringify(start);
236
- await writeFile(startConfigPath, serialized);
237
- if (!existsSync(rerunStartConfigPath)) {
238
- await writeFile(rerunStartConfigPath, serialized);
239
- }
240
- } catch {
241
- }
242
- };
243
- const emit = (event) => {
244
- const seq = ++seqCounter;
245
- const line = JSON.stringify({ ...event, seq });
246
- eventLog.push({ seq, line });
247
- diskBuffer += `${line}
248
- `;
249
- scheduleEventFlush();
250
- if (activeSocket?.readyState === WS_OPEN) {
251
- try {
252
- activeSocket.send(line);
253
- } catch {
254
- }
255
- }
256
- };
257
- const replay = (ws, afterSeq) => {
258
- for (const entry of eventLog) {
259
- if (entry.seq > afterSeq && ws.readyState === WS_OPEN) {
260
- ws.send(entry.line);
261
- }
262
- }
263
- };
264
- const shouldEmitDebugEvent = (level, subsystem) => {
265
- if (!debugConfig?.enabled) return false;
266
- const threshold = debugConfig.level ?? "debug";
267
- if (DEBUG_LEVEL_WEIGHT[level] > DEBUG_LEVEL_WEIGHT[threshold]) return false;
268
- return subsystemMatches(debugConfig.subsystems, subsystem);
269
- };
270
- const rawStdoutWrite = process.stdout.write.bind(process.stdout);
271
- const rawStderrWrite = process.stderr.write.bind(process.stderr);
272
- const writeErrorToStderr = (input) => {
273
- try {
274
- const formatted = formatBridgeError(input.error);
275
- rawStderrWrite(
276
- `[harness:${bridgeType}:error] ${input.message}: ${formatted.message}
277
- `
278
- );
279
- if (formatted.stack) {
280
- rawStderrWrite(`${formatted.stack}
281
- `);
282
- }
283
- } catch {
284
- }
285
- };
286
- const emitWarning = (input) => {
287
- try {
288
- for (const line of input.message.split("\n")) {
289
- if (line.trim().length > 0) {
290
- rawStderrWrite(`[harness:${bridgeType}:warn] ${line}
291
- `);
292
- }
293
- }
294
- } catch {
295
- }
296
- };
297
- const emitError = (input) => {
298
- writeErrorToStderr({
299
- message: input.message ?? "bridge error",
300
- error: input.error
301
- });
302
- emit({ type: "error", error: serialiseError(input.error) });
303
- };
304
- const installConsoleCapture = () => {
305
- if (consoleCaptureInstalled) return;
306
- consoleCaptureInstalled = true;
307
- const buffers = {
308
- stdout: "",
309
- stderr: ""
310
- };
311
- const patch = (stream, raw) => (chunk, encoding, cb) => {
312
- if (debugConfig?.enabled) {
313
- try {
314
- const enc = typeof encoding === "string" ? encoding : "utf8";
315
- const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString(
316
- enc
317
- );
318
- const combined = buffers[stream] + text.replace(/\r\n/g, "\n");
319
- const parts = combined.split("\n");
320
- buffers[stream] = parts.pop() ?? "";
321
- for (const line of parts) {
322
- const trimmed = line.replace(/\s+$/, "");
323
- if (trimmed) {
324
- emit({
325
- type: "sandbox-log",
326
- source: bridgeType,
327
- stream,
328
- line: trimmed
329
- });
330
- }
331
- }
332
- } catch {
333
- }
334
- }
335
- return raw(
336
- chunk,
337
- encoding,
338
- cb
339
- );
340
- };
341
- process.stdout.write = patch(
342
- "stdout",
343
- rawStdoutWrite
344
- );
345
- process.stderr.write = patch(
346
- "stderr",
347
- rawStderrWrite
348
- );
349
- };
350
- const handleInbound = async (msg, ws) => {
351
- switch (msg.type) {
352
- case "start": {
353
- for (; ; ) {
354
- const pendingTurn = activeTurn;
355
- if (pendingTurn == null) break;
356
- turnAbort?.abort();
357
- currentUserMessages?.close(
358
- new Error("A new bridge turn replaced the active turn.")
359
- );
360
- let graceTimer;
361
- const settled = await Promise.race([
362
- pendingTurn.then(() => true),
363
- new Promise((resolve) => {
364
- graceTimer = setTimeout(() => resolve(false), teardownGraceMs);
365
- graceTimer.unref?.();
366
- })
367
- ]);
368
- clearTimeout(graceTimer);
369
- if (!settled) break;
370
- }
371
- let turnFinished;
372
- const thisTurn = new Promise((resolve) => turnFinished = resolve);
373
- activeTurn = thisTurn;
374
- activeSocket = ws;
375
- const firstTurn = isFirstTurn;
376
- isFirstTurn = false;
377
- eventLog = [];
378
- diskBuffer = "";
379
- void writeFile(eventLogPath, "").catch(() => {
380
- });
381
- turnAbort = new AbortController();
382
- currentTurnState = "running";
383
- void writeStartConfig(msg);
384
- void writeBridgeMeta("running");
385
- const startDebug = msg.debug;
386
- debugConfig = {
387
- enabled: startDebug?.enabled ?? envDebugEnabled,
388
- level: startDebug?.level ?? procEnv.HARNESS_DEBUG_LEVEL,
389
- subsystems: startDebug?.subsystems ?? parseEnvList(procEnv.HARNESS_DEBUG_SUBSYSTEMS)
390
- };
391
- if (debugConfig.enabled) {
392
- installConsoleCapture();
393
- }
394
- const userMessages = createBridgeUserMessageQueue({ respond: emit });
395
- const turn = {
396
- emit,
397
- requestToolResult: (requestInput) => {
398
- const request = typeof requestInput === "string" ? { toolCallId: requestInput } : requestInput;
399
- const bufferedIndex = bufferedToolResults.findIndex(
400
- (buffered) => buffered.toolCallId === request.toolCallId || request.matches?.(buffered.result) === true
401
- );
402
- if (bufferedIndex >= 0) {
403
- return Promise.resolve(
404
- bufferedToolResults.splice(bufferedIndex, 1)[0].result
405
- );
406
- }
407
- return new Promise((resolve) => {
408
- pendingToolResults.set(request.toolCallId, {
409
- resolve,
410
- matches: request.matches
411
- });
412
- });
413
- },
414
- requestToolApproval: (approvalId) => new Promise((resolve) => {
415
- pendingToolApprovals.set(approvalId, resolve);
416
- }),
417
- experimental_userMessages: userMessages,
418
- abortSignal: turnAbort.signal,
419
- firstTurn,
420
- bridgeLog: (input) => {
421
- const level = input.level ?? "debug";
422
- if (!shouldEmitDebugEvent(level, input.subsystem)) return;
423
- emit({
424
- type: "debug-event",
425
- level,
426
- subsystem: input.subsystem,
427
- message: input.message,
428
- ...input.attrs ? { attrs: input.attrs } : {},
429
- ...input.error !== void 0 ? { error: formatBridgeError(input.error) } : {}
430
- });
431
- },
432
- emitWarning,
433
- emitError
434
- };
435
- currentUserMessages = userMessages;
436
- try {
437
- await onStart(msg, turn);
438
- } catch (err) {
439
- emitError({ error: err, message: "bridge turn failed" });
440
- } finally {
441
- userMessages.close();
442
- if (currentUserMessages === userMessages) {
443
- currentUserMessages = void 0;
444
- }
445
- if (activeTurn === thisTurn) {
446
- activeTurn = void 0;
447
- currentTurnState = "waiting";
448
- void writeBridgeMeta("waiting");
449
- }
450
- turnFinished();
451
- }
452
- return;
453
- }
454
- case "tool-result": {
455
- const result = {
456
- output: msg.output,
457
- isError: msg.isError,
458
- toolResult: msg.toolResult
459
- };
460
- const exactPending = pendingToolResults.get(msg.toolCallId);
461
- const matchingPending = exactPending == null ? Array.from(pendingToolResults.entries()).find(
462
- ([, pending2]) => pending2.matches?.(result) === true
463
- ) : void 0;
464
- const pending = exactPending ?? matchingPending?.[1];
465
- const pendingId = exactPending != null ? msg.toolCallId : matchingPending?.[0];
466
- if (pending != null && pendingId != null) {
467
- pendingToolResults.delete(pendingId);
468
- pending.resolve(result);
469
- } else {
470
- bufferedToolResults.push({
471
- toolCallId: msg.toolCallId,
472
- result
473
- });
474
- }
475
- return;
476
- }
477
- case "tool-approval-response": {
478
- const resolver = pendingToolApprovals.get(msg.approvalId);
479
- if (resolver) {
480
- pendingToolApprovals.delete(msg.approvalId);
481
- resolver({ approved: msg.approved, reason: msg.reason });
482
- }
483
- return;
484
- }
485
- case "user-message": {
486
- const messageId = msg.messageId ?? randomUUID();
487
- if (currentUserMessages == null) {
488
- sendControl(ws, {
489
- type: "user-message-response",
490
- messageId,
491
- accepted: false,
492
- error: { message: "The bridge has no active turn to steer." }
493
- });
494
- return;
495
- }
496
- if (ws !== activeSocket) {
497
- sendControl(ws, {
498
- type: "user-message-response",
499
- messageId,
500
- accepted: false,
501
- error: {
502
- message: "The connection does not own the active bridge turn."
503
- }
504
- });
505
- return;
506
- }
507
- currentUserMessages.enqueue({
508
- messageId,
509
- text: msg.text
510
- });
511
- return;
512
- }
513
- case "abort":
514
- turnAbort?.abort();
515
- return;
516
- case "resume":
517
- activeSocket = ws;
518
- replay(ws, msg.lastSeenEventId);
519
- return;
520
- case "destroy":
521
- currentTurnState = "done";
522
- void writeBridgeMeta("done");
523
- await onDestroy?.();
524
- drainThenExit(ws, 1e3, "destroy");
525
- return;
526
- case "stop": {
527
- currentTurnState = "done";
528
- void writeBridgeMeta("done");
529
- const data = await onStop?.() ?? {};
530
- sendControl(ws, { type: "bridge-stop", data });
531
- drainThenExit(ws, 1e3, "stop");
532
- }
533
- }
534
- };
535
- void writeBridgeMeta("init");
536
- const wss = new WebSocketServer({ port: bridgeWsPort, host: "0.0.0.0" });
537
- const exit = () => {
538
- if (options.onExit) {
539
- options.onExit();
540
- return;
541
- }
542
- wss.close(() => process.exit(0));
543
- setTimeout(() => process.exit(0), 1e3).unref();
544
- };
545
- const drainThenExit = (ws, code, reason) => {
546
- const start = Date.now();
547
- const tick = () => {
548
- const drained = ws.bufferedAmount === 0 || ws.readyState !== WS_OPEN;
549
- if (drained || Date.now() - start >= 5e3) {
550
- void flushPendingEventsToDisk().finally(() => {
551
- try {
552
- ws.close(code, reason);
553
- } finally {
554
- exit();
555
- }
556
- });
557
- return;
558
- }
559
- setTimeout(tick, 10).unref();
560
- };
561
- tick();
562
- };
563
- wss.on("listening", () => {
564
- const addr = wss.address();
565
- currentBoundPort = typeof addr === "object" && addr ? addr.port : 0;
566
- currentTurnState = "waiting";
567
- void writeBridgeMeta("waiting");
568
- stdout.write(
569
- JSON.stringify({
570
- type: "bridge-ready",
571
- port: currentBoundPort
572
- }) + "\n"
573
- );
574
- options.onListening?.(currentBoundPort);
575
- });
576
- wss.on("connection", (ws, req) => {
577
- const url = new URL(req.url ?? "/", "http://localhost");
578
- if (url.searchParams.get("agent_bridge_token") !== expectedToken) {
579
- ws.close(1008, "unauthorized");
580
- return;
581
- }
582
- sendControl(ws, {
583
- type: "bridge-hello",
584
- state: currentTurnState,
585
- lastSeq: seqCounter,
586
- capabilities: { experimental_userMessageResponses: true }
587
- });
588
- ws.on("message", (raw) => {
589
- let parsed;
590
- try {
591
- const text = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
592
- parsed = JSON.parse(text);
593
- } catch (err) {
594
- sendControl(ws, {
595
- type: "error",
596
- error: `protocol parse error: ${err.message}`
597
- });
598
- return;
599
- }
600
- void handleInbound(parsed, ws);
601
- });
602
- ws.on("close", () => {
603
- if (activeSocket === ws) {
604
- activeSocket = void 0;
605
- }
606
- });
607
- ws.on("error", () => {
608
- });
609
- });
610
- process.on("uncaughtException", (err) => {
611
- emitError({ error: err, message: "uncaught exception" });
612
- });
613
- process.on("unhandledRejection", (err) => {
614
- emitError({ error: err, message: "unhandled rejection" });
615
- });
616
- await new Promise((resolve, reject) => {
617
- if (wss.address() != null) {
618
- resolve();
619
- return;
620
- }
621
- wss.once("listening", resolve);
622
- wss.once("error", reject);
623
- });
624
- return {
625
- port: currentBoundPort,
626
- close: () => new Promise((resolve) => {
627
- wss.close(() => resolve());
628
- })
629
- };
136
+ const { bridgeType, bridgeStateDir, onStart, onStop, onDestroy } = options;
137
+ const teardownGraceMs = options.turnTeardownGraceMs ?? 1e4;
138
+ const expectedToken = options.token ?? env.BRIDGE_CHANNEL_TOKEN ?? "";
139
+ const bridgeWsPort = options.port ?? parseInt(env.BRIDGE_WS_PORT ?? "0", 10);
140
+ const bridgeMetaPath = `${bridgeStateDir}/bridge-meta.json`;
141
+ const startConfigPath = `${bridgeStateDir}/start-config.json`;
142
+ const rerunStartConfigPath = `${bridgeStateDir}/rerun-start-config.json`;
143
+ const eventLogPath = `${bridgeStateDir}/event-log.ndjson`;
144
+ try {
145
+ await mkdir(bridgeStateDir, { recursive: true });
146
+ } catch {}
147
+ let currentBoundPort = 0;
148
+ let currentTurnState = "init";
149
+ let activeSocket;
150
+ let isFirstTurn = true;
151
+ let turnAbort;
152
+ let currentUserMessages;
153
+ /**
154
+ * Settles when the in-flight turn has fully wound down — `onStart`
155
+ * returned or threw AND its completion state was recorded. `undefined`
156
+ * between turns. A new `start` fences on this so turns never overlap.
157
+ */
158
+ let activeTurn;
159
+ let debugConfig;
160
+ let consoleCaptureInstalled = false;
161
+ const envDebugEnabled = ENV_TRUTHY.has((env.HARNESS_DEBUG ?? "").toLowerCase());
162
+ let seqCounter = 0;
163
+ let eventLog = [];
164
+ let diskBuffer = "";
165
+ let flushPromise = null;
166
+ const flushEventsToDisk = async () => {
167
+ while (diskBuffer.length > 0) {
168
+ const buf = diskBuffer;
169
+ diskBuffer = "";
170
+ await appendFile(eventLogPath, buf).catch(() => {});
171
+ }
172
+ };
173
+ const scheduleEventFlush = () => {
174
+ if (flushPromise) return;
175
+ flushPromise = new Promise((resolve) => {
176
+ setImmediate(() => {
177
+ flushEventsToDisk().finally(resolve);
178
+ });
179
+ }).finally(() => {
180
+ flushPromise = null;
181
+ if (diskBuffer.length > 0) scheduleEventFlush();
182
+ });
183
+ };
184
+ const flushPendingEventsToDisk = async () => {
185
+ if (diskBuffer.length > 0 && !flushPromise) scheduleEventFlush();
186
+ let inFlight = flushPromise;
187
+ while (inFlight) {
188
+ await inFlight;
189
+ inFlight = flushPromise;
190
+ }
191
+ };
192
+ if (env.BRIDGE_REPLAY_FROM_DISK === "1" && existsSync(eventLogPath)) try {
193
+ eventLog = readFileSync(eventLogPath, "utf8").split("\n").map((line) => line.trim()).filter(Boolean).map((line) => ({
194
+ seq: JSON.parse(line).seq,
195
+ line
196
+ }));
197
+ seqCounter = eventLog.at(-1)?.seq ?? 0;
198
+ } catch {
199
+ eventLog = [];
200
+ seqCounter = 0;
201
+ }
202
+ const pendingToolResults = /* @__PURE__ */ new Map();
203
+ const bufferedToolResults = [];
204
+ const pendingToolApprovals = /* @__PURE__ */ new Map();
205
+ const writeBridgeMeta = async (state) => {
206
+ try {
207
+ await writeFile(bridgeMetaPath, JSON.stringify({
208
+ type: bridgeType,
209
+ port: currentBoundPort,
210
+ state,
211
+ pid
212
+ }));
213
+ } catch {}
214
+ };
215
+ const writeStartConfig = async (start) => {
216
+ try {
217
+ const serialized = JSON.stringify(start);
218
+ await writeFile(startConfigPath, serialized);
219
+ if (!existsSync(rerunStartConfigPath)) await writeFile(rerunStartConfigPath, serialized);
220
+ } catch {}
221
+ };
222
+ const emit = (event) => {
223
+ const seq = ++seqCounter;
224
+ const line = JSON.stringify({
225
+ ...event,
226
+ seq
227
+ });
228
+ eventLog.push({
229
+ seq,
230
+ line
231
+ });
232
+ diskBuffer += `${line}\n`;
233
+ scheduleEventFlush();
234
+ if (activeSocket?.readyState === WS_OPEN) try {
235
+ activeSocket.send(line);
236
+ } catch {}
237
+ };
238
+ const replay = (ws, afterSeq) => {
239
+ for (const entry of eventLog) if (entry.seq > afterSeq && ws.readyState === WS_OPEN) ws.send(entry.line);
240
+ };
241
+ const shouldEmitDebugEvent = (level, subsystem) => {
242
+ if (!debugConfig?.enabled) return false;
243
+ const threshold = debugConfig.level ?? "debug";
244
+ if (DEBUG_LEVEL_WEIGHT[level] > DEBUG_LEVEL_WEIGHT[threshold]) return false;
245
+ return subsystemMatches(debugConfig.subsystems, subsystem);
246
+ };
247
+ const rawStdoutWrite = process.stdout.write.bind(process.stdout);
248
+ const rawStderrWrite = process.stderr.write.bind(process.stderr);
249
+ const writeErrorToStderr = (input) => {
250
+ try {
251
+ const formatted = formatBridgeError(input.error);
252
+ rawStderrWrite(`[harness:${bridgeType}:error] ${input.message}: ${formatted.message}\n`);
253
+ if (formatted.stack) rawStderrWrite(`${formatted.stack}\n`);
254
+ } catch {}
255
+ };
256
+ const emitWarning = (input) => {
257
+ try {
258
+ for (const line of input.message.split("\n")) if (line.trim().length > 0) rawStderrWrite(`[harness:${bridgeType}:warn] ${line}\n`);
259
+ } catch {}
260
+ };
261
+ const emitError = (input) => {
262
+ writeErrorToStderr({
263
+ message: input.message ?? "bridge error",
264
+ error: input.error
265
+ });
266
+ emit({
267
+ type: "error",
268
+ error: serialiseError(input.error)
269
+ });
270
+ };
271
+ const installConsoleCapture = () => {
272
+ if (consoleCaptureInstalled) return;
273
+ consoleCaptureInstalled = true;
274
+ const buffers = {
275
+ stdout: "",
276
+ stderr: ""
277
+ };
278
+ const patch = (stream, raw) => (chunk, encoding, cb) => {
279
+ if (debugConfig?.enabled) try {
280
+ const enc = typeof encoding === "string" ? encoding : "utf8";
281
+ const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString(enc);
282
+ const parts = (buffers[stream] + text.replace(/\r\n/g, "\n")).split("\n");
283
+ buffers[stream] = parts.pop() ?? "";
284
+ for (const line of parts) {
285
+ const trimmed = line.replace(/\s+$/, "");
286
+ if (trimmed) emit({
287
+ type: "sandbox-log",
288
+ source: bridgeType,
289
+ stream,
290
+ line: trimmed
291
+ });
292
+ }
293
+ } catch {}
294
+ return raw(chunk, encoding, cb);
295
+ };
296
+ process.stdout.write = patch("stdout", rawStdoutWrite);
297
+ process.stderr.write = patch("stderr", rawStderrWrite);
298
+ };
299
+ const handleInbound = async (msg, ws) => {
300
+ switch (msg.type) {
301
+ case "start": {
302
+ for (;;) {
303
+ const pendingTurn = activeTurn;
304
+ if (pendingTurn == null) break;
305
+ turnAbort?.abort();
306
+ currentUserMessages?.close(/* @__PURE__ */ new Error("A new bridge turn replaced the active turn."));
307
+ let graceTimer;
308
+ const settled = await Promise.race([pendingTurn.then(() => true), new Promise((resolve) => {
309
+ graceTimer = setTimeout(() => resolve(false), teardownGraceMs);
310
+ graceTimer.unref?.();
311
+ })]);
312
+ clearTimeout(graceTimer);
313
+ if (!settled) break;
314
+ }
315
+ let turnFinished;
316
+ const thisTurn = new Promise((resolve) => turnFinished = resolve);
317
+ activeTurn = thisTurn;
318
+ activeSocket = ws;
319
+ const firstTurn = isFirstTurn;
320
+ isFirstTurn = false;
321
+ eventLog = [];
322
+ diskBuffer = "";
323
+ writeFile(eventLogPath, "").catch(() => {});
324
+ turnAbort = new AbortController();
325
+ currentTurnState = "running";
326
+ writeStartConfig(msg);
327
+ writeBridgeMeta("running");
328
+ const startDebug = msg.debug;
329
+ debugConfig = {
330
+ enabled: startDebug?.enabled ?? envDebugEnabled,
331
+ level: startDebug?.level ?? env.HARNESS_DEBUG_LEVEL,
332
+ subsystems: startDebug?.subsystems ?? parseEnvList(env.HARNESS_DEBUG_SUBSYSTEMS)
333
+ };
334
+ if (debugConfig.enabled) installConsoleCapture();
335
+ const userMessages = createBridgeUserMessageQueue({ respond: emit });
336
+ const turn = {
337
+ emit,
338
+ requestToolResult: (requestInput) => {
339
+ const request = typeof requestInput === "string" ? { toolCallId: requestInput } : requestInput;
340
+ const bufferedIndex = bufferedToolResults.findIndex((buffered) => buffered.toolCallId === request.toolCallId || request.matches?.(buffered.result) === true);
341
+ if (bufferedIndex >= 0) return Promise.resolve(bufferedToolResults.splice(bufferedIndex, 1)[0].result);
342
+ return new Promise((resolve) => {
343
+ pendingToolResults.set(request.toolCallId, {
344
+ resolve,
345
+ matches: request.matches
346
+ });
347
+ });
348
+ },
349
+ requestToolApproval: (approvalId) => new Promise((resolve) => {
350
+ pendingToolApprovals.set(approvalId, resolve);
351
+ }),
352
+ experimental_userMessages: userMessages,
353
+ abortSignal: turnAbort.signal,
354
+ firstTurn,
355
+ bridgeLog: (input) => {
356
+ const level = input.level ?? "debug";
357
+ if (!shouldEmitDebugEvent(level, input.subsystem)) return;
358
+ emit({
359
+ type: "debug-event",
360
+ level,
361
+ subsystem: input.subsystem,
362
+ message: input.message,
363
+ ...input.attrs ? { attrs: input.attrs } : {},
364
+ ...input.error !== void 0 ? { error: formatBridgeError(input.error) } : {}
365
+ });
366
+ },
367
+ emitWarning,
368
+ emitError
369
+ };
370
+ currentUserMessages = userMessages;
371
+ try {
372
+ await onStart(msg, turn);
373
+ } catch (err) {
374
+ emitError({
375
+ error: err,
376
+ message: "bridge turn failed"
377
+ });
378
+ } finally {
379
+ userMessages.close();
380
+ if (currentUserMessages === userMessages) currentUserMessages = void 0;
381
+ if (activeTurn === thisTurn) {
382
+ activeTurn = void 0;
383
+ currentTurnState = "waiting";
384
+ writeBridgeMeta("waiting");
385
+ }
386
+ turnFinished();
387
+ }
388
+ return;
389
+ }
390
+ case "tool-result": {
391
+ const result = {
392
+ output: msg.output,
393
+ isError: msg.isError,
394
+ toolResult: msg.toolResult
395
+ };
396
+ const exactPending = pendingToolResults.get(msg.toolCallId);
397
+ const matchingPending = exactPending == null ? Array.from(pendingToolResults.entries()).find(([, pending]) => pending.matches?.(result) === true) : void 0;
398
+ const pending = exactPending ?? matchingPending?.[1];
399
+ const pendingId = exactPending != null ? msg.toolCallId : matchingPending?.[0];
400
+ if (pending != null && pendingId != null) {
401
+ pendingToolResults.delete(pendingId);
402
+ pending.resolve(result);
403
+ } else bufferedToolResults.push({
404
+ toolCallId: msg.toolCallId,
405
+ result
406
+ });
407
+ return;
408
+ }
409
+ case "tool-approval-response": {
410
+ const resolver = pendingToolApprovals.get(msg.approvalId);
411
+ if (resolver) {
412
+ pendingToolApprovals.delete(msg.approvalId);
413
+ resolver({
414
+ approved: msg.approved,
415
+ reason: msg.reason
416
+ });
417
+ }
418
+ return;
419
+ }
420
+ case "user-message": {
421
+ const messageId = msg.messageId ?? randomUUID();
422
+ if (currentUserMessages == null) {
423
+ sendControl(ws, {
424
+ type: "user-message-response",
425
+ messageId,
426
+ accepted: false,
427
+ error: { message: "The bridge has no active turn to steer." }
428
+ });
429
+ return;
430
+ }
431
+ if (ws !== activeSocket) {
432
+ sendControl(ws, {
433
+ type: "user-message-response",
434
+ messageId,
435
+ accepted: false,
436
+ error: { message: "The connection does not own the active bridge turn." }
437
+ });
438
+ return;
439
+ }
440
+ currentUserMessages.enqueue({
441
+ messageId,
442
+ text: msg.text
443
+ });
444
+ return;
445
+ }
446
+ case "abort":
447
+ turnAbort?.abort();
448
+ return;
449
+ case "resume":
450
+ activeSocket = ws;
451
+ replay(ws, msg.lastSeenEventId);
452
+ return;
453
+ case "destroy":
454
+ currentTurnState = "done";
455
+ writeBridgeMeta("done");
456
+ await onDestroy?.();
457
+ drainThenExit(ws, 1e3, "destroy");
458
+ return;
459
+ case "stop":
460
+ currentTurnState = "done";
461
+ writeBridgeMeta("done");
462
+ sendControl(ws, {
463
+ type: "bridge-stop",
464
+ data: await onStop?.() ?? {}
465
+ });
466
+ drainThenExit(ws, 1e3, "stop");
467
+ }
468
+ };
469
+ writeBridgeMeta("init");
470
+ const wss = new WebSocketServer({
471
+ port: bridgeWsPort,
472
+ host: "0.0.0.0"
473
+ });
474
+ const exit = () => {
475
+ if (options.onExit) {
476
+ options.onExit();
477
+ return;
478
+ }
479
+ wss.close(() => process.exit(0));
480
+ setTimeout(() => process.exit(0), 1e3).unref();
481
+ };
482
+ const drainThenExit = (ws, code, reason) => {
483
+ const start = Date.now();
484
+ const tick = () => {
485
+ if (ws.bufferedAmount === 0 || ws.readyState !== WS_OPEN || Date.now() - start >= 5e3) {
486
+ flushPendingEventsToDisk().finally(() => {
487
+ try {
488
+ ws.close(code, reason);
489
+ } finally {
490
+ exit();
491
+ }
492
+ });
493
+ return;
494
+ }
495
+ setTimeout(tick, 10).unref();
496
+ };
497
+ tick();
498
+ };
499
+ wss.on("listening", () => {
500
+ const addr = wss.address();
501
+ currentBoundPort = typeof addr === "object" && addr ? addr.port : 0;
502
+ currentTurnState = "waiting";
503
+ writeBridgeMeta("waiting");
504
+ stdout.write(JSON.stringify({
505
+ type: "bridge-ready",
506
+ port: currentBoundPort
507
+ }) + "\n");
508
+ options.onListening?.(currentBoundPort);
509
+ });
510
+ wss.on("connection", (ws, req) => {
511
+ if (new URL(req.url ?? "/", "http://localhost").searchParams.get("agent_bridge_token") !== expectedToken) {
512
+ ws.close(1008, "unauthorized");
513
+ return;
514
+ }
515
+ sendControl(ws, {
516
+ type: "bridge-hello",
517
+ state: currentTurnState,
518
+ lastSeq: seqCounter,
519
+ capabilities: { experimental_userMessageResponses: true }
520
+ });
521
+ ws.on("message", (raw) => {
522
+ let parsed;
523
+ try {
524
+ const text = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
525
+ parsed = JSON.parse(text);
526
+ } catch (err) {
527
+ sendControl(ws, {
528
+ type: "error",
529
+ error: `protocol parse error: ${err.message}`
530
+ });
531
+ return;
532
+ }
533
+ handleInbound(parsed, ws);
534
+ });
535
+ ws.on("close", () => {
536
+ if (activeSocket === ws) activeSocket = void 0;
537
+ });
538
+ ws.on("error", () => {});
539
+ });
540
+ process.on("uncaughtException", (err) => {
541
+ emitError({
542
+ error: err,
543
+ message: "uncaught exception"
544
+ });
545
+ });
546
+ process.on("unhandledRejection", (err) => {
547
+ emitError({
548
+ error: err,
549
+ message: "unhandled rejection"
550
+ });
551
+ });
552
+ await new Promise((resolve, reject) => {
553
+ if (wss.address() != null) {
554
+ resolve();
555
+ return;
556
+ }
557
+ wss.once("listening", resolve);
558
+ wss.once("error", reject);
559
+ });
560
+ return {
561
+ port: currentBoundPort,
562
+ close: () => new Promise((resolve) => {
563
+ wss.close(() => resolve());
564
+ })
565
+ };
630
566
  }
631
567
  function sendControl(socket, message) {
632
- if (socket?.readyState === WS_OPEN) {
633
- try {
634
- socket.send(JSON.stringify(message));
635
- } catch {
636
- }
637
- }
568
+ if (socket?.readyState === WS_OPEN) try {
569
+ socket.send(JSON.stringify(message));
570
+ } catch {}
638
571
  }
639
572
  function serialiseError(err) {
640
- if (err instanceof Error) {
641
- return { name: err.name, message: err.message, stack: err.stack };
642
- }
643
- return err;
573
+ if (err instanceof Error) return {
574
+ name: err.name,
575
+ message: err.message,
576
+ stack: err.stack
577
+ };
578
+ return err;
644
579
  }
645
-
646
- // src/bridge/codex-app-server-driver.ts
647
- import { createHash as createHash2 } from "crypto";
648
- import { fileURLToPath } from "url";
649
-
650
- // src/bridge/codex-tool-filtering.ts
651
- function resolveCodexBuiltinToolPolicy({
652
- builtinToolFiltering,
653
- webSearch
654
- }) {
655
- const included = (toolName) => builtinToolFiltering == null || (builtinToolFiltering.mode === "allow" ? builtinToolFiltering.toolNames.includes(toolName) : !builtinToolFiltering.toolNames.includes(toolName));
656
- const disabled = {
657
- bash: !included("bash"),
658
- webSearch: !included("webSearch"),
659
- apply_patch: !included("apply_patch"),
660
- view_image: !included("view_image")
661
- };
662
- const disableEnvironments = disabled.bash && disabled.apply_patch && disabled.view_image;
663
- return {
664
- disabled,
665
- disableEnvironments,
666
- denyApplyPatchWithHook: disabled.apply_patch && !disableEnvironments,
667
- webSearchMode: webSearch && !disabled.webSearch ? "live" : "disabled"
668
- };
580
+ //#endregion
581
+ //#region src/bridge/codex-tool-filtering.ts
582
+ function resolveCodexBuiltinToolPolicy({ builtinToolFiltering, webSearch }) {
583
+ const included = (toolName) => builtinToolFiltering == null || (builtinToolFiltering.mode === "allow" ? builtinToolFiltering.toolNames.includes(toolName) : !builtinToolFiltering.toolNames.includes(toolName));
584
+ const disabled = {
585
+ bash: !included("bash"),
586
+ webSearch: !included("webSearch"),
587
+ apply_patch: !included("apply_patch"),
588
+ view_image: !included("view_image")
589
+ };
590
+ const disableEnvironments = disabled.bash && disabled.apply_patch && disabled.view_image;
591
+ return {
592
+ disabled,
593
+ disableEnvironments,
594
+ denyApplyPatchWithHook: disabled.apply_patch && !disableEnvironments,
595
+ webSearchMode: webSearch && !disabled.webSearch ? "live" : "disabled"
596
+ };
669
597
  }
670
-
671
- // src/bridge/codex-app-server-client.ts
672
- import { spawn } from "child_process";
598
+ //#endregion
599
+ //#region src/bridge/codex-app-server-client.ts
673
600
  var CodexAppServerRequestError = class extends Error {
674
- constructor({ method, error }) {
675
- super(`Codex app-server ${method} failed: ${error.message}`);
676
- this.name = "CodexAppServerRequestError";
677
- this.method = method;
678
- this.code = error.code;
679
- this.data = error.data;
680
- }
601
+ constructor({ method, error }) {
602
+ super(`Codex app-server ${method} failed: ${error.message}`);
603
+ this.name = "CodexAppServerRequestError";
604
+ this.method = method;
605
+ this.code = error.code;
606
+ this.data = error.data;
607
+ }
681
608
  };
682
609
  var CodexAppServerClient = class {
683
- constructor({
684
- executable,
685
- args: args2,
686
- cwd,
687
- env,
688
- onNotification,
689
- onRequest,
690
- onStderr
691
- }) {
692
- this.pending = /* @__PURE__ */ new Map();
693
- this.nextRequestId = 1;
694
- this.stdoutBuffer = "";
695
- this.parseQueue = Promise.resolve();
696
- this.closed = false;
697
- this.resolveFailure = () => {
698
- };
699
- this.exitResult = null;
700
- this.onNotification = onNotification;
701
- this.onRequest = onRequest;
702
- this.onStderr = onStderr;
703
- this.failed = new Promise((resolve) => {
704
- this.resolveFailure = resolve;
705
- });
706
- this.child = spawn(executable, args2, {
707
- cwd,
708
- env,
709
- stdio: ["pipe", "pipe", "pipe"]
710
- });
711
- this.exited = new Promise((resolve) => {
712
- this.child.once("exit", (code, signal) => {
713
- this.exitResult = { code, signal };
714
- this.fail(
715
- new Error(
716
- `Codex app-server exited (code ${code ?? "null"}, signal ${signal ?? "null"}).`
717
- )
718
- );
719
- resolve(this.exitResult);
720
- });
721
- });
722
- this.child.once("error", (error) => this.fail(error));
723
- this.child.stdout.setEncoding("utf8");
724
- this.child.stdout.on("data", (chunk) => this.consumeStdout(String(chunk)));
725
- this.child.stderr.setEncoding("utf8");
726
- this.child.stderr.on("data", (chunk) => this.onStderr(String(chunk)));
727
- }
728
- async initialize({
729
- clientName,
730
- clientVersion
731
- }) {
732
- await this.request({
733
- method: "initialize",
734
- params: {
735
- clientInfo: { name: clientName, version: clientVersion },
736
- capabilities: { experimentalApi: true }
737
- }
738
- });
739
- this.notify({ method: "initialized" });
740
- }
741
- request({
742
- method,
743
- params
744
- }) {
745
- if (this.closed || this.exitResult != null) {
746
- return Promise.reject(new Error("Codex app-server is not running."));
747
- }
748
- const id = this.nextRequestId++;
749
- const response = new Promise((resolve, reject) => {
750
- this.pending.set(id, { method, resolve, reject });
751
- });
752
- try {
753
- this.write({ id, method, ...params === void 0 ? {} : { params } });
754
- } catch (error) {
755
- this.pending.delete(id);
756
- return Promise.reject(error);
757
- }
758
- return response;
759
- }
760
- notify({ method, params }) {
761
- this.write({ method, ...params === void 0 ? {} : { params } });
762
- }
763
- async close() {
764
- if (this.closed) return;
765
- this.closed = true;
766
- this.child.stdin.end();
767
- if (await this.waitForExit({ timeoutMs: 500 })) return;
768
- this.child.kill("SIGTERM");
769
- if (await this.waitForExit({ timeoutMs: 1e3 })) return;
770
- this.child.kill("SIGKILL");
771
- await this.waitForExit({ timeoutMs: 1e3 });
772
- }
773
- waitUntilExit() {
774
- return this.exited;
775
- }
776
- waitUntilFailure() {
777
- return this.failed.then((error) => {
778
- throw error;
779
- });
780
- }
781
- write(message) {
782
- if (this.closed || this.exitResult != null || !this.child.stdin.writable) {
783
- throw new Error("Codex app-server is not writable.");
784
- }
785
- this.child.stdin.write(`${JSON.stringify(message)}
786
- `);
787
- }
788
- consumeStdout(chunk) {
789
- this.stdoutBuffer += chunk;
790
- let newline = this.stdoutBuffer.indexOf("\n");
791
- while (newline !== -1) {
792
- const line = this.stdoutBuffer.slice(0, newline).trim();
793
- this.stdoutBuffer = this.stdoutBuffer.slice(newline + 1);
794
- if (line.length > 0) {
795
- this.parseQueue = this.parseQueue.then(() => this.processLine(line)).catch((error) => this.fail(error));
796
- }
797
- newline = this.stdoutBuffer.indexOf("\n");
798
- }
799
- }
800
- async processLine(line) {
801
- let message;
802
- try {
803
- message = JSON.parse(line);
804
- } catch {
805
- throw new Error("Codex app-server wrote an invalid JSON-RPC message.");
806
- }
807
- if (!isRecord(message)) {
808
- throw new Error("Codex app-server wrote an invalid JSON-RPC message.");
809
- }
810
- if (typeof message.method === "string") {
811
- if (isJsonRpcId(message.id)) {
812
- void this.handleServerRequest({
813
- id: message.id,
814
- method: message.method,
815
- ...message.params === void 0 ? {} : { params: message.params }
816
- });
817
- } else {
818
- this.onNotification({
819
- method: message.method,
820
- ...message.params === void 0 ? {} : { params: message.params }
821
- });
822
- }
823
- return;
824
- }
825
- if (!isJsonRpcId(message.id)) {
826
- throw new Error("Codex app-server response is missing a valid id.");
827
- }
828
- const pending = this.pending.get(message.id);
829
- if (pending == null) return;
830
- this.pending.delete(message.id);
831
- if (isJsonRpcError(message.error)) {
832
- pending.reject(
833
- new CodexAppServerRequestError({
834
- method: pending.method,
835
- error: message.error
836
- })
837
- );
838
- return;
839
- }
840
- if (!Object.prototype.hasOwnProperty.call(message, "result")) {
841
- pending.reject(
842
- new Error(`Codex app-server ${pending.method} response has no result.`)
843
- );
844
- return;
845
- }
846
- pending.resolve(message.result);
847
- }
848
- async handleServerRequest(request) {
849
- try {
850
- const result = await this.onRequest(request);
851
- if (!this.closed && this.exitResult == null) {
852
- this.write({ id: request.id, result });
853
- }
854
- } catch (error) {
855
- if (!this.closed && this.exitResult == null) {
856
- this.write({
857
- id: request.id,
858
- error: {
859
- code: -32603,
860
- message: error instanceof Error ? error.message : String(error)
861
- }
862
- });
863
- }
864
- }
865
- }
866
- failPending(error) {
867
- for (const pending of this.pending.values()) pending.reject(error);
868
- this.pending.clear();
869
- }
870
- fail(error) {
871
- this.failPending(error);
872
- if (this.failure !== void 0) return;
873
- this.failure = error;
874
- this.resolveFailure(error);
875
- }
876
- async waitForExit({
877
- timeoutMs
878
- }) {
879
- if (this.exitResult != null) return true;
880
- return Promise.race([
881
- this.exited.then(() => true),
882
- new Promise((resolve) => {
883
- const timer = setTimeout(() => resolve(false), timeoutMs);
884
- timer.unref?.();
885
- })
886
- ]);
887
- }
610
+ constructor({ executable, args, cwd, env, onNotification, onRequest, onStderr }) {
611
+ this.pending = /* @__PURE__ */ new Map();
612
+ this.nextRequestId = 1;
613
+ this.stdoutBuffer = "";
614
+ this.parseQueue = Promise.resolve();
615
+ this.closed = false;
616
+ this.resolveFailure = () => {};
617
+ this.exitResult = null;
618
+ this.onNotification = onNotification;
619
+ this.onRequest = onRequest;
620
+ this.onStderr = onStderr;
621
+ this.failed = new Promise((resolve) => {
622
+ this.resolveFailure = resolve;
623
+ });
624
+ this.child = spawn(executable, args, {
625
+ cwd,
626
+ env,
627
+ stdio: [
628
+ "pipe",
629
+ "pipe",
630
+ "pipe"
631
+ ]
632
+ });
633
+ this.exited = new Promise((resolve) => {
634
+ this.child.once("exit", (code, signal) => {
635
+ this.exitResult = {
636
+ code,
637
+ signal
638
+ };
639
+ this.fail(/* @__PURE__ */ new Error(`Codex app-server exited (code ${code ?? "null"}, signal ${signal ?? "null"}).`));
640
+ resolve(this.exitResult);
641
+ });
642
+ });
643
+ this.child.once("error", (error) => this.fail(error));
644
+ this.child.stdout.setEncoding("utf8");
645
+ this.child.stdout.on("data", (chunk) => this.consumeStdout(String(chunk)));
646
+ this.child.stderr.setEncoding("utf8");
647
+ this.child.stderr.on("data", (chunk) => this.onStderr(String(chunk)));
648
+ }
649
+ async initialize({ clientName, clientVersion }) {
650
+ await this.request({
651
+ method: "initialize",
652
+ params: {
653
+ clientInfo: {
654
+ name: clientName,
655
+ version: clientVersion
656
+ },
657
+ capabilities: { experimentalApi: true }
658
+ }
659
+ });
660
+ this.notify({ method: "initialized" });
661
+ }
662
+ request({ method, params }) {
663
+ if (this.closed || this.exitResult != null) return Promise.reject(/* @__PURE__ */ new Error("Codex app-server is not running."));
664
+ const id = this.nextRequestId++;
665
+ const response = new Promise((resolve, reject) => {
666
+ this.pending.set(id, {
667
+ method,
668
+ resolve,
669
+ reject
670
+ });
671
+ });
672
+ try {
673
+ this.write({
674
+ id,
675
+ method,
676
+ ...params === void 0 ? {} : { params }
677
+ });
678
+ } catch (error) {
679
+ this.pending.delete(id);
680
+ return Promise.reject(error);
681
+ }
682
+ return response;
683
+ }
684
+ notify({ method, params }) {
685
+ this.write({
686
+ method,
687
+ ...params === void 0 ? {} : { params }
688
+ });
689
+ }
690
+ async close() {
691
+ if (this.closed) return;
692
+ this.closed = true;
693
+ this.child.stdin.end();
694
+ if (await this.waitForExit({ timeoutMs: 500 })) return;
695
+ this.child.kill("SIGTERM");
696
+ if (await this.waitForExit({ timeoutMs: 1e3 })) return;
697
+ this.child.kill("SIGKILL");
698
+ await this.waitForExit({ timeoutMs: 1e3 });
699
+ }
700
+ waitUntilExit() {
701
+ return this.exited;
702
+ }
703
+ waitUntilFailure() {
704
+ return this.failed.then((error) => {
705
+ throw error;
706
+ });
707
+ }
708
+ write(message) {
709
+ if (this.closed || this.exitResult != null || !this.child.stdin.writable) throw new Error("Codex app-server is not writable.");
710
+ this.child.stdin.write(`${JSON.stringify(message)}\n`);
711
+ }
712
+ consumeStdout(chunk) {
713
+ this.stdoutBuffer += chunk;
714
+ let newline = this.stdoutBuffer.indexOf("\n");
715
+ while (newline !== -1) {
716
+ const line = this.stdoutBuffer.slice(0, newline).trim();
717
+ this.stdoutBuffer = this.stdoutBuffer.slice(newline + 1);
718
+ if (line.length > 0) this.parseQueue = this.parseQueue.then(() => this.processLine(line)).catch((error) => this.fail(error));
719
+ newline = this.stdoutBuffer.indexOf("\n");
720
+ }
721
+ }
722
+ async processLine(line) {
723
+ let message;
724
+ try {
725
+ message = JSON.parse(line);
726
+ } catch {
727
+ throw new Error("Codex app-server wrote an invalid JSON-RPC message.");
728
+ }
729
+ if (!isRecord(message)) throw new Error("Codex app-server wrote an invalid JSON-RPC message.");
730
+ if (typeof message.method === "string") {
731
+ if (isJsonRpcId(message.id)) this.handleServerRequest({
732
+ id: message.id,
733
+ method: message.method,
734
+ ...message.params === void 0 ? {} : { params: message.params }
735
+ });
736
+ else this.onNotification({
737
+ method: message.method,
738
+ ...message.params === void 0 ? {} : { params: message.params }
739
+ });
740
+ return;
741
+ }
742
+ if (!isJsonRpcId(message.id)) throw new Error("Codex app-server response is missing a valid id.");
743
+ const pending = this.pending.get(message.id);
744
+ if (pending == null) return;
745
+ this.pending.delete(message.id);
746
+ if (isJsonRpcError(message.error)) {
747
+ pending.reject(new CodexAppServerRequestError({
748
+ method: pending.method,
749
+ error: message.error
750
+ }));
751
+ return;
752
+ }
753
+ if (!Object.prototype.hasOwnProperty.call(message, "result")) {
754
+ pending.reject(/* @__PURE__ */ new Error(`Codex app-server ${pending.method} response has no result.`));
755
+ return;
756
+ }
757
+ pending.resolve(message.result);
758
+ }
759
+ async handleServerRequest(request) {
760
+ try {
761
+ const result = await this.onRequest(request);
762
+ if (!this.closed && this.exitResult == null) this.write({
763
+ id: request.id,
764
+ result
765
+ });
766
+ } catch (error) {
767
+ if (!this.closed && this.exitResult == null) this.write({
768
+ id: request.id,
769
+ error: {
770
+ code: -32603,
771
+ message: error instanceof Error ? error.message : String(error)
772
+ }
773
+ });
774
+ }
775
+ }
776
+ failPending(error) {
777
+ for (const pending of this.pending.values()) pending.reject(error);
778
+ this.pending.clear();
779
+ }
780
+ fail(error) {
781
+ this.failPending(error);
782
+ if (this.failure !== void 0) return;
783
+ this.failure = error;
784
+ this.resolveFailure(error);
785
+ }
786
+ async waitForExit({ timeoutMs }) {
787
+ if (this.exitResult != null) return true;
788
+ return Promise.race([this.exited.then(() => true), new Promise((resolve) => {
789
+ setTimeout(() => resolve(false), timeoutMs).unref?.();
790
+ })]);
791
+ }
888
792
  };
889
793
  function isRecord(value) {
890
- return value != null && typeof value === "object" && !Array.isArray(value);
794
+ return value != null && typeof value === "object" && !Array.isArray(value);
891
795
  }
892
796
  function isJsonRpcId(value) {
893
- return typeof value === "string" || typeof value === "number";
797
+ return typeof value === "string" || typeof value === "number";
894
798
  }
895
799
  function isJsonRpcError(value) {
896
- return isRecord(value) && typeof value.code === "number" && typeof value.message === "string";
800
+ return isRecord(value) && typeof value.code === "number" && typeof value.message === "string";
897
801
  }
898
-
899
- // src/bridge/create-app-server-event-handler.ts
900
- function createAppServerEventHandler({
901
- stepTracker,
902
- emitStreamEvent,
903
- emitWarning,
904
- emitError
905
- }) {
906
- let activeThreadId;
907
- let activeTurnId;
908
- let settled = false;
909
- let accumulatedUsage = emptyUsageBreakdown();
910
- let lastCumulativeUsageKey;
911
- const textByItem = /* @__PURE__ */ new Map();
912
- const reasoningByItem = /* @__PURE__ */ new Map();
913
- const nativeToolCalls = /* @__PURE__ */ new Map();
914
- let resolveCompletion = () => {
915
- };
916
- const completion = new Promise((resolve) => {
917
- resolveCompletion = resolve;
918
- });
919
- const announceThread = (threadId) => {
920
- if (activeThreadId != null) return;
921
- activeThreadId = threadId;
922
- emitStreamEvent({ type: "thread.started", thread_id: threadId });
923
- };
924
- const handleItem = ({
925
- eventType,
926
- params
927
- }) => {
928
- if (!matchesActiveTurn({ params, activeThreadId, activeTurnId })) return;
929
- const item = asRecord(params.item);
930
- if (item == null || typeof item.type !== "string") return;
931
- if (eventType === "item.completed" && typeof item.id === "string") {
932
- const call = nativeToolCalls.get(item.id);
933
- if (call?.name === "apply_patch" && item.type === "fileChange") {
934
- call.successConfirmed = item.status === "completed";
935
- } else if (call?.name === "view_image" && item.type === "imageView") {
936
- call.successConfirmed = true;
937
- }
938
- }
939
- const normalized = normalizeItem({ item, textByItem, reasoningByItem });
940
- if (normalized == null) return;
941
- if (normalized.type === "dynamic_tool_call") {
942
- stepTracker.observeEvent({
943
- event: { type: eventType, item: normalized },
944
- itemId: normalized.id
945
- });
946
- return;
947
- }
948
- emitStreamEvent({ type: eventType, item: normalized });
949
- };
950
- const emitNativeToolResult = ({
951
- callId,
952
- result
953
- }) => {
954
- const call = nativeToolCalls.get(callId);
955
- if (call == null || call.resultEmitted) return;
956
- call.resultEmitted = true;
957
- emitStreamEvent({
958
- type: "item.completed",
959
- item: {
960
- type: "native_tool",
961
- id: callId,
962
- tool: call.name,
963
- result,
964
- ...!call.successConfirmed ? { isError: true } : {}
965
- }
966
- });
967
- };
968
- const handleRawItem = (params) => {
969
- if (!matchesActiveTurn({ params, activeThreadId, activeTurnId })) return;
970
- const item = asRecord(params.item);
971
- if (item == null) return;
972
- const toolCall = normalizeNativeToolCall({ item });
973
- if (toolCall != null) {
974
- if (nativeToolCalls.has(toolCall.callId)) return;
975
- nativeToolCalls.set(toolCall.callId, {
976
- name: toolCall.name,
977
- resultEmitted: false,
978
- successConfirmed: false
979
- });
980
- emitStreamEvent({
981
- type: "item.started",
982
- item: {
983
- type: "native_tool",
984
- id: toolCall.callId,
985
- tool: toolCall.name,
986
- input: toolCall.input
987
- }
988
- });
989
- return;
990
- }
991
- if (typeof item.call_id !== "string") return;
992
- const call = nativeToolCalls.get(item.call_id);
993
- if (call == null || (call.name === "apply_patch" ? item.type !== "custom_tool_call_output" : item.type !== "function_call_output") || !Object.prototype.hasOwnProperty.call(item, "output")) {
994
- return;
995
- }
996
- emitNativeToolResult({
997
- callId: item.call_id,
998
- result: call.name === "view_image" && call.successConfirmed && Array.isArray(item.output) && item.output.length === 0 ? "Image viewed." : item.output
999
- });
1000
- };
1001
- return {
1002
- announceThread,
1003
- setTurnId(turnId) {
1004
- activeTurnId = turnId;
1005
- },
1006
- handle(notification) {
1007
- const params = asRecord(notification.params);
1008
- if (notification.method === "thread/started") {
1009
- const thread = asRecord(params?.thread);
1010
- if (typeof thread?.id === "string") announceThread(thread.id);
1011
- return;
1012
- }
1013
- if (notification.method === "turn/started") {
1014
- const turn = asRecord(params?.turn);
1015
- if (params?.threadId === activeThreadId && typeof turn?.id === "string") {
1016
- activeTurnId = turn.id;
1017
- }
1018
- return;
1019
- }
1020
- if (notification.method === "item/started" && params != null) {
1021
- handleItem({ eventType: "item.started", params });
1022
- return;
1023
- }
1024
- if (notification.method === "item/completed" && params != null) {
1025
- handleItem({ eventType: "item.completed", params });
1026
- return;
1027
- }
1028
- if (notification.method === "rawResponseItem/completed" && params != null) {
1029
- handleRawItem(params);
1030
- return;
1031
- }
1032
- if (notification.method === "item/agentMessage/delta" && params != null && matchesActiveTurn({ params, activeThreadId, activeTurnId }) && typeof params.itemId === "string" && typeof params.delta === "string") {
1033
- const text = (textByItem.get(params.itemId) ?? "") + params.delta;
1034
- textByItem.set(params.itemId, text);
1035
- emitStreamEvent({
1036
- type: "item.updated",
1037
- item: { type: "agent_message", id: params.itemId, text }
1038
- });
1039
- return;
1040
- }
1041
- if (notification.method === "item/reasoning/summaryTextDelta" && params != null && matchesActiveTurn({ params, activeThreadId, activeTurnId }) && typeof params.itemId === "string" && typeof params.delta === "string") {
1042
- const text = (reasoningByItem.get(params.itemId) ?? "") + params.delta;
1043
- reasoningByItem.set(params.itemId, text);
1044
- emitStreamEvent({
1045
- type: "item.updated",
1046
- item: { type: "reasoning", id: params.itemId, text }
1047
- });
1048
- return;
1049
- }
1050
- if (notification.method === "thread/tokenUsage/updated" && params != null && matchesActiveTurn({ params, activeThreadId, activeTurnId })) {
1051
- const tokenUsage = asRecord(params.tokenUsage);
1052
- const total = readUsageBreakdown(tokenUsage?.total);
1053
- const last = readUsageBreakdown(tokenUsage?.last);
1054
- if (total != null && last != null) {
1055
- const key = usageKey(total);
1056
- if (key !== lastCumulativeUsageKey) {
1057
- lastCumulativeUsageKey = key;
1058
- accumulatedUsage = addUsage({
1059
- total: accumulatedUsage,
1060
- increment: last
1061
- });
1062
- }
1063
- }
1064
- return;
1065
- }
1066
- if (notification.method === "error" && params != null) {
1067
- if (!matchesActiveTurn({ params, activeThreadId, activeTurnId }))
1068
- return;
1069
- const error = asRecord(params.error);
1070
- const message = typeof error?.message === "string" ? error.message : "Codex app-server reported an error.";
1071
- if (params.willRetry === true) {
1072
- emitWarning({ message });
1073
- } else {
1074
- emitError({ error: message, message: "codex turn failed" });
1075
- }
1076
- return;
1077
- }
1078
- if ((notification.method === "warning" || notification.method === "configWarning" || notification.method === "deprecationNotice") && typeof params?.message === "string") {
1079
- emitWarning({ message: params.message });
1080
- return;
1081
- }
1082
- if (notification.method === "turn/completed" && params != null && matchesActiveTurn({ params, activeThreadId, activeTurnId })) {
1083
- if (settled) return;
1084
- settled = true;
1085
- const turn = asRecord(params.turn);
1086
- const status = typeof turn?.status === "string" ? turn.status : "failed";
1087
- const turnError = asRecord(turn?.error);
1088
- if (status === "completed") {
1089
- for (const [callId, call] of nativeToolCalls) {
1090
- if (call.resultEmitted) continue;
1091
- emitNativeToolResult({
1092
- callId,
1093
- result: call.successConfirmed ? call.name === "apply_patch" ? "Patch applied." : "Image viewed." : "Codex did not report the tool result."
1094
- });
1095
- }
1096
- }
1097
- emitStreamEvent({
1098
- type: "turn.completed",
1099
- usage: toLegacyUsage(accumulatedUsage)
1100
- });
1101
- resolveCompletion({
1102
- status,
1103
- ...typeof turnError?.message === "string" ? { error: turnError.message } : {}
1104
- });
1105
- }
1106
- },
1107
- waitForCompletion: () => completion
1108
- };
802
+ //#endregion
803
+ //#region src/bridge/create-app-server-event-handler.ts
804
+ function createAppServerEventHandler({ stepTracker, emitStreamEvent, emitWarning, emitError }) {
805
+ let activeThreadId;
806
+ let activeTurnId;
807
+ let settled = false;
808
+ let accumulatedUsage = emptyUsageBreakdown();
809
+ let lastCumulativeUsageKey;
810
+ const textByItem = /* @__PURE__ */ new Map();
811
+ const reasoningByItem = /* @__PURE__ */ new Map();
812
+ const nativeToolCalls = /* @__PURE__ */ new Map();
813
+ let resolveCompletion = () => {};
814
+ const completion = new Promise((resolve) => {
815
+ resolveCompletion = resolve;
816
+ });
817
+ const announceThread = (threadId) => {
818
+ if (activeThreadId != null) return;
819
+ activeThreadId = threadId;
820
+ emitStreamEvent({
821
+ type: "thread.started",
822
+ thread_id: threadId
823
+ });
824
+ };
825
+ const handleItem = ({ eventType, params }) => {
826
+ if (!matchesActiveTurn({
827
+ params,
828
+ activeThreadId,
829
+ activeTurnId
830
+ })) return;
831
+ const item = asRecord$2(params.item);
832
+ if (item == null || typeof item.type !== "string") return;
833
+ if (eventType === "item.completed" && typeof item.id === "string") {
834
+ const call = nativeToolCalls.get(item.id);
835
+ if (call?.name === "apply_patch" && item.type === "fileChange") call.successConfirmed = item.status === "completed";
836
+ else if (call?.name === "view_image" && item.type === "imageView") call.successConfirmed = true;
837
+ }
838
+ const normalized = normalizeItem({
839
+ item,
840
+ textByItem,
841
+ reasoningByItem
842
+ });
843
+ if (normalized == null) return;
844
+ if (normalized.type === "dynamic_tool_call") {
845
+ stepTracker.observeEvent({
846
+ event: {
847
+ type: eventType,
848
+ item: normalized
849
+ },
850
+ itemId: normalized.id
851
+ });
852
+ return;
853
+ }
854
+ emitStreamEvent({
855
+ type: eventType,
856
+ item: normalized
857
+ });
858
+ };
859
+ const emitNativeToolResult = ({ callId, result }) => {
860
+ const call = nativeToolCalls.get(callId);
861
+ if (call == null || call.resultEmitted) return;
862
+ call.resultEmitted = true;
863
+ emitStreamEvent({
864
+ type: "item.completed",
865
+ item: {
866
+ type: "native_tool",
867
+ id: callId,
868
+ tool: call.name,
869
+ result,
870
+ ...!call.successConfirmed ? { isError: true } : {}
871
+ }
872
+ });
873
+ };
874
+ const handleRawItem = (params) => {
875
+ if (!matchesActiveTurn({
876
+ params,
877
+ activeThreadId,
878
+ activeTurnId
879
+ })) return;
880
+ const item = asRecord$2(params.item);
881
+ if (item == null) return;
882
+ const toolCall = normalizeNativeToolCall({ item });
883
+ if (toolCall != null) {
884
+ if (nativeToolCalls.has(toolCall.callId)) return;
885
+ nativeToolCalls.set(toolCall.callId, {
886
+ name: toolCall.name,
887
+ resultEmitted: false,
888
+ successConfirmed: false
889
+ });
890
+ emitStreamEvent({
891
+ type: "item.started",
892
+ item: {
893
+ type: "native_tool",
894
+ id: toolCall.callId,
895
+ tool: toolCall.name,
896
+ input: toolCall.input
897
+ }
898
+ });
899
+ return;
900
+ }
901
+ if (typeof item.call_id !== "string") return;
902
+ const call = nativeToolCalls.get(item.call_id);
903
+ if (call == null || (call.name === "apply_patch" ? item.type !== "custom_tool_call_output" : item.type !== "function_call_output") || !Object.prototype.hasOwnProperty.call(item, "output")) return;
904
+ emitNativeToolResult({
905
+ callId: item.call_id,
906
+ result: call.name === "view_image" && call.successConfirmed && Array.isArray(item.output) && item.output.length === 0 ? "Image viewed." : item.output
907
+ });
908
+ };
909
+ return {
910
+ announceThread,
911
+ setTurnId(turnId) {
912
+ activeTurnId = turnId;
913
+ },
914
+ handle(notification) {
915
+ const params = asRecord$2(notification.params);
916
+ if (notification.method === "thread/started") {
917
+ const thread = asRecord$2(params?.thread);
918
+ if (typeof thread?.id === "string") announceThread(thread.id);
919
+ return;
920
+ }
921
+ if (notification.method === "turn/started") {
922
+ const turn = asRecord$2(params?.turn);
923
+ if (params?.threadId === activeThreadId && typeof turn?.id === "string") activeTurnId = turn.id;
924
+ return;
925
+ }
926
+ if (notification.method === "item/started" && params != null) {
927
+ handleItem({
928
+ eventType: "item.started",
929
+ params
930
+ });
931
+ return;
932
+ }
933
+ if (notification.method === "item/completed" && params != null) {
934
+ handleItem({
935
+ eventType: "item.completed",
936
+ params
937
+ });
938
+ return;
939
+ }
940
+ if (notification.method === "rawResponseItem/completed" && params != null) {
941
+ handleRawItem(params);
942
+ return;
943
+ }
944
+ if (notification.method === "item/agentMessage/delta" && params != null && matchesActiveTurn({
945
+ params,
946
+ activeThreadId,
947
+ activeTurnId
948
+ }) && typeof params.itemId === "string" && typeof params.delta === "string") {
949
+ const text = (textByItem.get(params.itemId) ?? "") + params.delta;
950
+ textByItem.set(params.itemId, text);
951
+ emitStreamEvent({
952
+ type: "item.updated",
953
+ item: {
954
+ type: "agent_message",
955
+ id: params.itemId,
956
+ text
957
+ }
958
+ });
959
+ return;
960
+ }
961
+ if (notification.method === "item/reasoning/summaryTextDelta" && params != null && matchesActiveTurn({
962
+ params,
963
+ activeThreadId,
964
+ activeTurnId
965
+ }) && typeof params.itemId === "string" && typeof params.delta === "string") {
966
+ const text = (reasoningByItem.get(params.itemId) ?? "") + params.delta;
967
+ reasoningByItem.set(params.itemId, text);
968
+ emitStreamEvent({
969
+ type: "item.updated",
970
+ item: {
971
+ type: "reasoning",
972
+ id: params.itemId,
973
+ text
974
+ }
975
+ });
976
+ return;
977
+ }
978
+ if (notification.method === "thread/tokenUsage/updated" && params != null && matchesActiveTurn({
979
+ params,
980
+ activeThreadId,
981
+ activeTurnId
982
+ })) {
983
+ const tokenUsage = asRecord$2(params.tokenUsage);
984
+ const total = readUsageBreakdown(tokenUsage?.total);
985
+ const last = readUsageBreakdown(tokenUsage?.last);
986
+ if (total != null && last != null) {
987
+ const key = usageKey(total);
988
+ if (key !== lastCumulativeUsageKey) {
989
+ lastCumulativeUsageKey = key;
990
+ accumulatedUsage = addUsage({
991
+ total: accumulatedUsage,
992
+ increment: last
993
+ });
994
+ }
995
+ }
996
+ return;
997
+ }
998
+ if (notification.method === "error" && params != null) {
999
+ if (!matchesActiveTurn({
1000
+ params,
1001
+ activeThreadId,
1002
+ activeTurnId
1003
+ })) return;
1004
+ const error = asRecord$2(params.error);
1005
+ const message = typeof error?.message === "string" ? error.message : "Codex app-server reported an error.";
1006
+ if (params.willRetry === true) emitWarning({ message });
1007
+ else emitError({
1008
+ error: message,
1009
+ message: "codex turn failed"
1010
+ });
1011
+ return;
1012
+ }
1013
+ if ((notification.method === "warning" || notification.method === "configWarning" || notification.method === "deprecationNotice") && typeof params?.message === "string") {
1014
+ emitWarning({ message: params.message });
1015
+ return;
1016
+ }
1017
+ if (notification.method === "turn/completed" && params != null && matchesActiveTurn({
1018
+ params,
1019
+ activeThreadId,
1020
+ activeTurnId
1021
+ })) {
1022
+ if (settled) return;
1023
+ settled = true;
1024
+ const turn = asRecord$2(params.turn);
1025
+ const status = typeof turn?.status === "string" ? turn.status : "failed";
1026
+ const turnError = asRecord$2(turn?.error);
1027
+ if (status === "completed") for (const [callId, call] of nativeToolCalls) {
1028
+ if (call.resultEmitted) continue;
1029
+ emitNativeToolResult({
1030
+ callId,
1031
+ result: call.successConfirmed ? call.name === "apply_patch" ? "Patch applied." : "Image viewed." : "Codex did not report the tool result."
1032
+ });
1033
+ }
1034
+ emitStreamEvent({
1035
+ type: "turn.completed",
1036
+ usage: toLegacyUsage(accumulatedUsage)
1037
+ });
1038
+ resolveCompletion({
1039
+ status,
1040
+ ...typeof turnError?.message === "string" ? { error: turnError.message } : {}
1041
+ });
1042
+ }
1043
+ },
1044
+ waitForCompletion: () => completion
1045
+ };
1109
1046
  }
1110
- function normalizeNativeToolCall({
1111
- item
1112
- }) {
1113
- if (item.namespace != null || typeof item.call_id !== "string" || item.call_id.length === 0) {
1114
- return void 0;
1115
- }
1116
- if (item.type === "custom_tool_call" && item.name === "apply_patch" && typeof item.input === "string") {
1117
- return {
1118
- callId: item.call_id,
1119
- name: "apply_patch",
1120
- input: JSON.stringify(item.input)
1121
- };
1122
- }
1123
- if (item.type === "function_call" && item.name === "view_image" && typeof item.arguments === "string") {
1124
- return {
1125
- callId: item.call_id,
1126
- name: "view_image",
1127
- input: item.arguments
1128
- };
1129
- }
1130
- return void 0;
1047
+ function normalizeNativeToolCall({ item }) {
1048
+ if (item.namespace != null || typeof item.call_id !== "string" || item.call_id.length === 0) return;
1049
+ if (item.type === "custom_tool_call" && item.name === "apply_patch" && typeof item.input === "string") return {
1050
+ callId: item.call_id,
1051
+ name: "apply_patch",
1052
+ input: JSON.stringify(item.input)
1053
+ };
1054
+ if (item.type === "function_call" && item.name === "view_image" && typeof item.arguments === "string") return {
1055
+ callId: item.call_id,
1056
+ name: "view_image",
1057
+ input: item.arguments
1058
+ };
1131
1059
  }
1132
- function normalizeItem({
1133
- item,
1134
- textByItem,
1135
- reasoningByItem
1136
- }) {
1137
- const id = typeof item.id === "string" ? item.id : void 0;
1138
- if (item.type === "agentMessage") {
1139
- const text = typeof item.text === "string" ? item.text : "";
1140
- if (id != null) textByItem.set(id, text);
1141
- return { type: "agent_message", id, text };
1142
- }
1143
- if (item.type === "reasoning") {
1144
- const summary = stringArray(item.summary).join("\n\n");
1145
- const content = stringArray(item.content).join("\n\n");
1146
- const text = summary || reasoningByItem.get(id ?? "") || content;
1147
- if (id != null) reasoningByItem.set(id, text);
1148
- return { type: "reasoning", id, text };
1149
- }
1150
- if (item.type === "commandExecution") {
1151
- return {
1152
- type: "command_execution",
1153
- id,
1154
- command: typeof item.command === "string" ? item.command : "",
1155
- exit_code: typeof item.exitCode === "number" ? item.exitCode : void 0,
1156
- aggregated_output: typeof item.aggregatedOutput === "string" ? item.aggregatedOutput : void 0,
1157
- status: normalizeCommandStatus(item.status)
1158
- };
1159
- }
1160
- if (item.type === "mcpToolCall") {
1161
- const result = asRecord(item.result);
1162
- return {
1163
- type: "mcp_tool_call",
1164
- id,
1165
- server: typeof item.server === "string" ? item.server : void 0,
1166
- tool: typeof item.tool === "string" ? item.tool : void 0,
1167
- arguments: item.arguments,
1168
- result: result == null ? item.result : {
1169
- content: result.content,
1170
- structured_content: result.structuredContent
1171
- },
1172
- error: asRecord(item.error)
1173
- };
1174
- }
1175
- if (item.type === "dynamicToolCall") {
1176
- return { type: "dynamic_tool_call", id };
1177
- }
1178
- if (item.type === "webSearch") {
1179
- return {
1180
- type: "web_search",
1181
- id,
1182
- query: typeof item.query === "string" ? item.query : void 0,
1183
- action: asRecord(item.action) ?? void 0,
1184
- result: item.results
1185
- };
1186
- }
1187
- if (item.type === "fileChange") {
1188
- return {
1189
- type: "file_change",
1190
- id,
1191
- changes: Array.isArray(item.changes) ? item.changes.flatMap((change) => {
1192
- const value = asRecord(change);
1193
- const kind = asRecord(value?.kind)?.type;
1194
- return typeof value?.path === "string" && (kind === "add" || kind === "delete" || kind === "update") ? [{ path: value.path, kind }] : [];
1195
- }) : []
1196
- };
1197
- }
1198
- if (item.type === "plan") return { type: "todo_list", id };
1199
- return void 0;
1060
+ function normalizeItem({ item, textByItem, reasoningByItem }) {
1061
+ const id = typeof item.id === "string" ? item.id : void 0;
1062
+ if (item.type === "agentMessage") {
1063
+ const text = typeof item.text === "string" ? item.text : "";
1064
+ if (id != null) textByItem.set(id, text);
1065
+ return {
1066
+ type: "agent_message",
1067
+ id,
1068
+ text
1069
+ };
1070
+ }
1071
+ if (item.type === "reasoning") {
1072
+ const summary = stringArray(item.summary).join("\n\n");
1073
+ const content = stringArray(item.content).join("\n\n");
1074
+ const text = summary || reasoningByItem.get(id ?? "") || content;
1075
+ if (id != null) reasoningByItem.set(id, text);
1076
+ return {
1077
+ type: "reasoning",
1078
+ id,
1079
+ text
1080
+ };
1081
+ }
1082
+ if (item.type === "commandExecution") return {
1083
+ type: "command_execution",
1084
+ id,
1085
+ command: typeof item.command === "string" ? item.command : "",
1086
+ exit_code: typeof item.exitCode === "number" ? item.exitCode : void 0,
1087
+ aggregated_output: typeof item.aggregatedOutput === "string" ? item.aggregatedOutput : void 0,
1088
+ status: normalizeCommandStatus(item.status)
1089
+ };
1090
+ if (item.type === "mcpToolCall") {
1091
+ const result = asRecord$2(item.result);
1092
+ return {
1093
+ type: "mcp_tool_call",
1094
+ id,
1095
+ server: typeof item.server === "string" ? item.server : void 0,
1096
+ tool: typeof item.tool === "string" ? item.tool : void 0,
1097
+ arguments: item.arguments,
1098
+ result: result == null ? item.result : {
1099
+ content: result.content,
1100
+ structured_content: result.structuredContent
1101
+ },
1102
+ error: asRecord$2(item.error)
1103
+ };
1104
+ }
1105
+ if (item.type === "dynamicToolCall") return {
1106
+ type: "dynamic_tool_call",
1107
+ id
1108
+ };
1109
+ if (item.type === "webSearch") return {
1110
+ type: "web_search",
1111
+ id,
1112
+ query: typeof item.query === "string" ? item.query : void 0,
1113
+ action: asRecord$2(item.action) ?? void 0,
1114
+ result: item.results
1115
+ };
1116
+ if (item.type === "fileChange") return {
1117
+ type: "file_change",
1118
+ id,
1119
+ changes: Array.isArray(item.changes) ? item.changes.flatMap((change) => {
1120
+ const value = asRecord$2(change);
1121
+ const kind = asRecord$2(value?.kind)?.type;
1122
+ return typeof value?.path === "string" && (kind === "add" || kind === "delete" || kind === "update") ? [{
1123
+ path: value.path,
1124
+ kind
1125
+ }] : [];
1126
+ }) : []
1127
+ };
1128
+ if (item.type === "plan") return {
1129
+ type: "todo_list",
1130
+ id
1131
+ };
1200
1132
  }
1201
1133
  function normalizeCommandStatus(value) {
1202
- if (value === "completed") return "completed";
1203
- if (value === "inProgress") return "in_progress";
1204
- return "failed";
1134
+ if (value === "completed") return "completed";
1135
+ if (value === "inProgress") return "in_progress";
1136
+ return "failed";
1205
1137
  }
1206
- function matchesActiveTurn({
1207
- params,
1208
- activeThreadId,
1209
- activeTurnId
1210
- }) {
1211
- const nestedTurn = asRecord(params.turn);
1212
- const turnId = typeof params.turnId === "string" ? params.turnId : typeof nestedTurn?.id === "string" ? nestedTurn.id : void 0;
1213
- return activeThreadId != null && activeTurnId != null && params.threadId === activeThreadId && turnId === activeTurnId;
1138
+ function matchesActiveTurn({ params, activeThreadId, activeTurnId }) {
1139
+ const nestedTurn = asRecord$2(params.turn);
1140
+ const turnId = typeof params.turnId === "string" ? params.turnId : typeof nestedTurn?.id === "string" ? nestedTurn.id : void 0;
1141
+ return activeThreadId != null && activeTurnId != null && params.threadId === activeThreadId && turnId === activeTurnId;
1214
1142
  }
1215
- function asRecord(value) {
1216
- return value != null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1143
+ function asRecord$2(value) {
1144
+ return value != null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1217
1145
  }
1218
1146
  function stringArray(value) {
1219
- return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
1147
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
1220
1148
  }
1221
1149
  function emptyUsageBreakdown() {
1222
- return {
1223
- inputTokens: 0,
1224
- cachedInputTokens: 0,
1225
- cacheWriteInputTokens: 0,
1226
- outputTokens: 0
1227
- };
1150
+ return {
1151
+ inputTokens: 0,
1152
+ cachedInputTokens: 0,
1153
+ cacheWriteInputTokens: 0,
1154
+ outputTokens: 0
1155
+ };
1228
1156
  }
1229
1157
  function readUsageBreakdown(value) {
1230
- const usage = asRecord(value);
1231
- if (usage == null) return void 0;
1232
- return {
1233
- inputTokens: numberOrZero(usage.inputTokens),
1234
- cachedInputTokens: numberOrZero(usage.cachedInputTokens),
1235
- cacheWriteInputTokens: numberOrZero(usage.cacheWriteInputTokens),
1236
- outputTokens: numberOrZero(usage.outputTokens)
1237
- };
1158
+ const usage = asRecord$2(value);
1159
+ if (usage == null) return void 0;
1160
+ return {
1161
+ inputTokens: numberOrZero(usage.inputTokens),
1162
+ cachedInputTokens: numberOrZero(usage.cachedInputTokens),
1163
+ cacheWriteInputTokens: numberOrZero(usage.cacheWriteInputTokens),
1164
+ outputTokens: numberOrZero(usage.outputTokens)
1165
+ };
1238
1166
  }
1239
- function addUsage({
1240
- total,
1241
- increment
1242
- }) {
1243
- return {
1244
- inputTokens: total.inputTokens + increment.inputTokens,
1245
- cachedInputTokens: total.cachedInputTokens + increment.cachedInputTokens,
1246
- cacheWriteInputTokens: total.cacheWriteInputTokens + increment.cacheWriteInputTokens,
1247
- outputTokens: total.outputTokens + increment.outputTokens
1248
- };
1167
+ function addUsage({ total, increment }) {
1168
+ return {
1169
+ inputTokens: total.inputTokens + increment.inputTokens,
1170
+ cachedInputTokens: total.cachedInputTokens + increment.cachedInputTokens,
1171
+ cacheWriteInputTokens: total.cacheWriteInputTokens + increment.cacheWriteInputTokens,
1172
+ outputTokens: total.outputTokens + increment.outputTokens
1173
+ };
1249
1174
  }
1250
1175
  function usageKey(usage) {
1251
- return [
1252
- usage.inputTokens,
1253
- usage.cachedInputTokens,
1254
- usage.cacheWriteInputTokens,
1255
- usage.outputTokens
1256
- ].join(":");
1176
+ return [
1177
+ usage.inputTokens,
1178
+ usage.cachedInputTokens,
1179
+ usage.cacheWriteInputTokens,
1180
+ usage.outputTokens
1181
+ ].join(":");
1257
1182
  }
1258
1183
  function toLegacyUsage(usage) {
1259
- return {
1260
- input_tokens: usage.inputTokens,
1261
- cached_input_tokens: usage.cachedInputTokens,
1262
- cache_write_input_tokens: usage.cacheWriteInputTokens,
1263
- output_tokens: usage.outputTokens
1264
- };
1184
+ return {
1185
+ input_tokens: usage.inputTokens,
1186
+ cached_input_tokens: usage.cachedInputTokens,
1187
+ cache_write_input_tokens: usage.cacheWriteInputTokens,
1188
+ output_tokens: usage.outputTokens
1189
+ };
1265
1190
  }
1266
1191
  function numberOrZero(value) {
1267
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
1192
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1268
1193
  }
1269
-
1270
- // src/bridge/codex-app-server-driver.ts
1271
- import { isDeepStrictEqual } from "util";
1272
-
1273
- // src/bridge/codex-tool-filtering-hook.ts
1274
- import { createHash } from "crypto";
1275
- import path from "path";
1276
- var MATCHER = "^apply_patch$";
1277
- var TIMEOUT_SECONDS = 10;
1278
- var COMMAND = `printf '%s\\n' "Tool 'apply_patch' is inactive due to the HarnessAgent tool filtering policy." >&2; exit 2`;
1279
- function createTrustedApplyPatchHook({
1280
- codexConfig
1281
- }) {
1282
- const threadConfig = { ...codexConfig };
1283
- const hooks = { ...asRecord2(threadConfig.hooks) };
1284
- delete threadConfig.hooks;
1285
- for (const [key2, value] of Object.entries(threadConfig)) {
1286
- if (!key2.startsWith("hooks.")) continue;
1287
- const name = key2.slice("hooks.".length);
1288
- if (name.startsWith("state.")) {
1289
- hooks.state = {
1290
- ...asRecord2(hooks.state),
1291
- [name.slice("state.".length)]: value
1292
- };
1293
- } else {
1294
- hooks[name] = value;
1295
- }
1296
- delete threadConfig[key2];
1297
- }
1298
- const userGroups = hooks.PreToolUse ?? [];
1299
- if (!Array.isArray(userGroups)) {
1300
- throw new Error("Codex config hooks.PreToolUse must be an array.");
1301
- }
1302
- const handler = {
1303
- type: "command",
1304
- command: COMMAND,
1305
- timeout: TIMEOUT_SECONDS,
1306
- async: false
1307
- };
1308
- const hash = `sha256:${createHash("sha256").update(
1309
- JSON.stringify(
1310
- canonicalize({
1311
- event_name: "pre_tool_use",
1312
- matcher: MATCHER,
1313
- hooks: [handler]
1314
- })
1315
- )
1316
- ).digest("hex")}`;
1317
- const sourcePath = process.platform === "win32" ? path.win32.resolve("C:\\", "<session-flags>/config.toml") : path.posix.resolve("/", "<session-flags>/config.toml");
1318
- const key = `${sourcePath}:pre_tool_use:${userGroups.length}:0`;
1319
- hooks.PreToolUse = [...userGroups, { matcher: MATCHER, hooks: [handler] }];
1320
- hooks.state = {
1321
- ...asRecord2(hooks.state),
1322
- [key]: { enabled: true, trusted_hash: hash }
1323
- };
1324
- return {
1325
- cliOverrides: [`hooks=${toTomlInline(hooks)}`, "features.hooks=true"],
1326
- threadConfig,
1327
- key,
1328
- hash,
1329
- command: COMMAND
1330
- };
1194
+ //#endregion
1195
+ //#region src/bridge/codex-tool-filtering-hook.ts
1196
+ const MATCHER = "^apply_patch$";
1197
+ const TIMEOUT_SECONDS = 10;
1198
+ const COMMAND = "printf '%s\\n' \"Tool 'apply_patch' is inactive due to the HarnessAgent tool filtering policy.\" >&2; exit 2";
1199
+ function createTrustedApplyPatchHook({ codexConfig }) {
1200
+ const threadConfig = { ...codexConfig };
1201
+ const hooks = { ...asRecord$1(threadConfig.hooks) };
1202
+ delete threadConfig.hooks;
1203
+ for (const [key, value] of Object.entries(threadConfig)) {
1204
+ if (!key.startsWith("hooks.")) continue;
1205
+ const name = key.slice(6);
1206
+ if (name.startsWith("state.")) hooks.state = {
1207
+ ...asRecord$1(hooks.state),
1208
+ [name.slice(6)]: value
1209
+ };
1210
+ else hooks[name] = value;
1211
+ delete threadConfig[key];
1212
+ }
1213
+ const userGroups = hooks.PreToolUse ?? [];
1214
+ if (!Array.isArray(userGroups)) throw new Error("Codex config hooks.PreToolUse must be an array.");
1215
+ const handler = {
1216
+ type: "command",
1217
+ command: COMMAND,
1218
+ timeout: TIMEOUT_SECONDS,
1219
+ async: false
1220
+ };
1221
+ const hash = `sha256:${createHash("sha256").update(JSON.stringify(canonicalize({
1222
+ event_name: "pre_tool_use",
1223
+ matcher: MATCHER,
1224
+ hooks: [handler]
1225
+ }))).digest("hex")}`;
1226
+ const key = `${process.platform === "win32" ? path.win32.resolve("C:\\", "<session-flags>/config.toml") : path.posix.resolve("/", "<session-flags>/config.toml")}:pre_tool_use:${userGroups.length}:0`;
1227
+ hooks.PreToolUse = [...userGroups, {
1228
+ matcher: MATCHER,
1229
+ hooks: [handler]
1230
+ }];
1231
+ hooks.state = {
1232
+ ...asRecord$1(hooks.state),
1233
+ [key]: {
1234
+ enabled: true,
1235
+ trusted_hash: hash
1236
+ }
1237
+ };
1238
+ return {
1239
+ cliOverrides: [`hooks=${toTomlInline(hooks)}`, "features.hooks=true"],
1240
+ threadConfig,
1241
+ key,
1242
+ hash,
1243
+ command: COMMAND
1244
+ };
1331
1245
  }
1332
- function assertTrustedApplyPatchHook({
1333
- response,
1334
- hook
1335
- }) {
1336
- const data = asRecord2(response)?.data;
1337
- const entry = Array.isArray(data) && data.length === 1 ? data[0] : null;
1338
- const record = asRecord2(entry);
1339
- const matches = Array.isArray(record?.hooks) ? record.hooks.filter((item) => asRecord2(item)?.key === hook.key) : [];
1340
- const match = matches.length === 1 ? asRecord2(matches[0]) : void 0;
1341
- if (!match || match.eventName !== "preToolUse" || match.handlerType !== "command" || match.command !== hook.command || match.matcher !== MATCHER || match.currentHash !== hook.hash || match.enabled !== true || match.trustStatus !== "trusted" || Array.isArray(record?.errors) && record.errors.length > 0) {
1342
- throw new Error(
1343
- "Codex app-server did not load the trusted apply_patch filtering hook."
1344
- );
1345
- }
1246
+ function assertTrustedApplyPatchHook({ response, hook }) {
1247
+ const data = asRecord$1(response)?.data;
1248
+ const record = asRecord$1(Array.isArray(data) && data.length === 1 ? data[0] : null);
1249
+ const matches = Array.isArray(record?.hooks) ? record.hooks.filter((item) => asRecord$1(item)?.key === hook.key) : [];
1250
+ const match = matches.length === 1 ? asRecord$1(matches[0]) : void 0;
1251
+ if (!match || match.eventName !== "preToolUse" || match.handlerType !== "command" || match.command !== hook.command || match.matcher !== MATCHER || match.currentHash !== hook.hash || match.enabled !== true || match.trustStatus !== "trusted" || Array.isArray(record?.errors) && record.errors.length > 0) throw new Error("Codex app-server did not load the trusted apply_patch filtering hook.");
1346
1252
  }
1347
1253
  function canonicalize(value) {
1348
- if (Array.isArray(value)) return value.map(canonicalize);
1349
- if (value != null && typeof value === "object") {
1350
- return Object.fromEntries(
1351
- Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, canonicalize(item)])
1352
- );
1353
- }
1354
- return value;
1254
+ if (Array.isArray(value)) return value.map(canonicalize);
1255
+ if (value != null && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, canonicalize(item)]));
1256
+ return value;
1355
1257
  }
1356
1258
  function toTomlInline(value) {
1357
- if (typeof value === "string") return JSON.stringify(value);
1358
- if (typeof value === "boolean") return String(value);
1359
- if (typeof value === "number" && Number.isFinite(value)) {
1360
- return String(value);
1361
- }
1362
- if (Array.isArray(value)) {
1363
- return `[${value.map(toTomlInline).join(",")}]`;
1364
- }
1365
- if (value != null && typeof value === "object") {
1366
- return `{${Object.entries(value).filter(([, item]) => item !== void 0).map(([key, item]) => `${JSON.stringify(key)}=${toTomlInline(item)}`).join(",")}}`;
1367
- }
1368
- throw new Error("Codex hook configuration contains an unsupported value.");
1259
+ if (typeof value === "string") return JSON.stringify(value);
1260
+ if (typeof value === "boolean") return String(value);
1261
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
1262
+ if (Array.isArray(value)) return `[${value.map(toTomlInline).join(",")}]`;
1263
+ if (value != null && typeof value === "object") return `{${Object.entries(value).filter(([, item]) => item !== void 0).map(([key, item]) => `${JSON.stringify(key)}=${toTomlInline(item)}`).join(",")}}`;
1264
+ throw new Error("Codex hook configuration contains an unsupported value.");
1369
1265
  }
1370
- function asRecord2(value) {
1371
- return value != null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1266
+ function asRecord$1(value) {
1267
+ return value != null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1372
1268
  }
1373
-
1374
- // src/bridge/codex-app-server-driver.ts
1375
- var CODEX_CLI_PATH = fileURLToPath(
1376
- new URL("./node_modules/@openai/codex/bin/codex.js", import.meta.url)
1377
- );
1269
+ //#endregion
1270
+ //#region src/bridge/codex-app-server-driver.ts
1271
+ const CODEX_CLI_PATH = fileURLToPath(new URL("./node_modules/@openai/codex/bin/codex.js", import.meta.url));
1378
1272
  function createCodexAppServerRuntime() {
1379
- let client;
1380
- let loadedThreadId;
1381
- let loadedConfig;
1382
- let activeTurn;
1383
- const close = async () => {
1384
- const previous = client;
1385
- client = void 0;
1386
- loadedThreadId = void 0;
1387
- loadedConfig = void 0;
1388
- await previous?.close();
1389
- };
1390
- const runTurn2 = async (options) => {
1391
- const { start, turn, emit, workdir: workdir2, threadId, codexModel, codexConfig } = options;
1392
- if (activeTurn != null) throw new Error("A Codex turn is already active.");
1393
- const toolPolicy = resolveCodexBuiltinToolPolicy({
1394
- builtinToolFiltering: start.builtinToolFiltering,
1395
- webSearch: start.webSearch
1396
- });
1397
- const trustedHook = toolPolicy.denyApplyPatchWithHook ? createTrustedApplyPatchHook({ codexConfig }) : void 0;
1398
- const nextConfig = {
1399
- ...trustedHook?.threadConfig ?? codexConfig,
1400
- web_search: toolPolicy.webSearchMode,
1401
- ...toolPolicy.disabled.bash || toolPolicy.disabled.view_image ? {
1402
- features: {
1403
- ...asRecord3(codexConfig.features),
1404
- ...toolPolicy.disabled.bash ? { shell_tool: false } : {},
1405
- ...toolPolicy.disabled.view_image ? { view_image: false } : {}
1406
- }
1407
- } : {}
1408
- };
1409
- const processConfig = {
1410
- thread: nextConfig,
1411
- cliOverrides: trustedHook?.cliOverrides ?? []
1412
- };
1413
- if (client != null && !isDeepStrictEqual(loadedConfig, processConfig)) {
1414
- await close();
1415
- }
1416
- const handler = createAppServerEventHandler({
1417
- stepTracker: options.stepTracker,
1418
- emitStreamEvent: options.emitStreamEvent,
1419
- emitWarning: turn.emitWarning,
1420
- emitError: turn.emitError
1421
- });
1422
- const dynamicTools = createDynamicTools(start.tools ?? []);
1423
- let rejectProtocolFailure = () => {
1424
- };
1425
- const protocolFailure = new Promise((_, reject) => {
1426
- rejectProtocolFailure = reject;
1427
- });
1428
- const currentTurn = {
1429
- threadId,
1430
- turnId: void 0,
1431
- handler,
1432
- dynamicTools,
1433
- options,
1434
- fail: rejectProtocolFailure
1435
- };
1436
- activeTurn = currentTurn;
1437
- let initialized = false;
1438
- if (client == null) {
1439
- const created = new CodexAppServerClient({
1440
- executable: process.execPath,
1441
- args: createCodexAppServerArgs({
1442
- cliOverrides: trustedHook?.cliOverrides
1443
- }),
1444
- cwd: workdir2,
1445
- env: process.env,
1446
- onNotification: (notification) => {
1447
- if (client !== created || activeTurn == null) return;
1448
- const params = asRecord3(notification.params);
1449
- if (notification.method === "turn/started" && params?.threadId === activeTurn.threadId && activeTurn.turnId == null) {
1450
- const turnId = asRecord3(params?.turn)?.id;
1451
- if (typeof turnId === "string") activeTurn.turnId = turnId;
1452
- }
1453
- activeTurn.handler.handle(notification);
1454
- },
1455
- onRequest: (request) => {
1456
- const active = activeTurn;
1457
- const params = asRecord3(request.params);
1458
- if (client !== created || active == null || params?.threadId !== active.threadId || params?.turnId !== active.turnId || active.turnId == null) {
1459
- return Promise.reject(
1460
- new Error(
1461
- "Codex app-server requested a tool for an inactive turn."
1462
- )
1463
- );
1464
- }
1465
- return handleAppServerRequest({
1466
- request,
1467
- dynamicTools: active.dynamicTools,
1468
- emit: active.options.emit,
1469
- requestToolResult: active.options.turn.requestToolResult
1470
- }).catch((error) => {
1471
- active.fail(error);
1472
- throw error;
1473
- });
1474
- },
1475
- onStderr: (text) => {
1476
- const message = text.trim();
1477
- if (message.length > 0) {
1478
- activeTurn?.options.turn.bridgeLog({
1479
- level: "debug",
1480
- subsystem: "codex.app-server.stderr",
1481
- message
1482
- });
1483
- }
1484
- }
1485
- });
1486
- client = created;
1487
- loadedConfig = processConfig;
1488
- initialized = true;
1489
- }
1490
- const runningClient = client;
1491
- const clientFailure = runningClient.waitUntilFailure();
1492
- const exitFailure = runningClient.waitUntilExit().then(({ code, signal }) => {
1493
- throw new Error(
1494
- `Codex app-server exited before the turn completed (code ${code ?? "null"}, signal ${signal ?? "null"}).`
1495
- );
1496
- });
1497
- let removeAbortListener = () => {
1498
- };
1499
- const abortFailure = new Promise((_, reject) => {
1500
- const onAbort = () => {
1501
- if (currentTurn.threadId != null && currentTurn.turnId != null) {
1502
- void runningClient.request({
1503
- method: "turn/interrupt",
1504
- params: {
1505
- threadId: currentTurn.threadId,
1506
- turnId: currentTurn.turnId
1507
- }
1508
- }).catch(() => {
1509
- });
1510
- }
1511
- reject(
1512
- turn.abortSignal.reason ?? new DOMException("Aborted", "AbortError")
1513
- );
1514
- };
1515
- if (turn.abortSignal.aborted) onAbort();
1516
- else {
1517
- turn.abortSignal.addEventListener("abort", onAbort, { once: true });
1518
- removeAbortListener = () => turn.abortSignal.removeEventListener("abort", onAbort);
1519
- }
1520
- });
1521
- const raceWithProcess = ({ operation }) => Promise.race([
1522
- operation,
1523
- abortFailure,
1524
- protocolFailure,
1525
- clientFailure,
1526
- exitFailure
1527
- ]);
1528
- let stopSteering = () => {
1529
- };
1530
- const steeringStopped = new Promise((resolve) => {
1531
- stopSteering = resolve;
1532
- });
1533
- const userMessageLoop = async () => {
1534
- for await (const message of turn.experimental_userMessages) {
1535
- try {
1536
- const response = await Promise.race([
1537
- raceWithProcess({
1538
- operation: runningClient.request({
1539
- method: "turn/steer",
1540
- params: {
1541
- threadId: currentTurn.threadId,
1542
- expectedTurnId: currentTurn.turnId,
1543
- clientUserMessageId: message.messageId,
1544
- input: [
1545
- { type: "text", text: message.text, text_elements: [] }
1546
- ]
1547
- }
1548
- })
1549
- }),
1550
- steeringStopped.then(() => {
1551
- throw new Error(
1552
- "The Codex turn ended before accepting the user message."
1553
- );
1554
- })
1555
- ]);
1556
- if (asRecord3(response)?.turnId !== currentTurn.turnId) {
1557
- throw new Error(
1558
- "Codex app-server turn/steer returned an unexpected turn ID."
1559
- );
1560
- }
1561
- message.accept();
1562
- } catch (error) {
1563
- message.reject(error);
1564
- }
1565
- }
1566
- };
1567
- let keepClient = false;
1568
- try {
1569
- if (initialized) {
1570
- await raceWithProcess({
1571
- operation: runningClient.initialize({
1572
- clientName: "ai-sdk-harness-codex",
1573
- clientVersion: "1"
1574
- })
1575
- });
1576
- }
1577
- if (start.restartThread && loadedThreadId != null) {
1578
- await raceWithProcess({
1579
- operation: runningClient.request({
1580
- method: "thread/unsubscribe",
1581
- params: { threadId: loadedThreadId }
1582
- })
1583
- });
1584
- loadedThreadId = void 0;
1585
- }
1586
- if (trustedHook != null) {
1587
- const hooks = await raceWithProcess({
1588
- operation: runningClient.request({
1589
- method: "hooks/list",
1590
- params: {}
1591
- })
1592
- });
1593
- assertTrustedApplyPatchHook({ response: hooks, hook: trustedHook });
1594
- }
1595
- const threadMethod = threadId == null ? "thread/start" : "thread/resume";
1596
- if (loadedThreadId !== threadId || threadId == null) {
1597
- const threadResponse = await raceWithProcess({
1598
- operation: runningClient.request({
1599
- method: threadMethod,
1600
- params: threadId == null ? {
1601
- ...createThreadParams({
1602
- start,
1603
- workdir: workdir2,
1604
- codexModel,
1605
- codexConfig: nextConfig
1606
- }),
1607
- dynamicTools: dynamicTools.specs,
1608
- experimentalRawEvents: true
1609
- } : {
1610
- threadId,
1611
- ...createThreadParams({
1612
- start,
1613
- workdir: workdir2,
1614
- codexModel,
1615
- codexConfig: nextConfig
1616
- }),
1617
- excludeTurns: true
1618
- }
1619
- })
1620
- });
1621
- assertCodexThreadPermissions({
1622
- response: threadResponse,
1623
- method: threadMethod
1624
- });
1625
- currentTurn.threadId = readNestedString({
1626
- value: threadResponse,
1627
- path: ["thread", "id"],
1628
- method: threadMethod
1629
- });
1630
- loadedThreadId = currentTurn.threadId;
1631
- }
1632
- handler.announceThread(currentTurn.threadId);
1633
- emit({ type: "stream-start" });
1634
- const turnResponse = await raceWithProcess({
1635
- operation: runningClient.request({
1636
- method: "turn/start",
1637
- params: createTurnParams({
1638
- threadId: currentTurn.threadId,
1639
- start,
1640
- codexModel,
1641
- disableEnvironments: toolPolicy.disableEnvironments
1642
- })
1643
- })
1644
- });
1645
- currentTurn.turnId = readNestedString({
1646
- value: turnResponse,
1647
- path: ["turn", "id"],
1648
- method: "turn/start"
1649
- });
1650
- handler.setTurnId(currentTurn.turnId);
1651
- void userMessageLoop();
1652
- const result = await raceWithProcess({
1653
- operation: handler.waitForCompletion()
1654
- });
1655
- keepClient = true;
1656
- assertSuccessfulTurn(result);
1657
- } catch (error) {
1658
- if (turn.abortSignal.aborted && currentTurn.turnId != null) {
1659
- let timer;
1660
- try {
1661
- keepClient = await Promise.race([
1662
- handler.waitForCompletion().then(() => true),
1663
- new Promise((resolve) => {
1664
- timer = setTimeout(() => resolve(false), 5e3);
1665
- }),
1666
- clientFailure.then(
1667
- () => false,
1668
- () => false
1669
- )
1670
- ]);
1671
- } finally {
1672
- clearTimeout(timer);
1673
- }
1674
- }
1675
- throw error;
1676
- } finally {
1677
- turn.experimental_userMessages.close();
1678
- stopSteering();
1679
- removeAbortListener();
1680
- if (activeTurn === currentTurn) activeTurn = void 0;
1681
- if (!keepClient) await close();
1682
- }
1683
- };
1684
- return { runTurn: runTurn2, close };
1273
+ let client;
1274
+ let loadedThreadId;
1275
+ let loadedConfig;
1276
+ let activeTurn;
1277
+ const close = async () => {
1278
+ const previous = client;
1279
+ client = void 0;
1280
+ loadedThreadId = void 0;
1281
+ loadedConfig = void 0;
1282
+ await previous?.close();
1283
+ };
1284
+ const runTurn = async (options) => {
1285
+ const { start, turn, emit, workdir, threadId, codexModel, codexConfig } = options;
1286
+ if (activeTurn != null) throw new Error("A Codex turn is already active.");
1287
+ const toolPolicy = resolveCodexBuiltinToolPolicy({
1288
+ builtinToolFiltering: start.builtinToolFiltering,
1289
+ webSearch: start.webSearch
1290
+ });
1291
+ const trustedHook = toolPolicy.denyApplyPatchWithHook ? createTrustedApplyPatchHook({ codexConfig }) : void 0;
1292
+ const nextConfig = {
1293
+ ...trustedHook?.threadConfig ?? codexConfig,
1294
+ web_search: toolPolicy.webSearchMode,
1295
+ ...toolPolicy.disabled.bash || toolPolicy.disabled.view_image ? { features: {
1296
+ ...asRecord(codexConfig.features),
1297
+ ...toolPolicy.disabled.bash ? { shell_tool: false } : {},
1298
+ ...toolPolicy.disabled.view_image ? { view_image: false } : {}
1299
+ } } : {}
1300
+ };
1301
+ const processConfig = {
1302
+ thread: nextConfig,
1303
+ cliOverrides: trustedHook?.cliOverrides ?? []
1304
+ };
1305
+ if (client != null && !isDeepStrictEqual(loadedConfig, processConfig)) await close();
1306
+ const handler = createAppServerEventHandler({
1307
+ stepTracker: options.stepTracker,
1308
+ emitStreamEvent: options.emitStreamEvent,
1309
+ emitWarning: turn.emitWarning,
1310
+ emitError: turn.emitError
1311
+ });
1312
+ const dynamicTools = createDynamicTools(start.tools ?? []);
1313
+ let rejectProtocolFailure = () => {};
1314
+ const protocolFailure = new Promise((_, reject) => {
1315
+ rejectProtocolFailure = reject;
1316
+ });
1317
+ const currentTurn = {
1318
+ threadId,
1319
+ turnId: void 0,
1320
+ handler,
1321
+ dynamicTools,
1322
+ options,
1323
+ fail: rejectProtocolFailure
1324
+ };
1325
+ activeTurn = currentTurn;
1326
+ let initialized = false;
1327
+ if (client == null) {
1328
+ const created = new CodexAppServerClient({
1329
+ executable: process.execPath,
1330
+ args: createCodexAppServerArgs({ cliOverrides: trustedHook?.cliOverrides }),
1331
+ cwd: workdir,
1332
+ env: process.env,
1333
+ onNotification: (notification) => {
1334
+ if (client !== created || activeTurn == null) return;
1335
+ const params = asRecord(notification.params);
1336
+ if (notification.method === "turn/started" && params?.threadId === activeTurn.threadId && activeTurn.turnId == null) {
1337
+ const turnId = asRecord(params?.turn)?.id;
1338
+ if (typeof turnId === "string") activeTurn.turnId = turnId;
1339
+ }
1340
+ activeTurn.handler.handle(notification);
1341
+ },
1342
+ onRequest: (request) => {
1343
+ const active = activeTurn;
1344
+ const params = asRecord(request.params);
1345
+ if (client !== created || active == null || params?.threadId !== active.threadId || params?.turnId !== active.turnId || active.turnId == null) return Promise.reject(/* @__PURE__ */ new Error("Codex app-server requested a tool for an inactive turn."));
1346
+ return handleAppServerRequest({
1347
+ request,
1348
+ dynamicTools: active.dynamicTools,
1349
+ emit: active.options.emit,
1350
+ requestToolResult: active.options.turn.requestToolResult
1351
+ }).catch((error) => {
1352
+ active.fail(error);
1353
+ throw error;
1354
+ });
1355
+ },
1356
+ onStderr: (text) => {
1357
+ const message = text.trim();
1358
+ if (message.length > 0) activeTurn?.options.turn.bridgeLog({
1359
+ level: "debug",
1360
+ subsystem: "codex.app-server.stderr",
1361
+ message
1362
+ });
1363
+ }
1364
+ });
1365
+ client = created;
1366
+ loadedConfig = processConfig;
1367
+ initialized = true;
1368
+ }
1369
+ const runningClient = client;
1370
+ const clientFailure = runningClient.waitUntilFailure();
1371
+ const exitFailure = runningClient.waitUntilExit().then(({ code, signal }) => {
1372
+ throw new Error(`Codex app-server exited before the turn completed (code ${code ?? "null"}, signal ${signal ?? "null"}).`);
1373
+ });
1374
+ let removeAbortListener = () => {};
1375
+ const abortFailure = new Promise((_, reject) => {
1376
+ const onAbort = () => {
1377
+ if (currentTurn.threadId != null && currentTurn.turnId != null) runningClient.request({
1378
+ method: "turn/interrupt",
1379
+ params: {
1380
+ threadId: currentTurn.threadId,
1381
+ turnId: currentTurn.turnId
1382
+ }
1383
+ }).catch(() => {});
1384
+ reject(turn.abortSignal.reason ?? new DOMException("Aborted", "AbortError"));
1385
+ };
1386
+ if (turn.abortSignal.aborted) onAbort();
1387
+ else {
1388
+ turn.abortSignal.addEventListener("abort", onAbort, { once: true });
1389
+ removeAbortListener = () => turn.abortSignal.removeEventListener("abort", onAbort);
1390
+ }
1391
+ });
1392
+ const raceWithProcess = ({ operation }) => Promise.race([
1393
+ operation,
1394
+ abortFailure,
1395
+ protocolFailure,
1396
+ clientFailure,
1397
+ exitFailure
1398
+ ]);
1399
+ let stopSteering = () => {};
1400
+ const steeringStopped = new Promise((resolve) => {
1401
+ stopSteering = resolve;
1402
+ });
1403
+ const userMessageLoop = async () => {
1404
+ for await (const message of turn.experimental_userMessages) try {
1405
+ if (asRecord(await Promise.race([raceWithProcess({ operation: runningClient.request({
1406
+ method: "turn/steer",
1407
+ params: {
1408
+ threadId: currentTurn.threadId,
1409
+ expectedTurnId: currentTurn.turnId,
1410
+ clientUserMessageId: message.messageId,
1411
+ input: [{
1412
+ type: "text",
1413
+ text: message.text,
1414
+ text_elements: []
1415
+ }]
1416
+ }
1417
+ }) }), steeringStopped.then(() => {
1418
+ throw new Error("The Codex turn ended before accepting the user message.");
1419
+ })]))?.turnId !== currentTurn.turnId) throw new Error("Codex app-server turn/steer returned an unexpected turn ID.");
1420
+ message.accept();
1421
+ } catch (error) {
1422
+ message.reject(error);
1423
+ }
1424
+ };
1425
+ let keepClient = false;
1426
+ try {
1427
+ if (initialized) await raceWithProcess({ operation: runningClient.initialize({
1428
+ clientName: "ai-sdk-harness-codex",
1429
+ clientVersion: "1"
1430
+ }) });
1431
+ if (start.restartThread && loadedThreadId != null) {
1432
+ await raceWithProcess({ operation: runningClient.request({
1433
+ method: "thread/unsubscribe",
1434
+ params: { threadId: loadedThreadId }
1435
+ }) });
1436
+ loadedThreadId = void 0;
1437
+ }
1438
+ if (trustedHook != null) assertTrustedApplyPatchHook({
1439
+ response: await raceWithProcess({ operation: runningClient.request({
1440
+ method: "hooks/list",
1441
+ params: {}
1442
+ }) }),
1443
+ hook: trustedHook
1444
+ });
1445
+ const threadMethod = threadId == null ? "thread/start" : "thread/resume";
1446
+ if (loadedThreadId !== threadId || threadId == null) {
1447
+ const threadResponse = await raceWithProcess({ operation: runningClient.request({
1448
+ method: threadMethod,
1449
+ params: threadId == null ? {
1450
+ ...createThreadParams({
1451
+ start,
1452
+ workdir,
1453
+ codexModel,
1454
+ codexConfig: nextConfig
1455
+ }),
1456
+ dynamicTools: dynamicTools.specs,
1457
+ experimentalRawEvents: true
1458
+ } : {
1459
+ threadId,
1460
+ ...createThreadParams({
1461
+ start,
1462
+ workdir,
1463
+ codexModel,
1464
+ codexConfig: nextConfig
1465
+ }),
1466
+ excludeTurns: true
1467
+ }
1468
+ }) });
1469
+ assertCodexThreadPermissions({
1470
+ response: threadResponse,
1471
+ method: threadMethod
1472
+ });
1473
+ currentTurn.threadId = readNestedString({
1474
+ value: threadResponse,
1475
+ path: ["thread", "id"],
1476
+ method: threadMethod
1477
+ });
1478
+ loadedThreadId = currentTurn.threadId;
1479
+ }
1480
+ handler.announceThread(currentTurn.threadId);
1481
+ emit({ type: "stream-start" });
1482
+ currentTurn.turnId = readNestedString({
1483
+ value: await raceWithProcess({ operation: runningClient.request({
1484
+ method: "turn/start",
1485
+ params: createTurnParams({
1486
+ threadId: currentTurn.threadId,
1487
+ start,
1488
+ codexModel,
1489
+ disableEnvironments: toolPolicy.disableEnvironments
1490
+ })
1491
+ }) }),
1492
+ path: ["turn", "id"],
1493
+ method: "turn/start"
1494
+ });
1495
+ handler.setTurnId(currentTurn.turnId);
1496
+ userMessageLoop();
1497
+ const result = await raceWithProcess({ operation: handler.waitForCompletion() });
1498
+ keepClient = true;
1499
+ assertSuccessfulTurn(result);
1500
+ } catch (error) {
1501
+ if (turn.abortSignal.aborted && currentTurn.turnId != null) {
1502
+ let timer;
1503
+ try {
1504
+ keepClient = await Promise.race([
1505
+ handler.waitForCompletion().then(() => true),
1506
+ new Promise((resolve) => {
1507
+ timer = setTimeout(() => resolve(false), 5e3);
1508
+ }),
1509
+ clientFailure.then(() => false, () => false)
1510
+ ]);
1511
+ } finally {
1512
+ clearTimeout(timer);
1513
+ }
1514
+ }
1515
+ throw error;
1516
+ } finally {
1517
+ turn.experimental_userMessages.close();
1518
+ stopSteering();
1519
+ removeAbortListener();
1520
+ if (activeTurn === currentTurn) activeTurn = void 0;
1521
+ if (!keepClient) await close();
1522
+ }
1523
+ };
1524
+ return {
1525
+ runTurn,
1526
+ close
1527
+ };
1685
1528
  }
1686
- function createCodexAppServerArgs({
1687
- cliOverrides = []
1688
- } = {}) {
1689
- return [
1690
- CODEX_CLI_PATH,
1691
- "--config",
1692
- 'sandbox_mode="danger-full-access"',
1693
- "--config",
1694
- 'approval_policy="never"',
1695
- ...cliOverrides.flatMap((override) => ["--config", override]),
1696
- "app-server",
1697
- "--stdio"
1698
- ];
1529
+ function createCodexAppServerArgs({ cliOverrides = [] } = {}) {
1530
+ return [
1531
+ CODEX_CLI_PATH,
1532
+ "--config",
1533
+ "sandbox_mode=\"danger-full-access\"",
1534
+ "--config",
1535
+ "approval_policy=\"never\"",
1536
+ ...cliOverrides.flatMap((override) => ["--config", override]),
1537
+ "app-server",
1538
+ "--stdio"
1539
+ ];
1699
1540
  }
1700
- function createThreadParams({
1701
- start,
1702
- workdir: workdir2,
1703
- codexModel,
1704
- codexConfig
1705
- }) {
1706
- return {
1707
- ...codexModel == null ? {} : { model: codexModel },
1708
- cwd: workdir2,
1709
- approvalPolicy: "never",
1710
- sandbox: "danger-full-access",
1711
- config: {
1712
- ...codexConfig,
1713
- web_search: codexConfig.web_search ?? (start.webSearch ? "live" : "disabled")
1714
- }
1715
- };
1541
+ function createThreadParams({ start, workdir, codexModel, codexConfig }) {
1542
+ return {
1543
+ ...codexModel == null ? {} : { model: codexModel },
1544
+ cwd: workdir,
1545
+ approvalPolicy: "never",
1546
+ sandbox: "danger-full-access",
1547
+ config: {
1548
+ ...codexConfig,
1549
+ web_search: codexConfig.web_search ?? (start.webSearch ? "live" : "disabled")
1550
+ }
1551
+ };
1716
1552
  }
1717
- function createTurnParams({
1718
- threadId,
1719
- start,
1720
- codexModel,
1721
- disableEnvironments = false
1722
- }) {
1723
- return {
1724
- threadId,
1725
- ...disableEnvironments ? { environments: [] } : {},
1726
- input: [{ type: "text", text: start.prompt, text_elements: [] }],
1727
- approvalPolicy: "never",
1728
- sandboxPolicy: {
1729
- type: "externalSandbox",
1730
- networkAccess: "enabled"
1731
- },
1732
- ...codexModel == null ? {} : { model: codexModel },
1733
- ...start.reasoningEffort == null ? {} : { effort: start.reasoningEffort },
1734
- ...start.responseFormat?.type === "json" && start.responseFormat.schema != null ? { outputSchema: start.responseFormat.schema } : {}
1735
- };
1553
+ function createTurnParams({ threadId, start, codexModel, disableEnvironments = false }) {
1554
+ return {
1555
+ threadId,
1556
+ ...disableEnvironments ? { environments: [] } : {},
1557
+ input: [{
1558
+ type: "text",
1559
+ text: start.prompt,
1560
+ text_elements: []
1561
+ }],
1562
+ approvalPolicy: "never",
1563
+ sandboxPolicy: {
1564
+ type: "externalSandbox",
1565
+ networkAccess: "enabled"
1566
+ },
1567
+ ...codexModel == null ? {} : { model: codexModel },
1568
+ ...start.reasoningEffort == null ? {} : { effort: start.reasoningEffort },
1569
+ ...start.responseFormat?.type === "json" && start.responseFormat.schema != null ? { outputSchema: start.responseFormat.schema } : {}
1570
+ };
1736
1571
  }
1737
- function assertCodexThreadPermissions({
1738
- response,
1739
- method
1740
- }) {
1741
- const value = asRecord3(response);
1742
- const sandbox = asRecord3(value?.sandbox);
1743
- if (value?.approvalPolicy !== "never" || sandbox?.type !== "dangerFullAccess") {
1744
- throw new Error(
1745
- `Codex app-server ${method} did not disable approvals and its platform sandbox.`
1746
- );
1747
- }
1572
+ function assertCodexThreadPermissions({ response, method }) {
1573
+ const value = asRecord(response);
1574
+ const sandbox = asRecord(value?.sandbox);
1575
+ if (value?.approvalPolicy !== "never" || sandbox?.type !== "dangerFullAccess") throw new Error(`Codex app-server ${method} did not disable approvals and its platform sandbox.`);
1748
1576
  }
1749
1577
  function createDynamicTools(tools) {
1750
- const validOriginalNames = new Set(
1751
- tools.map((tool) => tool.name).filter(isValidDynamicToolName)
1752
- );
1753
- const usedAliases = /* @__PURE__ */ new Set();
1754
- const originalNameByAlias = /* @__PURE__ */ new Map();
1755
- const specs = tools.map((tool, index) => {
1756
- let alias = tool.name;
1757
- if (!isValidDynamicToolName(alias) || usedAliases.has(alias)) {
1758
- const digest = createHash2("sha256").update(tool.name).digest("hex").slice(0, 16);
1759
- const base = `ai_sdk_tool_${digest}`;
1760
- alias = base;
1761
- let suffix = index;
1762
- while (usedAliases.has(alias) || validOriginalNames.has(alias)) {
1763
- alias = `${base}_${suffix++}`;
1764
- }
1765
- }
1766
- usedAliases.add(alias);
1767
- originalNameByAlias.set(alias, tool.name);
1768
- return {
1769
- type: "function",
1770
- name: alias,
1771
- description: tool.description ?? "",
1772
- inputSchema: tool.inputSchema ?? {}
1773
- };
1774
- });
1775
- return { specs, originalNameByAlias };
1578
+ const validOriginalNames = new Set(tools.map((tool) => tool.name).filter(isValidDynamicToolName));
1579
+ const usedAliases = /* @__PURE__ */ new Set();
1580
+ const originalNameByAlias = /* @__PURE__ */ new Map();
1581
+ return {
1582
+ specs: tools.map((tool, index) => {
1583
+ let alias = tool.name;
1584
+ if (!isValidDynamicToolName(alias) || usedAliases.has(alias)) {
1585
+ const base = `ai_sdk_tool_${createHash("sha256").update(tool.name).digest("hex").slice(0, 16)}`;
1586
+ alias = base;
1587
+ let suffix = index;
1588
+ while (usedAliases.has(alias) || validOriginalNames.has(alias)) alias = `${base}_${suffix++}`;
1589
+ }
1590
+ usedAliases.add(alias);
1591
+ originalNameByAlias.set(alias, tool.name);
1592
+ return {
1593
+ type: "function",
1594
+ name: alias,
1595
+ description: tool.description ?? "",
1596
+ inputSchema: tool.inputSchema ?? {}
1597
+ };
1598
+ }),
1599
+ originalNameByAlias
1600
+ };
1776
1601
  }
1777
1602
  function isValidDynamicToolName(name) {
1778
- return name.length > 0 && name.length <= 128 && /^[a-zA-Z0-9_-]+$/.test(name) && name !== "mcp" && !name.startsWith("mcp__");
1603
+ return name.length > 0 && name.length <= 128 && /^[a-zA-Z0-9_-]+$/.test(name) && name !== "mcp" && !name.startsWith("mcp__");
1779
1604
  }
1780
- async function handleAppServerRequest({
1781
- request,
1782
- dynamicTools,
1783
- emit,
1784
- requestToolResult
1785
- }) {
1786
- if (request.method !== "item/tool/call") {
1787
- throw new Error(
1788
- `Codex app-server requested unsupported method '${request.method}'.`
1789
- );
1790
- }
1791
- const params = asRecord3(request.params);
1792
- if (params == null || typeof params.callId !== "string" || typeof params.tool !== "string") {
1793
- throw new Error("Codex app-server sent an invalid dynamic tool request.");
1794
- }
1795
- const originalToolName = dynamicTools.originalNameByAlias.get(params.tool);
1796
- if (originalToolName == null) {
1797
- throw new Error(
1798
- `Codex app-server requested unknown dynamic tool '${params.tool}'.`
1799
- );
1800
- }
1801
- emit({
1802
- type: "tool-call",
1803
- toolCallId: params.callId,
1804
- toolName: originalToolName,
1805
- input: JSON.stringify(params.arguments ?? {}),
1806
- providerExecuted: false
1807
- });
1808
- const result = await requestToolResult(params.callId);
1809
- emit({
1810
- type: "tool-result",
1811
- toolCallId: params.callId,
1812
- toolName: originalToolName,
1813
- result: result.output ?? null,
1814
- isError: result.isError === true
1815
- });
1816
- return {
1817
- contentItems: [
1818
- { type: "inputText", text: serializeToolOutput(result.output) }
1819
- ],
1820
- success: result.isError !== true
1821
- };
1605
+ async function handleAppServerRequest({ request, dynamicTools, emit, requestToolResult }) {
1606
+ if (request.method !== "item/tool/call") throw new Error(`Codex app-server requested unsupported method '${request.method}'.`);
1607
+ const params = asRecord(request.params);
1608
+ if (params == null || typeof params.callId !== "string" || typeof params.tool !== "string") throw new Error("Codex app-server sent an invalid dynamic tool request.");
1609
+ const originalToolName = dynamicTools.originalNameByAlias.get(params.tool);
1610
+ if (originalToolName == null) throw new Error(`Codex app-server requested unknown dynamic tool '${params.tool}'.`);
1611
+ emit({
1612
+ type: "tool-call",
1613
+ toolCallId: params.callId,
1614
+ toolName: originalToolName,
1615
+ input: JSON.stringify(params.arguments ?? {}),
1616
+ providerExecuted: false
1617
+ });
1618
+ const result = await requestToolResult(params.callId);
1619
+ emit({
1620
+ type: "tool-result",
1621
+ toolCallId: params.callId,
1622
+ toolName: originalToolName,
1623
+ result: result.output ?? null,
1624
+ isError: result.isError === true
1625
+ });
1626
+ return {
1627
+ contentItems: [{
1628
+ type: "inputText",
1629
+ text: serializeToolOutput(result.output)
1630
+ }],
1631
+ success: result.isError !== true
1632
+ };
1822
1633
  }
1823
1634
  function serializeToolOutput(output) {
1824
- if (typeof output === "string") return output;
1825
- try {
1826
- return JSON.stringify(output) ?? String(output);
1827
- } catch {
1828
- return String(output);
1829
- }
1635
+ if (typeof output === "string") return output;
1636
+ try {
1637
+ return JSON.stringify(output) ?? String(output);
1638
+ } catch {
1639
+ return String(output);
1640
+ }
1830
1641
  }
1831
- function readNestedString({
1832
- value,
1833
- path: path2,
1834
- method
1835
- }) {
1836
- let current = value;
1837
- for (const segment of path2) current = asRecord3(current)?.[segment];
1838
- if (typeof current !== "string" || current.length === 0) {
1839
- throw new Error(`Codex app-server ${method} returned an invalid response.`);
1840
- }
1841
- return current;
1642
+ function readNestedString({ value, path, method }) {
1643
+ let current = value;
1644
+ for (const segment of path) current = asRecord(current)?.[segment];
1645
+ if (typeof current !== "string" || current.length === 0) throw new Error(`Codex app-server ${method} returned an invalid response.`);
1646
+ return current;
1842
1647
  }
1843
1648
  function assertSuccessfulTurn(result) {
1844
- if (result.status === "completed") return;
1845
- if (result.status === "interrupted") {
1846
- throw new DOMException("Codex turn was interrupted.", "AbortError");
1847
- }
1848
- throw new Error(
1849
- result.error ?? `Codex turn ended with status '${result.status}'.`
1850
- );
1649
+ if (result.status === "completed") return;
1650
+ if (result.status === "interrupted") throw new DOMException("Codex turn was interrupted.", "AbortError");
1651
+ throw new Error(result.error ?? `Codex turn ended with status '${result.status}'.`);
1851
1652
  }
1852
- function asRecord3(value) {
1853
- return value != null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1653
+ function asRecord(value) {
1654
+ return value != null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1854
1655
  }
1855
-
1856
- // src/bridge/codex-step-tracker.ts
1656
+ //#endregion
1657
+ //#region src/bridge/codex-step-tracker.ts
1857
1658
  function createCodexStepTracker(input) {
1858
- let stepOpen = false;
1859
- const pendingToolItemIds = /* @__PURE__ */ new Set();
1860
- const finishStep = () => {
1861
- if (!stepOpen || pendingToolItemIds.size > 0) return;
1862
- input.send({
1863
- type: "finish-step",
1864
- finishReason: { unified: "stop", raw: "stop" },
1865
- usage: defaultUsage(),
1866
- harnessMetadata: { codex: { inferredStep: true } }
1867
- });
1868
- stepOpen = false;
1869
- };
1870
- return {
1871
- observeEvent({ event, itemId }) {
1872
- const item = event.item;
1873
- if (!item || !isStepItem(item)) return;
1874
- stepOpen = true;
1875
- if (isToolStepItem(item)) {
1876
- if (event.type === "item.started" && itemId) {
1877
- pendingToolItemIds.add(itemId);
1878
- } else if (event.type === "item.completed") {
1879
- if (itemId) pendingToolItemIds.delete(itemId);
1880
- finishStep();
1881
- }
1882
- }
1883
- },
1884
- finishTurn() {
1885
- pendingToolItemIds.clear();
1886
- finishStep();
1887
- }
1888
- };
1659
+ let stepOpen = false;
1660
+ const pendingToolItemIds = /* @__PURE__ */ new Set();
1661
+ const finishStep = () => {
1662
+ if (!stepOpen || pendingToolItemIds.size > 0) return;
1663
+ input.send({
1664
+ type: "finish-step",
1665
+ finishReason: {
1666
+ unified: "stop",
1667
+ raw: "stop"
1668
+ },
1669
+ usage: defaultUsage(),
1670
+ harnessMetadata: { codex: { inferredStep: true } }
1671
+ });
1672
+ stepOpen = false;
1673
+ };
1674
+ return {
1675
+ observeEvent({ event, itemId }) {
1676
+ const item = event.item;
1677
+ if (!item || !isStepItem(item)) return;
1678
+ stepOpen = true;
1679
+ if (isToolStepItem(item)) {
1680
+ if (event.type === "item.started" && itemId) pendingToolItemIds.add(itemId);
1681
+ else if (event.type === "item.completed") {
1682
+ if (itemId) pendingToolItemIds.delete(itemId);
1683
+ finishStep();
1684
+ }
1685
+ }
1686
+ },
1687
+ finishTurn() {
1688
+ pendingToolItemIds.clear();
1689
+ finishStep();
1690
+ }
1691
+ };
1889
1692
  }
1890
1693
  function isStepItem(item) {
1891
- return isModelStepItem(item) || isToolStepItem(item);
1694
+ return isModelStepItem(item) || isToolStepItem(item);
1892
1695
  }
1893
1696
  function isModelStepItem(item) {
1894
- return item.type === "reasoning" || item.type === "agent_message";
1697
+ return item.type === "reasoning" || item.type === "agent_message";
1895
1698
  }
1896
1699
  function isToolStepItem(item) {
1897
- return item.type === "command_execution" || item.type === "native_tool" || item.type === "mcp_tool_call" || item.type === "dynamic_tool_call" || item.type === "web_search" || item.type === "file_change" || item.type === "todo_list";
1700
+ return item.type === "command_execution" || item.type === "native_tool" || item.type === "mcp_tool_call" || item.type === "dynamic_tool_call" || item.type === "web_search" || item.type === "file_change" || item.type === "todo_list";
1898
1701
  }
1899
1702
  function defaultUsage() {
1900
- return {
1901
- inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
1902
- outputTokens: { total: 0, text: 0 }
1903
- };
1703
+ return {
1704
+ inputTokens: {
1705
+ total: 0,
1706
+ noCache: 0,
1707
+ cacheRead: 0,
1708
+ cacheWrite: 0
1709
+ },
1710
+ outputTokens: {
1711
+ total: 0,
1712
+ text: 0
1713
+ }
1714
+ };
1904
1715
  }
1905
-
1906
- // src/bridge/create-emit-stream-event.ts
1907
- import { randomUUID as randomUUID2 } from "crypto";
1908
- var NATIVE_TO_COMMON = {
1909
- shell: "bash",
1910
- web_search: "webSearch"
1716
+ //#endregion
1717
+ //#region src/bridge/create-emit-stream-event.ts
1718
+ const NATIVE_TO_COMMON = {
1719
+ shell: "bash",
1720
+ web_search: "webSearch"
1911
1721
  };
1912
1722
  function toCommonName(nativeName) {
1913
- return NATIVE_TO_COMMON[nativeName] ?? nativeName;
1723
+ return NATIVE_TO_COMMON[nativeName] ?? nativeName;
1914
1724
  }
1915
1725
  function getMcpToolName(item) {
1916
- const toolName = item.tool ?? "unknown";
1917
- return item.server != null && item.server.length > 0 ? `mcp__${item.server}__${toolName}` : toolName;
1726
+ const toolName = item.tool ?? "unknown";
1727
+ return item.server != null && item.server.length > 0 ? `mcp__${item.server}__${toolName}` : toolName;
1918
1728
  }
1919
- function createEmitStreamEvent({
1920
- send,
1921
- stepTracker,
1922
- setTurnUsage,
1923
- setThreadId,
1924
- emitWarning,
1925
- emitError
1926
- }) {
1927
- const textByItem = /* @__PURE__ */ new Map();
1928
- const reasoningByItem = /* @__PURE__ */ new Map();
1929
- const emittedWebSearchToolCalls = /* @__PURE__ */ new Set();
1930
- return (event) => {
1931
- if (event.type === "thread.started" && typeof event.thread_id === "string") {
1932
- setThreadId(event.thread_id);
1933
- send({ type: "bridge-thread", threadId: event.thread_id });
1934
- }
1935
- if (event.type === "turn.completed") {
1936
- if (event.usage) setTurnUsage(mapUsage(event.usage));
1937
- stepTracker.finishTurn();
1938
- return;
1939
- }
1940
- if (event.type === "turn.failed") {
1941
- emitError({
1942
- error: event.error?.message ?? "codex turn failed",
1943
- message: "codex turn failed"
1944
- });
1945
- return;
1946
- }
1947
- if (event.type === "error") {
1948
- emitError({
1949
- error: event.message ?? "codex error",
1950
- message: "codex stream error"
1951
- });
1952
- return;
1953
- }
1954
- if (!event.item) return;
1955
- const item = event.item;
1956
- const id = item.id ?? randomUUID2();
1957
- const observeStep = () => {
1958
- stepTracker.observeEvent({ event, itemId: id });
1959
- };
1960
- if (item.type === "native_tool" && item.tool != null) {
1961
- if (event.type === "item.started" && item.input != null) {
1962
- send({
1963
- type: "tool-call",
1964
- toolCallId: id,
1965
- toolName: item.tool,
1966
- input: item.input,
1967
- providerExecuted: true
1968
- });
1969
- } else if (event.type === "item.completed") {
1970
- send({
1971
- type: "tool-result",
1972
- toolCallId: id,
1973
- toolName: item.tool,
1974
- result: item.result,
1975
- ...item.isError ? { isError: true } : {}
1976
- });
1977
- }
1978
- stepTracker.observeEvent({ event, itemId: `native-tool:${id}` });
1979
- return;
1980
- }
1981
- if (item.type === "agent_message" && typeof item.text === "string") {
1982
- if (!textByItem.has(id)) {
1983
- send({ type: "text-start", id });
1984
- textByItem.set(id, "");
1985
- }
1986
- const last = textByItem.get(id) ?? "";
1987
- const next = item.text;
1988
- if (next.length > last.length) {
1989
- send({ type: "text-delta", id, delta: next.slice(last.length) });
1990
- textByItem.set(id, next);
1991
- }
1992
- if (event.type === "item.completed") send({ type: "text-end", id });
1993
- observeStep();
1994
- return;
1995
- }
1996
- if (item.type === "reasoning" && typeof item.text === "string") {
1997
- if (!reasoningByItem.has(id)) {
1998
- send({ type: "reasoning-start", id });
1999
- reasoningByItem.set(id, "");
2000
- }
2001
- const last = reasoningByItem.get(id) ?? "";
2002
- const next = item.text;
2003
- if (next.length > last.length) {
2004
- send({ type: "reasoning-delta", id, delta: next.slice(last.length) });
2005
- reasoningByItem.set(id, next);
2006
- }
2007
- if (event.type === "item.completed") send({ type: "reasoning-end", id });
2008
- observeStep();
2009
- return;
2010
- }
2011
- if (item.type === "command_execution") {
2012
- const nativeName = "shell";
2013
- if (event.type === "item.started") {
2014
- send({
2015
- type: "tool-call",
2016
- toolCallId: id,
2017
- toolName: toCommonName(nativeName),
2018
- nativeName,
2019
- input: JSON.stringify({ command: item.command ?? "" }),
2020
- providerExecuted: true
2021
- });
2022
- } else if (event.type === "item.completed") {
2023
- send({
2024
- type: "tool-result",
2025
- toolCallId: id,
2026
- toolName: toCommonName(nativeName),
2027
- result: {
2028
- exitCode: item.exit_code ?? null,
2029
- output: item.aggregated_output ?? "",
2030
- status: item.status ?? "completed"
2031
- }
2032
- });
2033
- }
2034
- observeStep();
2035
- return;
2036
- }
2037
- if (item.type === "mcp_tool_call") {
2038
- const toolName = getMcpToolName(item);
2039
- if (event.type === "item.started") {
2040
- send({
2041
- type: "tool-call",
2042
- toolCallId: id,
2043
- toolName,
2044
- nativeName: toolName,
2045
- input: JSON.stringify(item.arguments ?? {}),
2046
- providerExecuted: true,
2047
- dynamic: true
2048
- });
2049
- } else if (event.type === "item.completed") {
2050
- send({
2051
- type: "tool-result",
2052
- toolCallId: id,
2053
- toolName,
2054
- result: extractMcpToolCallResult(item),
2055
- dynamic: true
2056
- });
2057
- }
2058
- observeStep();
2059
- return;
2060
- }
2061
- if (item.type === "web_search") {
2062
- const nativeName = "web_search";
2063
- const query = getWebSearchQuery(item);
2064
- if (event.type === "item.started" || event.type === "item.updated") {
2065
- if (query !== void 0) {
2066
- emitWebSearchToolCall({
2067
- id,
2068
- query,
2069
- nativeName,
2070
- emittedWebSearchToolCalls,
2071
- send
2072
- });
2073
- }
2074
- } else if (event.type === "item.completed") {
2075
- emitWebSearchToolCall({
2076
- id,
2077
- query: query ?? "",
2078
- nativeName,
2079
- emittedWebSearchToolCalls,
2080
- send
2081
- });
2082
- send({
2083
- type: "tool-result",
2084
- toolCallId: id,
2085
- toolName: toCommonName(nativeName),
2086
- result: item.result ?? item.action ?? null
2087
- });
2088
- emittedWebSearchToolCalls.delete(id);
2089
- }
2090
- observeStep();
2091
- return;
2092
- }
2093
- if (item.type === "file_change" && event.type === "item.completed") {
2094
- for (const change of item.changes ?? []) {
2095
- send({
2096
- type: "file-change",
2097
- event: change.kind === "add" ? "create" : change.kind === "delete" ? "delete" : "modify",
2098
- path: change.path
2099
- });
2100
- }
2101
- observeStep();
2102
- return;
2103
- }
2104
- if (item.type === "error" && event.type === "item.completed") {
2105
- const message = typeof item.message === "string" && item.message.trim() ? item.message : "codex reported a non-fatal error item";
2106
- emitWarning({ message });
2107
- }
2108
- };
1729
+ function createEmitStreamEvent({ send, stepTracker, setTurnUsage, setThreadId, emitWarning, emitError }) {
1730
+ const textByItem = /* @__PURE__ */ new Map();
1731
+ const reasoningByItem = /* @__PURE__ */ new Map();
1732
+ const emittedWebSearchToolCalls = /* @__PURE__ */ new Set();
1733
+ return (event) => {
1734
+ if (event.type === "thread.started" && typeof event.thread_id === "string") {
1735
+ setThreadId(event.thread_id);
1736
+ send({
1737
+ type: "bridge-thread",
1738
+ threadId: event.thread_id
1739
+ });
1740
+ }
1741
+ if (event.type === "turn.completed") {
1742
+ if (event.usage) setTurnUsage(mapUsage(event.usage));
1743
+ stepTracker.finishTurn();
1744
+ return;
1745
+ }
1746
+ if (event.type === "turn.failed") {
1747
+ emitError({
1748
+ error: event.error?.message ?? "codex turn failed",
1749
+ message: "codex turn failed"
1750
+ });
1751
+ return;
1752
+ }
1753
+ if (event.type === "error") {
1754
+ emitError({
1755
+ error: event.message ?? "codex error",
1756
+ message: "codex stream error"
1757
+ });
1758
+ return;
1759
+ }
1760
+ if (!event.item) return;
1761
+ const item = event.item;
1762
+ const id = item.id ?? randomUUID();
1763
+ const observeStep = () => {
1764
+ stepTracker.observeEvent({
1765
+ event,
1766
+ itemId: id
1767
+ });
1768
+ };
1769
+ if (item.type === "native_tool" && item.tool != null) {
1770
+ if (event.type === "item.started" && item.input != null) send({
1771
+ type: "tool-call",
1772
+ toolCallId: id,
1773
+ toolName: item.tool,
1774
+ input: item.input,
1775
+ providerExecuted: true
1776
+ });
1777
+ else if (event.type === "item.completed") send({
1778
+ type: "tool-result",
1779
+ toolCallId: id,
1780
+ toolName: item.tool,
1781
+ result: item.result,
1782
+ ...item.isError ? { isError: true } : {}
1783
+ });
1784
+ stepTracker.observeEvent({
1785
+ event,
1786
+ itemId: `native-tool:${id}`
1787
+ });
1788
+ return;
1789
+ }
1790
+ if (item.type === "agent_message" && typeof item.text === "string") {
1791
+ if (!textByItem.has(id)) {
1792
+ send({
1793
+ type: "text-start",
1794
+ id
1795
+ });
1796
+ textByItem.set(id, "");
1797
+ }
1798
+ const last = textByItem.get(id) ?? "";
1799
+ const next = item.text;
1800
+ if (next.length > last.length) {
1801
+ send({
1802
+ type: "text-delta",
1803
+ id,
1804
+ delta: next.slice(last.length)
1805
+ });
1806
+ textByItem.set(id, next);
1807
+ }
1808
+ if (event.type === "item.completed") send({
1809
+ type: "text-end",
1810
+ id
1811
+ });
1812
+ observeStep();
1813
+ return;
1814
+ }
1815
+ if (item.type === "reasoning" && typeof item.text === "string") {
1816
+ if (!reasoningByItem.has(id)) {
1817
+ send({
1818
+ type: "reasoning-start",
1819
+ id
1820
+ });
1821
+ reasoningByItem.set(id, "");
1822
+ }
1823
+ const last = reasoningByItem.get(id) ?? "";
1824
+ const next = item.text;
1825
+ if (next.length > last.length) {
1826
+ send({
1827
+ type: "reasoning-delta",
1828
+ id,
1829
+ delta: next.slice(last.length)
1830
+ });
1831
+ reasoningByItem.set(id, next);
1832
+ }
1833
+ if (event.type === "item.completed") send({
1834
+ type: "reasoning-end",
1835
+ id
1836
+ });
1837
+ observeStep();
1838
+ return;
1839
+ }
1840
+ if (item.type === "command_execution") {
1841
+ const nativeName = "shell";
1842
+ if (event.type === "item.started") send({
1843
+ type: "tool-call",
1844
+ toolCallId: id,
1845
+ toolName: toCommonName(nativeName),
1846
+ nativeName,
1847
+ input: JSON.stringify({ command: item.command ?? "" }),
1848
+ providerExecuted: true
1849
+ });
1850
+ else if (event.type === "item.completed") send({
1851
+ type: "tool-result",
1852
+ toolCallId: id,
1853
+ toolName: toCommonName(nativeName),
1854
+ result: {
1855
+ exitCode: item.exit_code ?? null,
1856
+ output: item.aggregated_output ?? "",
1857
+ status: item.status ?? "completed"
1858
+ }
1859
+ });
1860
+ observeStep();
1861
+ return;
1862
+ }
1863
+ if (item.type === "mcp_tool_call") {
1864
+ const toolName = getMcpToolName(item);
1865
+ if (event.type === "item.started") send({
1866
+ type: "tool-call",
1867
+ toolCallId: id,
1868
+ toolName,
1869
+ nativeName: toolName,
1870
+ input: JSON.stringify(item.arguments ?? {}),
1871
+ providerExecuted: true,
1872
+ dynamic: true
1873
+ });
1874
+ else if (event.type === "item.completed") send({
1875
+ type: "tool-result",
1876
+ toolCallId: id,
1877
+ toolName,
1878
+ result: extractMcpToolCallResult(item),
1879
+ dynamic: true
1880
+ });
1881
+ observeStep();
1882
+ return;
1883
+ }
1884
+ if (item.type === "web_search") {
1885
+ const nativeName = "web_search";
1886
+ const query = getWebSearchQuery(item);
1887
+ if (event.type === "item.started" || event.type === "item.updated") {
1888
+ if (query !== void 0) emitWebSearchToolCall({
1889
+ id,
1890
+ query,
1891
+ nativeName,
1892
+ emittedWebSearchToolCalls,
1893
+ send
1894
+ });
1895
+ } else if (event.type === "item.completed") {
1896
+ emitWebSearchToolCall({
1897
+ id,
1898
+ query: query ?? "",
1899
+ nativeName,
1900
+ emittedWebSearchToolCalls,
1901
+ send
1902
+ });
1903
+ send({
1904
+ type: "tool-result",
1905
+ toolCallId: id,
1906
+ toolName: toCommonName(nativeName),
1907
+ result: item.result ?? item.action ?? null
1908
+ });
1909
+ emittedWebSearchToolCalls.delete(id);
1910
+ }
1911
+ observeStep();
1912
+ return;
1913
+ }
1914
+ if (item.type === "file_change" && event.type === "item.completed") {
1915
+ for (const change of item.changes ?? []) send({
1916
+ type: "file-change",
1917
+ event: change.kind === "add" ? "create" : change.kind === "delete" ? "delete" : "modify",
1918
+ path: change.path
1919
+ });
1920
+ observeStep();
1921
+ return;
1922
+ }
1923
+ if (item.type === "error" && event.type === "item.completed") emitWarning({ message: typeof item.message === "string" && item.message.trim() ? item.message : "codex reported a non-fatal error item" });
1924
+ };
2109
1925
  }
2110
1926
  function getWebSearchQuery(item) {
2111
- if (typeof item.query === "string" && item.query.length > 0) {
2112
- return item.query;
2113
- }
2114
- if (typeof item.action?.query === "string" && item.action.query.length > 0) {
2115
- return item.action.query;
2116
- }
1927
+ if (typeof item.query === "string" && item.query.length > 0) return item.query;
1928
+ if (typeof item.action?.query === "string" && item.action.query.length > 0) return item.action.query;
2117
1929
  }
2118
- function emitWebSearchToolCall({
2119
- id,
2120
- query,
2121
- nativeName,
2122
- emittedWebSearchToolCalls,
2123
- send
2124
- }) {
2125
- if (emittedWebSearchToolCalls.has(id)) return;
2126
- emittedWebSearchToolCalls.add(id);
2127
- send({
2128
- type: "tool-call",
2129
- toolCallId: id,
2130
- toolName: toCommonName(nativeName),
2131
- nativeName,
2132
- input: JSON.stringify({ query }),
2133
- providerExecuted: true
2134
- });
1930
+ function emitWebSearchToolCall({ id, query, nativeName, emittedWebSearchToolCalls, send }) {
1931
+ if (emittedWebSearchToolCalls.has(id)) return;
1932
+ emittedWebSearchToolCalls.add(id);
1933
+ send({
1934
+ type: "tool-call",
1935
+ toolCallId: id,
1936
+ toolName: toCommonName(nativeName),
1937
+ nativeName,
1938
+ input: JSON.stringify({ query }),
1939
+ providerExecuted: true
1940
+ });
2135
1941
  }
2136
1942
  function extractMcpToolCallResult(item) {
2137
- if (item.result === void 0 || item.result === null || typeof item.result !== "object") {
2138
- return item.error?.message ? { error: item.error.message } : null;
2139
- }
2140
- const result = item.result;
2141
- if (result.structured_content !== void 0 && result.structured_content !== null) {
2142
- return result.structured_content;
2143
- }
2144
- return result.content ?? null;
1943
+ if (item.result === void 0 || item.result === null || typeof item.result !== "object") return item.error?.message ? { error: item.error.message } : null;
1944
+ const result = item.result;
1945
+ if (result.structured_content !== void 0 && result.structured_content !== null) return result.structured_content;
1946
+ return result.content ?? null;
2145
1947
  }
2146
1948
  function mapUsage(usage) {
2147
- const input = usage.input_tokens ?? 0;
2148
- const cacheRead = usage.cached_input_tokens ?? 0;
2149
- const cacheWrite = usage.cache_write_input_tokens ?? 0;
2150
- return {
2151
- inputTokens: {
2152
- total: input,
2153
- noCache: Math.max(0, input - cacheRead - cacheWrite),
2154
- cacheRead,
2155
- cacheWrite
2156
- },
2157
- outputTokens: {
2158
- total: usage.output_tokens ?? 0,
2159
- text: usage.output_tokens ?? 0
2160
- }
2161
- };
1949
+ const input = usage.input_tokens ?? 0;
1950
+ const cacheRead = usage.cached_input_tokens ?? 0;
1951
+ const cacheWrite = usage.cache_write_input_tokens ?? 0;
1952
+ return {
1953
+ inputTokens: {
1954
+ total: input,
1955
+ noCache: Math.max(0, input - cacheRead - cacheWrite),
1956
+ cacheRead,
1957
+ cacheWrite
1958
+ },
1959
+ outputTokens: {
1960
+ total: usage.output_tokens ?? 0,
1961
+ text: usage.output_tokens ?? 0
1962
+ }
1963
+ };
2162
1964
  }
2163
-
2164
- // src/bridge/index.ts
2165
- import { argv, env as procEnv2, stdout as stdout2 } from "process";
2166
- var args = parseArgs(argv.slice(2));
2167
- var workdir = requireArg({ value: args.workdir, name: "--workdir" });
2168
- var bridgeStateDir = requireArg({
2169
- value: args.bridgeStateDir,
2170
- name: "--bridge-state-dir"
1965
+ //#endregion
1966
+ //#region src/bridge/index.ts
1967
+ const args = parseArgs(argv.slice(2));
1968
+ const workdir = requireArg({
1969
+ value: args.workdir,
1970
+ name: "--workdir"
1971
+ });
1972
+ const bridgeStateDir = requireArg({
1973
+ value: args.bridgeStateDir,
1974
+ name: "--bridge-state-dir"
2171
1975
  });
2172
- var HARNESS_CLIENT_APP = procEnv2.AI_SDK_HARNESS_CLIENT_APP;
2173
- var threadState = { id: void 0 };
2174
- var appServer = createCodexAppServerRuntime();
1976
+ const HARNESS_CLIENT_APP = env.AI_SDK_HARNESS_CLIENT_APP;
1977
+ const threadState = { id: void 0 };
1978
+ const appServer = createCodexAppServerRuntime();
2175
1979
  await runBridge({
2176
- bridgeType: "codex",
2177
- bridgeStateDir,
2178
- onStart: runTurn,
2179
- onStop: async () => {
2180
- const data = threadState.id ? { threadId: threadState.id } : {};
2181
- await appServer.close();
2182
- return data;
2183
- },
2184
- onDestroy: () => appServer.close()
1980
+ bridgeType: "codex",
1981
+ bridgeStateDir,
1982
+ onStart: runTurn,
1983
+ onStop: async () => {
1984
+ const data = threadState.id ? { threadId: threadState.id } : {};
1985
+ await appServer.close();
1986
+ return data;
1987
+ },
1988
+ onDestroy: () => appServer.close()
2185
1989
  });
2186
1990
  async function runTurn(start, turn) {
2187
- const emit = (msg) => turn.emit(msg);
2188
- if (start.restartThread) {
2189
- threadState.id = void 0;
2190
- } else if (typeof start.resumeThreadId === "string" && start.resumeThreadId.length > 0) {
2191
- threadState.id = start.resumeThreadId;
2192
- }
2193
- const runtime = resolveCodexRuntime({ start });
2194
- let turnUsage = defaultUsage();
2195
- const stepTracker = createCodexStepTracker({ send: emit });
2196
- const emitStreamEvent = createEmitStreamEvent({
2197
- send: emit,
2198
- stepTracker,
2199
- setTurnUsage: (usage) => turnUsage = usage,
2200
- setThreadId: (threadId) => threadState.id = threadId,
2201
- emitWarning: turn.emitWarning,
2202
- emitError: turn.emitError
2203
- });
2204
- try {
2205
- await appServer.runTurn({
2206
- start,
2207
- turn,
2208
- emit,
2209
- workdir,
2210
- threadId: threadState.id,
2211
- codexModel: runtime.codexModel,
2212
- codexConfig: runtime.codexConfig,
2213
- stepTracker,
2214
- emitStreamEvent
2215
- });
2216
- } catch (error) {
2217
- if (!turn.abortSignal.aborted) {
2218
- turn.emitError({ error, message: "codex turn failed" });
2219
- }
2220
- return;
2221
- }
2222
- emit({
2223
- type: "finish",
2224
- finishReason: { unified: "stop", raw: "stop" },
2225
- totalUsage: turnUsage
2226
- });
1991
+ const emit = (msg) => turn.emit(msg);
1992
+ if (start.restartThread) threadState.id = void 0;
1993
+ else if (typeof start.resumeThreadId === "string" && start.resumeThreadId.length > 0) threadState.id = start.resumeThreadId;
1994
+ const runtime = resolveCodexRuntime({ start });
1995
+ let turnUsage = defaultUsage();
1996
+ const stepTracker = createCodexStepTracker({ send: emit });
1997
+ const emitStreamEvent = createEmitStreamEvent({
1998
+ send: emit,
1999
+ stepTracker,
2000
+ setTurnUsage: (usage) => turnUsage = usage,
2001
+ setThreadId: (threadId) => threadState.id = threadId,
2002
+ emitWarning: turn.emitWarning,
2003
+ emitError: turn.emitError
2004
+ });
2005
+ try {
2006
+ await appServer.runTurn({
2007
+ start,
2008
+ turn,
2009
+ emit,
2010
+ workdir,
2011
+ threadId: threadState.id,
2012
+ codexModel: runtime.codexModel,
2013
+ codexConfig: runtime.codexConfig,
2014
+ stepTracker,
2015
+ emitStreamEvent
2016
+ });
2017
+ } catch (error) {
2018
+ if (!turn.abortSignal.aborted) turn.emitError({
2019
+ error,
2020
+ message: "codex turn failed"
2021
+ });
2022
+ return;
2023
+ }
2024
+ emit({
2025
+ type: "finish",
2026
+ finishReason: {
2027
+ unified: "stop",
2028
+ raw: "stop"
2029
+ },
2030
+ totalUsage: turnUsage
2031
+ });
2227
2032
  }
2228
2033
  function resolveCodexRuntime({ start }) {
2229
- const codexConfig = {
2230
- ...start.codexConfig,
2231
- developer_instructions: [
2232
- start.instructions,
2233
- "Only respond with your `final` message once you have fully addressed the user request."
2234
- ].filter((instruction) => Boolean(instruction)).join("\n\n"),
2235
- model_reasoning_summary: "detailed"
2236
- };
2237
- const gatewayBaseUrl = procEnv2.AI_GATEWAY_BASE_URL;
2238
- const hasGatewayAuth = Boolean(procEnv2.AI_GATEWAY_API_KEY || gatewayBaseUrl);
2239
- if (hasGatewayAuth && !gatewayBaseUrl) {
2240
- throw new Error(
2241
- "AI Gateway auth was selected but AI_GATEWAY_BASE_URL is missing from the Codex bridge environment."
2242
- );
2243
- }
2244
- const apiBaseUrl = hasGatewayAuth ? gatewayBaseUrl : procEnv2.OPENAI_BASE_URL ?? (procEnv2.CODEX_API_KEY != null || start.headers != null ? "https://api.openai.com/v1" : void 0);
2245
- const codexModel = start.model && hasGatewayAuth && !start.model.includes("/") ? `openai/${start.model}` : start.model;
2246
- if (hasGatewayAuth && codexModel?.startsWith("openai/")) {
2247
- codexConfig.model_supports_reasoning_summaries = true;
2248
- }
2249
- if (apiBaseUrl) {
2250
- codexConfig.preferred_auth_method = "apikey";
2251
- codexConfig.model_provider = "agent_bridge_openai";
2252
- codexConfig.model_providers = {
2253
- agent_bridge_openai: {
2254
- name: procEnv2.CODEX_MODEL_PROVIDER_NAME || "Agent Bridge OpenAI",
2255
- base_url: apiBaseUrl,
2256
- env_key: "CODEX_API_KEY",
2257
- wire_api: "responses",
2258
- supports_websockets: false,
2259
- ...start.headers != null || hasGatewayAuth && HARNESS_CLIENT_APP ? {
2260
- http_headers: {
2261
- ...start.headers,
2262
- ...hasGatewayAuth && HARNESS_CLIENT_APP ? {
2263
- "User-Agent": HARNESS_CLIENT_APP,
2264
- "x-client-app": HARNESS_CLIENT_APP
2265
- } : {}
2266
- }
2267
- } : {}
2268
- }
2269
- };
2270
- }
2271
- if (start.mcpServers != null) {
2272
- codexConfig.mcp_servers = start.mcpServers;
2273
- }
2274
- return { codexConfig, codexModel };
2034
+ const codexConfig = {
2035
+ ...start.codexConfig,
2036
+ developer_instructions: [start.instructions, "Only respond with your `final` message once you have fully addressed the user request."].filter((instruction) => Boolean(instruction)).join("\n\n"),
2037
+ model_reasoning_summary: "detailed"
2038
+ };
2039
+ const gatewayBaseUrl = env.AI_GATEWAY_BASE_URL;
2040
+ const hasGatewayAuth = Boolean(env.AI_GATEWAY_API_KEY || gatewayBaseUrl);
2041
+ if (hasGatewayAuth && !gatewayBaseUrl) throw new Error("AI Gateway auth was selected but AI_GATEWAY_BASE_URL is missing from the Codex bridge environment.");
2042
+ const apiBaseUrl = hasGatewayAuth ? gatewayBaseUrl : env.OPENAI_BASE_URL ?? (env.CODEX_API_KEY != null || start.headers != null ? "https://api.openai.com/v1" : void 0);
2043
+ const codexModel = start.model && hasGatewayAuth && !start.model.includes("/") ? `openai/${start.model}` : start.model;
2044
+ if (hasGatewayAuth && codexModel?.startsWith("openai/")) codexConfig.model_supports_reasoning_summaries = true;
2045
+ if (apiBaseUrl) {
2046
+ codexConfig.preferred_auth_method = "apikey";
2047
+ codexConfig.model_provider = "agent_bridge_openai";
2048
+ codexConfig.model_providers = { agent_bridge_openai: {
2049
+ name: env.CODEX_MODEL_PROVIDER_NAME || "Agent Bridge OpenAI",
2050
+ base_url: apiBaseUrl,
2051
+ env_key: "CODEX_API_KEY",
2052
+ wire_api: "responses",
2053
+ supports_websockets: false,
2054
+ ...start.headers != null || hasGatewayAuth && HARNESS_CLIENT_APP ? { http_headers: {
2055
+ ...start.headers,
2056
+ ...hasGatewayAuth && HARNESS_CLIENT_APP ? {
2057
+ "User-Agent": HARNESS_CLIENT_APP,
2058
+ "x-client-app": HARNESS_CLIENT_APP
2059
+ } : {}
2060
+ } } : {}
2061
+ } };
2062
+ }
2063
+ if (start.mcpServers != null) codexConfig.mcp_servers = start.mcpServers;
2064
+ return {
2065
+ codexConfig,
2066
+ codexModel
2067
+ };
2275
2068
  }
2276
- function parseArgs(args2) {
2277
- const out = {};
2278
- for (let i = 0; i < args2.length; i++) {
2279
- if (args2[i] === "--workdir" && i + 1 < args2.length) {
2280
- out.workdir = args2[++i];
2281
- } else if (args2[i] === "--bridge-state-dir" && i + 1 < args2.length) {
2282
- out.bridgeStateDir = args2[++i];
2283
- }
2284
- }
2285
- return out;
2069
+ function parseArgs(args) {
2070
+ const out = {};
2071
+ for (let i = 0; i < args.length; i++) if (args[i] === "--workdir" && i + 1 < args.length) out.workdir = args[++i];
2072
+ else if (args[i] === "--bridge-state-dir" && i + 1 < args.length) out.bridgeStateDir = args[++i];
2073
+ return out;
2286
2074
  }
2287
2075
  function emitFatal(message) {
2288
- stdout2.write(JSON.stringify({ type: "bridge-fatal", message }) + "\n");
2289
- process.exit(1);
2076
+ stdout.write(JSON.stringify({
2077
+ type: "bridge-fatal",
2078
+ message
2079
+ }) + "\n");
2080
+ process.exit(1);
2290
2081
  }
2291
- function requireArg({
2292
- value,
2293
- name
2294
- }) {
2295
- if (!value) {
2296
- emitFatal(`Missing ${name} argument.`);
2297
- }
2298
- return value;
2082
+ function requireArg({ value, name }) {
2083
+ if (!value) emitFatal(`Missing ${name} argument.`);
2084
+ return value;
2299
2085
  }
2086
+ //#endregion
2087
+ export {};
2088
+
2300
2089
  //# sourceMappingURL=index.mjs.map