@lumifai/harness-model-codex-oauth 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/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # @lumifai/harness-model-codex-oauth
2
+
3
+ Mastra model gateway that bills OpenAI requests to a ChatGPT / Codex subscription
4
+ (Plus, Pro, Team, Business, or Enterprise) instead of a Platform API key.
5
+ Registering it lets any harness model string of the form
6
+ `codex-oauth/openai/<model-id>` run against `chatgpt.com/backend-api/codex` with
7
+ a subscription OAuth token, so mode models, subagents, and the model picker all
8
+ work unchanged.
9
+
10
+ The gateway is a thin credential swap over the standard `@ai-sdk/openai`
11
+ Responses provider: streaming, tool calls, usage accounting, and reasoning are
12
+ handled by that provider. The harness agent loop, tool permissions, and
13
+ human-in-the-loop flow are untouched.
14
+
15
+ ## Before you enable this
16
+
17
+ ChatGPT OAuth tokens are scoped to Codex / ChatGPT product surfaces. The
18
+ Platform API (`api.openai.com`) rejects them, and the Codex backend rejects
19
+ Platform API keys. This gateway talks to the subscription endpoint with the
20
+ credential written by `codex login`, which means **it presents your application
21
+ as a local Codex client**. Consequences you are accepting:
22
+
23
+ - It is a deliberate reuse of a subscription credential outside the Codex CLI,
24
+ not a supported multi-tenant configuration. The subscription account is the
25
+ one at risk if OpenAI objects.
26
+ - OpenAI can change the Codex backend check at any time and requests will start
27
+ failing with no deprecation path.
28
+ - Use this only for local or self-hosted development where each developer
29
+ supplies their own credential. Do not ship it as a customer-facing auth path,
30
+ and do not use it for production or multi-tenant traffic.
31
+
32
+ Because of that, the acknowledgement is a required constructor argument rather
33
+ than an option with a default:
34
+
35
+ ```typescript
36
+ createCodexOAuthGateway({ acknowledgeCodexSubscription: true });
37
+ ```
38
+
39
+ For anything customer-facing, use a normal `OPENAI_API_KEY` with the built-in
40
+ `openai/*` models instead.
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ pnpm add @lumifai/harness-model-codex-oauth
46
+ ```
47
+
48
+ ## Get credentials
49
+
50
+ Requires a ChatGPT plan that includes Codex. Sign in once with the Codex CLI:
51
+
52
+ ```bash
53
+ codex login
54
+ ```
55
+
56
+ That writes `~/.codex/auth.json` (access token, refresh token, account id). The
57
+ gateway reads that file by default and refreshes the access token in place when
58
+ it is near expiry. No env var export is required for the common local path.
59
+
60
+ Fallback when no auth file is present (typical in CI). To use a non-default
61
+ auth file path, set `CODEX_HOME` instead — env tokens do not override an
62
+ existing `auth.json`:
63
+
64
+ ```bash
65
+ export CODEX_OAUTH_ACCESS_TOKEN='…'
66
+ export CODEX_OAUTH_ACCOUNT_ID='…' # or embed the chatgpt_account_id claim in the JWT
67
+ export CODEX_OAUTH_REFRESH_TOKEN='…' # optional; enables refresh without the auth file
68
+ # or point at a non-default Codex home:
69
+ export CODEX_HOME='/path/to/codex-home'
70
+ ```
71
+
72
+ Treat these as secrets. The gateway resolves credentials per request, so a
73
+ rotated token is picked up without a restart.
74
+
75
+ ## Register the gateway
76
+
77
+ ```typescript
78
+ import { createHarnessSessionManager } from '@lumifai/harness-server';
79
+ import { createCodexOAuthGateway } from '@lumifai/harness-model-codex-oauth';
80
+
81
+ const manager = createHarnessSessionManager({
82
+ bootConfig: {
83
+ gateways: [createCodexOAuthGateway({ acknowledgeCodexSubscription: true })],
84
+
85
+ planModel: 'codex-oauth/openai/gpt-5.4',
86
+ buildModel: 'codex-oauth/openai/gpt-5.4',
87
+
88
+ workspaceBasePath: './workspace',
89
+ workspaceMode: 'local',
90
+ },
91
+ });
92
+ ```
93
+
94
+ The gateway claims only `codex-oauth/…` model ids, so bare `openai/…` ids keep
95
+ routing to the API-key provider and both can coexist in one process.
96
+
97
+ The advertised catalog is static, so model resolution works offline and issues
98
+ no network call at boot. Pass `models` to advertise a different set:
99
+
100
+ ```typescript
101
+ createCodexOAuthGateway({
102
+ acknowledgeCodexSubscription: true,
103
+ models: ['gpt-5.6-luna', 'gpt-5.4', 'gpt-5.4-mini'],
104
+ });
105
+ ```
106
+
107
+ Other options: `resolveCredentials` replaces the auth-file / env lookup (for a
108
+ secret manager), `baseURL` points at a proxy in front of the Codex backend, and
109
+ `originator` overrides the upstream identity header (defaults to `codex_cli_rs`).
110
+ The subscription bearer token is sent to whatever host `baseURL` resolves to —
111
+ only use a proxy you trust.
112
+
113
+ ## Notes
114
+
115
+ - **Rate limits are your subscription's.** Quotas are far tighter than
116
+ pay-as-you-go API limits and are shared with your own Codex CLI usage. Expect
117
+ rate-limit errors under concurrent load.
118
+ - **Responses API only.** The Codex ChatGPT backend speaks
119
+ `/backend-api/codex/responses`, not Chat Completions. The gateway configures
120
+ `@ai-sdk/openai` accordingly.
121
+ - **API credentials do not interfere.** This gateway never reads
122
+ `OPENAI_API_KEY`, so there is no credential precedence to unset.
123
+ - **Not multi-tenant.** One local credential per process. Do not pool or
124
+ redistribute tokens across users.
package/dist/auth.d.ts ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Loads ChatGPT / Codex subscription OAuth credentials for local use.
3
+ *
4
+ * Default source is the auth file written by `codex login`
5
+ * (`~/.codex/auth.json`). Tokens expire, so callers should run them through
6
+ * {@link ensureFreshCodexCredentials} before each request.
7
+ */
8
+ export declare const CODEX_OAUTH_ACCESS_TOKEN_ENV_VAR = "CODEX_OAUTH_ACCESS_TOKEN";
9
+ export declare const CODEX_OAUTH_ACCOUNT_ID_ENV_VAR = "CODEX_OAUTH_ACCOUNT_ID";
10
+ export declare const CODEX_OAUTH_REFRESH_TOKEN_ENV_VAR = "CODEX_OAUTH_REFRESH_TOKEN";
11
+ /** Codex CLI's public OAuth client id (not a secret). */
12
+ export declare const CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
13
+ export declare const CODEX_OAUTH_TOKEN_URL = "https://auth.openai.com/oauth/token";
14
+ /** Refresh when the access token is within this many ms of expiry. */
15
+ export declare const CODEX_OAUTH_REFRESH_MARGIN_MS: number;
16
+ export type CodexOAuthCredentials = {
17
+ accessToken: string;
18
+ accountId: string;
19
+ refreshToken?: string;
20
+ idToken?: string;
21
+ /** Absolute expiry time of the access token, in epoch milliseconds. */
22
+ expiresAt?: number;
23
+ /** Path of the auth file these credentials were loaded from, if any. */
24
+ authFilePath?: string;
25
+ };
26
+ export type CodexOAuthCredentialsResolver = () => CodexOAuthCredentials | undefined | Promise<CodexOAuthCredentials | undefined>;
27
+ /** Best-effort JWT payload decode — no signature verification. */
28
+ export declare function parseJwtClaims(token: string): Record<string, unknown> | undefined;
29
+ export declare function deriveAccountId(...tokens: Array<string | undefined>): string | undefined;
30
+ export declare function deriveExpiresAtMs(token: string): number | undefined;
31
+ /** Candidate auth.json paths, matching Codex / ChatGPT local CLI conventions. */
32
+ export declare function resolveCodexAuthFileCandidates(env?: NodeJS.ProcessEnv, home?: string): string[];
33
+ export declare function loadCodexAuthFile(path: string): (CodexOAuthCredentials & {
34
+ authFilePath: string;
35
+ }) | undefined;
36
+ export declare function resolveCodexCredentialsFromEnv(env?: NodeJS.ProcessEnv): CodexOAuthCredentials | undefined;
37
+ /**
38
+ * Default credential lookup: first existing Codex auth file, then env fallback
39
+ * when no auth file is present. (`CODEX_HOME` covers a non-default auth file path.)
40
+ */
41
+ export declare function resolveCodexCredentials(env?: NodeJS.ProcessEnv, home?: string): CodexOAuthCredentials | undefined;
42
+ export declare function needsCodexTokenRefresh(credentials: CodexOAuthCredentials, now?: number, marginMs?: number): boolean;
43
+ export type RefreshCodexTokensOptions = {
44
+ refreshToken: string;
45
+ /** Fallback when the refreshed tokens omit `chatgpt_account_id`. */
46
+ accountId?: string;
47
+ fetchImpl?: typeof fetch;
48
+ clientId?: string;
49
+ tokenUrl?: string;
50
+ now?: () => number;
51
+ };
52
+ export declare function refreshCodexTokens(options: RefreshCodexTokensOptions): Promise<CodexOAuthCredentials | undefined>;
53
+ /** Writes refreshed tokens back to a Codex-compatible auth.json (mode 0600). */
54
+ export declare function persistCodexAuthFile(path: string, credentials: CodexOAuthCredentials): void;
55
+ export type EnsureFreshCodexCredentialsOptions = {
56
+ credentials: CodexOAuthCredentials;
57
+ fetchImpl?: typeof fetch;
58
+ now?: () => number;
59
+ /** Persist refreshed tokens. Defaults to writing `credentials.authFilePath` when set. */
60
+ persist?: (credentials: CodexOAuthCredentials) => void | Promise<void>;
61
+ refreshMarginMs?: number;
62
+ /** Ignore expiry margin and refresh whenever a refresh token is available. */
63
+ forceRefresh?: boolean;
64
+ };
65
+ /**
66
+ * Returns credentials that are not near expiry, refreshing via the Codex OAuth
67
+ * client when a refresh token is available.
68
+ */
69
+ export declare function ensureFreshCodexCredentials(options: EnsureFreshCodexCredentialsOptions): Promise<CodexOAuthCredentials>;
70
+ /**
71
+ * Prefer an in-memory refreshed snapshot over a stale env/file reload of the
72
+ * same account so env-only credentials do not re-refresh on every request.
73
+ */
74
+ export declare function mergeCredentialSnapshot(cached: CodexOAuthCredentials | undefined, resolved: CodexOAuthCredentials): CodexOAuthCredentials;
75
+ export type LiveCodexCredentialsResolver = {
76
+ resolve: (options?: {
77
+ forceRefresh?: boolean;
78
+ }) => Promise<CodexOAuthCredentials>;
79
+ };
80
+ export type CreateLiveCodexCredentialsResolverOptions = {
81
+ resolveCredentials?: CodexOAuthCredentialsResolver;
82
+ fetchImpl?: typeof fetch;
83
+ now?: () => number;
84
+ persist?: EnsureFreshCodexCredentialsOptions['persist'];
85
+ refreshMarginMs?: number;
86
+ };
87
+ /**
88
+ * Per-gateway credential cache with in-flight refresh dedup. Concurrent callers
89
+ * share one refresh; sequential callers reuse the cached access token until it
90
+ * nears expiry (including the env-var path with no auth file to persist to).
91
+ */
92
+ export declare function createLiveCodexCredentialsResolver(options?: CreateLiveCodexCredentialsResolverOptions): LiveCodexCredentialsResolver;
package/dist/auth.js ADDED
@@ -0,0 +1,348 @@
1
+ "use strict";
2
+ /**
3
+ * Loads ChatGPT / Codex subscription OAuth credentials for local use.
4
+ *
5
+ * Default source is the auth file written by `codex login`
6
+ * (`~/.codex/auth.json`). Tokens expire, so callers should run them through
7
+ * {@link ensureFreshCodexCredentials} before each request.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.CODEX_OAUTH_REFRESH_MARGIN_MS = exports.CODEX_OAUTH_TOKEN_URL = exports.CODEX_OAUTH_CLIENT_ID = exports.CODEX_OAUTH_REFRESH_TOKEN_ENV_VAR = exports.CODEX_OAUTH_ACCOUNT_ID_ENV_VAR = exports.CODEX_OAUTH_ACCESS_TOKEN_ENV_VAR = void 0;
11
+ exports.parseJwtClaims = parseJwtClaims;
12
+ exports.deriveAccountId = deriveAccountId;
13
+ exports.deriveExpiresAtMs = deriveExpiresAtMs;
14
+ exports.resolveCodexAuthFileCandidates = resolveCodexAuthFileCandidates;
15
+ exports.loadCodexAuthFile = loadCodexAuthFile;
16
+ exports.resolveCodexCredentialsFromEnv = resolveCodexCredentialsFromEnv;
17
+ exports.resolveCodexCredentials = resolveCodexCredentials;
18
+ exports.needsCodexTokenRefresh = needsCodexTokenRefresh;
19
+ exports.refreshCodexTokens = refreshCodexTokens;
20
+ exports.persistCodexAuthFile = persistCodexAuthFile;
21
+ exports.ensureFreshCodexCredentials = ensureFreshCodexCredentials;
22
+ exports.mergeCredentialSnapshot = mergeCredentialSnapshot;
23
+ exports.createLiveCodexCredentialsResolver = createLiveCodexCredentialsResolver;
24
+ const node_os_1 = require("node:os");
25
+ const node_path_1 = require("node:path");
26
+ const node_fs_1 = require("node:fs");
27
+ exports.CODEX_OAUTH_ACCESS_TOKEN_ENV_VAR = 'CODEX_OAUTH_ACCESS_TOKEN';
28
+ exports.CODEX_OAUTH_ACCOUNT_ID_ENV_VAR = 'CODEX_OAUTH_ACCOUNT_ID';
29
+ exports.CODEX_OAUTH_REFRESH_TOKEN_ENV_VAR = 'CODEX_OAUTH_REFRESH_TOKEN';
30
+ /** Codex CLI's public OAuth client id (not a secret). */
31
+ exports.CODEX_OAUTH_CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
32
+ exports.CODEX_OAUTH_TOKEN_URL = 'https://auth.openai.com/oauth/token';
33
+ /** Refresh when the access token is within this many ms of expiry. */
34
+ exports.CODEX_OAUTH_REFRESH_MARGIN_MS = 5 * 60 * 1000;
35
+ function isRecord(value) {
36
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
37
+ }
38
+ /** Best-effort JWT payload decode — no signature verification. */
39
+ function parseJwtClaims(token) {
40
+ const parts = token.split('.');
41
+ if (parts.length !== 3 || !parts[1])
42
+ return undefined;
43
+ try {
44
+ const padded = parts[1].replace(/-/g, '+').replace(/_/g, '/');
45
+ const json = Buffer.from(padded + '='.repeat((4 - (padded.length % 4)) % 4), 'base64').toString('utf8');
46
+ const parsed = JSON.parse(json);
47
+ return isRecord(parsed) ? parsed : undefined;
48
+ }
49
+ catch {
50
+ return undefined;
51
+ }
52
+ }
53
+ function deriveAccountId(...tokens) {
54
+ for (const token of tokens) {
55
+ if (!token)
56
+ continue;
57
+ const claims = parseJwtClaims(token);
58
+ const auth = claims?.['https://api.openai.com/auth'];
59
+ if (isRecord(auth) && typeof auth.chatgpt_account_id === 'string' && auth.chatgpt_account_id) {
60
+ return auth.chatgpt_account_id;
61
+ }
62
+ }
63
+ return undefined;
64
+ }
65
+ function deriveExpiresAtMs(token) {
66
+ const claims = parseJwtClaims(token);
67
+ return typeof claims?.exp === 'number' ? claims.exp * 1000 : undefined;
68
+ }
69
+ /** Candidate auth.json paths, matching Codex / ChatGPT local CLI conventions. */
70
+ function resolveCodexAuthFileCandidates(env = process.env, home = (0, node_os_1.homedir)()) {
71
+ const paths = [];
72
+ if (env.CODEX_HOME)
73
+ paths.push((0, node_path_1.join)(env.CODEX_HOME, 'auth.json'));
74
+ if (env.CHATGPT_LOCAL_HOME)
75
+ paths.push((0, node_path_1.join)(env.CHATGPT_LOCAL_HOME, 'auth.json'));
76
+ paths.push((0, node_path_1.join)(home, '.codex', 'auth.json'));
77
+ paths.push((0, node_path_1.join)(home, '.chatgpt-local', 'auth.json'));
78
+ return paths;
79
+ }
80
+ function loadCodexAuthFile(path) {
81
+ if (!(0, node_fs_1.existsSync)(path))
82
+ return undefined;
83
+ let raw;
84
+ try {
85
+ raw = JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8'));
86
+ }
87
+ catch {
88
+ return undefined;
89
+ }
90
+ if (!isRecord(raw) || !isRecord(raw.tokens))
91
+ return undefined;
92
+ const tokens = raw.tokens;
93
+ const accessToken = tokens?.access_token?.trim();
94
+ if (!accessToken)
95
+ return undefined;
96
+ const accountId = tokens?.account_id?.trim() || deriveAccountId(tokens?.id_token, accessToken);
97
+ if (!accountId)
98
+ return undefined;
99
+ return {
100
+ accessToken,
101
+ accountId,
102
+ refreshToken: tokens?.refresh_token?.trim() || undefined,
103
+ idToken: tokens?.id_token?.trim() || undefined,
104
+ expiresAt: deriveExpiresAtMs(accessToken),
105
+ authFilePath: path,
106
+ };
107
+ }
108
+ function resolveCodexCredentialsFromEnv(env = process.env) {
109
+ const accessToken = env[exports.CODEX_OAUTH_ACCESS_TOKEN_ENV_VAR]?.trim();
110
+ if (!accessToken)
111
+ return undefined;
112
+ const accountId = env[exports.CODEX_OAUTH_ACCOUNT_ID_ENV_VAR]?.trim() || deriveAccountId(accessToken);
113
+ if (!accountId)
114
+ return undefined;
115
+ return {
116
+ accessToken,
117
+ accountId,
118
+ refreshToken: env[exports.CODEX_OAUTH_REFRESH_TOKEN_ENV_VAR]?.trim() || undefined,
119
+ expiresAt: deriveExpiresAtMs(accessToken),
120
+ };
121
+ }
122
+ /**
123
+ * Default credential lookup: first existing Codex auth file, then env fallback
124
+ * when no auth file is present. (`CODEX_HOME` covers a non-default auth file path.)
125
+ */
126
+ function resolveCodexCredentials(env = process.env, home = (0, node_os_1.homedir)()) {
127
+ for (const path of resolveCodexAuthFileCandidates(env, home)) {
128
+ const fromFile = loadCodexAuthFile(path);
129
+ if (fromFile)
130
+ return fromFile;
131
+ }
132
+ return resolveCodexCredentialsFromEnv(env);
133
+ }
134
+ function needsCodexTokenRefresh(credentials, now = Date.now(), marginMs = exports.CODEX_OAUTH_REFRESH_MARGIN_MS) {
135
+ const expiresAt = credentials.expiresAt ?? deriveExpiresAtMs(credentials.accessToken);
136
+ if (expiresAt == null || expiresAt <= 0)
137
+ return false;
138
+ return expiresAt <= now + marginMs;
139
+ }
140
+ async function refreshCodexTokens(options) {
141
+ const doFetch = options.fetchImpl ?? globalThis.fetch;
142
+ const response = await doFetch(options.tokenUrl ?? exports.CODEX_OAUTH_TOKEN_URL, {
143
+ method: 'POST',
144
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
145
+ body: new URLSearchParams({
146
+ grant_type: 'refresh_token',
147
+ refresh_token: options.refreshToken,
148
+ client_id: options.clientId ?? exports.CODEX_OAUTH_CLIENT_ID,
149
+ scope: 'openid profile email offline_access',
150
+ }).toString(),
151
+ });
152
+ if (!response.ok)
153
+ return undefined;
154
+ const payload = (await response.json());
155
+ if (!payload.access_token)
156
+ return undefined;
157
+ const now = options.now?.() ?? Date.now();
158
+ const accountId = deriveAccountId(payload.id_token, payload.access_token) ?? options.accountId;
159
+ if (!accountId)
160
+ return undefined;
161
+ return {
162
+ accessToken: payload.access_token,
163
+ refreshToken: payload.refresh_token ?? options.refreshToken,
164
+ idToken: payload.id_token,
165
+ accountId,
166
+ expiresAt: now + (payload.expires_in ?? 3600) * 1000,
167
+ };
168
+ }
169
+ /** Writes refreshed tokens back to a Codex-compatible auth.json (mode 0600). */
170
+ function persistCodexAuthFile(path, credentials) {
171
+ let existing = {};
172
+ if ((0, node_fs_1.existsSync)(path)) {
173
+ try {
174
+ const parsed = JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8'));
175
+ if (isRecord(parsed))
176
+ existing = parsed;
177
+ }
178
+ catch {
179
+ existing = {};
180
+ }
181
+ }
182
+ else {
183
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
184
+ }
185
+ const next = {
186
+ ...existing,
187
+ auth_mode: existing.auth_mode ?? 'chatgpt',
188
+ tokens: {
189
+ ...(isRecord(existing.tokens) ? existing.tokens : {}),
190
+ access_token: credentials.accessToken,
191
+ account_id: credentials.accountId,
192
+ // Only overwrite optional fields when present — a refresh_token grant often
193
+ // omits id_token, and JSON.stringify would otherwise delete the existing key.
194
+ ...(credentials.refreshToken ? { refresh_token: credentials.refreshToken } : {}),
195
+ ...(credentials.idToken ? { id_token: credentials.idToken } : {}),
196
+ },
197
+ last_refresh: new Date().toISOString(),
198
+ };
199
+ const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
200
+ try {
201
+ (0, node_fs_1.writeFileSync)(tmpPath, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
202
+ try {
203
+ (0, node_fs_1.chmodSync)(tmpPath, 0o600);
204
+ }
205
+ catch {
206
+ // Best-effort on platforms that ignore mode.
207
+ }
208
+ (0, node_fs_1.renameSync)(tmpPath, path);
209
+ }
210
+ catch (error) {
211
+ try {
212
+ (0, node_fs_1.unlinkSync)(tmpPath);
213
+ }
214
+ catch {
215
+ // Ignore cleanup failures.
216
+ }
217
+ throw error;
218
+ }
219
+ }
220
+ /**
221
+ * Returns credentials that are not near expiry, refreshing via the Codex OAuth
222
+ * client when a refresh token is available.
223
+ */
224
+ async function ensureFreshCodexCredentials(options) {
225
+ const { credentials } = options;
226
+ const now = options.now?.() ?? Date.now();
227
+ const shouldRefresh = options.forceRefresh || needsCodexTokenRefresh(credentials, now, options.refreshMarginMs);
228
+ if (!shouldRefresh) {
229
+ return credentials;
230
+ }
231
+ if (!credentials.refreshToken) {
232
+ if (options.forceRefresh || needsCodexTokenRefresh(credentials, now, 0)) {
233
+ throw new Error('Codex subscription OAuth token expired and no refresh token is available. Run `codex login`.');
234
+ }
235
+ return credentials;
236
+ }
237
+ const refreshed = await refreshCodexTokens({
238
+ refreshToken: credentials.refreshToken,
239
+ accountId: credentials.accountId,
240
+ fetchImpl: options.fetchImpl,
241
+ now: options.now,
242
+ });
243
+ if (!refreshed) {
244
+ if (options.forceRefresh || needsCodexTokenRefresh(credentials, now, 0)) {
245
+ throw new Error('Codex subscription OAuth token expired and refresh failed. Run `codex login`.');
246
+ }
247
+ return credentials;
248
+ }
249
+ const next = {
250
+ ...refreshed,
251
+ // Preserve an existing id_token when the refresh response omitted one.
252
+ idToken: refreshed.idToken ?? credentials.idToken,
253
+ authFilePath: credentials.authFilePath,
254
+ };
255
+ if (options.persist) {
256
+ await options.persist(next);
257
+ }
258
+ else if (credentials.authFilePath) {
259
+ persistCodexAuthFile(credentials.authFilePath, next);
260
+ }
261
+ return next;
262
+ }
263
+ function credentialExpiryMs(credentials) {
264
+ return credentials.expiresAt ?? deriveExpiresAtMs(credentials.accessToken) ?? 0;
265
+ }
266
+ /**
267
+ * Prefer an in-memory refreshed snapshot over a stale env/file reload of the
268
+ * same account so env-only credentials do not re-refresh on every request.
269
+ */
270
+ function mergeCredentialSnapshot(cached, resolved) {
271
+ if (!cached)
272
+ return resolved;
273
+ if (cached.accountId !== resolved.accountId)
274
+ return resolved;
275
+ if (cached.authFilePath &&
276
+ resolved.authFilePath &&
277
+ cached.authFilePath !== resolved.authFilePath) {
278
+ return resolved;
279
+ }
280
+ if (credentialExpiryMs(cached) < credentialExpiryMs(resolved)) {
281
+ return {
282
+ ...resolved,
283
+ idToken: resolved.idToken ?? cached.idToken,
284
+ refreshToken: resolved.refreshToken ?? cached.refreshToken,
285
+ authFilePath: resolved.authFilePath ?? cached.authFilePath,
286
+ };
287
+ }
288
+ return {
289
+ ...resolved,
290
+ accessToken: cached.accessToken,
291
+ refreshToken: cached.refreshToken ?? resolved.refreshToken,
292
+ idToken: cached.idToken ?? resolved.idToken,
293
+ expiresAt: cached.expiresAt ?? resolved.expiresAt,
294
+ authFilePath: resolved.authFilePath ?? cached.authFilePath,
295
+ };
296
+ }
297
+ /**
298
+ * Per-gateway credential cache with in-flight refresh dedup. Concurrent callers
299
+ * share one refresh; sequential callers reuse the cached access token until it
300
+ * nears expiry (including the env-var path with no auth file to persist to).
301
+ */
302
+ function createLiveCodexCredentialsResolver(options = {}) {
303
+ let cached;
304
+ let inflight;
305
+ let inflightForce = false;
306
+ return {
307
+ async resolve({ forceRefresh = false } = {}) {
308
+ if (inflight && (!forceRefresh || inflightForce)) {
309
+ return inflight;
310
+ }
311
+ const previous = inflight;
312
+ const run = (async () => {
313
+ if (previous) {
314
+ try {
315
+ await previous;
316
+ }
317
+ catch {
318
+ // Prior attempt failed; continue with a fresh resolve.
319
+ }
320
+ }
321
+ const resolved = await (options.resolveCredentials ?? resolveCodexCredentials)();
322
+ if (!resolved) {
323
+ throw new Error('Codex subscription OAuth credentials missing. Run `codex login` (writes ~/.codex/auth.json) or export CODEX_OAUTH_ACCESS_TOKEN and CODEX_OAUTH_ACCOUNT_ID.');
324
+ }
325
+ const candidate = mergeCredentialSnapshot(cached, resolved);
326
+ const fresh = await ensureFreshCodexCredentials({
327
+ credentials: candidate,
328
+ fetchImpl: options.fetchImpl,
329
+ now: options.now,
330
+ persist: options.persist,
331
+ refreshMarginMs: options.refreshMarginMs,
332
+ forceRefresh,
333
+ });
334
+ cached = fresh;
335
+ return fresh;
336
+ })().finally(() => {
337
+ if (inflight === run) {
338
+ inflight = undefined;
339
+ inflightForce = false;
340
+ }
341
+ });
342
+ inflight = run;
343
+ inflightForce = forceRefresh;
344
+ return run;
345
+ },
346
+ };
347
+ }
348
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import { MastraModelGateway } from '@mastra/core/llm';
2
+ import type { GatewayLanguageModel, ProviderConfig } from '@mastra/core/llm';
3
+ import { type CodexOAuthCredentialsResolver } from './auth';
4
+ import { type CodexOAuthModelId } from './models';
5
+ import { type CreateCodexOAuthFetchOptions } from './oauth-fetch';
6
+ export type CodexOAuthGatewayOptions = {
7
+ /**
8
+ * Required, explicit opt-in. This gateway bills ChatGPT / Codex subscription
9
+ * quotas via OAuth credentials intended for the Codex CLI. Only enable it for
10
+ * local/self-hosted use where each developer supplies their own credential;
11
+ * never as a customer-facing or multi-tenant auth path. See README
12
+ * "Before you enable this".
13
+ */
14
+ acknowledgeCodexSubscription: true;
15
+ /** Model ids to advertise under `codex-oauth/openai/<id>`. */
16
+ models?: ReadonlyArray<CodexOAuthModelId>;
17
+ /**
18
+ * Overrides the default `~/.codex/auth.json` / env lookup. Invoked on every
19
+ * outbound Codex request so a rotated token is picked up without resolving a
20
+ * new language model.
21
+ */
22
+ resolveCredentials?: CodexOAuthCredentialsResolver;
23
+ /**
24
+ * Overrides the Codex Responses base URL (proxy in front of it). The
25
+ * subscription OAuth bearer token is sent to whatever host this resolves to —
26
+ * only point it at a proxy you trust.
27
+ */
28
+ baseURL?: string;
29
+ /** Upstream `originator` header. Pass `false` to omit. */
30
+ originator?: CreateCodexOAuthFetchOptions['originator'];
31
+ /** Injected in tests. */
32
+ fetchImpl?: typeof fetch;
33
+ };
34
+ export declare function createCodexOAuthGateway(options: CodexOAuthGatewayOptions): CodexOAuthGateway;
35
+ export declare class CodexOAuthGateway extends MastraModelGateway {
36
+ readonly id: "codex-oauth";
37
+ readonly name = "Codex subscription (OAuth)";
38
+ private readonly models;
39
+ private readonly resolveCredentials;
40
+ private readonly liveCredentials;
41
+ private readonly oauthFetch;
42
+ private readonly baseURL;
43
+ private readonly originator;
44
+ private readonly fetchImpl?;
45
+ constructor(options: CodexOAuthGatewayOptions);
46
+ /**
47
+ * Claims only ids carrying this gateway's own prefix. Bare `openai/*` ids
48
+ * keep routing to the API-key provider so both can coexist in one process.
49
+ */
50
+ handlesModel(modelId: string): boolean;
51
+ fetchProviders(): Promise<Record<string, ProviderConfig>>;
52
+ buildUrl(): Promise<string | undefined>;
53
+ getApiKey(): Promise<string>;
54
+ resolveLanguageModel(args: {
55
+ modelId: string;
56
+ providerId: string;
57
+ apiKey: string;
58
+ headers?: Record<string, string>;
59
+ }): Promise<GatewayLanguageModel>;
60
+ }
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CodexOAuthGateway = void 0;
4
+ exports.createCodexOAuthGateway = createCodexOAuthGateway;
5
+ const openai_1 = require("@ai-sdk/openai");
6
+ const llm_1 = require("@mastra/core/llm");
7
+ const auth_1 = require("./auth");
8
+ const models_1 = require("./models");
9
+ const oauth_fetch_1 = require("./oauth-fetch");
10
+ function createCodexOAuthGateway(options) {
11
+ return new CodexOAuthGateway(options);
12
+ }
13
+ class CodexOAuthGateway extends llm_1.MastraModelGateway {
14
+ id = models_1.CODEX_OAUTH_GATEWAY_ID;
15
+ name = 'Codex subscription (OAuth)';
16
+ models;
17
+ resolveCredentials;
18
+ liveCredentials;
19
+ oauthFetch;
20
+ baseURL;
21
+ originator;
22
+ fetchImpl;
23
+ constructor(options) {
24
+ super();
25
+ if (options?.acknowledgeCodexSubscription !== true) {
26
+ throw new Error('CodexOAuthGateway requires `acknowledgeCodexSubscription: true`. Subscription OAuth tokens are only for local Codex use; see the package README before enabling this.');
27
+ }
28
+ this.models = options.models?.length ? [...options.models] : models_1.DEFAULT_CODEX_OAUTH_MODELS;
29
+ this.resolveCredentials = options.resolveCredentials ?? auth_1.resolveCodexCredentials;
30
+ this.baseURL = options.baseURL ?? oauth_fetch_1.CODEX_RESPONSES_BASE_URL;
31
+ this.originator = options.originator;
32
+ this.fetchImpl = options.fetchImpl;
33
+ this.liveCredentials = (0, auth_1.createLiveCodexCredentialsResolver)({
34
+ resolveCredentials: () => this.resolveCredentials(),
35
+ fetchImpl: this.fetchImpl,
36
+ });
37
+ this.oauthFetch = (0, oauth_fetch_1.createCodexOAuthFetch)({
38
+ liveCredentials: this.liveCredentials,
39
+ originator: this.originator,
40
+ fetchImpl: this.fetchImpl,
41
+ });
42
+ }
43
+ /**
44
+ * Claims only ids carrying this gateway's own prefix. Bare `openai/*` ids
45
+ * keep routing to the API-key provider so both can coexist in one process.
46
+ */
47
+ handlesModel(modelId) {
48
+ return modelId.startsWith(`${this.id}/`);
49
+ }
50
+ async fetchProviders() {
51
+ return {
52
+ [models_1.CODEX_OAUTH_PROVIDER_ID]: {
53
+ name: 'OpenAI (Codex subscription)',
54
+ apiKeyEnvVar: auth_1.CODEX_OAUTH_ACCESS_TOKEN_ENV_VAR,
55
+ gateway: this.id,
56
+ models: [...this.models],
57
+ docUrl: 'https://developers.openai.com/codex/',
58
+ },
59
+ };
60
+ }
61
+ async buildUrl() {
62
+ // The OpenAI provider owns its own URL from `baseURL`; nothing to build here.
63
+ return undefined;
64
+ }
65
+ async getApiKey() {
66
+ const fresh = await this.liveCredentials.resolve();
67
+ return fresh.accessToken;
68
+ }
69
+ async resolveLanguageModel(args) {
70
+ if (args.providerId !== models_1.CODEX_OAUTH_PROVIDER_ID) {
71
+ throw new Error(`CodexOAuthGateway only supports providerId="${models_1.CODEX_OAUTH_PROVIDER_ID}" (got ${args.providerId})`);
72
+ }
73
+ const openai = (0, openai_1.createOpenAI)({
74
+ // The bearer token is applied by the fetch wrapper. This placeholder only
75
+ // satisfies the provider's required apiKey setting.
76
+ apiKey: 'codex-oauth',
77
+ baseURL: this.baseURL,
78
+ headers: args.headers,
79
+ // Prefer the live resolver over Mastra's captured `args.apiKey` so a
80
+ // rotated / refreshed token is used on the next request without
81
+ // re-resolving the model. The fetch instance is shared so concurrent
82
+ // requests dedupe token refresh.
83
+ fetch: this.oauthFetch,
84
+ });
85
+ // Default OpenAI factory uses the Responses API, which is what the Codex
86
+ // ChatGPT backend speaks (`…/codex/responses`).
87
+ return openai((0, models_1.stripCodexOAuthPrefix)(args.modelId));
88
+ }
89
+ }
90
+ exports.CodexOAuthGateway = CodexOAuthGateway;
91
+ //# sourceMappingURL=gateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.js","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export { CodexOAuthGateway, createCodexOAuthGateway } from './gateway';
2
+ export type { CodexOAuthGatewayOptions } from './gateway';
3
+ export { CODEX_OAUTH_GATEWAY_ID, CODEX_OAUTH_PROVIDER_ID, DEFAULT_CODEX_OAUTH_MODELS, } from './models';
4
+ export type { CodexOAuthModelId, CodexOAuthProviderId } from './models';
5
+ export { CODEX_OAUTH_ACCESS_TOKEN_ENV_VAR, CODEX_OAUTH_ACCOUNT_ID_ENV_VAR, CODEX_OAUTH_REFRESH_TOKEN_ENV_VAR, resolveCodexCredentials, } from './auth';
6
+ export type { CodexOAuthCredentials, CodexOAuthCredentialsResolver } from './auth';
7
+ export { CODEX_RESPONSES_BASE_URL } from './oauth-fetch';
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CODEX_RESPONSES_BASE_URL = exports.resolveCodexCredentials = exports.CODEX_OAUTH_REFRESH_TOKEN_ENV_VAR = exports.CODEX_OAUTH_ACCOUNT_ID_ENV_VAR = exports.CODEX_OAUTH_ACCESS_TOKEN_ENV_VAR = exports.DEFAULT_CODEX_OAUTH_MODELS = exports.CODEX_OAUTH_PROVIDER_ID = exports.CODEX_OAUTH_GATEWAY_ID = exports.createCodexOAuthGateway = exports.CodexOAuthGateway = void 0;
4
+ var gateway_1 = require("./gateway");
5
+ Object.defineProperty(exports, "CodexOAuthGateway", { enumerable: true, get: function () { return gateway_1.CodexOAuthGateway; } });
6
+ Object.defineProperty(exports, "createCodexOAuthGateway", { enumerable: true, get: function () { return gateway_1.createCodexOAuthGateway; } });
7
+ var models_1 = require("./models");
8
+ Object.defineProperty(exports, "CODEX_OAUTH_GATEWAY_ID", { enumerable: true, get: function () { return models_1.CODEX_OAUTH_GATEWAY_ID; } });
9
+ Object.defineProperty(exports, "CODEX_OAUTH_PROVIDER_ID", { enumerable: true, get: function () { return models_1.CODEX_OAUTH_PROVIDER_ID; } });
10
+ Object.defineProperty(exports, "DEFAULT_CODEX_OAUTH_MODELS", { enumerable: true, get: function () { return models_1.DEFAULT_CODEX_OAUTH_MODELS; } });
11
+ var auth_1 = require("./auth");
12
+ Object.defineProperty(exports, "CODEX_OAUTH_ACCESS_TOKEN_ENV_VAR", { enumerable: true, get: function () { return auth_1.CODEX_OAUTH_ACCESS_TOKEN_ENV_VAR; } });
13
+ Object.defineProperty(exports, "CODEX_OAUTH_ACCOUNT_ID_ENV_VAR", { enumerable: true, get: function () { return auth_1.CODEX_OAUTH_ACCOUNT_ID_ENV_VAR; } });
14
+ Object.defineProperty(exports, "CODEX_OAUTH_REFRESH_TOKEN_ENV_VAR", { enumerable: true, get: function () { return auth_1.CODEX_OAUTH_REFRESH_TOKEN_ENV_VAR; } });
15
+ Object.defineProperty(exports, "resolveCodexCredentials", { enumerable: true, get: function () { return auth_1.resolveCodexCredentials; } });
16
+ var oauth_fetch_1 = require("./oauth-fetch");
17
+ Object.defineProperty(exports, "CODEX_RESPONSES_BASE_URL", { enumerable: true, get: function () { return oauth_fetch_1.CODEX_RESPONSES_BASE_URL; } });
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qCAAuE;AAA9D,4GAAA,iBAAiB,OAAA;AAAE,kHAAA,uBAAuB,OAAA;AAEnD,mCAIkB;AAHhB,gHAAA,sBAAsB,OAAA;AACtB,iHAAA,uBAAuB,OAAA;AACvB,oHAAA,0BAA0B,OAAA;AAG5B,+BAKgB;AAJd,wHAAA,gCAAgC,OAAA;AAChC,sHAAA,8BAA8B,OAAA;AAC9B,yHAAA,iCAAiC,OAAA;AACjC,+GAAA,uBAAuB,OAAA;AAGzB,6CAAyD;AAAhD,uHAAA,wBAAwB,OAAA"}
@@ -0,0 +1,16 @@
1
+ export declare const CODEX_OAUTH_GATEWAY_ID: "codex-oauth";
2
+ export declare const CODEX_OAUTH_PROVIDER_ID: "openai";
3
+ export type CodexOAuthProviderId = typeof CODEX_OAUTH_PROVIDER_ID;
4
+ export type CodexOAuthModelId = string;
5
+ /**
6
+ * Advertised catalog. Static so model resolution needs no network call at boot.
7
+ * Override with `createCodexOAuthGateway({ models })` to track new releases
8
+ * without waiting for a package bump.
9
+ */
10
+ export declare const DEFAULT_CODEX_OAUTH_MODELS: ReadonlyArray<CodexOAuthModelId>;
11
+ /**
12
+ * Strips a `codex-oauth/` or `codex-oauth/openai/` prefix from a router id.
13
+ * Mastra hands `resolveLanguageModel` the full router id, while the OpenAI
14
+ * provider expects the bare model id.
15
+ */
16
+ export declare function stripCodexOAuthPrefix(modelId: string): CodexOAuthModelId;
package/dist/models.js ADDED
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CODEX_OAUTH_MODELS = exports.CODEX_OAUTH_PROVIDER_ID = exports.CODEX_OAUTH_GATEWAY_ID = void 0;
4
+ exports.stripCodexOAuthPrefix = stripCodexOAuthPrefix;
5
+ exports.CODEX_OAUTH_GATEWAY_ID = 'codex-oauth';
6
+ exports.CODEX_OAUTH_PROVIDER_ID = 'openai';
7
+ /**
8
+ * Advertised catalog. Static so model resolution needs no network call at boot.
9
+ * Override with `createCodexOAuthGateway({ models })` to track new releases
10
+ * without waiting for a package bump.
11
+ */
12
+ exports.DEFAULT_CODEX_OAUTH_MODELS = [
13
+ 'gpt-5.6-sol',
14
+ 'gpt-5.6-terra',
15
+ 'gpt-5.6-luna',
16
+ 'gpt-5.5',
17
+ 'gpt-5.4',
18
+ 'gpt-5.4-mini',
19
+ ];
20
+ /**
21
+ * Strips a `codex-oauth/` or `codex-oauth/openai/` prefix from a router id.
22
+ * Mastra hands `resolveLanguageModel` the full router id, while the OpenAI
23
+ * provider expects the bare model id.
24
+ */
25
+ function stripCodexOAuthPrefix(modelId) {
26
+ const trimmed = modelId.trim();
27
+ const gatewayPrefix = `${exports.CODEX_OAUTH_GATEWAY_ID}/`;
28
+ const withoutGateway = trimmed.startsWith(gatewayPrefix)
29
+ ? trimmed.slice(gatewayPrefix.length)
30
+ : trimmed;
31
+ const providerPrefix = `${exports.CODEX_OAUTH_PROVIDER_ID}/`;
32
+ return withoutGateway.startsWith(providerPrefix)
33
+ ? withoutGateway.slice(providerPrefix.length)
34
+ : withoutGateway;
35
+ }
36
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":";;;AA2BA,sDAWC;AAtCY,QAAA,sBAAsB,GAAG,aAAsB,CAAC;AAEhD,QAAA,uBAAuB,GAAG,QAAiB,CAAC;AAMzD;;;;GAIG;AACU,QAAA,0BAA0B,GAAqC;IAC1E,aAAa;IACb,eAAe;IACf,cAAc;IACd,SAAS;IACT,SAAS;IACT,cAAc;CACf,CAAC;AAEF;;;;GAIG;AACH,SAAgB,qBAAqB,CAAC,OAAe;IACnD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAC/B,MAAM,aAAa,GAAG,GAAG,8BAAsB,GAAG,CAAC;IACnD,MAAM,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC;QACtD,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC;QACrC,CAAC,CAAC,OAAO,CAAC;IAEZ,MAAM,cAAc,GAAG,GAAG,+BAAuB,GAAG,CAAC;IACrD,OAAO,cAAc,CAAC,UAAU,CAAC,cAAc,CAAC;QAC9C,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC;QAC7C,CAAC,CAAC,cAAc,CAAC;AACrB,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Rewrites OpenAI Responses API requests so they authenticate against the
3
+ * ChatGPT Codex subscription backend instead of the metered Platform API.
4
+ *
5
+ * Deltas versus a normal API-key request:
6
+ * 1. `Authorization: Bearer <oauth>` plus `ChatGPT-Account-Id`.
7
+ * 2. `OpenAI-Beta: responses=experimental` and a Codex `originator`.
8
+ * 3. Body normalization: `store` defaults to false, `max_output_tokens` is
9
+ * dropped (Codex rejects it), and `system` roles become `developer`.
10
+ */
11
+ import { type CodexOAuthCredentialsResolver, type LiveCodexCredentialsResolver } from './auth';
12
+ export declare const CODEX_RESPONSES_BASE_URL = "https://chatgpt.com/backend-api/codex";
13
+ export declare const CODEX_OAUTH_ORIGINATOR = "codex_cli_rs";
14
+ export declare const CODEX_OAUTH_BETA = "responses=experimental";
15
+ export type CreateCodexOAuthFetchOptions = {
16
+ /** Resolved per request so a rotated / refreshed token is picked up live. */
17
+ resolveCredentials?: CodexOAuthCredentialsResolver;
18
+ /**
19
+ * Shared live credential cache (refresh dedup + in-memory reuse). When set,
20
+ * takes precedence over `resolveCredentials`.
21
+ */
22
+ liveCredentials?: LiveCodexCredentialsResolver;
23
+ /** Upstream `originator` header. Pass `false` to omit. */
24
+ originator?: string | false;
25
+ /** Defaults to global `fetch`. Injected in tests. */
26
+ fetchImpl?: typeof fetch;
27
+ };
28
+ /**
29
+ * Codex Responses body tweaks required for subscription auth compatibility.
30
+ * Exported for tests.
31
+ */
32
+ export declare function normalizeCodexResponsesBody(body: Record<string, unknown>): Record<string, unknown>;
33
+ export declare function createCodexOAuthFetch(options?: CreateCodexOAuthFetchOptions): typeof fetch;
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ /**
3
+ * Rewrites OpenAI Responses API requests so they authenticate against the
4
+ * ChatGPT Codex subscription backend instead of the metered Platform API.
5
+ *
6
+ * Deltas versus a normal API-key request:
7
+ * 1. `Authorization: Bearer <oauth>` plus `ChatGPT-Account-Id`.
8
+ * 2. `OpenAI-Beta: responses=experimental` and a Codex `originator`.
9
+ * 3. Body normalization: `store` defaults to false, `max_output_tokens` is
10
+ * dropped (Codex rejects it), and `system` roles become `developer`.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.CODEX_OAUTH_BETA = exports.CODEX_OAUTH_ORIGINATOR = exports.CODEX_RESPONSES_BASE_URL = void 0;
14
+ exports.normalizeCodexResponsesBody = normalizeCodexResponsesBody;
15
+ exports.createCodexOAuthFetch = createCodexOAuthFetch;
16
+ const auth_1 = require("./auth");
17
+ exports.CODEX_RESPONSES_BASE_URL = 'https://chatgpt.com/backend-api/codex';
18
+ exports.CODEX_OAUTH_ORIGINATOR = 'codex_cli_rs';
19
+ exports.CODEX_OAUTH_BETA = 'responses=experimental';
20
+ function normalizeHeaders(headers) {
21
+ const normalized = {};
22
+ if (!headers)
23
+ return normalized;
24
+ if (typeof Headers !== 'undefined' && headers instanceof Headers) {
25
+ headers.forEach((value, key) => {
26
+ normalized[key] = value;
27
+ });
28
+ return normalized;
29
+ }
30
+ if (Array.isArray(headers)) {
31
+ for (const [key, value] of headers) {
32
+ if (value !== undefined)
33
+ normalized[key] = value;
34
+ }
35
+ return normalized;
36
+ }
37
+ for (const [key, value] of Object.entries(headers)) {
38
+ if (value !== undefined)
39
+ normalized[key] = value;
40
+ }
41
+ return normalized;
42
+ }
43
+ function deleteHeader(headers, name) {
44
+ const lower = name.toLowerCase();
45
+ for (const key of Object.keys(headers)) {
46
+ if (key.toLowerCase() === lower)
47
+ delete headers[key];
48
+ }
49
+ }
50
+ function isRecord(value) {
51
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
52
+ }
53
+ /**
54
+ * Codex Responses body tweaks required for subscription auth compatibility.
55
+ * Exported for tests.
56
+ */
57
+ function normalizeCodexResponsesBody(body) {
58
+ const normalized = { ...body };
59
+ if (normalized.store === undefined) {
60
+ normalized.store = false;
61
+ }
62
+ delete normalized.max_output_tokens;
63
+ if (Array.isArray(normalized.input)) {
64
+ normalized.input = normalized.input.map((item) => {
65
+ if (!isRecord(item) || item.role !== 'system')
66
+ return item;
67
+ return { ...item, role: 'developer' };
68
+ });
69
+ }
70
+ return normalized;
71
+ }
72
+ function applyCredentialHeaders(headers, credentials, originator) {
73
+ deleteHeader(headers, 'authorization');
74
+ deleteHeader(headers, 'chatgpt-account-id');
75
+ deleteHeader(headers, 'openai-beta');
76
+ deleteHeader(headers, 'originator');
77
+ headers.authorization = `Bearer ${credentials.accessToken}`;
78
+ headers['ChatGPT-Account-Id'] = credentials.accountId;
79
+ headers['OpenAI-Beta'] = exports.CODEX_OAUTH_BETA;
80
+ if (originator)
81
+ headers.originator = originator;
82
+ }
83
+ function createCodexOAuthFetch(options = {}) {
84
+ const doFetch = options.fetchImpl ?? globalThis.fetch;
85
+ const originator = options.originator === false ? undefined : (options.originator ?? exports.CODEX_OAUTH_ORIGINATOR);
86
+ const liveCredentials = options.liveCredentials ??
87
+ (0, auth_1.createLiveCodexCredentialsResolver)({
88
+ resolveCredentials: options.resolveCredentials,
89
+ fetchImpl: doFetch,
90
+ });
91
+ return async function codexOAuthFetch(input, init) {
92
+ const credentials = await liveCredentials.resolve();
93
+ const rawBody = init?.body;
94
+ let body = rawBody;
95
+ if (typeof rawBody === 'string') {
96
+ try {
97
+ const parsed = JSON.parse(rawBody);
98
+ if (isRecord(parsed)) {
99
+ body = JSON.stringify(normalizeCodexResponsesBody(parsed));
100
+ }
101
+ }
102
+ catch {
103
+ // Non-JSON body: forward unchanged.
104
+ }
105
+ }
106
+ else if (rawBody != null) {
107
+ // The OpenAI Responses provider always sends a JSON string body. Anything
108
+ // else means the provider changed shape and body normalization would
109
+ // silently go missing.
110
+ throw new Error('Unexpected OpenAI request body: expected a JSON string. Codex OAuth requests cannot be rewritten.');
111
+ }
112
+ const headers = normalizeHeaders(init?.headers);
113
+ applyCredentialHeaders(headers, credentials, originator);
114
+ const response = await doFetch(input, { ...init, headers, body });
115
+ if (response.status !== 401)
116
+ return response;
117
+ // Opaque / unexpectedly expired access tokens never trip the proactive
118
+ // margin check — force one refresh and retry once.
119
+ let refreshed;
120
+ try {
121
+ refreshed = await liveCredentials.resolve({ forceRefresh: true });
122
+ }
123
+ catch {
124
+ return response;
125
+ }
126
+ if (refreshed.accessToken === credentials.accessToken) {
127
+ return response;
128
+ }
129
+ const retryHeaders = normalizeHeaders(init?.headers);
130
+ applyCredentialHeaders(retryHeaders, refreshed, originator);
131
+ return doFetch(input, { ...init, headers: retryHeaders, body });
132
+ };
133
+ }
134
+ //# sourceMappingURL=oauth-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-fetch.js","sourceRoot":"","sources":["../src/oauth-fetch.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@lumifai/harness-model-codex-oauth",
3
+ "version": "0.1.0",
4
+ "license": "UNLICENSED",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/Lumif-ai/lumif-harness.git",
8
+ "directory": "packages/model-providers/codex-oauth"
9
+ },
10
+ "engines": {
11
+ "node": ">=22.13.0"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js",
17
+ "require": "./dist/index.js",
18
+ "default": "./dist/index.js"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "main": "dist/index.js",
23
+ "types": "dist/index.d.ts",
24
+ "files": [
25
+ "dist",
26
+ "!dist/**/*.test.*",
27
+ "README.md"
28
+ ],
29
+ "dependencies": {
30
+ "@ai-sdk/openai": "^4.0.57",
31
+ "@mastra/core": "1.67.0"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public",
35
+ "registry": "https://registry.npmjs.org/"
36
+ },
37
+ "scripts": {
38
+ "build": "tsc -p tsconfig.json"
39
+ }
40
+ }