@opengeni/codex 0.2.5 → 0.2.9

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
@@ -24,9 +24,15 @@ import {
24
24
  CODEX_REFRESH_FALLBACK_MS,
25
25
  CODEX_REFRESH_WINDOW_MS,
26
26
  CODEX_RESPONSES_BASE,
27
+ CODEX_RESPONSE_HEADERS_TIMEOUT_MS,
28
+ CODEX_RESPONSE_NO_BYTE_RETRIES,
29
+ CODEX_RESPONSE_RETRY_BACKOFF_MS,
30
+ CODEX_RESPONSE_SDK_OUTER_TIMEOUT_MS,
31
+ CODEX_RESPONSE_STREAM_IDLE_TIMEOUT_MS,
32
+ CODEX_RESPONSE_WHOLE_TIMEOUT_MS,
27
33
  CODEX_TOKEN_URL,
28
34
  CODEX_WHAM_BASE
29
- } from "./chunk-AMRVHBCD.js";
35
+ } from "./chunk-YGKQDUY7.js";
30
36
 
31
37
  // src/billing.ts
32
38
  function isCodexBilledModel(model) {
@@ -107,7 +113,37 @@ async function exchangeDeviceCode(input, fetchImpl = fetch) {
107
113
  };
108
114
  }
109
115
 
116
+ // src/bounded-operation.ts
117
+ async function runBoundedCodexOperation(operation, timeoutMs) {
118
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
119
+ throw new Error("Codex operation timeout must be positive");
120
+ }
121
+ const controller = new AbortController();
122
+ let timedOut = false;
123
+ let timeout;
124
+ const work = operation(controller.signal).then(
125
+ (value) => ({ ok: true, value }),
126
+ () => ({
127
+ ok: false,
128
+ reason: timedOut || controller.signal.aborted ? "timeout" : "network_error"
129
+ })
130
+ );
131
+ const deadline = new Promise((resolve) => {
132
+ timeout = setTimeout(() => {
133
+ timedOut = true;
134
+ controller.abort();
135
+ resolve({ ok: false, reason: "timeout" });
136
+ }, timeoutMs);
137
+ });
138
+ try {
139
+ return await Promise.race([work, deadline]);
140
+ } finally {
141
+ if (timeout) clearTimeout(timeout);
142
+ }
143
+ }
144
+
110
145
  // src/refresh.ts
