@draht/ai 2026.3.2-2

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 (154) hide show
  1. package/README.md +1185 -0
  2. package/dist/api-registry.d.ts +20 -0
  3. package/dist/api-registry.d.ts.map +1 -0
  4. package/dist/api-registry.js +44 -0
  5. package/dist/api-registry.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +116 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/env-api-keys.d.ts +9 -0
  11. package/dist/env-api-keys.d.ts.map +1 -0
  12. package/dist/env-api-keys.js +99 -0
  13. package/dist/env-api-keys.js.map +1 -0
  14. package/dist/index.d.ts +22 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +21 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/models.d.ts +24 -0
  19. package/dist/models.d.ts.map +1 -0
  20. package/dist/models.generated.d.ts +13133 -0
  21. package/dist/models.generated.d.ts.map +1 -0
  22. package/dist/models.generated.js +12939 -0
  23. package/dist/models.generated.js.map +1 -0
  24. package/dist/models.js +55 -0
  25. package/dist/models.js.map +1 -0
  26. package/dist/providers/amazon-bedrock.d.ts +15 -0
  27. package/dist/providers/amazon-bedrock.d.ts.map +1 -0
  28. package/dist/providers/amazon-bedrock.js +585 -0
  29. package/dist/providers/amazon-bedrock.js.map +1 -0
  30. package/dist/providers/anthropic.d.ts +33 -0
  31. package/dist/providers/anthropic.d.ts.map +1 -0
  32. package/dist/providers/anthropic.js +729 -0
  33. package/dist/providers/anthropic.js.map +1 -0
  34. package/dist/providers/azure-openai-responses.d.ts +15 -0
  35. package/dist/providers/azure-openai-responses.d.ts.map +1 -0
  36. package/dist/providers/azure-openai-responses.js +184 -0
  37. package/dist/providers/azure-openai-responses.js.map +1 -0
  38. package/dist/providers/github-copilot-headers.d.ts +8 -0
  39. package/dist/providers/github-copilot-headers.d.ts.map +1 -0
  40. package/dist/providers/github-copilot-headers.js +29 -0
  41. package/dist/providers/github-copilot-headers.js.map +1 -0
  42. package/dist/providers/google-gemini-cli.d.ts +74 -0
  43. package/dist/providers/google-gemini-cli.d.ts.map +1 -0
  44. package/dist/providers/google-gemini-cli.js +735 -0
  45. package/dist/providers/google-gemini-cli.js.map +1 -0
  46. package/dist/providers/google-shared.d.ts +65 -0
  47. package/dist/providers/google-shared.d.ts.map +1 -0
  48. package/dist/providers/google-shared.js +306 -0
  49. package/dist/providers/google-shared.js.map +1 -0
  50. package/dist/providers/google-vertex.d.ts +15 -0
  51. package/dist/providers/google-vertex.d.ts.map +1 -0
  52. package/dist/providers/google-vertex.js +371 -0
  53. package/dist/providers/google-vertex.js.map +1 -0
  54. package/dist/providers/google.d.ts +13 -0
  55. package/dist/providers/google.d.ts.map +1 -0
  56. package/dist/providers/google.js +352 -0
  57. package/dist/providers/google.js.map +1 -0
  58. package/dist/providers/openai-codex-responses.d.ts +9 -0
  59. package/dist/providers/openai-codex-responses.d.ts.map +1 -0
  60. package/dist/providers/openai-codex-responses.js +699 -0
  61. package/dist/providers/openai-codex-responses.js.map +1 -0
  62. package/dist/providers/openai-completions.d.ts +15 -0
  63. package/dist/providers/openai-completions.d.ts.map +1 -0
  64. package/dist/providers/openai-completions.js +712 -0
  65. package/dist/providers/openai-completions.js.map +1 -0
  66. package/dist/providers/openai-responses-shared.d.ts +17 -0
  67. package/dist/providers/openai-responses-shared.d.ts.map +1 -0
  68. package/dist/providers/openai-responses-shared.js +427 -0
  69. package/dist/providers/openai-responses-shared.js.map +1 -0
  70. package/dist/providers/openai-responses.d.ts +13 -0
  71. package/dist/providers/openai-responses.d.ts.map +1 -0
  72. package/dist/providers/openai-responses.js +198 -0
  73. package/dist/providers/openai-responses.js.map +1 -0
  74. package/dist/providers/register-builtins.d.ts +3 -0
  75. package/dist/providers/register-builtins.d.ts.map +1 -0
  76. package/dist/providers/register-builtins.js +63 -0
  77. package/dist/providers/register-builtins.js.map +1 -0
  78. package/dist/providers/simple-options.d.ts +8 -0
  79. package/dist/providers/simple-options.d.ts.map +1 -0
  80. package/dist/providers/simple-options.js +35 -0
  81. package/dist/providers/simple-options.js.map +1 -0
  82. package/dist/providers/transform-messages.d.ts +8 -0
  83. package/dist/providers/transform-messages.d.ts.map +1 -0
  84. package/dist/providers/transform-messages.js +155 -0
  85. package/dist/providers/transform-messages.js.map +1 -0
  86. package/dist/stream.d.ts +9 -0
  87. package/dist/stream.d.ts.map +1 -0
  88. package/dist/stream.js +28 -0
  89. package/dist/stream.js.map +1 -0
  90. package/dist/types.d.ts +279 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +2 -0
  93. package/dist/types.js.map +1 -0
  94. package/dist/utils/event-stream.d.ts +21 -0
  95. package/dist/utils/event-stream.d.ts.map +1 -0
  96. package/dist/utils/event-stream.js +81 -0
  97. package/dist/utils/event-stream.js.map +1 -0
  98. package/dist/utils/http-proxy.d.ts +2 -0
  99. package/dist/utils/http-proxy.d.ts.map +1 -0
  100. package/dist/utils/http-proxy.js +15 -0
  101. package/dist/utils/http-proxy.js.map +1 -0
  102. package/dist/utils/json-parse.d.ts +9 -0
  103. package/dist/utils/json-parse.d.ts.map +1 -0
  104. package/dist/utils/json-parse.js +29 -0
  105. package/dist/utils/json-parse.js.map +1 -0
  106. package/dist/utils/oauth/anthropic.d.ts +17 -0
  107. package/dist/utils/oauth/anthropic.d.ts.map +1 -0
  108. package/dist/utils/oauth/anthropic.js +104 -0
  109. package/dist/utils/oauth/anthropic.js.map +1 -0
  110. package/dist/utils/oauth/github-copilot.d.ts +30 -0
  111. package/dist/utils/oauth/github-copilot.d.ts.map +1 -0
  112. package/dist/utils/oauth/github-copilot.js +281 -0
  113. package/dist/utils/oauth/github-copilot.js.map +1 -0
  114. package/dist/utils/oauth/google-antigravity.d.ts +26 -0
  115. package/dist/utils/oauth/google-antigravity.d.ts.map +1 -0
  116. package/dist/utils/oauth/google-antigravity.js +373 -0
  117. package/dist/utils/oauth/google-antigravity.js.map +1 -0
  118. package/dist/utils/oauth/google-gemini-cli.d.ts +26 -0
  119. package/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -0
  120. package/dist/utils/oauth/google-gemini-cli.js +478 -0
  121. package/dist/utils/oauth/google-gemini-cli.js.map +1 -0
  122. package/dist/utils/oauth/index.d.ts +62 -0
  123. package/dist/utils/oauth/index.d.ts.map +1 -0
  124. package/dist/utils/oauth/index.js +133 -0
  125. package/dist/utils/oauth/index.js.map +1 -0
  126. package/dist/utils/oauth/openai-codex.d.ts +34 -0
  127. package/dist/utils/oauth/openai-codex.d.ts.map +1 -0
  128. package/dist/utils/oauth/openai-codex.js +380 -0
  129. package/dist/utils/oauth/openai-codex.js.map +1 -0
  130. package/dist/utils/oauth/pkce.d.ts +13 -0
  131. package/dist/utils/oauth/pkce.d.ts.map +1 -0
  132. package/dist/utils/oauth/pkce.js +31 -0
  133. package/dist/utils/oauth/pkce.js.map +1 -0
  134. package/dist/utils/oauth/types.d.ts +47 -0
  135. package/dist/utils/oauth/types.d.ts.map +1 -0
  136. package/dist/utils/oauth/types.js +2 -0
  137. package/dist/utils/oauth/types.js.map +1 -0
  138. package/dist/utils/overflow.d.ts +52 -0
  139. package/dist/utils/overflow.d.ts.map +1 -0
  140. package/dist/utils/overflow.js +115 -0
  141. package/dist/utils/overflow.js.map +1 -0
  142. package/dist/utils/sanitize-unicode.d.ts +22 -0
  143. package/dist/utils/sanitize-unicode.d.ts.map +1 -0
  144. package/dist/utils/sanitize-unicode.js +26 -0
  145. package/dist/utils/sanitize-unicode.js.map +1 -0
  146. package/dist/utils/typebox-helpers.d.ts +17 -0
  147. package/dist/utils/typebox-helpers.d.ts.map +1 -0
  148. package/dist/utils/typebox-helpers.js +21 -0
  149. package/dist/utils/typebox-helpers.js.map +1 -0
  150. package/dist/utils/validation.d.ts +18 -0
  151. package/dist/utils/validation.d.ts.map +1 -0
  152. package/dist/utils/validation.js +72 -0
  153. package/dist/utils/validation.js.map +1 -0
  154. package/package.json +67 -0
