@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.
- package/dist/api-client.d.ts +2 -2
- package/dist/fetch.d.ts +9 -1
- package/dist/images.d.ts +34 -0
- package/dist/index.d.ts +17 -16
- package/dist/index.js +292 -39
- package/dist/index.js.map +1 -1
- package/dist/mcp-sanitize.d.ts +1 -1
- package/dist/model-output-truncation.d.ts +1 -1
- package/dist/normalize.d.ts +6 -0
- package/dist/realtime.d.ts +4 -4
- package/dist/refresh.d.ts +1 -1
- package/dist/response-timeout.d.ts +1 -1
- package/dist/usage-normalize.d.ts +1 -1
- package/package.json +2 -1
- package/src/fetch.ts +146 -54
- package/src/images.ts +209 -0
- package/src/index.ts +1 -0
- package/src/model-output-truncation.ts +7 -1
- package/src/normalize.ts +26 -0
package/dist/api-client.d.ts
CHANGED
|
@@ -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 = {
|
package/dist/images.d.ts
ADDED
|
@@ -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 "./
|
|
16
|
-
export * from "./realtime
|
|
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
|
|
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
|
|
1148
|
-
|
|
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 ?
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
1559
|
-
|
|
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
|
-
|
|
1574
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|