@cortexkit/opencode-openai-auth 1.0.4 → 1.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.
Files changed (104) hide show
  1. package/dist/bin.js +9 -8
  2. package/dist/{pool-lifecycle-DqKq8Eej.d.ts → codex-request-BXDXToyt.d.ts} +158 -27
  3. package/dist/index-0yh85bwb.js +69 -0
  4. package/dist/{index-jye6vccc.js → index-4c8yw54k.js} +2 -2
  5. package/dist/{index-k4eacw7x.js → index-7c6sxtd6.js} +2 -2
  6. package/dist/index-7eb3dw37.js +90 -0
  7. package/dist/index-8re8rprk.js +2 -0
  8. package/dist/{index-bgq9d21h.js → index-csky2xpg.js} +1 -1
  9. package/dist/index-edbdscy7.js +65 -0
  10. package/dist/{index-45qgkb0m.js → index-f2fmb596.js} +1 -1
  11. package/dist/{index-envpbt0k.js → index-j8v2xwq6.js} +1 -1
  12. package/dist/{index-egrty6tz.js → index-k6cydkgt.js} +2 -2
  13. package/dist/{index-dryydk6b.js → index-m5qmvr6c.js} +2 -2
  14. package/dist/index-msx2m78k.js +8 -0
  15. package/dist/{index-yeyp8t0k.js → index-zpm95mt5.js} +1 -1
  16. package/dist/index.d.ts +15 -7
  17. package/dist/index.js +1 -1
  18. package/dist/{map-CCbSZIhT.d.ts → map-Buq-GW7l.d.ts} +1 -1
  19. package/dist/{protocol-CSIDDBDf.d.ts → model-YRg7Uowo.d.ts} +67 -25
  20. package/dist/{port-file-CZ9MIdy2.d.ts → port-file-CxEpgSHY.d.ts} +1 -1
  21. package/dist/protocol-CDV1Pc6h.d.ts +29 -0
  22. package/dist/rpc/port-file.d.ts +1 -1
  23. package/dist/rpc/port-file.js +1 -1
  24. package/dist/rpc/protocol.d.ts +1 -1
  25. package/dist/rpc/protocol.js +1 -1
  26. package/dist/rpc/rpc-client.d.ts +3 -3
  27. package/dist/rpc/rpc-client.js +1 -1
  28. package/dist/rpc/rpc-dir.js +1 -1
  29. package/dist/{sidebar-state-BCxFpH68.d.ts → sidebar-state-DjTWUbmf.d.ts} +15 -9
  30. package/dist/sidebar-state.d.ts +1 -1
  31. package/dist/sidebar-state.js +1 -1
  32. package/dist/{token-vykm9bxh.js → token-rz4t3ay6.js} +1 -1
  33. package/dist/token-util-tpy2c7h6.js +1 -0
  34. package/dist/tui-preferences.js +1 -1
  35. package/dist/tui.d.ts +20 -2
  36. package/dist/v2/server.d.ts +73 -1
  37. package/dist/v2/server.js +3 -3
  38. package/package.json +2 -2
  39. package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +78 -42
  40. package/src/tui-compiled/raw/shared/0f77ebc3c914c9c6-custody.js +354 -0
  41. package/src/tui-compiled/raw/shared/134b008900698fe0-backoff.js +92 -0
  42. package/src/tui-compiled/raw/shared/169b2b4bf30909bc-consumer.js +393 -0
  43. package/src/tui-compiled/raw/shared/2577ec69f9973ae9-quota-normalize.js +154 -0
  44. package/src/tui-compiled/raw/shared/2dce6e9613993329-quota-manager.js +473 -0
  45. package/src/tui-compiled/raw/shared/30f5bc480f31f51c-roster.js +566 -0
  46. package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +0 -1
  47. package/src/tui-compiled/raw/shared/4593a4d71edf69fd-tombstone.js +25 -0
  48. package/src/tui-compiled/raw/shared/4d05c2d7a531aece-oauth.js +500 -0
  49. package/src/tui-compiled/raw/shared/6371b24a0573d79e-any-signal.js +42 -0
  50. package/src/tui-compiled/raw/shared/7c140680fc81be92-interlock.js +29 -0
  51. package/src/tui-compiled/raw/shared/85512cc5263ba259-provider.js +87 -0
  52. package/src/tui-compiled/raw/shared/abd062d633e29fd2-pool-routing.js +192 -0
  53. package/src/tui-compiled/raw/shared/b9588ad4725c1d10-enrollment.js +806 -0
  54. package/src/tui-compiled/raw/shared/bfbdd0f9aae56b47-pool-quota.js +85 -0
  55. package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +0 -1
  56. package/src/tui-compiled/raw/shared/ca0fccaba1d87af4-errors.js +8 -0
  57. package/src/tui-compiled/raw/shared/d0f0b6be200311af-insula.js +152 -0
  58. package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +1 -1
  59. package/src/tui-compiled/raw/shared/d9d2892a4c371301-host-slot.js +72 -0
  60. package/src/tui-compiled/raw/shared/dbc61cd405941828-accounts.js +1470 -0
  61. package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +26 -12
  62. package/src/tui-compiled/raw/shared/e8d3c8da776ef9cd-index.js +24 -0
  63. package/src/tui-compiled/raw/sidebar-state.js +41 -14
  64. package/src/tui-compiled/raw/tui/command-dialogs.tsx +3 -34
  65. package/src/tui-compiled/raw/tui/section-text.js +21 -0
  66. package/src/tui-compiled/raw/tui/v2-dialog.js +137 -0
  67. package/src/tui-compiled/raw/tui/v2.tsx +133 -0
  68. package/src/tui-compiled/raw/tui.tsx +146 -40
  69. package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +78 -42
  70. package/src/tui-compiled/runtime/shared/0f77ebc3c914c9c6-custody.js +354 -0
  71. package/src/tui-compiled/runtime/shared/134b008900698fe0-backoff.js +92 -0
  72. package/src/tui-compiled/runtime/shared/169b2b4bf30909bc-consumer.js +393 -0
  73. package/src/tui-compiled/runtime/shared/2577ec69f9973ae9-quota-normalize.js +154 -0
  74. package/src/tui-compiled/runtime/shared/2dce6e9613993329-quota-manager.js +473 -0
  75. package/src/tui-compiled/runtime/shared/30f5bc480f31f51c-roster.js +566 -0
  76. package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +0 -1
  77. package/src/tui-compiled/runtime/shared/4593a4d71edf69fd-tombstone.js +25 -0
  78. package/src/tui-compiled/runtime/shared/4d05c2d7a531aece-oauth.js +500 -0
  79. package/src/tui-compiled/runtime/shared/6371b24a0573d79e-any-signal.js +42 -0
  80. package/src/tui-compiled/runtime/shared/7c140680fc81be92-interlock.js +29 -0
  81. package/src/tui-compiled/runtime/shared/85512cc5263ba259-provider.js +87 -0
  82. package/src/tui-compiled/runtime/shared/abd062d633e29fd2-pool-routing.js +192 -0
  83. package/src/tui-compiled/runtime/shared/b9588ad4725c1d10-enrollment.js +806 -0
  84. package/src/tui-compiled/runtime/shared/bfbdd0f9aae56b47-pool-quota.js +85 -0
  85. package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +0 -1
  86. package/src/tui-compiled/runtime/shared/ca0fccaba1d87af4-errors.js +8 -0
  87. package/src/tui-compiled/runtime/shared/d0f0b6be200311af-insula.js +152 -0
  88. package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +1 -1
  89. package/src/tui-compiled/runtime/shared/d9d2892a4c371301-host-slot.js +72 -0
  90. package/src/tui-compiled/runtime/shared/dbc61cd405941828-accounts.js +1470 -0
  91. package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +26 -12
  92. package/src/tui-compiled/runtime/shared/e8d3c8da776ef9cd-index.js +24 -0
  93. package/src/tui-compiled/runtime/sidebar-state.js +41 -14
  94. package/src/tui-compiled/runtime/tui/command-dialogs.js +3 -15
  95. package/src/tui-compiled/runtime/tui/section-text.js +21 -0
  96. package/src/tui-compiled/runtime/tui/v2-dialog.js +137 -0
  97. package/src/tui-compiled/runtime/tui/v2.js +95 -0
  98. package/src/tui-compiled/runtime/tui.js +255 -163
  99. package/dist/index-2hch7e8n.js +0 -8
  100. package/dist/index-5dg931q0.js +0 -65
  101. package/dist/index-99esnzg0.js +0 -157
  102. package/dist/index-9reyazwh.js +0 -2
  103. package/dist/index-yf6arn5w.js +0 -2
  104. package/dist/token-util-gmn5yaqc.js +0 -1
