@opengeni/codex 0.2.15 → 0.2.16

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.
@@ -83,6 +83,12 @@ export type CodexRequestContext = {
83
83
  onUsageHeaders?: (snapshot: CodexUsageHeaderSnapshot) => void;
84
84
  /** Optional per-run override, primarily for deterministic transport tests. */
85
85
  responseTimeoutPolicy?: Partial<CodexResponseTimeoutPolicy>;
86
+ /**
87
+ * Synchronous, best-effort diagnostics for the request lifecycle. This hook
88
+ * runs before the durable audit sink and MUST remain non-blocking: a throw is
89
+ * swallowed by the transport and it must never receive request bodies/auth.
90
+ */
91
+ onModelRequestDiagnostic?: (event: CodexModelRequestEvent) => void;
86
92
  /** Worker-owned durable audit sink; payloads never contain request bodies or auth. */
87
93
  onModelRequestEvent?: (event: CodexModelRequestEvent) => Promise<void> | void;
88
94
  /** Exact opaque artifacts on the normalized wire request, never their ciphertext. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/codex",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
4
4
  "description": "ChatGPT/Codex subscription auth + transport: device-code login, token refresh, and the Responses-backend fetch. Pure HTTP + transforms; no database dependency.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/fetch.ts CHANGED
@@ -200,25 +200,157 @@ type RequestAudit = {
200
200
  transportAttempt: number;
201
201
  model?: string;
202
202
  logicalStartedAt: number;
203
- attemptStartedAt: number;
203
+ attemptStartedAtMonotonic: number;
204
204
  policy: CodexResponseTimeoutPolicy;
205
+ terminalOutcome: RequestTerminalOutcome | null;
205
206
  };
206
207
 
207
- async function emitRequestEvent(
208
+ type RequestTerminalOutcome = "completed" | "failed" | "timed_out";
209
+
210
+ type SemanticTerminalState = {
211
+ phase: "completed" | "failed" | null;
212
+ /** Non-streaming callers must parse the complete SSE body before settling. */
213
+ deferTransportTerminal: boolean;
214
+ };
215
+
216
+ type CodexSseEvent = {
217
+ type?: string;
218
+ response?: Record<string, unknown>;
219
+ error?: unknown;
220
+ code?: unknown;
221
+ message?: unknown;
222
+ param?: unknown;
223
+ item?: unknown;
224
+ };
225
+
226
+ type CodexSseTerminalClassification =
227
+ | { phase: "completed" }
228
+ | {
229
+ phase: "failed";
230
+ rawError: unknown;
231
+ fallbackCode: string;
232
+ fallbackMessage: string;
233
+ }
234
+ | null;
235
+
236
+ function classifyCodexSseTerminal(ev: CodexSseEvent): CodexSseTerminalClassification {
237
+ if (ev.type === "response.failed") {
238
+ return {
239
+ phase: "failed",
240
+ rawError: ev.response?.error,
241
+ fallbackCode: "response_failed",
242
+ fallbackMessage: "The Codex response failed",
243
+ };
244
+ }
245
+ if (ev.type === "error" || ev.type === "response.error") {
246
+ return {
247
+ phase: "failed",
248
+ rawError: ev.error ?? ev.response?.error ?? ev,
249
+ fallbackCode: "response_error",
250
+ fallbackMessage: "The Codex response stream reported an error",
251
+ };
252
+ }
253
+ if (ev.type === "response.incomplete") {
254
+ const details = ev.response?.incomplete_details;
255
+ const reason =
256
+ details && typeof details === "object"
257
+ ? (details as Record<string, unknown>).reason
258
+ : undefined;
259
+ return {
260
+ phase: "failed",
261
+ rawError: {
262
+ code: "response_incomplete",
263
+ message:
264
+ typeof reason === "string" && reason.length > 0
265
+ ? `The Codex response was incomplete (${reason})`
266
+ : "The Codex response was incomplete",
267
+ },
268
+ fallbackCode: "response_incomplete",
269
+ fallbackMessage: "The Codex response was incomplete",
270
+ };
271
+ }
272
+ if (ev.type !== "response.completed" && ev.type !== "response.done") {
273
+ return null;
274
+ }
275
+ if (!ev.response) {
276
+ return null;
277
+ }
278
+
279
+ const responseStatus = ev.response.status;
280
+ if (
281
+ (responseStatus !== undefined && responseStatus !== "completed") ||
282
+ (ev.response.error !== null && ev.response.error !== undefined)
283
+ ) {
284
+ const incomplete = responseStatus === "incomplete";
285
+ return {
286
+ phase: "failed",
287
+ rawError: ev.response.error,
288
+ fallbackCode: incomplete ? "response_incomplete" : "response_failed",
289
+ fallbackMessage: incomplete
290
+ ? "The Codex response was incomplete"
291
+ : "The Codex response failed",
292
+ };
293
+ }
294
+ return { phase: "completed" };
295
+ }
296
+
297
+ function markSemanticTerminal(state: SemanticTerminalState, phase: "completed" | "failed"): void {
298
+ if (state.phase === null) {
299
+ state.phase = phase;
300
+ }
301
+ }
302
+
303
+ function terminalOutcomeForPhase(
304
+ phase: CodexModelRequestEvent["phase"],
305
+ ): RequestTerminalOutcome | null {
306
+ if (phase === "completed" || phase === "failed" || phase === "timed_out") {
307
+ return phase;
308
+ }
309
+ return null;
310
+ }
311
+
312
+ function requestEventFor(
208
313
  audit: RequestAudit,
209
314
  event: Omit<
210
315
  CodexModelRequestEvent,
211
316
  "requestId" | "transportAttempt" | "model" | "durationMs" | "timeoutPolicy"
212
317
  >,
213
- ): Promise<void> {
214
- await audit.ctx.onModelRequestEvent?.({
318
+ ): CodexModelRequestEvent {
319
+ return {
215
320
  requestId: audit.requestId,
216
321
  transportAttempt: audit.transportAttempt,
217
322
  ...(audit.model ? { model: audit.model } : {}),
218
- durationMs: Math.max(0, Date.now() - audit.attemptStartedAt),
323
+ durationMs: Math.max(0, performance.now() - audit.attemptStartedAtMonotonic),
219
324
  timeoutPolicy: audit.policy,
220
325
  ...event,
221
- });
326
+ };
327
+ }
328
+
329
+ async function emitRequestEvent(
330
+ audit: RequestAudit,
331
+ event: Omit<
332
+ CodexModelRequestEvent,
333
+ "requestId" | "transportAttempt" | "model" | "durationMs" | "timeoutPolicy"
334
+ >,
335
+ ): Promise<boolean> {
336
+ const terminalOutcome = terminalOutcomeForPhase(event.phase);
337
+ if (terminalOutcome !== null) {
338
+ if (audit.terminalOutcome !== null) {
339
+ return false;
340
+ }
341
+ // Fence before invoking either observer. The durable observer may reject,
342
+ // but a later transport callback must never turn that one terminal into a
343
+ // contradictory second terminal.
344
+ audit.terminalOutcome = terminalOutcome;
345
+ }
346
+ const observed = requestEventFor(audit, event);
347
+ try {
348
+ audit.ctx.onModelRequestDiagnostic?.(observed);
349
+ } catch {
350
+ // Diagnostic observers are strictly non-blocking and cannot affect transport.
351
+ }
352
+ await audit.ctx.onModelRequestEvent?.(observed);
353
+ return true;
222
354
  }
