openai-codex-oauth 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +606 -25
- package/dist/index.js.map +1 -1
- package/dist/node/index.js +107 -20
- package/dist/node/index.js.map +1 -1
- package/dist/proxy.js +102 -6
- package/dist/proxy.js.map +1 -1
- package/package.json +1 -2
- package/dist/chunk-GI2K7JPK.js +0 -526
- package/dist/chunk-GI2K7JPK.js.map +0 -1
- package/dist/chunk-SCKIRN2D.js +0 -58
- package/dist/chunk-SCKIRN2D.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,25 +1,605 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEFAULT_CLIENT_ID: () => DEFAULT_CLIENT_ID,
|
|
24
|
+
DEFAULT_CODEX_BASE_URL: () => DEFAULT_CODEX_BASE_URL,
|
|
25
|
+
DEFAULT_ISSUER: () => DEFAULT_ISSUER,
|
|
26
|
+
OpenAIOAuthError: () => OpenAIOAuthError,
|
|
27
|
+
createCodexOAuthClient: () => createCodexOAuthClient,
|
|
28
|
+
createCodexOAuthFetch: () => createCodexOAuthFetch,
|
|
29
|
+
createMemoryTokenStore: () => createMemoryTokenStore,
|
|
30
|
+
createOpenAIOAuth: () => createOpenAIOAuth,
|
|
31
|
+
createOpenAIOAuthProxy: () => createOpenAIOAuthProxy,
|
|
32
|
+
deriveAccountId: () => deriveAccountId,
|
|
33
|
+
deriveExpiresAt: () => deriveExpiresAt,
|
|
34
|
+
normalizeCodexResponsesBody: () => normalizeCodexResponsesBody,
|
|
35
|
+
parseJwtClaims: () => parseJwtClaims,
|
|
36
|
+
refreshOpenAITokens: () => refreshOpenAITokens,
|
|
37
|
+
startOpenAIDeviceFlow: () => startOpenAIDeviceFlow
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
|
|
41
|
+
// src/jwt.ts
|
|
42
|
+
function decodeBase64Url(value) {
|
|
43
|
+
try {
|
|
44
|
+
const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
45
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
46
|
+
if (typeof globalThis.atob === "function") {
|
|
47
|
+
const binary = globalThis.atob(padded);
|
|
48
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
49
|
+
return new TextDecoder().decode(bytes);
|
|
50
|
+
}
|
|
51
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
52
|
+
} catch {
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function isRecord(value) {
|
|
57
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
58
|
+
}
|
|
59
|
+
function parseJwtClaims(token) {
|
|
60
|
+
if (!token?.includes(".")) {
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
const parts = token.split(".");
|
|
64
|
+
if (parts.length !== 3 || !parts[1]) {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
const payload = decodeBase64Url(parts[1]);
|
|
68
|
+
if (!payload) {
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const parsed = JSON.parse(payload);
|
|
73
|
+
return isRecord(parsed) ? parsed : void 0;
|
|
74
|
+
} catch {
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function deriveAccountId(...tokens) {
|
|
79
|
+
for (const token of tokens) {
|
|
80
|
+
const claims = parseJwtClaims(token);
|
|
81
|
+
const auth = claims?.["https://api.openai.com/auth"];
|
|
82
|
+
if (isRecord(auth) && typeof auth.chatgpt_account_id === "string" && auth.chatgpt_account_id.length > 0) {
|
|
83
|
+
return auth.chatgpt_account_id;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return void 0;
|
|
87
|
+
}
|
|
88
|
+
function deriveExpiresAt(token) {
|
|
89
|
+
const claims = parseJwtClaims(token);
|
|
90
|
+
return typeof claims?.exp === "number" ? claims.exp * 1e3 : void 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/types.ts
|
|
94
|
+
var OpenAIOAuthError = class extends Error {
|
|
95
|
+
code;
|
|
96
|
+
constructor(code, message, options) {
|
|
97
|
+
super(message, options);
|
|
98
|
+
this.name = "OpenAIOAuthError";
|
|
99
|
+
this.code = code;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/device-flow.ts
|
|
104
|
+
var DEFAULT_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
105
|
+
var DEFAULT_ISSUER = "https://auth.openai.com";
|
|
106
|
+
var POLL_BUFFER_MS = 3e3;
|
|
107
|
+
function pickFetch(customFetch) {
|
|
108
|
+
if (typeof customFetch === "function") {
|
|
109
|
+
return customFetch;
|
|
110
|
+
}
|
|
111
|
+
if (typeof globalThis.fetch === "function") {
|
|
112
|
+
return globalThis.fetch.bind(globalThis);
|
|
113
|
+
}
|
|
114
|
+
throw new OpenAIOAuthError("fetch_required", "A fetch implementation is required for OpenAI OAuth.");
|
|
115
|
+
}
|
|
116
|
+
async function startOpenAIDeviceFlow(options = {}) {
|
|
117
|
+
const fetch = pickFetch(options.fetch);
|
|
118
|
+
const issuer = (options.issuer ?? DEFAULT_ISSUER).replace(/\/$/, "");
|
|
119
|
+
const clientId = options.clientId ?? DEFAULT_CLIENT_ID;
|
|
120
|
+
const now = options.now ?? (() => Date.now());
|
|
121
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
122
|
+
const codeResponse = await fetch(`${issuer}/api/accounts/deviceauth/usercode`, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: {
|
|
125
|
+
"Content-Type": "application/json"
|
|
126
|
+
},
|
|
127
|
+
body: JSON.stringify({ client_id: clientId })
|
|
128
|
+
});
|
|
129
|
+
if (!codeResponse.ok) {
|
|
130
|
+
throw new OpenAIOAuthError("auth_failed", "Failed to initiate OpenAI authorization.");
|
|
131
|
+
}
|
|
132
|
+
const device = await codeResponse.json();
|
|
133
|
+
if (!device.device_auth_id || !device.user_code) {
|
|
134
|
+
throw new OpenAIOAuthError("auth_failed", "OpenAI authorization response did not include a device code.");
|
|
135
|
+
}
|
|
136
|
+
const intervalSeconds = typeof device.interval === "number" ? device.interval : parseInt(device.interval ?? "5", 10);
|
|
137
|
+
const intervalMs = Math.max(Number.isFinite(intervalSeconds) ? intervalSeconds : 5, 1) * 1e3;
|
|
138
|
+
return {
|
|
139
|
+
providerId: "openai",
|
|
140
|
+
url: `${issuer}/codex/device`,
|
|
141
|
+
code: device.user_code,
|
|
142
|
+
instructions: `Enter code: ${device.user_code}`,
|
|
143
|
+
async complete() {
|
|
144
|
+
while (true) {
|
|
145
|
+
const poll = await fetch(`${issuer}/api/accounts/deviceauth/token`, {
|
|
146
|
+
method: "POST",
|
|
147
|
+
headers: {
|
|
148
|
+
"Content-Type": "application/json"
|
|
149
|
+
},
|
|
150
|
+
body: JSON.stringify({
|
|
151
|
+
device_auth_id: device.device_auth_id,
|
|
152
|
+
user_code: device.user_code
|
|
153
|
+
})
|
|
154
|
+
});
|
|
155
|
+
if (poll.ok) {
|
|
156
|
+
const grant = await poll.json();
|
|
157
|
+
if (!grant.authorization_code || !grant.code_verifier) {
|
|
158
|
+
throw new OpenAIOAuthError("auth_failed", "OpenAI authorization grant was incomplete.");
|
|
159
|
+
}
|
|
160
|
+
const tokens = await exchangeAuthorizationCode({
|
|
161
|
+
fetch,
|
|
162
|
+
issuer,
|
|
163
|
+
clientId,
|
|
164
|
+
authorizationCode: grant.authorization_code,
|
|
165
|
+
codeVerifier: grant.code_verifier,
|
|
166
|
+
now
|
|
167
|
+
});
|
|
168
|
+
await options.tokenStore?.save(tokens);
|
|
169
|
+
return tokens;
|
|
170
|
+
}
|
|
171
|
+
if (poll.status !== 403 && poll.status !== 404) {
|
|
172
|
+
throw new OpenAIOAuthError("auth_failed", "OpenAI OAuth authorization failed.");
|
|
173
|
+
}
|
|
174
|
+
await sleep(intervalMs + POLL_BUFFER_MS);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
async function exchangeAuthorizationCode({
|
|
180
|
+
fetch,
|
|
181
|
+
issuer,
|
|
182
|
+
clientId,
|
|
183
|
+
authorizationCode,
|
|
184
|
+
codeVerifier,
|
|
185
|
+
now
|
|
186
|
+
}) {
|
|
187
|
+
const response = await fetch(`${issuer}/oauth/token`, {
|
|
188
|
+
method: "POST",
|
|
189
|
+
headers: {
|
|
190
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
191
|
+
},
|
|
192
|
+
body: new URLSearchParams({
|
|
193
|
+
grant_type: "authorization_code",
|
|
194
|
+
code: authorizationCode,
|
|
195
|
+
redirect_uri: `${issuer}/deviceauth/callback`,
|
|
196
|
+
client_id: clientId,
|
|
197
|
+
code_verifier: codeVerifier
|
|
198
|
+
}).toString()
|
|
199
|
+
});
|
|
200
|
+
if (!response.ok) {
|
|
201
|
+
throw new OpenAIOAuthError("auth_failed", "OpenAI token exchange failed.");
|
|
202
|
+
}
|
|
203
|
+
const token = await response.json();
|
|
204
|
+
if (!token.access_token) {
|
|
205
|
+
throw new OpenAIOAuthError("auth_failed", "OpenAI token exchange did not return an access token.");
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
refreshToken: token.refresh_token,
|
|
209
|
+
accessToken: token.access_token,
|
|
210
|
+
idToken: token.id_token,
|
|
211
|
+
expiresAt: now() + (token.expires_in ?? 3600) * 1e3,
|
|
212
|
+
accountId: deriveAccountId(token.id_token, token.access_token)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
async function refreshOpenAITokens({
|
|
216
|
+
tokens,
|
|
217
|
+
fetch,
|
|
218
|
+
clientId = DEFAULT_CLIENT_ID,
|
|
219
|
+
issuer = DEFAULT_ISSUER,
|
|
220
|
+
tokenUrl,
|
|
221
|
+
now = () => Date.now()
|
|
222
|
+
}) {
|
|
223
|
+
if (!tokens.refreshToken) {
|
|
224
|
+
return void 0;
|
|
225
|
+
}
|
|
226
|
+
const resolvedIssuer = issuer.replace(/\/$/, "");
|
|
227
|
+
const response = await fetch(tokenUrl ?? `${resolvedIssuer}/oauth/token`, {
|
|
228
|
+
method: "POST",
|
|
229
|
+
headers: {
|
|
230
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
231
|
+
},
|
|
232
|
+
body: new URLSearchParams({
|
|
233
|
+
grant_type: "refresh_token",
|
|
234
|
+
refresh_token: tokens.refreshToken,
|
|
235
|
+
client_id: clientId,
|
|
236
|
+
scope: "openid profile email offline_access"
|
|
237
|
+
}).toString()
|
|
238
|
+
});
|
|
239
|
+
if (!response.ok) {
|
|
240
|
+
return void 0;
|
|
241
|
+
}
|
|
242
|
+
const payload = await response.json();
|
|
243
|
+
if (!payload.access_token) {
|
|
244
|
+
return void 0;
|
|
245
|
+
}
|
|
246
|
+
const next = {
|
|
247
|
+
accessToken: payload.access_token,
|
|
248
|
+
refreshToken: payload.refresh_token ?? tokens.refreshToken,
|
|
249
|
+
idToken: payload.id_token ?? tokens.idToken,
|
|
250
|
+
expiresAt: now() + (payload.expires_in ?? 3600) * 1e3
|
|
251
|
+
};
|
|
252
|
+
next.accountId = deriveAccountId(next.idToken, next.accessToken) ?? tokens.accountId;
|
|
253
|
+
return next;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/memory-token-store.ts
|
|
257
|
+
function createMemoryTokenStore(initial) {
|
|
258
|
+
let current = initial;
|
|
259
|
+
return {
|
|
260
|
+
async load() {
|
|
261
|
+
return current;
|
|
262
|
+
},
|
|
263
|
+
async save(tokens) {
|
|
264
|
+
current = tokens;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/codex-fetch.ts
|
|
270
|
+
var DEFAULT_CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex";
|
|
271
|
+
var DEFAULT_BROWSER_PROXY_BASE_URL = "/api/proxy/openai/codex";
|
|
272
|
+
var DEFAULT_REFRESH_MARGIN_MS = 5 * 60 * 1e3;
|
|
273
|
+
var DEFAULT_INSTRUCTIONS = "";
|
|
274
|
+
var DEFAULT_ORIGINATOR = "openai-codex-oauth";
|
|
275
|
+
function pickFetch2(customFetch) {
|
|
276
|
+
if (typeof customFetch === "function") {
|
|
277
|
+
return customFetch;
|
|
278
|
+
}
|
|
279
|
+
if (typeof globalThis.fetch === "function") {
|
|
280
|
+
return globalThis.fetch.bind(globalThis);
|
|
281
|
+
}
|
|
282
|
+
throw new OpenAIOAuthError("fetch_required", "A fetch implementation is required for OpenAI OAuth.");
|
|
283
|
+
}
|
|
284
|
+
function withoutTrailingSlash(value) {
|
|
285
|
+
return value.replace(/\/$/, "");
|
|
286
|
+
}
|
|
287
|
+
function resolveBaseURL(baseURL) {
|
|
288
|
+
return withoutTrailingSlash(baseURL ?? DEFAULT_CODEX_BASE_URL);
|
|
289
|
+
}
|
|
290
|
+
function createStore(settings) {
|
|
291
|
+
if (settings.tokenStore) {
|
|
292
|
+
return settings.tokenStore;
|
|
293
|
+
}
|
|
294
|
+
if (settings.tokens) {
|
|
295
|
+
return createMemoryTokenStore(settings.tokens);
|
|
296
|
+
}
|
|
297
|
+
throw new OpenAIOAuthError(
|
|
298
|
+
"tokens_required",
|
|
299
|
+
"OpenAI OAuth tokens are required. Pass `tokens`, `tokenStore`, or use `createCodexAuthFileStore` from `@tolksyn/openai-oauth/node`."
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
function needsRefresh(tokens, now, marginMs) {
|
|
303
|
+
const expiresAt = tokens.expiresAt ?? deriveExpiresAt(tokens.accessToken);
|
|
304
|
+
if (!tokens.accessToken) {
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
if (expiresAt == null || expiresAt <= 0) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
return expiresAt <= now + marginMs;
|
|
311
|
+
}
|
|
312
|
+
function hasExpired(tokens, now) {
|
|
313
|
+
const expiresAt = tokens.expiresAt ?? deriveExpiresAt(tokens.accessToken);
|
|
314
|
+
return expiresAt != null && expiresAt > 0 && expiresAt <= now;
|
|
315
|
+
}
|
|
316
|
+
var TokenManager = class {
|
|
317
|
+
constructor(settings, store, fetch) {
|
|
318
|
+
this.settings = settings;
|
|
319
|
+
this.store = store;
|
|
320
|
+
this.fetch = fetch;
|
|
321
|
+
}
|
|
322
|
+
settings;
|
|
323
|
+
store;
|
|
324
|
+
fetch;
|
|
325
|
+
/** Promise for an in-flight token refresh to coalesce concurrent requests. */
|
|
326
|
+
inflight;
|
|
327
|
+
/** Cached current tokens to avoid redundant store loads. */
|
|
328
|
+
current;
|
|
329
|
+
/**
|
|
330
|
+
* Gets valid OAuth tokens, triggering refresh if needed.
|
|
331
|
+
* Coalesces concurrent requests to avoid duplicate refresh calls.
|
|
332
|
+
*/
|
|
333
|
+
async get() {
|
|
334
|
+
if (this.inflight) {
|
|
335
|
+
return this.inflight;
|
|
336
|
+
}
|
|
337
|
+
this.inflight = this.loadFresh().then((tokens) => {
|
|
338
|
+
this.current = tokens;
|
|
339
|
+
this.inflight = void 0;
|
|
340
|
+
return tokens;
|
|
341
|
+
}).catch((error) => {
|
|
342
|
+
this.inflight = void 0;
|
|
343
|
+
throw error;
|
|
344
|
+
});
|
|
345
|
+
return this.inflight;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Loads fresh tokens, optionally refreshing if expired or near expiry.
|
|
349
|
+
* Derives account ID from JWT claims if not explicitly provided.
|
|
350
|
+
*/
|
|
351
|
+
async loadFresh() {
|
|
352
|
+
let tokens = this.current ?? await this.store.load();
|
|
353
|
+
if (!tokens?.accessToken) {
|
|
354
|
+
throw new OpenAIOAuthError("auth_failed", "OpenAI OAuth access token is missing.");
|
|
355
|
+
}
|
|
356
|
+
const now = this.settings.now?.() ?? Date.now();
|
|
357
|
+
const refreshMarginMs = this.settings.refreshMarginMs ?? DEFAULT_REFRESH_MARGIN_MS;
|
|
358
|
+
if (needsRefresh(tokens, now, refreshMarginMs)) {
|
|
359
|
+
const refreshed = await refreshOpenAITokens({
|
|
360
|
+
tokens,
|
|
361
|
+
fetch: this.fetch,
|
|
362
|
+
clientId: this.settings.clientId,
|
|
363
|
+
issuer: this.settings.issuer,
|
|
364
|
+
tokenUrl: this.settings.tokenUrl,
|
|
365
|
+
now: this.settings.now
|
|
366
|
+
});
|
|
367
|
+
if (refreshed) {
|
|
368
|
+
tokens = refreshed;
|
|
369
|
+
await this.store.save(tokens);
|
|
370
|
+
await this.settings.onTokens?.(tokens);
|
|
371
|
+
} else if (hasExpired(tokens, now)) {
|
|
372
|
+
throw new OpenAIOAuthError("auth_failed", "OpenAI OAuth access token expired and refresh failed.");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const accountId = tokens.accountId ?? deriveAccountId(tokens.idToken, tokens.accessToken);
|
|
376
|
+
if (!accountId) {
|
|
377
|
+
throw new OpenAIOAuthError(
|
|
378
|
+
"account_id_missing",
|
|
379
|
+
"OpenAI OAuth account id is missing. Store `accountId` with the tokens or include an id_token with the ChatGPT account claim."
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
...tokens,
|
|
384
|
+
accountId
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
function resolveTargetUrl(input, baseURL) {
|
|
389
|
+
const base = new URL(baseURL);
|
|
390
|
+
const parsed = /^https?:\/\//.test(input) ? new URL(input) : new URL(input, "https://codex.invalid");
|
|
391
|
+
let pathname = parsed.pathname;
|
|
392
|
+
const basePath = withoutTrailingSlash(base.pathname);
|
|
393
|
+
if (pathname === basePath) {
|
|
394
|
+
pathname = "/";
|
|
395
|
+
} else if (basePath && pathname.startsWith(`${basePath}/`)) {
|
|
396
|
+
pathname = pathname.slice(basePath.length);
|
|
397
|
+
}
|
|
398
|
+
if (pathname === "/v1") {
|
|
399
|
+
pathname = "/";
|
|
400
|
+
} else if (pathname.startsWith("/v1/")) {
|
|
401
|
+
pathname = pathname.slice(3);
|
|
402
|
+
}
|
|
403
|
+
if (!pathname.startsWith("/")) {
|
|
404
|
+
pathname = `/${pathname}`;
|
|
405
|
+
}
|
|
406
|
+
return `${base.origin}${basePath}${pathname}${parsed.search}`;
|
|
407
|
+
}
|
|
408
|
+
function browserOrigin() {
|
|
409
|
+
const maybeWindow = globalThis.window;
|
|
410
|
+
return typeof maybeWindow?.location?.origin === "string" ? maybeWindow.location.origin.replace(/\/$/, "") : void 0;
|
|
411
|
+
}
|
|
412
|
+
function resolveBrowserProxyUrl(target, baseURL, settings) {
|
|
413
|
+
const origin = browserOrigin();
|
|
414
|
+
if (!origin || settings.browserProxyBaseUrl === false) {
|
|
415
|
+
return void 0;
|
|
416
|
+
}
|
|
417
|
+
const proxyBase = settings.browserProxyBaseUrl ?? DEFAULT_BROWSER_PROXY_BASE_URL;
|
|
418
|
+
const absoluteProxyBase = /^https?:\/\//.test(proxyBase) ? proxyBase.replace(/\/$/, "") : `${origin}${proxyBase.startsWith("/") ? "" : "/"}${proxyBase}`.replace(/\/$/, "");
|
|
419
|
+
const upstreamBasePath = withoutTrailingSlash(new URL(baseURL).pathname);
|
|
420
|
+
let pathname = target.pathname;
|
|
421
|
+
if (upstreamBasePath && pathname.startsWith(`${upstreamBasePath}/`)) {
|
|
422
|
+
pathname = pathname.slice(upstreamBasePath.length);
|
|
423
|
+
}
|
|
424
|
+
return `${absoluteProxyBase}${pathname}${target.search}`;
|
|
425
|
+
}
|
|
426
|
+
async function readRequestParts(input, init) {
|
|
427
|
+
if (input instanceof Request) {
|
|
428
|
+
const headers = new Headers(input.headers);
|
|
429
|
+
if (init?.headers) {
|
|
430
|
+
new Headers(init.headers).forEach((value, key) => headers.set(key, value));
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
url: input.url,
|
|
434
|
+
method: init?.method ?? input.method,
|
|
435
|
+
headers,
|
|
436
|
+
body: init?.body ?? (input.body == null ? void 0 : await input.clone().text()),
|
|
437
|
+
signal: init?.signal ?? input.signal
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
return {
|
|
441
|
+
url: String(input),
|
|
442
|
+
method: init?.method,
|
|
443
|
+
headers: new Headers(init?.headers),
|
|
444
|
+
body: init?.body,
|
|
445
|
+
signal: init?.signal
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
async function decodeBody(body) {
|
|
449
|
+
if (body == null) {
|
|
450
|
+
return void 0;
|
|
451
|
+
}
|
|
452
|
+
if (typeof body === "string") {
|
|
453
|
+
return body;
|
|
454
|
+
}
|
|
455
|
+
if (body instanceof URLSearchParams || body instanceof FormData || body instanceof ReadableStream) {
|
|
456
|
+
return void 0;
|
|
457
|
+
}
|
|
458
|
+
if (body instanceof Blob) {
|
|
459
|
+
return body.text();
|
|
460
|
+
}
|
|
461
|
+
if (body instanceof ArrayBuffer) {
|
|
462
|
+
return new TextDecoder().decode(body);
|
|
463
|
+
}
|
|
464
|
+
if (ArrayBuffer.isView(body)) {
|
|
465
|
+
return new TextDecoder().decode(body);
|
|
466
|
+
}
|
|
467
|
+
return void 0;
|
|
468
|
+
}
|
|
469
|
+
function normalizeCodexResponsesBody(body, options = {}) {
|
|
470
|
+
const normalized = { ...body };
|
|
471
|
+
if (typeof normalized.instructions !== "string") {
|
|
472
|
+
normalized.instructions = options.instructions ?? DEFAULT_INSTRUCTIONS;
|
|
473
|
+
}
|
|
474
|
+
if (normalized.store === void 0) {
|
|
475
|
+
normalized.store = options.store ?? false;
|
|
476
|
+
}
|
|
477
|
+
delete normalized.max_output_tokens;
|
|
478
|
+
return normalized;
|
|
479
|
+
}
|
|
480
|
+
async function prepareBody(pathname, headers, body, settings) {
|
|
481
|
+
if (!pathname.endsWith("/responses")) {
|
|
482
|
+
return body;
|
|
483
|
+
}
|
|
484
|
+
const contentType = headers.get("content-type");
|
|
485
|
+
if (contentType && !contentType.includes("application/json")) {
|
|
486
|
+
return body;
|
|
487
|
+
}
|
|
488
|
+
const bodyText = await decodeBody(body);
|
|
489
|
+
if (typeof bodyText !== "string") {
|
|
490
|
+
return body;
|
|
491
|
+
}
|
|
492
|
+
try {
|
|
493
|
+
const parsed = JSON.parse(bodyText);
|
|
494
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
495
|
+
return body;
|
|
496
|
+
}
|
|
497
|
+
return JSON.stringify(normalizeCodexResponsesBody(parsed, settings));
|
|
498
|
+
} catch {
|
|
499
|
+
return body;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function createCodexOAuthFetch(settings = {}) {
|
|
503
|
+
const fetch = pickFetch2(settings.fetch);
|
|
504
|
+
const store = createStore(settings);
|
|
505
|
+
const manager = new TokenManager(settings, store, fetch);
|
|
506
|
+
const baseURL = resolveBaseURL(settings.baseURL);
|
|
507
|
+
return async (input, init) => {
|
|
508
|
+
const request = await readRequestParts(input, init);
|
|
509
|
+
const targetUrl = resolveTargetUrl(request.url, baseURL);
|
|
510
|
+
const target = new URL(targetUrl);
|
|
511
|
+
const tokens = await manager.get();
|
|
512
|
+
const headers = new Headers(settings.headers);
|
|
513
|
+
request.headers.forEach((value, key) => headers.set(key, value));
|
|
514
|
+
headers.delete("authorization");
|
|
515
|
+
headers.delete("chatgpt-account-id");
|
|
516
|
+
headers.delete("openai-beta");
|
|
517
|
+
headers.set("Authorization", `Bearer ${tokens.accessToken}`);
|
|
518
|
+
headers.set("ChatGPT-Account-Id", tokens.accountId);
|
|
519
|
+
headers.set("OpenAI-Beta", "responses=experimental");
|
|
520
|
+
if (settings.originator !== false && !headers.has("originator")) {
|
|
521
|
+
headers.set("originator", settings.originator ?? DEFAULT_ORIGINATOR);
|
|
522
|
+
}
|
|
523
|
+
const body = await prepareBody(target.pathname, headers, request.body, settings);
|
|
524
|
+
return fetch(resolveBrowserProxyUrl(target, baseURL, settings) ?? target.toString(), {
|
|
525
|
+
method: request.method ?? init?.method,
|
|
526
|
+
headers,
|
|
527
|
+
body,
|
|
528
|
+
signal: request.signal ?? void 0
|
|
529
|
+
});
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
function createCodexOAuthClient(settings = {}) {
|
|
533
|
+
const baseURL = resolveBaseURL(settings.baseURL);
|
|
534
|
+
const fetch = createCodexOAuthFetch(settings);
|
|
535
|
+
return {
|
|
536
|
+
baseURL,
|
|
537
|
+
fetch,
|
|
538
|
+
request: (path, init) => fetch(resolveTargetUrl(path, baseURL), init)
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// src/proxy.ts
|
|
543
|
+
function pickFetch3(customFetch) {
|
|
544
|
+
if (typeof customFetch === "function") return customFetch;
|
|
545
|
+
if (typeof globalThis.fetch === "function") return globalThis.fetch.bind(globalThis);
|
|
546
|
+
throw new Error("A fetch implementation is required for OpenAI Codex proxy handlers.");
|
|
547
|
+
}
|
|
548
|
+
function noStoreText(text, status) {
|
|
549
|
+
return new Response(text, {
|
|
550
|
+
status,
|
|
551
|
+
headers: {
|
|
552
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
553
|
+
"Cache-Control": "no-store"
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
async function requestBody(request, options) {
|
|
558
|
+
const text = await request.text();
|
|
559
|
+
if (!text.trim()) return text;
|
|
560
|
+
try {
|
|
561
|
+
const parsed = JSON.parse(text);
|
|
562
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return text;
|
|
563
|
+
return JSON.stringify(normalizeCodexResponsesBody(parsed, options));
|
|
564
|
+
} catch {
|
|
565
|
+
return text;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function createOpenAIOAuthProxy(options = {}) {
|
|
569
|
+
const fetch = pickFetch3(options.fetch);
|
|
570
|
+
const baseURL = (options.baseURL ?? DEFAULT_CODEX_BASE_URL).replace(/\/$/, "");
|
|
571
|
+
return {
|
|
572
|
+
async responses(request) {
|
|
573
|
+
const authorization = request.headers.get("authorization") ?? "";
|
|
574
|
+
const accountId = request.headers.get("chatgpt-account-id") ?? "";
|
|
575
|
+
if (!authorization.trim() || !accountId.trim()) {
|
|
576
|
+
return noStoreText("Missing OpenAI OAuth credentials.", 401);
|
|
577
|
+
}
|
|
578
|
+
const upstream = await fetch(`${baseURL}/responses${new URL(request.url).search}`, {
|
|
579
|
+
method: "POST",
|
|
580
|
+
headers: {
|
|
581
|
+
"Content-Type": "application/json",
|
|
582
|
+
Authorization: authorization,
|
|
583
|
+
"ChatGPT-Account-Id": accountId,
|
|
584
|
+
"OpenAI-Beta": "responses=experimental",
|
|
585
|
+
...options.originator === false ? {} : { originator: options.originator ?? "openai-codex-oauth" }
|
|
586
|
+
},
|
|
587
|
+
body: await requestBody(request, options)
|
|
588
|
+
});
|
|
589
|
+
return new Response(upstream.body, {
|
|
590
|
+
status: upstream.status,
|
|
591
|
+
headers: {
|
|
592
|
+
"Content-Type": upstream.headers.get("Content-Type") ?? "application/json",
|
|
593
|
+
"Cache-Control": upstream.headers.get("Cache-Control") ?? "no-store"
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
}
|
|
19
599
|
|
|
20
600
|
// src/provider.ts
|
|
21
|
-
|
|
22
|
-
|
|
601
|
+
var import_openai = require("@ai-sdk/openai");
|
|
602
|
+
var import_provider = require("@ai-sdk/provider");
|
|
23
603
|
|
|
24
604
|
// src/stream-to-generate.ts
|
|
25
605
|
var emptyUsage = () => ({
|
|
@@ -203,7 +783,7 @@ var StreamOnlyLanguageModel = class {
|
|
|
203
783
|
function createOpenAIOAuth(settings = {}) {
|
|
204
784
|
const providerName = settings.name ?? "openai-oauth";
|
|
205
785
|
const oauthFetch = createCodexOAuthFetch(settings);
|
|
206
|
-
const openai = createOpenAI({
|
|
786
|
+
const openai = (0, import_openai.createOpenAI)({
|
|
207
787
|
apiKey: "oauth",
|
|
208
788
|
baseURL: settings.baseURL ?? DEFAULT_CODEX_BASE_URL,
|
|
209
789
|
name: providerName,
|
|
@@ -215,14 +795,15 @@ function createOpenAIOAuth(settings = {}) {
|
|
|
215
795
|
provider.languageModel = createLanguageModel;
|
|
216
796
|
provider.responses = createLanguageModel;
|
|
217
797
|
provider.embeddingModel = (modelId) => {
|
|
218
|
-
throw new NoSuchModelError({ modelId, modelType: "embeddingModel" });
|
|
798
|
+
throw new import_provider.NoSuchModelError({ modelId, modelType: "embeddingModel" });
|
|
219
799
|
};
|
|
220
800
|
provider.imageModel = (modelId) => {
|
|
221
|
-
throw new NoSuchModelError({ modelId, modelType: "imageModel" });
|
|
801
|
+
throw new import_provider.NoSuchModelError({ modelId, modelType: "imageModel" });
|
|
222
802
|
};
|
|
223
803
|
return provider;
|
|
224
804
|
}
|
|
225
|
-
export
|
|
805
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
806
|
+
0 && (module.exports = {
|
|
226
807
|
DEFAULT_CLIENT_ID,
|
|
227
808
|
DEFAULT_CODEX_BASE_URL,
|
|
228
809
|
DEFAULT_ISSUER,
|
|
@@ -238,5 +819,5 @@ export {
|
|
|
238
819
|
parseJwtClaims,
|
|
239
820
|
refreshOpenAITokens,
|
|
240
821
|
startOpenAIDeviceFlow
|
|
241
|
-
};
|
|
822
|
+
});
|
|
242
823
|
//# sourceMappingURL=index.js.map
|