146
+ var CODEX_REFRESH_TIMEOUT_MS = 5e3;
111
147
  var CodexReloginRequired = class extends Error {
112
148
  constructor(message) {
113
149
  super(message);
@@ -120,17 +156,24 @@ var CodexRefreshTransient = class extends Error {
120
156
  this.name = "CodexRefreshTransient";
121
157
  }
122
158
  };
123
- async function refreshCodexToken(refreshToken, fetchImpl = fetch) {
124
- const res = await fetchImpl(CODEX_TOKEN_URL, {
125
- method: "POST",
126
- headers: { "Content-Type": "application/json" },
127
- body: JSON.stringify({
128
- client_id: CODEX_CLIENT_ID,
129
- grant_type: "refresh_token",
130
- refresh_token: refreshToken
131
- })
132
- });
133
- const text = await res.text();
159
+ async function refreshCodexToken(refreshToken, fetchImpl = fetch, timeoutMs = CODEX_REFRESH_TIMEOUT_MS) {
160
+ const fetched = await runBoundedCodexOperation(async (signal) => {
161
+ const res2 = await fetchImpl(CODEX_TOKEN_URL, {
162
+ method: "POST",
163
+ headers: { "Content-Type": "application/json" },
164
+ body: JSON.stringify({
165
+ client_id: CODEX_CLIENT_ID,
166
+ grant_type: "refresh_token",
167
+ refresh_token: refreshToken
168
+ }),
169
+ signal
170
+ });
171
+ return { res: res2, text: await res2.text() };
172
+ }, timeoutMs);
173
+ if (!fetched.ok) {
174
+ throw new CodexRefreshTransient(`Codex token refresh ${fetched.reason}`);
175
+ }
176
+ const { res, text } = fetched.value;
134
177
  if (!res.ok) {
135
178
  const code = extractRefreshErrorCode(text);
136
179
  const msg = code ? PERMANENT_REFRESH_FAILURES[code] : void 0;
@@ -218,7 +261,8 @@ var CODEX_ALLOWED_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
218
261
  "stream",
219
262
  "include",
220
263
  "prompt_cache_key",
221
- "text"
264
+ "text",
265
+ "service_tier"
222
266
  ]);
223
267
  function normalizeCodexRequestBody(body, resolveModel) {
224
268
  body.store = false;
@@ -280,7 +324,89 @@ function buildModelResolver(liveSlugs, fallbackSlug) {
280
324
  }
281
325
 
282
326
  // src/usage-normalize.ts
327
+ import * as z2 from "zod/v4";
328
+
329
+ // src/reset-credits.ts
283
330
  import * as z from "zod/v4";
331
+ var CODEX_RATE_LIMIT_RESET_OUTCOMES = [
332
+ "reset",
333
+ "nothingToReset",
334
+ "noCredit",
335
+ "alreadyRedeemed"
336
+ ];
337
+ var nonNegativeInteger = z.number().int().nonnegative();
338
+ var backendTimestamp = z.string().datetime({ offset: true });
339
+ var backendCreditSchema = z.object({
340
+ id: z.string().min(1),
341
+ reset_type: z.string().min(1),
342
+ status: z.string().min(1),
343
+ granted_at: backendTimestamp,
344
+ expires_at: backendTimestamp.nullish(),
345
+ title: z.string().nullish(),
346
+ description: z.string().nullish()
347
+ }).passthrough();
348
+ var backendDetailsSchema = z.object({
349
+ credits: z.array(backendCreditSchema),
350
+ available_count: nonNegativeInteger
351
+ }).passthrough();
352
+ var backendUsageSummarySchema = z.object({
353
+ rate_limit_reset_credits: z.object({ available_count: nonNegativeInteger }).passthrough().nullish()
354
+ }).passthrough();
355
+ var backendConsumeOutcomes = [
356
+ "reset",
357
+ "nothing_to_reset",
358
+ "no_credit",
359
+ "already_redeemed"
360
+ ];
361
+ var backendConsumeSchema = z.object({
362
+ code: z.enum(backendConsumeOutcomes),
363
+ // The app-server intentionally discards this field. OpenGeni also refetches
364
+ // rather than inferring post-redemption state from it.
365
+ windows_reset: nonNegativeInteger.default(0)
366
+ }).passthrough();
367
+ function normalizedResetType(value) {
368
+ return value === "codex_rate_limits" ? "codexRateLimits" : "unknown";
369
+ }
370
+ function normalizedCreditStatus(value) {
371
+ if (value === "available" || value === "redeeming" || value === "redeemed") {
372
+ return value;
373
+ }
374
+ return "unknown";
375
+ }
376
+ function parseCodexRateLimitResetCreditsDetails(payload) {
377
+ const parsed = backendDetailsSchema.safeParse(payload);
378
+ if (!parsed.success) return null;
379
+ return {
380
+ availableCount: parsed.data.available_count,
381
+ credits: parsed.data.credits.map((credit) => ({
382
+ id: credit.id,
383
+ resetType: normalizedResetType(credit.reset_type),
384
+ status: normalizedCreditStatus(credit.status),
385
+ grantedAt: Math.floor(Date.parse(credit.granted_at) / 1e3),
386
+ expiresAt: credit.expires_at == null ? null : Math.floor(Date.parse(credit.expires_at) / 1e3),
387
+ title: credit.title ?? null,
388
+ description: credit.description ?? null
389
+ }))
390
+ };
391
+ }
392
+ function parseCodexRateLimitResetCreditsSummary(payload) {
393
+ const parsed = backendUsageSummarySchema.safeParse(payload);
394
+ const availableCount = parsed.success ? parsed.data.rate_limit_reset_credits?.available_count : void 0;
395
+ return availableCount === void 0 ? null : { availableCount, credits: null };
396
+ }
397
+ function parseCodexRateLimitResetConsumeResponse(payload) {
398
+ const parsed = backendConsumeSchema.safeParse(payload);
399
+ if (!parsed.success) return null;
400
+ const outcomes = {
401
+ reset: "reset",
402
+ nothing_to_reset: "nothingToReset",
403
+ no_credit: "noCredit",
404
+ already_redeemed: "alreadyRedeemed"
405
+ };
406
+ return { outcome: outcomes[parsed.data.code] };
407
+ }
408
+
409
+ // src/usage-normalize.ts
284
410
  var CODEX_FIVE_HOUR_WINDOW_SECONDS = 18e3;
285
411
  var CODEX_WEEKLY_WINDOW_SECONDS = 604800;
286
412
  function buildCodexUsageWindowFromCache(usedPercent, resetAt, limitWindowSeconds) {
@@ -301,34 +427,34 @@ function buildCodexUsageWindowFromCache(usedPercent, resetAt, limitWindowSeconds
301
427
  limitWindowSeconds
302
428
  };
303
429
  }
304
- var windowSchema = z.object({
305
- used_percent: z.number().optional(),
306
- reset_after_seconds: z.number().optional(),
307
- reset_at: z.number().optional(),
308
- limit_window_seconds: z.number().optional()
430
+ var windowSchema = z2.object({
431
+ used_percent: z2.number().optional(),
432
+ reset_after_seconds: z2.number().optional(),
433
+ reset_at: z2.number().optional(),
434
+ limit_window_seconds: z2.number().optional()
309
435
  }).nullish();
310
- var rateLimitSchema = z.object({
311
- allowed: z.boolean().optional(),
312
- limit_reached: z.boolean().optional(),
436
+ var rateLimitSchema = z2.object({
437
+ allowed: z2.boolean().optional(),
438
+ limit_reached: z2.boolean().optional(),
313
439
  primary_window: windowSchema,
314
440
  secondary_window: windowSchema
315
441
  }).nullish();
316
- var additionalLimitSchema = z.object({
317
- limit_name: z.string().optional(),
318
- metered_feature: z.string().optional(),
442
+ var additionalLimitSchema = z2.object({
443
+ limit_name: z2.string().optional(),
444
+ metered_feature: z2.string().optional(),
319
445
  primary_window: windowSchema,
320
446
  secondary_window: windowSchema
321
447
  });
322
- var creditsSchema = z.object({
323
- has_credits: z.boolean().optional(),
324
- unlimited: z.boolean().optional(),
325
- overage_limit_reached: z.boolean().optional(),
326
- balance: z.union([z.string(), z.number()]).optional()
448
+ var creditsSchema = z2.object({
449
+ has_credits: z2.boolean().optional(),
450
+ unlimited: z2.boolean().optional(),
451
+ overage_limit_reached: z2.boolean().optional(),
452
+ balance: z2.union([z2.string(), z2.number()]).optional()
327
453
  }).nullish();
328
- var usageBodySchema = z.object({
329
- plan_type: z.string().nullish(),
454
+ var usageBodySchema = z2.object({
455
+ plan_type: z2.string().nullish(),
330
456
  rate_limit: rateLimitSchema,
331
- additional_limits: z.array(additionalLimitSchema).nullish(),
457
+ additional_limits: z2.array(additionalLimitSchema).nullish(),
332
458
  credits: creditsSchema
333
459
  });
334
460
  function clampPercent(value) {
@@ -391,7 +517,8 @@ function normalizeCodexUsage(httpStatus, rawPayload) {
391
517
  fiveHour: null,
392
518
  weekly: null,
393
519
  limitReached: false,
394
- fetchedAt
520
+ fetchedAt,
521
+ rateLimitResetCredits: parseCodexRateLimitResetCreditsSummary(rawPayload)
395
522
  };
396
523
  if (httpStatus >= 400 && httpStatus !== 404 || body == null) {
397
524
  return { ...base, status: "error" };
@@ -437,6 +564,9 @@ function normalizeCodexUsage(httpStatus, rawPayload) {
437
564
  }
438
565
 
439
566
  // src/api-client.ts
567
+ var CODEX_READ_TIMEOUT_MS = 5e3;
568
+ var RESET_CREDIT_DETAILS_TIMEOUT_MS = 5e3;
569
+ var RESET_CREDIT_CONSUME_TIMEOUT_MS = 1e4;
440
570
  function subscriptionHeaders(a) {
441
571
  return {
442
572
  Authorization: `Bearer ${a.accessToken}`,
@@ -447,35 +577,183 @@ function subscriptionHeaders(a) {
447
577
  ...a.isFedramp ? { "X-OpenAI-Fedramp": "true" } : {}
448
578
  };
449
579
  }
450
- async function fetchCodexModels(a, fetchImpl = fetch) {
451
- const res = await fetchImpl(
452
- `${CODEX_RESPONSES_BASE}/models?client_version=${encodeURIComponent(a.clientVersion)}`,
453
- {
580
+ async function fetchCodexModels(a, fetchImpl = fetch, timeoutMs = CODEX_READ_TIMEOUT_MS) {
581
+ const fetched = await runBoundedCodexOperation(async (signal) => {
582
+ const res = await fetchImpl(
583
+ `${CODEX_RESPONSES_BASE}/models?client_version=${encodeURIComponent(a.clientVersion)}`,
584
+ { method: "GET", headers: subscriptionHeaders(a), signal }
585
+ );
586
+ if (!res.ok) {
587
+ await res.arrayBuffer().catch(() => void 0);
588
+ return { ok: false, status: res.status, slugs: [] };
589
+ }
590
+ const body = await res.json();
591
+ const slugs = (body.models ?? []).map((model) => model.slug).filter((slug) => typeof slug === "string");
592
+ return { ok: true, status: res.status, slugs };
593
+ }, timeoutMs);
594
+ return fetched.ok ? fetched.value : { ok: false, status: 0, slugs: [] };
595
+ }
596
+ async function fetchCodexUsage(a, fetchImpl = fetch, timeoutMs = CODEX_READ_TIMEOUT_MS) {
597
+ const fetched = await runBoundedCodexOperation(async (signal) => {
598
+ const res = await fetchImpl(`${CODEX_WHAM_BASE}/wham/usage`, {
599
+ method: "GET",
600
+ headers: subscriptionHeaders(a),
601
+ signal
602
+ });
603
+ const payload = res.ok || res.status === 404 ? await res.json().catch(() => null) : null;
604
+ if (!res.ok && res.status !== 404) await res.arrayBuffer().catch(() => void 0);
605
+ return { status: res.status, payload };
606
+ }, timeoutMs);
607
+ if (!fetched.ok) throw new Error(`Codex usage request ${fetched.reason}`);
608
+ return fetched.value;
609
+ }
610
+ async function fetchCodexRateLimitResetCredits(a, fetchImpl = fetch, timeoutMs = RESET_CREDIT_DETAILS_TIMEOUT_MS) {
611
+ const fetched = await runBoundedCodexOperation(async (signal) => {
612
+ const res = await fetchImpl(`${CODEX_WHAM_BASE}/wham/rate-limit-reset-credits`, {
454
613
  method: "GET",
455
- headers: subscriptionHeaders(a)
614
+ headers: subscriptionHeaders(a),
615
+ signal
616
+ });
617
+ if (!res.ok) {
618
+ await res.arrayBuffer().catch(() => void 0);
619
+ return { ok: false, status: res.status, reason: "http_error" };
456
620
  }
457
- );
458
- if (!res.ok) {
459
- return { ok: false, status: res.status, slugs: [] };
460
- }
461
- const body = await res.json();
462
- const slugs = (body.models ?? []).map((m) => m.slug).filter((s) => typeof s === "string");
463
- return { ok: true, status: res.status, slugs };
464
- }
465
- async function fetchCodexUsage(a, fetchImpl = fetch) {
466
- const res = await fetchImpl(`${CODEX_WHAM_BASE}/wham/usage`, {
467
- method: "GET",
468
- headers: subscriptionHeaders(a)
469
- });
470
- const payload = res.ok || res.status === 404 ? await res.json().catch(() => null) : null;
471
- return { status: res.status, payload };
621
+ const details = parseCodexRateLimitResetCreditsDetails(await res.json().catch(() => null));
622
+ return details ? { ok: true, status: res.status, details } : { ok: false, status: res.status, reason: "invalid_response" };
623
+ }, timeoutMs);
624
+ return fetched.ok ? fetched.value : { ok: false, status: 0, reason: fetched.reason };
625
+ }
626
+ async function consumeCodexRateLimitResetCredit(a, input, fetchImpl = fetch, timeoutMs = RESET_CREDIT_CONSUME_TIMEOUT_MS) {
627
+ if (input.idempotencyKey.length === 0 || input.creditId === "") {
628
+ return { ok: false, status: 0, reason: "invalid_request" };
629
+ }
630
+ const fetched = await runBoundedCodexOperation(async (signal) => {
631
+ const res = await fetchImpl(`${CODEX_WHAM_BASE}/wham/rate-limit-reset-credits/consume`, {
632
+ method: "POST",
633
+ headers: {
634
+ ...subscriptionHeaders(a),
635
+ "content-type": "application/json"
636
+ },
637
+ body: JSON.stringify({
638
+ redeem_request_id: input.idempotencyKey,
639
+ ...input.creditId ? { credit_id: input.creditId } : {}
640
+ }),
641
+ signal
642
+ });
643
+ if (!res.ok) {
644
+ await res.arrayBuffer().catch(() => void 0);
645
+ return { ok: false, status: res.status, reason: "http_error" };
646
+ }
647
+ const result = parseCodexRateLimitResetConsumeResponse(await res.json().catch(() => null));
648
+ return result ? { ok: true, status: res.status, result } : { ok: false, status: res.status, reason: "invalid_response" };
649
+ }, timeoutMs);
650
+ return fetched.ok ? fetched.value : { ok: false, status: 0, reason: fetched.reason };
472
651
  }
473
652
 
474
653
  // src/request-context.ts
475
654
  import { AsyncLocalStorage } from "async_hooks";
476
655
  var codexRequestStorage = new AsyncLocalStorage();
656
+ function withCodexRequestOverrides(overrides, fn) {
657
+ const current = codexRequestStorage.getStore();
658
+ if (!current) return fn();
659
+ return codexRequestStorage.run({ ...current, ...overrides }, fn);
660
+ }
661
+
662
+ // src/response-timeout.ts
663
+ var CODEX_RESPONSE_TIMEOUT_ERROR_TYPE = "opengeni_codex_response_timeout";
664
+ var DEFAULT_CODEX_RESPONSE_TIMEOUT_POLICY = Object.freeze({
665
+ headersTimeoutMs: CODEX_RESPONSE_HEADERS_TIMEOUT_MS,
666
+ streamIdleTimeoutMs: CODEX_RESPONSE_STREAM_IDLE_TIMEOUT_MS,
667
+ wholeRequestTimeoutMs: CODEX_RESPONSE_WHOLE_TIMEOUT_MS,
668
+ noByteRetries: CODEX_RESPONSE_NO_BYTE_RETRIES,
669
+ retryBackoffMs: CODEX_RESPONSE_RETRY_BACKOFF_MS
670
+ });
671
+ function positiveFinite(value, fallback) {
672
+ return value !== void 0 && Number.isFinite(value) && value > 0 ? value : fallback;
673
+ }
674
+ function resolveCodexResponseTimeoutPolicy(override) {
675
+ return {
676
+ headersTimeoutMs: positiveFinite(
677
+ override?.headersTimeoutMs,
678
+ DEFAULT_CODEX_RESPONSE_TIMEOUT_POLICY.headersTimeoutMs
679
+ ),
680
+ streamIdleTimeoutMs: positiveFinite(
681
+ override?.streamIdleTimeoutMs,
682
+ DEFAULT_CODEX_RESPONSE_TIMEOUT_POLICY.streamIdleTimeoutMs
683
+ ),
684
+ wholeRequestTimeoutMs: positiveFinite(
685
+ override?.wholeRequestTimeoutMs,
686
+ DEFAULT_CODEX_RESPONSE_TIMEOUT_POLICY.wholeRequestTimeoutMs
687
+ ),
688
+ // Automatic replay is fail-closed until the provider operation can be
689
+ // durably read/reconciled. Keep the field for policy/event compatibility,
690
+ // but never let a caller opt back into an unproved retry.
691
+ noByteRetries: 0,
692
+ retryBackoffMs: override?.retryBackoffMs !== void 0 && Number.isFinite(override.retryBackoffMs) && override.retryBackoffMs >= 0 ? override.retryBackoffMs : DEFAULT_CODEX_RESPONSE_TIMEOUT_POLICY.retryBackoffMs
693
+ };
694
+ }
695
+ var CodexResponseTimeoutError = class extends Error {
696
+ constructor(timeoutClass, requestId, responseObserved, message = `Codex response ${timeoutClass.replaceAll("_", " ")} timed out`) {
697
+ super(message);
698
+ this.timeoutClass = timeoutClass;
699
+ this.requestId = requestId;
700
+ this.responseObserved = responseObserved;
701
+ this.name = "CodexResponseTimeoutError";
702
+ }
703
+ code = CODEX_RESPONSE_TIMEOUT_ERROR_TYPE;
704
+ type = CODEX_RESPONSE_TIMEOUT_ERROR_TYPE;
705
+ };
706
+ function parseTimeoutClass(value) {
707
+ return value === "connect" || value === "headers" || value === "idle_stream" || value === "whole_request" ? value : null;
708
+ }
709
+ function classifyCodexResponseTimeoutError(error, options = {}) {
710
+ let current = error;
711
+ for (let depth = 0; depth < 8 && current && typeof current === "object"; depth += 1) {
712
+ const value = current;
713
+ const nested = value.error && typeof value.error === "object" ? value.error : void 0;
714
+ const type = (typeof value.type === "string" ? value.type : void 0) ?? (typeof value.code === "string" ? value.code : void 0) ?? (typeof nested?.type === "string" ? nested.type : void 0) ?? (typeof nested?.code === "string" ? nested.code : void 0);
715
+ if (type === CODEX_RESPONSE_TIMEOUT_ERROR_TYPE || value.name === "CodexResponseTimeoutError") {
716
+ const klass = parseTimeoutClass(value.timeoutClass) ?? parseTimeoutClass(nested?.timeout_class) ?? "headers";
717
+ return {
718
+ timeoutClass: klass,
719
+ requestId: (typeof value.requestId === "string" ? value.requestId : void 0) ?? (typeof nested?.request_id === "string" ? nested.request_id : null),
720
+ responseObserved: typeof value.responseObserved === "boolean" ? value.responseObserved : nested?.response_observed === true,
721
+ message: (typeof value.message === "string" ? value.message : void 0) ?? (typeof nested?.message === "string" ? nested.message : "Codex response timed out")
722
+ };
723
+ }
724
+ current = value.cause;
725
+ }
726
+ if (options.allowLegacyRequestTimeout && error && typeof error === "object") {
727
+ const value = error;
728
+ if (value.name === "APIConnectionTimeoutError" || value.message === "Request timed out." && value.name === "Error") {
729
+ return {
730
+ timeoutClass: "headers",
731
+ requestId: null,
732
+ responseObserved: false,
733
+ message: String(value.message ?? "Request timed out.")
734
+ };
735
+ }
736
+ }
737
+ return null;
738
+ }
739
+ function isPreHeadersTimeoutError(error) {
740
+ const structured = classifyCodexResponseTimeoutError(error);
741
+ if (structured && !structured.responseObserved) {
742
+ return structured.timeoutClass;
743
+ }
744
+ if (!error || typeof error !== "object") return null;
745
+ const value = error;
746
+ const code = typeof value.code === "string" ? value.code : "";
747
+ const name = typeof value.name === "string" ? value.name : "";
748
+ const message = typeof value.message === "string" ? value.message : String(error);
749
+ if (/^(?:ETIMEDOUT|UND_ERR_CONNECT_TIMEOUT)$/i.test(code) || /ConnectTimeout/i.test(name)) {
750
+ return "connect";
751
+ }
752
+ return /connect(?:ion)?[^.]*timed?\s*out/i.test(`${name} ${message}`) ? "connect" : null;
753
+ }
477
754
 
478
755
  // src/fetch.ts
756
+ import { randomUUID } from "crypto";
479
757
  var CODEX_TRANSPORT_ERROR_HEADER = "x-opengeni-codex-transport-error";
480
758
  function headersCarryCodexTransportMarker(headers) {
481
759
  if (!headers || typeof headers !== "object") return false;
@@ -495,6 +773,31 @@ function isCodexTransportError(error) {
495
773
  }
496
774
  return false;
497
775
  }
776
+ function classifyCodexEncryptedArtifactRejection(error) {
777
+ if (!isCodexTransportError(error)) return null;
778
+ let current = error;
779
+ for (let depth = 0; depth < 6 && current && typeof current === "object"; depth += 1) {
780
+ const value = current;
781
+ const body = value.error && typeof value.error === "object" ? value.error : null;
782
+ const status = Number(value.status ?? body?.status);
783
+ const message = [
784
+ typeof value.message === "string" ? value.message : "",
785
+ typeof body?.message === "string" ? body.message : "",
786
+ typeof value.code === "string" ? value.code : "",
787
+ typeof body?.code === "string" ? body.code : "",
788
+ typeof value.type === "string" ? value.type : "",
789
+ typeof body?.type === "string" ? body.type : ""
790
+ ].join(" ").toLowerCase();
791
+ const unsupportedFieldShape = /(?:invalid value|supported values?|unsupported|unknown (?:field|parameter|value))/.test(
792
+ message
793
+ );
794
+ if (status === 400 && !unsupportedFieldShape && /(?:encrypted[_ ]content|encrypted reasoning|reasoning artifact)/.test(message) && /(?:decrypt(?:ed|ion)?|could not be parsed|cannot be parsed|failed to parse)/.test(message)) {
795
+ return { status: 400, kind: "encrypted_content_rejected" };
796
+ }
797
+ current = value.cause;
798
+ }
799
+ return null;
800
+ }
498
801
  function parseIntHeader(value) {
499
802
  if (value === null) {
500
803
  return null;
@@ -538,6 +841,216 @@ function parseCodexUsageHeaders(headers) {
538
841
  checkedAt: new Date(nowMs)
539
842
  };
540
843
  }
844
+ async function emitRequestEvent(audit, event) {
845
+ await audit.ctx.onModelRequestEvent?.({
846
+ requestId: audit.requestId,
847
+ transportAttempt: audit.transportAttempt,
848
+ ...audit.model ? { model: audit.model } : {},
849
+ durationMs: Math.max(0, Date.now() - audit.attemptStartedAt),
850
+ timeoutPolicy: audit.policy,
851
+ ...event
852
+ });
853
+ }
854
+ function providerRequestId(headers) {
855
+ return headers.get("x-request-id") ?? headers.get("request-id") ?? void 0;
856
+ }
857
+ async function fetchBeforeHeaders(base, input, init, audit) {
858
+ const elapsed = Date.now() - audit.logicalStartedAt;
859
+ const wholeRemainingMs = audit.policy.wholeRequestTimeoutMs - elapsed;
860
+ const timeoutClass = wholeRemainingMs <= audit.policy.headersTimeoutMs ? "whole_request" : "headers";
861
+ const deadlineMs = Math.max(1, Math.min(audit.policy.headersTimeoutMs, wholeRemainingMs));
862
+ if (wholeRemainingMs <= 0) {
863
+ throw new CodexResponseTimeoutError("whole_request", audit.requestId, false);
864
+ }
865
+ const externalSignal = init.signal;
866
+ if (externalSignal?.aborted) throw externalSignal.reason;
867
+ const controller = new AbortController();
868
+ const forwardAbort = () => controller.abort(externalSignal?.reason);
869
+ externalSignal?.addEventListener("abort", forwardAbort, { once: true });
870
+ const basePromise = base(input, { ...init, signal: controller.signal });
871
+ let deadlineError = null;
872
+ let timer;
873
+ const deadline = new Promise((_resolve, reject) => {
874
+ timer = setTimeout(() => {
875
+ deadlineError = new CodexResponseTimeoutError(timeoutClass, audit.requestId, false);
876
+ reject(deadlineError);
877
+ }, deadlineMs);
878
+ });
879
+ try {
880
+ return await Promise.race([basePromise, deadline]);
881
+ } catch (error) {
882
+ if (deadlineError) {
883
+ controller.abort(deadlineError);
884
+ void basePromise.then((late) => late.body?.cancel(deadlineError ?? void 0)).catch(() => void 0);
885
+ throw deadlineError;
886
+ }
887
+ throw error;
888
+ } finally {
889
+ if (timer) clearTimeout(timer);
890
+ externalSignal?.removeEventListener("abort", forwardAbort);
891
+ }
892
+ }
893
+ async function observedResponse(res, audit, externalSignal) {
894
+ const requestId = providerRequestId(res.headers);
895
+ if (!res.body) {
896
+ await emitRequestEvent(audit, {
897
+ phase: res.ok ? "completed" : "failed",
898
+ responseObserved: true,
899
+ status: res.status,
900
+ ...requestId ? { providerRequestId: requestId } : {}
901
+ });
902
+ return res;
903
+ }
904
+ const reader = res.body.getReader();
905
+ let terminal = false;
906
+ let firstByte = false;
907
+ let idleTimer;
908
+ let wholeTimer;
909
+ let armIdle = () => void 0;
910
+ let abortFromOutside;
911
+ const clearTimers = () => {
912
+ if (idleTimer) clearTimeout(idleTimer);
913
+ if (wholeTimer) clearTimeout(wholeTimer);
914
+ if (abortFromOutside) externalSignal?.removeEventListener("abort", abortFromOutside);
915
+ };
916
+ const body = new ReadableStream({
917
+ start(controller) {
918
+ const timeOut = (klass) => {
919
+ if (terminal) return;
920
+ terminal = true;
921
+ clearTimers();
922
+ const error = new CodexResponseTimeoutError(klass, audit.requestId, true);
923
+ void reader.cancel(error).catch(() => void 0);
924
+ void emitRequestEvent(audit, {
925
+ phase: "timed_out",
926
+ responseObserved: true,
927
+ timeoutClass: klass,
928
+ status: res.status,
929
+ ...requestId ? { providerRequestId: requestId } : {}
930
+ }).then(
931
+ () => controller.error(error),
932
+ () => controller.error(error)
933
+ );
934
+ };
935
+ armIdle = () => {
936
+ if (idleTimer) clearTimeout(idleTimer);
937
+ idleTimer = setTimeout(() => timeOut("idle_stream"), audit.policy.streamIdleTimeoutMs);
938
+ };
939
+ armIdle();
940
+ const wholeRemaining = Math.max(
941
+ 1,
942
+ audit.policy.wholeRequestTimeoutMs - (Date.now() - audit.logicalStartedAt)
943
+ );
944
+ wholeTimer = setTimeout(() => timeOut("whole_request"), wholeRemaining);
945
+ abortFromOutside = () => {
946
+ if (terminal) return;
947
+ terminal = true;
948
+ clearTimers();
949
+ const reason = externalSignal?.reason ?? new DOMException("Aborted", "AbortError");
950
+ void reader.cancel(reason).catch(() => void 0);
951
+ void emitRequestEvent(audit, {
952
+ phase: "failed",
953
+ responseObserved: true,
954
+ status: res.status,
955
+ ...requestId ? { providerRequestId: requestId } : {}
956
+ }).then(
957
+ () => controller.error(reason),
958
+ () => controller.error(reason)
959
+ );
960
+ };
961
+ if (externalSignal?.aborted) {
962
+ abortFromOutside();
963
+ } else {
964
+ externalSignal?.addEventListener("abort", abortFromOutside, {
965
+ once: true
966
+ });
967
+ }
968
+ },
969
+ async pull(controller) {
970
+ if (terminal) return;
971
+ try {
972
+ const chunk = await reader.read();
973
+ if (terminal) return;
974
+ if (chunk.done) {
975
+ terminal = true;
976
+ clearTimers();
977
+ await emitRequestEvent(audit, {
978
+ phase: res.ok ? "completed" : "failed",
979
+ responseObserved: true,
980
+ status: res.status,
981
+ ...requestId ? { providerRequestId: requestId } : {}
982
+ });
983
+ controller.close();
984
+ return;
985
+ }
986
+ armIdle();
987
+ if (!firstByte) {
988
+ firstByte = true;
989
+ await emitRequestEvent(audit, {
990
+ phase: "first_byte",
991
+ responseObserved: true,
992
+ status: res.status,
993
+ ...requestId ? { providerRequestId: requestId } : {}
994
+ });
995
+ }
996
+ controller.enqueue(chunk.value);
997
+ } catch (error) {
998
+ if (terminal) return;
999
+ terminal = true;
1000
+ clearTimers();
1001
+ await emitRequestEvent(audit, {
1002
+ phase: "failed",
1003
+ responseObserved: true,
1004
+ status: res.status,
1005
+ ...requestId ? { providerRequestId: requestId } : {}
1006
+ });
1007
+ controller.error(error);
1008
+ }
1009
+ },
1010
+ async cancel(reason) {
1011
+ if (!terminal) {
1012
+ terminal = true;
1013
+ clearTimers();
1014
+ await emitRequestEvent(audit, {
1015
+ phase: "failed",
1016
+ responseObserved: true,
1017
+ status: res.status,
1018
+ ...requestId ? { providerRequestId: requestId } : {}
1019
+ }).catch(() => void 0);
1020
+ }
1021
+ await reader.cancel(reason).catch(() => void 0);
1022
+ }
1023
+ });
1024
+ const headers = new Headers(res.headers);
1025
+ headers.delete("content-length");
1026
+ return new Response(body, {
1027
+ status: res.status,
1028
+ statusText: res.statusText,
1029
+ headers
1030
+ });
1031
+ }
1032
+ function timeoutErrorResponse(info) {
1033
+ return new Response(
1034
+ JSON.stringify({
1035
+ error: {
1036
+ type: CODEX_RESPONSE_TIMEOUT_ERROR_TYPE,
1037
+ code: CODEX_RESPONSE_TIMEOUT_ERROR_TYPE,
1038
+ message: info.message,
1039
+ timeout_class: info.timeoutClass,
1040
+ response_observed: info.responseObserved,
1041
+ request_id: info.requestId
1042
+ }
1043
+ }),
1044
+ {
1045
+ status: 504,
1046
+ headers: {
1047
+ "content-type": "application/json",
1048
+ "x-should-retry": "false",
1049
+ [CODEX_TRANSPORT_ERROR_HEADER]: "1"
1050
+ }
1051
+ }
1052
+ );
1053
+ }
541
1054
  function codexSubscriptionFetch(base = globalThis.fetch) {
542
1055
  return async (input, init) => {
543
1056
  const ctx = codexRequestStorage.getStore();
@@ -546,7 +1059,11 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
546
1059
  }
547
1060
  const rawUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
548
1061
  const rewritten = rawUrl.replace(/(?<!\/codex)\/responses(\b|$)/, "/codex/responses$1");
549
- const attempt = async (auth) => {
1062
+ const policy = resolveCodexResponseTimeoutPolicy(ctx.responseTimeoutPolicy);
1063
+ const requestId = ctx.nextRequestId?.() ?? randomUUID();
1064
+ const logicalStartedAt = Date.now();
1065
+ let transportAttempt = 0;
1066
+ const attempt = async (auth, authenticationAttempt) => {
550
1067
  const headers = new Headers(init?.headers);
551
1068
  headers.set("Authorization", `Bearer ${auth.accessToken}`);
552
1069
  if (auth.chatgptAccountId) {
@@ -565,42 +1082,121 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
565
1082
  }
566
1083
  headers.delete("OpenAI-Beta");
567
1084
  headers.delete("x-api-key");
1085
+ if (ctx.betaFeatures && ctx.betaFeatures.length > 0) {
1086
+ headers.set("x-codex-beta-features", ctx.betaFeatures.join(","));
1087
+ }
1088
+ if (ctx.turnMetadata && Object.keys(ctx.turnMetadata).length > 0) {
1089
+ headers.set("x-codex-turn-metadata", JSON.stringify(ctx.turnMetadata));
1090
+ }
568
1091
  let callerWantsStream = true;
1092
+ let model;
569
1093
  const nextInit = { ...init, headers };
570
1094
  if (typeof init?.body === "string") {
571
1095
  try {
572
1096
  const parsed = JSON.parse(init.body);
573
1097
  callerWantsStream = parsed.stream === true;
574
- nextInit.body = JSON.stringify(normalizeCodexRequestBody(parsed, ctx.resolveModel));
1098
+ const normalized = normalizeCodexRequestBody(parsed, ctx.resolveModel);
1099
+ model = typeof normalized.model === "string" ? normalized.model : void 0;
1100
+ nextInit.body = JSON.stringify(normalized);
575
1101
  } catch {
576
1102
  }
577
1103
  }
1104
+ headers.set(
1105
+ "Idempotency-Key",
1106
+ authenticationAttempt === 0 ? requestId : `${requestId}:auth-${authenticationAttempt}`
1107
+ );
578
1108
  if (process.env.CODEX_DEBUG) {
579
- const keys = typeof nextInit.body === "string" ? Object.keys(JSON.parse(nextInit.body)) : [];
1109
+ let keys = [];
1110
+ if (typeof nextInit.body === "string") {
1111
+ try {
1112
+ keys = Object.keys(JSON.parse(nextInit.body));
1113
+ } catch {
1114
+ }
1115
+ }
580
1116
  console.error(
581
1117
  `[codex-debug] POST ${rewritten} stream=${callerWantsStream} bodyKeys=[${keys.join(",")}]`
582
1118
  );
583
1119
  }
584
- const res2 = await base(rewritten, nextInit);
585
- const usage = parseCodexUsageHeaders(res2.headers);
1120
+ let res;
1121
+ transportAttempt += 1;
1122
+ const audit = {
1123
+ ctx,
1124
+ requestId,
1125
+ transportAttempt,
1126
+ ...model ? { model } : {},
1127
+ logicalStartedAt,
1128
+ attemptStartedAt: Date.now(),
1129
+ policy
1130
+ };
1131
+ await emitRequestEvent(audit, {
1132
+ phase: "started",
1133
+ responseObserved: false
1134
+ });
1135
+ try {
1136
+ res = await fetchBeforeHeaders(base, rewritten, nextInit, audit);
1137
+ const upstreamRequestId = providerRequestId(res.headers);
1138
+ await emitRequestEvent(audit, {
1139
+ phase: "headers",
1140
+ responseObserved: true,
1141
+ status: res.status,
1142
+ ...upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}
1143
+ });
1144
+ const observed = await observedResponse(res, audit, nextInit.signal);
1145
+ res = observed;
1146
+ } catch (error) {
1147
+ if (nextInit.signal?.aborted) {
1148
+ await emitRequestEvent(audit, {
1149
+ phase: "failed",
1150
+ responseObserved: false
1151
+ }).catch(() => void 0);
1152
+ throw error;
1153
+ }
1154
+ const klass = isPreHeadersTimeoutError(error);
1155
+ if (!klass) {
1156
+ await emitRequestEvent(audit, {
1157
+ phase: "failed",
1158
+ responseObserved: false
1159
+ });
1160
+ throw error;
1161
+ }
1162
+ await emitRequestEvent(audit, {
1163
+ phase: "timed_out",
1164
+ responseObserved: false,
1165
+ timeoutClass: klass,
1166
+ willRetry: false
1167
+ }).catch(() => void 0);
1168
+ throw new CodexResponseTimeoutError(klass, requestId, false);
1169
+ }
1170
+ const usage = parseCodexUsageHeaders(res.headers);
586
1171
  if (usage) {
587
1172
  ctx.onUsageHeaders?.(usage);
588
1173
  }
589
- if (process.env.CODEX_DEBUG && !res2.ok) {
1174
+ if (process.env.CODEX_DEBUG && !res.ok) {
590
1175
  console.error(
591
- `[codex-debug] <- ${res2.status} requestId=${res2.headers.get("x-request-id") ?? "unknown"}`
1176
+ `[codex-debug] <- ${res.status} requestId=${res.headers.get("x-request-id") ?? "unknown"}`
592
1177
  );
593
1178
  }
594
- if (!res2.ok) {
595
- return await bufferCodexErrorResponse(res2);
1179
+ if (!res.ok) {
1180
+ return await bufferCodexErrorResponse(res);
596
1181
  }
597
- return callerWantsStream ? repairCodexStream(res2) : await sseToJsonResponse(res2);
1182
+ return callerWantsStream ? repairCodexStream(res) : await sseToJsonResponse(res);
598
1183
  };
599
- let res = await attempt(await ctx.getToken());
600
- if (res.status === 401) {
601
- res = await attempt(await ctx.refresh());
1184
+ try {
1185
+ let res = await attempt(await ctx.getToken(), 0);
1186
+ if (res.status === 401) {
1187
+ res = await attempt(await ctx.refresh(), 1);
1188
+ }
1189
+ return res;
1190
+ } catch (error) {
1191
+ const timeout = classifyCodexResponseTimeoutError(error);
1192
+ if (!timeout) throw error;
1193
+ return timeoutErrorResponse({
1194
+ timeoutClass: timeout.timeoutClass,
1195
+ requestId: timeout.requestId ?? requestId,
1196
+ responseObserved: timeout.responseObserved,
1197
+ message: timeout.message
1198
+ });
602
1199
  }
603
- return res;
604
1200
  };
605
1201
  }
606
1202
  var CODEX_USAGE_LIMIT_ERROR_TYPE = "usage_limit_reached";
@@ -1648,9 +2244,17 @@ export {
1648
2244
  CODEX_ORIGINATOR,
1649
2245
  CODEX_PROVIDER_BASE_URL,
1650
2246
  CODEX_PROVIDER_ID,
2247
+ CODEX_RATE_LIMIT_RESET_OUTCOMES,
1651
2248
  CODEX_REFRESH_FALLBACK_MS,
1652
2249
  CODEX_REFRESH_WINDOW_MS,
1653
2250
  CODEX_RESPONSES_BASE,
2251
+ CODEX_RESPONSE_HEADERS_TIMEOUT_MS,
2252
+ CODEX_RESPONSE_NO_BYTE_RETRIES,
2253
+ CODEX_RESPONSE_RETRY_BACKOFF_MS,
2254
+ CODEX_RESPONSE_SDK_OUTER_TIMEOUT_MS,
2255
+ CODEX_RESPONSE_STREAM_IDLE_TIMEOUT_MS,
2256
+ CODEX_RESPONSE_TIMEOUT_ERROR_TYPE,
2257
+ CODEX_RESPONSE_WHOLE_TIMEOUT_MS,
1654
2258
  CODEX_TOKEN_URL,
1655
2259
  CODEX_TOOL_OUTPUT_SERIALIZATION_ALLOWANCE,
1656
2260
  CODEX_TRANSPORT_ERROR_HEADER,
@@ -1660,7 +2264,9 @@ export {
1660
2264
  CodexDeviceError,
1661
2265
  CodexRefreshTransient,
1662
2266
  CodexReloginRequired,
2267
+ CodexResponseTimeoutError,
1663
2268
  CodexStreamingTerminalError,
2269
+ DEFAULT_CODEX_RESPONSE_TIMEOUT_POLICY,
1664
2270
  DEFAULT_MODEL_TOOL_OUTPUT_TRUNCATION_TOKENS,
1665
2271
  MODEL_TOOL_OUTPUT_OPAQUE_PAYLOAD_MAX_BYTES,
1666
2272
  MODEL_TOOL_OUTPUT_OVERSIZED_IMAGE_CARD_DATA_URL,
@@ -1671,27 +2277,37 @@ export {
1671
2277
  boundModelToolOutputItems,
1672
2278
  buildCodexUsageWindowFromCache,
1673
2279
  buildModelResolver,
2280
+ classifyCodexEncryptedArtifactRejection,
2281
+ classifyCodexResponseTimeoutError,
1674
2282
  classifyCodexUsageLimitError,
1675
2283
  codexAppsSanitizingFetch,
1676
2284
  codexRequestStorage,
1677
2285
  codexSubscriptionFetch,
2286
+ consumeCodexRateLimitResetCredit,
1678
2287
  decodeJwtPayload,
1679
2288
  exchangeDeviceCode,
1680
2289
  fetchCodexModels,
2290
+ fetchCodexRateLimitResetCredits,
1681
2291
  fetchCodexUsage,
1682
2292
  isCodexBilledModel,
1683
2293
  isCodexTransportError,
2294
+ isPreHeadersTimeoutError,
1684
2295
  modelToolOutputSerializationBudgetTokens,
1685
2296
  normalizeCodexRequestBody,
1686
2297
  normalizeCodexUsage,
2298
+ parseCodexRateLimitResetConsumeResponse,
2299
+ parseCodexRateLimitResetCreditsDetails,
2300
+ parseCodexRateLimitResetCreditsSummary,
1687
2301
  parseCodexUsageHeaders,
1688
2302
  parseIdToken,
1689
2303
  pollDeviceCode,
1690
2304
  refreshCodexToken,
1691
2305
  remapToolCallRequestBody,
2306
+ resolveCodexResponseTimeoutPolicy,
1692
2307
  sanitizeMcpJsonBody,
1693
2308
  sanitizeMcpSseBody,
1694
2309
  startDeviceCode,
1695
- truncateMiddleWithTokenBudget
2310
+ truncateMiddleWithTokenBudget,
2311
+ withCodexRequestOverrides
1696
2312
  };
1697
2313
  //# sourceMappingURL=index.js.map