@opengeni/codex 0.2.9 → 0.2.11
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 -0
- package/dist/{chunk-YGKQDUY7.js → chunk-HT463QWA.js} +10 -2
- package/dist/chunk-HT463QWA.js.map +1 -0
- package/dist/chunk-MFVGCR3N.js +236 -0
- package/dist/chunk-MFVGCR3N.js.map +1 -0
- package/dist/constants.d.ts +5 -1
- package/dist/constants.js +9 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +350 -10
- package/dist/index.js.map +1 -1
- package/dist/mcp-sanitize.d.ts +4 -3
- package/dist/opaque-artifact.d.ts +4 -0
- package/dist/realtime-v3.d.ts +86 -0
- package/dist/realtime-v3.js +25 -0
- package/dist/realtime-v3.js.map +1 -0
- package/dist/realtime.d.ts +48 -0
- package/dist/request-context.d.ts +6 -0
- package/package.json +5 -1
- package/src/api-client.ts +26 -9
- package/src/constants.ts +8 -1
- package/src/fetch.ts +4 -0
- package/src/index.ts +3 -0
- package/src/mcp-sanitize.ts +5 -4
- package/src/normalize.ts +1 -1
- package/src/opaque-artifact.ts +29 -0
- package/src/realtime-v3.ts +349 -0
- package/src/realtime.ts +356 -0
- package/src/request-context.ts +7 -0
- package/dist/chunk-YGKQDUY7.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
CODEX_ORIGINATOR,
|
|
22
22
|
CODEX_PROVIDER_BASE_URL,
|
|
23
23
|
CODEX_PROVIDER_ID,
|
|
24
|
+
CODEX_REALTIME_CALL_TIMEOUT_MS,
|
|
25
|
+
CODEX_REALTIME_DEFAULT_VOICE,
|
|
26
|
+
CODEX_REALTIME_MODEL,
|
|
27
|
+
CODEX_REALTIME_VERSION,
|
|
24
28
|
CODEX_REFRESH_FALLBACK_MS,
|
|
25
29
|
CODEX_REFRESH_WINDOW_MS,
|
|
26
30
|
CODEX_RESPONSES_BASE,
|
|
@@ -32,7 +36,19 @@ import {
|
|
|
32
36
|
CODEX_RESPONSE_WHOLE_TIMEOUT_MS,
|
|
33
37
|
CODEX_TOKEN_URL,
|
|
34
38
|
CODEX_WHAM_BASE
|
|
35
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-HT463QWA.js";
|
|
40
|
+
import {
|
|
41
|
+
CODEX_REALTIME_CONTEXT_APPEND_MAX_BYTES,
|
|
42
|
+
CODEX_REALTIME_INITIAL_ITEMS_MAX_COUNT,
|
|
43
|
+
CODEX_REALTIME_INITIAL_ITEMS_MAX_TOKENS,
|
|
44
|
+
CODEX_REALTIME_V3_MAX_EVENT_BYTES,
|
|
45
|
+
CODEX_REALTIME_V3_MAX_IDENTIFIER_BYTES,
|
|
46
|
+
CODEX_REALTIME_V3_MAX_TEXT_BYTES,
|
|
47
|
+
contextAppendChunks,
|
|
48
|
+
encodeCodexRealtimeV3DelegationContextAppend,
|
|
49
|
+
encodeCodexRealtimeV3SessionContextAppend,
|
|
50
|
+
parseCodexRealtimeV3Event
|
|
51
|
+
} from "./chunk-MFVGCR3N.js";
|
|
36
52
|
|
|
37
53
|
// src/billing.ts
|
|
38
54
|
function isCodexBilledModel(model) {
|
|
@@ -567,7 +583,7 @@ function normalizeCodexUsage(httpStatus, rawPayload) {
|
|
|
567
583
|
var CODEX_READ_TIMEOUT_MS = 5e3;
|
|
568
584
|
var RESET_CREDIT_DETAILS_TIMEOUT_MS = 5e3;
|
|
569
585
|
var RESET_CREDIT_CONSUME_TIMEOUT_MS = 1e4;
|
|
570
|
-
function
|
|
586
|
+
function codexSubscriptionHeaders(a) {
|
|
571
587
|
return {
|
|
572
588
|
Authorization: `Bearer ${a.accessToken}`,
|
|
573
589
|
...a.chatgptAccountId ? { "ChatGPT-Account-ID": a.chatgptAccountId } : {},
|
|
@@ -581,7 +597,7 @@ async function fetchCodexModels(a, fetchImpl = fetch, timeoutMs = CODEX_READ_TIM
|
|
|
581
597
|
const fetched = await runBoundedCodexOperation(async (signal) => {
|
|
582
598
|
const res = await fetchImpl(
|
|
583
599
|
`${CODEX_RESPONSES_BASE}/models?client_version=${encodeURIComponent(a.clientVersion)}`,
|
|
584
|
-
{ method: "GET", headers:
|
|
600
|
+
{ method: "GET", headers: codexSubscriptionHeaders(a), signal }
|
|
585
601
|
);
|
|
586
602
|
if (!res.ok) {
|
|
587
603
|
await res.arrayBuffer().catch(() => void 0);
|
|
@@ -597,7 +613,7 @@ async function fetchCodexUsage(a, fetchImpl = fetch, timeoutMs = CODEX_READ_TIME
|
|
|
597
613
|
const fetched = await runBoundedCodexOperation(async (signal) => {
|
|
598
614
|
const res = await fetchImpl(`${CODEX_WHAM_BASE}/wham/usage`, {
|
|
599
615
|
method: "GET",
|
|
600
|
-
headers:
|
|
616
|
+
headers: codexSubscriptionHeaders(a),
|
|
601
617
|
signal
|
|
602
618
|
});
|
|
603
619
|
const payload = res.ok || res.status === 404 ? await res.json().catch(() => null) : null;
|
|
@@ -611,15 +627,23 @@ async function fetchCodexRateLimitResetCredits(a, fetchImpl = fetch, timeoutMs =
|
|
|
611
627
|
const fetched = await runBoundedCodexOperation(async (signal) => {
|
|
612
628
|
const res = await fetchImpl(`${CODEX_WHAM_BASE}/wham/rate-limit-reset-credits`, {
|
|
613
629
|
method: "GET",
|
|
614
|
-
headers:
|
|
630
|
+
headers: codexSubscriptionHeaders(a),
|
|
615
631
|
signal
|
|
616
632
|
});
|
|
617
633
|
if (!res.ok) {
|
|
618
634
|
await res.arrayBuffer().catch(() => void 0);
|
|
619
|
-
return {
|
|
635
|
+
return {
|
|
636
|
+
ok: false,
|
|
637
|
+
status: res.status,
|
|
638
|
+
reason: "http_error"
|
|
639
|
+
};
|
|
620
640
|
}
|
|
621
641
|
const details = parseCodexRateLimitResetCreditsDetails(await res.json().catch(() => null));
|
|
622
|
-
return details ? { ok: true, status: res.status, details } : {
|
|
642
|
+
return details ? { ok: true, status: res.status, details } : {
|
|
643
|
+
ok: false,
|
|
644
|
+
status: res.status,
|
|
645
|
+
reason: "invalid_response"
|
|
646
|
+
};
|
|
623
647
|
}, timeoutMs);
|
|
624
648
|
return fetched.ok ? fetched.value : { ok: false, status: 0, reason: fetched.reason };
|
|
625
649
|
}
|
|
@@ -631,7 +655,7 @@ async function consumeCodexRateLimitResetCredit(a, input, fetchImpl = fetch, tim
|
|
|
631
655
|
const res = await fetchImpl(`${CODEX_WHAM_BASE}/wham/rate-limit-reset-credits/consume`, {
|
|
632
656
|
method: "POST",
|
|
633
657
|
headers: {
|
|
634
|
-
...
|
|
658
|
+
...codexSubscriptionHeaders(a),
|
|
635
659
|
"content-type": "application/json"
|
|
636
660
|
},
|
|
637
661
|
body: JSON.stringify({
|
|
@@ -642,10 +666,18 @@ async function consumeCodexRateLimitResetCredit(a, input, fetchImpl = fetch, tim
|
|
|
642
666
|
});
|
|
643
667
|
if (!res.ok) {
|
|
644
668
|
await res.arrayBuffer().catch(() => void 0);
|
|
645
|
-
return {
|
|
669
|
+
return {
|
|
670
|
+
ok: false,
|
|
671
|
+
status: res.status,
|
|
672
|
+
reason: "http_error"
|
|
673
|
+
};
|
|
646
674
|
}
|
|
647
675
|
const result = parseCodexRateLimitResetConsumeResponse(await res.json().catch(() => null));
|
|
648
|
-
return result ? { ok: true, status: res.status, result } : {
|
|
676
|
+
return result ? { ok: true, status: res.status, result } : {
|
|
677
|
+
ok: false,
|
|
678
|
+
status: res.status,
|
|
679
|
+
reason: "invalid_response"
|
|
680
|
+
};
|
|
649
681
|
}, timeoutMs);
|
|
650
682
|
return fetched.ok ? fetched.value : { ok: false, status: 0, reason: fetched.reason };
|
|
651
683
|
}
|
|
@@ -754,6 +786,27 @@ function isPreHeadersTimeoutError(error) {
|
|
|
754
786
|
|
|
755
787
|
// src/fetch.ts
|
|
756
788
|
import { randomUUID } from "crypto";
|
|
789
|
+
|
|
790
|
+
// src/opaque-artifact.ts
|
|
791
|
+
import { createHash } from "crypto";
|
|
792
|
+
function opaqueProviderArtifactFingerprint(item) {
|
|
793
|
+
if (!item || typeof item !== "object") return null;
|
|
794
|
+
const record = item;
|
|
795
|
+
if (record.type !== "reasoning" && record.type !== "compaction") return null;
|
|
796
|
+
const providerData = record.providerData && typeof record.providerData === "object" ? record.providerData : null;
|
|
797
|
+
const ciphertext = typeof record.encrypted_content === "string" && record.encrypted_content || typeof record.encryptedContent === "string" && record.encryptedContent || typeof providerData?.encrypted_content === "string" && providerData.encrypted_content || typeof providerData?.encryptedContent === "string" && providerData.encryptedContent || null;
|
|
798
|
+
if (!ciphertext) return null;
|
|
799
|
+
return `${record.type}:${createHash("sha256").update(ciphertext).digest("hex")}`;
|
|
800
|
+
}
|
|
801
|
+
function opaqueProviderArtifactFingerprints(input) {
|
|
802
|
+
if (!Array.isArray(input)) return [];
|
|
803
|
+
return input.flatMap((item) => {
|
|
804
|
+
const fingerprint = opaqueProviderArtifactFingerprint(item);
|
|
805
|
+
return fingerprint ? [fingerprint] : [];
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// src/fetch.ts
|
|
757
810
|
var CODEX_TRANSPORT_ERROR_HEADER = "x-opengeni-codex-transport-error";
|
|
758
811
|
function headersCarryCodexTransportMarker(headers) {
|
|
759
812
|
if (!headers || typeof headers !== "object") return false;
|
|
@@ -1090,6 +1143,7 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
|
|
|
1090
1143
|
}
|
|
1091
1144
|
let callerWantsStream = true;
|
|
1092
1145
|
let model;
|
|
1146
|
+
let requestOpaqueArtifacts = [];
|
|
1093
1147
|
const nextInit = { ...init, headers };
|
|
1094
1148
|
if (typeof init?.body === "string") {
|
|
1095
1149
|
try {
|
|
@@ -1098,9 +1152,11 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
|
|
|
1098
1152
|
const normalized = normalizeCodexRequestBody(parsed, ctx.resolveModel);
|
|
1099
1153
|
model = typeof normalized.model === "string" ? normalized.model : void 0;
|
|
1100
1154
|
nextInit.body = JSON.stringify(normalized);
|
|
1155
|
+
requestOpaqueArtifacts = opaqueProviderArtifactFingerprints(normalized.input);
|
|
1101
1156
|
} catch {
|
|
1102
1157
|
}
|
|
1103
1158
|
}
|
|
1159
|
+
ctx.onRequestOpaqueArtifacts?.({ requestId, fingerprints: requestOpaqueArtifacts });
|
|
1104
1160
|
headers.set(
|
|
1105
1161
|
"Idempotency-Key",
|
|
1106
1162
|
authenticationAttempt === 0 ? requestId : `${requestId}:auth-${authenticationAttempt}`
|
|
@@ -2219,6 +2275,269 @@ function isResponsesProtocolContentPrefix(output) {
|
|
|
2219
2275
|
function isImageDataUrl(value) {
|
|
2220
2276
|
return /^data:image\/[a-z0-9.+-]+;base64,/i.test(value);
|
|
2221
2277
|
}
|
|
2278
|
+
|
|
2279
|
+
// src/realtime.ts
|
|
2280
|
+
var CODEX_REALTIME_CALL_URL = `${CODEX_RESPONSES_BASE}/realtime/calls?intent=quicksilver&architecture=avas`;
|
|
2281
|
+
var MAX_REALTIME_SDP_BYTES = 1024 * 1024;
|
|
2282
|
+
var REALTIME_CALL_ID = /^(?:rtc_.+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
2283
|
+
var CODEX_REALTIME_VOICES = [
|
|
2284
|
+
"juniper",
|
|
2285
|
+
"maple",
|
|
2286
|
+
"spruce",
|
|
2287
|
+
"ember",
|
|
2288
|
+
"vale",
|
|
2289
|
+
"breeze",
|
|
2290
|
+
"arbor",
|
|
2291
|
+
"sol",
|
|
2292
|
+
"cove"
|
|
2293
|
+
];
|
|
2294
|
+
var CodexRealtimeError = class extends Error {
|
|
2295
|
+
constructor(code, message, providerStatus = null) {
|
|
2296
|
+
super(message);
|
|
2297
|
+
this.code = code;
|
|
2298
|
+
this.providerStatus = providerStatus;
|
|
2299
|
+
this.name = "CodexRealtimeError";
|
|
2300
|
+
}
|
|
2301
|
+
};
|
|
2302
|
+
async function createCodexRealtimeCall(auth, input, fetchImpl = fetch, options = {}) {
|
|
2303
|
+
validateRealtimeInput(input);
|
|
2304
|
+
const timeoutMs = options.timeoutMs ?? CODEX_REALTIME_CALL_TIMEOUT_MS;
|
|
2305
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
2306
|
+
throw new CodexRealtimeError("invalid_request", "Codex realtime timeout must be positive");
|
|
2307
|
+
}
|
|
2308
|
+
if (options.signal?.aborted) {
|
|
2309
|
+
throw new CodexRealtimeError("cancelled", "Codex realtime request cancelled");
|
|
2310
|
+
}
|
|
2311
|
+
const controller = new AbortController();
|
|
2312
|
+
let timedOut = false;
|
|
2313
|
+
let timeout;
|
|
2314
|
+
let rejectCancellation;
|
|
2315
|
+
const cancellation = new Promise((_resolve, reject) => {
|
|
2316
|
+
rejectCancellation = reject;
|
|
2317
|
+
});
|
|
2318
|
+
const onAbort = () => {
|
|
2319
|
+
controller.abort(options.signal?.reason);
|
|
2320
|
+
rejectCancellation?.(new CodexRealtimeError("cancelled", "Codex realtime request cancelled"));
|
|
2321
|
+
};
|
|
2322
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2323
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
2324
|
+
timeout = setTimeout(() => {
|
|
2325
|
+
timedOut = true;
|
|
2326
|
+
controller.abort();
|
|
2327
|
+
reject(new CodexRealtimeError("timeout", "Codex realtime request timed out"));
|
|
2328
|
+
}, timeoutMs);
|
|
2329
|
+
});
|
|
2330
|
+
const request = (async () => {
|
|
2331
|
+
const response = await fetchImpl(CODEX_REALTIME_CALL_URL, {
|
|
2332
|
+
method: "POST",
|
|
2333
|
+
headers: {
|
|
2334
|
+
...codexSubscriptionHeaders(auth),
|
|
2335
|
+
"content-type": "application/json",
|
|
2336
|
+
"openai-alpha": "quicksilver=v2",
|
|
2337
|
+
"session-id": input.sessionId,
|
|
2338
|
+
"thread-id": input.sessionId
|
|
2339
|
+
},
|
|
2340
|
+
body: JSON.stringify({
|
|
2341
|
+
sdp: input.sdp,
|
|
2342
|
+
session: {
|
|
2343
|
+
instructions: input.instructions ?? "",
|
|
2344
|
+
audio: {
|
|
2345
|
+
output: { voice: input.voice ?? CODEX_REALTIME_DEFAULT_VOICE }
|
|
2346
|
+
},
|
|
2347
|
+
delegation: { type: "client" },
|
|
2348
|
+
model: CODEX_REALTIME_MODEL,
|
|
2349
|
+
...input.initialItems?.length ? {
|
|
2350
|
+
initial_items: input.initialItems.map((item) => ({
|
|
2351
|
+
type: "message",
|
|
2352
|
+
role: item.role,
|
|
2353
|
+
content: [
|
|
2354
|
+
{
|
|
2355
|
+
type: item.role === "assistant" ? "output_text" : "input_text",
|
|
2356
|
+
text: item.text
|
|
2357
|
+
}
|
|
2358
|
+
]
|
|
2359
|
+
}))
|
|
2360
|
+
} : {}
|
|
2361
|
+
}
|
|
2362
|
+
}),
|
|
2363
|
+
signal: controller.signal
|
|
2364
|
+
});
|
|
2365
|
+
if (!response.ok) {
|
|
2366
|
+
await response.body?.cancel().catch(() => void 0);
|
|
2367
|
+
throw providerHttpError(response.status);
|
|
2368
|
+
}
|
|
2369
|
+
const location = response.headers.get("location");
|
|
2370
|
+
if (!location || !validRealtimeLocation(location)) {
|
|
2371
|
+
await response.body?.cancel().catch(() => void 0);
|
|
2372
|
+
throw new CodexRealtimeError(
|
|
2373
|
+
"invalid_response",
|
|
2374
|
+
"Codex realtime response did not identify a compatible call",
|
|
2375
|
+
response.status
|
|
2376
|
+
);
|
|
2377
|
+
}
|
|
2378
|
+
const sdp = await readBoundedSdp(response);
|
|
2379
|
+
if (!isAudioSdp(sdp)) {
|
|
2380
|
+
throw new CodexRealtimeError(
|
|
2381
|
+
"invalid_response",
|
|
2382
|
+
"Codex realtime response was not an audio SDP answer",
|
|
2383
|
+
response.status
|
|
2384
|
+
);
|
|
2385
|
+
}
|
|
2386
|
+
return {
|
|
2387
|
+
sdp,
|
|
2388
|
+
version: CODEX_REALTIME_VERSION,
|
|
2389
|
+
model: CODEX_REALTIME_MODEL
|
|
2390
|
+
};
|
|
2391
|
+
})();
|
|
2392
|
+
try {
|
|
2393
|
+
return await Promise.race([request, cancellation, deadline]);
|
|
2394
|
+
} catch (error) {
|
|
2395
|
+
if (error instanceof CodexRealtimeError) throw error;
|
|
2396
|
+
if (options.signal?.aborted) {
|
|
2397
|
+
throw new CodexRealtimeError("cancelled", "Codex realtime request cancelled");
|
|
2398
|
+
}
|
|
2399
|
+
if (timedOut || controller.signal.aborted) {
|
|
2400
|
+
throw new CodexRealtimeError("timeout", "Codex realtime request timed out");
|
|
2401
|
+
}
|
|
2402
|
+
throw new CodexRealtimeError("network", "Codex realtime provider request failed");
|
|
2403
|
+
} finally {
|
|
2404
|
+
if (timeout) clearTimeout(timeout);
|
|
2405
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
function selectCodexCredentialId(args) {
|
|
2409
|
+
if (args.sessionPinnedCredentialId && args.connectedIds.has(args.sessionPinnedCredentialId)) {
|
|
2410
|
+
return args.sessionPinnedCredentialId;
|
|
2411
|
+
}
|
|
2412
|
+
if (args.activeCredentialId && args.connectedIds.has(args.activeCredentialId)) {
|
|
2413
|
+
return args.activeCredentialId;
|
|
2414
|
+
}
|
|
2415
|
+
return null;
|
|
2416
|
+
}
|
|
2417
|
+
function validateRealtimeInput(input) {
|
|
2418
|
+
if (input.version !== CODEX_REALTIME_VERSION) {
|
|
2419
|
+
throw new CodexRealtimeError(
|
|
2420
|
+
"incompatible",
|
|
2421
|
+
`Codex realtime requires ${CODEX_REALTIME_VERSION}`
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
if (!input.sessionId || input.sessionId.length > 128) {
|
|
2425
|
+
throw new CodexRealtimeError("invalid_request", "Codex realtime session id is invalid");
|
|
2426
|
+
}
|
|
2427
|
+
if (new TextEncoder().encode(input.sdp).byteLength > MAX_REALTIME_SDP_BYTES) {
|
|
2428
|
+
throw new CodexRealtimeError("invalid_request", "Codex realtime SDP offer is too large");
|
|
2429
|
+
}
|
|
2430
|
+
if (!isAudioSdp(input.sdp)) {
|
|
2431
|
+
throw new CodexRealtimeError("invalid_request", "Codex realtime requires an audio SDP offer");
|
|
2432
|
+
}
|
|
2433
|
+
if (input.voice !== void 0 && !CODEX_REALTIME_VOICES.includes(input.voice)) {
|
|
2434
|
+
throw new CodexRealtimeError("invalid_request", "Codex realtime voice is unsupported");
|
|
2435
|
+
}
|
|
2436
|
+
const initialItems = input.initialItems ?? [];
|
|
2437
|
+
if (initialItems.length > CODEX_REALTIME_INITIAL_ITEMS_MAX_COUNT) {
|
|
2438
|
+
throw new CodexRealtimeError(
|
|
2439
|
+
"invalid_request",
|
|
2440
|
+
`Codex realtime history exceeds ${CODEX_REALTIME_INITIAL_ITEMS_MAX_COUNT} items`
|
|
2441
|
+
);
|
|
2442
|
+
}
|
|
2443
|
+
let estimatedTokens = 0;
|
|
2444
|
+
for (const item of initialItems) {
|
|
2445
|
+
if (item.role !== "user" && item.role !== "developer" && item.role !== "assistant" || typeof item.text !== "string") {
|
|
2446
|
+
throw new CodexRealtimeError("invalid_request", "Codex realtime history item is invalid");
|
|
2447
|
+
}
|
|
2448
|
+
const itemTokens = Math.ceil(new TextEncoder().encode(item.text).byteLength / 4);
|
|
2449
|
+
if (itemTokens > CODEX_REALTIME_INITIAL_ITEMS_MAX_TOKENS) {
|
|
2450
|
+
throw new CodexRealtimeError("invalid_request", "Codex realtime history item is too large");
|
|
2451
|
+
}
|
|
2452
|
+
estimatedTokens += itemTokens;
|
|
2453
|
+
}
|
|
2454
|
+
if (estimatedTokens > CODEX_REALTIME_INITIAL_ITEMS_MAX_TOKENS) {
|
|
2455
|
+
throw new CodexRealtimeError("invalid_request", "Codex realtime history is too large");
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
function providerHttpError(status) {
|
|
2459
|
+
if (status === 401) {
|
|
2460
|
+
return new CodexRealtimeError(
|
|
2461
|
+
"authentication",
|
|
2462
|
+
"Codex subscription rejected authentication",
|
|
2463
|
+
status
|
|
2464
|
+
);
|
|
2465
|
+
}
|
|
2466
|
+
if (status === 403) {
|
|
2467
|
+
return new CodexRealtimeError(
|
|
2468
|
+
"entitlement",
|
|
2469
|
+
"Codex subscription lacks realtime entitlement",
|
|
2470
|
+
status
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2473
|
+
if (status === 404) {
|
|
2474
|
+
return new CodexRealtimeError(
|
|
2475
|
+
"incompatible",
|
|
2476
|
+
"Codex subscription realtime is unavailable",
|
|
2477
|
+
status
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2480
|
+
if (status === 429) {
|
|
2481
|
+
return new CodexRealtimeError("rate_limited", "Codex realtime is rate limited", status);
|
|
2482
|
+
}
|
|
2483
|
+
return new CodexRealtimeError("provider", "Codex realtime provider request failed", status);
|
|
2484
|
+
}
|
|
2485
|
+
function validRealtimeLocation(location) {
|
|
2486
|
+
const path = location.split("?", 1)[0] ?? "";
|
|
2487
|
+
const segment = path.split("/").filter(Boolean).at(-1) ?? "";
|
|
2488
|
+
return REALTIME_CALL_ID.test(segment);
|
|
2489
|
+
}
|
|
2490
|
+
function isAudioSdp(sdp) {
|
|
2491
|
+
return /^v=0(?:\r?\n)/.test(sdp) && /(?:^|\r?\n)m=audio\s/m.test(sdp);
|
|
2492
|
+
}
|
|
2493
|
+
async function readBoundedSdp(response) {
|
|
2494
|
+
const declared = Number(response.headers.get("content-length"));
|
|
2495
|
+
if (Number.isFinite(declared) && declared > MAX_REALTIME_SDP_BYTES) {
|
|
2496
|
+
await response.body?.cancel().catch(() => void 0);
|
|
2497
|
+
throw new CodexRealtimeError(
|
|
2498
|
+
"invalid_response",
|
|
2499
|
+
"Codex realtime SDP answer is too large",
|
|
2500
|
+
response.status
|
|
2501
|
+
);
|
|
2502
|
+
}
|
|
2503
|
+
if (!response.body) return "";
|
|
2504
|
+
const reader = response.body.getReader();
|
|
2505
|
+
const chunks = [];
|
|
2506
|
+
let total = 0;
|
|
2507
|
+
try {
|
|
2508
|
+
while (true) {
|
|
2509
|
+
const next = await reader.read();
|
|
2510
|
+
if (next.done) break;
|
|
2511
|
+
total += next.value.byteLength;
|
|
2512
|
+
if (total > MAX_REALTIME_SDP_BYTES) {
|
|
2513
|
+
await reader.cancel();
|
|
2514
|
+
throw new CodexRealtimeError(
|
|
2515
|
+
"invalid_response",
|
|
2516
|
+
"Codex realtime SDP answer is too large",
|
|
2517
|
+
response.status
|
|
2518
|
+
);
|
|
2519
|
+
}
|
|
2520
|
+
chunks.push(next.value);
|
|
2521
|
+
}
|
|
2522
|
+
} finally {
|
|
2523
|
+
reader.releaseLock();
|
|
2524
|
+
}
|
|
2525
|
+
const bytes = new Uint8Array(total);
|
|
2526
|
+
let offset = 0;
|
|
2527
|
+
for (const chunk of chunks) {
|
|
2528
|
+
bytes.set(chunk, offset);
|
|
2529
|
+
offset += chunk.byteLength;
|
|
2530
|
+
}
|
|
2531
|
+
try {
|
|
2532
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
2533
|
+
} catch {
|
|
2534
|
+
throw new CodexRealtimeError(
|
|
2535
|
+
"invalid_response",
|
|
2536
|
+
"Codex realtime SDP answer was not valid UTF-8",
|
|
2537
|
+
response.status
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2222
2541
|
export {
|
|
2223
2542
|
CODEX_APPS_MCP_SERVER_ID,
|
|
2224
2543
|
CODEX_APPS_MCP_SERVER_NAME,
|
|
@@ -2245,6 +2564,17 @@ export {
|
|
|
2245
2564
|
CODEX_PROVIDER_BASE_URL,
|
|
2246
2565
|
CODEX_PROVIDER_ID,
|
|
2247
2566
|
CODEX_RATE_LIMIT_RESET_OUTCOMES,
|
|
2567
|
+
CODEX_REALTIME_CALL_TIMEOUT_MS,
|
|
2568
|
+
CODEX_REALTIME_CONTEXT_APPEND_MAX_BYTES,
|
|
2569
|
+
CODEX_REALTIME_DEFAULT_VOICE,
|
|
2570
|
+
CODEX_REALTIME_INITIAL_ITEMS_MAX_COUNT,
|
|
2571
|
+
CODEX_REALTIME_INITIAL_ITEMS_MAX_TOKENS,
|
|
2572
|
+
CODEX_REALTIME_MODEL,
|
|
2573
|
+
CODEX_REALTIME_V3_MAX_EVENT_BYTES,
|
|
2574
|
+
CODEX_REALTIME_V3_MAX_IDENTIFIER_BYTES,
|
|
2575
|
+
CODEX_REALTIME_V3_MAX_TEXT_BYTES,
|
|
2576
|
+
CODEX_REALTIME_VERSION,
|
|
2577
|
+
CODEX_REALTIME_VOICES,
|
|
2248
2578
|
CODEX_REFRESH_FALLBACK_MS,
|
|
2249
2579
|
CODEX_REFRESH_WINDOW_MS,
|
|
2250
2580
|
CODEX_RESPONSES_BASE,
|
|
@@ -2262,6 +2592,7 @@ export {
|
|
|
2262
2592
|
CODEX_WEEKLY_WINDOW_SECONDS,
|
|
2263
2593
|
CODEX_WHAM_BASE,
|
|
2264
2594
|
CodexDeviceError,
|
|
2595
|
+
CodexRealtimeError,
|
|
2265
2596
|
CodexRefreshTransient,
|
|
2266
2597
|
CodexReloginRequired,
|
|
2267
2598
|
CodexResponseTimeoutError,
|
|
@@ -2283,8 +2614,13 @@ export {
|
|
|
2283
2614
|
codexAppsSanitizingFetch,
|
|
2284
2615
|
codexRequestStorage,
|
|
2285
2616
|
codexSubscriptionFetch,
|
|
2617
|
+
codexSubscriptionHeaders,
|
|
2286
2618
|
consumeCodexRateLimitResetCredit,
|
|
2619
|
+
contextAppendChunks,
|
|
2620
|
+
createCodexRealtimeCall,
|
|
2287
2621
|
decodeJwtPayload,
|
|
2622
|
+
encodeCodexRealtimeV3DelegationContextAppend,
|
|
2623
|
+
encodeCodexRealtimeV3SessionContextAppend,
|
|
2288
2624
|
exchangeDeviceCode,
|
|
2289
2625
|
fetchCodexModels,
|
|
2290
2626
|
fetchCodexRateLimitResetCredits,
|
|
@@ -2295,9 +2631,12 @@ export {
|
|
|
2295
2631
|
modelToolOutputSerializationBudgetTokens,
|
|
2296
2632
|
normalizeCodexRequestBody,
|
|
2297
2633
|
normalizeCodexUsage,
|
|
2634
|
+
opaqueProviderArtifactFingerprint,
|
|
2635
|
+
opaqueProviderArtifactFingerprints,
|
|
2298
2636
|
parseCodexRateLimitResetConsumeResponse,
|
|
2299
2637
|
parseCodexRateLimitResetCreditsDetails,
|
|
2300
2638
|
parseCodexRateLimitResetCreditsSummary,
|
|
2639
|
+
parseCodexRealtimeV3Event,
|
|
2301
2640
|
parseCodexUsageHeaders,
|
|
2302
2641
|
parseIdToken,
|
|
2303
2642
|
pollDeviceCode,
|
|
@@ -2306,6 +2645,7 @@ export {
|
|
|
2306
2645
|
resolveCodexResponseTimeoutPolicy,
|
|
2307
2646
|
sanitizeMcpJsonBody,
|
|
2308
2647
|
sanitizeMcpSseBody,
|
|
2648
|
+
selectCodexCredentialId,
|
|
2309
2649
|
startDeviceCode,
|
|
2310
2650
|
truncateMiddleWithTokenBudget,
|
|
2311
2651
|
withCodexRequestOverrides
|