@opengeni/codex 0.2.15 → 0.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/fetch.ts CHANGED
@@ -17,11 +17,23 @@ import { opaqueProviderArtifactFingerprints } from "./opaque-artifact";
17
17
  import {
18
18
  codexRequestStorage,
19
19
  type CodexModelRequestEvent,
20
+ type CodexRequestPreparationPhase,
20
21
  type CodexRequestContext,
21
22
  type CodexResponseTimeoutPolicy,
22
23
  type CodexTokenSnapshot,
23
24
  type CodexUsageHeaderSnapshot,
24
25
  } from "./request-context";
26
+
27
+ function emitRequestPreparationDiagnostic(
28
+ ctx: CodexRequestContext,
29
+ phase: CodexRequestPreparationPhase,
30
+ ): void {
31
+ try {
32
+ ctx.onRequestPreparationDiagnostic?.(phase);
33
+ } catch {
34
+ // Diagnostic observers are non-blocking and cannot affect transport.
35
+ }
36
+ }
25
37
  import {
26
38
  CODEX_RESPONSE_TIMEOUT_ERROR_TYPE,
27
39
  CodexResponseTimeoutError,
@@ -200,25 +212,157 @@ type RequestAudit = {
200
212
  transportAttempt: number;
201
213
  model?: string;
202
214
  logicalStartedAt: number;
203
- attemptStartedAt: number;
215
+ attemptStartedAtMonotonic: number;
204
216
  policy: CodexResponseTimeoutPolicy;
217
+ terminalOutcome: RequestTerminalOutcome | null;
205
218
  };
206
219
 
207
- async function emitRequestEvent(
220
+ type RequestTerminalOutcome = "completed" | "failed" | "timed_out";
221
+
222
+ type SemanticTerminalState = {
223
+ phase: "completed" | "failed" | null;
224
+ /** Non-streaming callers must parse the complete SSE body before settling. */
225
+ deferTransportTerminal: boolean;
226
+ };
227
+
228
+ type CodexSseEvent = {
229
+ type?: string;
230
+ response?: Record<string, unknown>;
231
+ error?: unknown;
232
+ code?: unknown;
233
+ message?: unknown;
234
+ param?: unknown;
235
+ item?: unknown;
236
+ };
237
+
238
+ type CodexSseTerminalClassification =
239
+ | { phase: "completed" }
240
+ | {
241
+ phase: "failed";
242
+ rawError: unknown;
243
+ fallbackCode: string;
244
+ fallbackMessage: string;
245
+ }
246
+ | null;
247
+
248
+ function classifyCodexSseTerminal(ev: CodexSseEvent): CodexSseTerminalClassification {
249
+ if (ev.type === "response.failed") {
250
+ return {
251
+ phase: "failed",
252
+ rawError: ev.response?.error,
253
+ fallbackCode: "response_failed",
254
+ fallbackMessage: "The Codex response failed",
255
+ };
256
+ }
257
+ if (ev.type === "error" || ev.type === "response.error") {
258
+ return {
259
+ phase: "failed",
260
+ rawError: ev.error ?? ev.response?.error ?? ev,
261
+ fallbackCode: "response_error",
262
+ fallbackMessage: "The Codex response stream reported an error",
263
+ };
264
+ }
265
+ if (ev.type === "response.incomplete") {
266
+ const details = ev.response?.incomplete_details;
267
+ const reason =
268
+ details && typeof details === "object"
269
+ ? (details as Record<string, unknown>).reason
270
+ : undefined;
271
+ return {
272
+ phase: "failed",
273
+ rawError: {
274
+ code: "response_incomplete",
275
+ message:
276
+ typeof reason === "string" && reason.length > 0
277
+ ? `The Codex response was incomplete (${reason})`
278
+ : "The Codex response was incomplete",
279
+ },
280
+ fallbackCode: "response_incomplete",
281
+ fallbackMessage: "The Codex response was incomplete",
282
+ };
283
+ }
284
+ if (ev.type !== "response.completed" && ev.type !== "response.done") {
285
+ return null;
286
+ }
287
+ if (!ev.response) {
288
+ return null;
289
+ }
290
+
291
+ const responseStatus = ev.response.status;
292
+ if (
293
+ (responseStatus !== undefined && responseStatus !== "completed") ||
294
+ (ev.response.error !== null && ev.response.error !== undefined)
295
+ ) {
296
+ const incomplete = responseStatus === "incomplete";
297
+ return {
298
+ phase: "failed",
299
+ rawError: ev.response.error,
300
+ fallbackCode: incomplete ? "response_incomplete" : "response_failed",
301
+ fallbackMessage: incomplete
302
+ ? "The Codex response was incomplete"
303
+ : "The Codex response failed",
304
+ };
305
+ }
306
+ return { phase: "completed" };
307
+ }
308
+
309
+ function markSemanticTerminal(state: SemanticTerminalState, phase: "completed" | "failed"): void {
310
+ if (state.phase === null) {
311
+ state.phase = phase;
312
+ }
313
+ }
314
+
315
+ function terminalOutcomeForPhase(
316
+ phase: CodexModelRequestEvent["phase"],
317
+ ): RequestTerminalOutcome | null {
318
+ if (phase === "completed" || phase === "failed" || phase === "timed_out") {
319
+ return phase;
320
+ }
321
+ return null;
322
+ }
323
+
324
+ function requestEventFor(
208
325
  audit: RequestAudit,
209
326
  event: Omit<
210
327
  CodexModelRequestEvent,
211
328
  "requestId" | "transportAttempt" | "model" | "durationMs" | "timeoutPolicy"
212
329
  >,
213
- ): Promise<void> {
214
- await audit.ctx.onModelRequestEvent?.({
330
+ ): CodexModelRequestEvent {
331
+ return {
215
332
  requestId: audit.requestId,
216
333
  transportAttempt: audit.transportAttempt,
217
334
  ...(audit.model ? { model: audit.model } : {}),
218
- durationMs: Math.max(0, Date.now() - audit.attemptStartedAt),
335
+ durationMs: Math.max(0, performance.now() - audit.attemptStartedAtMonotonic),
219
336
  timeoutPolicy: audit.policy,
220
337
  ...event,
221
- });
338
+ };
339
+ }
340
+
341
+ async function emitRequestEvent(
342
+ audit: RequestAudit,
343
+ event: Omit<
344
+ CodexModelRequestEvent,
345
+ "requestId" | "transportAttempt" | "model" | "durationMs" | "timeoutPolicy"
346
+ >,
347
+ ): Promise<boolean> {
348
+ const terminalOutcome = terminalOutcomeForPhase(event.phase);
349
+ if (terminalOutcome !== null) {
350
+ if (audit.terminalOutcome !== null) {
351
+ return false;
352
+ }
353
+ // Fence before invoking either observer. The durable observer may reject,
354
+ // but a later transport callback must never turn that one terminal into a
355
+ // contradictory second terminal.
356
+ audit.terminalOutcome = terminalOutcome;
357
+ }
358
+ const observed = requestEventFor(audit, event);
359
+ try {
360
+ audit.ctx.onModelRequestDiagnostic?.(observed);
361
+ } catch {
362
+ // Diagnostic observers are strictly non-blocking and cannot affect transport.
363
+ }
364
+ await audit.ctx.onModelRequestEvent?.(observed);
365
+ return true;
222
366
  }
223
367
 
224
368
  function providerRequestId(headers: Headers): string | undefined {
@@ -275,11 +419,13 @@ async function observedResponse(
275
419
  res: Response,
276
420
  audit: RequestAudit,
277
421
  externalSignal: AbortSignal | null | undefined,
422
+ semanticTerminal?: SemanticTerminalState,
278
423
  ): Promise<Response> {
279
424
  const requestId = providerRequestId(res.headers);
280
425
  if (!res.body) {
426
+ if (semanticTerminal) markSemanticTerminal(semanticTerminal, "failed");
281
427
  await emitRequestEvent(audit, {
282
- phase: res.ok ? "completed" : "failed",
428
+ phase: semanticTerminal?.phase ?? (res.ok ? "completed" : "failed"),
283
429
  responseObserved: true,
284
430
  status: res.status,
285
431
  ...(requestId ? { providerRequestId: requestId } : {}),
@@ -307,17 +453,19 @@ async function observedResponse(
307
453
  if (terminal) return;
308
454
  terminal = true;
309
455
  clearTimers();
456
+ const semanticPhase = semanticTerminal?.phase;
457
+ const phase = semanticPhase ?? "timed_out";
310
458
  const error = new CodexResponseTimeoutError(klass, audit.requestId, true);
311
459
  void reader.cancel(error).catch(() => undefined);
312
460
  void emitRequestEvent(audit, {
313
- phase: "timed_out",
461
+ phase,
314
462
  responseObserved: true,
315
- timeoutClass: klass,
463
+ ...(phase === "timed_out" ? { timeoutClass: klass } : {}),
316
464
  status: res.status,
317
465
  ...(requestId ? { providerRequestId: requestId } : {}),
318
466
  }).then(
319
- () => controller.error(error),
320
- () => controller.error(error),
467
+ () => (phase === "completed" ? controller.close() : controller.error(error)),
468
+ () => (phase === "completed" ? controller.close() : controller.error(error)),
321
469
  );
322
470
  };
323
471
  armIdle = () => {
@@ -337,13 +485,15 @@ async function observedResponse(
337
485
  const reason = externalSignal?.reason ?? new DOMException("Aborted", "AbortError");
338
486
  void reader.cancel(reason).catch(() => undefined);
339
487
  void emitRequestEvent(audit, {
340
- phase: "failed",
488
+ phase: semanticTerminal?.phase ?? "failed",
341
489
  responseObserved: true,
342
490
  status: res.status,
343
491
  ...(requestId ? { providerRequestId: requestId } : {}),
344
492
  }).then(
345
- () => controller.error(reason),
346
- () => controller.error(reason),
493
+ () =>
494
+ semanticTerminal?.phase === "completed" ? controller.close() : controller.error(reason),
495
+ () =>
496
+ semanticTerminal?.phase === "completed" ? controller.close() : controller.error(reason),
347
497
  );
348
498
  };
349
499
  if (externalSignal?.aborted) {
@@ -362,12 +512,19 @@ async function observedResponse(
362
512
  if (chunk.done) {
363
513
  terminal = true;
364
514
  clearTimers();
365
- await emitRequestEvent(audit, {
366
- phase: res.ok ? "completed" : "failed",
367
- responseObserved: true,
368
- status: res.status,
369
- ...(requestId ? { providerRequestId: requestId } : {}),
370
- });
515
+ if (semanticTerminal && semanticTerminal.phase === null) {
516
+ if (!semanticTerminal.deferTransportTerminal) {
517
+ markSemanticTerminal(semanticTerminal, "failed");
518
+ }
519
+ }
520
+ if (!semanticTerminal?.deferTransportTerminal || semanticTerminal.phase !== null) {
521
+ await emitRequestEvent(audit, {
522
+ phase: semanticTerminal?.phase ?? (res.ok ? "completed" : "failed"),
523
+ responseObserved: true,
524
+ status: res.status,
525
+ ...(requestId ? { providerRequestId: requestId } : {}),
526
+ });
527
+ }
371
528
  controller.close();
372
529
  return;
373
530
  }
@@ -392,21 +549,32 @@ async function observedResponse(
392
549
  if (terminal) return;
393
550
  terminal = true;
394
551
  clearTimers();
552
+ const semanticPhase = semanticTerminal?.phase;
553
+ if (semanticTerminal && semanticPhase === null) {
554
+ markSemanticTerminal(semanticTerminal, "failed");
555
+ }
395
556
  await emitRequestEvent(audit, {
396
- phase: "failed",
557
+ phase: semanticPhase ?? "failed",
397
558
  responseObserved: true,
398
559
  status: res.status,
399
560
  ...(requestId ? { providerRequestId: requestId } : {}),
400
561
  });
401
- controller.error(error);
562
+ if (semanticPhase === "completed") {
563
+ controller.close();
564
+ } else {
565
+ controller.error(error);
566
+ }
402
567
  }
403
568
  },
404
569
  async cancel(reason) {
405
570
  if (!terminal) {
406
571
  terminal = true;
407
572
  clearTimers();
573
+ if (semanticTerminal && semanticTerminal.phase === null) {
574
+ markSemanticTerminal(semanticTerminal, "failed");
575
+ }
408
576
  await emitRequestEvent(audit, {
409
- phase: "failed",
577
+ phase: semanticTerminal?.phase ?? "failed",
410
578
  responseObserved: true,
411
579
  status: res.status,
412
580
  ...(requestId ? { providerRequestId: requestId } : {}),
@@ -458,6 +626,7 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
458
626
  if (!ctx) {
459
627
  return base(input, init); // not a codex turn — passthrough, untouched
460
628
  }
629
+ emitRequestPreparationDiagnostic(ctx, "transport_entry");
461
630
 
462
631
  const rawUrl =
463
632
  typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
@@ -544,7 +713,10 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
544
713
  throw new Error("Model request could not be prepared");
545
714
  }
546
715
  if (!bodyAlreadyNormalized) {
547
- ctx.onRequestOpaqueArtifacts?.({ requestId, fingerprints: requestOpaqueArtifacts });
716
+ ctx.onRequestOpaqueArtifacts?.({
717
+ requestId,
718
+ fingerprints: requestOpaqueArtifacts,
719
+ });
548
720
  }
549
721
  headers.set(
550
722
  "Idempotency-Key",
@@ -566,13 +738,19 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
566
738
  transportAttempt,
567
739
  ...(model ? { model } : {}),
568
740
  logicalStartedAt,
569
- attemptStartedAt: Date.now(),
741
+ attemptStartedAtMonotonic: performance.now(),
570
742
  policy,
743
+ terminalOutcome: null,
571
744
  };
745
+ emitRequestPreparationDiagnostic(ctx, "wire_request_ready");
572
746
  await emitRequestEvent(audit, {
573
747
  phase: "started",
574
748
  responseObserved: false,
575
749
  });
750
+ const semanticTerminal: SemanticTerminalState = {
751
+ phase: null,
752
+ deferTransportTerminal: !callerWantsStream,
753
+ };
576
754
  try {
577
755
  res = await fetchBeforeHeaders(base, rewritten, nextInit, audit);
578
756
  const upstreamRequestId = providerRequestId(res.headers);
@@ -582,8 +760,7 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
582
760
  status: res.status,
583
761
  ...(upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}),
584
762
  });
585
- const observed = await observedResponse(res, audit, nextInit.signal);
586
- res = observed;
763
+ res = await observedResponse(res, audit, nextInit.signal, semanticTerminal);
587
764
  } catch (error) {
588
765
  if (nextInit.signal?.aborted) {
589
766
  await emitRequestEvent(audit, {
@@ -645,13 +822,31 @@ export function codexSubscriptionFetch(base: FetchLike = globalThis.fetch): Fetc
645
822
  // Response lets the SDK reconstruct error.error for EVERY codex error
646
823
  // (401/400/5xx too). For a hard usage cap we also pin x-should-retry:false
647
824
  // so the SDK does not burn its retry budget on a limit that won't lift.
648
- return await bufferCodexErrorResponse(res);
825
+ const buffered = await bufferCodexErrorResponse(res);
826
+ const upstreamRequestId = providerRequestId(res.headers);
827
+ markSemanticTerminal(semanticTerminal, "failed");
828
+ await emitRequestEvent(audit, {
829
+ phase: "failed",
830
+ responseObserved: true,
831
+ status: res.status,
832
+ ...(upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}),
833
+ }).catch(() => undefined);
834
+ return buffered;
649
835
  }
650
- return callerWantsStream ? validateCodexStream(res) : await sseToJsonResponse(res);
836
+ if (callerWantsStream) {
837
+ res = validateCodexStream(res, (phase) => {
838
+ markSemanticTerminal(semanticTerminal, phase);
839
+ });
840
+ } else {
841
+ res = await sseToJsonResponse(res, audit, semanticTerminal);
842
+ }
843
+ return res;
651
844
  };
652
845
 
653
846
  try {
654
- let res = await attempt(await ctx.getToken(), 0);
847
+ const token = await ctx.getToken();
848
+ emitRequestPreparationDiagnostic(ctx, "credential_ready");
849
+ let res = await attempt(token, 0);
655
850
  if (res.status === 401) {
656
851
  res = await attempt(await ctx.refresh(), 1); // single refresh-on-401 retry (spec §1.9)
657
852
  }
@@ -808,7 +1003,12 @@ async function readBoundedResponseText(
808
1003
  * non-streaming `responses.create` caller expects: the terminal response.*
809
1004
  * event carries the full `response` payload.
810
1005
  */
811
- async function sseToJsonResponse(res: Response): Promise<Response> {
1006
+ async function sseToJsonResponse(
1007
+ res: Response,
1008
+ audit: RequestAudit,
1009
+ semanticTerminal: SemanticTerminalState,
1010
+ ): Promise<Response> {
1011
+ const upstreamRequestId = providerRequestId(res.headers);
812
1012
  const text = await res.text();
813
1013
  let final: Record<string, unknown> | null = null;
814
1014
  let terminalError: Response | null = null;
@@ -819,75 +1019,49 @@ async function sseToJsonResponse(res: Response): Promise<Response> {
819
1019
  continue;
820
1020
  }
821
1021
  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
- };
1022
+ const ev = JSON.parse(data) as CodexSseEvent;
831
1023
  if (ev.type === "response.output_item.done" && ev.item !== undefined) {
832
1024
  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;
1025
+ } else {
1026
+ const terminal = classifyCodexSseTerminal(ev);
1027
+ if (terminal?.phase === "failed") {
1028
+ terminalError = codexSseFailureResponse(
1029
+ res,
1030
+ terminal.rawError,
1031
+ terminal.fallbackCode,
1032
+ terminal.fallbackMessage,
1033
+ {
1034
+ eventType: ev.type,
1035
+ responseId: ev.response?.id,
1036
+ responseStatus: ev.response?.status,
1037
+ },
1038
+ );
1039
+ } else if (terminal?.phase === "completed") {
1040
+ final = ev.response ?? null;
1041
+ }
882
1042
  }
883
1043
  } catch {
884
1044
  /* ignore non-JSON keepalive lines */
885
1045
  }
886
1046
  }
887
1047
  if (terminalError) {
1048
+ markSemanticTerminal(semanticTerminal, "failed");
1049
+ await emitRequestEvent(audit, {
1050
+ phase: "failed",
1051
+ responseObserved: true,
1052
+ status: res.status,
1053
+ ...(upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}),
1054
+ });
888
1055
  return terminalError;
889
1056
  }
890
1057
  if (!final) {
1058
+ markSemanticTerminal(semanticTerminal, "failed");
1059
+ await emitRequestEvent(audit, {
1060
+ phase: "failed",
1061
+ responseObserved: true,
1062
+ status: res.status,
1063
+ ...(upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}),
1064
+ });
891
1065
  return codexSseFailureResponse(
892
1066
  res,
893
1067
  null,
@@ -903,6 +1077,13 @@ async function sseToJsonResponse(res: Response): Promise<Response> {
903
1077
  `[codex-debug] sse->json items=${items.length} outputLen=${Array.isArray(final?.output) ? (final.output as unknown[]).length : "?"}`,
904
1078
  );
905
1079
  }
1080
+ markSemanticTerminal(semanticTerminal, "completed");
1081
+ await emitRequestEvent(audit, {
1082
+ phase: "completed",
1083
+ responseObserved: true,
1084
+ status: res.status,
1085
+ ...(upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}),
1086
+ });
906
1087
  const headers = new Headers(res.headers);
907
1088
  headers.set("content-type", "application/json");
908
1089
  headers.delete("content-length");
@@ -1178,8 +1359,12 @@ function codexSseFailureError(
1178
1359
  * output reconstruction belongs to the model reducer, so this layer retains no
1179
1360
  * duplicate output-item graph.
1180
1361
  */
1181
- function validateCodexStream(res: Response): Response {
1362
+ function validateCodexStream(
1363
+ res: Response,
1364
+ onSemanticTerminal?: (phase: "completed" | "failed") => void,
1365
+ ): Response {
1182
1366
  if (!res.body) {
1367
+ onSemanticTerminal?.("failed");
1183
1368
  const error = codexSseFailureError(
1184
1369
  res,
1185
1370
  null,
@@ -1212,7 +1397,7 @@ function validateCodexStream(res: Response): Response {
1212
1397
  const block = buffer.slice(0, boundary.start);
1213
1398
  const separator = buffer.slice(boundary.start, boundary.end);
1214
1399
  buffer = buffer.slice(boundary.end);
1215
- successfulTerminalSeen ||= inspectCodexSseBlock(block, res);
1400
+ successfulTerminalSeen ||= inspectCodexSseBlock(block, res, onSemanticTerminal);
1216
1401
  controller.enqueue(encoder.encode(`${block}${separator}`));
1217
1402
  boundary = findSseBlockBoundary(buffer, final);
1218
1403
  }
@@ -1226,7 +1411,7 @@ function validateCodexStream(res: Response): Response {
1226
1411
  buffer += decoder.decode();
1227
1412
  emitCompleteBlocks(controller, true);
1228
1413
  if (buffer.length > 0) {
1229
- successfulTerminalSeen ||= inspectCodexSseBlock(buffer, res);
1414
+ successfulTerminalSeen ||= inspectCodexSseBlock(buffer, res, onSemanticTerminal);
1230
1415
  controller.enqueue(encoder.encode(buffer));
1231
1416
  buffer = "";
1232
1417
  }
@@ -1293,7 +1478,11 @@ const CODEX_TERMINAL_TYPE_HINTS = [
1293
1478
  * without object allocation; failed/error/incomplete terminals throw before the
1294
1479
  * model can mistake them for an ordinary response_done event.
1295
1480
  */
1296
- function inspectCodexSseBlock(block: string, source: Response): boolean {
1481
+ function inspectCodexSseBlock(
1482
+ block: string,
1483
+ source: Response,
1484
+ onSemanticTerminal?: (phase: "completed" | "failed") => void,
1485
+ ): boolean {
1297
1486
  const lines = block.split(/\r\n|\r|\n/);
1298
1487
  const dataStr = lines
1299
1488
  .filter((l) => l.startsWith("data:"))
@@ -1305,39 +1494,20 @@ function inspectCodexSseBlock(block: string, source: Response): boolean {
1305
1494
  if (!CODEX_TERMINAL_TYPE_HINTS.some((terminalType) => dataStr.includes(terminalType))) {
1306
1495
  return false;
1307
1496
  }
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
- };
1497
+ let ev: CodexSseEvent;
1317
1498
  try {
1318
1499
  ev = JSON.parse(dataStr);
1319
1500
  } catch {
1320
1501
  return false;
1321
1502
  }
1322
- if (ev.type === "response.failed") {
1323
- throw codexSseFailureError(
1324
- source,
1325
- ev.response?.error,
1326
- "response_failed",
1327
- "The Codex response failed",
1328
- {
1329
- eventType: ev.type,
1330
- responseId: ev.response?.id,
1331
- responseStatus: ev.response?.status,
1332
- },
1333
- );
1334
- }
1335
- if (ev.type === "error" || ev.type === "response.error") {
1503
+ const terminal = classifyCodexSseTerminal(ev);
1504
+ if (terminal?.phase === "failed") {
1505
+ onSemanticTerminal?.("failed");
1336
1506
  throw codexSseFailureError(
1337
1507
  source,
1338
- ev.error ?? ev.response?.error ?? ev,
1339
- "response_error",
1340
- "The Codex response stream reported an error",
1508
+ terminal.rawError,
1509
+ terminal.fallbackCode,
1510
+ terminal.fallbackMessage,
1341
1511
  {
1342
1512
  eventType: ev.type,
1343
1513
  responseId: ev.response?.id,
@@ -1345,49 +1515,8 @@ function inspectCodexSseBlock(block: string, source: Response): boolean {
1345
1515
  },
1346
1516
  );
1347
1517
  }
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
- }
1518
+ if (terminal?.phase === "completed") {
1519
+ onSemanticTerminal?.("completed");
1391
1520
  return true;
1392
1521
  }
1393
1522
  return false;
@@ -67,6 +67,11 @@ export type CodexRequestOpaqueArtifacts = {
67
67
  fingerprints: readonly string[];
68
68
  };
69
69
 
70
+ export type CodexRequestPreparationPhase =
71
+ | "transport_entry"
72
+ | "credential_ready"
73
+ | "wire_request_ready";
74
+
70
75
  export type CodexRequestContext = {
71
76
  clientVersion: string;
72
77
  /**
@@ -97,6 +102,14 @@ export type CodexRequestContext = {
97
102
  onUsageHeaders?: (snapshot: CodexUsageHeaderSnapshot) => void;
98
103
  /** Optional per-run override, primarily for deterministic transport tests. */
99
104
  responseTimeoutPolicy?: Partial<CodexResponseTimeoutPolicy>;
105
+ /**
106
+ * Synchronous, best-effort diagnostics for the request lifecycle. This hook
107
+ * runs before the durable audit sink and MUST remain non-blocking: a throw is
108
+ * swallowed by the transport and it must never receive request bodies/auth.
109
+ */
110
+ onModelRequestDiagnostic?: (event: CodexModelRequestEvent) => void;
111
+ /** Bounded synchronous checkpoints for pre-network request preparation. */
112
+ onRequestPreparationDiagnostic?: (phase: CodexRequestPreparationPhase) => void;
100
113
  /** Worker-owned durable audit sink; payloads never contain request bodies or auth. */
101
114
  onModelRequestEvent?: (event: CodexModelRequestEvent) => Promise<void> | void;
102
115
  /** Exact opaque artifacts on the normalized wire request, never their ciphertext. */