@mariozechner/pi-ai 0.35.0 → 0.37.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 (66) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +17 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/models.d.ts +1 -1
  5. package/dist/models.d.ts.map +1 -1
  6. package/dist/models.generated.d.ts +231 -42
  7. package/dist/models.generated.d.ts.map +1 -1
  8. package/dist/models.generated.js +269 -80
  9. package/dist/models.generated.js.map +1 -1
  10. package/dist/models.js +3 -2
  11. package/dist/models.js.map +1 -1
  12. package/dist/providers/openai-codex/constants.d.ts +21 -0
  13. package/dist/providers/openai-codex/constants.d.ts.map +1 -0
  14. package/dist/providers/openai-codex/constants.js +21 -0
  15. package/dist/providers/openai-codex/constants.js.map +1 -0
  16. package/dist/providers/openai-codex/prompts/codex-instructions.md +105 -0
  17. package/dist/providers/openai-codex/prompts/codex.d.ts +11 -0
  18. package/dist/providers/openai-codex/prompts/codex.d.ts.map +1 -0
  19. package/dist/providers/openai-codex/prompts/codex.js +184 -0
  20. package/dist/providers/openai-codex/prompts/codex.js.map +1 -0
  21. package/dist/providers/openai-codex/prompts/pi-codex-bridge.d.ts +6 -0
  22. package/dist/providers/openai-codex/prompts/pi-codex-bridge.d.ts.map +1 -0
  23. package/dist/providers/openai-codex/prompts/pi-codex-bridge.js +48 -0
  24. package/dist/providers/openai-codex/prompts/pi-codex-bridge.js.map +1 -0
  25. package/dist/providers/openai-codex/request-transformer.d.ts +41 -0
  26. package/dist/providers/openai-codex/request-transformer.d.ts.map +1 -0
  27. package/dist/providers/openai-codex/request-transformer.js +247 -0
  28. package/dist/providers/openai-codex/request-transformer.js.map +1 -0
  29. package/dist/providers/openai-codex/response-handler.d.ts +19 -0
  30. package/dist/providers/openai-codex/response-handler.d.ts.map +1 -0
  31. package/dist/providers/openai-codex/response-handler.js +107 -0
  32. package/dist/providers/openai-codex/response-handler.js.map +1 -0
  33. package/dist/providers/openai-codex-responses.d.ts +10 -0
  34. package/dist/providers/openai-codex-responses.d.ts.map +1 -0
  35. package/dist/providers/openai-codex-responses.js +530 -0
  36. package/dist/providers/openai-codex-responses.js.map +1 -0
  37. package/dist/stream.d.ts.map +1 -1
  38. package/dist/stream.js +27 -1
  39. package/dist/stream.js.map +1 -1
  40. package/dist/types.d.ts +6 -4
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/types.js.map +1 -1
  43. package/dist/utils/oauth/github-copilot.d.ts +2 -0
  44. package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  45. package/dist/utils/oauth/github-copilot.js +28 -5
  46. package/dist/utils/oauth/github-copilot.js.map +1 -1
  47. package/dist/utils/oauth/google-antigravity.d.ts +3 -1
  48. package/dist/utils/oauth/google-antigravity.d.ts.map +1 -1
  49. package/dist/utils/oauth/google-antigravity.js +100 -19
  50. package/dist/utils/oauth/google-antigravity.js.map +1 -1
  51. package/dist/utils/oauth/google-gemini-cli.d.ts +3 -1
  52. package/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -1
  53. package/dist/utils/oauth/google-gemini-cli.js +100 -19
  54. package/dist/utils/oauth/google-gemini-cli.js.map +1 -1
  55. package/dist/utils/oauth/index.d.ts +1 -0
  56. package/dist/utils/oauth/index.d.ts.map +1 -1
  57. package/dist/utils/oauth/index.js +11 -0
  58. package/dist/utils/oauth/index.js.map +1 -1
  59. package/dist/utils/oauth/openai-codex.d.ts +28 -0
  60. package/dist/utils/oauth/openai-codex.d.ts.map +1 -0
  61. package/dist/utils/oauth/openai-codex.js +342 -0
  62. package/dist/utils/oauth/openai-codex.js.map +1 -0
  63. package/dist/utils/oauth/types.d.ts +2 -1
  64. package/dist/utils/oauth/types.d.ts.map +1 -1
  65. package/dist/utils/oauth/types.js.map +1 -1
  66. package/package.json +2 -2
