@oh-my-pi/pi-ai 3.20.0 → 3.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -12
- package/package.json +3 -10
- package/src/cli.ts +89 -89
- package/src/index.ts +2 -2
- package/src/models.generated.ts +871 -151
- package/src/models.ts +11 -17
- package/src/providers/anthropic.ts +92 -28
- package/src/providers/google-gemini-cli.ts +268 -133
- package/src/providers/google-shared.ts +48 -5
- package/src/providers/google-vertex.ts +13 -3
- package/src/providers/google.ts +13 -3
- package/src/providers/openai-codex/index.ts +7 -0
- package/src/providers/openai-codex/prompts/codex.ts +26 -59
- package/src/providers/openai-codex/prompts/pi-codex-bridge.ts +38 -31
- package/src/providers/openai-codex/prompts/system-prompt.ts +26 -0
- package/src/providers/openai-codex/request-transformer.ts +38 -203
- package/src/providers/openai-codex-responses.ts +91 -24
- package/src/providers/openai-completions.ts +33 -26
- package/src/providers/openai-responses.ts +1 -1
- package/src/providers/transorm-messages.ts +4 -3
- package/src/stream.ts +34 -25
- package/src/types.ts +21 -4
- package/src/utils/oauth/github-copilot.ts +38 -3
- package/src/utils/oauth/google-antigravity.ts +146 -55
- package/src/utils/oauth/google-gemini-cli.ts +146 -55
- package/src/utils/oauth/index.ts +5 -5
- package/src/utils/oauth/openai-codex.ts +129 -54
- package/src/utils/overflow.ts +1 -1
- package/src/bun-imports.d.ts +0 -14
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* It is only intended for CLI use, not browser environments.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import type { Server } from "http";
|
|
9
10
|
import { generatePKCE } from "./pkce";
|
|
10
11
|
import type { OAuthCredentials } from "./types";
|
|
11
12
|
|
|
@@ -32,71 +33,97 @@ const TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
|
32
33
|
// Fallback project ID when discovery fails
|
|
33
34
|
const DEFAULT_PROJECT_ID = "rising-fact-p41fc";
|
|
34
35
|
|
|
36
|
+
type CallbackServerInfo = {
|
|
37
|
+
server: Server;
|
|
38
|
+
cancelWait: () => void;
|
|
39
|
+
waitForCode: () => Promise<{ code: string; state: string } | null>;
|
|
40
|
+
};
|
|
41
|
+
|
|
35
42
|
/**
|
|
36
43
|
* Start a local HTTP server to receive the OAuth callback
|
|
37
44
|
*/
|
|
38
|
-
async function startCallbackServer(): Promise<{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}> {
|
|
45
|
+
async function startCallbackServer(): Promise<CallbackServerInfo> {
|
|
46
|
+
const { createServer } = await import("http");
|
|
47
|
+
|
|
42
48
|
return new Promise((resolve, reject) => {
|
|
43
|
-
let
|
|
44
|
-
let
|
|
49
|
+
let result: { code: string; state: string } | null = null;
|
|
50
|
+
let cancelled = false;
|
|
45
51
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
codeReject = rej;
|
|
49
|
-
});
|
|
52
|
+
const server = createServer((req, res) => {
|
|
53
|
+
const url = new URL(req.url || "", `http://localhost:51121`);
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const url = new URL(req.url);
|
|
56
|
-
|
|
57
|
-
if (url.pathname === "/oauth-callback") {
|
|
58
|
-
const code = url.searchParams.get("code");
|
|
59
|
-
const state = url.searchParams.get("state");
|
|
60
|
-
const error = url.searchParams.get("error");
|
|
61
|
-
|
|
62
|
-
if (error) {
|
|
63
|
-
codeReject(new Error(`OAuth error: ${error}`));
|
|
64
|
-
return new Response(
|
|
65
|
-
`<html><body><h1>Authentication Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`,
|
|
66
|
-
{ status: 400, headers: { "Content-Type": "text/html" } },
|
|
67
|
-
);
|
|
68
|
-
}
|
|
55
|
+
if (url.pathname === "/oauth-callback") {
|
|
56
|
+
const code = url.searchParams.get("code");
|
|
57
|
+
const state = url.searchParams.get("state");
|
|
58
|
+
const error = url.searchParams.get("error");
|
|
69
59
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
60
|
+
if (error) {
|
|
61
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
62
|
+
res.end(
|
|
63
|
+
`<html><body><h1>Authentication Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`,
|
|
64
|
+
);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
77
67
|
|
|
78
|
-
|
|
79
|
-
|
|
68
|
+
if (code && state) {
|
|
69
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
70
|
+
res.end(
|
|
71
|
+
`<html><body><h1>Authentication Successful</h1><p>You can close this window and return to the terminal.</p></body></html>`,
|
|
72
|
+
);
|
|
73
|
+
result = { code, state };
|
|
74
|
+
} else {
|
|
75
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
76
|
+
res.end(
|
|
80
77
|
`<html><body><h1>Authentication Failed</h1><p>Missing code or state parameter.</p></body></html>`,
|
|
81
|
-
{ status: 400, headers: { "Content-Type": "text/html" } },
|
|
82
78
|
);
|
|
83
79
|
}
|
|
80
|
+
} else {
|
|
81
|
+
res.writeHead(404);
|
|
82
|
+
res.end();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
error(err) {
|
|
88
|
-
reject(err);
|
|
89
|
-
return new Response("Internal Server Error", { status: 500 });
|
|
90
|
-
},
|
|
86
|
+
server.on("error", (err) => {
|
|
87
|
+
reject(err);
|
|
91
88
|
});
|
|
92
89
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
server.listen(51121, "127.0.0.1", () => {
|
|
91
|
+
resolve({
|
|
92
|
+
server,
|
|
93
|
+
cancelWait: () => {
|
|
94
|
+
cancelled = true;
|
|
95
|
+
},
|
|
96
|
+
waitForCode: async () => {
|
|
97
|
+
const sleep = () => new Promise((r) => setTimeout(r, 100));
|
|
98
|
+
while (!result && !cancelled) {
|
|
99
|
+
await sleep();
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
},
|
|
103
|
+
});
|
|
96
104
|
});
|
|
97
105
|
});
|
|
98
106
|
}
|
|
99
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Parse redirect URL to extract code and state
|
|
110
|
+
*/
|
|
111
|
+
function parseRedirectUrl(input: string): { code?: string; state?: string } {
|
|
112
|
+
const value = input.trim();
|
|
113
|
+
if (!value) return {};
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const url = new URL(value);
|
|
117
|
+
return {
|
|
118
|
+
code: url.searchParams.get("code") ?? undefined,
|
|
119
|
+
state: url.searchParams.get("state") ?? undefined,
|
|
120
|
+
};
|
|
121
|
+
} catch {
|
|
122
|
+
// Not a URL, return empty
|
|
123
|
+
return {};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
100
127
|
interface LoadCodeAssistPayload {
|
|
101
128
|
cloudaicompanionProject?: string | { id?: string };
|
|
102
129
|
currentTier?: { id?: string };
|
|
@@ -223,16 +250,21 @@ export async function refreshAntigravityToken(refreshToken: string, projectId: s
|
|
|
223
250
|
*
|
|
224
251
|
* @param onAuth - Callback with URL and optional instructions
|
|
225
252
|
* @param onProgress - Optional progress callback
|
|
253
|
+
* @param onManualCodeInput - Optional promise that resolves with user-pasted redirect URL.
|
|
254
|
+
* Races with browser callback - whichever completes first wins.
|
|
226
255
|
*/
|
|
227
256
|
export async function loginAntigravity(
|
|
228
257
|
onAuth: (info: { url: string; instructions?: string }) => void,
|
|
229
258
|
onProgress?: (message: string) => void,
|
|
259
|
+
onManualCodeInput?: () => Promise<string>,
|
|
230
260
|
): Promise<OAuthCredentials> {
|
|
231
261
|
const { verifier, challenge } = await generatePKCE();
|
|
232
262
|
|
|
233
263
|
// Start local server for callback
|
|
234
264
|
onProgress?.("Starting local server for OAuth callback...");
|
|
235
|
-
const
|
|
265
|
+
const server = await startCallbackServer();
|
|
266
|
+
|
|
267
|
+
let code: string | undefined;
|
|
236
268
|
|
|
237
269
|
try {
|
|
238
270
|
// Build authorization URL
|
|
@@ -253,16 +285,75 @@ export async function loginAntigravity(
|
|
|
253
285
|
// Notify caller with URL to open
|
|
254
286
|
onAuth({
|
|
255
287
|
url: authUrl,
|
|
256
|
-
instructions: "Complete the sign-in in your browser.
|
|
288
|
+
instructions: "Complete the sign-in in your browser.",
|
|
257
289
|
});
|
|
258
290
|
|
|
259
|
-
// Wait for the callback
|
|
291
|
+
// Wait for the callback, racing with manual input if provided
|
|
260
292
|
onProgress?.("Waiting for OAuth callback...");
|
|
261
|
-
const { code, state } = await getCode();
|
|
262
293
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
294
|
+
if (onManualCodeInput) {
|
|
295
|
+
// Race between browser callback and manual input
|
|
296
|
+
let manualInput: string | undefined;
|
|
297
|
+
let manualError: Error | undefined;
|
|
298
|
+
const manualPromise = onManualCodeInput()
|
|
299
|
+
.then((input) => {
|
|
300
|
+
manualInput = input;
|
|
301
|
+
server.cancelWait();
|
|
302
|
+
})
|
|
303
|
+
.catch((err) => {
|
|
304
|
+
manualError = err instanceof Error ? err : new Error(String(err));
|
|
305
|
+
server.cancelWait();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const result = await server.waitForCode();
|
|
309
|
+
|
|
310
|
+
// If manual input was cancelled, throw that error
|
|
311
|
+
if (manualError) {
|
|
312
|
+
throw manualError;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (result?.code) {
|
|
316
|
+
// Browser callback won - verify state
|
|
317
|
+
if (result.state !== verifier) {
|
|
318
|
+
throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
319
|
+
}
|
|
320
|
+
code = result.code;
|
|
321
|
+
} else if (manualInput) {
|
|
322
|
+
// Manual input won
|
|
323
|
+
const parsed = parseRedirectUrl(manualInput);
|
|
324
|
+
if (parsed.state && parsed.state !== verifier) {
|
|
325
|
+
throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
326
|
+
}
|
|
327
|
+
code = parsed.code;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// If still no code, wait for manual promise and try that
|
|
331
|
+
if (!code) {
|
|
332
|
+
await manualPromise;
|
|
333
|
+
if (manualError) {
|
|
334
|
+
throw manualError;
|
|
335
|
+
}
|
|
336
|
+
if (manualInput) {
|
|
337
|
+
const parsed = parseRedirectUrl(manualInput);
|
|
338
|
+
if (parsed.state && parsed.state !== verifier) {
|
|
339
|
+
throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
340
|
+
}
|
|
341
|
+
code = parsed.code;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
// Original flow: just wait for callback
|
|
346
|
+
const result = await server.waitForCode();
|
|
347
|
+
if (result?.code) {
|
|
348
|
+
if (result.state !== verifier) {
|
|
349
|
+
throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
350
|
+
}
|
|
351
|
+
code = result.code;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (!code) {
|
|
356
|
+
throw new Error("No authorization code received");
|
|
266
357
|
}
|
|
267
358
|
|
|
268
359
|
// Exchange code for tokens
|
|
@@ -317,6 +408,6 @@ export async function loginAntigravity(
|
|
|
317
408
|
|
|
318
409
|
return credentials;
|
|
319
410
|
} finally {
|
|
320
|
-
server.
|
|
411
|
+
server.server.close();
|
|
321
412
|
}
|
|
322
413
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* It is only intended for CLI use, not browser environments.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import type { Server } from "http";
|
|
9
10
|
import { generatePKCE } from "./pkce";
|
|
10
11
|
import type { OAuthCredentials } from "./types";
|
|
11
12
|
|
|
@@ -24,71 +25,97 @@ const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
|
24
25
|
const TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
25
26
|
const CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
|
|
26
27
|
|
|
28
|
+
type CallbackServerInfo = {
|
|
29
|
+
server: Server;
|
|
30
|
+
cancelWait: () => void;
|
|
31
|
+
waitForCode: () => Promise<{ code: string; state: string } | null>;
|
|
32
|
+
};
|
|
33
|
+
|
|
27
34
|
/**
|
|
28
35
|
* Start a local HTTP server to receive the OAuth callback
|
|
29
36
|
*/
|
|
30
|
-
async function startCallbackServer(): Promise<{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}> {
|
|
37
|
+
async function startCallbackServer(): Promise<CallbackServerInfo> {
|
|
38
|
+
const { createServer } = await import("http");
|
|
39
|
+
|
|
34
40
|
return new Promise((resolve, reject) => {
|
|
35
|
-
let
|
|
36
|
-
let
|
|
41
|
+
let result: { code: string; state: string } | null = null;
|
|
42
|
+
let cancelled = false;
|
|
37
43
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
codeReject = rej;
|
|
41
|
-
});
|
|
44
|
+
const server = createServer((req, res) => {
|
|
45
|
+
const url = new URL(req.url || "", `http://localhost:8085`);
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const url = new URL(req.url);
|
|
48
|
-
|
|
49
|
-
if (url.pathname === "/oauth2callback") {
|
|
50
|
-
const code = url.searchParams.get("code");
|
|
51
|
-
const state = url.searchParams.get("state");
|
|
52
|
-
const error = url.searchParams.get("error");
|
|
53
|
-
|
|
54
|
-
if (error) {
|
|
55
|
-
codeReject(new Error(`OAuth error: ${error}`));
|
|
56
|
-
return new Response(
|
|
57
|
-
`<html><body><h1>Authentication Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`,
|
|
58
|
-
{ status: 400, headers: { "Content-Type": "text/html" } },
|
|
59
|
-
);
|
|
60
|
-
}
|
|
47
|
+
if (url.pathname === "/oauth2callback") {
|
|
48
|
+
const code = url.searchParams.get("code");
|
|
49
|
+
const state = url.searchParams.get("state");
|
|
50
|
+
const error = url.searchParams.get("error");
|
|
61
51
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
52
|
+
if (error) {
|
|
53
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
54
|
+
res.end(
|
|
55
|
+
`<html><body><h1>Authentication Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`,
|
|
56
|
+
);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
69
59
|
|
|
70
|
-
|
|
71
|
-
|
|
60
|
+
if (code && state) {
|
|
61
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
62
|
+
res.end(
|
|
63
|
+
`<html><body><h1>Authentication Successful</h1><p>You can close this window and return to the terminal.</p></body></html>`,
|
|
64
|
+
);
|
|
65
|
+
result = { code, state };
|
|
66
|
+
} else {
|
|
67
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
68
|
+
res.end(
|
|
72
69
|
`<html><body><h1>Authentication Failed</h1><p>Missing code or state parameter.</p></body></html>`,
|
|
73
|
-
{ status: 400, headers: { "Content-Type": "text/html" } },
|
|
74
70
|
);
|
|
75
71
|
}
|
|
72
|
+
} else {
|
|
73
|
+
res.writeHead(404);
|
|
74
|
+
res.end();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
error(err) {
|
|
80
|
-
reject(err);
|
|
81
|
-
return new Response("Internal Server Error", { status: 500 });
|
|
82
|
-
},
|
|
78
|
+
server.on("error", (err) => {
|
|
79
|
+
reject(err);
|
|
83
80
|
});
|
|
84
81
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
server.listen(8085, "127.0.0.1", () => {
|
|
83
|
+
resolve({
|
|
84
|
+
server,
|
|
85
|
+
cancelWait: () => {
|
|
86
|
+
cancelled = true;
|
|
87
|
+
},
|
|
88
|
+
waitForCode: async () => {
|
|
89
|
+
const sleep = () => new Promise((r) => setTimeout(r, 100));
|
|
90
|
+
while (!result && !cancelled) {
|
|
91
|
+
await sleep();
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
},
|
|
95
|
+
});
|
|
88
96
|
});
|
|
89
97
|
});
|
|
90
98
|
}
|
|
91
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Parse redirect URL to extract code and state
|
|
102
|
+
*/
|
|
103
|
+
function parseRedirectUrl(input: string): { code?: string; state?: string } {
|
|
104
|
+
const value = input.trim();
|
|
105
|
+
if (!value) return {};
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const url = new URL(value);
|
|
109
|
+
return {
|
|
110
|
+
code: url.searchParams.get("code") ?? undefined,
|
|
111
|
+
state: url.searchParams.get("state") ?? undefined,
|
|
112
|
+
};
|
|
113
|
+
} catch {
|
|
114
|
+
// Not a URL, return empty
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
92
119
|
interface LoadCodeAssistPayload {
|
|
93
120
|
cloudaicompanionProject?: string;
|
|
94
121
|
currentTier?: { id?: string };
|
|
@@ -254,16 +281,21 @@ export async function refreshGoogleCloudToken(refreshToken: string, projectId: s
|
|
|
254
281
|
*
|
|
255
282
|
* @param onAuth - Callback with URL and optional instructions
|
|
256
283
|
* @param onProgress - Optional progress callback
|
|
284
|
+
* @param onManualCodeInput - Optional promise that resolves with user-pasted redirect URL.
|
|
285
|
+
* Races with browser callback - whichever completes first wins.
|
|
257
286
|
*/
|
|
258
287
|
export async function loginGeminiCli(
|
|
259
288
|
onAuth: (info: { url: string; instructions?: string }) => void,
|
|
260
289
|
onProgress?: (message: string) => void,
|
|
290
|
+
onManualCodeInput?: () => Promise<string>,
|
|
261
291
|
): Promise<OAuthCredentials> {
|
|
262
292
|
const { verifier, challenge } = await generatePKCE();
|
|
263
293
|
|
|
264
294
|
// Start local server for callback
|
|
265
295
|
onProgress?.("Starting local server for OAuth callback...");
|
|
266
|
-
const
|
|
296
|
+
const server = await startCallbackServer();
|
|
297
|
+
|
|
298
|
+
let code: string | undefined;
|
|
267
299
|
|
|
268
300
|
try {
|
|
269
301
|
// Build authorization URL
|
|
@@ -284,16 +316,75 @@ export async function loginGeminiCli(
|
|
|
284
316
|
// Notify caller with URL to open
|
|
285
317
|
onAuth({
|
|
286
318
|
url: authUrl,
|
|
287
|
-
instructions: "Complete the sign-in in your browser.
|
|
319
|
+
instructions: "Complete the sign-in in your browser.",
|
|
288
320
|
});
|
|
289
321
|
|
|
290
|
-
// Wait for the callback
|
|
322
|
+
// Wait for the callback, racing with manual input if provided
|
|
291
323
|
onProgress?.("Waiting for OAuth callback...");
|
|
292
|
-
const { code, state } = await getCode();
|
|
293
324
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
325
|
+
if (onManualCodeInput) {
|
|
326
|
+
// Race between browser callback and manual input
|
|
327
|
+
let manualInput: string | undefined;
|
|
328
|
+
let manualError: Error | undefined;
|
|
329
|
+
const manualPromise = onManualCodeInput()
|
|
330
|
+
.then((input) => {
|
|
331
|
+
manualInput = input;
|
|
332
|
+
server.cancelWait();
|
|
333
|
+
})
|
|
334
|
+
.catch((err) => {
|
|
335
|
+
manualError = err instanceof Error ? err : new Error(String(err));
|
|
336
|
+
server.cancelWait();
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const result = await server.waitForCode();
|
|
340
|
+
|
|
341
|
+
// If manual input was cancelled, throw that error
|
|
342
|
+
if (manualError) {
|
|
343
|
+
throw manualError;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (result?.code) {
|
|
347
|
+
// Browser callback won - verify state
|
|
348
|
+
if (result.state !== verifier) {
|
|
349
|
+
throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
350
|
+
}
|
|
351
|
+
code = result.code;
|
|
352
|
+
} else if (manualInput) {
|
|
353
|
+
// Manual input won
|
|
354
|
+
const parsed = parseRedirectUrl(manualInput);
|
|
355
|
+
if (parsed.state && parsed.state !== verifier) {
|
|
356
|
+
throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
357
|
+
}
|
|
358
|
+
code = parsed.code;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// If still no code, wait for manual promise and try that
|
|
362
|
+
if (!code) {
|
|
363
|
+
await manualPromise;
|
|
364
|
+
if (manualError) {
|
|
365
|
+
throw manualError;
|
|
366
|
+
}
|
|
367
|
+
if (manualInput) {
|
|
368
|
+
const parsed = parseRedirectUrl(manualInput);
|
|
369
|
+
if (parsed.state && parsed.state !== verifier) {
|
|
370
|
+
throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
371
|
+
}
|
|
372
|
+
code = parsed.code;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
} else {
|
|
376
|
+
// Original flow: just wait for callback
|
|
377
|
+
const result = await server.waitForCode();
|
|
378
|
+
if (result?.code) {
|
|
379
|
+
if (result.state !== verifier) {
|
|
380
|
+
throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
381
|
+
}
|
|
382
|
+
code = result.code;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!code) {
|
|
387
|
+
throw new Error("No authorization code received");
|
|
297
388
|
}
|
|
298
389
|
|
|
299
390
|
// Exchange code for tokens
|
|
@@ -348,6 +439,6 @@ export async function loginGeminiCli(
|
|
|
348
439
|
|
|
349
440
|
return credentials;
|
|
350
441
|
} finally {
|
|
351
|
-
server.
|
|
442
|
+
server.server.close();
|
|
352
443
|
}
|
|
353
444
|
}
|
package/src/utils/oauth/index.ts
CHANGED
|
@@ -133,6 +133,11 @@ export function getOAuthProviders(): OAuthProviderInfo[] {
|
|
|
133
133
|
name: "Anthropic (Claude Pro/Max)",
|
|
134
134
|
available: true,
|
|
135
135
|
},
|
|
136
|
+
{
|
|
137
|
+
id: "openai-codex",
|
|
138
|
+
name: "ChatGPT Plus/Pro (Codex Subscription)",
|
|
139
|
+
available: true,
|
|
140
|
+
},
|
|
136
141
|
{
|
|
137
142
|
id: "github-copilot",
|
|
138
143
|
name: "GitHub Copilot",
|
|
@@ -148,10 +153,5 @@ export function getOAuthProviders(): OAuthProviderInfo[] {
|
|
|
148
153
|
name: "Antigravity (Gemini 3, Claude, GPT-OSS)",
|
|
149
154
|
available: true,
|
|
150
155
|
},
|
|
151
|
-
{
|
|
152
|
-
id: "openai-codex",
|
|
153
|
-
name: "ChatGPT Plus/Pro (Codex Subscription)",
|
|
154
|
-
available: true,
|
|
155
|
-
},
|
|
156
156
|
];
|
|
157
157
|
}
|