@kolisachint/hoocode-ai 0.5.77 → 0.5.79

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 (40) hide show
  1. package/README.md +5 -1
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/models.generated.d.ts +296 -0
  6. package/dist/models.generated.d.ts.map +1 -1
  7. package/dist/models.generated.js +305 -11
  8. package/dist/models.generated.js.map +1 -1
  9. package/dist/providers/google-gemini-cli.d.ts +70 -0
  10. package/dist/providers/google-gemini-cli.d.ts.map +1 -0
  11. package/dist/providers/google-gemini-cli.js +774 -0
  12. package/dist/providers/google-gemini-cli.js.map +1 -0
  13. package/dist/providers/google-shared.d.ts +2 -2
  14. package/dist/providers/google-shared.d.ts.map +1 -1
  15. package/dist/providers/google-shared.js +1 -1
  16. package/dist/providers/google-shared.js.map +1 -1
  17. package/dist/providers/register-builtins.d.ts +3 -0
  18. package/dist/providers/register-builtins.d.ts.map +1 -1
  19. package/dist/providers/register-builtins.js +18 -0
  20. package/dist/providers/register-builtins.js.map +1 -1
  21. package/dist/types.d.ts +2 -2
  22. package/dist/types.d.ts.map +1 -1
  23. package/dist/types.js.map +1 -1
  24. package/dist/utils/oauth/google-antigravity.d.ts +26 -0
  25. package/dist/utils/oauth/google-antigravity.d.ts.map +1 -0
  26. package/dist/utils/oauth/google-antigravity.js +433 -0
  27. package/dist/utils/oauth/google-antigravity.js.map +1 -0
  28. package/dist/utils/oauth/google-gemini-cli.d.ts +26 -0
  29. package/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -0
  30. package/dist/utils/oauth/google-gemini-cli.js +493 -0
  31. package/dist/utils/oauth/google-gemini-cli.js.map +1 -0
  32. package/dist/utils/oauth/google-oauth-client.d.ts +30 -0
  33. package/dist/utils/oauth/google-oauth-client.d.ts.map +1 -0
  34. package/dist/utils/oauth/google-oauth-client.js +28 -0
  35. package/dist/utils/oauth/google-oauth-client.js.map +1 -0
  36. package/dist/utils/oauth/index.d.ts +4 -0
  37. package/dist/utils/oauth/index.d.ts.map +1 -1
  38. package/dist/utils/oauth/index.js +10 -0
  39. package/dist/utils/oauth/index.js.map +1 -1
  40. package/package.json +5 -1