@@ -24,12 +24,8 @@ const CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
24
24
  async function startCallbackServer() {
25
25
  const { createServer } = await import("http");
26
26
  return new Promise((resolve, reject) => {
27
- let codeResolve;
28
- let codeReject;
29
- const codePromise = new Promise((res, rej) => {
30
- codeResolve = res;
31
- codeReject = rej;
32
- });
27
+ let result = null;
28
+ let cancelled = false;
33
29
  const server = createServer((req, res) => {
34
30
  const url = new URL(req.url || "", `http://localhost:8085`);
35
31
  if (url.pathname === "/oauth2callback") {
@@ -39,18 +35,16 @@ async function startCallbackServer() {
39
35
  if (error) {
40
36
  res.writeHead(400, { "Content-Type": "text/html" });
41
37
  res.end(`<html><body><h1>Authentication Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`);
42
- codeReject(new Error(`OAuth error: ${error}`));
43
38
  return;
44
39
  }
45
40
  if (code && state) {
46
41
  res.writeHead(200, { "Content-Type": "text/html" });
47
42
  res.end(`<html><body><h1>Authentication Successful</h1><p>You can close this window and return to the terminal.</p></body></html>`);
48
- codeResolve({ code, state });
43
+ result = { code, state };
49
44
  }
50
45
  else {
51
46
  res.writeHead(400, { "Content-Type": "text/html" });
52
47
  res.end(`<html><body><h1>Authentication Failed</h1><p>Missing code or state parameter.</p></body></html>`);
53
- codeReject(new Error("Missing code or state in callback"));
54
48
  }
55
49
  }
56
50
  else {
@@ -64,11 +58,39 @@ async function startCallbackServer() {
64
58
  server.listen(8085, "127.0.0.1", () => {
65
59
  resolve({
66
60
  server,
67
- getCode: () => codePromise,
61
+ cancelWait: () => {
62
+ cancelled = true;
63
+ },
64
+ waitForCode: async () => {
65
+ const sleep = () => new Promise((r) => setTimeout(r, 100));
66
+ while (!result && !cancelled) {
67
+ await sleep();
68
+ }
69
+ return result;
70
+ },
68
71
  });
69
72
  });
70
73
  });
71
74
  }
75
+ /**
76
+ * Parse redirect URL to extract code and state
77
+ */
78
+ function parseRedirectUrl(input) {
79
+ const value = input.trim();
80
+ if (!value)
81
+ return {};
82
+ try {
83
+ const url = new URL(value);
84
+ return {
85
+ code: url.searchParams.get("code") ?? undefined,
86
+ state: url.searchParams.get("state") ?? undefined,
87
+ };
88
+ }
89
+ catch {
90
+ // Not a URL, return empty
91
+ return {};
92
+ }
93
+ }
72
94
  /**
73
95
  * Wait helper for onboarding retries
74
96
  */
@@ -198,12 +220,15 @@ export async function refreshGoogleCloudToken(refreshToken, projectId) {
198
220
  *
199
221
  * @param onAuth - Callback with URL and optional instructions
200
222
  * @param onProgress - Optional progress callback
223
+ * @param onManualCodeInput - Optional promise that resolves with user-pasted redirect URL.
224
+ * Races with browser callback - whichever completes first wins.
201
225
  */
202
- export async function loginGeminiCli(onAuth, onProgress) {
226
+ export async function loginGeminiCli(onAuth, onProgress, onManualCodeInput) {
203
227
  const { verifier, challenge } = await generatePKCE();
204
228
  // Start local server for callback
205
229
  onProgress?.("Starting local server for OAuth callback...");
206
- const { server, getCode } = await startCallbackServer();
230
+ const server = await startCallbackServer();
231
+ let code;
207
232
  try {
208
233
  // Build authorization URL
209
234
  const authParams = new URLSearchParams({
@@ -221,14 +246,70 @@ export async function loginGeminiCli(onAuth, onProgress) {
221
246
  // Notify caller with URL to open
222
247
  onAuth({
223
248
  url: authUrl,
224
- instructions: "Complete the sign-in in your browser. The callback will be captured automatically.",
249
+ instructions: "Complete the sign-in in your browser.",
225
250
  });
226
- // Wait for the callback
251
+ // Wait for the callback, racing with manual input if provided
227
252
  onProgress?.("Waiting for OAuth callback...");
228
- const { code, state } = await getCode();
229
- // Verify state matches
230
- if (state !== verifier) {
231
- throw new Error("OAuth state mismatch - possible CSRF attack");
253
+ if (onManualCodeInput) {
254
+ // Race between browser callback and manual input
255
+ let manualInput;
256
+ let manualError;
257
+ const manualPromise = onManualCodeInput()
258
+ .then((input) => {
259
+ manualInput = input;
260
+ server.cancelWait();
261
+ })
262
+ .catch((err) => {
263
+ manualError = err instanceof Error ? err : new Error(String(err));
264
+ server.cancelWait();
265
+ });
266
+ const result = await server.waitForCode();
267
+ // If manual input was cancelled, throw that error
268
+ if (manualError) {
269
+ throw manualError;
270
+ }
271
+ if (result?.code) {
272
+ // Browser callback won - verify state
273
+ if (result.state !== verifier) {
274
+ throw new Error("OAuth state mismatch - possible CSRF attack");
275
+ }
276
+ code = result.code;
277
+ }
278
+ else if (manualInput) {
279
+ // Manual input won
280
+ const parsed = parseRedirectUrl(manualInput);
281
+ if (parsed.state && parsed.state !== verifier) {
282
+ throw new Error("OAuth state mismatch - possible CSRF attack");
283
+ }
284
+ code = parsed.code;
285
+ }
286
+ // If still no code, wait for manual promise and try that
287
+ if (!code) {
288
+ await manualPromise;
289
+ if (manualError) {
290
+ throw manualError;
291
+ }
292
+ if (manualInput) {
293
+ const parsed = parseRedirectUrl(manualInput);
294
+ if (parsed.state && parsed.state !== verifier) {
295
+ throw new Error("OAuth state mismatch - possible CSRF attack");
296
+ }
297
+ code = parsed.code;
298
+ }
299
+ }
300
+ }
301
+ else {
302
+ // Original flow: just wait for callback
303
+ const result = await server.waitForCode();
304
+ if (result?.code) {
305
+ if (result.state !== verifier) {
306
+ throw new Error("OAuth state mismatch - possible CSRF attack");
307
+ }
308
+ code = result.code;
309
+ }
310
+ }
311
+ if (!code) {
312
+ throw new Error("No authorization code received");
232
313
  }
233
314
  // Exchange code for tokens
234
315
  onProgress?.("Exchanging authorization code for tokens...");
@@ -271,7 +352,7 @@ export async function loginGeminiCli(onAuth, onProgress) {
271
352
  return credentials;
272
353
  }
273
354
  finally {
274
- server.close();
355
+ server.server.close();
275
356
  }
276
357
  }
277
358
  //# sourceMappingURL=google-gemini-cli.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"google-gemini-cli.js","sourceRoot":"","sources":["../../../src/utils/oauth/google-gemini-cli.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAGzC,MAAM,MAAM,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACtC,MAAM,SAAS,GAAG,MAAM,CACvB,kGAAkG,CAClG,CAAC;AACF,MAAM,aAAa,GAAG,MAAM,CAAC,kDAAkD,CAAC,CAAC;AACjF,MAAM,YAAY,GAAG,sCAAsC,CAAC;AAC5D,MAAM,MAAM,GAAG;IACd,gDAAgD;IAChD,gDAAgD;IAChD,kDAAkD;CAClD,CAAC;AACF,MAAM,QAAQ,GAAG,8CAA8C,CAAC;AAChE,MAAM,SAAS,GAAG,qCAAqC,CAAC;AACxD,MAAM,oBAAoB,GAAG,qCAAqC,CAAC;AAEnE;;GAEG;AACH,KAAK,UAAU,mBAAmB,GAG/B;IACF,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;IAE9C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;QACvC,IAAI,WAA6D,CAAC;QAClE,IAAI,UAAkC,CAAC;QAEvC,MAAM,WAAW,GAAG,IAAI,OAAO,CAAkC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC;YAC9E,WAAW,GAAG,GAAG,CAAC;YAClB,UAAU,GAAG,GAAG,CAAC;QAAA,CACjB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC;YACzC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,EAAE,uBAAuB,CAAC,CAAC;YAE5D,IAAI,GAAG,CAAC,QAAQ,KAAK,iBAAiB,EAAE,CAAC;gBACxC,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAC5C,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAE5C,IAAI,KAAK,EAAE,CAAC;oBACX,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAC;oBACpD,GAAG,CAAC,GAAG,CACN,uDAAuD,KAAK,qDAAqD,CACjH,CAAC;oBACF,UAAU,CAAC,IAAI,KAAK,CAAC,gBAAgB,KAAK,EAAE,CAAC,CAAC,CAAC;oBAC/C,OAAO;gBACR,CAAC;gBAED,IAAI,IAAI,IAAI,KAAK,EAAE,CAAC;oBACnB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAC;oBACpD,GAAG,CAAC,GAAG,CACN,0HAA0H,CAC1H,CAAC;oBACF,WAAW,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC9B,CAAC;qBAAM,CAAC;oBACP,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAC;oBACpD,GAAG,CAAC,GAAG,CACN,iGAAiG,CACjG,CAAC;oBACF,UAAU,CAAC,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC,CAAC;gBAC5D,CAAC;YACF,CAAC;iBAAM,CAAC;gBACP,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACnB,GAAG,CAAC,GAAG,EAAE,CAAC;YACX,CAAC;QAAA,CACD,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,CAAC,CAAC;QAAA,CACZ,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC;YACtC,OAAO,CAAC;gBACP,MAAM;gBACN,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW;aAC1B,CAAC,CAAC;QAAA,CACH,CAAC,CAAC;IAAA,CACH,CAAC,CAAC;AAAA,CACH;AAeD;;GAEG;AACH,SAAS,IAAI,CAAC,EAAU,EAAiB;IACxC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAAA,CACzD;AAED;;GAEG;AACH,SAAS,gBAAgB,CAAC,YAA0D,EAAsB;IACzG,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACjE,MAAM,WAAW,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC1D,OAAO,WAAW,EAAE,EAAE,IAAI,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AAAA,CAC9C;AAED;;GAEG;AACH,KAAK,UAAU,eAAe,CAAC,WAAmB,EAAE,UAAsC,EAAmB;IAC5G,MAAM,OAAO,GAAG;QACf,aAAa,EAAE,UAAU,WAAW,EAAE;QACtC,cAAc,EAAE,kBAAkB;QAClC,YAAY,EAAE,iCAAiC;QAC/C,mBAAmB,EAAE,iBAAiB;KACtC,CAAC;IAEF,kDAAkD;IAClD,UAAU,EAAE,CAAC,oDAAoD,CAAC,CAAC;IACnE,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,GAAG,oBAAoB,4BAA4B,EAAE;QACrF,MAAM,EAAE,MAAM;QACd,OAAO;QACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACpB,QAAQ,EAAE;gBACT,OAAO,EAAE,iBAAiB;gBAC1B,QAAQ,EAAE,sBAAsB;gBAChC,UAAU,EAAE,QAAQ;aACpB;SACD,CAAC;KACF,CAAC,CAAC;IAEH,IAAI,YAAY,CAAC,EAAE,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,CAAC,MAAM,YAAY,CAAC,IAAI,EAAE,CAA0B,CAAC;QAElE,yCAAyC;QACzC,IAAI,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC,uBAAuB,CAAC;QACrC,CAAC;QAED,+CAA+C;QAC/C,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,MAAM,CAAC;QAE7D,UAAU,EAAE,CAAC,oEAAoE,CAAC,CAAC;QAEnF,4DAA4D;QAC5D,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC;YAC/C,MAAM,eAAe,GAAG,MAAM,KAAK,CAAC,GAAG,oBAAoB,yBAAyB,EAAE;gBACrF,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACpB,MAAM;oBACN,QAAQ,EAAE;wBACT,OAAO,EAAE,iBAAiB;wBAC1B,QAAQ,EAAE,sBAAsB;wBAChC,UAAU,EAAE,QAAQ;qBACpB;iBACD,CAAC;aACF,CAAC,CAAC;YAEH,IAAI,eAAe,CAAC,EAAE,EAAE,CAAC;gBACxB,MAAM,WAAW,GAAG,CAAC,MAAM,eAAe,CAAC,IAAI,EAAE,CAAuB,CAAC;gBACzE,MAAM,SAAS,GAAG,WAAW,CAAC,QAAQ,EAAE,uBAAuB,EAAE,EAAE,CAAC;gBAEpE,IAAI,WAAW,CAAC,IAAI,IAAI,SAAS,EAAE,CAAC;oBACnC,OAAO,SAAS,CAAC;gBAClB,CAAC;YACF,CAAC;YAED,uBAAuB;YACvB,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;gBACjB,UAAU,EAAE,CAAC,6CAA6C,OAAO,GAAG,CAAC,SAAS,CAAC,CAAC;gBAChF,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;YAClB,CAAC;QACF,CAAC;IACF,CAAC;IAED,MAAM,IAAI,KAAK,CACd,0DAA0D;QACzD,yEAAyE,CAC1E,CAAC;AAAA,CACF;AAED;;GAEG;AACH,KAAK,UAAU,YAAY,CAAC,WAAmB,EAA+B;IAC7E,IAAI,CAAC;QACJ,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,wDAAwD,EAAE;YACtF,OAAO,EAAE;gBACR,aAAa,EAAE,UAAU,WAAW,EAAE;aACtC;SACD,CAAC,CAAC;QAEH,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAuB,CAAC;YAC3D,OAAO,IAAI,CAAC,KAAK,CAAC;QACnB,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,mCAAmC;IACpC,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,YAAoB,EAAE,SAAiB,EAA6B;IACjH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;QACvC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;QAChE,IAAI,EAAE,IAAI,eAAe,CAAC;YACzB,SAAS,EAAE,SAAS;YACpB,aAAa,EAAE,aAAa;YAC5B,aAAa,EAAE,YAAY;YAC3B,UAAU,EAAE,eAAe;SAC3B,CAAC;KACF,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,sCAAsC,KAAK,EAAE,CAAC,CAAC;IAChE,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAIlC,CAAC;IAEF,OAAO;QACN,OAAO,EAAE,IAAI,CAAC,aAAa,IAAI,YAAY;QAC3C,MAAM,EAAE,IAAI,CAAC,YAAY;QACzB,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI;QAC5D,SAAS;KACT,CAAC;AAAA,CACF;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,MAA8D,EAC9D,UAAsC,EACV;IAC5B,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,MAAM,YAAY,EAAE,CAAC;IAErD,kCAAkC;IAClC,UAAU,EAAE,CAAC,6CAA6C,CAAC,CAAC;IAC5D,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,mBAAmB,EAAE,CAAC;IAExD,IAAI,CAAC;QACJ,0BAA0B;QAC1B,MAAM,UAAU,GAAG,IAAI,eAAe,CAAC;YACtC,SAAS,EAAE,SAAS;YACpB,aAAa,EAAE,MAAM;YACrB,YAAY,EAAE,YAAY;YAC1B,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;YACvB,cAAc,EAAE,SAAS;YACzB,qBAAqB,EAAE,MAAM;YAC7B,KAAK,EAAE,QAAQ;YACf,WAAW,EAAE,SAAS;YACtB,MAAM,EAAE,SAAS;SACjB,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,GAAG,QAAQ,IAAI,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC;QAEvD,iCAAiC;QACjC,MAAM,CAAC;YACN,GAAG,EAAE,OAAO;YACZ,YAAY,EAAE,oFAAoF;SAClG,CAAC,CAAC;QAEH,wBAAwB;QACxB,UAAU,EAAE,CAAC,+BAA+B,CAAC,CAAC;QAC9C,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,OAAO,EAAE,CAAC;QAExC,uBAAuB;QACvB,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QAChE,CAAC;QAED,2BAA2B;QAC3B,UAAU,EAAE,CAAC,6CAA6C,CAAC,CAAC;QAC5D,MAAM,aAAa,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;YAC5C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACR,cAAc,EAAE,mCAAmC;aACnD;YACD,IAAI,EAAE,IAAI,eAAe,CAAC;gBACzB,SAAS,EAAE,SAAS;gBACpB,aAAa,EAAE,aAAa;gBAC5B,IAAI;gBACJ,UAAU,EAAE,oBAAoB;gBAChC,YAAY,EAAE,YAAY;gBAC1B,aAAa,EAAE,QAAQ;aACvB,CAAC;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC;YACzC,MAAM,IAAI,KAAK,CAAC,0BAA0B,KAAK,EAAE,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,SAAS,GAAG,CAAC,MAAM,aAAa,CAAC,IAAI,EAAE,CAI5C,CAAC;QAEF,IAAI,CAAC,SAAS,CAAC,aAAa,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;QACjE,CAAC;QAED,iBAAiB;QACjB,UAAU,EAAE,CAAC,sBAAsB,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QAEzD,mBAAmB;QACnB,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,SAAS,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAE5E,2EAA2E;QAC3E,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,UAAU,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QAE3E,MAAM,WAAW,GAAqB;YACrC,OAAO,EAAE,SAAS,CAAC,aAAa;YAChC,MAAM,EAAE,SAAS,CAAC,YAAY;YAC9B,OAAO,EAAE,SAAS;YAClB,SAAS;YACT,KAAK;SACL,CAAC;QAEF,OAAO,WAAW,CAAC;IACpB,CAAC;YAAS,CAAC;QACV,MAAM,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;AAAA,CACD","sourcesContent":["/**\n * Gemini CLI OAuth flow (Google Cloud Code Assist)\n * Standard Gemini models only (gemini-2.0-flash, gemini-2.5-*)\n *\n * NOTE: This module uses Node.js http.createServer for the OAuth callback.\n * It is only intended for CLI use, not browser environments.\n */\n\nimport type { Server } from \"http\";\nimport { generatePKCE } from \"./pkce.js\";\nimport type { OAuthCredentials } from \"./types.js\";\n\nconst decode = (s: string) => atob(s);\nconst CLIENT_ID = decode(\n\t\"NjgxMjU1ODA5Mzk1LW9vOGZ0Mm9wcmRybnA5ZTNhcWY2YXYzaG1kaWIxMzVqLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29t\",\n);\nconst CLIENT_SECRET = decode(\"R09DU1BYLTR1SGdNUG0tMW83U2stZ2VWNkN1NWNsWEZzeGw=\");\nconst REDIRECT_URI = \"http://localhost:8085/oauth2callback\";\nconst SCOPES = [\n\t\"https://www.googleapis.com/auth/cloud-platform\",\n\t\"https://www.googleapis.com/auth/userinfo.email\",\n\t\"https://www.googleapis.com/auth/userinfo.profile\",\n];\nconst AUTH_URL = \"https://accounts.google.com/o/oauth2/v2/auth\";\nconst TOKEN_URL = \"https://oauth2.googleapis.com/token\";\nconst CODE_ASSIST_ENDPOINT = \"https://cloudcode-pa.googleapis.com\";\n\n/**\n * Start a local HTTP server to receive the OAuth callback\n */\nasync function startCallbackServer(): Promise<{\n\tserver: Server;\n\tgetCode: () => Promise<{ code: string; state: string }>;\n}> {\n\tconst { createServer } = await import(\"http\");\n\n\treturn new Promise((resolve, reject) => {\n\t\tlet codeResolve: (value: { code: string; state: string }) => void;\n\t\tlet codeReject: (error: Error) => void;\n\n\t\tconst codePromise = new Promise<{ code: string; state: string }>((res, rej) => {\n\t\t\tcodeResolve = res;\n\t\t\tcodeReject = rej;\n\t\t});\n\n\t\tconst server = createServer((req, res) => {\n\t\t\tconst url = new URL(req.url || \"\", `http://localhost:8085`);\n\n\t\t\tif (url.pathname === \"/oauth2callback\") {\n\t\t\t\tconst code = url.searchParams.get(\"code\");\n\t\t\t\tconst state = url.searchParams.get(\"state\");\n\t\t\t\tconst error = url.searchParams.get(\"error\");\n\n\t\t\t\tif (error) {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\t`<html><body><h1>Authentication Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`,\n\t\t\t\t\t);\n\t\t\t\t\tcodeReject(new Error(`OAuth error: ${error}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (code && state) {\n\t\t\t\t\tres.writeHead(200, { \"Content-Type\": \"text/html\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\t`<html><body><h1>Authentication Successful</h1><p>You can close this window and return to the terminal.</p></body></html>`,\n\t\t\t\t\t);\n\t\t\t\t\tcodeResolve({ code, state });\n\t\t\t\t} else {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\t`<html><body><h1>Authentication Failed</h1><p>Missing code or state parameter.</p></body></html>`,\n\t\t\t\t\t);\n\t\t\t\t\tcodeReject(new Error(\"Missing code or state in callback\"));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tres.writeHead(404);\n\t\t\t\tres.end();\n\t\t\t}\n\t\t});\n\n\t\tserver.on(\"error\", (err) => {\n\t\t\treject(err);\n\t\t});\n\n\t\tserver.listen(8085, \"127.0.0.1\", () => {\n\t\t\tresolve({\n\t\t\t\tserver,\n\t\t\t\tgetCode: () => codePromise,\n\t\t\t});\n\t\t});\n\t});\n}\n\ninterface LoadCodeAssistPayload {\n\tcloudaicompanionProject?: string;\n\tcurrentTier?: { id?: string };\n\tallowedTiers?: Array<{ id?: string; isDefault?: boolean }>;\n}\n\ninterface OnboardUserPayload {\n\tdone?: boolean;\n\tresponse?: {\n\t\tcloudaicompanionProject?: { id?: string };\n\t};\n}\n\n/**\n * Wait helper for onboarding retries\n */\nfunction wait(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Get default tier ID from allowed tiers\n */\nfunction getDefaultTierId(allowedTiers?: Array<{ id?: string; isDefault?: boolean }>): string | undefined {\n\tif (!allowedTiers || allowedTiers.length === 0) return undefined;\n\tconst defaultTier = allowedTiers.find((t) => t.isDefault);\n\treturn defaultTier?.id ?? allowedTiers[0]?.id;\n}\n\n/**\n * Discover or provision a Google Cloud project for the user\n */\nasync function discoverProject(accessToken: string, onProgress?: (message: string) => void): Promise<string> {\n\tconst headers = {\n\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"User-Agent\": \"google-api-nodejs-client/9.15.1\",\n\t\t\"X-Goog-Api-Client\": \"gl-node/22.17.0\",\n\t};\n\n\t// Try to load existing project via loadCodeAssist\n\tonProgress?.(\"Checking for existing Cloud Code Assist project...\");\n\tconst loadResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {\n\t\tmethod: \"POST\",\n\t\theaders,\n\t\tbody: JSON.stringify({\n\t\t\tmetadata: {\n\t\t\t\tideType: \"IDE_UNSPECIFIED\",\n\t\t\t\tplatform: \"PLATFORM_UNSPECIFIED\",\n\t\t\t\tpluginType: \"GEMINI\",\n\t\t\t},\n\t\t}),\n\t});\n\n\tif (loadResponse.ok) {\n\t\tconst data = (await loadResponse.json()) as LoadCodeAssistPayload;\n\n\t\t// If we have an existing project, use it\n\t\tif (data.cloudaicompanionProject) {\n\t\t\treturn data.cloudaicompanionProject;\n\t\t}\n\n\t\t// Otherwise, try to onboard with the FREE tier\n\t\tconst tierId = getDefaultTierId(data.allowedTiers) ?? \"FREE\";\n\n\t\tonProgress?.(\"Provisioning Cloud Code Assist project (this may take a moment)...\");\n\n\t\t// Onboard with retries (the API may take time to provision)\n\t\tfor (let attempt = 0; attempt < 10; attempt++) {\n\t\t\tconst onboardResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders,\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\ttierId,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\tideType: \"IDE_UNSPECIFIED\",\n\t\t\t\t\t\tplatform: \"PLATFORM_UNSPECIFIED\",\n\t\t\t\t\t\tpluginType: \"GEMINI\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t});\n\n\t\t\tif (onboardResponse.ok) {\n\t\t\t\tconst onboardData = (await onboardResponse.json()) as OnboardUserPayload;\n\t\t\t\tconst projectId = onboardData.response?.cloudaicompanionProject?.id;\n\n\t\t\t\tif (onboardData.done && projectId) {\n\t\t\t\t\treturn projectId;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Wait before retrying\n\t\t\tif (attempt < 9) {\n\t\t\t\tonProgress?.(`Waiting for project provisioning (attempt ${attempt + 2}/10)...`);\n\t\t\t\tawait wait(3000);\n\t\t\t}\n\t\t}\n\t}\n\n\tthrow new Error(\n\t\t\"Could not discover or provision a Google Cloud project. \" +\n\t\t\t\"Please ensure you have access to Google Cloud Code Assist (Gemini CLI).\",\n\t);\n}\n\n/**\n * Get user email from the access token\n */\nasync function getUserEmail(accessToken: string): Promise<string | undefined> {\n\ttry {\n\t\tconst response = await fetch(\"https://www.googleapis.com/oauth2/v1/userinfo?alt=json\", {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\n\t\tif (response.ok) {\n\t\t\tconst data = (await response.json()) as { email?: string };\n\t\t\treturn data.email;\n\t\t}\n\t} catch {\n\t\t// Ignore errors, email is optional\n\t}\n\treturn undefined;\n}\n\n/**\n * Refresh Google Cloud Code Assist token\n */\nexport async function refreshGoogleCloudToken(refreshToken: string, projectId: string): Promise<OAuthCredentials> {\n\tconst response = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\tbody: new URLSearchParams({\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tclient_secret: CLIENT_SECRET,\n\t\t\trefresh_token: refreshToken,\n\t\t\tgrant_type: \"refresh_token\",\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tconst error = await response.text();\n\t\tthrow new Error(`Google Cloud token refresh failed: ${error}`);\n\t}\n\n\tconst data = (await response.json()) as {\n\t\taccess_token: string;\n\t\texpires_in: number;\n\t\trefresh_token?: string;\n\t};\n\n\treturn {\n\t\trefresh: data.refresh_token || refreshToken,\n\t\taccess: data.access_token,\n\t\texpires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,\n\t\tprojectId,\n\t};\n}\n\n/**\n * Login with Gemini CLI (Google Cloud Code Assist) OAuth\n *\n * @param onAuth - Callback with URL and optional instructions\n * @param onProgress - Optional progress callback\n */\nexport async function loginGeminiCli(\n\tonAuth: (info: { url: string; instructions?: string }) => void,\n\tonProgress?: (message: string) => void,\n): Promise<OAuthCredentials> {\n\tconst { verifier, challenge } = await generatePKCE();\n\n\t// Start local server for callback\n\tonProgress?.(\"Starting local server for OAuth callback...\");\n\tconst { server, getCode } = await startCallbackServer();\n\n\ttry {\n\t\t// Build authorization URL\n\t\tconst authParams = new URLSearchParams({\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tresponse_type: \"code\",\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tscope: SCOPES.join(\" \"),\n\t\t\tcode_challenge: challenge,\n\t\t\tcode_challenge_method: \"S256\",\n\t\t\tstate: verifier,\n\t\t\taccess_type: \"offline\",\n\t\t\tprompt: \"consent\",\n\t\t});\n\n\t\tconst authUrl = `${AUTH_URL}?${authParams.toString()}`;\n\n\t\t// Notify caller with URL to open\n\t\tonAuth({\n\t\t\turl: authUrl,\n\t\t\tinstructions: \"Complete the sign-in in your browser. The callback will be captured automatically.\",\n\t\t});\n\n\t\t// Wait for the callback\n\t\tonProgress?.(\"Waiting for OAuth callback...\");\n\t\tconst { code, state } = await getCode();\n\n\t\t// Verify state matches\n\t\tif (state !== verifier) {\n\t\t\tthrow new Error(\"OAuth state mismatch - possible CSRF attack\");\n\t\t}\n\n\t\t// Exchange code for tokens\n\t\tonProgress?.(\"Exchanging authorization code for tokens...\");\n\t\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\tclient_secret: CLIENT_SECRET,\n\t\t\t\tcode,\n\t\t\t\tgrant_type: \"authorization_code\",\n\t\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\t\tcode_verifier: verifier,\n\t\t\t}),\n\t\t});\n\n\t\tif (!tokenResponse.ok) {\n\t\t\tconst error = await tokenResponse.text();\n\t\t\tthrow new Error(`Token exchange failed: ${error}`);\n\t\t}\n\n\t\tconst tokenData = (await tokenResponse.json()) as {\n\t\t\taccess_token: string;\n\t\t\trefresh_token: string;\n\t\t\texpires_in: number;\n\t\t};\n\n\t\tif (!tokenData.refresh_token) {\n\t\t\tthrow new Error(\"No refresh token received. Please try again.\");\n\t\t}\n\n\t\t// Get user email\n\t\tonProgress?.(\"Getting user info...\");\n\t\tconst email = await getUserEmail(tokenData.access_token);\n\n\t\t// Discover project\n\t\tconst projectId = await discoverProject(tokenData.access_token, onProgress);\n\n\t\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\t\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\t\tconst credentials: OAuthCredentials = {\n\t\t\trefresh: tokenData.refresh_token,\n\t\t\taccess: tokenData.access_token,\n\t\t\texpires: expiresAt,\n\t\t\tprojectId,\n\t\t\temail,\n\t\t};\n\n\t\treturn credentials;\n\t} finally {\n\t\tserver.close();\n\t}\n}\n"]}
1
+ {"version":3,"file":"google-gemini-cli.js","sourceRoot":"","sources":["../../../src/utils/oauth/google-gemini-cli.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Gemini CLI OAuth flow (Google Cloud Code Assist)\n * Standard Gemini models only (gemini-2.0-flash, gemini-2.5-*)\n *\n * NOTE: This module uses Node.js http.createServer for the OAuth callback.\n * It is only intended for CLI use, not browser environments.\n */\n\nimport type { Server } from \"http\";\nimport { generatePKCE } from \"./pkce.js\";\nimport type { OAuthCredentials } from \"./types.js\";\n\nconst decode = (s: string) => atob(s);\nconst CLIENT_ID = decode(\n\t\"NjgxMjU1ODA5Mzk1LW9vOGZ0Mm9wcmRybnA5ZTNhcWY2YXYzaG1kaWIxMzVqLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29t\",\n);\nconst CLIENT_SECRET = decode(\"R09DU1BYLTR1SGdNUG0tMW83U2stZ2VWNkN1NWNsWEZzeGw=\");\nconst REDIRECT_URI = \"http://localhost:8085/oauth2callback\";\nconst SCOPES = [\n\t\"https://www.googleapis.com/auth/cloud-platform\",\n\t\"https://www.googleapis.com/auth/userinfo.email\",\n\t\"https://www.googleapis.com/auth/userinfo.profile\",\n];\nconst AUTH_URL = \"https://accounts.google.com/o/oauth2/v2/auth\";\nconst TOKEN_URL = \"https://oauth2.googleapis.com/token\";\nconst CODE_ASSIST_ENDPOINT = \"https://cloudcode-pa.googleapis.com\";\n\ntype CallbackServerInfo = {\n\tserver: Server;\n\tcancelWait: () => void;\n\twaitForCode: () => Promise<{ code: string; state: string } | null>;\n};\n\n/**\n * Start a local HTTP server to receive the OAuth callback\n */\nasync function startCallbackServer(): Promise<CallbackServerInfo> {\n\tconst { createServer } = await import(\"http\");\n\n\treturn new Promise((resolve, reject) => {\n\t\tlet result: { code: string; state: string } | null = null;\n\t\tlet cancelled = false;\n\n\t\tconst server = createServer((req, res) => {\n\t\t\tconst url = new URL(req.url || \"\", `http://localhost:8085`);\n\n\t\t\tif (url.pathname === \"/oauth2callback\") {\n\t\t\t\tconst code = url.searchParams.get(\"code\");\n\t\t\t\tconst state = url.searchParams.get(\"state\");\n\t\t\t\tconst error = url.searchParams.get(\"error\");\n\n\t\t\t\tif (error) {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\t`<html><body><h1>Authentication Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (code && state) {\n\t\t\t\t\tres.writeHead(200, { \"Content-Type\": \"text/html\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\t`<html><body><h1>Authentication Successful</h1><p>You can close this window and return to the terminal.</p></body></html>`,\n\t\t\t\t\t);\n\t\t\t\t\tresult = { code, state };\n\t\t\t\t} else {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\t`<html><body><h1>Authentication Failed</h1><p>Missing code or state parameter.</p></body></html>`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tres.writeHead(404);\n\t\t\t\tres.end();\n\t\t\t}\n\t\t});\n\n\t\tserver.on(\"error\", (err) => {\n\t\t\treject(err);\n\t\t});\n\n\t\tserver.listen(8085, \"127.0.0.1\", () => {\n\t\t\tresolve({\n\t\t\t\tserver,\n\t\t\t\tcancelWait: () => {\n\t\t\t\t\tcancelled = true;\n\t\t\t\t},\n\t\t\t\twaitForCode: async () => {\n\t\t\t\t\tconst sleep = () => new Promise((r) => setTimeout(r, 100));\n\t\t\t\t\twhile (!result && !cancelled) {\n\t\t\t\t\t\tawait sleep();\n\t\t\t\t\t}\n\t\t\t\t\treturn result;\n\t\t\t\t},\n\t\t\t});\n\t\t});\n\t});\n}\n\n/**\n * Parse redirect URL to extract code and state\n */\nfunction parseRedirectUrl(input: string): { code?: string; state?: string } {\n\tconst value = input.trim();\n\tif (!value) return {};\n\n\ttry {\n\t\tconst url = new URL(value);\n\t\treturn {\n\t\t\tcode: url.searchParams.get(\"code\") ?? undefined,\n\t\t\tstate: url.searchParams.get(\"state\") ?? undefined,\n\t\t};\n\t} catch {\n\t\t// Not a URL, return empty\n\t\treturn {};\n\t}\n}\n\ninterface LoadCodeAssistPayload {\n\tcloudaicompanionProject?: string;\n\tcurrentTier?: { id?: string };\n\tallowedTiers?: Array<{ id?: string; isDefault?: boolean }>;\n}\n\ninterface OnboardUserPayload {\n\tdone?: boolean;\n\tresponse?: {\n\t\tcloudaicompanionProject?: { id?: string };\n\t};\n}\n\n/**\n * Wait helper for onboarding retries\n */\nfunction wait(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Get default tier ID from allowed tiers\n */\nfunction getDefaultTierId(allowedTiers?: Array<{ id?: string; isDefault?: boolean }>): string | undefined {\n\tif (!allowedTiers || allowedTiers.length === 0) return undefined;\n\tconst defaultTier = allowedTiers.find((t) => t.isDefault);\n\treturn defaultTier?.id ?? allowedTiers[0]?.id;\n}\n\n/**\n * Discover or provision a Google Cloud project for the user\n */\nasync function discoverProject(accessToken: string, onProgress?: (message: string) => void): Promise<string> {\n\tconst headers = {\n\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"User-Agent\": \"google-api-nodejs-client/9.15.1\",\n\t\t\"X-Goog-Api-Client\": \"gl-node/22.17.0\",\n\t};\n\n\t// Try to load existing project via loadCodeAssist\n\tonProgress?.(\"Checking for existing Cloud Code Assist project...\");\n\tconst loadResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {\n\t\tmethod: \"POST\",\n\t\theaders,\n\t\tbody: JSON.stringify({\n\t\t\tmetadata: {\n\t\t\t\tideType: \"IDE_UNSPECIFIED\",\n\t\t\t\tplatform: \"PLATFORM_UNSPECIFIED\",\n\t\t\t\tpluginType: \"GEMINI\",\n\t\t\t},\n\t\t}),\n\t});\n\n\tif (loadResponse.ok) {\n\t\tconst data = (await loadResponse.json()) as LoadCodeAssistPayload;\n\n\t\t// If we have an existing project, use it\n\t\tif (data.cloudaicompanionProject) {\n\t\t\treturn data.cloudaicompanionProject;\n\t\t}\n\n\t\t// Otherwise, try to onboard with the FREE tier\n\t\tconst tierId = getDefaultTierId(data.allowedTiers) ?? \"FREE\";\n\n\t\tonProgress?.(\"Provisioning Cloud Code Assist project (this may take a moment)...\");\n\n\t\t// Onboard with retries (the API may take time to provision)\n\t\tfor (let attempt = 0; attempt < 10; attempt++) {\n\t\t\tconst onboardResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders,\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\ttierId,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\tideType: \"IDE_UNSPECIFIED\",\n\t\t\t\t\t\tplatform: \"PLATFORM_UNSPECIFIED\",\n\t\t\t\t\t\tpluginType: \"GEMINI\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t});\n\n\t\t\tif (onboardResponse.ok) {\n\t\t\t\tconst onboardData = (await onboardResponse.json()) as OnboardUserPayload;\n\t\t\t\tconst projectId = onboardData.response?.cloudaicompanionProject?.id;\n\n\t\t\t\tif (onboardData.done && projectId) {\n\t\t\t\t\treturn projectId;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Wait before retrying\n\t\t\tif (attempt < 9) {\n\t\t\t\tonProgress?.(`Waiting for project provisioning (attempt ${attempt + 2}/10)...`);\n\t\t\t\tawait wait(3000);\n\t\t\t}\n\t\t}\n\t}\n\n\tthrow new Error(\n\t\t\"Could not discover or provision a Google Cloud project. \" +\n\t\t\t\"Please ensure you have access to Google Cloud Code Assist (Gemini CLI).\",\n\t);\n}\n\n/**\n * Get user email from the access token\n */\nasync function getUserEmail(accessToken: string): Promise<string | undefined> {\n\ttry {\n\t\tconst response = await fetch(\"https://www.googleapis.com/oauth2/v1/userinfo?alt=json\", {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\n\t\tif (response.ok) {\n\t\t\tconst data = (await response.json()) as { email?: string };\n\t\t\treturn data.email;\n\t\t}\n\t} catch {\n\t\t// Ignore errors, email is optional\n\t}\n\treturn undefined;\n}\n\n/**\n * Refresh Google Cloud Code Assist token\n */\nexport async function refreshGoogleCloudToken(refreshToken: string, projectId: string): Promise<OAuthCredentials> {\n\tconst response = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\tbody: new URLSearchParams({\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tclient_secret: CLIENT_SECRET,\n\t\t\trefresh_token: refreshToken,\n\t\t\tgrant_type: \"refresh_token\",\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tconst error = await response.text();\n\t\tthrow new Error(`Google Cloud token refresh failed: ${error}`);\n\t}\n\n\tconst data = (await response.json()) as {\n\t\taccess_token: string;\n\t\texpires_in: number;\n\t\trefresh_token?: string;\n\t};\n\n\treturn {\n\t\trefresh: data.refresh_token || refreshToken,\n\t\taccess: data.access_token,\n\t\texpires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,\n\t\tprojectId,\n\t};\n}\n\n/**\n * Login with Gemini CLI (Google Cloud Code Assist) OAuth\n *\n * @param onAuth - Callback with URL and optional instructions\n * @param onProgress - Optional progress callback\n * @param onManualCodeInput - Optional promise that resolves with user-pasted redirect URL.\n * Races with browser callback - whichever completes first wins.\n */\nexport async function loginGeminiCli(\n\tonAuth: (info: { url: string; instructions?: string }) => void,\n\tonProgress?: (message: string) => void,\n\tonManualCodeInput?: () => Promise<string>,\n): Promise<OAuthCredentials> {\n\tconst { verifier, challenge } = await generatePKCE();\n\n\t// Start local server for callback\n\tonProgress?.(\"Starting local server for OAuth callback...\");\n\tconst server = await startCallbackServer();\n\n\tlet code: string | undefined;\n\n\ttry {\n\t\t// Build authorization URL\n\t\tconst authParams = new URLSearchParams({\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tresponse_type: \"code\",\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tscope: SCOPES.join(\" \"),\n\t\t\tcode_challenge: challenge,\n\t\t\tcode_challenge_method: \"S256\",\n\t\t\tstate: verifier,\n\t\t\taccess_type: \"offline\",\n\t\t\tprompt: \"consent\",\n\t\t});\n\n\t\tconst authUrl = `${AUTH_URL}?${authParams.toString()}`;\n\n\t\t// Notify caller with URL to open\n\t\tonAuth({\n\t\t\turl: authUrl,\n\t\t\tinstructions: \"Complete the sign-in in your browser.\",\n\t\t});\n\n\t\t// Wait for the callback, racing with manual input if provided\n\t\tonProgress?.(\"Waiting for OAuth callback...\");\n\n\t\tif (onManualCodeInput) {\n\t\t\t// Race between browser callback and manual input\n\t\t\tlet manualInput: string | undefined;\n\t\t\tlet manualError: Error | undefined;\n\t\t\tconst manualPromise = onManualCodeInput()\n\t\t\t\t.then((input) => {\n\t\t\t\t\tmanualInput = input;\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tmanualError = err instanceof Error ? err : new Error(String(err));\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t});\n\n\t\t\tconst result = await server.waitForCode();\n\n\t\t\t// If manual input was cancelled, throw that error\n\t\t\tif (manualError) {\n\t\t\t\tthrow manualError;\n\t\t\t}\n\n\t\t\tif (result?.code) {\n\t\t\t\t// Browser callback won - verify state\n\t\t\t\tif (result.state !== verifier) {\n\t\t\t\t\tthrow new Error(\"OAuth state mismatch - possible CSRF attack\");\n\t\t\t\t}\n\t\t\t\tcode = result.code;\n\t\t\t} else if (manualInput) {\n\t\t\t\t// Manual input won\n\t\t\t\tconst parsed = parseRedirectUrl(manualInput);\n\t\t\t\tif (parsed.state && parsed.state !== verifier) {\n\t\t\t\t\tthrow new Error(\"OAuth state mismatch - possible CSRF attack\");\n\t\t\t\t}\n\t\t\t\tcode = parsed.code;\n\t\t\t}\n\n\t\t\t// If still no code, wait for manual promise and try that\n\t\t\tif (!code) {\n\t\t\t\tawait manualPromise;\n\t\t\t\tif (manualError) {\n\t\t\t\t\tthrow manualError;\n\t\t\t\t}\n\t\t\t\tif (manualInput) {\n\t\t\t\t\tconst parsed = parseRedirectUrl(manualInput);\n\t\t\t\t\tif (parsed.state && parsed.state !== verifier) {\n\t\t\t\t\t\tthrow new Error(\"OAuth state mismatch - possible CSRF attack\");\n\t\t\t\t\t}\n\t\t\t\t\tcode = parsed.code;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Original flow: just wait for callback\n\t\t\tconst result = await server.waitForCode();\n\t\t\tif (result?.code) {\n\t\t\t\tif (result.state !== verifier) {\n\t\t\t\t\tthrow new Error(\"OAuth state mismatch - possible CSRF attack\");\n\t\t\t\t}\n\t\t\t\tcode = result.code;\n\t\t\t}\n\t\t}\n\n\t\tif (!code) {\n\t\t\tthrow new Error(\"No authorization code received\");\n\t\t}\n\n\t\t// Exchange code for tokens\n\t\tonProgress?.(\"Exchanging authorization code for tokens...\");\n\t\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\tclient_secret: CLIENT_SECRET,\n\t\t\t\tcode,\n\t\t\t\tgrant_type: \"authorization_code\",\n\t\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\t\tcode_verifier: verifier,\n\t\t\t}),\n\t\t});\n\n\t\tif (!tokenResponse.ok) {\n\t\t\tconst error = await tokenResponse.text();\n\t\t\tthrow new Error(`Token exchange failed: ${error}`);\n\t\t}\n\n\t\tconst tokenData = (await tokenResponse.json()) as {\n\t\t\taccess_token: string;\n\t\t\trefresh_token: string;\n\t\t\texpires_in: number;\n\t\t};\n\n\t\tif (!tokenData.refresh_token) {\n\t\t\tthrow new Error(\"No refresh token received. Please try again.\");\n\t\t}\n\n\t\t// Get user email\n\t\tonProgress?.(\"Getting user info...\");\n\t\tconst email = await getUserEmail(tokenData.access_token);\n\n\t\t// Discover project\n\t\tconst projectId = await discoverProject(tokenData.access_token, onProgress);\n\n\t\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\t\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\t\tconst credentials: OAuthCredentials = {\n\t\t\trefresh: tokenData.refresh_token,\n\t\t\taccess: tokenData.access_token,\n\t\t\texpires: expiresAt,\n\t\t\tprojectId,\n\t\t\temail,\n\t\t};\n\n\t\treturn credentials;\n\t} finally {\n\t\tserver.server.close();\n\t}\n}\n"]}
@@ -12,6 +12,7 @@ export { loginAnthropic, refreshAnthropicToken } from "./anthropic.js";
12
12
  export { getGitHubCopilotBaseUrl, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.js";
13
13
  export { loginAntigravity, refreshAntigravityToken, } from "./google-antigravity.js";
14
14
  export { loginGeminiCli, refreshGoogleCloudToken, } from "./google-gemini-cli.js";
15
+ export { loginOpenAICodex, refreshOpenAICodexToken, } from "./openai-codex.js";
15
16
  export * from "./types.js";
16
17
  import type { OAuthCredentials, OAuthProvider, OAuthProviderInfo } from "./types.js";
17
18
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEvE,OAAO,EACN,uBAAuB,EACvB,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACN,gBAAgB,EAChB,uBAAuB,GACvB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACN,cAAc,EACd,uBAAuB,GACvB,MAAM,wBAAwB,CAAC;AAEhC,cAAc,YAAY,CAAC;AAU3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAErF;;;GAGG;AACH,wBAAsB,iBAAiB,CACtC,QAAQ,EAAE,aAAa,EACvB,WAAW,EAAE,gBAAgB,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CA+B3B;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CACnC,QAAQ,EAAE,aAAa,EACvB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAC3C,OAAO,CAAC;IAAE,cAAc,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAmBtE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,EAAE,CAuBvD","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n * - Google Cloud Code Assist (Gemini CLI)\n * - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)\n */\n\n// Anthropic\nexport { loginAnthropic, refreshAnthropicToken } from \"./anthropic.js\";\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.js\";\n// Google Antigravity\nexport {\n\tloginAntigravity,\n\trefreshAntigravityToken,\n} from \"./google-antigravity.js\";\n// Google Gemini CLI\nexport {\n\tloginGeminiCli,\n\trefreshGoogleCloudToken,\n} from \"./google-gemini-cli.js\";\n\nexport * from \"./types.js\";\n\n// ============================================================================\n// High-level API\n// ============================================================================\n\nimport { refreshAnthropicToken } from \"./anthropic.js\";\nimport { refreshGitHubCopilotToken } from \"./github-copilot.js\";\nimport { refreshAntigravityToken } from \"./google-antigravity.js\";\nimport { refreshGoogleCloudToken } from \"./google-gemini-cli.js\";\nimport type { OAuthCredentials, OAuthProvider, OAuthProviderInfo } from \"./types.js\";\n\n/**\n * Refresh token for any OAuth provider.\n * Saves the new credentials and returns the new access token.\n */\nexport async function refreshOAuthToken(\n\tprovider: OAuthProvider,\n\tcredentials: OAuthCredentials,\n): Promise<OAuthCredentials> {\n\tif (!credentials) {\n\t\tthrow new Error(`No OAuth credentials found for ${provider}`);\n\t}\n\n\tlet newCredentials: OAuthCredentials;\n\n\tswitch (provider) {\n\t\tcase \"anthropic\":\n\t\t\tnewCredentials = await refreshAnthropicToken(credentials.refresh);\n\t\t\tbreak;\n\t\tcase \"github-copilot\":\n\t\t\tnewCredentials = await refreshGitHubCopilotToken(credentials.refresh, credentials.enterpriseUrl);\n\t\t\tbreak;\n\t\tcase \"google-gemini-cli\":\n\t\t\tif (!credentials.projectId) {\n\t\t\t\tthrow new Error(\"Google Cloud credentials missing projectId\");\n\t\t\t}\n\t\t\tnewCredentials = await refreshGoogleCloudToken(credentials.refresh, credentials.projectId);\n\t\t\tbreak;\n\t\tcase \"google-antigravity\":\n\t\t\tif (!credentials.projectId) {\n\t\t\t\tthrow new Error(\"Antigravity credentials missing projectId\");\n\t\t\t}\n\t\t\tnewCredentials = await refreshAntigravityToken(credentials.refresh, credentials.projectId);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown OAuth provider: ${provider}`);\n\t}\n\n\treturn newCredentials;\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * For google-gemini-cli and antigravity, returns JSON-encoded { token, projectId }\n *\n * @returns API key string, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tprovider: OAuthProvider,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tlet creds = credentials[provider];\n\tif (!creds) {\n\t\treturn null;\n\t}\n\n\t// Refresh if expired\n\tif (Date.now() >= creds.expires) {\n\t\ttry {\n\t\t\tcreds = await refreshOAuthToken(provider, creds);\n\t\t} catch (_error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${provider}`);\n\t\t}\n\t}\n\n\t// For providers that need projectId, return JSON\n\tconst needsProjectId = provider === \"google-gemini-cli\" || provider === \"google-antigravity\";\n\tconst apiKey = needsProjectId ? JSON.stringify({ token: creds.access, projectId: creds.projectId }) : creds.access;\n\treturn { newCredentials: creds, apiKey };\n}\n\n/**\n * Get list of OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInfo[] {\n\treturn [\n\t\t{\n\t\t\tid: \"anthropic\",\n\t\t\tname: \"Anthropic (Claude Pro/Max)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"github-copilot\",\n\t\t\tname: \"GitHub Copilot\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"google-gemini-cli\",\n\t\t\tname: \"Google Cloud Code Assist (Gemini CLI)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"google-antigravity\",\n\t\t\tname: \"Antigravity (Gemini 3, Claude, GPT-OSS)\",\n\t\t\tavailable: true,\n\t\t},\n\t];\n}\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEvE,OAAO,EACN,uBAAuB,EACvB,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACN,gBAAgB,EAChB,uBAAuB,GACvB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACN,cAAc,EACd,uBAAuB,GACvB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACN,gBAAgB,EAChB,uBAAuB,GACvB,MAAM,mBAAmB,CAAC;AAE3B,cAAc,YAAY,CAAC;AAW3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAErF;;;GAGG;AACH,wBAAsB,iBAAiB,CACtC,QAAQ,EAAE,aAAa,EACvB,WAAW,EAAE,gBAAgB,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAkC3B;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CACnC,QAAQ,EAAE,aAAa,EACvB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAC3C,OAAO,CAAC;IAAE,cAAc,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAmBtE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,EAAE,CA4BvD","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n * - Google Cloud Code Assist (Gemini CLI)\n * - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)\n */\n\n// Anthropic\nexport { loginAnthropic, refreshAnthropicToken } from \"./anthropic.js\";\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.js\";\n// Google Antigravity\nexport {\n\tloginAntigravity,\n\trefreshAntigravityToken,\n} from \"./google-antigravity.js\";\n// Google Gemini CLI\nexport {\n\tloginGeminiCli,\n\trefreshGoogleCloudToken,\n} from \"./google-gemini-cli.js\";\n// OpenAI Codex (ChatGPT OAuth)\nexport {\n\tloginOpenAICodex,\n\trefreshOpenAICodexToken,\n} from \"./openai-codex.js\";\n\nexport * from \"./types.js\";\n\n// ============================================================================\n// High-level API\n// ============================================================================\n\nimport { refreshAnthropicToken } from \"./anthropic.js\";\nimport { refreshGitHubCopilotToken } from \"./github-copilot.js\";\nimport { refreshAntigravityToken } from \"./google-antigravity.js\";\nimport { refreshGoogleCloudToken } from \"./google-gemini-cli.js\";\nimport { refreshOpenAICodexToken } from \"./openai-codex.js\";\nimport type { OAuthCredentials, OAuthProvider, OAuthProviderInfo } from \"./types.js\";\n\n/**\n * Refresh token for any OAuth provider.\n * Saves the new credentials and returns the new access token.\n */\nexport async function refreshOAuthToken(\n\tprovider: OAuthProvider,\n\tcredentials: OAuthCredentials,\n): Promise<OAuthCredentials> {\n\tif (!credentials) {\n\t\tthrow new Error(`No OAuth credentials found for ${provider}`);\n\t}\n\n\tlet newCredentials: OAuthCredentials;\n\n\tswitch (provider) {\n\t\tcase \"anthropic\":\n\t\t\tnewCredentials = await refreshAnthropicToken(credentials.refresh);\n\t\t\tbreak;\n\t\tcase \"github-copilot\":\n\t\t\tnewCredentials = await refreshGitHubCopilotToken(credentials.refresh, credentials.enterpriseUrl);\n\t\t\tbreak;\n\t\tcase \"google-gemini-cli\":\n\t\t\tif (!credentials.projectId) {\n\t\t\t\tthrow new Error(\"Google Cloud credentials missing projectId\");\n\t\t\t}\n\t\t\tnewCredentials = await refreshGoogleCloudToken(credentials.refresh, credentials.projectId);\n\t\t\tbreak;\n\t\tcase \"google-antigravity\":\n\t\t\tif (!credentials.projectId) {\n\t\t\t\tthrow new Error(\"Antigravity credentials missing projectId\");\n\t\t\t}\n\t\t\tnewCredentials = await refreshAntigravityToken(credentials.refresh, credentials.projectId);\n\t\t\tbreak;\n\t\tcase \"openai-codex\":\n\t\t\tnewCredentials = await refreshOpenAICodexToken(credentials.refresh);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown OAuth provider: ${provider}`);\n\t}\n\n\treturn newCredentials;\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * For google-gemini-cli and antigravity, returns JSON-encoded { token, projectId }\n *\n * @returns API key string, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tprovider: OAuthProvider,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tlet creds = credentials[provider];\n\tif (!creds) {\n\t\treturn null;\n\t}\n\n\t// Refresh if expired\n\tif (Date.now() >= creds.expires) {\n\t\ttry {\n\t\t\tcreds = await refreshOAuthToken(provider, creds);\n\t\t} catch (_error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${provider}`);\n\t\t}\n\t}\n\n\t// For providers that need projectId, return JSON\n\tconst needsProjectId = provider === \"google-gemini-cli\" || provider === \"google-antigravity\";\n\tconst apiKey = needsProjectId ? JSON.stringify({ token: creds.access, projectId: creds.projectId }) : creds.access;\n\treturn { newCredentials: creds, apiKey };\n}\n\n/**\n * Get list of OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInfo[] {\n\treturn [\n\t\t{\n\t\t\tid: \"anthropic\",\n\t\t\tname: \"Anthropic (Claude Pro/Max)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"openai-codex\",\n\t\t\tname: \"ChatGPT Plus/Pro (Codex Subscription)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"github-copilot\",\n\t\t\tname: \"GitHub Copilot\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"google-gemini-cli\",\n\t\t\tname: \"Google Cloud Code Assist (Gemini CLI)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"google-antigravity\",\n\t\t\tname: \"Antigravity (Gemini 3, Claude, GPT-OSS)\",\n\t\t\tavailable: true,\n\t\t},\n\t];\n}\n"]}
@@ -16,6 +16,8 @@ export { getGitHubCopilotBaseUrl, loginGitHubCopilot, normalizeDomain, refreshGi
16
16
  export { loginAntigravity, refreshAntigravityToken, } from "./google-antigravity.js";
17
17
  // Google Gemini CLI
18
18
  export { loginGeminiCli, refreshGoogleCloudToken, } from "./google-gemini-cli.js";
19
+ // OpenAI Codex (ChatGPT OAuth)
20
+ export { loginOpenAICodex, refreshOpenAICodexToken, } from "./openai-codex.js";
19
21
  export * from "./types.js";
20
22
  // ============================================================================
21
23
  // High-level API
@@ -24,6 +26,7 @@ import { refreshAnthropicToken } from "./anthropic.js";
24
26
  import { refreshGitHubCopilotToken } from "./github-copilot.js";
25
27
  import { refreshAntigravityToken } from "./google-antigravity.js";
26
28
  import { refreshGoogleCloudToken } from "./google-gemini-cli.js";
29
+ import { refreshOpenAICodexToken } from "./openai-codex.js";
27
30
  /**
28
31
  * Refresh token for any OAuth provider.
29
32
  * Saves the new credentials and returns the new access token.
@@ -52,6 +55,9 @@ export async function refreshOAuthToken(provider, credentials) {
52
55
  }
53
56
  newCredentials = await refreshAntigravityToken(credentials.refresh, credentials.projectId);
54
57
  break;
58
+ case "openai-codex":
59
+ newCredentials = await refreshOpenAICodexToken(credentials.refresh);
60
+ break;
55
61
  default:
56
62
  throw new Error(`Unknown OAuth provider: ${provider}`);
57
63
  }
@@ -95,6 +101,11 @@ export function getOAuthProviders() {
95
101
  name: "Anthropic (Claude Pro/Max)",
96
102
  available: true,
97
103
  },
104
+ {
105
+ id: "openai-codex",
106
+ name: "ChatGPT Plus/Pro (Codex Subscription)",
107
+ available: true,
108
+ },
98
109
  {
99
110
  id: "github-copilot",
100
111
  name: "GitHub Copilot",
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,YAAY;AACZ,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvE,iBAAiB;AACjB,OAAO,EACN,uBAAuB,EACvB,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,qBAAqB;AACrB,OAAO,EACN,gBAAgB,EAChB,uBAAuB,GACvB,MAAM,yBAAyB,CAAC;AACjC,oBAAoB;AACpB,OAAO,EACN,cAAc,EACd,uBAAuB,GACvB,MAAM,wBAAwB,CAAC;AAEhC,cAAc,YAAY,CAAC;AAE3B,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAGjE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACtC,QAAuB,EACvB,WAA6B,EACD;IAC5B,IAAI,CAAC,WAAW,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,kCAAkC,QAAQ,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,cAAgC,CAAC;IAErC,QAAQ,QAAQ,EAAE,CAAC;QAClB,KAAK,WAAW;YACf,cAAc,GAAG,MAAM,qBAAqB,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YAClE,MAAM;QACP,KAAK,gBAAgB;YACpB,cAAc,GAAG,MAAM,yBAAyB,CAAC,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,aAAa,CAAC,CAAC;YACjG,MAAM;QACP,KAAK,mBAAmB;YACvB,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;YAC/D,CAAC;YACD,cAAc,GAAG,MAAM,uBAAuB,CAAC,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;YAC3F,MAAM;QACP,KAAK,oBAAoB;YACxB,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;YAC9D,CAAC;YACD,cAAc,GAAG,MAAM,uBAAuB,CAAC,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;YAC3F,MAAM;QACP;YACC,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,OAAO,cAAc,CAAC;AAAA,CACtB;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,QAAuB,EACvB,WAA6C,EAC0B;IACvE,IAAI,KAAK,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IACb,CAAC;IAED,qBAAqB;IACrB,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACjC,IAAI,CAAC;YACJ,KAAK,GAAG,MAAM,iBAAiB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,MAAM,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,qCAAqC,QAAQ,EAAE,CAAC,CAAC;QAClE,CAAC;IACF,CAAC;IAED,iDAAiD;IACjD,MAAM,cAAc,GAAG,QAAQ,KAAK,mBAAmB,IAAI,QAAQ,KAAK,oBAAoB,CAAC;IAC7F,MAAM,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;IACnH,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAAA,CACzC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,GAAwB;IACxD,OAAO;QACN;YACC,EAAE,EAAE,WAAW;YACf,IAAI,EAAE,4BAA4B;YAClC,SAAS,EAAE,IAAI;SACf;QACD;YACC,EAAE,EAAE,gBAAgB;YACpB,IAAI,EAAE,gBAAgB;YACtB,SAAS,EAAE,IAAI;SACf;QACD;YACC,EAAE,EAAE,mBAAmB;YACvB,IAAI,EAAE,uCAAuC;YAC7C,SAAS,EAAE,IAAI;SACf;QACD;YACC,EAAE,EAAE,oBAAoB;YACxB,IAAI,EAAE,yCAAyC;YAC/C,SAAS,EAAE,IAAI;SACf;KACD,CAAC;AAAA,CACF","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n * - Google Cloud Code Assist (Gemini CLI)\n * - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)\n */\n\n// Anthropic\nexport { loginAnthropic, refreshAnthropicToken } from \"./anthropic.js\";\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.js\";\n// Google Antigravity\nexport {\n\tloginAntigravity,\n\trefreshAntigravityToken,\n} from \"./google-antigravity.js\";\n// Google Gemini CLI\nexport {\n\tloginGeminiCli,\n\trefreshGoogleCloudToken,\n} from \"./google-gemini-cli.js\";\n\nexport * from \"./types.js\";\n\n// ============================================================================\n// High-level API\n// ============================================================================\n\nimport { refreshAnthropicToken } from \"./anthropic.js\";\nimport { refreshGitHubCopilotToken } from \"./github-copilot.js\";\nimport { refreshAntigravityToken } from \"./google-antigravity.js\";\nimport { refreshGoogleCloudToken } from \"./google-gemini-cli.js\";\nimport type { OAuthCredentials, OAuthProvider, OAuthProviderInfo } from \"./types.js\";\n\n/**\n * Refresh token for any OAuth provider.\n * Saves the new credentials and returns the new access token.\n */\nexport async function refreshOAuthToken(\n\tprovider: OAuthProvider,\n\tcredentials: OAuthCredentials,\n): Promise<OAuthCredentials> {\n\tif (!credentials) {\n\t\tthrow new Error(`No OAuth credentials found for ${provider}`);\n\t}\n\n\tlet newCredentials: OAuthCredentials;\n\n\tswitch (provider) {\n\t\tcase \"anthropic\":\n\t\t\tnewCredentials = await refreshAnthropicToken(credentials.refresh);\n\t\t\tbreak;\n\t\tcase \"github-copilot\":\n\t\t\tnewCredentials = await refreshGitHubCopilotToken(credentials.refresh, credentials.enterpriseUrl);\n\t\t\tbreak;\n\t\tcase \"google-gemini-cli\":\n\t\t\tif (!credentials.projectId) {\n\t\t\t\tthrow new Error(\"Google Cloud credentials missing projectId\");\n\t\t\t}\n\t\t\tnewCredentials = await refreshGoogleCloudToken(credentials.refresh, credentials.projectId);\n\t\t\tbreak;\n\t\tcase \"google-antigravity\":\n\t\t\tif (!credentials.projectId) {\n\t\t\t\tthrow new Error(\"Antigravity credentials missing projectId\");\n\t\t\t}\n\t\t\tnewCredentials = await refreshAntigravityToken(credentials.refresh, credentials.projectId);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown OAuth provider: ${provider}`);\n\t}\n\n\treturn newCredentials;\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * For google-gemini-cli and antigravity, returns JSON-encoded { token, projectId }\n *\n * @returns API key string, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tprovider: OAuthProvider,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tlet creds = credentials[provider];\n\tif (!creds) {\n\t\treturn null;\n\t}\n\n\t// Refresh if expired\n\tif (Date.now() >= creds.expires) {\n\t\ttry {\n\t\t\tcreds = await refreshOAuthToken(provider, creds);\n\t\t} catch (_error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${provider}`);\n\t\t}\n\t}\n\n\t// For providers that need projectId, return JSON\n\tconst needsProjectId = provider === \"google-gemini-cli\" || provider === \"google-antigravity\";\n\tconst apiKey = needsProjectId ? JSON.stringify({ token: creds.access, projectId: creds.projectId }) : creds.access;\n\treturn { newCredentials: creds, apiKey };\n}\n\n/**\n * Get list of OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInfo[] {\n\treturn [\n\t\t{\n\t\t\tid: \"anthropic\",\n\t\t\tname: \"Anthropic (Claude Pro/Max)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"github-copilot\",\n\t\t\tname: \"GitHub Copilot\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"google-gemini-cli\",\n\t\t\tname: \"Google Cloud Code Assist (Gemini CLI)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"google-antigravity\",\n\t\t\tname: \"Antigravity (Gemini 3, Claude, GPT-OSS)\",\n\t\t\tavailable: true,\n\t\t},\n\t];\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,YAAY;AACZ,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvE,iBAAiB;AACjB,OAAO,EACN,uBAAuB,EACvB,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,qBAAqB;AACrB,OAAO,EACN,gBAAgB,EAChB,uBAAuB,GACvB,MAAM,yBAAyB,CAAC;AACjC,oBAAoB;AACpB,OAAO,EACN,cAAc,EACd,uBAAuB,GACvB,MAAM,wBAAwB,CAAC;AAChC,+BAA+B;AAC/B,OAAO,EACN,gBAAgB,EAChB,uBAAuB,GACvB,MAAM,mBAAmB,CAAC;AAE3B,cAAc,YAAY,CAAC;AAE3B,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAG5D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACtC,QAAuB,EACvB,WAA6B,EACD;IAC5B,IAAI,CAAC,WAAW,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,kCAAkC,QAAQ,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,cAAgC,CAAC;IAErC,QAAQ,QAAQ,EAAE,CAAC;QAClB,KAAK,WAAW;YACf,cAAc,GAAG,MAAM,qBAAqB,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YAClE,MAAM;QACP,KAAK,gBAAgB;YACpB,cAAc,GAAG,MAAM,yBAAyB,CAAC,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,aAAa,CAAC,CAAC;YACjG,MAAM;QACP,KAAK,mBAAmB;YACvB,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;YAC/D,CAAC;YACD,cAAc,GAAG,MAAM,uBAAuB,CAAC,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;YAC3F,MAAM;QACP,KAAK,oBAAoB;YACxB,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;YAC9D,CAAC;YACD,cAAc,GAAG,MAAM,uBAAuB,CAAC,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;YAC3F,MAAM;QACP,KAAK,cAAc;YAClB,cAAc,GAAG,MAAM,uBAAuB,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YACpE,MAAM;QACP;YACC,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,OAAO,cAAc,CAAC;AAAA,CACtB;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,QAAuB,EACvB,WAA6C,EAC0B;IACvE,IAAI,KAAK,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IACb,CAAC;IAED,qBAAqB;IACrB,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACjC,IAAI,CAAC;YACJ,KAAK,GAAG,MAAM,iBAAiB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,MAAM,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,qCAAqC,QAAQ,EAAE,CAAC,CAAC;QAClE,CAAC;IACF,CAAC;IAED,iDAAiD;IACjD,MAAM,cAAc,GAAG,QAAQ,KAAK,mBAAmB,IAAI,QAAQ,KAAK,oBAAoB,CAAC;IAC7F,MAAM,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;IACnH,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAAA,CACzC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,GAAwB;IACxD,OAAO;QACN;YACC,EAAE,EAAE,WAAW;YACf,IAAI,EAAE,4BAA4B;YAClC,SAAS,EAAE,IAAI;SACf;QACD;YACC,EAAE,EAAE,cAAc;YAClB,IAAI,EAAE,uCAAuC;YAC7C,SAAS,EAAE,IAAI;SACf;QACD;YACC,EAAE,EAAE,gBAAgB;YACpB,IAAI,EAAE,gBAAgB;YACtB,SAAS,EAAE,IAAI;SACf;QACD;YACC,EAAE,EAAE,mBAAmB;YACvB,IAAI,EAAE,uCAAuC;YAC7C,SAAS,EAAE,IAAI;SACf;QACD;YACC,EAAE,EAAE,oBAAoB;YACxB,IAAI,EAAE,yCAAyC;YAC/C,SAAS,EAAE,IAAI;SACf;KACD,CAAC;AAAA,CACF","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n * - Google Cloud Code Assist (Gemini CLI)\n * - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)\n */\n\n// Anthropic\nexport { loginAnthropic, refreshAnthropicToken } from \"./anthropic.js\";\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.js\";\n// Google Antigravity\nexport {\n\tloginAntigravity,\n\trefreshAntigravityToken,\n} from \"./google-antigravity.js\";\n// Google Gemini CLI\nexport {\n\tloginGeminiCli,\n\trefreshGoogleCloudToken,\n} from \"./google-gemini-cli.js\";\n// OpenAI Codex (ChatGPT OAuth)\nexport {\n\tloginOpenAICodex,\n\trefreshOpenAICodexToken,\n} from \"./openai-codex.js\";\n\nexport * from \"./types.js\";\n\n// ============================================================================\n// High-level API\n// ============================================================================\n\nimport { refreshAnthropicToken } from \"./anthropic.js\";\nimport { refreshGitHubCopilotToken } from \"./github-copilot.js\";\nimport { refreshAntigravityToken } from \"./google-antigravity.js\";\nimport { refreshGoogleCloudToken } from \"./google-gemini-cli.js\";\nimport { refreshOpenAICodexToken } from \"./openai-codex.js\";\nimport type { OAuthCredentials, OAuthProvider, OAuthProviderInfo } from \"./types.js\";\n\n/**\n * Refresh token for any OAuth provider.\n * Saves the new credentials and returns the new access token.\n */\nexport async function refreshOAuthToken(\n\tprovider: OAuthProvider,\n\tcredentials: OAuthCredentials,\n): Promise<OAuthCredentials> {\n\tif (!credentials) {\n\t\tthrow new Error(`No OAuth credentials found for ${provider}`);\n\t}\n\n\tlet newCredentials: OAuthCredentials;\n\n\tswitch (provider) {\n\t\tcase \"anthropic\":\n\t\t\tnewCredentials = await refreshAnthropicToken(credentials.refresh);\n\t\t\tbreak;\n\t\tcase \"github-copilot\":\n\t\t\tnewCredentials = await refreshGitHubCopilotToken(credentials.refresh, credentials.enterpriseUrl);\n\t\t\tbreak;\n\t\tcase \"google-gemini-cli\":\n\t\t\tif (!credentials.projectId) {\n\t\t\t\tthrow new Error(\"Google Cloud credentials missing projectId\");\n\t\t\t}\n\t\t\tnewCredentials = await refreshGoogleCloudToken(credentials.refresh, credentials.projectId);\n\t\t\tbreak;\n\t\tcase \"google-antigravity\":\n\t\t\tif (!credentials.projectId) {\n\t\t\t\tthrow new Error(\"Antigravity credentials missing projectId\");\n\t\t\t}\n\t\t\tnewCredentials = await refreshAntigravityToken(credentials.refresh, credentials.projectId);\n\t\t\tbreak;\n\t\tcase \"openai-codex\":\n\t\t\tnewCredentials = await refreshOpenAICodexToken(credentials.refresh);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown OAuth provider: ${provider}`);\n\t}\n\n\treturn newCredentials;\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * For google-gemini-cli and antigravity, returns JSON-encoded { token, projectId }\n *\n * @returns API key string, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tprovider: OAuthProvider,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tlet creds = credentials[provider];\n\tif (!creds) {\n\t\treturn null;\n\t}\n\n\t// Refresh if expired\n\tif (Date.now() >= creds.expires) {\n\t\ttry {\n\t\t\tcreds = await refreshOAuthToken(provider, creds);\n\t\t} catch (_error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${provider}`);\n\t\t}\n\t}\n\n\t// For providers that need projectId, return JSON\n\tconst needsProjectId = provider === \"google-gemini-cli\" || provider === \"google-antigravity\";\n\tconst apiKey = needsProjectId ? JSON.stringify({ token: creds.access, projectId: creds.projectId }) : creds.access;\n\treturn { newCredentials: creds, apiKey };\n}\n\n/**\n * Get list of OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInfo[] {\n\treturn [\n\t\t{\n\t\t\tid: \"anthropic\",\n\t\t\tname: \"Anthropic (Claude Pro/Max)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"openai-codex\",\n\t\t\tname: \"ChatGPT Plus/Pro (Codex Subscription)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"github-copilot\",\n\t\t\tname: \"GitHub Copilot\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"google-gemini-cli\",\n\t\t\tname: \"Google Cloud Code Assist (Gemini CLI)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"google-antigravity\",\n\t\t\tname: \"Antigravity (Gemini 3, Claude, GPT-OSS)\",\n\t\t\tavailable: true,\n\t\t},\n\t];\n}\n"]}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * OpenAI Codex (ChatGPT OAuth) flow
3
+ */
4
+ import type { OAuthCredentials, OAuthPrompt } from "./types.js";
5
+ /**
6
+ * Login with OpenAI Codex OAuth
7
+ *
8
+ * @param options.onAuth - Called with URL and instructions when auth starts
9
+ * @param options.onPrompt - Called to prompt user for manual code paste (fallback if no onManualCodeInput)
10
+ * @param options.onProgress - Optional progress messages
11
+ * @param options.onManualCodeInput - Optional promise that resolves with user-pasted code.
12
+ * Races with browser callback - whichever completes first wins.
13
+ * Useful for showing paste input immediately alongside browser flow.
14
+ */
15
+ export declare function loginOpenAICodex(options: {
16
+ onAuth: (info: {
17
+ url: string;
18
+ instructions?: string;
19
+ }) => void;
20
+ onPrompt: (prompt: OAuthPrompt) => Promise<string>;
21
+ onProgress?: (message: string) => void;
22
+ onManualCodeInput?: () => Promise<string>;
23
+ }): Promise<OAuthCredentials>;
24
+ /**
25
+ * Refresh OpenAI Codex OAuth token
26
+ */
27
+ export declare function refreshOpenAICodexToken(refreshToken: string): Promise<OAuthCredentials>;
28
+ //# sourceMappingURL=openai-codex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-codex.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/openai-codex.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAyQhE;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC/D,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACnD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1C,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmG5B;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiB7F","sourcesContent":["/**\n * OpenAI Codex (ChatGPT OAuth) flow\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport http from \"node:http\";\nimport { generatePKCE } from \"./pkce.js\";\nimport type { OAuthCredentials, OAuthPrompt } from \"./types.js\";\n\nconst CLIENT_ID = \"app_EMoamEEZ73f0CkXaXp7hrann\";\nconst AUTHORIZE_URL = \"https://auth.openai.com/oauth/authorize\";\nconst TOKEN_URL = \"https://auth.openai.com/oauth/token\";\nconst REDIRECT_URI = \"http://localhost:1455/auth/callback\";\nconst SCOPE = \"openid profile email offline_access\";\nconst JWT_CLAIM_PATH = \"https://api.openai.com/auth\";\n\nconst SUCCESS_HTML = `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>Authentication successful</title>\n</head>\n<body>\n <p>Authentication successful. Return to your terminal to continue.</p>\n</body>\n</html>`;\n\ntype TokenSuccess = { type: \"success\"; access: string; refresh: string; expires: number };\ntype TokenFailure = { type: \"failed\" };\ntype TokenResult = TokenSuccess | TokenFailure;\n\ntype JwtPayload = {\n\t[JWT_CLAIM_PATH]?: {\n\t\tchatgpt_account_id?: string;\n\t};\n\t[key: string]: unknown;\n};\n\nfunction createState(): string {\n\treturn randomBytes(16).toString(\"hex\");\n}\n\nfunction parseAuthorizationInput(input: string): { code?: string; state?: string } {\n\tconst value = input.trim();\n\tif (!value) return {};\n\n\ttry {\n\t\tconst url = new URL(value);\n\t\treturn {\n\t\t\tcode: url.searchParams.get(\"code\") ?? undefined,\n\t\t\tstate: url.searchParams.get(\"state\") ?? undefined,\n\t\t};\n\t} catch {\n\t\t// not a URL\n\t}\n\n\tif (value.includes(\"#\")) {\n\t\tconst [code, state] = value.split(\"#\", 2);\n\t\treturn { code, state };\n\t}\n\n\tif (value.includes(\"code=\")) {\n\t\tconst params = new URLSearchParams(value);\n\t\treturn {\n\t\t\tcode: params.get(\"code\") ?? undefined,\n\t\t\tstate: params.get(\"state\") ?? undefined,\n\t\t};\n\t}\n\n\treturn { code: value };\n}\n\nfunction decodeJwt(token: string): JwtPayload | null {\n\ttry {\n\t\tconst parts = token.split(\".\");\n\t\tif (parts.length !== 3) return null;\n\t\tconst payload = parts[1] ?? \"\";\n\t\tconst decoded = Buffer.from(payload, \"base64\").toString(\"utf-8\");\n\t\treturn JSON.parse(decoded) as JwtPayload;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function exchangeAuthorizationCode(\n\tcode: string,\n\tverifier: string,\n\tredirectUri: string = REDIRECT_URI,\n): Promise<TokenResult> {\n\tconst response = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\tbody: new URLSearchParams({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode,\n\t\t\tcode_verifier: verifier,\n\t\t\tredirect_uri: redirectUri,\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tconst text = await response.text().catch(() => \"\");\n\t\tconsole.error(\"[openai-codex] code->token failed:\", response.status, text);\n\t\treturn { type: \"failed\" };\n\t}\n\n\tconst json = (await response.json()) as {\n\t\taccess_token?: string;\n\t\trefresh_token?: string;\n\t\texpires_in?: number;\n\t};\n\n\tif (!json.access_token || !json.refresh_token || typeof json.expires_in !== \"number\") {\n\t\tconsole.error(\"[openai-codex] token response missing fields:\", json);\n\t\treturn { type: \"failed\" };\n\t}\n\n\treturn {\n\t\ttype: \"success\",\n\t\taccess: json.access_token,\n\t\trefresh: json.refresh_token,\n\t\texpires: Date.now() + json.expires_in * 1000,\n\t};\n}\n\nasync function refreshAccessToken(refreshToken: string): Promise<TokenResult> {\n\ttry {\n\t\tconst response = await fetch(TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t}),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tconsole.error(\"[openai-codex] Token refresh failed:\", response.status, text);\n\t\t\treturn { type: \"failed\" };\n\t\t}\n\n\t\tconst json = (await response.json()) as {\n\t\t\taccess_token?: string;\n\t\t\trefresh_token?: string;\n\t\t\texpires_in?: number;\n\t\t};\n\n\t\tif (!json.access_token || !json.refresh_token || typeof json.expires_in !== \"number\") {\n\t\t\tconsole.error(\"[openai-codex] Token refresh response missing fields:\", json);\n\t\t\treturn { type: \"failed\" };\n\t\t}\n\n\t\treturn {\n\t\t\ttype: \"success\",\n\t\t\taccess: json.access_token,\n\t\t\trefresh: json.refresh_token,\n\t\t\texpires: Date.now() + json.expires_in * 1000,\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"[openai-codex] Token refresh error:\", error);\n\t\treturn { type: \"failed\" };\n\t}\n}\n\nasync function createAuthorizationFlow(): Promise<{ verifier: string; state: string; url: string }> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst state = createState();\n\n\tconst url = new URL(AUTHORIZE_URL);\n\turl.searchParams.set(\"response_type\", \"code\");\n\turl.searchParams.set(\"client_id\", CLIENT_ID);\n\turl.searchParams.set(\"redirect_uri\", REDIRECT_URI);\n\turl.searchParams.set(\"scope\", SCOPE);\n\turl.searchParams.set(\"code_challenge\", challenge);\n\turl.searchParams.set(\"code_challenge_method\", \"S256\");\n\turl.searchParams.set(\"state\", state);\n\turl.searchParams.set(\"id_token_add_organizations\", \"true\");\n\turl.searchParams.set(\"codex_cli_simplified_flow\", \"true\");\n\turl.searchParams.set(\"originator\", \"codex_cli_rs\");\n\n\treturn { verifier, state, url: url.toString() };\n}\n\ntype OAuthServerInfo = {\n\tclose: () => void;\n\tcancelWait: () => void;\n\twaitForCode: () => Promise<{ code: string } | null>;\n};\n\nfunction startLocalOAuthServer(state: string): Promise<OAuthServerInfo> {\n\tlet lastCode: string | null = null;\n\tlet cancelled = false;\n\tconst server = http.createServer((req, res) => {\n\t\ttry {\n\t\t\tconst url = new URL(req.url || \"\", \"http://localhost\");\n\t\t\tif (url.pathname !== \"/auth/callback\") {\n\t\t\t\tres.statusCode = 404;\n\t\t\t\tres.end(\"Not found\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (url.searchParams.get(\"state\") !== state) {\n\t\t\t\tres.statusCode = 400;\n\t\t\t\tres.end(\"State mismatch\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst code = url.searchParams.get(\"code\");\n\t\t\tif (!code) {\n\t\t\t\tres.statusCode = 400;\n\t\t\t\tres.end(\"Missing authorization code\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tres.statusCode = 200;\n\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\tres.end(SUCCESS_HTML);\n\t\t\tlastCode = code;\n\t\t} catch {\n\t\t\tres.statusCode = 500;\n\t\t\tres.end(\"Internal error\");\n\t\t}\n\t});\n\n\treturn new Promise((resolve) => {\n\t\tserver\n\t\t\t.listen(1455, \"127.0.0.1\", () => {\n\t\t\t\tresolve({\n\t\t\t\t\tclose: () => server.close(),\n\t\t\t\t\tcancelWait: () => {\n\t\t\t\t\t\tcancelled = true;\n\t\t\t\t\t},\n\t\t\t\t\twaitForCode: async () => {\n\t\t\t\t\t\tconst sleep = () => new Promise((r) => setTimeout(r, 100));\n\t\t\t\t\t\tfor (let i = 0; i < 600; i += 1) {\n\t\t\t\t\t\t\tif (lastCode) return { code: lastCode };\n\t\t\t\t\t\t\tif (cancelled) return null;\n\t\t\t\t\t\t\tawait sleep();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t})\n\t\t\t.on(\"error\", (err: NodeJS.ErrnoException) => {\n\t\t\t\tconsole.error(\n\t\t\t\t\t\"[openai-codex] Failed to bind http://127.0.0.1:1455 (\",\n\t\t\t\t\terr.code,\n\t\t\t\t\t\") Falling back to manual paste.\",\n\t\t\t\t);\n\t\t\t\tresolve({\n\t\t\t\t\tclose: () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tserver.close();\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// ignore\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tcancelWait: () => {},\n\t\t\t\t\twaitForCode: async () => null,\n\t\t\t\t});\n\t\t\t});\n\t});\n}\n\nfunction getAccountId(accessToken: string): string | null {\n\tconst payload = decodeJwt(accessToken);\n\tconst auth = payload?.[JWT_CLAIM_PATH];\n\tconst accountId = auth?.chatgpt_account_id;\n\treturn typeof accountId === \"string\" && accountId.length > 0 ? accountId : null;\n}\n\n/**\n * Login with OpenAI Codex OAuth\n *\n * @param options.onAuth - Called with URL and instructions when auth starts\n * @param options.onPrompt - Called to prompt user for manual code paste (fallback if no onManualCodeInput)\n * @param options.onProgress - Optional progress messages\n * @param options.onManualCodeInput - Optional promise that resolves with user-pasted code.\n * Races with browser callback - whichever completes first wins.\n * Useful for showing paste input immediately alongside browser flow.\n */\nexport async function loginOpenAICodex(options: {\n\tonAuth: (info: { url: string; instructions?: string }) => void;\n\tonPrompt: (prompt: OAuthPrompt) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tonManualCodeInput?: () => Promise<string>;\n}): Promise<OAuthCredentials> {\n\tconst { verifier, state, url } = await createAuthorizationFlow();\n\tconst server = await startLocalOAuthServer(state);\n\n\toptions.onAuth({ url, instructions: \"A browser window should open. Complete login to finish.\" });\n\n\tlet code: string | undefined;\n\ttry {\n\t\tif (options.onManualCodeInput) {\n\t\t\t// Race between browser callback and manual input\n\t\t\tlet manualCode: string | undefined;\n\t\t\tlet manualError: Error | undefined;\n\t\t\tconst manualPromise = options\n\t\t\t\t.onManualCodeInput()\n\t\t\t\t.then((input) => {\n\t\t\t\t\tmanualCode = input;\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tmanualError = err instanceof Error ? err : new Error(String(err));\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t});\n\n\t\t\tconst result = await server.waitForCode();\n\n\t\t\t// If manual input was cancelled, throw that error\n\t\t\tif (manualError) {\n\t\t\t\tthrow manualError;\n\t\t\t}\n\n\t\t\tif (result?.code) {\n\t\t\t\t// Browser callback won\n\t\t\t\tcode = result.code;\n\t\t\t} else if (manualCode) {\n\t\t\t\t// Manual input won (or callback timed out and user had entered code)\n\t\t\t\tconst parsed = parseAuthorizationInput(manualCode);\n\t\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t\t}\n\t\t\t\tcode = parsed.code;\n\t\t\t}\n\n\t\t\t// If still no code, wait for manual promise to complete and try that\n\t\t\tif (!code) {\n\t\t\t\tawait manualPromise;\n\t\t\t\tif (manualError) {\n\t\t\t\t\tthrow manualError;\n\t\t\t\t}\n\t\t\t\tif (manualCode) {\n\t\t\t\t\tconst parsed = parseAuthorizationInput(manualCode);\n\t\t\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t\t\t}\n\t\t\t\t\tcode = parsed.code;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Original flow: wait for callback, then prompt if needed\n\t\t\tconst result = await server.waitForCode();\n\t\t\tif (result?.code) {\n\t\t\t\tcode = result.code;\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to onPrompt if still no code\n\t\tif (!code) {\n\t\t\tconst input = await options.onPrompt({\n\t\t\t\tmessage: \"Paste the authorization code (or full redirect URL):\",\n\t\t\t});\n\t\t\tconst parsed = parseAuthorizationInput(input);\n\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t}\n\t\t\tcode = parsed.code;\n\t\t}\n\n\t\tif (!code) {\n\t\t\tthrow new Error(\"Missing authorization code\");\n\t\t}\n\n\t\tconst tokenResult = await exchangeAuthorizationCode(code, verifier);\n\t\tif (tokenResult.type !== \"success\") {\n\t\t\tthrow new Error(\"Token exchange failed\");\n\t\t}\n\n\t\tconst accountId = getAccountId(tokenResult.access);\n\t\tif (!accountId) {\n\t\t\tthrow new Error(\"Failed to extract accountId from token\");\n\t\t}\n\n\t\treturn {\n\t\t\taccess: tokenResult.access,\n\t\t\trefresh: tokenResult.refresh,\n\t\t\texpires: tokenResult.expires,\n\t\t\taccountId,\n\t\t};\n\t} finally {\n\t\tserver.close();\n\t}\n}\n\n/**\n * Refresh OpenAI Codex OAuth token\n */\nexport async function refreshOpenAICodexToken(refreshToken: string): Promise<OAuthCredentials> {\n\tconst result = await refreshAccessToken(refreshToken);\n\tif (result.type !== \"success\") {\n\t\tthrow new Error(\"Failed to refresh OpenAI Codex token\");\n\t}\n\n\tconst accountId = getAccountId(result.access);\n\tif (!accountId) {\n\t\tthrow new Error(\"Failed to extract accountId from token\");\n\t}\n\n\treturn {\n\t\taccess: result.access,\n\t\trefresh: result.refresh,\n\t\texpires: result.expires,\n\t\taccountId,\n\t};\n}\n"]}