@opengeni/codex 0.2.13 → 0.2.15

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,5 +1,5 @@
1
- import type { CodexFetch } from "./device-code";
2
- import { type CodexRateLimitResetConsumeResponse, type CodexRateLimitResetCreditsDetails } from "./reset-credits";
1
+ import type { CodexFetch } from "./device-code.js";
2
+ import { type CodexRateLimitResetConsumeResponse, type CodexRateLimitResetCreditsDetails } from "./reset-credits.js";
3
3
  export type CodexAuthHeaders = {
4
4
  accessToken: string;
5
5
  chatgptAccountId: string | null;
package/dist/fetch.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type CodexUsageHeaderSnapshot } from "./request-context";
1
+ import { type CodexUsageHeaderSnapshot } from "./request-context.js";
2
2
  export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
3
3
  /**
4
4
  * Internal provenance marker copied onto buffered non-OK Codex responses.
@@ -7,6 +7,14 @@ export type FetchLike = (input: string | URL | Request, init?: RequestInit) => P
7
7
  * error that happened during the same Codex turn.
8
8
  */
9
9
  export declare const CODEX_TRANSPORT_ERROR_HEADER = "x-opengeni-codex-transport-error";
10
+ /** Internal transport handoff; always removed before network I/O. */
11
+ export declare const CODEX_REQUEST_BODY_NORMALIZED_HEADER = "x-opengeni-request-body-normalized";
12
+ /** Internal resolved-model handoff; always removed before network I/O. */
13
+ export declare const CODEX_REQUEST_MODEL_HEADER = "x-opengeni-request-model";
14
+ /** Internal durable request-identity handoff; always removed before network I/O. */
15
+ export declare const CODEX_REQUEST_ID_HEADER = "x-opengeni-request-id";
16
+ /** Internal original response-mode handoff; always removed before network I/O. */
17
+ export declare const CODEX_REQUEST_CALLER_STREAM_HEADER = "x-opengeni-request-caller-stream";
10
18
  /** True only for an error produced from this Codex transport's non-OK response. */
11
19
  export declare function isCodexTransportError(error: unknown): boolean;