@@ -0,0 +1,500 @@
1
+ import {
2
+ createServer
3
+ } from "node:http";
4
+ import { setTimeout as sleep } from "node:timers/promises";
5
+ export const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann", ISSUER = "https://auth.openai.com", OAUTH_PORT = 1455, OAUTH_POLLING_SAFETY_MARGIN_MS = 3000, USER_AGENT_PRODUCT = "cortexkit-opencode-openai-auth";
6
+ export function buildUserAgent(version) {
7
+ return `${USER_AGENT_PRODUCT}/${version}`;
8
+ }
9
+ export const RESERVED_ACCOUNT_ID = "main", RESERVED_ACCOUNT_ID_ERROR = `"${RESERVED_ACCOUNT_ID}" is a reserved account id; choose a different label.`;
10
+ export function base64UrlEncode(buffer) {
11
+ const bytes = new Uint8Array(buffer), binary = String.fromCharCode(...bytes);
12
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
13
+ }
14
+ export async function generatePKCE() {
15
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~", verifier = Array.from(crypto.getRandomValues(new Uint8Array(43))).map((b) => chars[b % chars.length]).join(""), challenge = base64UrlEncode(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier)));
16
+ return { verifier, challenge };
17
+ }
18
+ export function parseJwtClaims(token) {
19
+ if (token.length > 16384)
20
+ return;
21
+ const parts = token.split(".");
22
+ if (parts.length !== 3)
23
+ return;
24
+ const payload = parts[1];
25
+ if (!payload || payload.length > 8192)
26
+ return;
27
+ try {
28
+ return JSON.parse(Buffer.from(payload, "base64url").toString());
29
+ } catch {
30
+ return;
31
+ }
32
+ }
33
+ export function extractAccountIdFromClaims(claims) {
34
+ const direct = claims.chatgpt_account_id;
35
+ if (typeof direct === "string" && direct.length > 0)
36
+ return direct;
37
+ const nested = claims["https://api.openai.com/auth"];
38
+ if (nested && typeof nested === "object" && !Array.isArray(nested) && typeof nested.chatgpt_account_id === "string" && nested.chatgpt_account_id.length > 0)
39
+ return nested.chatgpt_account_id;
40
+ const orgs = claims.organizations;
41
+ if (Array.isArray(orgs)) {
42
+ const first = orgs[0];
43
+ if (first && typeof first === "object" && !Array.isArray(first) && typeof first.id === "string" && first.id.length > 0)
44
+ return first.id;
45
+ }
46
+ return;
47
+ }
48
+ export function extractAccountId(tokens) {
49
+ if (tokens.id_token) {
50
+ const claims = parseJwtClaims(tokens.id_token), accountId = claims && extractAccountIdFromClaims(claims);
51
+ if (accountId)
52
+ return accountId;
53
+ }
54
+ if (tokens.access_token) {
55
+ const claims = parseJwtClaims(tokens.access_token);
56
+ return claims ? extractAccountIdFromClaims(claims) : void 0;
57
+ }
58
+ return;
59
+ }
60
+ export function isReservedAccountId(value) {
61
+ return typeof value === "string" && value.toLowerCase() === RESERVED_ACCOUNT_ID;
62
+ }
63
+ export function assertFallbackAccountIdAllowed(value) {
64
+ if (isReservedAccountId(value))
65
+ throw Error(RESERVED_ACCOUNT_ID_ERROR);
66
+ return value;
67
+ }
68
+ export function buildAuthorizeUrl(redirectUri, pkce, state) {
69
+ const params = new URLSearchParams({
70
+ response_type: "code",
71
+ client_id: CLIENT_ID,
72
+ redirect_uri: redirectUri,
73
+ scope: "openid profile email offline_access",
74
+ code_challenge: pkce.challenge,
75
+ code_challenge_method: "S256",
76
+ id_token_add_organizations: "true",
77
+ codex_cli_simplified_flow: "true",
78
+ state,
79
+ originator: "opencode"
80
+ });
81
+ return `${ISSUER}/oauth/authorize?${params.toString()}`;
82
+ }
83
+ export async function exchangeCodeForTokens(code, redirectUri, pkce) {
84
+ const response = await fetch(`${ISSUER}/oauth/token`, {
85
+ method: "POST",
86
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
87
+ body: new URLSearchParams({
88
+ grant_type: "authorization_code",
89
+ code,
90
+ redirect_uri: redirectUri,
91
+ client_id: CLIENT_ID,
92
+ code_verifier: pkce.verifier
93
+ }).toString()
94
+ });
95
+ if (!response.ok)
96
+ throw Error(`Token exchange failed: ${response.status}`);
97
+ return await response.json();
98
+ }
99
+ export function escapeHtml(value) {
100
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
101
+ }
102
+ export const HTML_SUCCESS = `<!doctype html>
103
+ <html>
104
+ <head>
105
+ <title>CortexKit OpenAI Auth - Authorization Successful</title>
106
+ <style>
107
+ body {
108
+ font-family:
109
+ system-ui,
110
+ -apple-system,
111
+ sans-serif;
112
+ display: flex;
113
+ justify-content: center;
114
+ align-items: center;
115
+ height: 100vh;
116
+ margin: 0;
117
+ background: #131010;
118
+ color: #f1ecec;
119
+ }
120
+ .container {
121
+ text-align: center;
122
+ padding: 2rem;
123
+ }
124
+ h1 {
125
+ color: #f1ecec;
126
+ margin-bottom: 1rem;
127
+ }
128
+ p {
129
+ color: #b7b1b1;
130
+ }
131
+ </style>
132
+ </head>
133
+ <body>
134
+ <div class="container">
135
+ <h1>Authorization Successful</h1>
136
+ <p>You can close this window and return to OpenCode.</p>
137
+ </div>
138
+ <script>
139
+ setTimeout(() => window.close(), 2000)
140
+ </script>
141
+ </body>
142
+ </html>`, HTML_ERROR = (error) => `<!doctype html>
143
+ <html>
144
+ <head>
145
+ <title>CortexKit OpenAI Auth - Authorization Failed</title>
146
+ <style>
147
+ body {
148
+ font-family:
149
+ system-ui,
150
+ -apple-system,
151
+ sans-serif;
152
+ display: flex;
153
+ justify-content: center;
154
+ align-items: center;
155
+ height: 100vh;
156
+ margin: 0;
157
+ background: #131010;
158
+ color: #f1ecec;
159
+ }
160
+ .container {
161
+ text-align: center;
162
+ padding: 2rem;
163
+ }
164
+ h1 {
165
+ color: #fc533a;
166
+ margin-bottom: 1rem;
167
+ }
168
+ p {
169
+ color: #b7b1b1;
170
+ }
171
+ .error {
172
+ color: #ff917b;
173
+ font-family: monospace;
174
+ margin-top: 1rem;
175
+ padding: 1rem;
176
+ background: #3c140d;
177
+ border-radius: 0.5rem;
178
+ }
179
+ </style>
180
+ </head>
181
+ <body>
182
+ <div class="container">
183
+ <h1>Authorization Failed</h1>
184
+ <p>An error occurred during authorization.</p>
185
+ <div class="error">${escapeHtml(error)}</div>
186
+ </div>
187
+ </body>
188
+ </html>`;
189
+ let oauthServers = [], serverStarting;
190
+ const pendingFlows = new Map;
191
+ function listenOAuthServer(server, host) {
192
+ return new Promise((resolve, reject) => {
193
+ const onError = (error) => {
194
+ server.off("listening", onListening);
195
+ reject(error);
196
+ }, onListening = () => {
197
+ server.off("error", onError);
198
+ resolve();
199
+ };
200
+ server.once("error", onError);
201
+ server.once("listening", onListening);
202
+ server.listen(OAUTH_PORT, host);
203
+ });
204
+ }
205
+ function closeOAuthServer(server) {
206
+ server.close(() => {});
207
+ server.closeIdleConnections?.();
208
+ setTimeout(() => {
209
+ server.closeAllConnections?.();
210
+ }, 25).unref?.();
211
+ }
212
+ function flowCount() {
213
+ return pendingFlows.size;
214
+ }
215
+ export function flowCleanup(state) {
216
+ pendingFlows.delete(state);
217
+ if (flowCount() === 0)
218
+ stopOAuthServer();
219
+ }
220
+ export async function startOAuthServer() {
221
+ const redirectUri = `http://localhost:${OAUTH_PORT}/auth/callback`;
222
+ if (oauthServers.length > 0)
223
+ return { port: OAUTH_PORT, redirectUri };
224
+ if (serverStarting)
225
+ return serverStarting;
226
+ serverStarting = (async () => {
227
+ const handler = (req, res) => {
228
+ const url = new URL(req.url || "/", `http://127.0.0.1:${OAUTH_PORT}`);
229
+ if (url.pathname === "/auth/callback") {
230
+ const code = url.searchParams.get("code"), state = url.searchParams.get("state"), error = url.searchParams.get("error"), errorDescription = url.searchParams.get("error_description");
231
+ if (error) {
232
+ const errorMsg = errorDescription || error;
233
+ if (state) {
234
+ const entry = pendingFlows.get(state);
235
+ if (entry) {
236
+ entry.reject(Error(errorMsg));
237
+ flowCleanup(state);
238
+ } else if (flowCount() === 0)
239
+ stopOAuthServer();
240
+ } else if (flowCount() === 0)
241
+ stopOAuthServer();
242
+ res.writeHead(200, { "Content-Type": "text/html" });
243
+ res.end(HTML_ERROR(errorMsg));
244
+ return;
245
+ }
246
+ if (!state) {
247
+ if (flowCount() === 0)
248
+ stopOAuthServer();
249
+ res.writeHead(400, { "Content-Type": "text/html" });
250
+ res.end(HTML_ERROR("Missing state parameter"));
251
+ return;
252
+ }
253
+ if (!code) {
254
+ if (flowCount() === 0)
255
+ stopOAuthServer();
256
+ res.writeHead(400, { "Content-Type": "text/html" });
257
+ res.end(HTML_ERROR("Missing authorization code"));
258
+ return;
259
+ }
260
+ const entry = pendingFlows.get(state);
261
+ if (!entry) {
262
+ const errorMsg = "Unknown state \u2014 authorization may have expired";
263
+ if (flowCount() === 0)
264
+ stopOAuthServer();
265
+ res.writeHead(400, { "Content-Type": "text/html" });
266
+ res.end(HTML_ERROR(errorMsg));
267
+ return;
268
+ }
269
+ flowCleanup(state);
270
+ exchangeCodeForTokens(code, redirectUri, entry.pkce).then((tokens) => entry.resolve(tokens)).catch((err) => entry.reject(err));
271
+ res.writeHead(200, { "Content-Type": "text/html" });
272
+ res.end(HTML_SUCCESS);
273
+ return;
274
+ }
275
+ if (url.pathname === "/cancel") {
276
+ const state = url.searchParams.get("state"), states = state ? [state] : [...pendingFlows.keys()];
277
+ res.writeHead(200);
278
+ res.end("Login cancelled");
279
+ for (const flowState of states) {
280
+ const entry = pendingFlows.get(flowState);
281
+ if (!entry)
282
+ continue;
283
+ entry.reject(Error("Login cancelled"));
284
+ flowCleanup(flowState);
285
+ }
286
+ if (flowCount() === 0)
287
+ stopOAuthServer();
288
+ return;
289
+ }
290
+ res.writeHead(404);
291
+ res.end("Not found");
292
+ }, ipv4Server = createServer(handler);
293
+ await listenOAuthServer(ipv4Server, "127.0.0.1");
294
+ const listeners = [ipv4Server], ipv6Server = createServer(handler);
295
+ try {
296
+ await listenOAuthServer(ipv6Server, "::1");
297
+ listeners.push(ipv6Server);
298
+ } catch (error) {
299
+ const code = error.code;
300
+ if (code !== "EADDRNOTAVAIL" && code !== "EAFNOSUPPORT") {
301
+ closeOAuthServer(ipv4Server);
302
+ throw error;
303
+ }
304
+ }
305
+ oauthServers = listeners;
306
+ return { port: OAUTH_PORT, redirectUri };
307
+ })();
308
+ try {
309
+ return await serverStarting;
310
+ } finally {
311
+ serverStarting = void 0;
312
+ }
313
+ }
314
+ export function stopOAuthServer() {
315
+ const servers = oauthServers;
316
+ oauthServers = [];
317
+ for (const server of servers)
318
+ closeOAuthServer(server);
319
+ }
320
+ export function resetOAuthStateForTest() {
321
+ pendingFlows.clear();
322
+ stopOAuthServer();
323
+ }
324
+ export function waitForOAuthCallback(pkce, state, timeoutMs = 300000, signal) {
325
+ return new Promise((resolve, reject) => {
326
+ if (signal?.aborted) {
327
+ flowCleanup(state);
328
+ reject(Error("Login cancelled"));
329
+ return;
330
+ }
331
+ const timeout = setTimeout(() => {
332
+ if (pendingFlows.get(state)) {
333
+ flowCleanup(state);
334
+ reject(Error("OAuth callback timeout \u2014 authorization took too long"));
335
+ }
336
+ }, timeoutMs), onAbort = () => {
337
+ clearTimeout(timeout);
338
+ if (pendingFlows.get(state)) {
339
+ flowCleanup(state);
340
+ reject(Error("Login cancelled"));
341
+ }
342
+ };
343
+ if (signal)
344
+ signal.addEventListener("abort", onAbort);
345
+ pendingFlows.set(state, {
346
+ pkce,
347
+ state,
348
+ resolve: (tokens) => {
349
+ clearTimeout(timeout);
350
+ if (signal)
351
+ signal.removeEventListener("abort", onAbort);
352
+ resolve(tokens);
353
+ },
354
+ reject: (error) => {
355
+ clearTimeout(timeout);
356
+ if (signal)
357
+ signal.removeEventListener("abort", onAbort);
358
+ reject(error);
359
+ }
360
+ });
361
+ });
362
+ }
363
+ export async function beginDeviceAuth(version) {
364
+ const deviceResponse = await fetch(`${ISSUER}/api/accounts/deviceauth/usercode`, {
365
+ method: "POST",
366
+ headers: {
367
+ "Content-Type": "application/json",
368
+ "User-Agent": buildUserAgent(version)
369
+ },
370
+ body: JSON.stringify({ client_id: CLIENT_ID })
371
+ });
372
+ if (!deviceResponse.ok)
373
+ throw Error("Failed to initiate device authorization");
374
+ const deviceData = await deviceResponse.json();
375
+ return {
376
+ deviceData,
377
+ url: `${ISSUER}/codex/device`,
378
+ instructions: `Enter code: ${deviceData.user_code}`
379
+ };
380
+ }
381
+ export async function completeDeviceAuth(deviceData, version, signal) {
382
+ const interval = Math.max(parseInt(deviceData.interval, 10) || 5, 1) * 1000, expires_in = typeof deviceData.expires_in === "number" ? deviceData.expires_in : parseInt(deviceData.expires_in || "", 10), maxDurationMs = !Number.isNaN(expires_in) && expires_in > 0 ? expires_in * 1000 : 900000, startTime = Date.now();
383
+ while (!0) {
384
+ if (signal?.aborted)
385
+ throw Error("Device authorization cancelled");
386
+ if (Date.now() - startTime > maxDurationMs)
387
+ throw Error("Device authorization expired");
388
+ const response = await fetch(`${ISSUER}/api/accounts/deviceauth/token`, {
389
+ method: "POST",
390
+ headers: {
391
+ "Content-Type": "application/json",
392
+ "User-Agent": buildUserAgent(version)
393
+ },
394
+ body: JSON.stringify({
395
+ device_auth_id: deviceData.device_auth_id,
396
+ user_code: deviceData.user_code
397
+ })
398
+ });
399
+ if (response.ok) {
400
+ const data = await response.json(), tokenResponse = await fetch(`${ISSUER}/oauth/token`, {
401
+ method: "POST",
402
+ headers: {
403
+ "Content-Type": "application/x-www-form-urlencoded"
404
+ },
405
+ body: new URLSearchParams({
406
+ grant_type: "authorization_code",
407
+ code: data.authorization_code,
408
+ redirect_uri: `${ISSUER}/deviceauth/callback`,
409
+ client_id: CLIENT_ID,
410
+ code_verifier: data.code_verifier
411
+ }).toString()
412
+ });
413
+ if (!tokenResponse.ok)
414
+ throw Error(`Token exchange failed: ${tokenResponse.status}`);
415
+ return await tokenResponse.json();
416
+ }
417
+ if (response.status !== 403 && response.status !== 404)
418
+ throw Error(`Device authorization failed: ${response.status}`);
419
+ const remainingMs = maxDurationMs - (Date.now() - startTime);
420
+ if (remainingMs <= 0)
421
+ throw Error("Device authorization expired");
422
+ const sleepMs = Math.min(interval + OAUTH_POLLING_SAFETY_MARGIN_MS, remainingMs);
423
+ try {
424
+ await sleep(sleepMs, void 0, { signal });
425
+ } catch (err) {
426
+ if (err instanceof Error && err.name === "AbortError")
427
+ throw Error("Device authorization cancelled");
428
+ throw err;
429
+ }
430
+ }
431
+ }
432
+ export function upsertAccount(accounts, account) {
433
+ let index = -1;
434
+ if (account.accountId)
435
+ index = accounts.findIndex((candidate) => candidate.accountId && candidate.accountId === account.accountId);
436
+ if (index < 0)
437
+ index = accounts.findIndex((candidate) => candidate.id === account.id);
438
+ if (index < 0 && account.label && !account.accountId)
439
+ index = accounts.findIndex((candidate) => candidate.label === account.label);
440
+ if (index >= 0) {
441
+ accounts[index] = {
442
+ ...accounts[index],
443
+ ...account,
444
+ addedAt: accounts[index]?.addedAt ?? account.addedAt
445
+ };
446
+ return index;
447
+ }
448
+ accounts.push(account);
449
+ return accounts.length - 1;
450
+ }
451
+ export async function beginAccountLogin(opts) {
452
+ const { label, headless = !1, signal, version } = opts;
453
+ assertFallbackAccountIdAllowed(label);
454
+ if (headless) {
455
+ const { deviceData, url, instructions } = await beginDeviceAuth(version), completion = (async () => {
456
+ const tokens = await completeDeviceAuth(deviceData, version, signal), now = Date.now(), accountId = extractAccountId(tokens);
457
+ return {
458
+ id: assertFallbackAccountIdAllowed(label || accountId || crypto.randomUUID()),
459
+ label: label || void 0,
460
+ type: "oauth",
461
+ access: tokens.access_token,
462
+ refresh: tokens.refresh_token,
463
+ expires: now + (tokens.expires_in ?? 3600) * 1000,
464
+ enabled: !0,
465
+ addedAt: now,
466
+ lastUsed: now,
467
+ lastRefreshedAt: now,
468
+ accountId
469
+ };
470
+ })();
471
+ return { url, instructions, completion };
472
+ }
473
+ const { redirectUri } = await startOAuthServer(), pkce = await generatePKCE(), state = base64UrlEncode(crypto.getRandomValues(new Uint8Array(32)).buffer), authUrl = buildAuthorizeUrl(redirectUri, pkce, state), completion = (async () => {
474
+ try {
475
+ if (signal?.aborted)
476
+ throw Error("Login cancelled");
477
+ const tokens = await waitForOAuthCallback(pkce, state, void 0, signal), now = Date.now(), accountId = extractAccountId(tokens);
478
+ return {
479
+ id: assertFallbackAccountIdAllowed(label || accountId || crypto.randomUUID()),
480
+ label: label || void 0,
481
+ type: "oauth",
482
+ access: tokens.access_token,
483
+ refresh: tokens.refresh_token,
484
+ expires: now + (tokens.expires_in ?? 3600) * 1000,
485
+ enabled: !0,
486
+ addedAt: now,
487
+ lastUsed: now,
488
+ lastRefreshedAt: now,
489
+ accountId
490
+ };
491
+ } finally {
492
+ flowCleanup(state);
493
+ }
494
+ })();
495
+ return {
496
+ url: authUrl,
497
+ instructions: "Complete authorization in your browser. This window will close automatically.",
498
+ completion
499
+ };
500
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Combine abort signals without relying on `AbortSignal.any`, which is missing
3
+ * from early Node 20 releases. Call `dispose` when the operation using the
4
+ * combined signal settles so long-lived source signals do not retain listeners.
5
+ */
6
+ export function combineAbortSignals(signals) {
7
+ const controller = new AbortController();
8
+ const listeners = [];
9
+ let disposed = false;
10
+ const dispose = () => {
11
+ if (disposed)
12
+ return;
13
+ disposed = true;
14
+ for (const { source, listener } of listeners) {
15
+ source.removeEventListener('abort', listener);
16
+ }
17
+ listeners.length = 0;
18
+ };
19
+ const abort = (reason) => {
20
+ if (disposed || controller.signal.aborted)
21
+ return;
22
+ controller.abort(reason);
23
+ dispose();
24
+ };
25
+ const alreadyAborted = signals.find((signal) => signal.aborted);
26
+ if (alreadyAborted) {
27
+ controller.abort(alreadyAborted.reason);
28
+ return { signal: controller.signal, dispose };
29
+ }
30
+ for (const source of signals) {
31
+ if (controller.signal.aborted)
32
+ break;
33
+ const listener = () => abort(source.reason);
34
+ listeners.push({ source, listener });
35
+ source.addEventListener('abort', listener, { once: true });
36
+ // Cover a source that aborts after the initial check but before its listener
37
+ // is installed.
38
+ if (source.aborted)
39
+ abort(source.reason);
40
+ }
41
+ return { signal: controller.signal, dispose };
42
+ }
@@ -0,0 +1,29 @@
1
+ function matches(entry, credentialId, accountIdentity) {
2
+ if (entry.accountIdentity !== undefined && accountIdentity !== undefined)
3
+ return entry.accountIdentity === accountIdentity;
4
+ return entry.credentialId === credentialId;
5
+ }
6
+ export function isDeclined(entries, credentialId, accountIdentity) {
7
+ return entries.some((entry) => matches(entry, credentialId, accountIdentity));
8
+ }
9
+ export function declineAccount(entries, credentialId, accountIdentity) {
10
+ const entry = {
11
+ credentialId,
12
+ ...(accountIdentity !== undefined && { accountIdentity }),
13
+ };
14
+ return [
15
+ ...entries.filter((existing) => existing.credentialId !== credentialId ||
16
+ existing.accountIdentity !== accountIdentity),
17
+ entry,
18
+ ];
19
+ }
20
+ /**
21
+ * Lift every entry that declines a row: the row's credential ids (the
22
+ * representative and its aliases) under the row's account. An entry for a
23
+ * different known account is left alone even when it names one of these
24
+ * credential ids, so accepting a replacement never re-enables the account the
25
+ * user declined.
26
+ */
27
+ export function acceptAccount(entries, credentialIds, accountIdentity) {
28
+ return entries.filter((entry) => !credentialIds.some((id) => matches(entry, id, accountIdentity)));
29
+ }
@@ -0,0 +1,87 @@
1
+ import { parseRetryAfter } from "./134b008900698fe0-backoff.js";
2
+ import { createLogger } from "./d669d72c40e9e116-logger.js";
3
+ import { normalizeWham } from "./2577ec69f9973ae9-quota-normalize.js";
4
+ import { assertNotTombstoneRefresh } from "./4593a4d71edf69fd-tombstone.js";
5
+ import { errorMessage } from "./f37fcefc7aedbf25-error.js";
6
+ const log = createLogger("quota");
7
+ export const PRIMARY = "primary", SECONDARY = "secondary", CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann", CODEX_ISSUER = "https://auth.openai.com";
8
+ export async function codexRefreshFn(input) {
9
+ assertNotTombstoneRefresh(input.refreshToken);
10
+ const response = await input.fetchImpl(`${CODEX_ISSUER}/oauth/token`, {
11
+ method: "POST",
12
+ signal: AbortSignal.timeout(15000),
13
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
14
+ body: new URLSearchParams({
15
+ grant_type: "refresh_token",
16
+ refresh_token: input.refreshToken,
17
+ client_id: CODEX_CLIENT_ID
18
+ }).toString()
19
+ });
20
+ if (!response.ok) {
21
+ const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
22
+ throw Object.assign(Error(`Token refresh failed: ${response.status}`), {
23
+ status: response.status,
24
+ retryAfter,
25
+ isRefreshError: !0
26
+ });
27
+ }
28
+ const tokens = await response.json();
29
+ if (!tokens || typeof tokens.access_token !== "string" || !tokens.access_token || typeof tokens.expires_in !== "number")
30
+ throw Object.assign(Error("Token refresh failed: malformed response"), {
31
+ status: response.status,
32
+ isRefreshError: !0
33
+ });
34
+ if (tokens.refresh_token !== void 0 && typeof tokens.refresh_token !== "string")
35
+ throw Object.assign(Error("Token refresh failed: malformed response"), {
36
+ status: response.status,
37
+ isRefreshError: !0
38
+ });
39
+ const rotatedRefresh = typeof tokens.refresh_token === "string" && tokens.refresh_token ? tokens.refresh_token : input.refreshToken;
40
+ return {
41
+ access: tokens.access_token,
42
+ refresh: rotatedRefresh,
43
+ expires: input.now() + tokens.expires_in * 1000,
44
+ expiresIn: tokens.expires_in
45
+ };
46
+ }
47
+ const WHAM_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
48
+ export async function whamUsageFn(input) {
49
+ const logger = input.logger ?? log, accountId = input.accountKey ?? "unknown", startedAt = Date.now();
50
+ logger.debug("wham usage fetch started", { pid: process.pid, accountId });
51
+ try {
52
+ const res = await input.fetchImpl(WHAM_USAGE_URL, {
53
+ signal: AbortSignal.timeout(15000),
54
+ headers: {
55
+ authorization: `Bearer ${input.accessToken}`,
56
+ "chatgpt-account-id": input.accountId ?? "",
57
+ "oai-client-platform": "web",
58
+ "oai-client-version": "0",
59
+ "x-openai-target-path": "/backend-api/wham/usage"
60
+ }
61
+ });
62
+ if (!res.ok) {
63
+ const retryAfter = parseRetryAfter(res.headers.get("retry-after"));
64
+ throw Object.assign(Error(`wham usage check failed: ${res.status}`), {
65
+ status: res.status,
66
+ retryAfter
67
+ });
68
+ }
69
+ const snapshot = normalizeWham(await res.json());
70
+ logger.debug("wham usage fetch succeeded", {
71
+ pid: process.pid,
72
+ accountId,
73
+ status: res.status,
74
+ elapsedMs: Date.now() - startedAt
75
+ });
76
+ return snapshot;
77
+ } catch (error) {
78
+ logger.warn("wham usage fetch failed", {
79
+ pid: process.pid,
80
+ accountId,
81
+ status: typeof error?.status === "number" ? error.status : "error",
82
+ elapsedMs: Date.now() - startedAt,
83
+ error: errorMessage(error)
84
+ });
85
+ throw error;
86
+ }
87
+ }