@@ -0,0 +1,478 @@
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 { generatePKCE } from "./pkce.js";
9
+ let _createServer = null;
10
+ let _httpImportPromise = null;
11
+ if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
12
+ _httpImportPromise = import("node:http").then((m) => {
13
+ _createServer = m.createServer;
14
+ });
15
+ }
16
+ const decode = (s) => atob(s);
17
+ const CLIENT_ID = decode("NjgxMjU1ODA5Mzk1LW9vOGZ0Mm9wcmRybnA5ZTNhcWY2YXYzaG1kaWIxMzVqLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29t");
18
+ const CLIENT_SECRET = decode("R09DU1BYLTR1SGdNUG0tMW83U2stZ2VWNkN1NWNsWEZzeGw=");
19
+ const REDIRECT_URI = "http://localhost:8085/oauth2callback";
20
+ const SCOPES = [
21
+ "https://www.googleapis.com/auth/cloud-platform",
22
+ "https://www.googleapis.com/auth/userinfo.email",
23
+ "https://www.googleapis.com/auth/userinfo.profile",
24
+ ];
25
+ const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
26
+ const TOKEN_URL = "https://oauth2.googleapis.com/token";
27
+ const CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
28
+ /**
29
+ * Start a local HTTP server to receive the OAuth callback
30
+ */
31
+ async function getNodeCreateServer() {
32
+ if (_createServer)
33
+ return _createServer;
34
+ if (_httpImportPromise) {
35
+ await _httpImportPromise;
36
+ }
37
+ if (_createServer)
38
+ return _createServer;
39
+ throw new Error("Gemini CLI OAuth is only available in Node.js environments");
40
+ }
41
+ async function startCallbackServer() {
42
+ const createServer = await getNodeCreateServer();
43
+ return new Promise((resolve, reject) => {
44
+ let result = null;
45
+ let cancelled = false;
46
+ const server = createServer((req, res) => {
47
+ const url = new URL(req.url || "", `http://localhost:8085`);
48
+ if (url.pathname === "/oauth2callback") {
49
+ const code = url.searchParams.get("code");
50
+ const state = url.searchParams.get("state");
51
+ const error = url.searchParams.get("error");
52
+ if (error) {
53
+ res.writeHead(400, { "Content-Type": "text/html" });
54
+ res.end(`<html><body><h1>Authentication Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`);
55
+ return;
56
+ }
57
+ if (code && state) {
58
+ res.writeHead(200, { "Content-Type": "text/html" });
59
+ res.end(`<html><body><h1>Authentication Successful</h1><p>You can close this window and return to the terminal.</p></body></html>`);
60
+ result = { code, state };
61
+ }
62
+ else {
63
+ res.writeHead(400, { "Content-Type": "text/html" });
64
+ res.end(`<html><body><h1>Authentication Failed</h1><p>Missing code or state parameter.</p></body></html>`);
65
+ }
66
+ }
67
+ else {
68
+ res.writeHead(404);
69
+ res.end();
70
+ }
71
+ });
72
+ server.on("error", (err) => {
73
+ reject(err);
74
+ });
75
+ server.listen(8085, "127.0.0.1", () => {
76
+ resolve({
77
+ server,
78
+ cancelWait: () => {
79
+ cancelled = true;
80
+ },
81
+ waitForCode: async () => {
82
+ const sleep = () => new Promise((r) => setTimeout(r, 100));
83
+ while (!result && !cancelled) {
84
+ await sleep();
85
+ }
86
+ return result;
87
+ },
88
+ });
89
+ });
90
+ });
91
+ }
92
+ /**
93
+ * Parse redirect URL to extract code and state
94
+ */
95
+ function parseRedirectUrl(input) {
96
+ const value = input.trim();
97
+ if (!value)
98
+ return {};
99
+ try {
100
+ const url = new URL(value);
101
+ return {
102
+ code: url.searchParams.get("code") ?? undefined,
103
+ state: url.searchParams.get("state") ?? undefined,
104
+ };
105
+ }
106
+ catch {
107
+ // Not a URL, return empty
108
+ return {};
109
+ }
110
+ }
111
+ // Tier IDs as used by the Cloud Code API
112
+ const TIER_FREE = "free-tier";
113
+ const TIER_LEGACY = "legacy-tier";
114
+ const TIER_STANDARD = "standard-tier";
115
+ /**
116
+ * Wait helper for onboarding retries
117
+ */
118
+ function wait(ms) {
119
+ return new Promise((resolve) => setTimeout(resolve, ms));
120
+ }
121
+ /**
122
+ * Get default tier from allowed tiers
123
+ */
124
+ function getDefaultTier(allowedTiers) {
125
+ if (!allowedTiers || allowedTiers.length === 0)
126
+ return { id: TIER_LEGACY };
127
+ const defaultTier = allowedTiers.find((t) => t.isDefault);
128
+ return defaultTier ?? { id: TIER_LEGACY };
129
+ }
130
+ function isVpcScAffectedUser(payload) {
131
+ if (!payload || typeof payload !== "object")
132
+ return false;
133
+ if (!("error" in payload))
134
+ return false;
135
+ const error = payload.error;
136
+ if (!error?.details || !Array.isArray(error.details))
137
+ return false;
138
+ return error.details.some((detail) => detail.reason === "SECURITY_POLICY_VIOLATED");
139
+ }
140
+ /**
141
+ * Poll a long-running operation until completion
142
+ */
143
+ async function pollOperation(operationName, headers, onProgress) {
144
+ let attempt = 0;
145
+ while (true) {
146
+ if (attempt > 0) {
147
+ onProgress?.(`Waiting for project provisioning (attempt ${attempt + 1})...`);
148
+ await wait(5000);
149
+ }
150
+ const response = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal/${operationName}`, {
151
+ method: "GET",
152
+ headers,
153
+ });
154
+ if (!response.ok) {
155
+ throw new Error(`Failed to poll operation: ${response.status} ${response.statusText}`);
156
+ }
157
+ const data = (await response.json());
158
+ if (data.done) {
159
+ return data;
160
+ }
161
+ attempt += 1;
162
+ }
163
+ }
164
+ /**
165
+ * Discover or provision a Google Cloud project for the user
166
+ */
167
+ async function discoverProject(accessToken, onProgress) {
168
+ // Check for user-provided project ID via environment variable
169
+ const envProjectId = process.env.GOOGLE_CLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT_ID;
170
+ const headers = {
171
+ Authorization: `Bearer ${accessToken}`,
172
+ "Content-Type": "application/json",
173
+ "User-Agent": "google-api-nodejs-client/9.15.1",
174
+ "X-Goog-Api-Client": "gl-node/22.17.0",
175
+ };
176
+ // Try to load existing project via loadCodeAssist
177
+ onProgress?.("Checking for existing Cloud Code Assist project...");
178
+ const loadResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {
179
+ method: "POST",
180
+ headers,
181
+ body: JSON.stringify({
182
+ cloudaicompanionProject: envProjectId,
183
+ metadata: {
184
+ ideType: "IDE_UNSPECIFIED",
185
+ platform: "PLATFORM_UNSPECIFIED",
186
+ pluginType: "GEMINI",
187
+ duetProject: envProjectId,
188
+ },
189
+ }),
190
+ });
191
+ let data;
192
+ if (!loadResponse.ok) {
193
+ let errorPayload;
194
+ try {
195
+ errorPayload = await loadResponse.clone().json();
196
+ }
197
+ catch {
198
+ errorPayload = undefined;
199
+ }
200
+ if (isVpcScAffectedUser(errorPayload)) {
201
+ data = { currentTier: { id: TIER_STANDARD } };
202
+ }
203
+ else {
204
+ const errorText = await loadResponse.text();
205
+ throw new Error(`loadCodeAssist failed: ${loadResponse.status} ${loadResponse.statusText}: ${errorText}`);
206
+ }
207
+ }
208
+ else {
209
+ data = (await loadResponse.json());
210
+ }
211
+ // If user already has a current tier and project, use it
212
+ if (data.currentTier) {
213
+ if (data.cloudaicompanionProject) {
214
+ return data.cloudaicompanionProject;
215
+ }
216
+ // User has a tier but no managed project - they need to provide one via env var
217
+ if (envProjectId) {
218
+ return envProjectId;
219
+ }
220
+ throw new Error("This account requires setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID environment variable. " +
221
+ "See https://goo.gle/gemini-cli-auth-docs#workspace-gca");
222
+ }
223
+ // User needs to be onboarded - get the default tier
224
+ const tier = getDefaultTier(data.allowedTiers);
225
+ const tierId = tier?.id ?? TIER_FREE;
226
+ if (tierId !== TIER_FREE && !envProjectId) {
227
+ throw new Error("This account requires setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID environment variable. " +
228
+ "See https://goo.gle/gemini-cli-auth-docs#workspace-gca");
229
+ }
230
+ onProgress?.("Provisioning Cloud Code Assist project (this may take a moment)...");
231
+ // Build onboard request - for free tier, don't include project ID (Google provisions one)
232
+ // For other tiers, include the user's project ID if available
233
+ const onboardBody = {
234
+ tierId,
235
+ metadata: {
236
+ ideType: "IDE_UNSPECIFIED",
237
+ platform: "PLATFORM_UNSPECIFIED",
238
+ pluginType: "GEMINI",
239
+ },
240
+ };
241
+ if (tierId !== TIER_FREE && envProjectId) {
242
+ onboardBody.cloudaicompanionProject = envProjectId;
243
+ onboardBody.metadata.duetProject = envProjectId;
244
+ }
245
+ // Start onboarding - this returns a long-running operation
246
+ const onboardResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {
247
+ method: "POST",
248
+ headers,
249
+ body: JSON.stringify(onboardBody),
250
+ });
251
+ if (!onboardResponse.ok) {
252
+ const errorText = await onboardResponse.text();
253
+ throw new Error(`onboardUser failed: ${onboardResponse.status} ${onboardResponse.statusText}: ${errorText}`);
254
+ }
255
+ let lroData = (await onboardResponse.json());
256
+ // If the operation isn't done yet, poll until completion
257
+ if (!lroData.done && lroData.name) {
258
+ lroData = await pollOperation(lroData.name, headers, onProgress);
259
+ }
260
+ // Try to get project ID from the response
261
+ const projectId = lroData.response?.cloudaicompanionProject?.id;
262
+ if (projectId) {
263
+ return projectId;
264
+ }
265
+ // If no project ID from onboarding, fall back to env var
266
+ if (envProjectId) {
267
+ return envProjectId;
268
+ }
269
+ throw new Error("Could not discover or provision a Google Cloud project. " +
270
+ "Try setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID environment variable. " +
271
+ "See https://goo.gle/gemini-cli-auth-docs#workspace-gca");
272
+ }
273
+ /**
274
+ * Get user email from the access token
275
+ */
276
+ async function getUserEmail(accessToken) {
277
+ try {
278
+ const response = await fetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", {
279
+ headers: {
280
+ Authorization: `Bearer ${accessToken}`,
281
+ },
282
+ });
283
+ if (response.ok) {
284
+ const data = (await response.json());
285
+ return data.email;
286
+ }
287
+ }
288
+ catch {
289
+ // Ignore errors, email is optional
290
+ }
291
+ return undefined;
292
+ }
293
+ /**
294
+ * Refresh Google Cloud Code Assist token
295
+ */
296
+ export async function refreshGoogleCloudToken(refreshToken, projectId) {
297
+ const response = await fetch(TOKEN_URL, {
298
+ method: "POST",
299
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
300
+ body: new URLSearchParams({
301
+ client_id: CLIENT_ID,
302
+ client_secret: CLIENT_SECRET,
303
+ refresh_token: refreshToken,
304
+ grant_type: "refresh_token",
305
+ }),
306
+ });
307
+ if (!response.ok) {
308
+ const error = await response.text();
309
+ throw new Error(`Google Cloud token refresh failed: ${error}`);
310
+ }
311
+ const data = (await response.json());
312
+ return {
313
+ refresh: data.refresh_token || refreshToken,
314
+ access: data.access_token,
315
+ expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
316
+ projectId,
317
+ };
318
+ }
319
+ /**
320
+ * Login with Gemini CLI (Google Cloud Code Assist) OAuth
321
+ *
322
+ * @param onAuth - Callback with URL and optional instructions
323
+ * @param onProgress - Optional progress callback
324
+ * @param onManualCodeInput - Optional promise that resolves with user-pasted redirect URL.
325
+ * Races with browser callback - whichever completes first wins.
326
+ */
327
+ export async function loginGeminiCli(onAuth, onProgress, onManualCodeInput) {
328
+ const { verifier, challenge } = await generatePKCE();
329
+ // Start local server for callback
330
+ onProgress?.("Starting local server for OAuth callback...");
331
+ const server = await startCallbackServer();
332
+ let code;
333
+ try {
334
+ // Build authorization URL
335
+ const authParams = new URLSearchParams({
336
+ client_id: CLIENT_ID,
337
+ response_type: "code",
338
+ redirect_uri: REDIRECT_URI,
339
+ scope: SCOPES.join(" "),
340
+ code_challenge: challenge,
341
+ code_challenge_method: "S256",
342
+ state: verifier,
343
+ access_type: "offline",
344
+ prompt: "consent",
345
+ });
346
+ const authUrl = `${AUTH_URL}?${authParams.toString()}`;
347
+ // Notify caller with URL to open
348
+ onAuth({
349
+ url: authUrl,
350
+ instructions: "Complete the sign-in in your browser.",
351
+ });
352
+ // Wait for the callback, racing with manual input if provided
353
+ onProgress?.("Waiting for OAuth callback...");
354
+ if (onManualCodeInput) {
355
+ // Race between browser callback and manual input
356
+ let manualInput;
357
+ let manualError;
358
+ const manualPromise = onManualCodeInput()
359
+ .then((input) => {
360
+ manualInput = input;
361
+ server.cancelWait();
362
+ })
363
+ .catch((err) => {
364
+ manualError = err instanceof Error ? err : new Error(String(err));
365
+ server.cancelWait();
366
+ });
367
+ const result = await server.waitForCode();
368
+ // If manual input was cancelled, throw that error
369
+ if (manualError) {
370
+ throw manualError;
371
+ }
372
+ if (result?.code) {
373
+ // Browser callback won - verify state
374
+ if (result.state !== verifier) {
375
+ throw new Error("OAuth state mismatch - possible CSRF attack");
376
+ }
377
+ code = result.code;
378
+ }
379
+ else if (manualInput) {
380
+ // Manual input won
381
+ const parsed = parseRedirectUrl(manualInput);
382
+ if (parsed.state && parsed.state !== verifier) {
383
+ throw new Error("OAuth state mismatch - possible CSRF attack");
384
+ }
385
+ code = parsed.code;
386
+ }
387
+ // If still no code, wait for manual promise and try that
388
+ if (!code) {
389
+ await manualPromise;
390
+ if (manualError) {
391
+ throw manualError;
392
+ }
393
+ if (manualInput) {
394
+ const parsed = parseRedirectUrl(manualInput);
395
+ if (parsed.state && parsed.state !== verifier) {
396
+ throw new Error("OAuth state mismatch - possible CSRF attack");
397
+ }
398
+ code = parsed.code;
399
+ }
400
+ }
401
+ }
402
+ else {
403
+ // Original flow: just wait for callback
404
+ const result = await server.waitForCode();
405
+ if (result?.code) {
406
+ if (result.state !== verifier) {
407
+ throw new Error("OAuth state mismatch - possible CSRF attack");
408
+ }
409
+ code = result.code;
410
+ }
411
+ }
412
+ if (!code) {
413
+ throw new Error("No authorization code received");
414
+ }
415
+ // Exchange code for tokens
416
+ onProgress?.("Exchanging authorization code for tokens...");
417
+ const tokenResponse = await fetch(TOKEN_URL, {
418
+ method: "POST",
419
+ headers: {
420
+ "Content-Type": "application/x-www-form-urlencoded",
421
+ },
422
+ body: new URLSearchParams({
423
+ client_id: CLIENT_ID,
424
+ client_secret: CLIENT_SECRET,
425
+ code,
426
+ grant_type: "authorization_code",
427
+ redirect_uri: REDIRECT_URI,
428
+ code_verifier: verifier,
429
+ }),
430
+ });
431
+ if (!tokenResponse.ok) {
432
+ const error = await tokenResponse.text();
433
+ throw new Error(`Token exchange failed: ${error}`);
434
+ }
435
+ const tokenData = (await tokenResponse.json());
436
+ if (!tokenData.refresh_token) {
437
+ throw new Error("No refresh token received. Please try again.");
438
+ }
439
+ // Get user email
440
+ onProgress?.("Getting user info...");
441
+ const email = await getUserEmail(tokenData.access_token);
442
+ // Discover project
443
+ const projectId = await discoverProject(tokenData.access_token, onProgress);
444
+ // Calculate expiry time (current time + expires_in seconds - 5 min buffer)
445
+ const expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;
446
+ const credentials = {
447
+ refresh: tokenData.refresh_token,
448
+ access: tokenData.access_token,
449
+ expires: expiresAt,
450
+ projectId,
451
+ email,
452
+ };
453
+ return credentials;
454
+ }
455
+ finally {
456
+ server.server.close();
457
+ }
458
+ }
459
+ export const geminiCliOAuthProvider = {
460
+ id: "google-gemini-cli",
461
+ name: "Google Cloud Code Assist (Gemini CLI)",
462
+ usesCallbackServer: true,
463
+ async login(callbacks) {
464
+ return loginGeminiCli(callbacks.onAuth, callbacks.onProgress, callbacks.onManualCodeInput);
465
+ },
466
+ async refreshToken(credentials) {
467
+ const creds = credentials;
468
+ if (!creds.projectId) {
469
+ throw new Error("Google Cloud credentials missing projectId");
470
+ }
471
+ return refreshGoogleCloudToken(creds.refresh, creds.projectId);
472
+ },
473
+ getApiKey(credentials) {
474
+ const creds = credentials;
475
+ return JSON.stringify({ token: creds.access, projectId: creds.projectId });
476
+ },
477
+ };
478
+ //# sourceMappingURL=google-gemini-cli.js.map
@@ -0,0 +1 @@
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 \"node:http\";\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 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 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 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\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 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\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"]}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * OAuth credential management for AI providers.
3
+ *
4
+ * This module handles login, token refresh, and credential storage
5
+ * for OAuth-based providers:
6
+ * - Anthropic (Claude Pro/Max)
7
+ * - GitHub Copilot
8
+ * - Google Cloud Code Assist (Gemini CLI)
9
+ * - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)
10
+ */
11
+ import "../http-proxy.js";
12
+ export { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from "./anthropic.js";
13
+ export { getGitHubCopilotBaseUrl, githubCopilotOAuthProvider, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.js";
14
+ export { antigravityOAuthProvider, loginAntigravity, refreshAntigravityToken } from "./google-antigravity.js";
15
+ export { geminiCliOAuthProvider, loginGeminiCli, refreshGoogleCloudToken } from "./google-gemini-cli.js";
16
+ export { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from "./openai-codex.js";
17
+ export * from "./types.js";
18
+ import type { OAuthCredentials, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface } from "./types.js";
19
+ /**
20
+ * Get an OAuth provider by ID
21
+ */
22
+ export declare function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined;
23
+ /**
24
+ * Register a custom OAuth provider
25
+ */
26
+ export declare function registerOAuthProvider(provider: OAuthProviderInterface): void;
27
+ /**
28
+ * Unregister an OAuth provider.
29
+ *
30
+ * If the provider is built-in, restores the built-in implementation.
31
+ * Custom providers are removed completely.
32
+ */
33
+ export declare function unregisterOAuthProvider(id: string): void;
34
+ /**
35
+ * Reset OAuth providers to built-ins.
36
+ */
37
+ export declare function resetOAuthProviders(): void;
38
+ /**
39
+ * Get all registered OAuth providers
40
+ */
41
+ export declare function getOAuthProviders(): OAuthProviderInterface[];
42
+ /**
43
+ * @deprecated Use getOAuthProviders() which returns OAuthProviderInterface[]
44
+ */
45
+ export declare function getOAuthProviderInfoList(): OAuthProviderInfo[];
46
+ /**
47
+ * Refresh token for any OAuth provider.
48
+ * @deprecated Use getOAuthProvider(id).refreshToken() instead
49
+ */
50
+ export declare function refreshOAuthToken(providerId: OAuthProviderId, credentials: OAuthCredentials): Promise<OAuthCredentials>;
51
+ /**
52
+ * Get API key for a provider from OAuth credentials.
53
+ * Automatically refreshes expired tokens.
54
+ *
55
+ * @returns API key string and updated credentials, or null if no credentials
56
+ * @throws Error if refresh fails
57
+ */
58
+ export declare function getOAuthApiKey(providerId: OAuthProviderId, credentials: Record<string, OAuthCredentials>): Promise<{
59
+ newCredentials: OAuthCredentials;
60
+ apiKey: string;
61
+ } | null>;
62
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,kBAAkB,CAAC;AAG1B,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE/F,OAAO,EACN,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAE9G,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEzG,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAExG,cAAc,YAAY,CAAC;AAW3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAc/G;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,eAAe,GAAG,sBAAsB,GAAG,SAAS,CAExF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,CAE5E;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAOxD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAK1C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,sBAAsB,EAAE,CAE5D;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,iBAAiB,EAAE,CAM9D;AAMD;;;GAGG;AACH,wBAAsB,iBAAiB,CACtC,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,gBAAgB,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CACnC,UAAU,EAAE,eAAe,EAC3B,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,CAsBtE","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// Set up HTTP proxy for fetch() calls (respects HTTP_PROXY, HTTPS_PROXY env vars)\nimport \"../http-proxy.js\";\n\n// Anthropic\nexport { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from \"./anthropic.js\";\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tgithubCopilotOAuthProvider,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.js\";\n// Google Antigravity\nexport { antigravityOAuthProvider, loginAntigravity, refreshAntigravityToken } from \"./google-antigravity.js\";\n// Google Gemini CLI\nexport { geminiCliOAuthProvider, loginGeminiCli, refreshGoogleCloudToken } from \"./google-gemini-cli.js\";\n// OpenAI Codex (ChatGPT OAuth)\nexport { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from \"./openai-codex.js\";\n\nexport * from \"./types.js\";\n\n// ============================================================================\n// Provider Registry\n// ============================================================================\n\nimport { anthropicOAuthProvider } from \"./anthropic.js\";\nimport { githubCopilotOAuthProvider } from \"./github-copilot.js\";\nimport { antigravityOAuthProvider } from \"./google-antigravity.js\";\nimport { geminiCliOAuthProvider } from \"./google-gemini-cli.js\";\nimport { openaiCodexOAuthProvider } from \"./openai-codex.js\";\nimport type { OAuthCredentials, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface } from \"./types.js\";\n\nconst BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [\n\tanthropicOAuthProvider,\n\tgithubCopilotOAuthProvider,\n\tgeminiCliOAuthProvider,\n\tantigravityOAuthProvider,\n\topenaiCodexOAuthProvider,\n];\n\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>(\n\tBUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]),\n);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n\treturn oauthProviderRegistry.get(id);\n}\n\n/**\n * Register a custom OAuth provider\n */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n\toauthProviderRegistry.set(provider.id, provider);\n}\n\n/**\n * Unregister an OAuth provider.\n *\n * If the provider is built-in, restores the built-in implementation.\n * Custom providers are removed completely.\n */\nexport function unregisterOAuthProvider(id: string): void {\n\tconst builtInProvider = BUILT_IN_OAUTH_PROVIDERS.find((provider) => provider.id === id);\n\tif (builtInProvider) {\n\t\toauthProviderRegistry.set(id, builtInProvider);\n\t\treturn;\n\t}\n\toauthProviderRegistry.delete(id);\n}\n\n/**\n * Reset OAuth providers to built-ins.\n */\nexport function resetOAuthProviders(): void {\n\toauthProviderRegistry.clear();\n\tfor (const provider of BUILT_IN_OAUTH_PROVIDERS) {\n\t\toauthProviderRegistry.set(provider.id, provider);\n\t}\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n\treturn Array.from(oauthProviderRegistry.values());\n}\n\n/**\n * @deprecated Use getOAuthProviders() which returns OAuthProviderInterface[]\n */\nexport function getOAuthProviderInfoList(): OAuthProviderInfo[] {\n\treturn getOAuthProviders().map((p) => ({\n\t\tid: p.id,\n\t\tname: p.name,\n\t\tavailable: true,\n\t}));\n}\n\n// ============================================================================\n// High-level API (uses provider registry)\n// ============================================================================\n\n/**\n * Refresh token for any OAuth provider.\n * @deprecated Use getOAuthProvider(id).refreshToken() instead\n */\nexport async function refreshOAuthToken(\n\tproviderId: OAuthProviderId,\n\tcredentials: OAuthCredentials,\n): Promise<OAuthCredentials> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\treturn provider.refreshToken(credentials);\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * @returns API key string and updated credentials, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tproviderId: OAuthProviderId,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\n\tlet creds = credentials[providerId];\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 provider.refreshToken(creds);\n\t\t} catch (_error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${providerId}`);\n\t\t}\n\t}\n\n\tconst apiKey = provider.getApiKey(creds);\n\treturn { newCredentials: creds, apiKey };\n}\n"]}