223
355
 
224
356
  function providerRequestId(headers: Headers): string | undefined {
@@ -275,11 +407,13 @@ async function observedResponse(
275
407
  res: Response,
276
408
  audit: RequestAudit,
277
409
  externalSignal: AbortSignal | null | undefined,
410
+ semanticTerminal?: SemanticTerminalState,
278
411
  ): Promise<Response> {
279
412
  const requestId = providerRequestId(res.headers);
280
413
  if (!res.body) {
414
+ if (semanticTerminal) markSemanticTerminal(semanticTerminal, "failed");
281
415
  await emitRequestEvent(audit, {
282
- phase: res.ok ? "completed" : "failed",
416
+ phase: semanticTerminal?.phase ?? (res.ok ? "completed" : "failed"),
283
417
  responseObserved: true,
284
418
  status: res.status,
285
419
  ...(requestId ? { providerRequestId: requestId } : {}),
@@ -307,17 +441,19 @@ async function observedResponse(
307
441
  if (terminal) return;
308
442
  terminal = true;
309
443
  clearTimers();
444
+ const semanticPhase = semanticTerminal?.phase;
445
+ const phase = semanticPhase ?? "timed_out";
310
446
  const error = new CodexResponseTimeoutError(klass, audit.requestId, true);
311
447
  void reader.cancel(error).catch(() => undefined);
312
448
  void emitRequestEvent(audit, {
313
- phase: "timed_out",
449
+ phase,
314
450
  responseObserved: true,
315
- timeoutClass: klass,
451
+ ...(phase === "timed_out" ? { timeoutClass: klass } : {}),
316
452
  status: res.status,
317
453
  ...(requestId ? { providerRequestId: requestId } : {}),
318
454
  }).then(
319
- () => controller.error(error),
320
- () => controller.error(error),
455
+ () => (phase === "completed" ? controller.close() : controller.error(error)),
456
+ () => (phase === "completed" ? controller.close() : controller.error(error)),
321
457
  );
322
458
  };
323
459
  armIdle = () => {
@@ -337,13 +473,15 @@ async function observedResponse(
337
473
  const reason = externalSignal?.reason ?? new DOMException("Aborted", "AbortError");
338
474
  void reader.cancel(reason).catch(() => undefined);
339
475
  void emitRequestEvent(audit, {
340
- phase: "failed",
476
+ phase: semanticTerminal?.phase ?? "failed",
341
477
  responseObserved: true,
342
478
  status: res.status,
343
479
  ...(requestId ? { providerRequestId: requestId } : {}),
344
480
  }).then(
345
- () => controller.error(reason),
346
- () => controller.error(reason),
481
+ () =>
482
+ semanticTerminal?.phase === "completed" ? controller.close() : controller.error(reason),
483
+ () =>
484
+ semanticTerminal?.phase === "completed" ? controller.close() : controller.error(reason),
347
485
  );
348
486
  };
349
487
  if (externalSignal?.aborted) {
@@ -362,12 +500,19 @@ async function observedResponse(
362
500
  if (chunk.done) {
363
501
  terminal = true;
364
502
  clearTimers();
365
- await emitRequestEvent(audit, {
366
- phase: res.ok ? "completed" : "failed",
367
- responseObserved: true,
368
- status: res.status,
369
- ...(requestId ? { providerRequestId: requestId } : {}),
370
- });
503
+ if (semanticTerminal && semanticTerminal.phase === null) {
504
+ if (!semanticTerminal.deferTransportTerminal) {
505
+ markSemanticTerminal(semanticTerminal, "failed");
506
+ }
507
+ }
508
+ if (!semanticTerminal?.deferTransportTerminal || semanticTerminal.phase !== null) {
509
+ await emitRequestEvent(audit, {
510
+ phase: semanticTerminal?.phase ?? (res.ok ? "completed" : "failed"),
511
+ responseObserved: true,
512
+ status: res.status,
513
+ ...(requestId ? { providerRequestId: requestId } : {}),
514
+ });
515
+ }
371
516
  controller.close();
372
517
  return;
373
518
  }
@@ -392,21 +537,32 @@ async function observedResponse(
392
537
  if (terminal) return;
393
538
  terminal = true;
394
539
  clearTimers();
540
+ const semanticPhase = semanticTerminal?.phase;
541
+ if (semanticTerminal && semanticPhase === null) {
542
+ markSemanticTerminal(semanticTerminal, "failed");
543
+ }
395
544
  await emitRequestEvent(audit, {
396
- phase: "failed",
545
+ phase: semanticPhase ?? "failed",
397
546
  responseObserved: true,
398
547
  status: res.status,
399
548
  ...(requestId ? { providerRequestId: requestId } : {}),
400
549
  });
401
- controller.error(error);
550
+ if (semanticPhase === "completed") {
551
+ controller.close();
552
+ } else {
553
+ controller.error(error);
554
+ }
402
555
  }
403
556
  },
404
557
  async cancel(reason) {
405
558
  if (!terminal) {
406
559
  terminal = true;
407
560
  clearTimers();
561
+ if (semanticTerminal && semanticTerminal.phase === null) {
562
+ markSemanticTerminal(semanticTerminal, "failed");
563
+ }
408
564
  await emitRequestEvent(audit, {
409
- phase: "failed",
565
+ phase: semanticTerminal?.phase ?? "failed",
410
566
  responseObserved: true,
411
567
  status: res.status,
412
568
  ...(requestId ? { providerRequestId: requestId } : {}),
@@ -566,13 +722,18 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
566
722
  transportAttempt,
567
723
  ...(model ? { model } : {}),
568
724
  logicalStartedAt,
569
- attemptStartedAt: Date.now(),
725
+ attemptStartedAtMonotonic: performance.now(),
570
726
  policy,
727
+ terminalOutcome: null,
571
728
  };
572
729
  await emitRequestEvent(audit, {
573
730
  phase: "started",
574
731
  responseObserved: false,
575
732
  });
733
+ const semanticTerminal: SemanticTerminalState = {
734
+ phase: null,
735
+ deferTransportTerminal: !callerWantsStream,
736
+ };
576
737
  try {
577
738
  res = await fetchBeforeHeaders(base, rewritten, nextInit, audit);
578
739
  const upstreamRequestId = providerRequestId(res.headers);
@@ -582,8 +743,7 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
582
743
  status: res.status,
583
744
  ...(upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}),
584
745
  });
585
- const observed = await observedResponse(res, audit, nextInit.signal);
586
- res = observed;
746
+ res = await observedResponse(res, audit, nextInit.signal, semanticTerminal);
587
747
  } catch (error) {
588
748
  if (nextInit.signal?.aborted) {
589
749
  await emitRequestEvent(audit, {
@@ -645,9 +805,25 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
645
805
  // Response lets the SDK reconstruct error.error for EVERY codex error
646
806
  // (401/400/5xx too). For a hard usage cap we also pin x-should-retry:false
647
807
  // so the SDK does not burn its retry budget on a limit that won't lift.
648
- return await bufferCodexErrorResponse(res);
808
+ const buffered = await bufferCodexErrorResponse(res);
809
+ const upstreamRequestId = providerRequestId(res.headers);
810
+ markSemanticTerminal(semanticTerminal, "failed");
811
+ await emitRequestEvent(audit, {
812
+ phase: "failed",
813
+ responseObserved: true,
814
+ status: res.status,
815
+ ...(upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}),
816
+ }).catch(() => undefined);
817
+ return buffered;
649
818
  }
650
- return callerWantsStream ? validateCodexStream(res) : await sseToJsonResponse(res);
819
+ if (callerWantsStream) {
820
+ res = validateCodexStream(res, (phase) => {
821
+ markSemanticTerminal(semanticTerminal, phase);
822
+ });
823
+ } else {
824
+ res = await sseToJsonResponse(res, audit, semanticTerminal);
825
+ }
826
+ return res;
651
827
  };
652
828
 
653
829
  try {
@@ -808,7 +984,12 @@ async function readBoundedResponseText(
808
984
  * non-streaming `responses.create` caller expects: the terminal response.*
809
985
  * event carries the full `response` payload.
810
986
  */
811
- async function sseToJsonResponse(res: Response): Promise<Response> {
987
+ async function sseToJsonResponse(
988
+ res: Response,
989
+ audit: RequestAudit,
990
+ semanticTerminal: SemanticTerminalState,
991
+ ): Promise<Response> {
992
+ const upstreamRequestId = providerRequestId(res.headers);
812
993
  const text = await res.text();
813
994
  let final: Record<string, unknown> | null = null;
814
995
  let terminalError: Response | null = null;
@@ -819,75 +1000,49 @@ async function sseToJsonResponse(res: Response): Promise<Response> {
819
1000
  continue;
820
1001
  }
821
1002
  try {
822
- const ev = JSON.parse(data) as {
823
- type?: string;
824
- response?: Record<string, unknown>;
825
- error?: unknown;
826
- code?: unknown;
827
- message?: unknown;
828
- param?: unknown;
829
- item?: unknown;
830
- };
1003
+ const ev = JSON.parse(data) as CodexSseEvent;
831
1004
  if (ev.type === "response.output_item.done" && ev.item !== undefined) {
832
1005
  items.push(ev.item);
833
- } else if (ev.type === "response.failed") {
834
- terminalError = codexSseFailureResponse(
835
- res,
836
- ev.response?.error,
837
- "response_failed",
838
- "The Codex response failed",
839
- {
840
- eventType: ev.type,
841
- responseId: ev.response?.id,
842
- responseStatus: ev.response?.status,
843
- },
844
- );
845
- } else if (ev.type === "error" || ev.type === "response.error") {
846
- terminalError = codexSseFailureResponse(
847
- res,
848
- ev.error ?? ev.response?.error ?? ev,
849
- "response_error",
850
- "The Codex response stream reported an error",
851
- {
852
- eventType: ev.type,
853
- responseId: ev.response?.id,
854
- responseStatus: ev.response?.status,
855
- },
856
- );
857
- } else if (ev.type === "response.incomplete") {
858
- const details = ev.response?.incomplete_details;
859
- const reason =
860
- details && typeof details === "object"
861
- ? (details as Record<string, unknown>).reason
862
- : undefined;
863
- terminalError = codexSseFailureResponse(
864
- res,
865
- {
866
- code: "response_incomplete",
867
- message:
868
- typeof reason === "string" && reason.length > 0
869
- ? `The Codex response was incomplete (${reason})`
870
- : "The Codex response was incomplete",
871
- },
872
- "response_incomplete",
873
- "The Codex response was incomplete",
874
- {
875
- eventType: ev.type,
876
- responseId: ev.response?.id,
877
- responseStatus: ev.response?.status,
878
- },
879
- );
880
- } else if (ev.type === "response.completed" || ev.type === "response.done") {
881
- final = ev.response ?? null;
1006
+ } else {
1007
+ const terminal = classifyCodexSseTerminal(ev);
1008
+ if (terminal?.phase === "failed") {
1009
+ terminalError = codexSseFailureResponse(
1010
+ res,
1011
+ terminal.rawError,
1012
+ terminal.fallbackCode,
1013
+ terminal.fallbackMessage,
1014
+ {
1015
+ eventType: ev.type,
1016
+ responseId: ev.response?.id,
1017
+ responseStatus: ev.response?.status,
1018
+ },
1019
+ );
1020
+ } else if (terminal?.phase === "completed") {
1021
+ final = ev.response ?? null;
1022
+ }
882
1023
  }
883
1024
  } catch {
884
1025
  /* ignore non-JSON keepalive lines */
885
1026
  }
886
1027
  }
887
1028
  if (terminalError) {
1029
+ markSemanticTerminal(semanticTerminal, "failed");
1030
+ await emitRequestEvent(audit, {
1031
+ phase: "failed",
1032
+ responseObserved: true,
1033
+ status: res.status,
1034
+ ...(upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}),
1035
+ });
888
1036
  return terminalError;
889
1037
  }
890
1038
  if (!final) {
1039
+ markSemanticTerminal(semanticTerminal, "failed");
1040
+ await emitRequestEvent(audit, {
1041
+ phase: "failed",
1042
+ responseObserved: true,
1043
+ status: res.status,
1044
+ ...(upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}),
1045
+ });
891
1046
  return codexSseFailureResponse(
892
1047
  res,
893
1048
  null,
@@ -903,6 +1058,13 @@ async function sseToJsonResponse(res: Response): Promise<Response> {
903
1058
  `[codex-debug] sse->json items=${items.length} outputLen=${Array.isArray(final?.output) ? (final.output as unknown[]).length : "?"}`,
904
1059
  );
905
1060
  }
1061
+ markSemanticTerminal(semanticTerminal, "completed");
1062
+ await emitRequestEvent(audit, {
1063
+ phase: "completed",
1064
+ responseObserved: true,
1065
+ status: res.status,
1066
+ ...(upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}),
1067
+ });
906
1068
  const headers = new Headers(res.headers);
907
1069
  headers.set("content-type", "application/json");
908
1070
  headers.delete("content-length");
@@ -1178,8 +1340,12 @@ function codexSseFailureError(
1178
1340
  * output reconstruction belongs to the model reducer, so this layer retains no
1179
1341
  * duplicate output-item graph.
1180
1342
  */
1181
- function validateCodexStream(res: Response): Response {
1343
+ function validateCodexStream(
1344
+ res: Response,
1345
+ onSemanticTerminal?: (phase: "completed" | "failed") => void,
1346
+ ): Response {
1182
1347
  if (!res.body) {
1348
+ onSemanticTerminal?.("failed");
1183
1349
  const error = codexSseFailureError(
1184
1350
  res,
1185
1351
  null,
@@ -1212,7 +1378,7 @@ function validateCodexStream(res: Response): Response {
1212
1378
  const block = buffer.slice(0, boundary.start);
1213
1379
  const separator = buffer.slice(boundary.start, boundary.end);
1214
1380
  buffer = buffer.slice(boundary.end);
1215
- successfulTerminalSeen ||= inspectCodexSseBlock(block, res);
1381
+ successfulTerminalSeen ||= inspectCodexSseBlock(block, res, onSemanticTerminal);
1216
1382
  controller.enqueue(encoder.encode(`${block}${separator}`));
1217
1383
  boundary = findSseBlockBoundary(buffer, final);
1218
1384
  }
@@ -1226,7 +1392,7 @@ function validateCodexStream(res: Response): Response {
1226
1392
  buffer += decoder.decode();
1227
1393
  emitCompleteBlocks(controller, true);
1228
1394
  if (buffer.length > 0) {
1229
- successfulTerminalSeen ||= inspectCodexSseBlock(buffer, res);
1395
+ successfulTerminalSeen ||= inspectCodexSseBlock(buffer, res, onSemanticTerminal);
1230
1396
  controller.enqueue(encoder.encode(buffer));
1231
1397
  buffer = "";
1232
1398
  }
@@ -1293,7 +1459,11 @@ const CODEX_TERMINAL_TYPE_HINTS = [
1293
1459
  * without object allocation; failed/error/incomplete terminals throw before the
1294
1460
  * model can mistake them for an ordinary response_done event.
1295
1461
  */
1296
- function inspectCodexSseBlock(block: string, source: Response): boolean {
1462
+ function inspectCodexSseBlock(
1463
+ block: string,
1464
+ source: Response,
1465
+ onSemanticTerminal?: (phase: "completed" | "failed") => void,
1466
+ ): boolean {
1297
1467
  const lines = block.split(/\r\n|\r|\n/);
1298
1468
  const dataStr = lines
1299
1469
  .filter((l) => l.startsWith("data:"))
@@ -1305,26 +1475,20 @@ function inspectCodexSseBlock(block: string, source: Response): boolean {
1305
1475
  if (!CODEX_TERMINAL_TYPE_HINTS.some((terminalType) => dataStr.includes(terminalType))) {
1306
1476
  return false;
1307
1477
  }
1308
- let ev: {
1309
- type?: string;
1310
- item?: unknown;
1311
- response?: Record<string, unknown>;
1312
- error?: unknown;
1313
- code?: unknown;
1314
- message?: unknown;
1315
- param?: unknown;
1316
- };
1478
+ let ev: CodexSseEvent;
1317
1479
  try {
1318
1480
  ev = JSON.parse(dataStr);
1319
1481
  } catch {
1320
1482
  return false;
1321
1483
  }
1322
- if (ev.type === "response.failed") {
1484
+ const terminal = classifyCodexSseTerminal(ev);
1485
+ if (terminal?.phase === "failed") {
1486
+ onSemanticTerminal?.("failed");
1323
1487
  throw codexSseFailureError(
1324
1488
  source,
1325
- ev.response?.error,
1326
- "response_failed",
1327
- "The Codex response failed",
1489
+ terminal.rawError,
1490
+ terminal.fallbackCode,
1491
+ terminal.fallbackMessage,
1328
1492
  {
1329
1493
  eventType: ev.type,
1330
1494
  responseId: ev.response?.id,
@@ -1332,62 +1496,8 @@ function inspectCodexSseBlock(block: string, source: Response): boolean {
1332
1496
  },
1333
1497
  );
1334
1498
  }
1335
- if (ev.type === "error" || ev.type === "response.error") {
1336
- throw codexSseFailureError(
1337
- source,
1338
- ev.error ?? ev.response?.error ?? ev,
1339
- "response_error",
1340
- "The Codex response stream reported an error",
1341
- {
1342
- eventType: ev.type,
1343
- responseId: ev.response?.id,
1344
- responseStatus: ev.response?.status,
1345
- },
1346
- );
1347
- }
1348
- if (ev.type === "response.incomplete") {
1349
- const details = ev.response?.incomplete_details;
1350
- const reason =
1351
- details && typeof details === "object"
1352
- ? (details as Record<string, unknown>).reason
1353
- : undefined;
1354
- throw codexSseFailureError(
1355
- source,
1356
- {
1357
- code: "response_incomplete",
1358
- message:
1359
- typeof reason === "string" && reason.length > 0
1360
- ? `The Codex response was incomplete (${reason})`
1361
- : "The Codex response was incomplete",
1362
- },
1363
- "response_incomplete",
1364
- "The Codex response was incomplete",
1365
- {
1366
- eventType: ev.type,
1367
- responseId: ev.response?.id,
1368
- responseStatus: ev.response?.status,
1369
- },
1370
- );
1371
- }
1372
- if ((ev.type === "response.completed" || ev.type === "response.done") && ev.response) {
1373
- if (
1374
- (ev.response.status !== undefined && ev.response.status !== "completed") ||
1375
- (ev.response.error !== null && ev.response.error !== undefined)
1376
- ) {
1377
- throw codexSseFailureError(
1378
- source,
1379
- ev.response.error,
1380
- ev.response.status === "incomplete" ? "response_incomplete" : "response_failed",
1381
- ev.response.status === "incomplete"
1382
- ? "The Codex response was incomplete"
1383
- : "The Codex response failed",
1384
- {
1385
- eventType: ev.type,
1386
- responseId: ev.response.id,
1387
- responseStatus: ev.response.status,
1388
- },
1389
- );
1390
- }
1499
+ if (terminal?.phase === "completed") {
1500
+ onSemanticTerminal?.("completed");
1391
1501
  return true;
1392
1502
  }
1393
1503
  return false;
@@ -97,6 +97,12 @@ export type CodexRequestContext = {
97
97
  onUsageHeaders?: (snapshot: CodexUsageHeaderSnapshot) => void;
98
98
  /** Optional per-run override, primarily for deterministic transport tests. */
99
99
  responseTimeoutPolicy?: Partial<CodexResponseTimeoutPolicy>;
100
+ /**
101
+ * Synchronous, best-effort diagnostics for the request lifecycle. This hook
102
+ * runs before the durable audit sink and MUST remain non-blocking: a throw is
103
+ * swallowed by the transport and it must never receive request bodies/auth.
104
+ */
105
+ onModelRequestDiagnostic?: (event: CodexModelRequestEvent) => void;
100
106
  /** Worker-owned durable audit sink; payloads never contain request bodies or auth. */
101
107
  onModelRequestEvent?: (event: CodexModelRequestEvent) => Promise<void> | void;
102
108
  /** Exact opaque artifacts on the normalized wire request, never their ciphertext. */