12
20
  export type CodexEncryptedArtifactRejection = {
@@ -0,0 +1,34 @@
1
+ import type { CodexRequestContext } from "./request-context.js";
2
+ import type { FetchLike } from "./fetch.js";
3
+ export type CodexGeneratedImage = {
4
+ bytes: Uint8Array;
5
+ declaredMediaType: "image/png";
6
+ };
7
+ export type CodexImageReferenceInput = Readonly<{
8
+ mediaType: "image/png" | "image/jpeg" | "image/webp";
9
+ bytes: Uint8Array;
10
+ }>;
11
+ export declare class CodexImageApiError extends Error {
12
+ readonly status: number;
13
+ constructor(status: number, message: string);
14
+ }
15
+ export declare class CodexImageRequestTimeoutError extends Error {
16
+ readonly timeoutMs: number;
17
+ constructor(timeoutMs: number);
18
+ }
19
+ /**
20
+ * Execute Codex's standalone, client-side image tool against the same
21
+ * ChatGPT/Codex account as the owning model turn. Only a definitive 401 is
22
+ * retried, after refreshing auth; ambiguous transport/5xx outcomes are never
23
+ * replayed because an image request may already have incurred work or cost.
24
+ */
25
+ export declare function generateCodexSubscriptionImage(input: {
26
+ prompt: string;
27
+ references?: readonly CodexImageReferenceInput[];
28
+ turnId: string;
29
+ context: Pick<CodexRequestContext, "clientVersion" | "getToken" | "refresh">;
30
+ abortSignal?: AbortSignal;
31
+ fetch?: FetchLike;
32
+ /** Internal test/host override; one absolute budget covers auth retry and body streaming. */
33
+ requestTimeoutMs?: number;
34
+ }): Promise<CodexGeneratedImage>;
package/dist/index.d.ts CHANGED
@@ -1,16 +1,17 @@
1
- export * from "./constants";
2
- export * from "./billing";
3
- export * from "./device-code";
4
- export * from "./refresh";
5
- export * from "./normalize";
6
- export * from "./usage-normalize";
7
- export * from "./reset-credits";
8
- export * from "./api-client";
9
- export * from "./request-context";
10
- export * from "./response-timeout";
11
- export * from "./fetch";
12
- export * from "./mcp-sanitize";
13
- export * from "./model-output-truncation";
14
- export * from "./opaque-artifact";
15
- export * from "./realtime";
16
- export * from "./realtime-v3";
1
+ export * from "./constants.js";
2
+ export * from "./billing.js";
3
+ export * from "./device-code.js";
4
+ export * from "./refresh.js";
5
+ export * from "./normalize.js";
6
+ export * from "./usage-normalize.js";
7
+ export * from "./reset-credits.js";
8
+ export * from "./api-client.js";
9
+ export * from "./request-context.js";
10
+ export * from "./response-timeout.js";
11
+ export * from "./fetch.js";
12
+ export * from "./mcp-sanitize.js";
13
+ export * from "./model-output-truncation.js";
14
+ export * from "./opaque-artifact.js";
15
+ export * from "./images.js";
16
+ export * from "./realtime.js";
17
+ export * from "./realtime-v3.js";
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;
@@ -1036,16 +1056,20 @@ async function observedResponse(res, audit, externalSignal) {
1036
1056
  controller.close();
1037
1057
  return;
1038
1058
  }
1039
- armIdle();
1040
1059
  if (!firstByte) {
1041
1060
  firstByte = true;
1061
+ if (idleTimer) clearTimeout(idleTimer);
1062
+ controller.enqueue(chunk.value);
1042
1063
  await emitRequestEvent(audit, {
1043
1064
  phase: "first_byte",
1044
1065
  responseObserved: true,
1045
1066
  status: res.status,
1046
1067
  ...requestId ? { providerRequestId: requestId } : {}
1047
1068
  });
1069
+ if (!terminal) armIdle();
1070
+ return;
1048
1071
  }
1072
+ armIdle();
1049
1073
  controller.enqueue(chunk.value);
1050
1074
  } catch (error) {
1051
1075
  if (terminal) return;
@@ -1113,11 +1137,19 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
1113
1137
  const rawUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
1114
1138
  const rewritten = rawUrl.replace(/(?<!\/codex)\/responses(\b|$)/, "/codex/responses$1");
1115
1139
  const policy = resolveCodexResponseTimeoutPolicy(ctx.responseTimeoutPolicy);
1116
- const requestId = ctx.nextRequestId?.() ?? randomUUID();
1140
+ const handedRequestId = new Headers(init?.headers).get(CODEX_REQUEST_ID_HEADER);
1141
+ const requestId = handedRequestId ?? ctx.nextRequestId?.() ?? randomUUID();
1117
1142
  const logicalStartedAt = Date.now();
1118
1143
  let transportAttempt = 0;
1119
1144
  const attempt = async (auth, authenticationAttempt) => {
1120
1145
  const headers = new Headers(init?.headers);
1146
+ const bodyAlreadyNormalized = headers.get(CODEX_REQUEST_BODY_NORMALIZED_HEADER) === "1";
1147
+ const normalizedModel = headers.get(CODEX_REQUEST_MODEL_HEADER) ?? void 0;
1148
+ const normalizedCallerStream = headers.get(CODEX_REQUEST_CALLER_STREAM_HEADER);
1149
+ headers.delete(CODEX_REQUEST_BODY_NORMALIZED_HEADER);
1150
+ headers.delete(CODEX_REQUEST_MODEL_HEADER);
1151
+ headers.delete(CODEX_REQUEST_ID_HEADER);
1152
+ headers.delete(CODEX_REQUEST_CALLER_STREAM_HEADER);
1121
1153
  headers.set("Authorization", `Bearer ${auth.accessToken}`);
1122
1154
  if (auth.chatgptAccountId) {
1123
1155
  headers.set("ChatGPT-Account-ID", auth.chatgptAccountId);
@@ -1141,11 +1173,16 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
1141
1173
  if (ctx.turnMetadata && Object.keys(ctx.turnMetadata).length > 0) {
1142
1174
  headers.set("x-codex-turn-metadata", JSON.stringify(ctx.turnMetadata));
1143
1175
  }
1144
- let callerWantsStream = true;
1145
- let model;
1176
+ let callerWantsStream = bodyAlreadyNormalized ? normalizedCallerStream !== "0" : true;
1177
+ let model = normalizedModel;
1146
1178
  let requestOpaqueArtifacts = [];
1147
- const nextInit = { ...init, headers };
1148
- if (typeof init?.body === "string") {
1179
+ const replayableBodyFactory = init?.[REPLAYABLE_REQUEST_BODY_FACTORY];
1180
+ const nextInit = {
1181
+ ...init,
1182
+ headers,
1183
+ ...replayableBodyFactory ? { body: replayableBodyFactory() } : {}
1184
+ };
1185
+ if (!bodyAlreadyNormalized && typeof init?.body === "string") {
1149
1186
  try {
1150
1187
  const parsed = JSON.parse(init.body);
1151
1188
  callerWantsStream = parsed.stream === true;
@@ -1154,9 +1191,14 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
1154
1191
  nextInit.body = JSON.stringify(normalized);
1155
1192
  requestOpaqueArtifacts = opaqueProviderArtifactFingerprints(normalized.input);
1156
1193
  } catch {
1194
+ throw new Error("Model request could not be prepared");
1157
1195
  }
1196
+ } else if (!bodyAlreadyNormalized) {
1197
+ throw new Error("Model request could not be prepared");
1198
+ }
1199
+ if (!bodyAlreadyNormalized) {
1200
+ ctx.onRequestOpaqueArtifacts?.({ requestId, fingerprints: requestOpaqueArtifacts });
1158
1201
  }
1159
- ctx.onRequestOpaqueArtifacts?.({ requestId, fingerprints: requestOpaqueArtifacts });
1160
1202
  headers.set(
1161
1203
  "Idempotency-Key",
1162
1204
  authenticationAttempt === 0 ? requestId : `${requestId}:auth-${authenticationAttempt}`
@@ -1233,7 +1275,7 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
1233
1275
  if (!res.ok) {
1234
1276
  return await bufferCodexErrorResponse(res);
1235
1277
  }
1236
- return callerWantsStream ? repairCodexStream(res) : await sseToJsonResponse(res);
1278
+ return callerWantsStream ? validateCodexStream(res) : await sseToJsonResponse(res);
1237
1279
  };
1238
1280
  try {
1239
1281
  let res = await attempt(await ctx.getToken(), 0);
@@ -1271,27 +1313,75 @@ function classifyCodexUsageLimitError(error) {
1271
1313
  return null;
1272
1314
  }
1273
1315
  async function bufferCodexErrorResponse(res) {
1274
- const bodyText = await res.text().catch(() => "");
1316
+ const { text: bodyText, truncated } = await readBoundedResponseText(
1317
+ res,
1318
+ MAX_CODEX_ERROR_BODY_BYTES
1319
+ );
1275
1320
  const headers = new Headers(res.headers);
1276
1321
  headers.set("content-type", "application/json");
1277
1322
  headers.set(CODEX_TRANSPORT_ERROR_HEADER, "1");
1278
1323
  headers.delete("content-length");
1279
1324
  headers.delete("content-encoding");
1280
1325
  let errorType;
1326
+ let responseBody = bodyText;
1281
1327
  try {
1282
1328
  const parsed = JSON.parse(bodyText);
1283
1329
  errorType = typeof parsed.error?.type === "string" ? parsed.error.type : void 0;
1284
1330
  } catch {
1285
1331
  }
1332
+ if (truncated) {
1333
+ responseBody = JSON.stringify({
1334
+ error: {
1335
+ type: "provider_error_body_too_large",
1336
+ code: "provider_error_body_too_large",
1337
+ message: `The provider returned an error body larger than ${MAX_CODEX_ERROR_BODY_BYTES} bytes`
1338
+ }
1339
+ });
1340
+ headers.set("x-opengeni-provider-error-truncated", "1");
1341
+ }
1286
1342
  if (errorType === CODEX_USAGE_LIMIT_ERROR_TYPE) {
1287
1343
  headers.set("x-should-retry", "false");
1288
1344
  }
1289
- return new Response(bodyText, {
1345
+ return new Response(responseBody, {
1290
1346
  status: res.status,
1291
1347
  statusText: res.statusText,
1292
1348
  headers
1293
1349
  });
1294
1350
  }
1351
+ async function readBoundedResponseText(response, maxBytes) {
1352
+ if (!response.body) return { text: "", truncated: false };
1353
+ const reader = response.body.getReader();
1354
+ const decoder = new TextDecoder();
1355
+ const parts = [];
1356
+ let bytes = 0;
1357
+ let truncated = false;
1358
+ try {
1359
+ while (bytes < maxBytes) {
1360
+ const next = await reader.read();
1361
+ if (next.done) {
1362
+ parts.push(decoder.decode());
1363
+ return { text: parts.join(""), truncated };
1364
+ }
1365
+ const remaining = maxBytes - bytes;
1366
+ const accepted = next.value.byteLength > remaining ? next.value.subarray(0, remaining) : next.value;
1367
+ bytes += accepted.byteLength;
1368
+ parts.push(decoder.decode(accepted, { stream: true }));
1369
+ if (accepted.byteLength !== next.value.byteLength) {
1370
+ truncated = true;
1371
+ break;
1372
+ }
1373
+ if (bytes >= maxBytes) {
1374
+ truncated = true;
1375
+ break;
1376
+ }
1377
+ }
1378
+ } catch {
1379
+ truncated = true;
1380
+ } finally {
1381
+ if (truncated) void reader.cancel().catch(() => void 0);
1382
+ }
1383
+ return { text: parts.join(""), truncated };
1384
+ }
1295
1385
  async function sseToJsonResponse(res) {
1296
1386
  const text = await res.text();
1297
1387
  let final = null;
@@ -1523,7 +1613,7 @@ function codexSseFailureError(source, rawError, fallbackCode, publicMessage, met
1523
1613
  codexSseFailureProjection(source, rawError, fallbackCode, publicMessage, metadata)
1524
1614
  );
1525
1615
  }
1526
- function repairCodexStream(res) {
1616
+ function validateCodexStream(res) {
1527
1617
  if (!res.body) {
1528
1618
  const error = codexSseFailureError(
1529
1619
  res,
@@ -1544,7 +1634,6 @@ function repairCodexStream(res) {
1544
1634
  headers: headers2
1545
1635
  });
1546
1636
  }
1547
- const items = [];
1548
1637
  const decoder = new TextDecoder();
1549
1638
  const encoder = new TextEncoder();
1550
1639
  let buffer = "";
@@ -1555,9 +1644,8 @@ function repairCodexStream(res) {
1555
1644
  const block = buffer.slice(0, boundary.start);
1556
1645
  const separator = buffer.slice(boundary.start, boundary.end);
1557
1646
  buffer = buffer.slice(boundary.end);
1558
- const patched = patchSseBlock(block, items, res);
1559
- successfulTerminalSeen ||= patched.successfulTerminal;
1560
- controller.enqueue(encoder.encode(`${patched.block}${separator}`));
1647
+ successfulTerminalSeen ||= inspectCodexSseBlock(block, res);
1648
+ controller.enqueue(encoder.encode(`${block}${separator}`));
1561
1649
  boundary = findSseBlockBoundary(buffer, final);
1562
1650
  }
1563
1651
  };
@@ -1570,9 +1658,8 @@ function repairCodexStream(res) {
1570
1658
  buffer += decoder.decode();
1571
1659
  emitCompleteBlocks(controller, true);
1572
1660
  if (buffer.length > 0) {
1573
- const patched = patchSseBlock(buffer, items, res);
1574
- successfulTerminalSeen ||= patched.successfulTerminal;
1575
- controller.enqueue(encoder.encode(patched.block));
1661
+ successfulTerminalSeen ||= inspectCodexSseBlock(buffer, res);
1662
+ controller.enqueue(encoder.encode(buffer));
1576
1663
  buffer = "";
1577
1664
  }
1578
1665
  if (!successfulTerminalSeen) {
@@ -1613,21 +1700,28 @@ function sseLineEndingEnd(value, index, final) {
1613
1700
  }
1614
1701
  return final ? index + 1 : null;
1615
1702
  }
1616
- function patchSseBlock(block, items, source) {
1703
+ var CODEX_TERMINAL_TYPE_HINTS = [
1704
+ '"response.completed"',
1705
+ '"response.done"',
1706
+ '"response.failed"',
1707
+ '"response.incomplete"',
1708
+ '"response.error"',
1709
+ '"error"'
1710
+ ];
1711
+ function inspectCodexSseBlock(block, source) {
1617
1712
  const lines = block.split(/\r\n|\r|\n/);
1618
1713
  const dataStr = lines.filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim()).join("\n");
1619
1714
  if (!dataStr || dataStr === "[DONE]") {
1620
- return { block, successfulTerminal: false };
1715
+ return false;
1716
+ }
1717
+ if (!CODEX_TERMINAL_TYPE_HINTS.some((terminalType) => dataStr.includes(terminalType))) {
1718
+ return false;
1621
1719
  }
1622
1720
  let ev;
1623
1721
  try {
1624
1722
  ev = JSON.parse(dataStr);
1625
1723
  } 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 };
1724
+ return false;
1631
1725
  }
1632
1726
  if (ev.type === "response.failed") {
1633
1727
  throw codexSseFailureError(
@@ -1674,7 +1768,7 @@ function patchSseBlock(block, items, source) {
1674
1768
  );
1675
1769
  }
1676
1770
  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) {
1771
+ if (ev.response.status !== void 0 && ev.response.status !== "completed" || ev.response.error !== null && ev.response.error !== void 0) {
1678
1772
  throw codexSseFailureError(
1679
1773
  source,
1680
1774
  ev.response.error,
@@ -1687,19 +1781,9 @@ function patchSseBlock(block, items, source) {
1687
1781
  }
1688
1782
  );
1689
1783
  }
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 };
1784
+ return true;
1701
1785
  }
1702
- return { block, successfulTerminal: false };
1786
+ return false;
1703
1787
  }
1704
1788
 
1705
1789
  // src/mcp-sanitize.ts
@@ -1962,7 +2046,13 @@ function boundModelToolOutputItem(item, policyTokens = DEFAULT_MODEL_TOOL_OUTPUT
1962
2046
  return boundedOutput === item.output ? item : { ...item, output: boundedOutput };
1963
2047
  }
1964
2048
  function boundModelToolOutputItems(items, policyTokens = DEFAULT_MODEL_TOOL_OUTPUT_TRUNCATION_TOKENS) {
1965
- return items.map((item) => boundModelToolOutputItem(item, policyTokens));
2049
+ let bounded = null;
2050
+ for (const [index, item] of items.entries()) {
2051
+ const next = boundModelToolOutputItem(item, policyTokens);
2052
+ if (next !== item && bounded === null) bounded = items.slice(0, index);
2053
+ bounded?.push(next);
2054
+ }
2055
+ return bounded ?? items;
1966
2056
  }
1967
2057
  function boundToolOutputValue(output, budgetTokens) {
1968
2058
  const state = modelOutputBoundState(budgetTokens);
@@ -2277,6 +2367,161 @@ function isImageDataUrl(value) {
2277
2367
  return /^data:image\/[a-z0-9.+-]+;base64,/i.test(value);
2278
2368
  }
2279
2369
 
2370
+ // src/images.ts
2371
+ import { pinnedFetch, readJsonBase64Field, readResponseTextBounded } from "@opengeni/network";
2372
+ var CODEX_IMAGE_MODEL = "gpt-image-2";
2373
+ var CODEX_IMAGE_RESPONSE_MAX_BYTES = 90 * 1024 * 1024;
2374
+ var CODEX_IMAGE_ERROR_MAX_BYTES = 64 * 1024;
2375
+ var CODEX_IMAGE_MAX_BYTES = 64 * 1024 * 1024;
2376
+ var CODEX_IMAGE_REQUEST_TIMEOUT_MS = 5 * 6e4;
2377
+ var CODEX_IMAGE_MAX_REFERENCES = 5;
2378
+ var codexImageFetch = async (input, init) => await pinnedFetch(
2379
+ input,
2380
+ init,
2381
+ {
2382
+ environment: "production",
2383
+ integrationsAllowPrivateNetworkTargets: false
2384
+ },
2385
+ {
2386
+ label: "Codex image generation",
2387
+ requireHttpsOutsideLocalTest: true
2388
+ }
2389
+ );
2390
+ var CodexImageApiError = class extends Error {
2391
+ constructor(status, message) {
2392
+ super(message);
2393
+ this.status = status;
2394
+ this.name = "CodexImageApiError";
2395
+ }
2396
+ };
2397
+ var CodexImageRequestTimeoutError = class extends Error {
2398
+ constructor(timeoutMs) {
2399
+ super(`Codex image generation timed out after ${Math.ceil(timeoutMs / 1e3)} seconds`);
2400
+ this.timeoutMs = timeoutMs;
2401
+ this.name = "CodexImageRequestTimeoutError";
2402
+ }
2403
+ };
2404
+ async function generateCodexSubscriptionImage(input) {
2405
+ const fetchImpl = input.fetch ?? codexImageFetch;
2406
+ const timeoutMs = input.requestTimeoutMs ?? CODEX_IMAGE_REQUEST_TIMEOUT_MS;
2407
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
2408
+ throw new RangeError("Codex image request timeout must be a positive safe integer");
2409
+ }
2410
+ const references = input.references ?? [];
2411
+ if (references.length > CODEX_IMAGE_MAX_REFERENCES) {
2412
+ throw new RangeError(
2413
+ `Codex image editing accepts at most ${CODEX_IMAGE_MAX_REFERENCES} images`
2414
+ );
2415
+ }
2416
+ for (const reference of references) {
2417
+ if (reference.bytes.byteLength === 0) throw new Error("Codex image reference is empty");
2418
+ }
2419
+ const deadline = new AbortController();
2420
+ const timer = setTimeout(
2421
+ () => deadline.abort(new CodexImageRequestTimeoutError(timeoutMs)),
2422
+ timeoutMs
2423
+ );
2424
+ const signal = input.abortSignal ? AbortSignal.any([input.abortSignal, deadline.signal]) : deadline.signal;
2425
+ const request = async (auth) => {
2426
+ const headers = codexImageHeaders(auth, input.context.clientVersion, input.turnId);
2427
+ return await fetchImpl(
2428
+ `${CODEX_RESPONSES_BASE}/${references.length > 0 ? "images/edits" : "images/generations"}`,
2429
+ {
2430
+ method: "POST",
2431
+ redirect: "error",
2432
+ headers,
2433
+ body: JSON.stringify(
2434
+ references.length > 0 ? {
2435
+ images: references.map((reference) => ({
2436
+ image_url: `data:${reference.mediaType};base64,${Buffer.from(reference.bytes).toString("base64")}`
2437
+ })),
2438
+ prompt: input.prompt,
2439
+ background: "auto",
2440
+ model: CODEX_IMAGE_MODEL,
2441
+ quality: "auto",
2442
+ size: "auto"
2443
+ } : {
2444
+ prompt: input.prompt,
2445
+ background: "auto",
2446
+ model: CODEX_IMAGE_MODEL,
2447
+ quality: "auto",
2448
+ size: "auto"
2449
+ }
2450
+ ),
2451
+ signal
2452
+ }
2453
+ );
2454
+ };
2455
+ const operation = (async () => {
2456
+ let response = await request(await input.context.getToken());
2457
+ if (response.status === 401) {
2458
+ await response.body?.cancel().catch(() => void 0);
2459
+ response = await request(await input.context.refresh());
2460
+ }
2461
+ if (!response.ok) {
2462
+ const detail = await readResponseTextBounded(
2463
+ response,
2464
+ CODEX_IMAGE_ERROR_MAX_BYTES,
2465
+ "Codex image error",
2466
+ { signal }
2467
+ ).catch(() => "");
2468
+ throw new CodexImageApiError(
2469
+ response.status,
2470
+ detail ? `Codex image generation failed (${response.status}): ${boundedErrorMessage(detail)}` : `Codex image generation failed (${response.status})`
2471
+ );
2472
+ }
2473
+ const bytes = await readJsonBase64Field(response, {
2474
+ fieldName: "b64_json",
2475
+ shape: "string",
2476
+ maxResponseBytes: CODEX_IMAGE_RESPONSE_MAX_BYTES,
2477
+ maxDecodedBytes: CODEX_IMAGE_MAX_BYTES,
2478
+ label: "Codex image generation",
2479
+ signal
2480
+ });
2481
+ return { bytes, declaredMediaType: "image/png" };
2482
+ })();
2483
+ let removeAbortListener = () => void 0;
2484
+ const aborted = new Promise((_resolve, reject) => {
2485
+ const onAbort = () => reject(signal.reason);
2486
+ if (signal.aborted) {
2487
+ onAbort();
2488
+ return;
2489
+ }
2490
+ signal.addEventListener("abort", onAbort, { once: true });
2491
+ removeAbortListener = () => signal.removeEventListener("abort", onAbort);
2492
+ });
2493
+ try {
2494
+ return await Promise.race([operation, aborted]);
2495
+ } finally {
2496
+ removeAbortListener();
2497
+ clearTimeout(timer);
2498
+ }
2499
+ }
2500
+ function codexImageHeaders(auth, clientVersion, turnId) {
2501
+ const headers = new Headers({
2502
+ Authorization: `Bearer ${auth.accessToken}`,
2503
+ accept: "application/json",
2504
+ "content-type": "application/json",
2505
+ originator: CODEX_ORIGINATOR,
2506
+ "User-Agent": `${CODEX_ORIGINATOR}/${clientVersion || CODEX_CLIENT_VERSION}`,
2507
+ version: clientVersion || CODEX_CLIENT_VERSION,
2508
+ "x-codex-image-turn-id": turnId
2509
+ });
2510
+ if (auth.chatgptAccountId) headers.set("ChatGPT-Account-ID", auth.chatgptAccountId);
2511
+ if (auth.isFedramp) headers.set("X-OpenAI-Fedramp", "true");
2512
+ return headers;
2513
+ }
2514
+ function boundedErrorMessage(body) {
2515
+ let message = body;
2516
+ try {
2517
+ const value = JSON.parse(body);
2518
+ const candidate = value.error?.message ?? value.message;
2519
+ if (typeof candidate === "string") message = candidate;
2520
+ } catch {
2521
+ }
2522
+ return message.replace(/\s+/g, " ").trim().slice(0, 1e3);
2523
+ }
2524
+
2280
2525
  // src/realtime.ts
2281
2526
  var CODEX_REALTIME_CALL_URL = `${CODEX_RESPONSES_BASE}/realtime/calls?intent=quicksilver&architecture=avas`;
2282
2527
  var MAX_REALTIME_SDP_BYTES = 1024 * 1024;
@@ -2578,6 +2823,10 @@ export {
2578
2823
  CODEX_REALTIME_VOICES,
2579
2824
  CODEX_REFRESH_FALLBACK_MS,
2580
2825
  CODEX_REFRESH_WINDOW_MS,
2826
+ CODEX_REQUEST_BODY_NORMALIZED_HEADER,
2827
+ CODEX_REQUEST_CALLER_STREAM_HEADER,
2828
+ CODEX_REQUEST_ID_HEADER,
2829
+ CODEX_REQUEST_MODEL_HEADER,
2581
2830
  CODEX_RESPONSES_BASE,
2582
2831
  CODEX_RESPONSE_HEADERS_TIMEOUT_MS,
2583
2832
  CODEX_RESPONSE_NO_BYTE_RETRIES,
@@ -2593,6 +2842,8 @@ export {
2593
2842
  CODEX_WEEKLY_WINDOW_SECONDS,
2594
2843
  CODEX_WHAM_BASE,
2595
2844
  CodexDeviceError,
2845
+ CodexImageApiError,
2846
+ CodexImageRequestTimeoutError,
2596
2847
  CodexRealtimeError,
2597
2848
  CodexRefreshTransient,
2598
2849
  CodexReloginRequired,
@@ -2626,12 +2877,14 @@ export {
2626
2877
  fetchCodexModels,
2627
2878
  fetchCodexRateLimitResetCredits,
2628
2879
  fetchCodexUsage,
2880
+ generateCodexSubscriptionImage,
2629
2881
  isCodexBilledModel,
2630
2882
  isCodexTransportError,
2631
2883
  isPreHeadersTimeoutError,
2632
2884
  modelToolOutputSerializationBudgetTokens,
2633
2885
  normalizeCodexRequestBody,
2634
2886
  normalizeCodexUsage,
2887
+ normalizedCodexRequestBody,
2635
2888
  opaqueProviderArtifactFingerprint,
2636
2889
  opaqueProviderArtifactFingerprints,
2637
2890
  parseCodexRateLimitResetConsumeResponse,