@opengeni/codex 0.2.5 → 0.2.7

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;
@@ -280,7 +323,89 @@ function buildModelResolver(liveSlugs, fallbackSlug) {
280
323
  }
281
324
 
282
325
  // src/usage-normalize.ts
326
+ import * as z2 from "zod/v4";
327
+
328
+ // src/reset-credits.ts
283
329
  import * as z from "zod/v4";
330
+ var CODEX_RATE_LIMIT_RESET_OUTCOMES = [
331
+ "reset",
332
+ "nothingToReset",
333
+ "noCredit",
334
+ "alreadyRedeemed"
335
+ ];
336
+ var nonNegativeInteger = z.number().int().nonnegative();
337
+ var backendTimestamp = z.string().datetime({ offset: true });
338
+ var backendCreditSchema = z.object({
339
+ id: z.string().min(1),
340
+ reset_type: z.string().min(1),
341
+ status: z.string().min(1),
342
+ granted_at: backendTimestamp,
343
+ expires_at: backendTimestamp.nullish(),
344
+ title: z.string().nullish(),
345
+ description: z.string().nullish()
346
+ }).passthrough();
347
+ var backendDetailsSchema = z.object({
348
+ credits: z.array(backendCreditSchema),
349
+ available_count: nonNegativeInteger
350
+ }).passthrough();
351
+ var backendUsageSummarySchema = z.object({
352
+ rate_limit_reset_credits: z.object({ available_count: nonNegativeInteger }).passthrough().nullish()
353
+ }).passthrough();
354
+ var backendConsumeOutcomes = [
355
+ "reset",
356
+ "nothing_to_reset",
357
+ "no_credit",
358
+ "already_redeemed"
359
+ ];
360
+ var backendConsumeSchema = z.object({
361
+ code: z.enum(backendConsumeOutcomes),
362
+ // The app-server intentionally discards this field. OpenGeni also refetches
363
+ // rather than inferring post-redemption state from it.
364
+ windows_reset: nonNegativeInteger.default(0)
365
+ }).passthrough();
366
+ function normalizedResetType(value) {
367
+ return value === "codex_rate_limits" ? "codexRateLimits" : "unknown";
368
+ }
369
+ function normalizedCreditStatus(value) {
370
+ if (value === "available" || value === "redeeming" || value === "redeemed") {
371
+ return value;
372
+ }
373
+ return "unknown";
374
+ }
375
+ function parseCodexRateLimitResetCreditsDetails(payload) {
376
+ const parsed = backendDetailsSchema.safeParse(payload);
377
+ if (!parsed.success) return null;
378
+ return {
379
+ availableCount: parsed.data.available_count,
380
+ credits: parsed.data.credits.map((credit) => ({
381
+ id: credit.id,
382
+ resetType: normalizedResetType(credit.reset_type),
383
+ status: normalizedCreditStatus(credit.status),
384
+ grantedAt: Math.floor(Date.parse(credit.granted_at) / 1e3),
385
+ expiresAt: credit.expires_at == null ? null : Math.floor(Date.parse(credit.expires_at) / 1e3),
386
+ title: credit.title ?? null,
387
+ description: credit.description ?? null
388
+ }))
389
+ };
390
+ }
391
+ function parseCodexRateLimitResetCreditsSummary(payload) {
392
+ const parsed = backendUsageSummarySchema.safeParse(payload);
393
+ const availableCount = parsed.success ? parsed.data.rate_limit_reset_credits?.available_count : void 0;
394
+ return availableCount === void 0 ? null : { availableCount, credits: null };
395
+ }
396
+ function parseCodexRateLimitResetConsumeResponse(payload) {
397
+ const parsed = backendConsumeSchema.safeParse(payload);
398
+ if (!parsed.success) return null;
399
+ const outcomes = {
400
+ reset: "reset",
401
+ nothing_to_reset: "nothingToReset",
402
+ no_credit: "noCredit",
403
+ already_redeemed: "alreadyRedeemed"
404
+ };
405
+ return { outcome: outcomes[parsed.data.code] };
406
+ }
407
+
408
+ // src/usage-normalize.ts
284
409
  var CODEX_FIVE_HOUR_WINDOW_SECONDS = 18e3;
285
410
  var CODEX_WEEKLY_WINDOW_SECONDS = 604800;
286
411
  function buildCodexUsageWindowFromCache(usedPercent, resetAt, limitWindowSeconds) {
@@ -301,34 +426,34 @@ function buildCodexUsageWindowFromCache(usedPercent, resetAt, limitWindowSeconds
301
426
  limitWindowSeconds
302
427
  };
303
428
  }
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()
429
+ var windowSchema = z2.object({
430
+ used_percent: z2.number().optional(),
431
+ reset_after_seconds: z2.number().optional(),
432
+ reset_at: z2.number().optional(),
433
+ limit_window_seconds: z2.number().optional()
309
434
  }).nullish();
