openai-codex-oauth 0.0.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/LICENSE +21 -0
- package/NOTICE +27 -0
- package/README.md +214 -0
- package/dist/chunk-DXYYRLYI.js +526 -0
- package/dist/chunk-DXYYRLYI.js.map +1 -0
- package/dist/chunk-SCKIRN2D.js +58 -0
- package/dist/chunk-SCKIRN2D.js.map +1 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.js +242 -0
- package/dist/index.js.map +1 -0
- package/dist/node/index.d.ts +38 -0
- package/dist/node/index.js +106 -0
- package/dist/node/index.js.map +1 -0
- package/dist/proxy.d.ts +24 -0
- package/dist/proxy.js +8 -0
- package/dist/proxy.js.map +1 -0
- package/dist/types-Bv0Lf2Og.d.ts +107 -0
- package/package.json +77 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deriveAccountId,
|
|
3
|
+
deriveExpiresAt
|
|
4
|
+
} from "./chunk-SCKIRN2D.js";
|
|
5
|
+
|
|
6
|
+
// src/types.ts
|
|
7
|
+
var OpenAIOAuthError = class extends Error {
|
|
8
|
+
code;
|
|
9
|
+
constructor(code, message, options) {
|
|
10
|
+
super(message, options);
|
|
11
|
+
this.name = "OpenAIOAuthError";
|
|
12
|
+
this.code = code;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/device-flow.ts
|
|
17
|
+
var DEFAULT_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
18
|
+
var DEFAULT_ISSUER = "https://auth.openai.com";
|
|
19
|
+
var POLL_BUFFER_MS = 3e3;
|
|
20
|
+
function pickFetch(customFetch) {
|
|
21
|
+
if (typeof customFetch === "function") {
|
|
22
|
+
return customFetch;
|
|
23
|
+
}
|
|
24
|
+
if (typeof globalThis.fetch === "function") {
|
|
25
|
+
return globalThis.fetch.bind(globalThis);
|
|
26
|
+
}
|
|
27
|
+
throw new OpenAIOAuthError("fetch_required", "A fetch implementation is required for OpenAI OAuth.");
|
|
28
|
+
}
|
|
29
|
+
async function startOpenAIDeviceFlow(options = {}) {
|
|
30
|
+
const fetch = pickFetch(options.fetch);
|
|
31
|
+
const issuer = (options.issuer ?? DEFAULT_ISSUER).replace(/\/$/, "");
|
|
32
|
+
const clientId = options.clientId ?? DEFAULT_CLIENT_ID;
|
|
33
|
+
const now = options.now ?? (() => Date.now());
|
|
34
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
35
|
+
const codeResponse = await fetch(`${issuer}/api/accounts/deviceauth/usercode`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: {
|
|
38
|
+
"Content-Type": "application/json"
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify({ client_id: clientId })
|
|
41
|
+
});
|
|
42
|
+
if (!codeResponse.ok) {
|
|
43
|
+
throw new OpenAIOAuthError("auth_failed", "Failed to initiate OpenAI authorization.");
|
|
44
|
+
}
|
|
45
|
+
const device = await codeResponse.json();
|
|
46
|
+
if (!device.device_auth_id || !device.user_code) {
|
|
47
|
+
throw new OpenAIOAuthError("auth_failed", "OpenAI authorization response did not include a device code.");
|
|
48
|
+
}
|
|
49
|
+
const intervalSeconds = typeof device.interval === "number" ? device.interval : parseInt(device.interval ?? "5", 10);
|
|
50
|
+
const intervalMs = Math.max(Number.isFinite(intervalSeconds) ? intervalSeconds : 5, 1) * 1e3;
|
|
51
|
+
return {
|
|
52
|
+
providerId: "openai",
|
|
53
|
+
url: `${issuer}/codex/device`,
|
|
54
|
+
code: device.user_code,
|
|
55
|
+
instructions: `Enter code: ${device.user_code}`,
|
|
56
|
+
async complete() {
|
|
57
|
+
while (true) {
|
|
58
|
+
const poll = await fetch(`${issuer}/api/accounts/deviceauth/token`, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: {
|
|
61
|
+
"Content-Type": "application/json"
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
device_auth_id: device.device_auth_id,
|
|
65
|
+
user_code: device.user_code
|
|
66
|
+
})
|
|
67
|
+
});
|
|
68
|
+
if (poll.ok) {
|
|
69
|
+
const grant = await poll.json();
|
|
70
|
+
if (!grant.authorization_code || !grant.code_verifier) {
|
|
71
|
+
throw new OpenAIOAuthError("auth_failed", "OpenAI authorization grant was incomplete.");
|
|
72
|
+
}
|
|
73
|
+
const tokens = await exchangeAuthorizationCode({
|
|
74
|
+
fetch,
|
|
75
|
+
issuer,
|
|
76
|
+
clientId,
|
|
77
|
+
authorizationCode: grant.authorization_code,
|
|
78
|
+
codeVerifier: grant.code_verifier,
|
|
79
|
+
now
|
|
80
|
+
});
|
|
81
|
+
await options.tokenStore?.save(tokens);
|
|
82
|
+
return tokens;
|
|
83
|
+
}
|
|
84
|
+
if (poll.status !== 403 && poll.status !== 404) {
|
|
85
|
+
throw new OpenAIOAuthError("auth_failed", "OpenAI OAuth authorization failed.");
|
|
86
|
+
}
|
|
87
|
+
await sleep(intervalMs + POLL_BUFFER_MS);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function exchangeAuthorizationCode({
|
|
93
|
+
fetch,
|
|
94
|
+
issuer,
|
|
95
|
+
clientId,
|
|
96
|
+
authorizationCode,
|
|
97
|
+
codeVerifier,
|
|
98
|
+
now
|
|
99
|
+
}) {
|
|
100
|
+
const response = await fetch(`${issuer}/oauth/token`, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: {
|
|
103
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
104
|
+
},
|
|
105
|
+
body: new URLSearchParams({
|
|
106
|
+
grant_type: "authorization_code",
|
|
107
|
+
code: authorizationCode,
|
|
108
|
+
redirect_uri: `${issuer}/deviceauth/callback`,
|
|
109
|
+
client_id: clientId,
|
|
110
|
+
code_verifier: codeVerifier
|
|
111
|
+
}).toString()
|
|
112
|
+
});
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
throw new OpenAIOAuthError("auth_failed", "OpenAI token exchange failed.");
|
|
115
|
+
}
|
|
116
|
+
const token = await response.json();
|
|
117
|
+
if (!token.access_token) {
|
|
118
|
+
throw new OpenAIOAuthError("auth_failed", "OpenAI token exchange did not return an access token.");
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
refreshToken: token.refresh_token,
|
|
122
|
+
accessToken: token.access_token,
|
|
123
|
+
idToken: token.id_token,
|
|
124
|
+
expiresAt: now() + (token.expires_in ?? 3600) * 1e3,
|
|
125
|
+
accountId: deriveAccountId(token.id_token, token.access_token)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async function refreshOpenAITokens({
|
|
129
|
+
tokens,
|
|
130
|
+
fetch,
|
|
131
|
+
clientId = DEFAULT_CLIENT_ID,
|
|
132
|
+
issuer = DEFAULT_ISSUER,
|
|
133
|
+
tokenUrl,
|
|
134
|
+
now = () => Date.now()
|
|
135
|
+
}) {
|
|
136
|
+
if (!tokens.refreshToken) {
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
const resolvedIssuer = issuer.replace(/\/$/, "");
|
|
140
|
+
const response = await fetch(tokenUrl ?? `${resolvedIssuer}/oauth/token`, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: {
|
|
143
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
144
|
+
},
|
|
145
|
+
body: new URLSearchParams({
|
|
146
|
+
grant_type: "refresh_token",
|
|
147
|
+
refresh_token: tokens.refreshToken,
|
|
148
|
+
client_id: clientId,
|
|
149
|
+
scope: "openid profile email offline_access"
|
|
150
|
+
}).toString()
|
|
151
|
+
});
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
return void 0;
|
|
154
|
+
}
|
|
155
|
+
const payload = await response.json();
|
|
156
|
+
if (!payload.access_token) {
|
|
157
|
+
return void 0;
|
|
158
|
+
}
|
|
159
|
+
const next = {
|
|
160
|
+
accessToken: payload.access_token,
|
|
161
|
+
refreshToken: payload.refresh_token ?? tokens.refreshToken,
|
|
162
|
+
idToken: payload.id_token ?? tokens.idToken,
|
|
163
|
+
expiresAt: now() + (payload.expires_in ?? 3600) * 1e3
|
|
164
|
+
};
|
|
165
|
+
next.accountId = deriveAccountId(next.idToken, next.accessToken) ?? tokens.accountId;
|
|
166
|
+
return next;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/memory-token-store.ts
|
|
170
|
+
function createMemoryTokenStore(initial) {
|
|
171
|
+
let current = initial;
|
|
172
|
+
return {
|
|
173
|
+
async load() {
|
|
174
|
+
return current;
|
|
175
|
+
},
|
|
176
|
+
async save(tokens) {
|
|
177
|
+
current = tokens;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/codex-fetch.ts
|
|
183
|
+
var DEFAULT_CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex";
|
|
184
|
+
var DEFAULT_BROWSER_PROXY_BASE_URL = "/api/proxy/openai/codex";
|
|
185
|
+
var DEFAULT_REFRESH_MARGIN_MS = 5 * 60 * 1e3;
|
|
186
|
+
var DEFAULT_INSTRUCTIONS = "";
|
|
187
|
+
var DEFAULT_ORIGINATOR = "openai-codex-oauth";
|
|
188
|
+
function pickFetch2(customFetch) {
|
|
189
|
+
if (typeof customFetch === "function") {
|
|
190
|
+
return customFetch;
|
|
191
|
+
}
|
|
192
|
+
if (typeof globalThis.fetch === "function") {
|
|
193
|
+
return globalThis.fetch.bind(globalThis);
|
|
194
|
+
}
|
|
195
|
+
throw new OpenAIOAuthError("fetch_required", "A fetch implementation is required for OpenAI OAuth.");
|
|
196
|
+
}
|
|
197
|
+
function withoutTrailingSlash(value) {
|
|
198
|
+
return value.replace(/\/$/, "");
|
|
199
|
+
}
|
|
200
|
+
function resolveBaseURL(baseURL) {
|
|
201
|
+
return withoutTrailingSlash(baseURL ?? DEFAULT_CODEX_BASE_URL);
|
|
202
|
+
}
|
|
203
|
+
function createStore(settings) {
|
|
204
|
+
if (settings.tokenStore) {
|
|
205
|
+
return settings.tokenStore;
|
|
206
|
+
}
|
|
207
|
+
if (settings.tokens) {
|
|
208
|
+
return createMemoryTokenStore(settings.tokens);
|
|
209
|
+
}
|
|
210
|
+
throw new OpenAIOAuthError(
|
|
211
|
+
"tokens_required",
|
|
212
|
+
"OpenAI OAuth tokens are required. Pass `tokens`, `tokenStore`, or use `createCodexAuthFileStore` from `@tolksyn/openai-oauth/node`."
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
function needsRefresh(tokens, now, marginMs) {
|
|
216
|
+
const expiresAt = tokens.expiresAt ?? deriveExpiresAt(tokens.accessToken);
|
|
217
|
+
if (!tokens.accessToken) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
if (expiresAt == null || expiresAt <= 0) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
return expiresAt <= now + marginMs;
|
|
224
|
+
}
|
|
225
|
+
function hasExpired(tokens, now) {
|
|
226
|
+
const expiresAt = tokens.expiresAt ?? deriveExpiresAt(tokens.accessToken);
|
|
227
|
+
return expiresAt != null && expiresAt > 0 && expiresAt <= now;
|
|
228
|
+
}
|
|
229
|
+
var TokenManager = class {
|
|
230
|
+
constructor(settings, store, fetch) {
|
|
231
|
+
this.settings = settings;
|
|
232
|
+
this.store = store;
|
|
233
|
+
this.fetch = fetch;
|
|
234
|
+
}
|
|
235
|
+
settings;
|
|
236
|
+
store;
|
|
237
|
+
fetch;
|
|
238
|
+
/** Promise for an in-flight token refresh to coalesce concurrent requests. */
|
|
239
|
+
inflight;
|
|
240
|
+
/** Cached current tokens to avoid redundant store loads. */
|
|
241
|
+
current;
|
|
242
|
+
/**
|
|
243
|
+
* Gets valid OAuth tokens, triggering refresh if needed.
|
|
244
|
+
* Coalesces concurrent requests to avoid duplicate refresh calls.
|
|
245
|
+
*/
|
|
246
|
+
async get() {
|
|
247
|
+
if (this.inflight) {
|
|
248
|
+
return this.inflight;
|
|
249
|
+
}
|
|
250
|
+
this.inflight = this.loadFresh().then((tokens) => {
|
|
251
|
+
this.current = tokens;
|
|
252
|
+
this.inflight = void 0;
|
|
253
|
+
return tokens;
|
|
254
|
+
}).catch((error) => {
|
|
255
|
+
this.inflight = void 0;
|
|
256
|
+
throw error;
|
|
257
|
+
});
|
|
258
|
+
return this.inflight;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Loads fresh tokens, optionally refreshing if expired or near expiry.
|
|
262
|
+
* Derives account ID from JWT claims if not explicitly provided.
|
|
263
|
+
*/
|
|
264
|
+
async loadFresh() {
|
|
265
|
+
let tokens = this.current ?? await this.store.load();
|
|
266
|
+
if (!tokens?.accessToken) {
|
|
267
|
+
throw new OpenAIOAuthError("auth_failed", "OpenAI OAuth access token is missing.");
|
|
268
|
+
}
|
|
269
|
+
const now = this.settings.now?.() ?? Date.now();
|
|
270
|
+
const refreshMarginMs = this.settings.refreshMarginMs ?? DEFAULT_REFRESH_MARGIN_MS;
|
|
271
|
+
if (needsRefresh(tokens, now, refreshMarginMs)) {
|
|
272
|
+
const refreshed = await refreshOpenAITokens({
|
|
273
|
+
tokens,
|
|
274
|
+
fetch: this.fetch,
|
|
275
|
+
clientId: this.settings.clientId,
|
|
276
|
+
issuer: this.settings.issuer,
|
|
277
|
+
tokenUrl: this.settings.tokenUrl,
|
|
278
|
+
now: this.settings.now
|
|
279
|
+
});
|
|
280
|
+
if (refreshed) {
|
|
281
|
+
tokens = refreshed;
|
|
282
|
+
await this.store.save(tokens);
|
|
283
|
+
await this.settings.onTokens?.(tokens);
|
|
284
|
+
} else if (hasExpired(tokens, now)) {
|
|
285
|
+
throw new OpenAIOAuthError("auth_failed", "OpenAI OAuth access token expired and refresh failed.");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const accountId = tokens.accountId ?? deriveAccountId(tokens.idToken, tokens.accessToken);
|
|
289
|
+
if (!accountId) {
|
|
290
|
+
throw new OpenAIOAuthError(
|
|
291
|
+
"account_id_missing",
|
|
292
|
+
"OpenAI OAuth account id is missing. Store `accountId` with the tokens or include an id_token with the ChatGPT account claim."
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
...tokens,
|
|
297
|
+
accountId
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
function resolveTargetUrl(input, baseURL) {
|
|
302
|
+
const base = new URL(baseURL);
|
|
303
|
+
const parsed = /^https?:\/\//.test(input) ? new URL(input) : new URL(input, "https://codex.invalid");
|
|
304
|
+
let pathname = parsed.pathname;
|
|
305
|
+
const basePath = withoutTrailingSlash(base.pathname);
|
|
306
|
+
if (pathname === basePath) {
|
|
307
|
+
pathname = "/";
|
|
308
|
+
} else if (basePath && pathname.startsWith(`${basePath}/`)) {
|
|
309
|
+
pathname = pathname.slice(basePath.length);
|
|
310
|
+
}
|
|
311
|
+
if (pathname === "/v1") {
|
|
312
|
+
pathname = "/";
|
|
313
|
+
} else if (pathname.startsWith("/v1/")) {
|
|
314
|
+
pathname = pathname.slice(3);
|
|
315
|
+
}
|
|
316
|
+
if (!pathname.startsWith("/")) {
|
|
317
|
+
pathname = `/${pathname}`;
|
|
318
|
+
}
|
|
319
|
+
return `${base.origin}${basePath}${pathname}${parsed.search}`;
|
|
320
|
+
}
|
|
321
|
+
function browserOrigin() {
|
|
322
|
+
const maybeWindow = globalThis.window;
|
|
323
|
+
return typeof maybeWindow?.location?.origin === "string" ? maybeWindow.location.origin.replace(/\/$/, "") : void 0;
|
|
324
|
+
}
|
|
325
|
+
function resolveBrowserProxyUrl(target, baseURL, settings) {
|
|
326
|
+
const origin = browserOrigin();
|
|
327
|
+
if (!origin || settings.browserProxyBaseUrl === false) {
|
|
328
|
+
return void 0;
|
|
329
|
+
}
|
|
330
|
+
const proxyBase = settings.browserProxyBaseUrl ?? DEFAULT_BROWSER_PROXY_BASE_URL;
|
|
331
|
+
const absoluteProxyBase = /^https?:\/\//.test(proxyBase) ? proxyBase.replace(/\/$/, "") : `${origin}${proxyBase.startsWith("/") ? "" : "/"}${proxyBase}`.replace(/\/$/, "");
|
|
332
|
+
const upstreamBasePath = withoutTrailingSlash(new URL(baseURL).pathname);
|
|
333
|
+
let pathname = target.pathname;
|
|
334
|
+
if (upstreamBasePath && pathname.startsWith(`${upstreamBasePath}/`)) {
|
|
335
|
+
pathname = pathname.slice(upstreamBasePath.length);
|
|
336
|
+
}
|
|
337
|
+
return `${absoluteProxyBase}${pathname}${target.search}`;
|
|
338
|
+
}
|
|
339
|
+
async function readRequestParts(input, init) {
|
|
340
|
+
if (input instanceof Request) {
|
|
341
|
+
const headers = new Headers(input.headers);
|
|
342
|
+
if (init?.headers) {
|
|
343
|
+
new Headers(init.headers).forEach((value, key) => headers.set(key, value));
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
url: input.url,
|
|
347
|
+
method: init?.method ?? input.method,
|
|
348
|
+
headers,
|
|
349
|
+
body: init?.body ?? (input.body == null ? void 0 : await input.clone().text()),
|
|
350
|
+
signal: init?.signal ?? input.signal
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
url: String(input),
|
|
355
|
+
method: init?.method,
|
|
356
|
+
headers: new Headers(init?.headers),
|
|
357
|
+
body: init?.body,
|
|
358
|
+
signal: init?.signal
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
async function decodeBody(body) {
|
|
362
|
+
if (body == null) {
|
|
363
|
+
return void 0;
|
|
364
|
+
}
|
|
365
|
+
if (typeof body === "string") {
|
|
366
|
+
return body;
|
|
367
|
+
}
|
|
368
|
+
if (body instanceof URLSearchParams || body instanceof FormData || body instanceof ReadableStream) {
|
|
369
|
+
return void 0;
|
|
370
|
+
}
|
|
371
|
+
if (body instanceof Blob) {
|
|
372
|
+
return body.text();
|
|
373
|
+
}
|
|
374
|
+
if (body instanceof ArrayBuffer) {
|
|
375
|
+
return new TextDecoder().decode(body);
|
|
376
|
+
}
|
|
377
|
+
if (ArrayBuffer.isView(body)) {
|
|
378
|
+
return new TextDecoder().decode(body);
|
|
379
|
+
}
|
|
380
|
+
return void 0;
|
|
381
|
+
}
|
|
382
|
+
function normalizeCodexResponsesBody(body, options = {}) {
|
|
383
|
+
const normalized = { ...body };
|
|
384
|
+
if (typeof normalized.instructions !== "string") {
|
|
385
|
+
normalized.instructions = options.instructions ?? DEFAULT_INSTRUCTIONS;
|
|
386
|
+
}
|
|
387
|
+
if (normalized.store === void 0) {
|
|
388
|
+
normalized.store = options.store ?? false;
|
|
389
|
+
}
|
|
390
|
+
delete normalized.max_output_tokens;
|
|
391
|
+
return normalized;
|
|
392
|
+
}
|
|
393
|
+
async function prepareBody(pathname, headers, body, settings) {
|
|
394
|
+
if (!pathname.endsWith("/responses")) {
|
|
395
|
+
return body;
|
|
396
|
+
}
|
|
397
|
+
const contentType = headers.get("content-type");
|
|
398
|
+
if (contentType && !contentType.includes("application/json")) {
|
|
399
|
+
return body;
|
|
400
|
+
}
|
|
401
|
+
const bodyText = await decodeBody(body);
|
|
402
|
+
if (typeof bodyText !== "string") {
|
|
403
|
+
return body;
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
const parsed = JSON.parse(bodyText);
|
|
407
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
408
|
+
return body;
|
|
409
|
+
}
|
|
410
|
+
return JSON.stringify(normalizeCodexResponsesBody(parsed, settings));
|
|
411
|
+
} catch {
|
|
412
|
+
return body;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function createCodexOAuthFetch(settings = {}) {
|
|
416
|
+
const fetch = pickFetch2(settings.fetch);
|
|
417
|
+
const store = createStore(settings);
|
|
418
|
+
const manager = new TokenManager(settings, store, fetch);
|
|
419
|
+
const baseURL = resolveBaseURL(settings.baseURL);
|
|
420
|
+
return async (input, init) => {
|
|
421
|
+
const request = await readRequestParts(input, init);
|
|
422
|
+
const targetUrl = resolveTargetUrl(request.url, baseURL);
|
|
423
|
+
const target = new URL(targetUrl);
|
|
424
|
+
const tokens = await manager.get();
|
|
425
|
+
const headers = new Headers(settings.headers);
|
|
426
|
+
request.headers.forEach((value, key) => headers.set(key, value));
|
|
427
|
+
headers.delete("authorization");
|
|
428
|
+
headers.delete("chatgpt-account-id");
|
|
429
|
+
headers.delete("openai-beta");
|
|
430
|
+
headers.set("Authorization", `Bearer ${tokens.accessToken}`);
|
|
431
|
+
headers.set("ChatGPT-Account-Id", tokens.accountId);
|
|
432
|
+
headers.set("OpenAI-Beta", "responses=experimental");
|
|
433
|
+
if (settings.originator !== false && !headers.has("originator")) {
|
|
434
|
+
headers.set("originator", settings.originator ?? DEFAULT_ORIGINATOR);
|
|
435
|
+
}
|
|
436
|
+
const body = await prepareBody(target.pathname, headers, request.body, settings);
|
|
437
|
+
return fetch(resolveBrowserProxyUrl(target, baseURL, settings) ?? target.toString(), {
|
|
438
|
+
method: request.method ?? init?.method,
|
|
439
|
+
headers,
|
|
440
|
+
body,
|
|
441
|
+
signal: request.signal ?? void 0
|
|
442
|
+
});
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function createCodexOAuthClient(settings = {}) {
|
|
446
|
+
const baseURL = resolveBaseURL(settings.baseURL);
|
|
447
|
+
const fetch = createCodexOAuthFetch(settings);
|
|
448
|
+
return {
|
|
449
|
+
baseURL,
|
|
450
|
+
fetch,
|
|
451
|
+
request: (path, init) => fetch(resolveTargetUrl(path, baseURL), init)
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// src/proxy.ts
|
|
456
|
+
function pickFetch3(customFetch) {
|
|
457
|
+
if (typeof customFetch === "function") return customFetch;
|
|
458
|
+
if (typeof globalThis.fetch === "function") return globalThis.fetch.bind(globalThis);
|
|
459
|
+
throw new Error("A fetch implementation is required for OpenAI Codex proxy handlers.");
|
|
460
|
+
}
|
|
461
|
+
function noStoreText(text, status) {
|
|
462
|
+
return new Response(text, {
|
|
463
|
+
status,
|
|
464
|
+
headers: {
|
|
465
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
466
|
+
"Cache-Control": "no-store"
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
async function requestBody(request, options) {
|
|
471
|
+
const text = await request.text();
|
|
472
|
+
if (!text.trim()) return text;
|
|
473
|
+
try {
|
|
474
|
+
const parsed = JSON.parse(text);
|
|
475
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return text;
|
|
476
|
+
return JSON.stringify(normalizeCodexResponsesBody(parsed, options));
|
|
477
|
+
} catch {
|
|
478
|
+
return text;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function createOpenAIOAuthProxy(options = {}) {
|
|
482
|
+
const fetch = pickFetch3(options.fetch);
|
|
483
|
+
const baseURL = (options.baseURL ?? DEFAULT_CODEX_BASE_URL).replace(/\/$/, "");
|
|
484
|
+
return {
|
|
485
|
+
async responses(request) {
|
|
486
|
+
const authorization = request.headers.get("authorization") ?? "";
|
|
487
|
+
const accountId = request.headers.get("chatgpt-account-id") ?? "";
|
|
488
|
+
if (!authorization.trim() || !accountId.trim()) {
|
|
489
|
+
return noStoreText("Missing OpenAI OAuth credentials.", 401);
|
|
490
|
+
}
|
|
491
|
+
const upstream = await fetch(`${baseURL}/responses${new URL(request.url).search}`, {
|
|
492
|
+
method: "POST",
|
|
493
|
+
headers: {
|
|
494
|
+
"Content-Type": "application/json",
|
|
495
|
+
Authorization: authorization,
|
|
496
|
+
"ChatGPT-Account-Id": accountId,
|
|
497
|
+
"OpenAI-Beta": "responses=experimental",
|
|
498
|
+
...options.originator === false ? {} : { originator: options.originator ?? "openai-codex-oauth" }
|
|
499
|
+
},
|
|
500
|
+
body: await requestBody(request, options)
|
|
501
|
+
});
|
|
502
|
+
return new Response(await upstream.text(), {
|
|
503
|
+
status: upstream.status,
|
|
504
|
+
headers: {
|
|
505
|
+
"Content-Type": upstream.headers.get("Content-Type") ?? "application/json",
|
|
506
|
+
"Cache-Control": "no-store"
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export {
|
|
514
|
+
OpenAIOAuthError,
|
|
515
|
+
DEFAULT_CLIENT_ID,
|
|
516
|
+
DEFAULT_ISSUER,
|
|
517
|
+
startOpenAIDeviceFlow,
|
|
518
|
+
refreshOpenAITokens,
|
|
519
|
+
createMemoryTokenStore,
|
|
520
|
+
DEFAULT_CODEX_BASE_URL,
|
|
521
|
+
normalizeCodexResponsesBody,
|
|
522
|
+
createCodexOAuthFetch,
|
|
523
|
+
createCodexOAuthClient,
|
|
524
|
+
createOpenAIOAuthProxy
|
|
525
|
+
};
|
|
526
|
+
//# sourceMappingURL=chunk-DXYYRLYI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/device-flow.ts","../src/memory-token-store.ts","../src/codex-fetch.ts","../src/proxy.ts"],"sourcesContent":["/**\n * @file types.ts\n *\n * Core type definitions for the OpenAI Codex OAuth library.\n * These types define the contract for authentication, token management,\n * and configuration across all modules.\n */\n\n/**\n * Type alias for the global fetch function signature.\n * Used to allow custom fetch implementations in various environments.\n */\nexport type FetchLike = typeof globalThis.fetch;\n\n/**\n * OAuth credentials used to call the OpenAI Codex backend.\n * Treat these as password-equivalent secrets.\n */\nexport type OpenAIOAuthTokens = {\n /** Bearer token sent to the Codex backend. */\n accessToken: string;\n /** Refresh token used to obtain a new access token before expiry. */\n refreshToken?: string;\n /** Access-token expiry as epoch milliseconds. */\n expiresAt?: number;\n /** Optional OpenID token. Used to derive `accountId` when available. */\n idToken?: string;\n /** ChatGPT account id required by the Codex backend. */\n accountId?: string;\n};\n\n/** Async storage interface for loading and persisting OAuth credentials. */\nexport type TokenStore = {\n /** Load the latest known credentials. Return `undefined` when the user is not signed in. */\n load(): Promise<OpenAIOAuthTokens | undefined>;\n /** Persist new credentials after sign-in or refresh. */\n save(tokens: OpenAIOAuthTokens): Promise<void>;\n};\n\n/** In-progress OpenAI Codex device authorization flow. */\nexport type OpenAIDeviceFlow = {\n providerId: 'openai';\n /** URL the user should open to authorize the device flow. */\n url: string;\n /** User code to enter on the authorization page. */\n code: string;\n /** Human-readable instruction string for command-line or app UI. */\n instructions: string;\n /** Poll until authorization completes, exchange the grant, and return OAuth tokens. */\n complete(): Promise<OpenAIOAuthTokens>;\n};\n\n/** Options for starting OpenAI's Codex device OAuth flow. */\nexport type OpenAIDeviceFlowOptions = {\n /** Custom fetch implementation, useful for tests and non-standard runtimes. */\n fetch?: FetchLike;\n /** Clock override returning epoch milliseconds. */\n now?: () => number;\n /** Sleep override used between polling attempts. */\n sleep?: (ms: number) => Promise<void>;\n /** OAuth client id. Defaults to the Codex client id. */\n clientId?: string;\n /** OAuth issuer. Defaults to `https://auth.openai.com`. */\n issuer?: string;\n /** Optional store that receives tokens after successful authorization. */\n tokenStore?: TokenStore;\n};\n\n/** Shared settings for Codex OAuth fetch, client, and AI SDK provider creation. */\nexport type OpenAIOAuthSettings = {\n /** Custom fetch implementation for both token refresh and Codex requests. */\n fetch?: FetchLike;\n /** Secure token storage used to load and save refreshed credentials. */\n tokenStore?: TokenStore;\n /** Inline tokens for scripts/tests. Prefer `tokenStore` for production apps. */\n tokens?: OpenAIOAuthTokens;\n /** OAuth client id. Defaults to the Codex client id. */\n clientId?: string;\n /** OAuth issuer. Defaults to `https://auth.openai.com`. */\n issuer?: string;\n /** Override token endpoint for refresh. */\n tokenUrl?: string;\n /** Codex backend base URL. Defaults to `https://chatgpt.com/backend-api/codex`. */\n baseURL?: string;\n /** Browser proxy base URL for Codex requests. Defaults to `/api/proxy/openai/codex` in browsers. Pass `false` to disable. */\n browserProxyBaseUrl?: string | false;\n /** Additional headers sent to the Codex backend before OAuth headers are applied. */\n headers?: Record<string, string>;\n /** Default `instructions` value for Responses requests that omit it. */\n instructions?: string;\n /** Default OpenAI Responses `store` value. Defaults to `false`. */\n store?: boolean;\n /** Upstream `originator` header. Pass `false` to omit it. */\n originator?: string | false;\n /** Refresh access tokens this many milliseconds before expiry. */\n refreshMarginMs?: number;\n /** Clock override returning epoch milliseconds. */\n now?: () => number;\n /** Called after a successful token refresh. */\n onTokens?: (tokens: OpenAIOAuthTokens) => void | Promise<void>;\n};\n\n/** Settings for the AI SDK provider factory. */\nexport type OpenAIOAuthProviderSettings = OpenAIOAuthSettings & {\n /** Provider name exposed to AI SDK telemetry and metadata. */\n name?: string;\n};\n\n/** Error class used for OAuth, token, and credential setup failures. */\nexport class OpenAIOAuthError extends Error {\n readonly code: string;\n\n constructor(code: string, message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'OpenAIOAuthError';\n this.code = code;\n }\n}\n","/**\n * @file device-flow.ts\n *\n * Implements the OAuth 2.0 Device Authorization Grant flow for OpenAI Codex.\n * This flow is designed for CLI tools and headless applications where\n * the user authorizes on a separate device with a browser.\n *\n * The flow:\n * 1. Client requests a device code from the authorization server\n * 2. User visits the authorization URL and enters the displayed code\n * 3. Client polls for authorization completion\n * 4. On success, client exchanges the authorization grant for tokens\n */\n\nimport { deriveAccountId, deriveExpiresAt } from './jwt';\nimport type { FetchLike, OpenAIDeviceFlow, OpenAIDeviceFlowOptions, OpenAIOAuthTokens } from './types';\nimport { OpenAIOAuthError } from './types';\n\n/** Default OAuth client ID for Codex/ChatGPT. */\nconst DEFAULT_CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';\n/** Default OAuth issuer/authorization server URL. */\nconst DEFAULT_ISSUER = 'https://auth.openai.com';\n/** Additional wait time added to the poll interval to avoid race conditions. */\nconst POLL_BUFFER_MS = 3000;\n\n/**\n * Resolves the fetch function to use for OAuth requests.\n */\nfunction pickFetch(customFetch?: FetchLike): FetchLike {\n if (typeof customFetch === 'function') {\n return customFetch;\n }\n\n if (typeof globalThis.fetch === 'function') {\n return globalThis.fetch.bind(globalThis);\n }\n\n throw new OpenAIOAuthError('fetch_required', 'A fetch implementation is required for OpenAI OAuth.');\n}\n\n/**\n * Start OpenAI's Codex device OAuth flow.\n *\n * The returned flow contains a URL and user code for authorization. Call\n * `flow.complete()` after showing those values to poll for authorization and\n * exchange the grant for OAuth tokens.\n */\nexport async function startOpenAIDeviceFlow(options: OpenAIDeviceFlowOptions = {}): Promise<OpenAIDeviceFlow> {\n const fetch = pickFetch(options.fetch);\n const issuer = (options.issuer ?? DEFAULT_ISSUER).replace(/\\/$/, '');\n const clientId = options.clientId ?? DEFAULT_CLIENT_ID;\n const now = options.now ?? (() => Date.now());\n const sleep = options.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));\n\n const codeResponse = await fetch(`${issuer}/api/accounts/deviceauth/usercode`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ client_id: clientId }),\n });\n\n if (!codeResponse.ok) {\n throw new OpenAIOAuthError('auth_failed', 'Failed to initiate OpenAI authorization.');\n }\n\n const device = (await codeResponse.json()) as {\n device_auth_id?: string;\n user_code?: string;\n interval?: string | number;\n };\n\n if (!device.device_auth_id || !device.user_code) {\n throw new OpenAIOAuthError('auth_failed', 'OpenAI authorization response did not include a device code.');\n }\n\n const intervalSeconds = typeof device.interval === 'number' ? device.interval : parseInt(device.interval ?? '5', 10);\n const intervalMs = Math.max(Number.isFinite(intervalSeconds) ? intervalSeconds : 5, 1) * 1000;\n\n return {\n providerId: 'openai',\n url: `${issuer}/codex/device`,\n code: device.user_code,\n instructions: `Enter code: ${device.user_code}`,\n async complete() {\n while (true) {\n const poll = await fetch(`${issuer}/api/accounts/deviceauth/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n device_auth_id: device.device_auth_id,\n user_code: device.user_code,\n }),\n });\n\n if (poll.ok) {\n const grant = (await poll.json()) as {\n authorization_code?: string;\n code_verifier?: string;\n };\n\n if (!grant.authorization_code || !grant.code_verifier) {\n throw new OpenAIOAuthError('auth_failed', 'OpenAI authorization grant was incomplete.');\n }\n\n const tokens = await exchangeAuthorizationCode({\n fetch,\n issuer,\n clientId,\n authorizationCode: grant.authorization_code,\n codeVerifier: grant.code_verifier,\n now,\n });\n\n await options.tokenStore?.save(tokens);\n return tokens;\n }\n\n if (poll.status !== 403 && poll.status !== 404) {\n throw new OpenAIOAuthError('auth_failed', 'OpenAI OAuth authorization failed.');\n }\n\n await sleep(intervalMs + POLL_BUFFER_MS);\n }\n },\n };\n}\n\nasync function exchangeAuthorizationCode({\n fetch,\n issuer,\n clientId,\n authorizationCode,\n codeVerifier,\n now,\n}: {\n fetch: FetchLike;\n issuer: string;\n clientId: string;\n authorizationCode: string;\n codeVerifier: string;\n now: () => number;\n}): Promise<OpenAIOAuthTokens> {\n const response = await fetch(`${issuer}/oauth/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: authorizationCode,\n redirect_uri: `${issuer}/deviceauth/callback`,\n client_id: clientId,\n code_verifier: codeVerifier,\n }).toString(),\n });\n\n if (!response.ok) {\n throw new OpenAIOAuthError('auth_failed', 'OpenAI token exchange failed.');\n }\n\n const token = (await response.json()) as {\n refresh_token?: string;\n access_token?: string;\n id_token?: string;\n expires_in?: number;\n };\n\n if (!token.access_token) {\n throw new OpenAIOAuthError('auth_failed', 'OpenAI token exchange did not return an access token.');\n }\n\n return {\n refreshToken: token.refresh_token,\n accessToken: token.access_token,\n idToken: token.id_token,\n expiresAt: now() + (token.expires_in ?? 3600) * 1000,\n accountId: deriveAccountId(token.id_token, token.access_token),\n };\n}\n\n/**\n * Refresh OpenAI OAuth credentials with a refresh token.\n *\n * Returns `undefined` when refresh is not possible or the server rejects the\n * refresh request. Callers should treat expired credentials as unusable when\n * this returns `undefined`.\n */\nexport async function refreshOpenAITokens({\n tokens,\n fetch,\n clientId = DEFAULT_CLIENT_ID,\n issuer = DEFAULT_ISSUER,\n tokenUrl,\n now = () => Date.now(),\n}: {\n tokens: OpenAIOAuthTokens;\n fetch: FetchLike;\n clientId?: string;\n issuer?: string;\n tokenUrl?: string;\n now?: () => number;\n}): Promise<OpenAIOAuthTokens | undefined> {\n if (!tokens.refreshToken) {\n return undefined;\n }\n\n const resolvedIssuer = issuer.replace(/\\/$/, '');\n const response = await fetch(tokenUrl ?? `${resolvedIssuer}/oauth/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: tokens.refreshToken,\n client_id: clientId,\n scope: 'openid profile email offline_access',\n }).toString(),\n });\n\n if (!response.ok) {\n return undefined;\n }\n\n const payload = (await response.json()) as {\n access_token?: string;\n refresh_token?: string;\n id_token?: string;\n expires_in?: number;\n };\n\n if (!payload.access_token) {\n return undefined;\n }\n\n const next: OpenAIOAuthTokens = {\n accessToken: payload.access_token,\n refreshToken: payload.refresh_token ?? tokens.refreshToken,\n idToken: payload.id_token ?? tokens.idToken,\n expiresAt: now() + (payload.expires_in ?? 3600) * 1000,\n };\n next.accountId = deriveAccountId(next.idToken, next.accessToken) ?? tokens.accountId;\n\n return next;\n}\n\nexport { DEFAULT_CLIENT_ID, DEFAULT_ISSUER };\n","/**\n * @file memory-token-store.ts\n *\n * In-memory token storage implementation.\n * Useful for testing and short-lived processes that don't need\n * persistent token storage.\n */\n\nimport type { OpenAIOAuthTokens, TokenStore } from './types';\n\n/**\n * Create an in-memory token store.\n *\n * This is useful for tests and short-lived scripts. It does not persist tokens\n * across process restarts.\n */\nexport function createMemoryTokenStore(initial?: OpenAIOAuthTokens): TokenStore {\n let current = initial;\n\n return {\n async load() {\n return current;\n },\n async save(tokens) {\n current = tokens;\n },\n };\n}\n","/**\n * @file codex-fetch.ts\n *\n * Core module that provides the fetch wrapper for authenticated Codex requests.\n * This is the main entry point for making OAuth-authenticated requests to\n * the OpenAI Codex backend.\n *\n * Key features:\n * - Automatic token management and refresh before expiry\n * - Request/response normalization for Codex API compatibility\n * - Browser proxy URL resolution for cross-origin requests\n * - Account ID derivation from JWT claims\n */\n\nimport { refreshOpenAITokens } from './device-flow';\nimport { deriveAccountId, deriveExpiresAt } from './jwt';\nimport { createMemoryTokenStore } from './memory-token-store';\nimport type { FetchLike, OpenAIOAuthSettings, OpenAIOAuthTokens, TokenStore } from './types';\nimport { OpenAIOAuthError } from './types';\n\n/** Default OpenAI Codex backend base URL. */\nexport const DEFAULT_CODEX_BASE_URL = 'https://chatgpt.com/backend-api/codex';\n/** Default proxy base path when running in a browser environment. */\nconst DEFAULT_BROWSER_PROXY_BASE_URL = '/api/proxy/openai/codex';\n/** Default time (5 minutes) before expiry to trigger token refresh. */\nconst DEFAULT_REFRESH_MARGIN_MS = 5 * 60 * 1000;\n/** Default empty instructions value for Responses API requests. */\nconst DEFAULT_INSTRUCTIONS = '';\n/** Default originator header value identifying this library. */\nconst DEFAULT_ORIGINATOR = 'openai-codex-oauth';\n\n/**\n * Parsed request components used internally for request transformation.\n */\ntype RequestParts = {\n url: string;\n method?: string;\n headers: Headers;\n body?: BodyInit | null;\n signal?: AbortSignal | null;\n};\n\n/**\n * Resolves the fetch function to use for OAuth and Codex requests.\n * Prefers a custom fetch if provided, falls back to globalThis.fetch.\n */\nfunction pickFetch(customFetch?: FetchLike): FetchLike {\n if (typeof customFetch === 'function') {\n return customFetch;\n }\n\n if (typeof globalThis.fetch === 'function') {\n return globalThis.fetch.bind(globalThis);\n }\n\n throw new OpenAIOAuthError('fetch_required', 'A fetch implementation is required for OpenAI OAuth.');\n}\n\n/** Removes trailing slash from a URL string for consistent handling. */\nfunction withoutTrailingSlash(value: string): string {\n return value.replace(/\\/$/, '');\n}\n\n/** Resolves the base URL for Codex requests, defaulting to the official endpoint. */\nfunction resolveBaseURL(baseURL?: string): string {\n return withoutTrailingSlash(baseURL ?? DEFAULT_CODEX_BASE_URL);\n}\n\n/**\n * Creates a token store based on provided settings.\n * Prefers explicit tokenStore, then inline tokens, then throws an error.\n */\nfunction createStore(settings: OpenAIOAuthSettings): TokenStore {\n if (settings.tokenStore) {\n return settings.tokenStore;\n }\n\n if (settings.tokens) {\n return createMemoryTokenStore(settings.tokens);\n }\n\n throw new OpenAIOAuthError(\n 'tokens_required',\n 'OpenAI OAuth tokens are required. Pass `tokens`, `tokenStore`, or use `createCodexAuthFileStore` from `@tolksyn/openai-oauth/node`.',\n );\n}\n\n/**\n * Determines whether a token needs refresh based on expiry time.\n * Considers the marginMs parameter to refresh proactively before actual expiry.\n */\nfunction needsRefresh(tokens: OpenAIOAuthTokens, now: number, marginMs: number): boolean {\n const expiresAt = tokens.expiresAt ?? deriveExpiresAt(tokens.accessToken);\n if (!tokens.accessToken) {\n return true;\n }\n\n if (expiresAt == null || expiresAt <= 0) {\n return false;\n }\n\n return expiresAt <= now + marginMs;\n}\n\n/**\n * Checks if a token has already expired based on current time.\n */\nfunction hasExpired(tokens: OpenAIOAuthTokens, now: number): boolean {\n const expiresAt = tokens.expiresAt ?? deriveExpiresAt(tokens.accessToken);\n return expiresAt != null && expiresAt > 0 && expiresAt <= now;\n}\n\n/**\n * Manages OAuth token lifecycle including loading, caching, refresh, and validation.\n * Uses a promise coalescing pattern to handle concurrent token requests efficiently.\n */\nclass TokenManager {\n /** Promise for an in-flight token refresh to coalesce concurrent requests. */\n private inflight?: Promise<OpenAIOAuthTokens>;\n /** Cached current tokens to avoid redundant store loads. */\n private current?: OpenAIOAuthTokens;\n\n constructor(\n private readonly settings: OpenAIOAuthSettings,\n private readonly store: TokenStore,\n private readonly fetch: FetchLike,\n ) {}\n\n /**\n * Gets valid OAuth tokens, triggering refresh if needed.\n * Coalesces concurrent requests to avoid duplicate refresh calls.\n */\n async get(): Promise<OpenAIOAuthTokens> {\n if (this.inflight) {\n return this.inflight;\n }\n\n this.inflight = this.loadFresh()\n .then((tokens) => {\n this.current = tokens;\n this.inflight = undefined;\n return tokens;\n })\n .catch((error) => {\n this.inflight = undefined;\n throw error;\n });\n\n return this.inflight;\n }\n\n /**\n * Loads fresh tokens, optionally refreshing if expired or near expiry.\n * Derives account ID from JWT claims if not explicitly provided.\n */\n private async loadFresh(): Promise<OpenAIOAuthTokens> {\n let tokens = this.current ?? (await this.store.load());\n if (!tokens?.accessToken) {\n throw new OpenAIOAuthError('auth_failed', 'OpenAI OAuth access token is missing.');\n }\n\n const now = this.settings.now?.() ?? Date.now();\n const refreshMarginMs = this.settings.refreshMarginMs ?? DEFAULT_REFRESH_MARGIN_MS;\n\n if (needsRefresh(tokens, now, refreshMarginMs)) {\n const refreshed = await refreshOpenAITokens({\n tokens,\n fetch: this.fetch,\n clientId: this.settings.clientId,\n issuer: this.settings.issuer,\n tokenUrl: this.settings.tokenUrl,\n now: this.settings.now,\n });\n\n if (refreshed) {\n tokens = refreshed;\n await this.store.save(tokens);\n await this.settings.onTokens?.(tokens);\n } else if (hasExpired(tokens, now)) {\n throw new OpenAIOAuthError('auth_failed', 'OpenAI OAuth access token expired and refresh failed.');\n }\n }\n\n const accountId = tokens.accountId ?? deriveAccountId(tokens.idToken, tokens.accessToken);\n if (!accountId) {\n throw new OpenAIOAuthError(\n 'account_id_missing',\n 'OpenAI OAuth account id is missing. Store `accountId` with the tokens or include an id_token with the ChatGPT account claim.',\n );\n }\n\n return {\n ...tokens,\n accountId,\n };\n }\n}\n\n/**\n * Resolves a target URL relative to the Codex base URL.\n * Handles path rewriting from `/v1/responses` to Codex backend paths.\n */\nfunction resolveTargetUrl(input: string, baseURL: string): string {\n const base = new URL(baseURL);\n const parsed = /^https?:\\/\\//.test(input) ? new URL(input) : new URL(input, 'https://codex.invalid');\n let pathname = parsed.pathname;\n const basePath = withoutTrailingSlash(base.pathname);\n\n if (pathname === basePath) {\n pathname = '/';\n } else if (basePath && pathname.startsWith(`${basePath}/`)) {\n pathname = pathname.slice(basePath.length);\n }\n\n if (pathname === '/v1') {\n pathname = '/';\n } else if (pathname.startsWith('/v1/')) {\n pathname = pathname.slice(3);\n }\n\n if (!pathname.startsWith('/')) {\n pathname = `/${pathname}`;\n }\n\n return `${base.origin}${basePath}${pathname}${parsed.search}`;\n}\n\n/**\n * Detects the browser's origin from window.location if available.\n * Returns undefined in non-browser environments.\n */\nfunction browserOrigin(): string | undefined {\n const maybeWindow = (globalThis as { window?: { location?: { origin?: string } } }).window;\n return typeof maybeWindow?.location?.origin === 'string' ? maybeWindow.location.origin.replace(/\\/$/, '') : undefined;\n}\n\n/**\n * Resolves a proxy URL for browser environments to handle cross-origin requests.\n * When running in a browser, direct Codex requests may fail CORS, so we proxy\n * through a local path that forwards to the Codex backend.\n */\nfunction resolveBrowserProxyUrl(target: URL, baseURL: string, settings: OpenAIOAuthSettings): string | undefined {\n const origin = browserOrigin();\n if (!origin || settings.browserProxyBaseUrl === false) {\n return undefined;\n }\n\n const proxyBase = settings.browserProxyBaseUrl ?? DEFAULT_BROWSER_PROXY_BASE_URL;\n const absoluteProxyBase = /^https?:\\/\\//.test(proxyBase) ? proxyBase.replace(/\\/$/, '') : `${origin}${proxyBase.startsWith('/') ? '' : '/'}${proxyBase}`.replace(/\\/$/, '');\n const upstreamBasePath = withoutTrailingSlash(new URL(baseURL).pathname);\n let pathname = target.pathname;\n\n if (upstreamBasePath && pathname.startsWith(`${upstreamBasePath}/`)) {\n pathname = pathname.slice(upstreamBasePath.length);\n }\n\n return `${absoluteProxyBase}${pathname}${target.search}`;\n}\n\n/**\n * Parses a fetch input (Request or URL string) into structured request parts.\n * Handles merging of Request defaults with optional init overrides.\n */\nasync function readRequestParts(input: Parameters<FetchLike>[0], init?: Parameters<FetchLike>[1]): Promise<RequestParts> {\n if (input instanceof Request) {\n const headers = new Headers(input.headers);\n if (init?.headers) {\n new Headers(init.headers).forEach((value, key) => headers.set(key, value));\n }\n\n return {\n url: input.url,\n method: init?.method ?? input.method,\n headers,\n body: init?.body ?? (input.body == null ? undefined : await input.clone().text()),\n signal: init?.signal ?? input.signal,\n };\n }\n\n return {\n url: String(input),\n method: init?.method,\n headers: new Headers(init?.headers),\n body: init?.body,\n signal: init?.signal,\n };\n}\n\n/**\n * Attempts to decode a request body to a string for JSON parsing.\n * Supports string, Blob, and ArrayBuffer types; returns undefined for\n * types that cannot be meaningfully decoded (FormData, ReadableStream, etc.)\n */\nasync function decodeBody(body: BodyInit | null | undefined): Promise<string | undefined> {\n if (body == null) {\n return undefined;\n }\n\n if (typeof body === 'string') {\n return body;\n }\n\n if (body instanceof URLSearchParams || body instanceof FormData || body instanceof ReadableStream) {\n return undefined;\n }\n\n if (body instanceof Blob) {\n return body.text();\n }\n\n if (body instanceof ArrayBuffer) {\n return new TextDecoder().decode(body);\n }\n\n if (ArrayBuffer.isView(body)) {\n return new TextDecoder().decode(body);\n }\n\n return undefined;\n}\n\n/**\n * Normalize an OpenAI Responses request body for the Codex backend.\n *\n * Codex expects requests to be stateless by default, so `store` defaults to\n * `false`. Unsupported `max_output_tokens` is removed.\n */\nexport function normalizeCodexResponsesBody(\n body: Record<string, unknown>,\n options: Pick<OpenAIOAuthSettings, 'instructions' | 'store'> = {},\n): Record<string, unknown> {\n const normalized = { ...body };\n\n if (typeof normalized.instructions !== 'string') {\n normalized.instructions = options.instructions ?? DEFAULT_INSTRUCTIONS;\n }\n\n if (normalized.store === undefined) {\n normalized.store = options.store ?? false;\n }\n\n delete normalized.max_output_tokens;\n\n return normalized;\n}\n\n/**\n * Prepares the request body for Codex /responses endpoints.\n * Normalizes JSON bodies to ensure Codex-compatible request format.\n */\nasync function prepareBody(pathname: string, headers: Headers, body: BodyInit | null | undefined, settings: OpenAIOAuthSettings) {\n if (!pathname.endsWith('/responses')) {\n return body;\n }\n\n const contentType = headers.get('content-type');\n if (contentType && !contentType.includes('application/json')) {\n return body;\n }\n\n const bodyText = await decodeBody(body);\n if (typeof bodyText !== 'string') {\n return body;\n }\n\n try {\n const parsed = JSON.parse(bodyText) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return body;\n }\n\n return JSON.stringify(normalizeCodexResponsesBody(parsed as Record<string, unknown>, settings));\n } catch {\n return body;\n }\n}\n\n/**\n * Create a fetch implementation that authenticates OpenAI Responses requests\n * with Codex OAuth credentials.\n *\n * The returned function rewrites `/v1/responses` requests to the Codex backend,\n * injects `Authorization` and `ChatGPT-Account-Id`, and refreshes credentials\n * before expiry when a refresh token is available.\n */\nexport function createCodexOAuthFetch(settings: OpenAIOAuthSettings = {}): FetchLike {\n const fetch = pickFetch(settings.fetch);\n const store = createStore(settings);\n const manager = new TokenManager(settings, store, fetch);\n const baseURL = resolveBaseURL(settings.baseURL);\n\n return async (input, init) => {\n const request = await readRequestParts(input, init);\n const targetUrl = resolveTargetUrl(request.url, baseURL);\n const target = new URL(targetUrl);\n const tokens = await manager.get();\n const headers = new Headers(settings.headers);\n\n request.headers.forEach((value, key) => headers.set(key, value));\n headers.delete('authorization');\n headers.delete('chatgpt-account-id');\n headers.delete('openai-beta');\n\n headers.set('Authorization', `Bearer ${tokens.accessToken}`);\n headers.set('ChatGPT-Account-Id', tokens.accountId!);\n headers.set('OpenAI-Beta', 'responses=experimental');\n\n if (settings.originator !== false && !headers.has('originator')) {\n headers.set('originator', settings.originator ?? DEFAULT_ORIGINATOR);\n }\n\n const body = await prepareBody(target.pathname, headers, request.body, settings);\n\n return fetch(resolveBrowserProxyUrl(target, baseURL, settings) ?? target.toString(), {\n method: request.method ?? init?.method,\n headers,\n body,\n signal: request.signal ?? undefined,\n });\n };\n}\n\n/** Create a small Codex client around `createCodexOAuthFetch`. */\nexport function createCodexOAuthClient(settings: OpenAIOAuthSettings = {}) {\n const baseURL = resolveBaseURL(settings.baseURL);\n const fetch = createCodexOAuthFetch(settings);\n\n return {\n baseURL,\n fetch,\n request: (path: string, init?: RequestInit) => fetch(resolveTargetUrl(path, baseURL), init),\n };\n}\n","/**\n * @file proxy.ts\n *\n * Server-side request handlers for proxying OpenAI Codex OAuth requests.\n * This module provides framework-agnostic handlers that can be used in\n * various server environments (Node.js, Deno, Cloudflare Workers, etc.)\n * to enable browser clients to make authenticated Codex requests.\n *\n * The proxy validates OAuth credentials (Authorization header and account ID),\n * rewrites requests to the Codex backend, and passes through responses.\n */\n\nimport { DEFAULT_CODEX_BASE_URL, normalizeCodexResponsesBody } from './codex-fetch';\nimport type { FetchLike, OpenAIOAuthSettings } from './types';\n\nexport type OpenAIOAuthProxyOptions = Pick<OpenAIOAuthSettings, 'instructions' | 'originator' | 'store'> & {\n fetch?: FetchLike;\n baseURL?: string;\n};\n\n/**\n * Resolves the fetch function to use for upstream requests.\n * Prefers a custom fetch if provided, falls back to globalThis.fetch,\n * and throws if neither is available.\n */\nfunction pickFetch(customFetch?: FetchLike): FetchLike {\n if (typeof customFetch === 'function') return customFetch;\n if (typeof globalThis.fetch === 'function') return globalThis.fetch.bind(globalThis);\n throw new Error('A fetch implementation is required for OpenAI Codex proxy handlers.');\n}\n\n/**\n * Creates a Response with no-store cache control header.\n * Used for error responses to prevent caching.\n */\nfunction noStoreText(text: string, status: number): Response {\n return new Response(text, {\n status,\n headers: {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Cache-Control': 'no-store',\n },\n });\n}\n\n/**\n * Extracts and optionally normalizes the request body.\n * For JSON bodies, normalizes Codex-specific fields like instructions and store.\n */\nasync function requestBody(request: Request, options: OpenAIOAuthProxyOptions): Promise<string> {\n const text = await request.text();\n if (!text.trim()) return text;\n\n try {\n const parsed = JSON.parse(text) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return text;\n return JSON.stringify(normalizeCodexResponsesBody(parsed as Record<string, unknown>, options));\n } catch {\n return text;\n }\n}\n\n/** Create framework-agnostic server handlers for browser-safe Codex OAuth proxying. */\nexport function createOpenAIOAuthProxy(options: OpenAIOAuthProxyOptions = {}) {\n const fetch = pickFetch(options.fetch);\n const baseURL = (options.baseURL ?? DEFAULT_CODEX_BASE_URL).replace(/\\/$/, '');\n\n return {\n async responses(request: Request): Promise<Response> {\n const authorization = request.headers.get('authorization') ?? '';\n const accountId = request.headers.get('chatgpt-account-id') ?? '';\n if (!authorization.trim() || !accountId.trim()) {\n return noStoreText('Missing OpenAI OAuth credentials.', 401);\n }\n\n const upstream = await fetch(`${baseURL}/responses${new URL(request.url).search}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: authorization,\n 'ChatGPT-Account-Id': accountId,\n 'OpenAI-Beta': 'responses=experimental',\n ...(options.originator === false ? {} : { originator: options.originator ?? 'openai-codex-oauth' }),\n },\n body: await requestBody(request, options),\n });\n\n return new Response(await upstream.text(), {\n status: upstream.status,\n headers: {\n 'Content-Type': upstream.headers.get('Content-Type') ?? 'application/json',\n 'Cache-Control': 'no-store',\n },\n });\n },\n };\n}\n"],"mappings":"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","names":["pickFetch","pickFetch"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/jwt.ts
|
|
2
|
+
function decodeBase64Url(value) {
|
|
3
|
+
try {
|
|
4
|
+
const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
5
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
6
|
+
if (typeof globalThis.atob === "function") {
|
|
7
|
+
const binary = globalThis.atob(padded);
|
|
8
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
9
|
+
return new TextDecoder().decode(bytes);
|
|
10
|
+
}
|
|
11
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
12
|
+
} catch {
|
|
13
|
+
return void 0;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function isRecord(value) {
|
|
17
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
function parseJwtClaims(token) {
|
|
20
|
+
if (!token?.includes(".")) {
|
|
21
|
+
return void 0;
|
|
22
|
+
}
|
|
23
|
+
const parts = token.split(".");
|
|
24
|
+
if (parts.length !== 3 || !parts[1]) {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
const payload = decodeBase64Url(parts[1]);
|
|
28
|
+
if (!payload) {
|
|
29
|
+
return void 0;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(payload);
|
|
33
|
+
return isRecord(parsed) ? parsed : void 0;
|
|
34
|
+
} catch {
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function deriveAccountId(...tokens) {
|
|
39
|
+
for (const token of tokens) {
|
|
40
|
+
const claims = parseJwtClaims(token);
|
|
41
|
+
const auth = claims?.["https://api.openai.com/auth"];
|
|
42
|
+
if (isRecord(auth) && typeof auth.chatgpt_account_id === "string" && auth.chatgpt_account_id.length > 0) {
|
|
43
|
+
return auth.chatgpt_account_id;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return void 0;
|
|
47
|
+
}
|
|
48
|
+
function deriveExpiresAt(token) {
|
|
49
|
+
const claims = parseJwtClaims(token);
|
|
50
|
+
return typeof claims?.exp === "number" ? claims.exp * 1e3 : void 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
parseJwtClaims,
|
|
55
|
+
deriveAccountId,
|
|
56
|
+
deriveExpiresAt
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=chunk-SCKIRN2D.js.map
|