@alvin0/ai-agent-sdk-provider-codex 0.1.0

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 ADDED
@@ -0,0 +1,996 @@
1
+ import { AgentSdkError, MISSING_CREDENTIAL_CODE, ReasoningEffortId, waitForSettlement } from "@alvin0/ai-agent-sdk-core";
2
+ import { AgentSdkError as AgentSdkError$1, CREDENTIAL_CAPABILITY_API_VERSION, defineCredentialStore, defineModelProviderPlugin } from "@alvin0/ai-agent-sdk-core/provider";
3
+ import { createHttpProvider, createRuntimeHttpProvider, observeCredentialOperation } from "@alvin0/ai-agent-sdk-provider-http";
4
+ import { openAiResponsesProtocol, openAiResponsesProtocol as openAiResponsesProtocol$1 } from "@alvin0/ai-agent-sdk-protocol-responses";
5
+
6
+ //#region src/auth.ts
7
+ /**
8
+ * Universal Codex credential contracts and JWT helpers.
9
+ *
10
+ * Storage is injected. Filesystem/path/environment ownership belongs to the
11
+ * Node auth package, never this Universal provider.
12
+ */
13
+ /** An in-memory {@link CodexAuthStore}, for tests. */
14
+ function memoryCodexAuthStore(initial) {
15
+ let current = initial;
16
+ return {
17
+ location: "<memory>",
18
+ read: () => Promise.resolve(current),
19
+ write: (file) => {
20
+ current = file;
21
+ return Promise.resolve();
22
+ }
23
+ };
24
+ }
25
+ /** In-memory compare-and-swap store for deterministic runtime/tests. */
26
+ function memoryCodexCredentialStore(initial) {
27
+ let current = initial === void 0 ? void 0 : structuredClone(initial);
28
+ let revision = 0;
29
+ return defineCredentialStore({
30
+ id: "codex-memory-credentials",
31
+ label: "<memory>",
32
+ async read({ signal }) {
33
+ signal.throwIfAborted();
34
+ return current === void 0 ? void 0 : {
35
+ value: structuredClone(current),
36
+ revision: String(revision)
37
+ };
38
+ },
39
+ async commit(input, { signal }) {
40
+ signal.throwIfAborted();
41
+ const expected = current === void 0 ? null : String(revision);
42
+ if (input.expectedRevision !== expected) throw new AgentSdkError("Codex credential revision changed before commit", "CODEX_CREDENTIAL_REVISION_CONFLICT");
43
+ current = structuredClone(input.value);
44
+ revision++;
45
+ return { revision: String(revision) };
46
+ }
47
+ });
48
+ }
49
+ /** The custom claim namespace OpenAI puts its ChatGPT account fields under. */
50
+ const AUTH_CLAIM_NAMESPACE = "https://api.openai.com/auth";
51
+ /** Decode a base64url segment without requiring Node's Buffer. */
52
+ function decodeBase64Url(segment) {
53
+ const padded = segment.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - segment.length % 4) % 4);
54
+ const binary = atob(padded);
55
+ const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
56
+ return new TextDecoder().decode(bytes);
57
+ }
58
+ /**
59
+ * Read the claims this SDK cares about out of a JWT.
60
+ *
61
+ * The signature is NOT verified, and does not need to be: this token is being
62
+ * read to decide which account id to send and whether to refresh, not to grant
63
+ * anything. The issuer verifies it.
64
+ * @param jwt - a compact-serialization JWT.
65
+ * @returns the claims, or `undefined` when the token is unreadable.
66
+ */
67
+ function readJwtClaims(jwt) {
68
+ const parts = jwt.split(".");
69
+ const payload = parts.length === 3 ? parts[1] : void 0;
70
+ if (payload === void 0 || payload.length === 0) return void 0;
71
+ let parsed;
72
+ try {
73
+ parsed = JSON.parse(decodeBase64Url(payload));
74
+ } catch {
75
+ return;
76
+ }
77
+ const auth = parsed[AUTH_CLAIM_NAMESPACE];
78
+ const authClaims = typeof auth === "object" && auth !== null ? auth : {};
79
+ const exp = parsed.exp;
80
+ const email = parsed.email;
81
+ const accountId = authClaims.chatgpt_account_id;
82
+ const planType = authClaims.chatgpt_plan_type;
83
+ return {
84
+ ...typeof exp === "number" ? { exp } : {},
85
+ ...typeof email === "string" ? { email } : {},
86
+ ...typeof accountId === "string" ? { accountId } : {},
87
+ ...typeof planType === "string" ? { planType } : {},
88
+ isFedramp: authClaims.chatgpt_account_is_fedramp === true
89
+ };
90
+ }
91
+ /**
92
+ * Resolve the account id to send as `ChatGPT-Account-ID`.
93
+ *
94
+ * Prefers the stored value and falls back to the `id_token` claim, because the
95
+ * stored field is legitimately null for personal accounts.
96
+ * @param tokens - the stored tokens.
97
+ * @returns the account id, or `undefined` when neither source has one.
98
+ */
99
+ function resolveAccountId(tokens) {
100
+ const stored = tokens.account_id;
101
+ if (typeof stored === "string" && stored.length > 0) return stored;
102
+ return readJwtClaims(tokens.id_token)?.accountId;
103
+ }
104
+ /** Whether this account must be routed through the FedRAMP edge. */
105
+ function isFedrampAccount(tokens) {
106
+ return readJwtClaims(tokens.id_token)?.isFedramp === true;
107
+ }
108
+ /** Refresh this long before the access token actually expires. */
109
+ const ACCESS_TOKEN_REFRESH_WINDOW_MS = 3e5;
110
+ /** Fallback staleness bound, used only when `exp` cannot be read. */
111
+ const LAST_REFRESH_MAX_AGE_MS = 6912e5;
112
+ /**
113
+ * Whether the access token should be refreshed before the next request.
114
+ *
115
+ * Primary signal is the token's own `exp`, with a five-minute margin so a request
116
+ * cannot expire in flight. The `last_refresh` age is only a fallback for a token
117
+ * whose `exp` is unreadable — matching how the Codex CLI decides.
118
+ * @param file - the credential file.
119
+ * @param now - current time in epoch milliseconds; injectable for tests.
120
+ * @returns true when a refresh is due.
121
+ */
122
+ function shouldRefresh(file, now = Date.now()) {
123
+ const tokens = file.tokens;
124
+ if (tokens === void 0 || tokens === null) return false;
125
+ const exp = readJwtClaims(tokens.access_token)?.exp;
126
+ if (exp !== void 0) return exp * 1e3 <= now + ACCESS_TOKEN_REFRESH_WINDOW_MS;
127
+ const lastRefresh = file.last_refresh;
128
+ if (lastRefresh === void 0 || lastRefresh === null) return false;
129
+ const at = Date.parse(lastRefresh);
130
+ return Number.isFinite(at) && at < now - 6912e5;
131
+ }
132
+ /**
133
+ * Require usable ChatGPT tokens, with a message that says how to get them.
134
+ * @param file - the credential file, or `undefined` when absent.
135
+ * @param location - the path checked, named in the diagnostic.
136
+ * @returns the tokens.
137
+ */
138
+ function requireTokens(file, location) {
139
+ const tokens = file?.tokens;
140
+ if (tokens === void 0 || tokens === null || typeof tokens.access_token !== "string" || tokens.access_token.length === 0) throw new AgentSdkError(`no Codex credentials at ${location}; run \`npm run provider:codex:login-device\` to sign in`, MISSING_CREDENTIAL_CODE);
141
+ return tokens;
142
+ }
143
+
144
+ //#endregion
145
+ //#region src/common/store-capture.ts
146
+ /** Capture store identity and methods without invoking accessors or doing storage I/O. */
147
+ function captureCodexStore(value) {
148
+ try {
149
+ if (value === null || typeof value !== "object") throw new TypeError("store must be an object");
150
+ const marker = dataValue(value, "kind", false);
151
+ if (marker === void 0) return captureLegacy(value);
152
+ if (marker !== "credential-store" || dataValue(value, "apiVersion") !== CREDENTIAL_CAPABILITY_API_VERSION) throw new TypeError("unsupported credential-store marker");
153
+ const id = boundedString(dataValue(value, "id"), 128, "credential store id");
154
+ const label = boundedString(dataValue(value, "label"), 256, "credential store label");
155
+ const read = capturedMethod(value, "read");
156
+ const commit = capturedMethod(value, "commit");
157
+ return Object.freeze({
158
+ kind: "versioned",
159
+ label,
160
+ store: Object.freeze({
161
+ kind: "credential-store",
162
+ apiVersion: CREDENTIAL_CAPABILITY_API_VERSION,
163
+ id,
164
+ label,
165
+ read,
166
+ commit
167
+ })
168
+ });
169
+ } catch (error) {
170
+ throw new AgentSdkError$1("Codex authStore credential store is invalid", "CREDENTIAL_STORE_INVALID", { cause: error });
171
+ }
172
+ }
173
+ function captureLegacy(source) {
174
+ const location = boundedString(dataValue(source, "location"), 1024, "Codex auth store location");
175
+ const read = capturedMethod(source, "read");
176
+ const write = capturedMethod(source, "write");
177
+ return Object.freeze({
178
+ kind: "legacy",
179
+ label: location,
180
+ store: Object.freeze({
181
+ location,
182
+ read,
183
+ write
184
+ })
185
+ });
186
+ }
187
+ function capturedMethod(source, key) {
188
+ const method = dataValue(source, key);
189
+ if (typeof method !== "function") throw new TypeError(`${String(key)} must be a function`);
190
+ return (...args) => Reflect.apply(method, source, args);
191
+ }
192
+ function dataValue(source, key, required = true) {
193
+ let owner = source;
194
+ while (owner !== null) {
195
+ const descriptor = Object.getOwnPropertyDescriptor(owner, key);
196
+ if (descriptor !== void 0) {
197
+ if (!("value" in descriptor)) throw new TypeError(`${String(key)} must not be an accessor`);
198
+ return descriptor.value;
199
+ }
200
+ owner = Object.getPrototypeOf(owner);
201
+ }
202
+ if (!required) return void 0;
203
+ throw new TypeError(`missing ${String(key)}`);
204
+ }
205
+ function boundedString(value, maxLength, label) {
206
+ if (typeof value !== "string" || value.length === 0 || value.length > maxLength) throw new TypeError(`${label} must be a bounded non-empty string`);
207
+ return value;
208
+ }
209
+
210
+ //#endregion
211
+ //#region src/common/no-follow.ts
212
+ /** Reject every redirect shape exposed by Web fetch before any second request. */
213
+ async function rejectCodexRedirect(response, requestedUrl, operation, teardownTimeoutMs) {
214
+ const redirectStatus = response.status >= 300 && response.status < 400;
215
+ const responseUrlChanged = response.url.length > 0 && response.url !== requestedUrl;
216
+ if (response.type !== "opaqueredirect" && response.redirected !== true && !redirectStatus && !responseUrlChanged) return;
217
+ if (response.body !== null) await waitForSettlement(response.body.cancel().catch(() => void 0), teardownTimeoutMs);
218
+ throw new TypeError(`Codex ${operation} rejected a redirect before following it`);
219
+ }
220
+
221
+ //#endregion
222
+ //#region src/oauth.ts
223
+ /**
224
+ * The OAuth flows behind the Codex credential file: device-code sign-in and
225
+ * refresh-token rotation.
226
+ *
227
+ * Device code rather than a browser redirect because this SDK has no business
228
+ * binding a localhost port: the flow works over SSH, in containers, and in CI,
229
+ * and it needs no callback server.
230
+ *
231
+ * One surprise worth flagging: in this flow the SERVER generates the PKCE pair
232
+ * and returns both the verifier and the challenge alongside the authorization
233
+ * code. That inverts normal PKCE, where the client generates the verifier and
234
+ * never transmits it. It is what the endpoint does, so it is what this
235
+ * implements — but it means the device-code leg is only as safe as the TLS
236
+ * channel, and it is why the user-facing prompt carries a phishing warning.
237
+ *
238
+ * @module ai-agent-sdk/providers/codex/oauth
239
+ */
240
+ const NEVER_ABORTED_SIGNAL = new AbortController().signal;
241
+ const NULL_LOGGER$1 = Object.freeze({
242
+ child: () => NULL_LOGGER$1,
243
+ trace: () => void 0,
244
+ debug: () => void 0,
245
+ info: () => void 0,
246
+ warn: () => void 0,
247
+ error: () => void 0,
248
+ fatal: () => void 0
249
+ });
250
+ /** OpenAI's auth issuer. */
251
+ const DEFAULT_CODEX_ISSUER = "https://auth.openai.com";
252
+ /** The public OAuth client id the Codex CLI uses; not a secret. */
253
+ const CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
254
+ /** The device code expires server-side after this long. */
255
+ const DEVICE_CODE_MAX_WAIT_MS = 9e5;
256
+ /** Used when the server does not state a polling interval. */
257
+ const DEFAULT_POLL_INTERVAL_SECONDS = 5;
258
+ function issuerOf(options) {
259
+ const url = new URL(options.issuer ?? "https://auth.openai.com");
260
+ if (url.username.length > 0 || url.password.length > 0) throw new TypeError("Codex OAuth issuer must not contain credentials");
261
+ if (url.protocol !== "https:" && !(options.allowInsecureIssuer === true && url.protocol === "http:")) throw new TypeError("Codex OAuth issuer must use https");
262
+ return url.href.replace(/\/+$/, "");
263
+ }
264
+ function clientIdOf(options) {
265
+ return options.clientId ?? "app_EMoamEEZ73f0CkXaXp7hrann";
266
+ }
267
+ async function oauthFetch(options, input, init) {
268
+ const issuer = new URL(issuerOf(options));
269
+ const url = new URL(input);
270
+ if (url.origin !== issuer.origin) throw new TypeError(`Codex OAuth endpoint origin '${url.origin}' is not allowed`);
271
+ const timeoutMs = positiveSafeInteger$1(options.requestTimeoutMs ?? 3e4, "requestTimeoutMs");
272
+ const timeout = AbortSignal.timeout(timeoutMs);
273
+ const signal = options.signal === void 0 ? timeout : AbortSignal.any([options.signal, timeout]);
274
+ const fetchImpl = options.fetch ?? globalThis.fetch;
275
+ if (typeof fetchImpl !== "function") throw new TypeError("Codex OAuth requires fetch");
276
+ const response = await raceAbort$1(Promise.resolve(fetchImpl(url, {
277
+ ...init,
278
+ signal,
279
+ redirect: "manual"
280
+ })), signal);
281
+ await rejectCodexRedirect(response, url.href, "OAuth", 3e4);
282
+ return response;
283
+ }
284
+ async function readResponseText(response, options) {
285
+ const maxBytes = positiveSafeInteger$1(options.maxResponseBytes ?? 1048576, "maxResponseBytes");
286
+ const maxChunks = positiveSafeInteger$1(options.maxResponseChunks ?? 1e4, "maxResponseChunks");
287
+ const declared = Number(response.headers.get("content-length"));
288
+ if (Number.isFinite(declared) && declared > maxBytes) {
289
+ if (response.body !== null) await waitForSettlement(response.body.cancel().catch(() => void 0), 3e4);
290
+ throw new RangeError(`Codex OAuth response exceeds the ${maxBytes}-byte limit`);
291
+ }
292
+ if (response.body === null) return "";
293
+ const timeout = AbortSignal.timeout(positiveSafeInteger$1(options.requestTimeoutMs ?? 3e4, "requestTimeoutMs"));
294
+ const signal = options.signal === void 0 ? timeout : AbortSignal.any([options.signal, timeout]);
295
+ const reader = response.body.getReader();
296
+ const decoder = new TextDecoder();
297
+ let bytes = 0;
298
+ let chunks = 0;
299
+ let result = "";
300
+ try {
301
+ while (true) {
302
+ const next = await raceAbort$1(reader.read(), signal);
303
+ if (next.done) return result + decoder.decode();
304
+ if (next.value === void 0) continue;
305
+ chunks++;
306
+ bytes += next.value.byteLength;
307
+ if (chunks > maxChunks || bytes > maxBytes) {
308
+ await waitForSettlement(reader.cancel().catch(() => void 0), 3e4);
309
+ throw new RangeError(`Codex OAuth response exceeds its configured resource limit`);
310
+ }
311
+ result += decoder.decode(next.value, { stream: true });
312
+ }
313
+ } finally {
314
+ reader.releaseLock();
315
+ }
316
+ }
317
+ function raceAbort$1(pending, signal) {
318
+ if (signal.aborted) return Promise.reject(signal.reason ?? /* @__PURE__ */ new Error("Codex OAuth operation aborted"));
319
+ return new Promise((resolve, reject) => {
320
+ const abort = () => {
321
+ cleanup();
322
+ reject(signal.reason ?? /* @__PURE__ */ new Error("Codex OAuth operation aborted"));
323
+ };
324
+ const cleanup = () => signal.removeEventListener("abort", abort);
325
+ signal.addEventListener("abort", abort, { once: true });
326
+ pending.then((value) => {
327
+ cleanup();
328
+ resolve(value);
329
+ }, (error) => {
330
+ cleanup();
331
+ reject(error);
332
+ });
333
+ });
334
+ }
335
+ function positiveSafeInteger$1(value, field) {
336
+ if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`Codex OAuth ${field} must be a positive safe integer`);
337
+ return value;
338
+ }
339
+ /** Read a JSON body, failing with the status when it is not JSON. */
340
+ async function readJson(response, what, options) {
341
+ const raw = await readResponseText(response, options);
342
+ try {
343
+ return JSON.parse(raw);
344
+ } catch (error) {
345
+ throw new AgentSdkError(`${what} returned a non-JSON response (HTTP ${response.status})`, "CODEX_AUTH_MALFORMED", { cause: error });
346
+ }
347
+ }
348
+ function requireString(source, key, what) {
349
+ const value = source[key];
350
+ if (typeof value !== "string" || value.length === 0) throw new AgentSdkError(`${what} omitted "${key}"`, "CODEX_AUTH_MALFORMED");
351
+ return value;
352
+ }
353
+ /**
354
+ * Start a device authorization.
355
+ * @param options - issuer, client id, cancellation.
356
+ * @returns the code and URL to show the user.
357
+ */
358
+ async function requestDeviceCode(options = {}) {
359
+ const issuer = issuerOf(options);
360
+ const response = await oauthFetch(options, `${issuer}/api/accounts/deviceauth/usercode`, {
361
+ method: "POST",
362
+ headers: { "content-type": "application/json" },
363
+ body: JSON.stringify({ client_id: clientIdOf(options) })
364
+ });
365
+ if (response.status === 404) throw new AgentSdkError(`device-code login is not available at ${issuer}; check the issuer URL`, "CODEX_AUTH_UNAVAILABLE");
366
+ if (!response.ok) throw new AgentSdkError(`device-code request failed (HTTP ${response.status})`, "CODEX_AUTH_FAILED", { cause: new Error(await readResponseText(response, options)) });
367
+ const body = await readJson(response, "the device-code endpoint", options);
368
+ const rawInterval = body.interval;
369
+ const parsed = typeof rawInterval === "string" ? Number.parseInt(rawInterval.trim(), 10) : typeof rawInterval === "number" ? rawInterval : NaN;
370
+ return {
371
+ verificationUrl: `${issuer}/codex/device`,
372
+ userCode: requireString(body, "user_code", "the device-code endpoint"),
373
+ deviceAuthId: requireString(body, "device_auth_id", "the device-code endpoint"),
374
+ intervalSeconds: Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_POLL_INTERVAL_SECONDS
375
+ };
376
+ }
377
+ function sleep(ms, signal) {
378
+ if (signal?.aborted === true) return Promise.reject(new AgentSdkError("device-code login cancelled", "ABORTED"));
379
+ return new Promise((resolve, reject) => {
380
+ const onAbort = () => {
381
+ clearTimeout(timer);
382
+ reject(new AgentSdkError("device-code login cancelled", "ABORTED"));
383
+ };
384
+ const timer = setTimeout(() => {
385
+ signal?.removeEventListener("abort", onAbort);
386
+ resolve();
387
+ }, ms);
388
+ signal?.addEventListener("abort", onAbort, { once: true });
389
+ });
390
+ }
391
+ /**
392
+ * Poll until the user approves the code, or the authorization expires.
393
+ *
394
+ * `403` and `404` both mean "not approved yet" here, which is unusual — most
395
+ * device flows use a `authorization_pending` error code — so anything else is
396
+ * treated as a real failure rather than retried.
397
+ * @param code - the pending authorization.
398
+ * @param options - issuer, client id, cancellation.
399
+ * @param progress - poll notifications.
400
+ * @returns the authorization code and its server-issued PKCE verifier.
401
+ */
402
+ async function pollForAuthorization(code, options, progress) {
403
+ const url = `${issuerOf(options)}/api/accounts/deviceauth/token`;
404
+ const startedAt = Date.now();
405
+ while (true) {
406
+ const elapsed = Date.now() - startedAt;
407
+ try {
408
+ progress.onPoll?.(elapsed);
409
+ } catch {}
410
+ const response = await oauthFetch(options, url, {
411
+ method: "POST",
412
+ headers: { "content-type": "application/json" },
413
+ body: JSON.stringify({
414
+ device_auth_id: code.deviceAuthId,
415
+ user_code: code.userCode
416
+ })
417
+ });
418
+ if (response.ok) {
419
+ const body = await readJson(response, "the device-token endpoint", options);
420
+ return {
421
+ authorizationCode: requireString(body, "authorization_code", "the device-token endpoint"),
422
+ codeVerifier: requireString(body, "code_verifier", "the device-token endpoint")
423
+ };
424
+ }
425
+ if (response.status === 403 || response.status === 404) {
426
+ const remaining = DEVICE_CODE_MAX_WAIT_MS - (Date.now() - startedAt);
427
+ if (remaining <= 0) throw new AgentSdkError("device-code login timed out after 15 minutes without approval", "CODEX_AUTH_TIMEOUT");
428
+ await sleep(Math.min(code.intervalSeconds * 1e3, remaining), options.signal);
429
+ continue;
430
+ }
431
+ throw new AgentSdkError(`device-code polling failed (HTTP ${response.status})`, "CODEX_AUTH_FAILED", { cause: new Error(await readResponseText(response, options)) });
432
+ }
433
+ }
434
+ /**
435
+ * Exchange an approved authorization code for tokens.
436
+ *
437
+ * Form-encoded, not JSON — the token endpoint differs from the device-auth
438
+ * endpoints in this respect, and sending JSON here fails.
439
+ */
440
+ async function exchangeCodeForTokens(grant, options) {
441
+ const issuer = issuerOf(options);
442
+ const body = new URLSearchParams({
443
+ grant_type: "authorization_code",
444
+ code: grant.authorizationCode,
445
+ redirect_uri: `${issuer}/deviceauth/callback`,
446
+ client_id: clientIdOf(options),
447
+ code_verifier: grant.codeVerifier
448
+ });
449
+ const response = await oauthFetch(options, `${issuer}/oauth/token`, {
450
+ method: "POST",
451
+ headers: { "content-type": "application/x-www-form-urlencoded" },
452
+ body: body.toString()
453
+ });
454
+ if (!response.ok) throw new AgentSdkError(`token exchange failed (HTTP ${response.status})`, "CODEX_AUTH_FAILED", { cause: new Error(await readResponseText(response, options)) });
455
+ const parsed = await readJson(response, "the token endpoint", options);
456
+ return {
457
+ id_token: requireString(parsed, "id_token", "the token endpoint"),
458
+ access_token: requireString(parsed, "access_token", "the token endpoint"),
459
+ refresh_token: requireString(parsed, "refresh_token", "the token endpoint")
460
+ };
461
+ }
462
+ /** Build the credential file for a freshly issued token set. */
463
+ function authFileFor(tokens) {
464
+ const accountId = resolveAccountId(tokens);
465
+ return {
466
+ auth_mode: "chatgpt",
467
+ OPENAI_API_KEY: null,
468
+ tokens: {
469
+ ...tokens,
470
+ account_id: accountId ?? null
471
+ },
472
+ last_refresh: (/* @__PURE__ */ new Date()).toISOString()
473
+ };
474
+ }
475
+ async function runDeviceCodeLogin(store, options = {}, progress = {}) {
476
+ const captured = captureCodexStore(store);
477
+ const operation = credentialOperation(options.signal);
478
+ const initial = await readStore(captured, operation);
479
+ const code = await requestDeviceCode(options);
480
+ try {
481
+ progress.onPrompt?.(code);
482
+ } catch {}
483
+ const tokens = await exchangeCodeForTokens(await pollForAuthorization(code, options, progress), options);
484
+ const file = authFileFor(tokens);
485
+ await commitStore(captured, file, initial.revision, operation);
486
+ const claims = readJwtClaims(tokens.id_token);
487
+ return {
488
+ location: storeLabel(captured),
489
+ email: claims?.email,
490
+ accountId: file.tokens?.account_id ?? void 0,
491
+ planType: claims?.planType
492
+ };
493
+ }
494
+ /** A refresh that did not succeed. */
495
+ var CodexRefreshError = class extends AgentSdkError {
496
+ kind;
497
+ constructor(message, kind, options) {
498
+ super(message, kind === "permanent" ? "CODEX_REAUTH_REQUIRED" : "CODEX_REFRESH_TRANSIENT", options);
499
+ this.kind = kind;
500
+ }
501
+ };
502
+ /** Error codes that mean the refresh token is gone for good. */
503
+ const PERMANENT_REFRESH_CODES = /* @__PURE__ */ new Set([
504
+ "refresh_token_expired",
505
+ "refresh_token_reused",
506
+ "refresh_token_invalidated",
507
+ "invalid_grant"
508
+ ]);
509
+ /** Pull an OAuth error code out of either body shape the endpoint uses. */
510
+ function refreshErrorCode(raw) {
511
+ try {
512
+ const parsed = JSON.parse(raw);
513
+ const error = parsed.error;
514
+ if (typeof error === "string") return error;
515
+ if (typeof error === "object" && error !== null) {
516
+ const code = error.code;
517
+ if (typeof code === "string") return code;
518
+ }
519
+ const code = parsed.code;
520
+ return typeof code === "string" ? code : void 0;
521
+ } catch {
522
+ return;
523
+ }
524
+ }
525
+ async function refreshCodexTokens(store, options = {}) {
526
+ return await refreshCodexTokensWithOperation(store, options, credentialOperation(options.signal));
527
+ }
528
+ /** Internal runtime path that preserves the caller's bound credential logger. */
529
+ async function refreshCodexTokensWithOperation(store, options, operation) {
530
+ const captured = captureCodexStore(store);
531
+ const snapshot = await readStore(captured, operation);
532
+ const file = snapshot.file;
533
+ const current = file?.tokens;
534
+ if (current === void 0 || current === null || current.refresh_token.length === 0) throw new CodexRefreshError(`no refresh token at ${storeLabel(captured)}; run \`npm run provider:codex:login-device\``, "permanent");
535
+ const issuer = issuerOf(options);
536
+ let response;
537
+ try {
538
+ response = await oauthFetch(options, `${issuer}/oauth/token`, {
539
+ method: "POST",
540
+ headers: { "content-type": "application/json" },
541
+ body: JSON.stringify({
542
+ client_id: clientIdOf(options),
543
+ grant_type: "refresh_token",
544
+ refresh_token: current.refresh_token
545
+ })
546
+ });
547
+ } catch (error) {
548
+ throw new CodexRefreshError("token refresh could not reach the auth service", "transient", { cause: error });
549
+ }
550
+ if (!response.ok) {
551
+ const raw = await readResponseText(response, options);
552
+ const code = refreshErrorCode(raw);
553
+ const permanent = response.status === 401 || code !== void 0 && PERMANENT_REFRESH_CODES.has(code.toLowerCase());
554
+ throw new CodexRefreshError(permanent ? `Codex credentials are no longer valid (${code ?? `HTTP ${response.status}`}); run \`npm run provider:codex:login-device\` to sign in again` : `token refresh failed (HTTP ${response.status})`, permanent ? "permanent" : "transient", { cause: new Error(raw) });
555
+ }
556
+ const parsed = await readJson(response, "the token endpoint", options);
557
+ const next = {
558
+ id_token: typeof parsed.id_token === "string" ? parsed.id_token : current.id_token,
559
+ access_token: typeof parsed.access_token === "string" ? parsed.access_token : current.access_token,
560
+ refresh_token: typeof parsed.refresh_token === "string" ? parsed.refresh_token : current.refresh_token
561
+ };
562
+ const accountId = resolveAccountId(next);
563
+ const updated = {
564
+ ...next,
565
+ account_id: accountId ?? null
566
+ };
567
+ const nextFile = {
568
+ ...file,
569
+ auth_mode: file?.auth_mode ?? "chatgpt",
570
+ tokens: updated,
571
+ last_refresh: (/* @__PURE__ */ new Date()).toISOString()
572
+ };
573
+ try {
574
+ await commitStore(captured, nextFile, snapshot.revision, operation);
575
+ } catch (error) {
576
+ if (!isRevisionConflict(error) || captured.kind !== "versioned") throw error;
577
+ const winner = await readStore(captured, operation);
578
+ const winnerTokens = winner.file?.tokens;
579
+ if (winner.revision === snapshot.revision || winnerTokens === void 0 || winnerTokens === null) throw error;
580
+ return requireRefreshTokens(winnerTokens, storeLabel(captured));
581
+ }
582
+ return updated;
583
+ }
584
+ function credentialOperation(signal) {
585
+ return {
586
+ signal: signal ?? NEVER_ABORTED_SIGNAL,
587
+ logger: NULL_LOGGER$1
588
+ };
589
+ }
590
+ async function readStore(captured, operation) {
591
+ if (captured.kind === "versioned") {
592
+ const record = await captured.store.read(operation);
593
+ return record === void 0 ? {
594
+ file: void 0,
595
+ revision: null
596
+ } : {
597
+ file: record.value,
598
+ revision: record.revision
599
+ };
600
+ }
601
+ return {
602
+ file: await captured.store.read(),
603
+ revision: null
604
+ };
605
+ }
606
+ async function commitStore(captured, file, expectedRevision, operation) {
607
+ if (captured.kind === "versioned") {
608
+ await captured.store.commit({
609
+ value: file,
610
+ expectedRevision
611
+ }, operation);
612
+ return;
613
+ }
614
+ await captured.store.write(file);
615
+ }
616
+ function storeLabel(captured) {
617
+ return captured.label;
618
+ }
619
+ function isRevisionConflict(error) {
620
+ if (error === null || typeof error !== "object") return false;
621
+ const descriptor = Object.getOwnPropertyDescriptor(error, "code");
622
+ return descriptor !== void 0 && "value" in descriptor && descriptor.value === "CODEX_CREDENTIAL_REVISION_CONFLICT";
623
+ }
624
+ function requireRefreshTokens(tokens, location) {
625
+ if (typeof tokens.access_token !== "string" || tokens.access_token.length === 0 || typeof tokens.refresh_token !== "string" || tokens.refresh_token.length === 0) throw new CodexRefreshError(`refreshed credentials at ${location} are incomplete`, "permanent");
626
+ return tokens;
627
+ }
628
+
629
+ //#endregion
630
+ //#region src/common/response-media.ts
631
+ /**
632
+ * Contain the official ChatGPT Codex endpoint's missing SSE media-type header.
633
+ * Generic HTTP providers and custom Codex gateways remain strict.
634
+ */
635
+ function codexResponseMediaFetch(options) {
636
+ const officialEndpoint = endpoint(options.officialBaseUrl);
637
+ const configuredEndpoint = endpoint(options.baseUrl);
638
+ const official = configuredEndpoint !== void 0 && configuredEndpoint === officialEndpoint;
639
+ return async (input, init) => {
640
+ const response = await (options.fetch ?? globalThis.fetch)(input, init);
641
+ if (typeof input !== "string") return response;
642
+ const requestedUrl = input;
643
+ if (!official || requestedUrl !== configuredEndpoint || response.status !== 200 || response.body === null || response.headers.get("content-type") !== null || response.redirected || response.type === "opaqueredirect" || response.url.length > 0 && response.url !== requestedUrl) return response;
644
+ const headers = new Headers(response.headers);
645
+ headers.set("content-type", "text/event-stream");
646
+ return new Response(response.body, {
647
+ status: response.status,
648
+ statusText: response.statusText,
649
+ headers
650
+ });
651
+ };
652
+ }
653
+ function endpoint(value) {
654
+ try {
655
+ const url = new URL(value);
656
+ if (url.search.length > 0 || url.hash.length > 0) return void 0;
657
+ return `${url.href.replace(/\/+$/u, "")}/responses`;
658
+ } catch {
659
+ return;
660
+ }
661
+ }
662
+
663
+ //#endregion
664
+ //#region src/adapter.ts
665
+ /** The ChatGPT-backed Codex API base. */
666
+ const CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex";
667
+ /** Client identifier this endpoint expects. See the module note. */
668
+ const CODEX_ORIGINATOR = "codex_cli_rs";
669
+ /**
670
+ * Client version sent when listing models.
671
+ *
672
+ * NOT cosmetic: the model catalog is gated on it, and an older value returns a
673
+ * shorter list or an empty one. Verified against a live account — `0.45.0` returns
674
+ * `{"models":[]}` while `1.0.0` returns the full set.
675
+ */
676
+ const CODEX_CLIENT_VERSION = "1.0.0";
677
+ function randomId() {
678
+ return globalThis.crypto?.randomUUID?.() ?? `sdk-${Date.now().toString(36)}`;
679
+ }
680
+ /** Read `/models`, which requires — and is gated on — a client version. */
681
+ async function discoverCodexModels(context, clientVersion, limits, fetchImpl) {
682
+ const url = `${context.baseUrl}/models?client_version=${encodeURIComponent(clientVersion)}`;
683
+ const timeout = AbortSignal.timeout(limits.timeoutMs);
684
+ const signal = context.signal === void 0 ? timeout : AbortSignal.any([context.signal, timeout]);
685
+ const response = await fetchImpl(url, {
686
+ headers: context.headers,
687
+ signal,
688
+ redirect: "manual"
689
+ });
690
+ await rejectCodexRedirect(response, url, "model catalog", 3e4);
691
+ if (!response.ok) return [];
692
+ const body = await readCatalogJson(response, limits.maxBytes, limits.maxChunks, signal);
693
+ const models = Array.isArray(body.models) ? body.models : [];
694
+ if (models.length > limits.maxModels) throw new RangeError(`Codex model catalog exceeds the ${limits.maxModels}-model limit`);
695
+ return models.flatMap((entry) => {
696
+ if (typeof entry.slug !== "string" || entry.slug.length === 0) return [];
697
+ const modalities = (entry.input_modalities ?? []).filter((value) => value === "text" || value === "image");
698
+ const outputModalities = (entry.output_modalities ?? []).filter((value) => value === "text" || value === "image");
699
+ const efforts = (entry.supported_reasoning_levels ?? []).flatMap((candidate) => {
700
+ if (typeof candidate.effort !== "string" || candidate.effort.length === 0) return [];
701
+ return [{
702
+ id: ReasoningEffortId(candidate.effort),
703
+ name: candidate.effort,
704
+ ...candidate.description === void 0 ? {} : { description: candidate.description }
705
+ }];
706
+ });
707
+ const defaultEffort = typeof entry.default_reasoning_level === "string" && efforts.some((effort) => effort.id === entry.default_reasoning_level) ? ReasoningEffortId(entry.default_reasoning_level) : void 0;
708
+ return [{
709
+ id: entry.slug,
710
+ ...entry.display_name === void 0 ? {} : { name: entry.display_name },
711
+ ...entry.description === void 0 ? {} : { description: entry.description },
712
+ ...modalities.length > 0 ? { inputModalities: modalities } : {},
713
+ ...outputModalities.length > 0 ? { outputModalities } : {},
714
+ ...typeof entry.context_window === "number" && Number.isSafeInteger(entry.context_window) && entry.context_window > 0 ? { contextWindow: entry.context_window } : {},
715
+ ...efforts.length === 0 ? {} : { reasoning: {
716
+ efforts,
717
+ ...defaultEffort === void 0 ? {} : { defaultEffort }
718
+ } }
719
+ }];
720
+ });
721
+ }
722
+ function codexAdapter(options) {
723
+ const captured = captureCodexStore(options?.authStore);
724
+ return captured.kind === "versioned" ? runtimeCodexAdapter(options, captured) : legacyCodexAdapter(options, captured);
725
+ }
726
+ function legacyCodexAdapter(options, captured = captureCodexStore(options?.authStore)) {
727
+ if (captured.kind !== "legacy") throw new TypeError("Codex legacy adapter requires a read/write auth store");
728
+ const store = captured.store;
729
+ const promptCacheKey = options.promptCacheKey ?? randomId();
730
+ const clientVersion = options.clientVersion ?? "1.0.0";
731
+ const catalogLimits = Object.freeze({
732
+ maxBytes: positiveSafeInteger(options.maxCatalogBytes ?? 4194304, "maxCatalogBytes"),
733
+ maxModels: positiveSafeInteger(options.maxCatalogModels ?? 2048, "maxCatalogModels"),
734
+ maxChunks: positiveSafeInteger(options.maxCatalogChunks ?? 1e4, "maxCatalogChunks"),
735
+ timeoutMs: positiveSafeInteger(options.catalogTimeoutMs ?? 3e4, "catalogTimeoutMs")
736
+ });
737
+ /**
738
+ * The Codex request schema has no `temperature`, `top_p`, or
739
+ * `max_output_tokens`, so those knobs are turned off rather than sent and
740
+ * rejected.
741
+ */
742
+ const dialect = {
743
+ sampling: false,
744
+ maxOutputTokens: false,
745
+ structuredOutputs: true,
746
+ store: false,
747
+ messagePhase: true,
748
+ promptCacheKey
749
+ };
750
+ return createHttpProvider({
751
+ displayName: "Codex",
752
+ protocol: openAiResponsesProtocol$1,
753
+ baseUrl: options.baseUrl ?? "https://chatgpt.com/backend-api/codex",
754
+ dialect,
755
+ /**
756
+ * Resolved per operation, which is what lets OAuth live in configuration.
757
+ *
758
+ * Refresh happens HERE, proactively, keyed on the access token's own `exp`
759
+ * with a five-minute margin. Doing it before the request rather than reacting
760
+ * to a 401 keeps `AUTH` correctly non-retryable: by the time a 401 does
761
+ * arrive, the credentials really are dead and the fix is re-login.
762
+ */
763
+ auth: {
764
+ kind: "dynamic",
765
+ resolve: async (_signal, context) => {
766
+ const file = await store.read();
767
+ let tokens = requireTokens(file, store.location);
768
+ if (file !== void 0 && shouldRefresh(file)) tokens = await observeCredentialOperation(context, "codex", "refresh", async () => await refreshCodexTokens(store, options.oauth ?? {}));
769
+ const accountId = resolveAccountId(tokens);
770
+ return {
771
+ "authorization": `Bearer ${tokens.access_token}`,
772
+ "originator": options.originator ?? "codex_cli_rs",
773
+ ...accountId === void 0 ? {} : { "chatgpt-account-id": accountId },
774
+ ...isFedrampAccount(tokens) ? { "x-openai-fedramp": "true" } : {},
775
+ "session-id": promptCacheKey
776
+ };
777
+ }
778
+ },
779
+ ...options.models === void 0 ? { discoverModels: (context) => discoverCodexModels(context, clientVersion, catalogLimits, options.fetch ?? globalThis.fetch) } : { models: options.models },
780
+ defaultMaxTokens: options.defaultMaxTokens ?? 32e3,
781
+ defaultContextWindow: options.defaultContextWindow ?? 272e3,
782
+ ...options.streamIdleTimeoutMs === void 0 ? {} : { streamIdleTimeoutMs: options.streamIdleTimeoutMs },
783
+ ...options.catalogTtlMs === void 0 ? {} : { catalogTtlMs: options.catalogTtlMs },
784
+ ...options.catalogStaleTtlMs === void 0 ? {} : { catalogStaleTtlMs: options.catalogStaleTtlMs },
785
+ ...options.catalogFailureBackoffMs === void 0 ? {} : { catalogFailureBackoffMs: options.catalogFailureBackoffMs },
786
+ ...transportLimits(options),
787
+ ...options.retryPolicy === void 0 ? {} : { retryPolicy: options.retryPolicy },
788
+ ...options.requestLogger === void 0 ? {} : { requestLogger: options.requestLogger }
789
+ });
790
+ }
791
+ const NULL_LOGGER = Object.freeze({
792
+ child: () => NULL_LOGGER,
793
+ trace: () => void 0,
794
+ debug: () => void 0,
795
+ info: () => void 0,
796
+ warn: () => void 0,
797
+ error: () => void 0,
798
+ fatal: () => void 0
799
+ });
800
+ function runtimeCodexAdapter(options, captured = captureCodexStore(options.authStore)) {
801
+ if (captured.kind !== "versioned") throw new TypeError("Codex runtime authStore must be a versioned credential store");
802
+ const store = captured.store;
803
+ const promptCacheKey = options.promptCacheKey ?? randomId();
804
+ const clientVersion = options.clientVersion ?? "1.0.0";
805
+ const catalogLimits = Object.freeze({
806
+ maxBytes: positiveSafeInteger(options.maxCatalogBytes ?? 4194304, "maxCatalogBytes"),
807
+ maxModels: positiveSafeInteger(options.maxCatalogModels ?? 2048, "maxCatalogModels"),
808
+ maxChunks: positiveSafeInteger(options.maxCatalogChunks ?? 1e4, "maxCatalogChunks"),
809
+ timeoutMs: positiveSafeInteger(options.catalogTimeoutMs ?? 3e4, "catalogTimeoutMs")
810
+ });
811
+ const dialect = {
812
+ sampling: false,
813
+ maxOutputTokens: false,
814
+ structuredOutputs: true,
815
+ store: false,
816
+ messagePhase: true,
817
+ promptCacheKey
818
+ };
819
+ return createRuntimeHttpProvider({
820
+ displayName: "Codex",
821
+ protocol: openAiResponsesProtocol$1,
822
+ baseUrl: options.baseUrl ?? "https://chatgpt.com/backend-api/codex",
823
+ dialect,
824
+ auth: {
825
+ kind: "dynamic",
826
+ resolve: async ({ provider, signal, context }) => {
827
+ const operation = {
828
+ signal,
829
+ logger: context?.logger ?? NULL_LOGGER
830
+ };
831
+ const file = (await store.read(operation))?.value;
832
+ let tokens = requireTokens(file, store.label);
833
+ if (file !== void 0 && shouldRefresh(file)) tokens = await observeCredentialOperation(context, provider, "refresh", async () => await refreshCodexTokensWithOperation(store, {
834
+ ...options.oauth ?? {},
835
+ signal
836
+ }, operation));
837
+ const accountId = resolveAccountId(tokens);
838
+ return {
839
+ authorization: `Bearer ${tokens.access_token}`,
840
+ originator: options.originator ?? "codex_cli_rs",
841
+ ...accountId === void 0 ? {} : { "chatgpt-account-id": accountId },
842
+ ...isFedrampAccount(tokens) ? { "x-openai-fedramp": "true" } : {},
843
+ "session-id": promptCacheKey
844
+ };
845
+ }
846
+ },
847
+ ...options.models === void 0 ? { discoverModels: (context) => discoverCodexModels({
848
+ baseUrl: context.baseUrl.href.replace(/\/+$/, ""),
849
+ headers: context.headers,
850
+ signal: context.signal,
851
+ provider: context.provider,
852
+ ...context.context === void 0 ? {} : { context: context.context }
853
+ }, clientVersion, catalogLimits, options.fetch ?? globalThis.fetch) } : { models: options.models },
854
+ ...options.catalogTtlMs === void 0 ? {} : { catalogTtlMs: options.catalogTtlMs },
855
+ ...options.catalogStaleTtlMs === void 0 ? {} : { catalogStaleTtlMs: options.catalogStaleTtlMs },
856
+ ...options.catalogFailureBackoffMs === void 0 ? {} : { catalogFailureBackoffMs: options.catalogFailureBackoffMs },
857
+ defaultMaxTokens: options.defaultMaxTokens ?? 32e3,
858
+ defaultContextWindow: options.defaultContextWindow ?? 272e3,
859
+ ...options.streamIdleTimeoutMs === void 0 ? {} : { streamIdleTimeoutMs: options.streamIdleTimeoutMs },
860
+ ...transportLimits(options),
861
+ ...options.retryPolicy === void 0 ? {} : { retryPolicy: options.retryPolicy },
862
+ ...options.requestLogger === void 0 ? {} : { requestLogger: options.requestLogger }
863
+ });
864
+ }
865
+ function codexPlugin(options) {
866
+ if (isVersionedStoreInput(options.authStore)) {
867
+ const id = "id" in options && options.id !== void 0 ? options.id : "codex";
868
+ const routes = Object.freeze([...options.routes ?? [id]]);
869
+ return defineModelProviderPlugin({
870
+ id,
871
+ family: "codex",
872
+ displayName: "Codex",
873
+ routes,
874
+ ...runtimeDefaultModel("defaultModel" in options ? options.defaultModel : void 0, routes),
875
+ setup(registrar) {
876
+ const adapter = runtimeCodexAdapter(options);
877
+ const remove = registrar.registerAdapter(adapter);
878
+ return () => {
879
+ remove();
880
+ };
881
+ }
882
+ });
883
+ }
884
+ return legacyCodexPlugin(options);
885
+ }
886
+ /** Marker inspection only; full store capture stays deferred to preferred setup. */
887
+ function isVersionedStoreInput(value) {
888
+ if (typeof value !== "object" || value === null) return false;
889
+ const kind = Object.getOwnPropertyDescriptor(value, "kind");
890
+ return kind !== void 0 && "value" in kind && kind.value === "credential-store";
891
+ }
892
+ function legacyCodexPlugin(options, captured) {
893
+ const routes = Object.freeze([...options.routes ?? ["codex"]]);
894
+ const adapter = legacyCodexAdapter(options, captured);
895
+ return Object.freeze({
896
+ id: "codex",
897
+ displayName: "Codex",
898
+ setup(registrar) {
899
+ registrar.registerAdapter(routes, adapter);
900
+ }
901
+ });
902
+ }
903
+ function runtimeDefaultModel(value, routes) {
904
+ if (value === void 0) return {};
905
+ if (typeof value !== "string") return { defaultModel: value };
906
+ if (routes.length !== 1) throw new TypeError("A string defaultModel requires exactly one Codex route");
907
+ return { defaultModel: Object.freeze({
908
+ provider: routes[0],
909
+ id: value
910
+ }) };
911
+ }
912
+ async function readCatalogJson(response, maxBytes, maxChunks, signal) {
913
+ const declared = Number(response.headers.get("content-length"));
914
+ if (Number.isFinite(declared) && declared > maxBytes) {
915
+ if (response.body !== null) await waitForSettlement(response.body.cancel().catch(() => void 0), 3e4);
916
+ throw new RangeError(`Codex model catalog exceeds the ${maxBytes}-byte limit`);
917
+ }
918
+ if (response.body === null) throw new TypeError("Codex model catalog returned no body");
919
+ const reader = response.body.getReader();
920
+ const chunks = [];
921
+ let bytes = 0;
922
+ let chunkCount = 0;
923
+ try {
924
+ while (true) {
925
+ const next = await raceAbort(reader.read(), signal);
926
+ if (next.done) break;
927
+ if (next.value === void 0) continue;
928
+ chunkCount++;
929
+ if (chunkCount > maxChunks) {
930
+ await waitForSettlement(reader.cancel().catch(() => void 0), 3e4);
931
+ throw new RangeError(`Codex model catalog exceeds the ${maxChunks}-chunk limit`);
932
+ }
933
+ bytes += next.value.byteLength;
934
+ if (bytes > maxBytes) {
935
+ await waitForSettlement(reader.cancel().catch(() => void 0), 3e4);
936
+ throw new RangeError(`Codex model catalog exceeds the ${maxBytes}-byte limit`);
937
+ }
938
+ chunks.push(next.value);
939
+ }
940
+ } finally {
941
+ reader.releaseLock();
942
+ }
943
+ const merged = new Uint8Array(bytes);
944
+ let offset = 0;
945
+ for (const chunk of chunks) {
946
+ merged.set(chunk, offset);
947
+ offset += chunk.byteLength;
948
+ }
949
+ const parsed = JSON.parse(new TextDecoder().decode(merged));
950
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new TypeError("Codex model catalog must be a JSON object");
951
+ return parsed;
952
+ }
953
+ function raceAbort(pending, signal) {
954
+ if (signal.aborted) return Promise.reject(signal.reason ?? /* @__PURE__ */ new Error("Codex catalog request aborted"));
955
+ return new Promise((resolve, reject) => {
956
+ const abort = () => {
957
+ cleanup();
958
+ reject(signal.reason ?? /* @__PURE__ */ new Error("Codex catalog request aborted"));
959
+ };
960
+ const cleanup = () => signal.removeEventListener("abort", abort);
961
+ signal.addEventListener("abort", abort, { once: true });
962
+ pending.then((value) => {
963
+ cleanup();
964
+ resolve(value);
965
+ }, (error) => {
966
+ cleanup();
967
+ reject(error);
968
+ });
969
+ });
970
+ }
971
+ function positiveSafeInteger(value, field) {
972
+ if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`Codex ${field} must be a positive safe integer`);
973
+ return value;
974
+ }
975
+ function transportLimits(options) {
976
+ const fetch = codexResponseMediaFetch({
977
+ baseUrl: options.baseUrl ?? "https://chatgpt.com/backend-api/codex",
978
+ officialBaseUrl: CODEX_BASE_URL,
979
+ ...options.fetch === void 0 ? {} : { fetch: options.fetch }
980
+ });
981
+ return {
982
+ ...options.requestTimeoutMs === void 0 ? {} : { requestTimeoutMs: options.requestTimeoutMs },
983
+ ...options.maxRequestBytes === void 0 ? {} : { maxRequestBytes: options.maxRequestBytes },
984
+ ...options.maxResponseBytes === void 0 ? {} : { maxResponseBytes: options.maxResponseBytes },
985
+ ...options.maxResponseChunks === void 0 ? {} : { maxResponseChunks: options.maxResponseChunks },
986
+ ...options.maxSseEvents === void 0 ? {} : { maxSseEvents: options.maxSseEvents },
987
+ ...options.maxSseEventChars === void 0 ? {} : { maxSseEventChars: options.maxSseEventChars },
988
+ ...options.maxErrorBodyBytes === void 0 ? {} : { maxErrorBodyBytes: options.maxErrorBodyBytes },
989
+ ...options.requestLoggerTimeoutMs === void 0 ? {} : { requestLoggerTimeoutMs: options.requestLoggerTimeoutMs },
990
+ fetch
991
+ };
992
+ }
993
+
994
+ //#endregion
995
+ export { ACCESS_TOKEN_REFRESH_WINDOW_MS, CODEX_BASE_URL, CODEX_CLIENT_ID, CODEX_CLIENT_VERSION, CODEX_ORIGINATOR, CodexRefreshError, DEFAULT_CODEX_ISSUER, LAST_REFRESH_MAX_AGE_MS, codexAdapter, codexPlugin, isFedrampAccount, memoryCodexAuthStore, memoryCodexCredentialStore, openAiResponsesProtocol, readJwtClaims, refreshCodexTokens, requestDeviceCode, requireTokens, resolveAccountId, runDeviceCodeLogin, shouldRefresh };
996
+ //# sourceMappingURL=index.js.map