@@ -0,0 +1,433 @@
1
+ /**
2
+ * Antigravity OAuth flow (Gemini 3, Claude, GPT-OSS via Google Cloud)
3
+ * Uses different OAuth credentials than google-gemini-cli for access to additional models.
4
+ *
5
+ * NOTE: This module uses Node.js http.createServer for the OAuth callback.
6
+ * It is only intended for CLI use, not browser environments.
7
+ */
8
+ import { readGoogleOAuthClient } from "./google-oauth-client.js";
9
+ import { oauthErrorHtml, oauthSuccessHtml } from "./oauth-page.js";
10
+ import { generatePKCE } from "./pkce.js";
11
+ let _createServer = null;
12
+ let _httpImportPromise = null;
13
+ if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
14
+ _httpImportPromise = import("node:http").then((m) => {
15
+ _createServer = m.createServer;
16
+ });
17
+ }
18
+ const CALLBACK_HOST = process.env.HOOCODE_OAUTH_CALLBACK_HOST || "127.0.0.1";
19
+ const CALLBACK_PORT = 51121;
20
+ // Which client signs in decides which tiers Google serves: the Antigravity
21
+ // client reaches the consumer free tier that the Gemini CLI client is refused
22
+ // with UNSUPPORTED_CLIENT. Google's token endpoint also demands a
23
+ // client_secret, so this cannot run as a bare public PKCE client the way the
24
+ // Anthropic and OpenAI logins do. hoocode ships no credentials; see
25
+ // google-oauth-client.ts.
26
+ const CLIENT_ENV = {
27
+ idVar: "HOOCODE_ANTIGRAVITY_CLIENT_ID",
28
+ secretVar: "HOOCODE_ANTIGRAVITY_CLIENT_SECRET",
29
+ productName: "Google Antigravity",
30
+ };
31
+ const REDIRECT_URI = "http://localhost:51121/oauth-callback";
32
+ // Antigravity requires additional scopes
33
+ const SCOPES = [
34
+ "https://www.googleapis.com/auth/cloud-platform",
35
+ "https://www.googleapis.com/auth/userinfo.email",
36
+ "https://www.googleapis.com/auth/userinfo.profile",
37
+ "https://www.googleapis.com/auth/cclog",
38
+ "https://www.googleapis.com/auth/experimentsandconfigs",
39
+ ];
40
+ const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
41
+ const TOKEN_URL = "https://oauth2.googleapis.com/token";
42
+ const CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
43
+ // Google's managed project for consumer Antigravity accounts, as handed back by
44
+ // `onboardUser` for a free-tier sign-in. Used when neither the account nor the
45
+ // environment names one: eligibility calls answer `{}` or 403 for stretches even
46
+ // on accounts that stream fine, so a discovery hiccup must not fail the login.
47
+ const MANAGED_CONSUMER_PROJECT = "aicode-consumers";
48
+ /**
49
+ * Start a local HTTP server to receive the OAuth callback
50
+ */
51
+ async function getNodeCreateServer() {
52
+ if (_createServer)
53
+ return _createServer;
54
+ if (_httpImportPromise) {
55
+ await _httpImportPromise;
56
+ }
57
+ if (_createServer)
58
+ return _createServer;
59
+ throw new Error("Antigravity OAuth is only available in Node.js environments");
60
+ }
61
+ async function startCallbackServer() {
62
+ const createServer = await getNodeCreateServer();
63
+ return new Promise((resolve, reject) => {
64
+ let settleWait;
65
+ const waitForCodePromise = new Promise((resolveWait) => {
66
+ let settled = false;
67
+ settleWait = (value) => {
68
+ if (settled)
69
+ return;
70
+ settled = true;
71
+ resolveWait(value);
72
+ };
73
+ });
74
+ const server = createServer((req, res) => {
75
+ const url = new URL(req.url || "", `http://localhost:${CALLBACK_PORT}`);
76
+ if (url.pathname === "/oauth-callback") {
77
+ const code = url.searchParams.get("code");
78
+ const state = url.searchParams.get("state");
79
+ const error = url.searchParams.get("error");
80
+ if (error) {
81
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
82
+ res.end(oauthErrorHtml("Google authentication did not complete.", `Error: ${error}`));
83
+ return;
84
+ }
85
+ if (code && state) {
86
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
87
+ res.end(oauthSuccessHtml("Google authentication completed. You can close this window."));
88
+ settleWait?.({ code, state });
89
+ }
90
+ else {
91
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
92
+ res.end(oauthErrorHtml("Missing code or state parameter."));
93
+ }
94
+ }
95
+ else {
96
+ res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
97
+ res.end(oauthErrorHtml("Callback route not found."));
98
+ }
99
+ });
100
+ server.on("error", (err) => {
101
+ reject(err);
102
+ });
103
+ server.listen(CALLBACK_PORT, CALLBACK_HOST, () => {
104
+ resolve({
105
+ server,
106
+ cancelWait: () => {
107
+ settleWait?.(null);
108
+ },
109
+ waitForCode: () => waitForCodePromise,
110
+ });
111
+ });
112
+ });
113
+ }
114
+ /**
115
+ * Parse redirect URL to extract code and state
116
+ */
117
+ function parseRedirectUrl(input) {
118
+ const value = input.trim();
119
+ if (!value)
120
+ return {};
121
+ try {
122
+ const url = new URL(value);
123
+ return {
124
+ code: url.searchParams.get("code") ?? undefined,
125
+ state: url.searchParams.get("state") ?? undefined,
126
+ };
127
+ }
128
+ catch {
129
+ // Not a URL, return empty
130
+ return {};
131
+ }
132
+ }
133
+ const ANTIGRAVITY_METADATA = {
134
+ ideType: "ANTIGRAVITY",
135
+ platform: "PLATFORM_UNSPECIFIED",
136
+ pluginType: "GEMINI",
137
+ };
138
+ function readProjectId(value) {
139
+ if (typeof value === "string")
140
+ return value || undefined;
141
+ return value?.id || undefined;
142
+ }
143
+ function wait(ms) {
144
+ return new Promise((resolve) => setTimeout(resolve, ms));
145
+ }
146
+ /**
147
+ * Discover or provision the Cloud project every Antigravity request is billed to.
148
+ *
149
+ * A free-tier account has no project of its own: `loadCodeAssist` answers with a
150
+ * tier and no `cloudaicompanionProject`, and it is `onboardUser` that hands back
151
+ * Google's managed project. Both calls are best effort — they have been observed
152
+ * answering `{}` and `403 not eligible` minutes apart on an account whose
153
+ * requests stream fine — so every step falls through to the next rather than
154
+ * failing the login.
155
+ */
156
+ async function discoverProject(accessToken, onProgress) {
157
+ const headers = {
158
+ Authorization: `Bearer ${accessToken}`,
159
+ "Content-Type": "application/json",
160
+ "User-Agent": "google-api-nodejs-client/9.15.1",
161
+ "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
162
+ "Client-Metadata": JSON.stringify(ANTIGRAVITY_METADATA),
163
+ };
164
+ const envProjectId = process.env.GOOGLE_CLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT_ID;
165
+ onProgress?.("Checking for an existing project...");
166
+ let tierId;
167
+ try {
168
+ const loadResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {
169
+ method: "POST",
170
+ headers,
171
+ body: JSON.stringify({ metadata: ANTIGRAVITY_METADATA }),
172
+ });
173
+ if (loadResponse.ok) {
174
+ const data = (await loadResponse.json());
175
+ const existing = readProjectId(data.cloudaicompanionProject);
176
+ if (existing) {
177
+ return existing;
178
+ }
179
+ tierId =
180
+ data.currentTier?.id ?? data.allowedTiers?.find((tier) => tier.isDefault)?.id ?? data.allowedTiers?.[0]?.id;
181
+ }
182
+ }
183
+ catch {
184
+ // Fall through to onboarding.
185
+ }
186
+ if (tierId) {
187
+ try {
188
+ onProgress?.("Provisioning the Antigravity project (this may take a moment)...");
189
+ const onboardBody = { tierId, metadata: ANTIGRAVITY_METADATA };
190
+ if (envProjectId) {
191
+ onboardBody.cloudaicompanionProject = envProjectId;
192
+ }
193
+ const onboardResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {
194
+ method: "POST",
195
+ headers,
196
+ body: JSON.stringify(onboardBody),
197
+ });
198
+ if (onboardResponse.ok) {
199
+ let operation = (await onboardResponse.json());
200
+ for (let attempt = 0; !operation.done && operation.name && attempt < 12; attempt++) {
201
+ await wait(5000);
202
+ onProgress?.(`Waiting for project provisioning (attempt ${attempt + 2})...`);
203
+ const poll = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal/${operation.name}`, { headers });
204
+ if (!poll.ok)
205
+ break;
206
+ operation = (await poll.json());
207
+ }
208
+ const provisioned = readProjectId(operation.response?.cloudaicompanionProject);
209
+ if (provisioned) {
210
+ return provisioned;
211
+ }
212
+ }
213
+ }
214
+ catch {
215
+ // Fall through to the environment / managed project.
216
+ }
217
+ }
218
+ if (envProjectId) {
219
+ return envProjectId;
220
+ }
221
+ onProgress?.("Using Google's managed Antigravity project...");
222
+ return MANAGED_CONSUMER_PROJECT;
223
+ }
224
+ /**
225
+ * Get user email from the access token
226
+ */
227
+ async function getUserEmail(accessToken) {
228
+ try {
229
+ const response = await fetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", {
230
+ headers: {
231
+ Authorization: `Bearer ${accessToken}`,
232
+ },
233
+ });
234
+ if (response.ok) {
235
+ const data = (await response.json());
236
+ return data.email;
237
+ }
238
+ }
239
+ catch {
240
+ // Ignore errors, email is optional
241
+ }
242
+ return undefined;
243
+ }
244
+ /**
245
+ * Refresh Antigravity token
246
+ */
247
+ export async function refreshAntigravityToken(refreshToken, projectId) {
248
+ const { clientId, clientSecret } = readGoogleOAuthClient(CLIENT_ENV);
249
+ const response = await fetch(TOKEN_URL, {
250
+ method: "POST",
251
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
252
+ body: new URLSearchParams({
253
+ client_id: clientId,
254
+ client_secret: clientSecret,
255
+ refresh_token: refreshToken,
256
+ grant_type: "refresh_token",
257
+ }),
258
+ });
259
+ if (!response.ok) {
260
+ const error = await response.text();
261
+ throw new Error(`Antigravity token refresh failed: ${error}`);
262
+ }
263
+ const data = (await response.json());
264
+ return {
265
+ refresh: data.refresh_token || refreshToken,
266
+ access: data.access_token,
267
+ expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
268
+ projectId,
269
+ };
270
+ }
271
+ /**
272
+ * Login with Antigravity OAuth
273
+ *
274
+ * @param onAuth - Callback with URL and optional instructions
275
+ * @param onProgress - Optional progress callback
276
+ * @param onManualCodeInput - Optional promise that resolves with user-pasted redirect URL.
277
+ * Races with browser callback - whichever completes first wins.
278
+ */
279
+ export async function loginAntigravity(onAuth, onProgress, onManualCodeInput) {
280
+ const { verifier, challenge } = await generatePKCE();
281
+ // Read before the browser opens: a missing client should fail here, not after
282
+ // the user has signed in.
283
+ const { clientId, clientSecret } = readGoogleOAuthClient(CLIENT_ENV);
284
+ // Start local server for callback
285
+ onProgress?.("Starting local server for OAuth callback...");
286
+ const server = await startCallbackServer();
287
+ let code;
288
+ try {
289
+ // Build authorization URL
290
+ const authParams = new URLSearchParams({
291
+ client_id: clientId,
292
+ response_type: "code",
293
+ redirect_uri: REDIRECT_URI,
294
+ scope: SCOPES.join(" "),
295
+ code_challenge: challenge,
296
+ code_challenge_method: "S256",
297
+ state: verifier,
298
+ access_type: "offline",
299
+ prompt: "consent",
300
+ });
301
+ const authUrl = `${AUTH_URL}?${authParams.toString()}`;
302
+ // Notify caller with URL to open
303
+ onAuth({
304
+ url: authUrl,
305
+ instructions: "Complete the sign-in in your browser.",
306
+ });
307
+ // Wait for the callback, racing with manual input if provided
308
+ onProgress?.("Waiting for OAuth callback...");
309
+ if (onManualCodeInput) {
310
+ // Race between browser callback and manual input
311
+ let manualInput;
312
+ let manualError;
313
+ const manualPromise = onManualCodeInput()
314
+ .then((input) => {
315
+ manualInput = input;
316
+ server.cancelWait();
317
+ })
318
+ .catch((err) => {
319
+ manualError = err instanceof Error ? err : new Error(String(err));
320
+ server.cancelWait();
321
+ });
322
+ const result = await server.waitForCode();
323
+ // If manual input was cancelled, throw that error
324
+ if (manualError) {
325
+ throw manualError;
326
+ }
327
+ if (result?.code) {
328
+ // Browser callback won - verify state
329
+ if (result.state !== verifier) {
330
+ throw new Error("OAuth state mismatch - possible CSRF attack");
331
+ }
332
+ code = result.code;
333
+ }
334
+ else if (manualInput) {
335
+ // Manual input won
336
+ const parsed = parseRedirectUrl(manualInput);
337
+ if (parsed.state && parsed.state !== verifier) {
338
+ throw new Error("OAuth state mismatch - possible CSRF attack");
339
+ }
340
+ code = parsed.code;
341
+ }
342
+ // If still no code, wait for manual promise and try that
343
+ if (!code) {
344
+ await manualPromise;
345
+ if (manualError) {
346
+ throw manualError;
347
+ }
348
+ if (manualInput) {
349
+ const parsed = parseRedirectUrl(manualInput);
350
+ if (parsed.state && parsed.state !== verifier) {
351
+ throw new Error("OAuth state mismatch - possible CSRF attack");
352
+ }
353
+ code = parsed.code;
354
+ }
355
+ }
356
+ }
357
+ else {
358
+ // Original flow: just wait for callback
359
+ const result = await server.waitForCode();
360
+ if (result?.code) {
361
+ if (result.state !== verifier) {
362
+ throw new Error("OAuth state mismatch - possible CSRF attack");
363
+ }
364
+ code = result.code;
365
+ }
366
+ }
367
+ if (!code) {
368
+ throw new Error("No authorization code received");
369
+ }
370
+ // Exchange code for tokens
371
+ onProgress?.("Exchanging authorization code for tokens...");
372
+ const tokenResponse = await fetch(TOKEN_URL, {
373
+ method: "POST",
374
+ headers: {
375
+ "Content-Type": "application/x-www-form-urlencoded",
376
+ },
377
+ body: new URLSearchParams({
378
+ client_id: clientId,
379
+ client_secret: clientSecret,
380
+ code,
381
+ grant_type: "authorization_code",
382
+ redirect_uri: REDIRECT_URI,
383
+ code_verifier: verifier,
384
+ }),
385
+ });
386
+ if (!tokenResponse.ok) {
387
+ const error = await tokenResponse.text();
388
+ throw new Error(`Token exchange failed: ${error}`);
389
+ }
390
+ const tokenData = (await tokenResponse.json());
391
+ if (!tokenData.refresh_token) {
392
+ throw new Error("No refresh token received. Please try again.");
393
+ }
394
+ // Get user email
395
+ onProgress?.("Getting user info...");
396
+ const email = await getUserEmail(tokenData.access_token);
397
+ // Discover project
398
+ const projectId = await discoverProject(tokenData.access_token, onProgress);
399
+ // Calculate expiry time (current time + expires_in seconds - 5 min buffer)
400
+ const expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;
401
+ const credentials = {
402
+ refresh: tokenData.refresh_token,
403
+ access: tokenData.access_token,
404
+ expires: expiresAt,
405
+ projectId,
406
+ email,
407
+ };
408
+ return credentials;
409
+ }
410
+ finally {
411
+ server.server.close();
412
+ }
413
+ }
414
+ export const antigravityOAuthProvider = {
415
+ id: "google-antigravity",
416
+ name: "Google Antigravity (Gemini, Claude, GPT-OSS)",
417
+ usesCallbackServer: true,
418
+ async login(callbacks) {
419
+ return loginAntigravity(callbacks.onAuth, callbacks.onProgress, callbacks.onManualCodeInput);
420
+ },
421
+ async refreshToken(credentials) {
422
+ const creds = credentials;
423
+ if (!creds.projectId) {
424
+ throw new Error("Antigravity credentials missing projectId");
425
+ }
426
+ return refreshAntigravityToken(creds.refresh, creds.projectId);
427
+ },
428
+ getApiKey(credentials) {
429
+ const creds = credentials;
430
+ return JSON.stringify({ token: creds.access, projectId: creds.projectId });
431
+ },
432
+ };
433
+ //# sourceMappingURL=google-antigravity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-antigravity.js","sourceRoot":"","sources":["../../../src/utils/oauth/google-antigravity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Antigravity OAuth flow (Gemini 3, Claude, GPT-OSS via Google Cloud)\n * Uses different OAuth credentials than google-gemini-cli for access to additional models.\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 \"node:http\";\nimport { type GoogleOAuthClientEnv, readGoogleOAuthClient } from \"./google-oauth-client.js\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"./oauth-page.js\";\nimport { generatePKCE } from \"./pkce.js\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.js\";\n\ntype AntigravityCredentials = OAuthCredentials & {\n\tprojectId: string;\n};\n\nlet _createServer: typeof import(\"node:http\").createServer | null = null;\nlet _httpImportPromise: Promise<void> | null = null;\nif (typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun)) {\n\t_httpImportPromise = import(\"node:http\").then((m) => {\n\t\t_createServer = m.createServer;\n\t});\n}\n\nconst CALLBACK_HOST = process.env.HOOCODE_OAUTH_CALLBACK_HOST || \"127.0.0.1\";\nconst CALLBACK_PORT = 51121;\n\n// Which client signs in decides which tiers Google serves: the Antigravity\n// client reaches the consumer free tier that the Gemini CLI client is refused\n// with UNSUPPORTED_CLIENT. Google's token endpoint also demands a\n// client_secret, so this cannot run as a bare public PKCE client the way the\n// Anthropic and OpenAI logins do. hoocode ships no credentials; see\n// google-oauth-client.ts.\nconst CLIENT_ENV: GoogleOAuthClientEnv = {\n\tidVar: \"HOOCODE_ANTIGRAVITY_CLIENT_ID\",\n\tsecretVar: \"HOOCODE_ANTIGRAVITY_CLIENT_SECRET\",\n\tproductName: \"Google Antigravity\",\n};\nconst REDIRECT_URI = \"http://localhost:51121/oauth-callback\";\n\n// Antigravity requires additional scopes\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\t\"https://www.googleapis.com/auth/cclog\",\n\t\"https://www.googleapis.com/auth/experimentsandconfigs\",\n];\n\nconst AUTH_URL = \"https://accounts.google.com/o/oauth2/v2/auth\";\nconst TOKEN_URL = \"https://oauth2.googleapis.com/token\";\n\nconst CODE_ASSIST_ENDPOINT = \"https://cloudcode-pa.googleapis.com\";\n\n// Google's managed project for consumer Antigravity accounts, as handed back by\n// `onboardUser` for a free-tier sign-in. Used when neither the account nor the\n// environment names one: eligibility calls answer `{}` or 403 for stretches even\n// on accounts that stream fine, so a discovery hiccup must not fail the login.\nconst MANAGED_CONSUMER_PROJECT = \"aicode-consumers\";\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 getNodeCreateServer(): Promise<typeof import(\"node:http\").createServer> {\n\tif (_createServer) return _createServer;\n\tif (_httpImportPromise) {\n\t\tawait _httpImportPromise;\n\t}\n\tif (_createServer) return _createServer;\n\tthrow new Error(\"Antigravity OAuth is only available in Node.js environments\");\n}\n\nasync function startCallbackServer(): Promise<CallbackServerInfo> {\n\tconst createServer = await getNodeCreateServer();\n\n\treturn new Promise((resolve, reject) => {\n\t\tlet settleWait: ((value: { code: string; state: string } | null) => void) | undefined;\n\t\tconst waitForCodePromise = new Promise<{ code: string; state: string } | null>((resolveWait) => {\n\t\t\tlet settled = false;\n\t\t\tsettleWait = (value) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tresolveWait(value);\n\t\t\t};\n\t\t});\n\n\t\tconst server = createServer((req, res) => {\n\t\t\tconst url = new URL(req.url || \"\", `http://localhost:${CALLBACK_PORT}`);\n\n\t\t\tif (url.pathname === \"/oauth-callback\") {\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; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"Google authentication did not complete.\", `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; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthSuccessHtml(\"Google authentication completed. You can close this window.\"));\n\t\t\t\t\tsettleWait?.({ code, state });\n\t\t\t\t} else {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"Missing code or state parameter.\"));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tres.writeHead(404, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\tres.end(oauthErrorHtml(\"Callback route not found.\"));\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(CALLBACK_PORT, CALLBACK_HOST, () => {\n\t\t\tresolve({\n\t\t\t\tserver,\n\t\t\t\tcancelWait: () => {\n\t\t\t\t\tsettleWait?.(null);\n\t\t\t\t},\n\t\t\t\twaitForCode: () => waitForCodePromise,\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 | { id?: string };\n\tcurrentTier?: { id?: string };\n\tallowedTiers?: Array<{ id?: string; isDefault?: boolean }>;\n}\n\n/** Long-running operation returned by onboardUser. */\ninterface OnboardUserOperation {\n\tname?: string;\n\tdone?: boolean;\n\tresponse?: { cloudaicompanionProject?: string | { id?: string } };\n}\n\nconst ANTIGRAVITY_METADATA = {\n\tideType: \"ANTIGRAVITY\",\n\tplatform: \"PLATFORM_UNSPECIFIED\",\n\tpluginType: \"GEMINI\",\n};\n\nfunction readProjectId(value: string | { id?: string } | undefined): string | undefined {\n\tif (typeof value === \"string\") return value || undefined;\n\treturn value?.id || undefined;\n}\n\nfunction wait(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Discover or provision the Cloud project every Antigravity request is billed to.\n *\n * A free-tier account has no project of its own: `loadCodeAssist` answers with a\n * tier and no `cloudaicompanionProject`, and it is `onboardUser` that hands back\n * Google's managed project. Both calls are best effort — they have been observed\n * answering `{}` and `403 not eligible` minutes apart on an account whose\n * requests stream fine — so every step falls through to the next rather than\n * failing the login.\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\": \"google-cloud-sdk vscode_cloudshelleditor/0.1\",\n\t\t\"Client-Metadata\": JSON.stringify(ANTIGRAVITY_METADATA),\n\t};\n\tconst envProjectId = process.env.GOOGLE_CLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT_ID;\n\n\tonProgress?.(\"Checking for an existing project...\");\n\tlet tierId: string | undefined;\n\ttry {\n\t\tconst loadResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders,\n\t\t\tbody: JSON.stringify({ metadata: ANTIGRAVITY_METADATA }),\n\t\t});\n\t\tif (loadResponse.ok) {\n\t\t\tconst data = (await loadResponse.json()) as LoadCodeAssistPayload;\n\t\t\tconst existing = readProjectId(data.cloudaicompanionProject);\n\t\t\tif (existing) {\n\t\t\t\treturn existing;\n\t\t\t}\n\t\t\ttierId =\n\t\t\t\tdata.currentTier?.id ?? data.allowedTiers?.find((tier) => tier.isDefault)?.id ?? data.allowedTiers?.[0]?.id;\n\t\t}\n\t} catch {\n\t\t// Fall through to onboarding.\n\t}\n\n\tif (tierId) {\n\t\ttry {\n\t\t\tonProgress?.(\"Provisioning the Antigravity project (this may take a moment)...\");\n\t\t\tconst onboardBody: Record<string, unknown> = { tierId, metadata: ANTIGRAVITY_METADATA };\n\t\t\tif (envProjectId) {\n\t\t\t\tonboardBody.cloudaicompanionProject = envProjectId;\n\t\t\t}\n\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(onboardBody),\n\t\t\t});\n\n\t\t\tif (onboardResponse.ok) {\n\t\t\t\tlet operation = (await onboardResponse.json()) as OnboardUserOperation;\n\t\t\t\tfor (let attempt = 0; !operation.done && operation.name && attempt < 12; attempt++) {\n\t\t\t\t\tawait wait(5000);\n\t\t\t\t\tonProgress?.(`Waiting for project provisioning (attempt ${attempt + 2})...`);\n\t\t\t\t\tconst poll = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal/${operation.name}`, { headers });\n\t\t\t\t\tif (!poll.ok) break;\n\t\t\t\t\toperation = (await poll.json()) as OnboardUserOperation;\n\t\t\t\t}\n\t\t\t\tconst provisioned = readProjectId(operation.response?.cloudaicompanionProject);\n\t\t\t\tif (provisioned) {\n\t\t\t\t\treturn provisioned;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Fall through to the environment / managed project.\n\t\t}\n\t}\n\n\tif (envProjectId) {\n\t\treturn envProjectId;\n\t}\n\n\tonProgress?.(\"Using Google's managed Antigravity project...\");\n\treturn MANAGED_CONSUMER_PROJECT;\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 Antigravity token\n */\nexport async function refreshAntigravityToken(refreshToken: string, projectId: string): Promise<OAuthCredentials> {\n\tconst { clientId, clientSecret } = readGoogleOAuthClient(CLIENT_ENV);\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: clientId,\n\t\t\tclient_secret: clientSecret,\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(`Antigravity 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 Antigravity 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 loginAntigravity(\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// Read before the browser opens: a missing client should fail here, not after\n\t// the user has signed in.\n\tconst { clientId, clientSecret } = readGoogleOAuthClient(CLIENT_ENV);\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: clientId,\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: clientId,\n\t\t\t\tclient_secret: clientSecret,\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\nexport const antigravityOAuthProvider: OAuthProviderInterface = {\n\tid: \"google-antigravity\",\n\tname: \"Google Antigravity (Gemini, Claude, GPT-OSS)\",\n\tusesCallbackServer: true,\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\treturn loginAntigravity(callbacks.onAuth, callbacks.onProgress, callbacks.onManualCodeInput);\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\tconst creds = credentials as AntigravityCredentials;\n\t\tif (!creds.projectId) {\n\t\t\tthrow new Error(\"Antigravity credentials missing projectId\");\n\t\t}\n\t\treturn refreshAntigravityToken(creds.refresh, creds.projectId);\n\t},\n\n\tgetApiKey(credentials: OAuthCredentials): string {\n\t\tconst creds = credentials as AntigravityCredentials;\n\t\treturn JSON.stringify({ token: creds.access, projectId: creds.projectId });\n\t},\n};\n"]}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Gemini CLI OAuth flow (Google Cloud Code Assist)
3
+ * Standard Gemini models only (gemini-2.0-flash, gemini-2.5-*)
4
+ *
5
+ * NOTE: This module uses Node.js http.createServer for the OAuth callback.
6
+ * It is only intended for CLI use, not browser environments.
7
+ */
8
+ import type { OAuthCredentials, OAuthProviderInterface } from "./types.js";
9
+ /**
10
+ * Refresh Google Cloud Code Assist token
11
+ */
12
+ export declare function refreshGoogleCloudToken(refreshToken: string, projectId: string): Promise<OAuthCredentials>;
13
+ /**
14
+ * Login with Gemini CLI (Google Cloud Code Assist) OAuth
15
+ *
16
+ * @param onAuth - Callback with URL and optional instructions
17
+ * @param onProgress - Optional progress callback
18
+ * @param onManualCodeInput - Optional promise that resolves with user-pasted redirect URL.
19
+ * Races with browser callback - whichever completes first wins.
20
+ */
21
+ export declare function loginGeminiCli(onAuth: (info: {
22
+ url: string;
23
+ instructions?: string;
24
+ }) => void, onProgress?: (message: string) => void, onManualCodeInput?: () => Promise<string>): Promise<OAuthCredentials>;
25
+ export declare const geminiCliOAuthProvider: OAuthProviderInterface;
26
+ //# sourceMappingURL=google-gemini-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-gemini-cli.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/google-gemini-cli.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAuB,sBAAsB,EAAE,MAAM,YAAY,CAAC;AA+WhG;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA8BhH;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CACnC,MAAM,EAAE,CAAC,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,EAC9D,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EACtC,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GACvC,OAAO,CAAC,gBAAgB,CAAC,CA6J3B;AAED,eAAO,MAAM,sBAAsB,EAAE,sBAqBpC,CAAC","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 \"node:http\";\nimport { type GoogleOAuthClientEnv, readGoogleOAuthClient } from \"./google-oauth-client.js\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"./oauth-page.js\";\nimport { generatePKCE } from \"./pkce.js\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.js\";\n\ntype GeminiCredentials = OAuthCredentials & {\n\tprojectId: string;\n};\n\nlet _createServer: typeof import(\"node:http\").createServer | null = null;\nlet _httpImportPromise: Promise<void> | null = null;\nif (typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun)) {\n\t_httpImportPromise = import(\"node:http\").then((m) => {\n\t\t_createServer = m.createServer;\n\t});\n}\n\nconst CALLBACK_HOST = process.env.HOOCODE_OAUTH_CALLBACK_HOST || \"127.0.0.1\";\nconst CALLBACK_PORT = 8085;\n\n// Google's token endpoint rejects these clients without a client_secret, so\n// unlike the Anthropic and OpenAI logins this one cannot run as a bare public\n// PKCE client. hoocode ships no credentials; see google-oauth-client.ts.\nconst CLIENT_ENV: GoogleOAuthClientEnv = {\n\tidVar: \"HOOCODE_GEMINI_CLI_CLIENT_ID\",\n\tsecretVar: \"HOOCODE_GEMINI_CLI_CLIENT_SECRET\",\n\tproductName: \"Google Cloud Code Assist (Gemini CLI)\",\n};\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 getNodeCreateServer(): Promise<typeof import(\"node:http\").createServer> {\n\tif (_createServer) return _createServer;\n\tif (_httpImportPromise) {\n\t\tawait _httpImportPromise;\n\t}\n\tif (_createServer) return _createServer;\n\tthrow new Error(\"Gemini CLI OAuth is only available in Node.js environments\");\n}\n\nasync function startCallbackServer(): Promise<CallbackServerInfo> {\n\tconst createServer = await getNodeCreateServer();\n\n\treturn new Promise((resolve, reject) => {\n\t\tlet settleWait: ((value: { code: string; state: string } | null) => void) | undefined;\n\t\tconst waitForCodePromise = new Promise<{ code: string; state: string } | null>((resolveWait) => {\n\t\t\tlet settled = false;\n\t\t\tsettleWait = (value) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tresolveWait(value);\n\t\t\t};\n\t\t});\n\n\t\tconst server = createServer((req, res) => {\n\t\t\tconst url = new URL(req.url || \"\", `http://localhost:${CALLBACK_PORT}`);\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; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"Google authentication did not complete.\", `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; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthSuccessHtml(\"Google authentication completed. You can close this window.\"));\n\t\t\t\t\tsettleWait?.({ code, state });\n\t\t\t\t} else {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\t\tres.end(oauthErrorHtml(\"Missing code or state parameter.\"));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tres.writeHead(404, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\t\tres.end(oauthErrorHtml(\"Callback route not found.\"));\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(CALLBACK_PORT, CALLBACK_HOST, () => {\n\t\t\tresolve({\n\t\t\t\tserver,\n\t\t\t\tcancelWait: () => {\n\t\t\t\t\tsettleWait?.(null);\n\t\t\t\t},\n\t\t\t\twaitForCode: () => waitForCodePromise,\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\n/**\n * Long-running operation response from onboardUser\n */\ninterface LongRunningOperationResponse {\n\tname?: string;\n\tdone?: boolean;\n\tresponse?: {\n\t\tcloudaicompanionProject?: { id?: string };\n\t};\n}\n\n// Tier IDs as used by the Cloud Code API\nconst TIER_FREE = \"free-tier\";\nconst TIER_LEGACY = \"legacy-tier\";\nconst TIER_STANDARD = \"standard-tier\";\n\ninterface GoogleRpcErrorResponse {\n\terror?: {\n\t\tdetails?: Array<{ reason?: 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 from allowed tiers\n */\nfunction getDefaultTier(allowedTiers?: Array<{ id?: string; isDefault?: boolean }>): { id?: string } {\n\tif (!allowedTiers || allowedTiers.length === 0) return { id: TIER_LEGACY };\n\tconst defaultTier = allowedTiers.find((t) => t.isDefault);\n\treturn defaultTier ?? { id: TIER_LEGACY };\n}\n\nfunction isVpcScAffectedUser(payload: unknown): boolean {\n\tif (!payload || typeof payload !== \"object\") return false;\n\tif (!(\"error\" in payload)) return false;\n\tconst error = (payload as GoogleRpcErrorResponse).error;\n\tif (!error?.details || !Array.isArray(error.details)) return false;\n\treturn error.details.some((detail) => detail.reason === \"SECURITY_POLICY_VIOLATED\");\n}\n\n/**\n * Poll a long-running operation until completion\n */\nasync function pollOperation(\n\toperationName: string,\n\theaders: Record<string, string>,\n\tonProgress?: (message: string) => void,\n): Promise<LongRunningOperationResponse> {\n\tlet attempt = 0;\n\twhile (true) {\n\t\tif (attempt > 0) {\n\t\t\tonProgress?.(`Waiting for project provisioning (attempt ${attempt + 1})...`);\n\t\t\tawait wait(5000);\n\t\t}\n\n\t\tconst response = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal/${operationName}`, {\n\t\t\tmethod: \"GET\",\n\t\t\theaders,\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to poll operation: ${response.status} ${response.statusText}`);\n\t\t}\n\n\t\tconst data = (await response.json()) as LongRunningOperationResponse;\n\t\tif (data.done) {\n\t\t\treturn data;\n\t\t}\n\n\t\tattempt += 1;\n\t}\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\t// Check for user-provided project ID via environment variable\n\tconst envProjectId = process.env.GOOGLE_CLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT_ID;\n\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\tcloudaicompanionProject: envProjectId,\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\tduetProject: envProjectId,\n\t\t\t},\n\t\t}),\n\t});\n\n\tlet data: LoadCodeAssistPayload;\n\n\tif (!loadResponse.ok) {\n\t\tlet errorPayload: unknown;\n\t\ttry {\n\t\t\terrorPayload = await loadResponse.clone().json();\n\t\t} catch {\n\t\t\terrorPayload = undefined;\n\t\t}\n\n\t\tif (isVpcScAffectedUser(errorPayload)) {\n\t\t\tdata = { currentTier: { id: TIER_STANDARD } };\n\t\t} else {\n\t\t\tconst errorText = await loadResponse.text();\n\t\t\tthrow new Error(`loadCodeAssist failed: ${loadResponse.status} ${loadResponse.statusText}: ${errorText}`);\n\t\t}\n\t} else {\n\t\tdata = (await loadResponse.json()) as LoadCodeAssistPayload;\n\t}\n\n\t// If user already has a current tier and project, use it\n\tif (data.currentTier) {\n\t\tif (data.cloudaicompanionProject) {\n\t\t\treturn data.cloudaicompanionProject;\n\t\t}\n\t\t// User has a tier but no managed project - they need to provide one via env var\n\t\tif (envProjectId) {\n\t\t\treturn envProjectId;\n\t\t}\n\t\tthrow new Error(\n\t\t\t\"This account requires setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID environment variable. \" +\n\t\t\t\t\"See https://goo.gle/gemini-cli-auth-docs#workspace-gca\",\n\t\t);\n\t}\n\n\t// User needs to be onboarded - get the default tier\n\tconst tier = getDefaultTier(data.allowedTiers);\n\tconst tierId = tier?.id ?? TIER_FREE;\n\n\tif (tierId !== TIER_FREE && !envProjectId) {\n\t\tthrow new Error(\n\t\t\t\"This account requires setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID environment variable. \" +\n\t\t\t\t\"See https://goo.gle/gemini-cli-auth-docs#workspace-gca\",\n\t\t);\n\t}\n\n\tonProgress?.(\"Provisioning Cloud Code Assist project (this may take a moment)...\");\n\n\t// Build onboard request - for free tier, don't include project ID (Google provisions one)\n\t// For other tiers, include the user's project ID if available\n\tconst onboardBody: Record<string, unknown> = {\n\t\ttierId,\n\t\tmetadata: {\n\t\t\tideType: \"IDE_UNSPECIFIED\",\n\t\t\tplatform: \"PLATFORM_UNSPECIFIED\",\n\t\t\tpluginType: \"GEMINI\",\n\t\t},\n\t};\n\n\tif (tierId !== TIER_FREE && envProjectId) {\n\t\tonboardBody.cloudaicompanionProject = envProjectId;\n\t\t(onboardBody.metadata as Record<string, unknown>).duetProject = envProjectId;\n\t}\n\n\t// Start onboarding - this returns a long-running operation\n\tconst onboardResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {\n\t\tmethod: \"POST\",\n\t\theaders,\n\t\tbody: JSON.stringify(onboardBody),\n\t});\n\n\tif (!onboardResponse.ok) {\n\t\tconst errorText = await onboardResponse.text();\n\t\tthrow new Error(`onboardUser failed: ${onboardResponse.status} ${onboardResponse.statusText}: ${errorText}`);\n\t}\n\n\tlet lroData = (await onboardResponse.json()) as LongRunningOperationResponse;\n\n\t// If the operation isn't done yet, poll until completion\n\tif (!lroData.done && lroData.name) {\n\t\tlroData = await pollOperation(lroData.name, headers, onProgress);\n\t}\n\n\t// Try to get project ID from the response\n\tconst projectId = lroData.response?.cloudaicompanionProject?.id;\n\tif (projectId) {\n\t\treturn projectId;\n\t}\n\n\t// If no project ID from onboarding, fall back to env var\n\tif (envProjectId) {\n\t\treturn envProjectId;\n\t}\n\n\tthrow new Error(\n\t\t\"Could not discover or provision a Google Cloud project. \" +\n\t\t\t\"Try setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID environment variable. \" +\n\t\t\t\"See https://goo.gle/gemini-cli-auth-docs#workspace-gca\",\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 { clientId, clientSecret } = readGoogleOAuthClient(CLIENT_ENV);\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: clientId,\n\t\t\tclient_secret: clientSecret,\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\t// Read before the browser opens: a missing client should fail here, not after\n\t// the user has signed in.\n\tconst { clientId, clientSecret } = readGoogleOAuthClient(CLIENT_ENV);\n\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: clientId,\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: clientId,\n\t\t\t\tclient_secret: clientSecret,\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\nexport const geminiCliOAuthProvider: OAuthProviderInterface = {\n\tid: \"google-gemini-cli\",\n\tname: \"Google Cloud Code Assist (Gemini CLI)\",\n\tusesCallbackServer: true,\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\treturn loginGeminiCli(callbacks.onAuth, callbacks.onProgress, callbacks.onManualCodeInput);\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\tconst creds = credentials as GeminiCredentials;\n\t\tif (!creds.projectId) {\n\t\t\tthrow new Error(\"Google Cloud credentials missing projectId\");\n\t\t}\n\t\treturn refreshGoogleCloudToken(creds.refresh, creds.projectId);\n\t},\n\n\tgetApiKey(credentials: OAuthCredentials): string {\n\t\tconst creds = credentials as GeminiCredentials;\n\t\treturn JSON.stringify({ token: creds.access, projectId: creds.projectId });\n\t},\n};\n"]}