310
- var rateLimitSchema = z.object({
311
- allowed: z.boolean().optional(),
312
- limit_reached: z.boolean().optional(),
435
+ var rateLimitSchema = z2.object({
436
+ allowed: z2.boolean().optional(),
437
+ limit_reached: z2.boolean().optional(),
313
438
  primary_window: windowSchema,
314
439
  secondary_window: windowSchema
315
440
  }).nullish();
316
- var additionalLimitSchema = z.object({
317
- limit_name: z.string().optional(),
318
- metered_feature: z.string().optional(),
441
+ var additionalLimitSchema = z2.object({
442
+ limit_name: z2.string().optional(),
443
+ metered_feature: z2.string().optional(),
319
444
  primary_window: windowSchema,
320
445
  secondary_window: windowSchema
321
446
  });
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()
447
+ var creditsSchema = z2.object({
448
+ has_credits: z2.boolean().optional(),
449
+ unlimited: z2.boolean().optional(),
450
+ overage_limit_reached: z2.boolean().optional(),
451
+ balance: z2.union([z2.string(), z2.number()]).optional()
327
452
  }).nullish();
328
- var usageBodySchema = z.object({
329
- plan_type: z.string().nullish(),
453
+ var usageBodySchema = z2.object({
454
+ plan_type: z2.string().nullish(),
330
455
  rate_limit: rateLimitSchema,
331
- additional_limits: z.array(additionalLimitSchema).nullish(),
456
+ additional_limits: z2.array(additionalLimitSchema).nullish(),
332
457
  credits: creditsSchema
333
458
  });
334
459
  function clampPercent(value) {
@@ -391,7 +516,8 @@ function normalizeCodexUsage(httpStatus, rawPayload) {
391
516
  fiveHour: null,
392
517
  weekly: null,
393
518
  limitReached: false,
394
- fetchedAt
519
+ fetchedAt,
520
+ rateLimitResetCredits: parseCodexRateLimitResetCreditsSummary(rawPayload)
395
521
  };
396
522
  if (httpStatus >= 400 && httpStatus !== 404 || body == null) {
397
523
  return { ...base, status: "error" };
@@ -437,6 +563,9 @@ function normalizeCodexUsage(httpStatus, rawPayload) {
437
563
  }
438
564
 
439
565
  // src/api-client.ts
566
+ var CODEX_READ_TIMEOUT_MS = 5e3;
567
+ var RESET_CREDIT_DETAILS_TIMEOUT_MS = 5e3;
568
+ var RESET_CREDIT_CONSUME_TIMEOUT_MS = 1e4;
440
569
  function subscriptionHeaders(a) {
441
570
  return {
442
571
  Authorization: `Bearer ${a.accessToken}`,
@@ -447,35 +576,178 @@ function subscriptionHeaders(a) {
447
576
  ...a.isFedramp ? { "X-OpenAI-Fedramp": "true" } : {}
448
577
  };
449
578
  }
450
- async function fetchCodexModels(a, fetchImpl = fetch) {
451
- const res = await fetchImpl(
452
- `${CODEX_RESPONSES_BASE}/models?client_version=${encodeURIComponent(a.clientVersion)}`,
453
- {
579
+ async function fetchCodexModels(a, fetchImpl = fetch, timeoutMs = CODEX_READ_TIMEOUT_MS) {
580
+ const fetched = await runBoundedCodexOperation(async (signal) => {
581
+ const res = await fetchImpl(
582
+ `${CODEX_RESPONSES_BASE}/models?client_version=${encodeURIComponent(a.clientVersion)}`,
583
+ { method: "GET", headers: subscriptionHeaders(a), signal }
584
+ );
585
+ if (!res.ok) {
586
+ await res.arrayBuffer().catch(() => void 0);
587
+ return { ok: false, status: res.status, slugs: [] };
588
+ }
589
+ const body = await res.json();
590
+ const slugs = (body.models ?? []).map((model) => model.slug).filter((slug) => typeof slug === "string");
591
+ return { ok: true, status: res.status, slugs };
592
+ }, timeoutMs);
593
+ return fetched.ok ? fetched.value : { ok: false, status: 0, slugs: [] };
594
+ }
595
+ async function fetchCodexUsage(a, fetchImpl = fetch, timeoutMs = CODEX_READ_TIMEOUT_MS) {
596
+ const fetched = await runBoundedCodexOperation(async (signal) => {
597
+ const res = await fetchImpl(`${CODEX_WHAM_BASE}/wham/usage`, {
454
598
  method: "GET",
455
- headers: subscriptionHeaders(a)
599
+ headers: subscriptionHeaders(a),
600
+ signal
601
+ });
602
+ const payload = res.ok || res.status === 404 ? await res.json().catch(() => null) : null;
603
+ if (!res.ok && res.status !== 404) await res.arrayBuffer().catch(() => void 0);
604
+ return { status: res.status, payload };
605
+ }, timeoutMs);
606
+ if (!fetched.ok) throw new Error(`Codex usage request ${fetched.reason}`);
607
+ return fetched.value;
608
+ }
609
+ async function fetchCodexRateLimitResetCredits(a, fetchImpl = fetch, timeoutMs = RESET_CREDIT_DETAILS_TIMEOUT_MS) {
610
+ const fetched = await runBoundedCodexOperation(async (signal) => {
611
+ const res = await fetchImpl(`${CODEX_WHAM_BASE}/wham/rate-limit-reset-credits`, {
612
+ method: "GET",
613
+ headers: subscriptionHeaders(a),
614
+ signal
615
+ });
616
+ if (!res.ok) {
617
+ await res.arrayBuffer().catch(() => void 0);
618
+ return { ok: false, status: res.status, reason: "http_error" };
456
619
  }
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 };
620
+ const details = parseCodexRateLimitResetCreditsDetails(await res.json().catch(() => null));
621
+ return details ? { ok: true, status: res.status, details } : { ok: false, status: res.status, reason: "invalid_response" };
622
+ }, timeoutMs);
623
+ return fetched.ok ? fetched.value : { ok: false, status: 0, reason: fetched.reason };
624
+ }
625
+ async function consumeCodexRateLimitResetCredit(a, input, fetchImpl = fetch, timeoutMs = RESET_CREDIT_CONSUME_TIMEOUT_MS) {
626
+ if (input.idempotencyKey.length === 0 || input.creditId === "") {
627
+ return { ok: false, status: 0, reason: "invalid_request" };
628
+ }
629
+ const fetched = await runBoundedCodexOperation(async (signal) => {
630
+ const res = await fetchImpl(`${CODEX_WHAM_BASE}/wham/rate-limit-reset-credits/consume`, {
631
+ method: "POST",
632
+ headers: {
633
+ ...subscriptionHeaders(a),
634
+ "content-type": "application/json"
635
+ },
636
+ body: JSON.stringify({
637
+ redeem_request_id: input.idempotencyKey,
638
+ ...input.creditId ? { credit_id: input.creditId } : {}
639
+ }),
640
+ signal
641
+ });
642
+ if (!res.ok) {
643
+ await res.arrayBuffer().catch(() => void 0);
644
+ return { ok: false, status: res.status, reason: "http_error" };
645
+ }
646
+ const result = parseCodexRateLimitResetConsumeResponse(await res.json().catch(() => null));
647
+ return result ? { ok: true, status: res.status, result } : { ok: false, status: res.status, reason: "invalid_response" };
648
+ }, timeoutMs);
649
+ return fetched.ok ? fetched.value : { ok: false, status: 0, reason: fetched.reason };
472
650
  }
473
651
 
474
652
  // src/request-context.ts
475
653
  import { AsyncLocalStorage } from "async_hooks";
476
654
  var codexRequestStorage = new AsyncLocalStorage();
477
655
 
656
+ // src/response-timeout.ts
657
+ var CODEX_RESPONSE_TIMEOUT_ERROR_TYPE = "opengeni_codex_response_timeout";
658
+ var DEFAULT_CODEX_RESPONSE_TIMEOUT_POLICY = Object.freeze({
659
+ headersTimeoutMs: CODEX_RESPONSE_HEADERS_TIMEOUT_MS,
660
+ streamIdleTimeoutMs: CODEX_RESPONSE_STREAM_IDLE_TIMEOUT_MS,
661
+ wholeRequestTimeoutMs: CODEX_RESPONSE_WHOLE_TIMEOUT_MS,
662
+ noByteRetries: CODEX_RESPONSE_NO_BYTE_RETRIES,
663
+ retryBackoffMs: CODEX_RESPONSE_RETRY_BACKOFF_MS
664
+ });
665
+ function positiveFinite(value, fallback) {
666
+ return value !== void 0 && Number.isFinite(value) && value > 0 ? value : fallback;
667
+ }
668
+ function resolveCodexResponseTimeoutPolicy(override) {
669
+ return {
670
+ headersTimeoutMs: positiveFinite(
671
+ override?.headersTimeoutMs,
672
+ DEFAULT_CODEX_RESPONSE_TIMEOUT_POLICY.headersTimeoutMs
673
+ ),
674
+ streamIdleTimeoutMs: positiveFinite(
675
+ override?.streamIdleTimeoutMs,
676
+ DEFAULT_CODEX_RESPONSE_TIMEOUT_POLICY.streamIdleTimeoutMs
677
+ ),
678
+ wholeRequestTimeoutMs: positiveFinite(
679
+ override?.wholeRequestTimeoutMs,
680
+ DEFAULT_CODEX_RESPONSE_TIMEOUT_POLICY.wholeRequestTimeoutMs
681
+ ),
682
+ // Automatic replay is fail-closed until the provider operation can be
683
+ // durably read/reconciled. Keep the field for policy/event compatibility,
684
+ // but never let a caller opt back into an unproved retry.
685
+ noByteRetries: 0,
686
+ retryBackoffMs: override?.retryBackoffMs !== void 0 && Number.isFinite(override.retryBackoffMs) && override.retryBackoffMs >= 0 ? override.retryBackoffMs : DEFAULT_CODEX_RESPONSE_TIMEOUT_POLICY.retryBackoffMs
687
+ };
688
+ }
689
+ var CodexResponseTimeoutError = class extends Error {
690
+ constructor(timeoutClass, requestId, responseObserved, message = `Codex response ${timeoutClass.replaceAll("_", " ")} timed out`) {
691
+ super(message);
692
+ this.timeoutClass = timeoutClass;
693
+ this.requestId = requestId;
694
+ this.responseObserved = responseObserved;
695
+ this.name = "CodexResponseTimeoutError";
696
+ }
697
+ code = CODEX_RESPONSE_TIMEOUT_ERROR_TYPE;
698
+ type = CODEX_RESPONSE_TIMEOUT_ERROR_TYPE;
699
+ };
700
+ function parseTimeoutClass(value) {
701
+ return value === "connect" || value === "headers" || value === "idle_stream" || value === "whole_request" ? value : null;
702
+ }
703
+ function classifyCodexResponseTimeoutError(error, options = {}) {
704
+ let current = error;
705
+ for (let depth = 0; depth < 8 && current && typeof current === "object"; depth += 1) {
706
+ const value = current;
707
+ const nested = value.error && typeof value.error === "object" ? value.error : void 0;
708
+ 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);
709
+ if (type === CODEX_RESPONSE_TIMEOUT_ERROR_TYPE || value.name === "CodexResponseTimeoutError") {
710
+ const klass = parseTimeoutClass(value.timeoutClass) ?? parseTimeoutClass(nested?.timeout_class) ?? "headers";
711
+ return {
712
+ timeoutClass: klass,
713
+ requestId: (typeof value.requestId === "string" ? value.requestId : void 0) ?? (typeof nested?.request_id === "string" ? nested.request_id : null),
714
+ responseObserved: typeof value.responseObserved === "boolean" ? value.responseObserved : nested?.response_observed === true,
715
+ message: (typeof value.message === "string" ? value.message : void 0) ?? (typeof nested?.message === "string" ? nested.message : "Codex response timed out")
716
+ };
717
+ }
718
+ current = value.cause;
719
+ }
720
+ if (options.allowLegacyRequestTimeout && error && typeof error === "object") {
721
+ const value = error;
722
+ if (value.name === "APIConnectionTimeoutError" || value.message === "Request timed out." && value.name === "Error") {
723
+ return {
724
+ timeoutClass: "headers",
725
+ requestId: null,
726
+ responseObserved: false,
727
+ message: String(value.message ?? "Request timed out.")
728
+ };
729
+ }
730
+ }
731
+ return null;
732
+ }
733
+ function isPreHeadersTimeoutError(error) {
734
+ const structured = classifyCodexResponseTimeoutError(error);
735
+ if (structured && !structured.responseObserved) {
736
+ return structured.timeoutClass;
737
+ }
738
+ if (!error || typeof error !== "object") return null;
739
+ const value = error;
740
+ const code = typeof value.code === "string" ? value.code : "";
741
+ const name = typeof value.name === "string" ? value.name : "";
742
+ const message = typeof value.message === "string" ? value.message : String(error);
743
+ if (/^(?:ETIMEDOUT|UND_ERR_CONNECT_TIMEOUT)$/i.test(code) || /ConnectTimeout/i.test(name)) {
744
+ return "connect";
745
+ }
746
+ return /connect(?:ion)?[^.]*timed?\s*out/i.test(`${name} ${message}`) ? "connect" : null;
747
+ }
748
+
478
749
  // src/fetch.ts
750
+ import { randomUUID } from "crypto";
479
751
  var CODEX_TRANSPORT_ERROR_HEADER = "x-opengeni-codex-transport-error";
480
752
  function headersCarryCodexTransportMarker(headers) {
481
753
  if (!headers || typeof headers !== "object") return false;
@@ -538,6 +810,210 @@ function parseCodexUsageHeaders(headers) {
538
810
  checkedAt: new Date(nowMs)
539
811
  };
540
812
  }
813
+ async function emitRequestEvent(audit, event) {
814
+ await audit.ctx.onModelRequestEvent?.({
815
+ requestId: audit.requestId,
816
+ transportAttempt: audit.transportAttempt,
817
+ ...audit.model ? { model: audit.model } : {},
818
+ durationMs: Math.max(0, Date.now() - audit.attemptStartedAt),
819
+ timeoutPolicy: audit.policy,
820
+ ...event
821
+ });
822
+ }
823
+ function providerRequestId(headers) {
824
+ return headers.get("x-request-id") ?? headers.get("request-id") ?? void 0;
825
+ }
826
+ async function fetchBeforeHeaders(base, input, init, audit) {
827
+ const elapsed = Date.now() - audit.logicalStartedAt;
828
+ const wholeRemainingMs = audit.policy.wholeRequestTimeoutMs - elapsed;
829
+ const timeoutClass = wholeRemainingMs <= audit.policy.headersTimeoutMs ? "whole_request" : "headers";
830
+ const deadlineMs = Math.max(1, Math.min(audit.policy.headersTimeoutMs, wholeRemainingMs));
831
+ if (wholeRemainingMs <= 0) {
832
+ throw new CodexResponseTimeoutError("whole_request", audit.requestId, false);
833
+ }
834
+ const externalSignal = init.signal;
835
+ if (externalSignal?.aborted) throw externalSignal.reason;
836
+ const controller = new AbortController();
837
+ const forwardAbort = () => controller.abort(externalSignal?.reason);
838
+ externalSignal?.addEventListener("abort", forwardAbort, { once: true });
839
+ const basePromise = base(input, { ...init, signal: controller.signal });
840
+ let deadlineError = null;
841
+ let timer;
842
+ const deadline = new Promise((_resolve, reject) => {
843
+ timer = setTimeout(() => {
844
+ deadlineError = new CodexResponseTimeoutError(timeoutClass, audit.requestId, false);
845
+ reject(deadlineError);
846
+ }, deadlineMs);
847
+ });
848
+ try {
849
+ return await Promise.race([basePromise, deadline]);
850
+ } catch (error) {
851
+ if (deadlineError) {
852
+ controller.abort(deadlineError);
853
+ void basePromise.then((late) => late.body?.cancel(deadlineError ?? void 0)).catch(() => void 0);
854
+ throw deadlineError;
855
+ }
856
+ throw error;
857
+ } finally {
858
+ if (timer) clearTimeout(timer);
859
+ externalSignal?.removeEventListener("abort", forwardAbort);
860
+ }
861
+ }
862
+ async function observedResponse(res, audit, externalSignal) {
863
+ const requestId = providerRequestId(res.headers);
864
+ if (!res.body) {
865
+ await emitRequestEvent(audit, {
866
+ phase: res.ok ? "completed" : "failed",
867
+ responseObserved: true,
868
+ status: res.status,
869
+ ...requestId ? { providerRequestId: requestId } : {}
870
+ });
871
+ return res;
872
+ }
873
+ const reader = res.body.getReader();
874
+ let terminal = false;
875
+ let firstByte = false;
876
+ let idleTimer;
877
+ let wholeTimer;
878
+ let armIdle = () => void 0;
879
+ let abortFromOutside;
880
+ const clearTimers = () => {
881
+ if (idleTimer) clearTimeout(idleTimer);
882
+ if (wholeTimer) clearTimeout(wholeTimer);
883
+ if (abortFromOutside) externalSignal?.removeEventListener("abort", abortFromOutside);
884
+ };
885
+ const body = new ReadableStream({
886
+ start(controller) {
887
+ const timeOut = (klass) => {
888
+ if (terminal) return;
889
+ terminal = true;
890
+ clearTimers();
891
+ const error = new CodexResponseTimeoutError(klass, audit.requestId, true);
892
+ void reader.cancel(error).catch(() => void 0);
893
+ void emitRequestEvent(audit, {
894
+ phase: "timed_out",
895
+ responseObserved: true,
896
+ timeoutClass: klass,
897
+ status: res.status,
898
+ ...requestId ? { providerRequestId: requestId } : {}
899
+ }).then(
900
+ () => controller.error(error),
901
+ () => controller.error(error)
902
+ );
903
+ };
904
+ armIdle = () => {
905
+ if (idleTimer) clearTimeout(idleTimer);
906
+ idleTimer = setTimeout(() => timeOut("idle_stream"), audit.policy.streamIdleTimeoutMs);
907
+ };
908
+ armIdle();
909
+ const wholeRemaining = Math.max(
910
+ 1,
911
+ audit.policy.wholeRequestTimeoutMs - (Date.now() - audit.logicalStartedAt)
912
+ );
913
+ wholeTimer = setTimeout(() => timeOut("whole_request"), wholeRemaining);
914
+ abortFromOutside = () => {
915
+ if (terminal) return;
916
+ terminal = true;
917
+ clearTimers();
918
+ const reason = externalSignal?.reason ?? new DOMException("Aborted", "AbortError");
919
+ void reader.cancel(reason).catch(() => void 0);
920
+ void emitRequestEvent(audit, {
921
+ phase: "failed",
922
+ responseObserved: true,
923
+ status: res.status,
924
+ ...requestId ? { providerRequestId: requestId } : {}
925
+ }).then(
926
+ () => controller.error(reason),
927
+ () => controller.error(reason)
928
+ );
929
+ };
930
+ if (externalSignal?.aborted) {
931
+ abortFromOutside();
932
+ } else {
933
+ externalSignal?.addEventListener("abort", abortFromOutside, { once: true });
934
+ }
935
+ },
936
+ async pull(controller) {
937
+ if (terminal) return;
938
+ try {
939
+ const chunk = await reader.read();
940
+ if (terminal) return;
941
+ if (chunk.done) {
942
+ terminal = true;
943
+ clearTimers();
944
+ await emitRequestEvent(audit, {
945
+ phase: res.ok ? "completed" : "failed",
946
+ responseObserved: true,
947
+ status: res.status,
948
+ ...requestId ? { providerRequestId: requestId } : {}
949
+ });
950
+ controller.close();
951
+ return;
952
+ }
953
+ armIdle();
954
+ if (!firstByte) {
955
+ firstByte = true;
956
+ await emitRequestEvent(audit, {
957
+ phase: "first_byte",
958
+ responseObserved: true,
959
+ status: res.status,
960
+ ...requestId ? { providerRequestId: requestId } : {}
961
+ });
962
+ }
963
+ controller.enqueue(chunk.value);
964
+ } catch (error) {
965
+ if (terminal) return;
966
+ terminal = true;
967
+ clearTimers();
968
+ await emitRequestEvent(audit, {
969
+ phase: "failed",
970
+ responseObserved: true,
971
+ status: res.status,
972
+ ...requestId ? { providerRequestId: requestId } : {}
973
+ });
974
+ controller.error(error);
975
+ }
976
+ },
977
+ async cancel(reason) {
978
+ if (!terminal) {
979
+ terminal = true;
980
+ clearTimers();
981
+ await emitRequestEvent(audit, {
982
+ phase: "failed",
983
+ responseObserved: true,
984
+ status: res.status,
985
+ ...requestId ? { providerRequestId: requestId } : {}
986
+ }).catch(() => void 0);
987
+ }
988
+ await reader.cancel(reason).catch(() => void 0);
989
+ }
990
+ });
991
+ const headers = new Headers(res.headers);
992
+ headers.delete("content-length");
993
+ return new Response(body, { status: res.status, statusText: res.statusText, headers });
994
+ }
995
+ function timeoutErrorResponse(info) {
996
+ return new Response(
997
+ JSON.stringify({
998
+ error: {
999
+ type: CODEX_RESPONSE_TIMEOUT_ERROR_TYPE,
1000
+ code: CODEX_RESPONSE_TIMEOUT_ERROR_TYPE,
1001
+ message: info.message,
1002
+ timeout_class: info.timeoutClass,
1003
+ response_observed: info.responseObserved,
1004
+ request_id: info.requestId
1005
+ }
1006
+ }),
1007
+ {
1008
+ status: 504,
1009
+ headers: {
1010
+ "content-type": "application/json",
1011
+ "x-should-retry": "false",
1012
+ [CODEX_TRANSPORT_ERROR_HEADER]: "1"
1013
+ }
1014
+ }
1015
+ );
1016
+ }
541
1017
  function codexSubscriptionFetch(base = globalThis.fetch) {
542
1018
  return async (input, init) => {
543
1019
  const ctx = codexRequestStorage.getStore();
@@ -546,7 +1022,11 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
546
1022
  }
547
1023
  const rawUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
548
1024
  const rewritten = rawUrl.replace(/(?<!\/codex)\/responses(\b|$)/, "/codex/responses$1");
549
- const attempt = async (auth) => {
1025
+ const policy = resolveCodexResponseTimeoutPolicy(ctx.responseTimeoutPolicy);
1026
+ const requestId = ctx.nextRequestId?.() ?? randomUUID();
1027
+ const logicalStartedAt = Date.now();
1028
+ let transportAttempt = 0;
1029
+ const attempt = async (auth, authenticationAttempt) => {
550
1030
  const headers = new Headers(init?.headers);
551
1031
  headers.set("Authorization", `Bearer ${auth.accessToken}`);
552
1032
  if (auth.chatgptAccountId) {
@@ -566,41 +1046,114 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
566
1046
  headers.delete("OpenAI-Beta");
567
1047
  headers.delete("x-api-key");
568
1048
  let callerWantsStream = true;
1049
+ let model;
569
1050
  const nextInit = { ...init, headers };
570
1051
  if (typeof init?.body === "string") {
571
1052
  try {
572
1053
  const parsed = JSON.parse(init.body);
573
1054
  callerWantsStream = parsed.stream === true;
574
- nextInit.body = JSON.stringify(normalizeCodexRequestBody(parsed, ctx.resolveModel));
1055
+ const normalized = normalizeCodexRequestBody(parsed, ctx.resolveModel);
1056
+ model = typeof normalized.model === "string" ? normalized.model : void 0;
1057
+ nextInit.body = JSON.stringify(normalized);
575
1058
  } catch {
576
1059
  }
577
1060
  }
1061
+ headers.set(
1062
+ "Idempotency-Key",
1063
+ authenticationAttempt === 0 ? requestId : `${requestId}:auth-${authenticationAttempt}`
1064
+ );
578
1065
  if (process.env.CODEX_DEBUG) {
579
- const keys = typeof nextInit.body === "string" ? Object.keys(JSON.parse(nextInit.body)) : [];
1066
+ let keys = [];
1067
+ if (typeof nextInit.body === "string") {
1068
+ try {
1069
+ keys = Object.keys(JSON.parse(nextInit.body));
1070
+ } catch {
1071
+ }
1072
+ }
580
1073
  console.error(
581
1074
  `[codex-debug] POST ${rewritten} stream=${callerWantsStream} bodyKeys=[${keys.join(",")}]`
582
1075
  );
583
1076
  }
584
- const res2 = await base(rewritten, nextInit);
585
- const usage = parseCodexUsageHeaders(res2.headers);
1077
+ let res;
1078
+ transportAttempt += 1;
1079
+ const audit = {
1080
+ ctx,
1081
+ requestId,
1082
+ transportAttempt,
1083
+ ...model ? { model } : {},
1084
+ logicalStartedAt,
1085
+ attemptStartedAt: Date.now(),
1086
+ policy
1087
+ };
1088
+ await emitRequestEvent(audit, {
1089
+ phase: "started",
1090
+ responseObserved: false
1091
+ });
1092
+ try {
1093
+ res = await fetchBeforeHeaders(base, rewritten, nextInit, audit);
1094
+ const upstreamRequestId = providerRequestId(res.headers);
1095
+ await emitRequestEvent(audit, {
1096
+ phase: "headers",
1097
+ responseObserved: true,
1098
+ status: res.status,
1099
+ ...upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}
1100
+ });
1101
+ const observed = await observedResponse(res, audit, nextInit.signal);
1102
+ res = observed;
1103
+ } catch (error) {
1104
+ if (nextInit.signal?.aborted) {
1105
+ await emitRequestEvent(audit, {
1106
+ phase: "failed",
1107
+ responseObserved: false
1108
+ }).catch(() => void 0);
1109
+ throw error;
1110
+ }
1111
+ const klass = isPreHeadersTimeoutError(error);
1112
+ if (!klass) {
1113
+ await emitRequestEvent(audit, {
1114
+ phase: "failed",
1115
+ responseObserved: false
1116
+ });
1117
+ throw error;
1118
+ }
1119
+ await emitRequestEvent(audit, {
1120
+ phase: "timed_out",
1121
+ responseObserved: false,
1122
+ timeoutClass: klass,
1123
+ willRetry: false
1124
+ }).catch(() => void 0);
1125
+ throw new CodexResponseTimeoutError(klass, requestId, false);
1126
+ }
1127
+ const usage = parseCodexUsageHeaders(res.headers);
586
1128
  if (usage) {
587
1129
  ctx.onUsageHeaders?.(usage);
588
1130
  }
589
- if (process.env.CODEX_DEBUG && !res2.ok) {
1131
+ if (process.env.CODEX_DEBUG && !res.ok) {
590
1132
  console.error(
591
- `[codex-debug] <- ${res2.status} requestId=${res2.headers.get("x-request-id") ?? "unknown"}`
1133
+ `[codex-debug] <- ${res.status} requestId=${res.headers.get("x-request-id") ?? "unknown"}`
592
1134
  );
593
1135
  }
594
- if (!res2.ok) {
595
- return await bufferCodexErrorResponse(res2);
1136
+ if (!res.ok) {
1137
+ return await bufferCodexErrorResponse(res);
596
1138
  }
597
- return callerWantsStream ? repairCodexStream(res2) : await sseToJsonResponse(res2);
1139
+ return callerWantsStream ? repairCodexStream(res) : await sseToJsonResponse(res);
598
1140
  };
599
- let res = await attempt(await ctx.getToken());
600
- if (res.status === 401) {
601
- res = await attempt(await ctx.refresh());
1141
+ try {
1142
+ let res = await attempt(await ctx.getToken(), 0);
1143
+ if (res.status === 401) {
1144
+ res = await attempt(await ctx.refresh(), 1);
1145
+ }
1146
+ return res;
1147
+ } catch (error) {
1148
+ const timeout = classifyCodexResponseTimeoutError(error);
1149
+ if (!timeout) throw error;
1150
+ return timeoutErrorResponse({
1151
+ timeoutClass: timeout.timeoutClass,
1152
+ requestId: timeout.requestId ?? requestId,
1153
+ responseObserved: timeout.responseObserved,
1154
+ message: timeout.message
1155
+ });
602
1156
  }
603
- return res;
604
1157
  };
605
1158
  }
606
1159
  var CODEX_USAGE_LIMIT_ERROR_TYPE = "usage_limit_reached";
@@ -1648,9 +2201,17 @@ export {
1648
2201
  CODEX_ORIGINATOR,
1649
2202
  CODEX_PROVIDER_BASE_URL,
1650
2203
  CODEX_PROVIDER_ID,
2204
+ CODEX_RATE_LIMIT_RESET_OUTCOMES,
1651
2205
  CODEX_REFRESH_FALLBACK_MS,
1652
2206
  CODEX_REFRESH_WINDOW_MS,
1653
2207
  CODEX_RESPONSES_BASE,
2208
+ CODEX_RESPONSE_HEADERS_TIMEOUT_MS,
2209
+ CODEX_RESPONSE_NO_BYTE_RETRIES,
2210
+ CODEX_RESPONSE_RETRY_BACKOFF_MS,
2211
+ CODEX_RESPONSE_SDK_OUTER_TIMEOUT_MS,
2212
+ CODEX_RESPONSE_STREAM_IDLE_TIMEOUT_MS,
2213
+ CODEX_RESPONSE_TIMEOUT_ERROR_TYPE,
2214
+ CODEX_RESPONSE_WHOLE_TIMEOUT_MS,
1654
2215
  CODEX_TOKEN_URL,
1655
2216
  CODEX_TOOL_OUTPUT_SERIALIZATION_ALLOWANCE,
1656
2217
  CODEX_TRANSPORT_ERROR_HEADER,
@@ -1660,7 +2221,9 @@ export {
1660
2221
  CodexDeviceError,
1661
2222
  CodexRefreshTransient,
1662
2223
  CodexReloginRequired,
2224
+ CodexResponseTimeoutError,
1663
2225
  CodexStreamingTerminalError,
2226
+ DEFAULT_CODEX_RESPONSE_TIMEOUT_POLICY,
1664
2227
  DEFAULT_MODEL_TOOL_OUTPUT_TRUNCATION_TOKENS,
1665
2228
  MODEL_TOOL_OUTPUT_OPAQUE_PAYLOAD_MAX_BYTES,
1666
2229
  MODEL_TOOL_OUTPUT_OVERSIZED_IMAGE_CARD_DATA_URL,
@@ -1671,24 +2234,32 @@ export {
1671
2234
  boundModelToolOutputItems,
1672
2235
  buildCodexUsageWindowFromCache,
1673
2236
  buildModelResolver,
2237
+ classifyCodexResponseTimeoutError,
1674
2238
  classifyCodexUsageLimitError,
1675
2239
  codexAppsSanitizingFetch,
1676
2240
  codexRequestStorage,
1677
2241
  codexSubscriptionFetch,
2242
+ consumeCodexRateLimitResetCredit,
1678
2243
  decodeJwtPayload,
1679
2244
  exchangeDeviceCode,
1680
2245
  fetchCodexModels,
2246
+ fetchCodexRateLimitResetCredits,
1681
2247
  fetchCodexUsage,
1682
2248
  isCodexBilledModel,
1683
2249
  isCodexTransportError,
2250
+ isPreHeadersTimeoutError,
1684
2251
  modelToolOutputSerializationBudgetTokens,
1685
2252
  normalizeCodexRequestBody,
1686
2253
  normalizeCodexUsage,
2254
+ parseCodexRateLimitResetConsumeResponse,
2255
+ parseCodexRateLimitResetCreditsDetails,
2256
+ parseCodexRateLimitResetCreditsSummary,
1687
2257
  parseCodexUsageHeaders,
1688
2258
  parseIdToken,
1689
2259
  pollDeviceCode,
1690
2260
  refreshCodexToken,
1691
2261
  remapToolCallRequestBody,
2262
+ resolveCodexResponseTimeoutPolicy,
1692
2263
  sanitizeMcpJsonBody,
1693
2264
  sanitizeMcpSseBody,
1694
2265
  startDeviceCode,