@opengeni/codex 0.2.13 → 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.
package/dist/index.js CHANGED
@@ -326,6 +326,20 @@ function normalizeCodexRequestBody(body, resolveModel) {
326
326
  }
327
327
  return body;
328
328
  }
329
+ function normalizedCodexRequestBody(body, resolveModel) {
330
+ const projected = { ...body };
331
+ if (body.reasoning && typeof body.reasoning === "object" && !Array.isArray(body.reasoning)) {
332
+ projected.reasoning = { ...body.reasoning };
333
+ }
334
+ if (Array.isArray(body.input)) {
335
+ projected.input = body.input.map((item) => {
336
+ if (!item || typeof item !== "object" || Array.isArray(item)) return item;
337
+ const record = item;
338
+ return "id" in record || record.type === "tool_search_call" && typeof record.arguments === "string" ? { ...record } : item;
339
+ });
340
+ }
341
+ return normalizeCodexRequestBody(projected, resolveModel);
342
+ }
329
343
  function buildModelResolver(liveSlugs, fallbackSlug) {
330
344
  return (requested) => {
331
345
  const stripped = requested.includes("/") ? requested.slice(requested.indexOf("/") + 1) : requested;
@@ -808,6 +822,12 @@ function opaqueProviderArtifactFingerprints(input) {
808
822
 
809
823
  // src/fetch.ts
810
824
  var CODEX_TRANSPORT_ERROR_HEADER = "x-opengeni-codex-transport-error";
825
+ var CODEX_REQUEST_BODY_NORMALIZED_HEADER = "x-opengeni-request-body-normalized";
826
+ var REPLAYABLE_REQUEST_BODY_FACTORY = Symbol.for("opengeni.replayable-request-body-factory");
827
+ var CODEX_REQUEST_MODEL_HEADER = "x-opengeni-request-model";
828
+ var CODEX_REQUEST_ID_HEADER = "x-opengeni-request-id";
829
+ var CODEX_REQUEST_CALLER_STREAM_HEADER = "x-opengeni-request-caller-stream";
830
+ var MAX_CODEX_ERROR_BODY_BYTES = 64 * 1024;
811
831
  function headersCarryCodexTransportMarker(headers) {
812
832
  if (!headers || typeof headers !== "object") return false;
813
833
  const getter = headers.get;
@@ -894,15 +914,90 @@ function parseCodexUsageHeaders(headers) {
894
914
  checkedAt: new Date(nowMs)
895
915
  };
896
916
  }
897
- async function emitRequestEvent(audit, event) {
898
- await audit.ctx.onModelRequestEvent?.({
917
+ function classifyCodexSseTerminal(ev) {
918
+ if (ev.type === "response.failed") {
919
+ return {
920
+ phase: "failed",
921
+ rawError: ev.response?.error,
922
+ fallbackCode: "response_failed",
923
+ fallbackMessage: "The Codex response failed"
924
+ };
925
+ }
926
+ if (ev.type === "error" || ev.type === "response.error") {
927
+ return {
928
+ phase: "failed",
929
+ rawError: ev.error ?? ev.response?.error ?? ev,
930
+ fallbackCode: "response_error",
931
+ fallbackMessage: "The Codex response stream reported an error"
932
+ };
933
+ }
934
+ if (ev.type === "response.incomplete") {
935
+ const details = ev.response?.incomplete_details;
936
+ const reason = details && typeof details === "object" ? details.reason : void 0;
937
+ return {
938
+ phase: "failed",
939
+ rawError: {
940
+ code: "response_incomplete",
941
+ message: typeof reason === "string" && reason.length > 0 ? `The Codex response was incomplete (${reason})` : "The Codex response was incomplete"
942
+ },
943
+ fallbackCode: "response_incomplete",
944
+ fallbackMessage: "The Codex response was incomplete"
945
+ };
946
+ }
947
+ if (ev.type !== "response.completed" && ev.type !== "response.done") {
948
+ return null;
949
+ }
950
+ if (!ev.response) {
951
+ return null;
952
+ }
953
+ const responseStatus = ev.response.status;
954
+ if (responseStatus !== void 0 && responseStatus !== "completed" || ev.response.error !== null && ev.response.error !== void 0) {
955
+ const incomplete = responseStatus === "incomplete";
956
+ return {
957
+ phase: "failed",
958
+ rawError: ev.response.error,
959
+ fallbackCode: incomplete ? "response_incomplete" : "response_failed",
960
+ fallbackMessage: incomplete ? "The Codex response was incomplete" : "The Codex response failed"
961
+ };
962
+ }
963
+ return { phase: "completed" };
964
+ }
965
+ function markSemanticTerminal(state, phase) {
966
+ if (state.phase === null) {
967
+ state.phase = phase;
968
+ }
969
+ }
970
+ function terminalOutcomeForPhase(phase) {
971
+ if (phase === "completed" || phase === "failed" || phase === "timed_out") {
972
+ return phase;
973
+ }
974
+ return null;
975
+ }
976
+ function requestEventFor(audit, event) {
977
+ return {
899
978
  requestId: audit.requestId,
900
979
  transportAttempt: audit.transportAttempt,
901
980
  ...audit.model ? { model: audit.model } : {},
902
- durationMs: Math.max(0, Date.now() - audit.attemptStartedAt),
981
+ durationMs: Math.max(0, performance.now() - audit.attemptStartedAtMonotonic),
903
982
  timeoutPolicy: audit.policy,
904
983
  ...event
905
- });
984
+ };
985
+ }
986
+ async function emitRequestEvent(audit, event) {
987
+ const terminalOutcome = terminalOutcomeForPhase(event.phase);
988
+ if (terminalOutcome !== null) {
989
+ if (audit.terminalOutcome !== null) {
990
+ return false;
991
+ }
992
+ audit.terminalOutcome = terminalOutcome;
993
+ }
994
+ const observed = requestEventFor(audit, event);
995
+ try {
996
+ audit.ctx.onModelRequestDiagnostic?.(observed);
997
+ } catch {
998
+ }
999
+ await audit.ctx.onModelRequestEvent?.(observed);
1000
+ return true;
906
1001
  }
907
1002
  function providerRequestId(headers) {
908
1003
  return headers.get("x-request-id") ?? headers.get("request-id") ?? void 0;
@@ -943,11 +1038,12 @@ async function fetchBeforeHeaders(base, input, init, audit) {
943
1038
  externalSignal?.removeEventListener("abort", forwardAbort);
944
1039
  }
945
1040
  }
946
- async function observedResponse(res, audit, externalSignal) {
1041
+ async function observedResponse(res, audit, externalSignal, semanticTerminal) {
947
1042
  const requestId = providerRequestId(res.headers);
948
1043
  if (!res.body) {
1044
+ if (semanticTerminal) markSemanticTerminal(semanticTerminal, "failed");
949
1045
  await emitRequestEvent(audit, {
950
- phase: res.ok ? "completed" : "failed",
1046
+ phase: semanticTerminal?.phase ?? (res.ok ? "completed" : "failed"),
951
1047
  responseObserved: true,
952
1048
  status: res.status,
953
1049
  ...requestId ? { providerRequestId: requestId } : {}
@@ -972,17 +1068,19 @@ async function observedResponse(res, audit, externalSignal) {
972
1068
  if (terminal) return;
973
1069
  terminal = true;
974
1070
  clearTimers();
1071
+ const semanticPhase = semanticTerminal?.phase;
1072
+ const phase = semanticPhase ?? "timed_out";
975
1073
  const error = new CodexResponseTimeoutError(klass, audit.requestId, true);
976
1074
  void reader.cancel(error).catch(() => void 0);
977
1075
  void emitRequestEvent(audit, {
978
- phase: "timed_out",
1076
+ phase,
979
1077
  responseObserved: true,
980
- timeoutClass: klass,
1078
+ ...phase === "timed_out" ? { timeoutClass: klass } : {},
981
1079
  status: res.status,
982
1080
  ...requestId ? { providerRequestId: requestId } : {}
983
1081
  }).then(
984
- () => controller.error(error),
985
- () => controller.error(error)
1082
+ () => phase === "completed" ? controller.close() : controller.error(error),
1083
+ () => phase === "completed" ? controller.close() : controller.error(error)
986
1084
  );
987
1085
  };
988
1086
  armIdle = () => {
@@ -1002,13 +1100,13 @@ async function observedResponse(res, audit, externalSignal) {
1002
1100
  const reason = externalSignal?.reason ?? new DOMException("Aborted", "AbortError");
1003
1101
  void reader.cancel(reason).catch(() => void 0);
1004
1102
  void emitRequestEvent(audit, {
1005
- phase: "failed",
1103
+ phase: semanticTerminal?.phase ?? "failed",
1006
1104
  responseObserved: true,
1007
1105
  status: res.status,
1008
1106
  ...requestId ? { providerRequestId: requestId } : {}
1009
1107
  }).then(
1010
- () => controller.error(reason),
1011
- () => controller.error(reason)
1108
+ () => semanticTerminal?.phase === "completed" ? controller.close() : controller.error(reason),
1109
+ () => semanticTerminal?.phase === "completed" ? controller.close() : controller.error(reason)
1012
1110
  );
1013
1111
  };
1014
1112
  if (externalSignal?.aborted) {
@@ -1027,45 +1125,67 @@ async function observedResponse(res, audit, externalSignal) {
1027
1125
  if (chunk.done) {
1028
1126
  terminal = true;
1029
1127
  clearTimers();
1030
- await emitRequestEvent(audit, {
1031
- phase: res.ok ? "completed" : "failed",
1032
- responseObserved: true,
1033
- status: res.status,
1034
- ...requestId ? { providerRequestId: requestId } : {}
1035
- });
1128
+ if (semanticTerminal && semanticTerminal.phase === null) {
1129
+ if (!semanticTerminal.deferTransportTerminal) {
1130
+ markSemanticTerminal(semanticTerminal, "failed");
1131
+ }
1132
+ }
1133
+ if (!semanticTerminal?.deferTransportTerminal || semanticTerminal.phase !== null) {
1134
+ await emitRequestEvent(audit, {
1135
+ phase: semanticTerminal?.phase ?? (res.ok ? "completed" : "failed"),
1136
+ responseObserved: true,
1137
+ status: res.status,
1138
+ ...requestId ? { providerRequestId: requestId } : {}
1139
+ });
1140
+ }
1036
1141
  controller.close();
1037
1142
  return;
1038
1143
  }
1039
- armIdle();
1040
1144
  if (!firstByte) {
1041
1145
  firstByte = true;
1146
+ if (idleTimer) clearTimeout(idleTimer);
1147
+ controller.enqueue(chunk.value);
1042
1148
  await emitRequestEvent(audit, {
1043
1149
  phase: "first_byte",
1044
1150
  responseObserved: true,
1045
1151
  status: res.status,
1046
1152
  ...requestId ? { providerRequestId: requestId } : {}
1047
1153
  });
1154
+ if (!terminal) armIdle();
1155
+ return;
1048
1156
  }
1157
+ armIdle();
1049
1158
  controller.enqueue(chunk.value);
1050
1159
  } catch (error) {
1051
1160
  if (terminal) return;
1052
1161
  terminal = true;
1053
1162
  clearTimers();
1163
+ const semanticPhase = semanticTerminal?.phase;
1164
+ if (semanticTerminal && semanticPhase === null) {
1165
+ markSemanticTerminal(semanticTerminal, "failed");
1166
+ }
1054
1167
  await emitRequestEvent(audit, {
1055
- phase: "failed",
1168
+ phase: semanticPhase ?? "failed",
1056
1169
  responseObserved: true,
1057
1170
  status: res.status,
1058
1171
  ...requestId ? { providerRequestId: requestId } : {}
1059
1172
  });
1060
- controller.error(error);
1173
+ if (semanticPhase === "completed") {
1174
+ controller.close();
1175
+ } else {
1176
+ controller.error(error);
1177
+ }
1061
1178
  }
1062
1179
  },
1063
1180
  async cancel(reason) {
1064
1181
  if (!terminal) {
1065
1182
  terminal = true;
1066
1183
  clearTimers();
1184
+ if (semanticTerminal && semanticTerminal.phase === null) {
1185
+ markSemanticTerminal(semanticTerminal, "failed");
1186
+ }
1067
1187
  await emitRequestEvent(audit, {
1068
- phase: "failed",
1188
+ phase: semanticTerminal?.phase ?? "failed",
1069
1189
  responseObserved: true,
1070
1190
  status: res.status,
1071
1191
  ...requestId ? { providerRequestId: requestId } : {}
@@ -1113,11 +1233,19 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
1113
1233
  const rawUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
1114
1234
  const rewritten = rawUrl.replace(/(?<!\/codex)\/responses(\b|$)/, "/codex/responses$1");
1115
1235
  const policy = resolveCodexResponseTimeoutPolicy(ctx.responseTimeoutPolicy);
1116
- const requestId = ctx.nextRequestId?.() ?? randomUUID();
1236
+ const handedRequestId = new Headers(init?.headers).get(CODEX_REQUEST_ID_HEADER);
1237
+ const requestId = handedRequestId ?? ctx.nextRequestId?.() ?? randomUUID();
1117
1238
  const logicalStartedAt = Date.now();
1118
1239
  let transportAttempt = 0;
1119
1240
  const attempt = async (auth, authenticationAttempt) => {
1120
1241
  const headers = new Headers(init?.headers);
1242
+ const bodyAlreadyNormalized = headers.get(CODEX_REQUEST_BODY_NORMALIZED_HEADER) === "1";
1243
+ const normalizedModel = headers.get(CODEX_REQUEST_MODEL_HEADER) ?? void 0;
1244
+ const normalizedCallerStream = headers.get(CODEX_REQUEST_CALLER_STREAM_HEADER);
1245
+ headers.delete(CODEX_REQUEST_BODY_NORMALIZED_HEADER);
1246
+ headers.delete(CODEX_REQUEST_MODEL_HEADER);
1247
+ headers.delete(CODEX_REQUEST_ID_HEADER);
1248
+ headers.delete(CODEX_REQUEST_CALLER_STREAM_HEADER);
1121
1249
  headers.set("Authorization", `Bearer ${auth.accessToken}`);
1122
1250
  if (auth.chatgptAccountId) {
1123
1251
  headers.set("ChatGPT-Account-ID", auth.chatgptAccountId);
@@ -1141,11 +1269,16 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
1141
1269
  if (ctx.turnMetadata && Object.keys(ctx.turnMetadata).length > 0) {
1142
1270
  headers.set("x-codex-turn-metadata", JSON.stringify(ctx.turnMetadata));
1143
1271
  }
1144
- let callerWantsStream = true;
1145
- let model;
1272
+ let callerWantsStream = bodyAlreadyNormalized ? normalizedCallerStream !== "0" : true;
1273
+ let model = normalizedModel;
1146
1274
  let requestOpaqueArtifacts = [];
1147
- const nextInit = { ...init, headers };
1148
- if (typeof init?.body === "string") {
1275
+ const replayableBodyFactory = init?.[REPLAYABLE_REQUEST_BODY_FACTORY];
1276
+ const nextInit = {
1277
+ ...init,
1278
+ headers,
1279
+ ...replayableBodyFactory ? { body: replayableBodyFactory() } : {}
1280
+ };
1281
+ if (!bodyAlreadyNormalized && typeof init?.body === "string") {
1149
1282
  try {
1150
1283
  const parsed = JSON.parse(init.body);
1151
1284
  callerWantsStream = parsed.stream === true;
@@ -1154,9 +1287,14 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
1154
1287
  nextInit.body = JSON.stringify(normalized);
1155
1288
  requestOpaqueArtifacts = opaqueProviderArtifactFingerprints(normalized.input);
1156
1289
  } catch {
1290
+ throw new Error("Model request could not be prepared");
1157
1291
  }
1292
+ } else if (!bodyAlreadyNormalized) {
1293
+ throw new Error("Model request could not be prepared");
1294
+ }
1295
+ if (!bodyAlreadyNormalized) {
1296
+ ctx.onRequestOpaqueArtifacts?.({ requestId, fingerprints: requestOpaqueArtifacts });
1158
1297
  }
1159
- ctx.onRequestOpaqueArtifacts?.({ requestId, fingerprints: requestOpaqueArtifacts });
1160
1298
  headers.set(
1161
1299
  "Idempotency-Key",
1162
1300
  authenticationAttempt === 0 ? requestId : `${requestId}:auth-${authenticationAttempt}`
@@ -1177,13 +1315,18 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
1177
1315
  transportAttempt,
1178
1316
  ...model ? { model } : {},
1179
1317
  logicalStartedAt,
1180
- attemptStartedAt: Date.now(),
1181
- policy
1318
+ attemptStartedAtMonotonic: performance.now(),
1319
+ policy,
1320
+ terminalOutcome: null
1182
1321
  };
1183
1322
  await emitRequestEvent(audit, {
1184
1323
  phase: "started",
1185
1324
  responseObserved: false
1186
1325
  });
1326
+ const semanticTerminal = {
1327
+ phase: null,
1328
+ deferTransportTerminal: !callerWantsStream
1329
+ };
1187
1330
  try {
1188
1331
  res = await fetchBeforeHeaders(base, rewritten, nextInit, audit);
1189
1332
  const upstreamRequestId = providerRequestId(res.headers);
@@ -1193,8 +1336,7 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
1193
1336
  status: res.status,
1194
1337
  ...upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}
1195
1338
  });
1196
- const observed = await observedResponse(res, audit, nextInit.signal);
1197
- res = observed;
1339
+ res = await observedResponse(res, audit, nextInit.signal, semanticTerminal);
1198
1340
  } catch (error) {
1199
1341
  if (nextInit.signal?.aborted) {
1200
1342
  await emitRequestEvent(audit, {
@@ -1231,9 +1373,25 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
1231
1373
  });
1232
1374
  }
1233
1375
  if (!res.ok) {
1234
- return await bufferCodexErrorResponse(res);
1376
+ const buffered = await bufferCodexErrorResponse(res);
1377
+ const upstreamRequestId = providerRequestId(res.headers);
1378
+ markSemanticTerminal(semanticTerminal, "failed");
1379
+ await emitRequestEvent(audit, {
1380
+ phase: "failed",
1381
+ responseObserved: true,
1382
+ status: res.status,
1383
+ ...upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}
1384
+ }).catch(() => void 0);
1385
+ return buffered;
1235
1386
  }
1236
- return callerWantsStream ? repairCodexStream(res) : await sseToJsonResponse(res);
1387
+ if (callerWantsStream) {
1388
+ res = validateCodexStream(res, (phase) => {
1389
+ markSemanticTerminal(semanticTerminal, phase);
1390
+ });
1391
+ } else {
1392
+ res = await sseToJsonResponse(res, audit, semanticTerminal);
1393
+ }
1394
+ return res;
1237
1395
  };
1238
1396
  try {
1239
1397
  let res = await attempt(await ctx.getToken(), 0);
@@ -1271,28 +1429,77 @@ function classifyCodexUsageLimitError(error) {
1271
1429
  return null;
1272
1430
  }
1273
1431
  async function bufferCodexErrorResponse(res) {
1274
- const bodyText = await res.text().catch(() => "");
1432
+ const { text: bodyText, truncated } = await readBoundedResponseText(
1433
+ res,
1434
+ MAX_CODEX_ERROR_BODY_BYTES
1435
+ );
1275
1436
  const headers = new Headers(res.headers);
1276
1437
  headers.set("content-type", "application/json");
1277
1438
  headers.set(CODEX_TRANSPORT_ERROR_HEADER, "1");
1278
1439
  headers.delete("content-length");
1279
1440
  headers.delete("content-encoding");
1280
1441
  let errorType;
1442
+ let responseBody = bodyText;
1281
1443
  try {
1282
1444
  const parsed = JSON.parse(bodyText);
1283
1445
  errorType = typeof parsed.error?.type === "string" ? parsed.error.type : void 0;
1284
1446
  } catch {
1285
1447
  }
1448
+ if (truncated) {
1449
+ responseBody = JSON.stringify({
1450
+ error: {
1451
+ type: "provider_error_body_too_large",
1452
+ code: "provider_error_body_too_large",
1453
+ message: `The provider returned an error body larger than ${MAX_CODEX_ERROR_BODY_BYTES} bytes`
1454
+ }
1455
+ });
1456
+ headers.set("x-opengeni-provider-error-truncated", "1");
1457
+ }
1286
1458
  if (errorType === CODEX_USAGE_LIMIT_ERROR_TYPE) {
1287
1459
  headers.set("x-should-retry", "false");
1288
1460
  }
1289
- return new Response(bodyText, {
1461
+ return new Response(responseBody, {
1290
1462
  status: res.status,
1291
1463
  statusText: res.statusText,
1292
1464
  headers
1293
1465
  });
1294
1466
  }
1295
- async function sseToJsonResponse(res) {
1467
+ async function readBoundedResponseText(response, maxBytes) {
1468
+ if (!response.body) return { text: "", truncated: false };
1469
+ const reader = response.body.getReader();
1470
+ const decoder = new TextDecoder();
1471
+ const parts = [];
1472
+ let bytes = 0;
1473
+ let truncated = false;
1474
+ try {
1475
+ while (bytes < maxBytes) {
1476
+ const next = await reader.read();
1477
+ if (next.done) {
1478
+ parts.push(decoder.decode());
1479
+ return { text: parts.join(""), truncated };
1480
+ }
1481
+ const remaining = maxBytes - bytes;
1482
+ const accepted = next.value.byteLength > remaining ? next.value.subarray(0, remaining) : next.value;
1483
+ bytes += accepted.byteLength;
1484
+ parts.push(decoder.decode(accepted, { stream: true }));
1485
+ if (accepted.byteLength !== next.value.byteLength) {
1486
+ truncated = true;
1487
+ break;
1488
+ }
1489
+ if (bytes >= maxBytes) {
1490
+ truncated = true;
1491
+ break;
1492
+ }
1493
+ }
1494
+ } catch {
1495
+ truncated = true;
1496
+ } finally {
1497
+ if (truncated) void reader.cancel().catch(() => void 0);
1498
+ }
1499
+ return { text: parts.join(""), truncated };
1500
+ }
1501
+ async function sseToJsonResponse(res, audit, semanticTerminal) {
1502
+ const upstreamRequestId = providerRequestId(res.headers);
1296
1503
  const text = await res.text();
1297
1504
  let final = null;
1298
1505
  let terminalError = null;
@@ -1305,57 +1512,45 @@ async function sseToJsonResponse(res) {
1305
1512
  const ev = JSON.parse(data);
1306
1513
  if (ev.type === "response.output_item.done" && ev.item !== void 0) {
1307
1514
  items.push(ev.item);
1308
- } else if (ev.type === "response.failed") {
1309
- terminalError = codexSseFailureResponse(
1310
- res,
1311
- ev.response?.error,
1312
- "response_failed",
1313
- "The Codex response failed",
1314
- {
1315
- eventType: ev.type,
1316
- responseId: ev.response?.id,
1317
- responseStatus: ev.response?.status
1318
- }
1319
- );
1320
- } else if (ev.type === "error" || ev.type === "response.error") {
1321
- terminalError = codexSseFailureResponse(
1322
- res,
1323
- ev.error ?? ev.response?.error ?? ev,
1324
- "response_error",
1325
- "The Codex response stream reported an error",
1326
- {
1327
- eventType: ev.type,
1328
- responseId: ev.response?.id,
1329
- responseStatus: ev.response?.status
1330
- }
1331
- );
1332
- } else if (ev.type === "response.incomplete") {
1333
- const details = ev.response?.incomplete_details;
1334
- const reason = details && typeof details === "object" ? details.reason : void 0;
1335
- terminalError = codexSseFailureResponse(
1336
- res,
1337
- {
1338
- code: "response_incomplete",
1339
- message: typeof reason === "string" && reason.length > 0 ? `The Codex response was incomplete (${reason})` : "The Codex response was incomplete"
1340
- },
1341
- "response_incomplete",
1342
- "The Codex response was incomplete",
1343
- {
1344
- eventType: ev.type,
1345
- responseId: ev.response?.id,
1346
- responseStatus: ev.response?.status
1347
- }
1348
- );
1349
- } else if (ev.type === "response.completed" || ev.type === "response.done") {
1350
- final = ev.response ?? null;
1515
+ } else {
1516
+ const terminal = classifyCodexSseTerminal(ev);
1517
+ if (terminal?.phase === "failed") {
1518
+ terminalError = codexSseFailureResponse(
1519
+ res,
1520
+ terminal.rawError,
1521
+ terminal.fallbackCode,
1522
+ terminal.fallbackMessage,
1523
+ {
1524
+ eventType: ev.type,
1525
+ responseId: ev.response?.id,
1526
+ responseStatus: ev.response?.status
1527
+ }
1528
+ );
1529
+ } else if (terminal?.phase === "completed") {
1530
+ final = ev.response ?? null;
1531
+ }
1351
1532
  }
1352
1533
  } catch {
1353
1534
  }
1354
1535
  }
1355
1536
  if (terminalError) {
1537
+ markSemanticTerminal(semanticTerminal, "failed");
1538
+ await emitRequestEvent(audit, {
1539
+ phase: "failed",
1540
+ responseObserved: true,
1541
+ status: res.status,
1542
+ ...upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}
1543
+ });
1356
1544
  return terminalError;
1357
1545
  }
1358
1546
  if (!final) {
1547
+ markSemanticTerminal(semanticTerminal, "failed");
1548
+ await emitRequestEvent(audit, {
1549
+ phase: "failed",
1550
+ responseObserved: true,
1551
+ status: res.status,
1552
+ ...upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}
1553
+ });
1359
1554
  return codexSseFailureResponse(
1360
1555
  res,
1361
1556
  null,
@@ -1371,6 +1566,13 @@ async function sseToJsonResponse(res) {
1371
1566
  `[codex-debug] sse->json items=${items.length} outputLen=${Array.isArray(final?.output) ? final.output.length : "?"}`
1372
1567
  );
1373
1568
  }
1569
+ markSemanticTerminal(semanticTerminal, "completed");
1570
+ await emitRequestEvent(audit, {
1571
+ phase: "completed",
1572
+ responseObserved: true,
1573
+ status: res.status,
1574
+ ...upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}
1575
+ });
1374
1576
  const headers = new Headers(res.headers);
1375
1577
  headers.set("content-type", "application/json");
1376
1578
  headers.delete("content-length");
@@ -1523,8 +1725,9 @@ function codexSseFailureError(source, rawError, fallbackCode, publicMessage, met
1523
1725
  codexSseFailureProjection(source, rawError, fallbackCode, publicMessage, metadata)
1524
1726
  );
1525
1727
  }
1526
- function repairCodexStream(res) {
1728
+ function validateCodexStream(res, onSemanticTerminal) {
1527
1729
  if (!res.body) {
1730
+ onSemanticTerminal?.("failed");
1528
1731
  const error = codexSseFailureError(
1529
1732
  res,
1530
1733
  null,
@@ -1544,7 +1747,6 @@ function repairCodexStream(res) {
1544
1747
  headers: headers2
1545
1748
  });
1546
1749
  }
1547
- const items = [];
1548
1750
  const decoder = new TextDecoder();
1549
1751
  const encoder = new TextEncoder();
1550
1752
  let buffer = "";
@@ -1555,9 +1757,8 @@ function repairCodexStream(res) {
1555
1757
  const block = buffer.slice(0, boundary.start);
1556
1758
  const separator = buffer.slice(boundary.start, boundary.end);
1557
1759
  buffer = buffer.slice(boundary.end);
1558
- const patched = patchSseBlock(block, items, res);
1559
- successfulTerminalSeen ||= patched.successfulTerminal;
1560
- controller.enqueue(encoder.encode(`${patched.block}${separator}`));
1760
+ successfulTerminalSeen ||= inspectCodexSseBlock(block, res, onSemanticTerminal);
1761
+ controller.enqueue(encoder.encode(`${block}${separator}`));
1561
1762
  boundary = findSseBlockBoundary(buffer, final);
1562
1763
  }
1563
1764
  };
@@ -1570,9 +1771,8 @@ function repairCodexStream(res) {
1570
1771
  buffer += decoder.decode();
1571
1772
  emitCompleteBlocks(controller, true);
1572
1773
  if (buffer.length > 0) {
1573
- const patched = patchSseBlock(buffer, items, res);
1574
- successfulTerminalSeen ||= patched.successfulTerminal;
1575
- controller.enqueue(encoder.encode(patched.block));
1774
+ successfulTerminalSeen ||= inspectCodexSseBlock(buffer, res, onSemanticTerminal);
1775
+ controller.enqueue(encoder.encode(buffer));
1576
1776
  buffer = "";
1577
1777
  }
1578
1778
  if (!successfulTerminalSeen) {
@@ -1613,59 +1813,37 @@ function sseLineEndingEnd(value, index, final) {
1613
1813
  }
1614
1814
  return final ? index + 1 : null;
1615
1815
  }
1616
- function patchSseBlock(block, items, source) {
1816
+ var CODEX_TERMINAL_TYPE_HINTS = [
1817
+ '"response.completed"',
1818
+ '"response.done"',
1819
+ '"response.failed"',
1820
+ '"response.incomplete"',
1821
+ '"response.error"',
1822
+ '"error"'
1823
+ ];
1824
+ function inspectCodexSseBlock(block, source, onSemanticTerminal) {
1617
1825
  const lines = block.split(/\r\n|\r|\n/);
1618
1826
  const dataStr = lines.filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim()).join("\n");
1619
1827
  if (!dataStr || dataStr === "[DONE]") {
1620
- return { block, successfulTerminal: false };
1828
+ return false;
1829
+ }
1830
+ if (!CODEX_TERMINAL_TYPE_HINTS.some((terminalType) => dataStr.includes(terminalType))) {
1831
+ return false;
1621
1832
  }
1622
1833
  let ev;
1623
1834
  try {
1624
1835
  ev = JSON.parse(dataStr);
1625
1836
  } catch {
1626
- return { block, successfulTerminal: false };
1627
- }
1628
- if (ev.type === "response.output_item.done" && ev.item !== void 0) {
1629
- items.push(ev.item);
1630
- return { block, successfulTerminal: false };
1631
- }
1632
- if (ev.type === "response.failed") {
1633
- throw codexSseFailureError(
1634
- source,
1635
- ev.response?.error,
1636
- "response_failed",
1637
- "The Codex response failed",
1638
- {
1639
- eventType: ev.type,
1640
- responseId: ev.response?.id,
1641
- responseStatus: ev.response?.status
1642
- }
1643
- );
1644
- }
1645
- if (ev.type === "error" || ev.type === "response.error") {
1646
- throw codexSseFailureError(
1647
- source,
1648
- ev.error ?? ev.response?.error ?? ev,
1649
- "response_error",
1650
- "The Codex response stream reported an error",
1651
- {
1652
- eventType: ev.type,
1653
- responseId: ev.response?.id,
1654
- responseStatus: ev.response?.status
1655
- }
1656
- );
1837
+ return false;
1657
1838
  }
1658
- if (ev.type === "response.incomplete") {
1659
- const details = ev.response?.incomplete_details;
1660
- const reason = details && typeof details === "object" ? details.reason : void 0;
1839
+ const terminal = classifyCodexSseTerminal(ev);
1840
+ if (terminal?.phase === "failed") {
1841
+ onSemanticTerminal?.("failed");
1661
1842
  throw codexSseFailureError(
1662
1843
  source,
1663
- {
1664
- code: "response_incomplete",
1665
- message: typeof reason === "string" && reason.length > 0 ? `The Codex response was incomplete (${reason})` : "The Codex response was incomplete"
1666
- },
1667
- "response_incomplete",
1668
- "The Codex response was incomplete",
1844
+ terminal.rawError,
1845
+ terminal.fallbackCode,
1846
+ terminal.fallbackMessage,
1669
1847
  {
1670
1848
  eventType: ev.type,
1671
1849
  responseId: ev.response?.id,
@@ -1673,33 +1851,11 @@ function patchSseBlock(block, items, source) {
1673
1851
  }
1674
1852
  );
1675
1853
  }
1676
- if ((ev.type === "response.completed" || ev.type === "response.done") && ev.response) {
1677
- if (ev.response.status === "failed" || ev.response.status === "incomplete" || ev.response.error !== null && ev.response.error !== void 0) {
1678
- throw codexSseFailureError(
1679
- source,
1680
- ev.response.error,
1681
- ev.response.status === "incomplete" ? "response_incomplete" : "response_failed",
1682
- ev.response.status === "incomplete" ? "The Codex response was incomplete" : "The Codex response failed",
1683
- {
1684
- eventType: ev.type,
1685
- responseId: ev.response.id,
1686
- responseStatus: ev.response.status
1687
- }
1688
- );
1689
- }
1690
- const out = ev.response.output;
1691
- if ((!Array.isArray(out) || out.length === 0) && items.length > 0) {
1692
- ev.response = { ...ev.response, output: items };
1693
- const nonData = lines.filter((l) => !l.startsWith("data:"));
1694
- const lineEnding = block.match(/\r\n|\r|\n/)?.[0] ?? "\n";
1695
- return {
1696
- block: [...nonData, `data: ${JSON.stringify(ev)}`].join(lineEnding),
1697
- successfulTerminal: true
1698
- };
1699
- }
1700
- return { block, successfulTerminal: true };
1854
+ if (terminal?.phase === "completed") {
1855
+ onSemanticTerminal?.("completed");
1856
+ return true;
1701
1857
  }
1702
- return { block, successfulTerminal: false };
1858
+ return false;
1703
1859
  }
1704
1860
 
1705
1861
  // src/mcp-sanitize.ts
@@ -1962,7 +2118,13 @@ function boundModelToolOutputItem(item, policyTokens = DEFAULT_MODEL_TOOL_OUTPUT
1962
2118
  return boundedOutput === item.output ? item : { ...item, output: boundedOutput };
1963
2119
  }
1964
2120
  function boundModelToolOutputItems(items, policyTokens = DEFAULT_MODEL_TOOL_OUTPUT_TRUNCATION_TOKENS) {
1965
- return items.map((item) => boundModelToolOutputItem(item, policyTokens));
2121
+ let bounded = null;
2122
+ for (const [index, item] of items.entries()) {
2123
+ const next = boundModelToolOutputItem(item, policyTokens);
2124
+ if (next !== item && bounded === null) bounded = items.slice(0, index);
2125
+ bounded?.push(next);
2126
+ }
2127
+ return bounded ?? items;
1966
2128
  }
1967
2129
  function boundToolOutputValue(output, budgetTokens) {
1968
2130
  const state = modelOutputBoundState(budgetTokens);
@@ -2277,6 +2439,161 @@ function isImageDataUrl(value) {
2277
2439
  return /^data:image\/[a-z0-9.+-]+;base64,/i.test(value);
2278
2440
  }
2279
2441
 
2442
+ // src/images.ts
2443
+ import { pinnedFetch, readJsonBase64Field, readResponseTextBounded } from "@opengeni/network";
2444
+ var CODEX_IMAGE_MODEL = "gpt-image-2";
2445
+ var CODEX_IMAGE_RESPONSE_MAX_BYTES = 90 * 1024 * 1024;
2446
+ var CODEX_IMAGE_ERROR_MAX_BYTES = 64 * 1024;
2447
+ var CODEX_IMAGE_MAX_BYTES = 64 * 1024 * 1024;
2448
+ var CODEX_IMAGE_REQUEST_TIMEOUT_MS = 5 * 6e4;
2449
+ var CODEX_IMAGE_MAX_REFERENCES = 5;
2450
+ var codexImageFetch = async (input, init) => await pinnedFetch(
2451
+ input,
2452
+ init,
2453
+ {
2454
+ environment: "production",
2455
+ integrationsAllowPrivateNetworkTargets: false
2456
+ },
2457
+ {
2458
+ label: "Codex image generation",
2459
+ requireHttpsOutsideLocalTest: true
2460
+ }
2461
+ );
2462
+ var CodexImageApiError = class extends Error {
2463
+ constructor(status, message) {
2464
+ super(message);
2465
+ this.status = status;
2466
+ this.name = "CodexImageApiError";
2467
+ }
2468
+ };
2469
+ var CodexImageRequestTimeoutError = class extends Error {
2470
+ constructor(timeoutMs) {
2471
+ super(`Codex image generation timed out after ${Math.ceil(timeoutMs / 1e3)} seconds`);
2472
+ this.timeoutMs = timeoutMs;
2473
+ this.name = "CodexImageRequestTimeoutError";
2474
+ }
2475
+ };
2476
+ async function generateCodexSubscriptionImage(input) {
2477
+ const fetchImpl = input.fetch ?? codexImageFetch;
2478
+ const timeoutMs = input.requestTimeoutMs ?? CODEX_IMAGE_REQUEST_TIMEOUT_MS;
2479
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
2480
+ throw new RangeError("Codex image request timeout must be a positive safe integer");
2481
+ }
2482
+ const references = input.references ?? [];
2483
+ if (references.length > CODEX_IMAGE_MAX_REFERENCES) {
2484
+ throw new RangeError(
2485
+ `Codex image editing accepts at most ${CODEX_IMAGE_MAX_REFERENCES} images`
2486
+ );
2487
+ }
2488
+ for (const reference of references) {
2489
+ if (reference.bytes.byteLength === 0) throw new Error("Codex image reference is empty");
2490
+ }
2491
+ const deadline = new AbortController();
2492
+ const timer = setTimeout(
2493
+ () => deadline.abort(new CodexImageRequestTimeoutError(timeoutMs)),
2494
+ timeoutMs
2495
+ );
2496
+ const signal = input.abortSignal ? AbortSignal.any([input.abortSignal, deadline.signal]) : deadline.signal;
2497
+ const request = async (auth) => {
2498
+ const headers = codexImageHeaders(auth, input.context.clientVersion, input.turnId);
2499
+ return await fetchImpl(
2500
+ `${CODEX_RESPONSES_BASE}/${references.length > 0 ? "images/edits" : "images/generations"}`,
2501
+ {
2502
+ method: "POST",
2503
+ redirect: "error",
2504
+ headers,
2505
+ body: JSON.stringify(
2506
+ references.length > 0 ? {
2507
+ images: references.map((reference) => ({
2508
+ image_url: `data:${reference.mediaType};base64,${Buffer.from(reference.bytes).toString("base64")}`
2509
+ })),
2510
+ prompt: input.prompt,
2511
+ background: "auto",
2512
+ model: CODEX_IMAGE_MODEL,
2513
+ quality: "auto",
2514
+ size: "auto"
2515
+ } : {
2516
+ prompt: input.prompt,
2517
+ background: "auto",
2518
+ model: CODEX_IMAGE_MODEL,
2519
+ quality: "auto",
2520
+ size: "auto"
2521
+ }
2522
+ ),
2523
+ signal
2524
+ }
2525
+ );
2526
+ };
2527
+ const operation = (async () => {
2528
+ let response = await request(await input.context.getToken());
2529
+ if (response.status === 401) {
2530
+ await response.body?.cancel().catch(() => void 0);
2531
+ response = await request(await input.context.refresh());
2532
+ }
2533
+ if (!response.ok) {
2534
+ const detail = await readResponseTextBounded(
2535
+ response,
2536
+ CODEX_IMAGE_ERROR_MAX_BYTES,
2537
+ "Codex image error",
2538
+ { signal }
2539
+ ).catch(() => "");
2540
+ throw new CodexImageApiError(
2541
+ response.status,
2542
+ detail ? `Codex image generation failed (${response.status}): ${boundedErrorMessage(detail)}` : `Codex image generation failed (${response.status})`
2543
+ );
2544
+ }
2545
+ const bytes = await readJsonBase64Field(response, {
2546
+ fieldName: "b64_json",
2547
+ shape: "string",
2548
+ maxResponseBytes: CODEX_IMAGE_RESPONSE_MAX_BYTES,
2549
+ maxDecodedBytes: CODEX_IMAGE_MAX_BYTES,
2550
+ label: "Codex image generation",
2551
+ signal
2552
+ });
2553
+ return { bytes, declaredMediaType: "image/png" };
2554
+ })();
2555
+ let removeAbortListener = () => void 0;
2556
+ const aborted = new Promise((_resolve, reject) => {
2557
+ const onAbort = () => reject(signal.reason);
2558
+ if (signal.aborted) {
2559
+ onAbort();
2560
+ return;
2561
+ }
2562
+ signal.addEventListener("abort", onAbort, { once: true });
2563
+ removeAbortListener = () => signal.removeEventListener("abort", onAbort);
2564
+ });
2565
+ try {
2566
+ return await Promise.race([operation, aborted]);
2567
+ } finally {
2568
+ removeAbortListener();
2569
+ clearTimeout(timer);
2570
+ }
2571
+ }
2572
+ function codexImageHeaders(auth, clientVersion, turnId) {
2573
+ const headers = new Headers({
2574
+ Authorization: `Bearer ${auth.accessToken}`,
2575
+ accept: "application/json",
2576
+ "content-type": "application/json",
2577
+ originator: CODEX_ORIGINATOR,
2578
+ "User-Agent": `${CODEX_ORIGINATOR}/${clientVersion || CODEX_CLIENT_VERSION}`,
2579
+ version: clientVersion || CODEX_CLIENT_VERSION,
2580
+ "x-codex-image-turn-id": turnId
2581
+ });
2582
+ if (auth.chatgptAccountId) headers.set("ChatGPT-Account-ID", auth.chatgptAccountId);
2583
+ if (auth.isFedramp) headers.set("X-OpenAI-Fedramp", "true");
2584
+ return headers;
2585
+ }
2586
+ function boundedErrorMessage(body) {
2587
+ let message = body;
2588
+ try {
2589
+ const value = JSON.parse(body);
2590
+ const candidate = value.error?.message ?? value.message;
2591
+ if (typeof candidate === "string") message = candidate;
2592
+ } catch {
2593
+ }
2594
+ return message.replace(/\s+/g, " ").trim().slice(0, 1e3);
2595
+ }
2596
+
2280
2597
  // src/realtime.ts
2281
2598
  var CODEX_REALTIME_CALL_URL = `${CODEX_RESPONSES_BASE}/realtime/calls?intent=quicksilver&architecture=avas`;
2282
2599
  var MAX_REALTIME_SDP_BYTES = 1024 * 1024;
@@ -2578,6 +2895,10 @@ export {
2578
2895
  CODEX_REALTIME_VOICES,
2579
2896
  CODEX_REFRESH_FALLBACK_MS,
2580
2897
  CODEX_REFRESH_WINDOW_MS,
2898
+ CODEX_REQUEST_BODY_NORMALIZED_HEADER,
2899
+ CODEX_REQUEST_CALLER_STREAM_HEADER,
2900
+ CODEX_REQUEST_ID_HEADER,
2901
+ CODEX_REQUEST_MODEL_HEADER,
2581
2902
  CODEX_RESPONSES_BASE,
2582
2903
  CODEX_RESPONSE_HEADERS_TIMEOUT_MS,
2583
2904
  CODEX_RESPONSE_NO_BYTE_RETRIES,
@@ -2593,6 +2914,8 @@ export {
2593
2914
  CODEX_WEEKLY_WINDOW_SECONDS,
2594
2915
  CODEX_WHAM_BASE,
2595
2916
  CodexDeviceError,
2917
+ CodexImageApiError,
2918
+ CodexImageRequestTimeoutError,
2596
2919
  CodexRealtimeError,
2597
2920
  CodexRefreshTransient,
2598
2921
  CodexReloginRequired,
@@ -2626,12 +2949,14 @@ export {
2626
2949
  fetchCodexModels,
2627
2950
  fetchCodexRateLimitResetCredits,
2628
2951
  fetchCodexUsage,
2952
+ generateCodexSubscriptionImage,
2629
2953
  isCodexBilledModel,
2630
2954
  isCodexTransportError,
2631
2955
  isPreHeadersTimeoutError,
2632
2956
  modelToolOutputSerializationBudgetTokens,
2633
2957
  normalizeCodexRequestBody,
2634
2958
  normalizeCodexUsage,
2959
+ normalizedCodexRequestBody,
2635
2960
  opaqueProviderArtifactFingerprint,
2636
2961
  opaqueProviderArtifactFingerprints,
2637
2962
  parseCodexRateLimitResetConsumeResponse,