@mariozechner/pi-ai 0.24.5 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +120 -10
  2. package/dist/agent/agent-loop.d.ts.map +1 -1
  3. package/dist/agent/agent-loop.js +58 -5
  4. package/dist/agent/agent-loop.js.map +1 -1
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/models.generated.d.ts +179 -22
  10. package/dist/models.generated.d.ts.map +1 -1
  11. package/dist/models.generated.js +205 -48
  12. package/dist/models.generated.js.map +1 -1
  13. package/dist/providers/anthropic.d.ts.map +1 -1
  14. package/dist/providers/anthropic.js +0 -2
  15. package/dist/providers/anthropic.js.map +1 -1
  16. package/dist/providers/google-gemini-cli.d.ts +16 -0
  17. package/dist/providers/google-gemini-cli.d.ts.map +1 -0
  18. package/dist/providers/google-gemini-cli.js +347 -0
  19. package/dist/providers/google-gemini-cli.js.map +1 -0
  20. package/dist/providers/google-shared.d.ts +34 -0
  21. package/dist/providers/google-shared.d.ts.map +1 -0
  22. package/dist/providers/google-shared.js +211 -0
  23. package/dist/providers/google-shared.js.map +1 -0
  24. package/dist/providers/google.d.ts.map +1 -1
  25. package/dist/providers/google.js +3 -162
  26. package/dist/providers/google.js.map +1 -1
  27. package/dist/providers/openai-completions.d.ts.map +1 -1
  28. package/dist/providers/openai-completions.js +25 -0
  29. package/dist/providers/openai-completions.js.map +1 -1
  30. package/dist/providers/openai-responses.d.ts.map +1 -1
  31. package/dist/providers/openai-responses.js +24 -1
  32. package/dist/providers/openai-responses.js.map +1 -1
  33. package/dist/providers/transorm-messages.d.ts.map +1 -1
  34. package/dist/providers/transorm-messages.js +62 -43
  35. package/dist/providers/transorm-messages.js.map +1 -1
  36. package/dist/stream.d.ts +15 -0
  37. package/dist/stream.d.ts.map +1 -1
  38. package/dist/stream.js +39 -0
  39. package/dist/stream.js.map +1 -1
  40. package/dist/types.d.ts +4 -2
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/types.js.map +1 -1
  43. package/dist/utils/oauth/anthropic.d.ts +16 -0
  44. package/dist/utils/oauth/anthropic.d.ts.map +1 -0
  45. package/dist/utils/oauth/anthropic.js +102 -0
  46. package/dist/utils/oauth/anthropic.js.map +1 -0
  47. package/dist/utils/oauth/github-copilot.d.ts +43 -0
  48. package/dist/utils/oauth/github-copilot.d.ts.map +1 -0
  49. package/dist/utils/oauth/github-copilot.js +236 -0
  50. package/dist/utils/oauth/github-copilot.js.map +1 -0
  51. package/dist/utils/oauth/google-antigravity.d.ts +24 -0
  52. package/dist/utils/oauth/google-antigravity.d.ts.map +1 -0
  53. package/dist/utils/oauth/google-antigravity.js +272 -0
  54. package/dist/utils/oauth/google-antigravity.js.map +1 -0
  55. package/dist/utils/oauth/google-gemini-cli.d.ts +24 -0
  56. package/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -0
  57. package/dist/utils/oauth/google-gemini-cli.js +285 -0
  58. package/dist/utils/oauth/google-gemini-cli.js.map +1 -0
  59. package/dist/utils/oauth/index.d.ts +54 -0
  60. package/dist/utils/oauth/index.d.ts.map +1 -0
  61. package/dist/utils/oauth/index.js +147 -0
  62. package/dist/utils/oauth/index.js.map +1 -0
  63. package/dist/utils/oauth/storage.d.ts +81 -0
  64. package/dist/utils/oauth/storage.d.ts.map +1 -0
  65. package/dist/utils/oauth/storage.js +119 -0
  66. package/dist/utils/oauth/storage.js.map +1 -0
  67. package/dist/utils/overflow.d.ts +2 -2
  68. package/dist/utils/overflow.d.ts.map +1 -1
  69. package/dist/utils/overflow.js +7 -4
  70. package/dist/utils/overflow.js.map +1 -1
  71. package/package.json +1 -1
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Gemini CLI OAuth flow (Google Cloud Code Assist)
3
+ * Standard Gemini models only (gemini-2.0-flash, gemini-2.5-*)
4
+ */
5
+ import { type OAuthCredentials } from "./storage.js";
6
+ export interface GoogleCloudCredentials extends OAuthCredentials {
7
+ projectId: string;
8
+ email?: string;
9
+ }
10
+ /**
11
+ * Refresh Google Cloud Code Assist token
12
+ */
13
+ export declare function refreshGoogleCloudToken(refreshToken: string, projectId: string): Promise<OAuthCredentials>;
14
+ /**
15
+ * Login with Gemini CLI (Google Cloud Code Assist) OAuth
16
+ *
17
+ * @param onAuth - Callback with URL and optional instructions
18
+ * @param onProgress - Optional progress callback
19
+ */
20
+ export declare function loginGeminiCli(onAuth: (info: {
21
+ url: string;
22
+ instructions?: string;
23
+ }) => void, onProgress?: (message: string) => void): Promise<GoogleCloudCredentials>;
24
+ //# 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;;;GAGG;AAIH,OAAO,EAAE,KAAK,gBAAgB,EAAwB,MAAM,cAAc,CAAC;AAc3E,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAuMD;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA8BhH;AAED;;;;;GAKG;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,GACpC,OAAO,CAAC,sBAAsB,CAAC,CA+FjC","sourcesContent":["/**\n * Gemini CLI OAuth flow (Google Cloud Code Assist)\n * Standard Gemini models only (gemini-2.0-flash, gemini-2.5-*)\n */\n\nimport { createHash, randomBytes } from \"crypto\";\nimport { createServer, type Server } from \"http\";\nimport { type OAuthCredentials, saveOAuthCredentials } from \"./storage.js\";\n\nconst CLIENT_ID = \"681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com\";\nconst CLIENT_SECRET = \"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\";\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\nexport interface GoogleCloudCredentials extends OAuthCredentials {\n\tprojectId: string;\n\temail?: string;\n}\n\n/**\n * Generate PKCE code verifier and challenge\n */\nfunction generatePKCE(): { verifier: string; challenge: string } {\n\tconst verifier = randomBytes(32).toString(\"base64url\");\n\tconst challenge = createHash(\"sha256\").update(verifier).digest(\"base64url\");\n\treturn { verifier, challenge };\n}\n\n/**\n * Start a local HTTP server to receive the OAuth callback\n */\nfunction startCallbackServer(): Promise<{ server: Server; getCode: () => Promise<{ code: string; state: string }> }> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet codeResolve: (value: { code: string; state: string }) => void;\n\t\tlet codeReject: (error: Error) => void;\n\n\t\tconst codePromise = new Promise<{ code: string; state: string }>((res, rej) => {\n\t\t\tcodeResolve = res;\n\t\t\tcodeReject = rej;\n\t\t});\n\n\t\tconst server = createServer((req, res) => {\n\t\t\tconst url = new URL(req.url || \"\", `http://localhost:8085`);\n\n\t\t\tif (url.pathname === \"/oauth2callback\") {\n\t\t\t\tconst code = url.searchParams.get(\"code\");\n\t\t\t\tconst state = url.searchParams.get(\"state\");\n\t\t\t\tconst error = url.searchParams.get(\"error\");\n\n\t\t\t\tif (error) {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\t`<html><body><h1>Authentication Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`,\n\t\t\t\t\t);\n\t\t\t\t\tcodeReject(new Error(`OAuth error: ${error}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (code && state) {\n\t\t\t\t\tres.writeHead(200, { \"Content-Type\": \"text/html\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\t`<html><body><h1>Authentication Successful</h1><p>You can close this window and return to the terminal.</p></body></html>`,\n\t\t\t\t\t);\n\t\t\t\t\tcodeResolve({ code, state });\n\t\t\t\t} else {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\t`<html><body><h1>Authentication Failed</h1><p>Missing code or state parameter.</p></body></html>`,\n\t\t\t\t\t);\n\t\t\t\t\tcodeReject(new Error(\"Missing code or state in callback\"));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tres.writeHead(404);\n\t\t\t\tres.end();\n\t\t\t}\n\t\t});\n\n\t\tserver.on(\"error\", (err) => {\n\t\t\treject(err);\n\t\t});\n\n\t\tserver.listen(8085, \"127.0.0.1\", () => {\n\t\t\tresolve({\n\t\t\t\tserver,\n\t\t\t\tgetCode: () => codePromise,\n\t\t\t});\n\t\t});\n\t});\n}\n\ninterface LoadCodeAssistPayload {\n\tcloudaicompanionProject?: string;\n\tcurrentTier?: { id?: string };\n\tallowedTiers?: Array<{ id?: string; isDefault?: boolean }>;\n}\n\ninterface OnboardUserPayload {\n\tdone?: boolean;\n\tresponse?: {\n\t\tcloudaicompanionProject?: { id?: string };\n\t};\n}\n\n/**\n * Wait helper for onboarding retries\n */\nfunction wait(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Get default tier ID from allowed tiers\n */\nfunction getDefaultTierId(allowedTiers?: Array<{ id?: string; isDefault?: boolean }>): string | undefined {\n\tif (!allowedTiers || allowedTiers.length === 0) return undefined;\n\tconst defaultTier = allowedTiers.find((t) => t.isDefault);\n\treturn defaultTier?.id ?? allowedTiers[0]?.id;\n}\n\n/**\n * Discover or provision a Google Cloud project for the user\n */\nasync function discoverProject(accessToken: string, onProgress?: (message: string) => void): Promise<string> {\n\tconst headers = {\n\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"User-Agent\": \"google-api-nodejs-client/9.15.1\",\n\t\t\"X-Goog-Api-Client\": \"gl-node/22.17.0\",\n\t};\n\n\t// Try to load existing project via loadCodeAssist\n\tonProgress?.(\"Checking for existing Cloud Code Assist project...\");\n\tconst loadResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {\n\t\tmethod: \"POST\",\n\t\theaders,\n\t\tbody: JSON.stringify({\n\t\t\tmetadata: {\n\t\t\t\tideType: \"IDE_UNSPECIFIED\",\n\t\t\t\tplatform: \"PLATFORM_UNSPECIFIED\",\n\t\t\t\tpluginType: \"GEMINI\",\n\t\t\t},\n\t\t}),\n\t});\n\n\tif (loadResponse.ok) {\n\t\tconst data = (await loadResponse.json()) as LoadCodeAssistPayload;\n\n\t\t// If we have an existing project, use it\n\t\tif (data.cloudaicompanionProject) {\n\t\t\treturn data.cloudaicompanionProject;\n\t\t}\n\n\t\t// Otherwise, try to onboard with the FREE tier\n\t\tconst tierId = getDefaultTierId(data.allowedTiers) ?? \"FREE\";\n\n\t\tonProgress?.(\"Provisioning Cloud Code Assist project (this may take a moment)...\");\n\n\t\t// Onboard with retries (the API may take time to provision)\n\t\tfor (let attempt = 0; attempt < 10; attempt++) {\n\t\t\tconst onboardResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders,\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\ttierId,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\tideType: \"IDE_UNSPECIFIED\",\n\t\t\t\t\t\tplatform: \"PLATFORM_UNSPECIFIED\",\n\t\t\t\t\t\tpluginType: \"GEMINI\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t});\n\n\t\t\tif (onboardResponse.ok) {\n\t\t\t\tconst onboardData = (await onboardResponse.json()) as OnboardUserPayload;\n\t\t\t\tconst projectId = onboardData.response?.cloudaicompanionProject?.id;\n\n\t\t\t\tif (onboardData.done && projectId) {\n\t\t\t\t\treturn projectId;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Wait before retrying\n\t\t\tif (attempt < 9) {\n\t\t\t\tonProgress?.(`Waiting for project provisioning (attempt ${attempt + 2}/10)...`);\n\t\t\t\tawait wait(3000);\n\t\t\t}\n\t\t}\n\t}\n\n\tthrow new Error(\n\t\t\"Could not discover or provision a Google Cloud project. \" +\n\t\t\t\"Please ensure you have access to Google Cloud Code Assist (Gemini CLI).\",\n\t);\n}\n\n/**\n * Get user email from the access token\n */\nasync function getUserEmail(accessToken: string): Promise<string | undefined> {\n\ttry {\n\t\tconst response = await fetch(\"https://www.googleapis.com/oauth2/v1/userinfo?alt=json\", {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\n\t\tif (response.ok) {\n\t\t\tconst data = (await response.json()) as { email?: string };\n\t\t\treturn data.email;\n\t\t}\n\t} catch {\n\t\t// Ignore errors, email is optional\n\t}\n\treturn undefined;\n}\n\n/**\n * Refresh Google Cloud Code Assist token\n */\nexport async function refreshGoogleCloudToken(refreshToken: string, projectId: string): Promise<OAuthCredentials> {\n\tconst response = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\tbody: new URLSearchParams({\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tclient_secret: CLIENT_SECRET,\n\t\t\trefresh_token: refreshToken,\n\t\t\tgrant_type: \"refresh_token\",\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tconst error = await response.text();\n\t\tthrow new Error(`Google Cloud token refresh failed: ${error}`);\n\t}\n\n\tconst data = (await response.json()) as {\n\t\taccess_token: string;\n\t\texpires_in: number;\n\t\trefresh_token?: string;\n\t};\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: data.refresh_token || refreshToken,\n\t\taccess: data.access_token,\n\t\texpires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,\n\t\tprojectId,\n\t};\n}\n\n/**\n * Login with Gemini CLI (Google Cloud Code Assist) OAuth\n *\n * @param onAuth - Callback with URL and optional instructions\n * @param onProgress - Optional progress callback\n */\nexport async function loginGeminiCli(\n\tonAuth: (info: { url: string; instructions?: string }) => void,\n\tonProgress?: (message: string) => void,\n): Promise<GoogleCloudCredentials> {\n\tconst { verifier, challenge } = generatePKCE();\n\n\t// Start local server for callback\n\tonProgress?.(\"Starting local server for OAuth callback...\");\n\tconst { server, getCode } = await startCallbackServer();\n\n\ttry {\n\t\t// Build authorization URL\n\t\tconst authParams = new URLSearchParams({\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tresponse_type: \"code\",\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tscope: SCOPES.join(\" \"),\n\t\t\tcode_challenge: challenge,\n\t\t\tcode_challenge_method: \"S256\",\n\t\t\tstate: verifier,\n\t\t\taccess_type: \"offline\",\n\t\t\tprompt: \"consent\",\n\t\t});\n\n\t\tconst authUrl = `${AUTH_URL}?${authParams.toString()}`;\n\n\t\t// Notify caller with URL to open\n\t\tonAuth({\n\t\t\turl: authUrl,\n\t\t\tinstructions: \"Complete the sign-in in your browser. The callback will be captured automatically.\",\n\t\t});\n\n\t\t// Wait for the callback\n\t\tonProgress?.(\"Waiting for OAuth callback...\");\n\t\tconst { code, state } = await getCode();\n\n\t\t// Verify state matches\n\t\tif (state !== verifier) {\n\t\t\tthrow new Error(\"OAuth state mismatch - possible CSRF attack\");\n\t\t}\n\n\t\t// Exchange code for tokens\n\t\tonProgress?.(\"Exchanging authorization code for tokens...\");\n\t\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\tclient_secret: CLIENT_SECRET,\n\t\t\t\tcode,\n\t\t\t\tgrant_type: \"authorization_code\",\n\t\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\t\tcode_verifier: verifier,\n\t\t\t}),\n\t\t});\n\n\t\tif (!tokenResponse.ok) {\n\t\t\tconst error = await tokenResponse.text();\n\t\t\tthrow new Error(`Token exchange failed: ${error}`);\n\t\t}\n\n\t\tconst tokenData = (await tokenResponse.json()) as {\n\t\t\taccess_token: string;\n\t\t\trefresh_token: string;\n\t\t\texpires_in: number;\n\t\t};\n\n\t\tif (!tokenData.refresh_token) {\n\t\t\tthrow new Error(\"No refresh token received. Please try again.\");\n\t\t}\n\n\t\t// Get user email\n\t\tonProgress?.(\"Getting user info...\");\n\t\tconst email = await getUserEmail(tokenData.access_token);\n\n\t\t// Discover project\n\t\tconst projectId = await discoverProject(tokenData.access_token, onProgress);\n\n\t\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\t\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\t\tconst credentials: GoogleCloudCredentials = {\n\t\t\ttype: \"oauth\",\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\tsaveOAuthCredentials(\"google-gemini-cli\", credentials);\n\n\t\treturn credentials;\n\t} finally {\n\t\tserver.close();\n\t}\n}\n"]}
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Gemini CLI OAuth flow (Google Cloud Code Assist)
3
+ * Standard Gemini models only (gemini-2.0-flash, gemini-2.5-*)
4
+ */
5
+ import { createHash, randomBytes } from "crypto";
6
+ import { createServer } from "http";
7
+ import { saveOAuthCredentials } from "./storage.js";
8
+ const CLIENT_ID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com";
9
+ const CLIENT_SECRET = "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";
10
+ const REDIRECT_URI = "http://localhost:8085/oauth2callback";
11
+ const SCOPES = [
12
+ "https://www.googleapis.com/auth/cloud-platform",
13
+ "https://www.googleapis.com/auth/userinfo.email",
14
+ "https://www.googleapis.com/auth/userinfo.profile",
15
+ ];
16
+ const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
17
+ const TOKEN_URL = "https://oauth2.googleapis.com/token";
18
+ const CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
19
+ /**
20
+ * Generate PKCE code verifier and challenge
21
+ */
22
+ function generatePKCE() {
23
+ const verifier = randomBytes(32).toString("base64url");
24
+ const challenge = createHash("sha256").update(verifier).digest("base64url");
25
+ return { verifier, challenge };
26
+ }
27
+ /**
28
+ * Start a local HTTP server to receive the OAuth callback
29
+ */
30
+ function startCallbackServer() {
31
+ return new Promise((resolve, reject) => {
32
+ let codeResolve;
33
+ let codeReject;
34
+ const codePromise = new Promise((res, rej) => {
35
+ codeResolve = res;
36
+ codeReject = rej;
37
+ });
38
+ const server = createServer((req, res) => {
39
+ const url = new URL(req.url || "", `http://localhost:8085`);
40
+ if (url.pathname === "/oauth2callback") {
41
+ const code = url.searchParams.get("code");
42
+ const state = url.searchParams.get("state");
43
+ const error = url.searchParams.get("error");
44
+ if (error) {
45
+ res.writeHead(400, { "Content-Type": "text/html" });
46
+ res.end(`<html><body><h1>Authentication Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`);
47
+ codeReject(new Error(`OAuth error: ${error}`));
48
+ return;
49
+ }
50
+ if (code && state) {
51
+ res.writeHead(200, { "Content-Type": "text/html" });
52
+ res.end(`<html><body><h1>Authentication Successful</h1><p>You can close this window and return to the terminal.</p></body></html>`);
53
+ codeResolve({ code, state });
54
+ }
55
+ else {
56
+ res.writeHead(400, { "Content-Type": "text/html" });
57
+ res.end(`<html><body><h1>Authentication Failed</h1><p>Missing code or state parameter.</p></body></html>`);
58
+ codeReject(new Error("Missing code or state in callback"));
59
+ }
60
+ }
61
+ else {
62
+ res.writeHead(404);
63
+ res.end();
64
+ }
65
+ });
66
+ server.on("error", (err) => {
67
+ reject(err);
68
+ });
69
+ server.listen(8085, "127.0.0.1", () => {
70
+ resolve({
71
+ server,
72
+ getCode: () => codePromise,
73
+ });
74
+ });
75
+ });
76
+ }
77
+ /**
78
+ * Wait helper for onboarding retries
79
+ */
80
+ function wait(ms) {
81
+ return new Promise((resolve) => setTimeout(resolve, ms));
82
+ }
83
+ /**
84
+ * Get default tier ID from allowed tiers
85
+ */
86
+ function getDefaultTierId(allowedTiers) {
87
+ if (!allowedTiers || allowedTiers.length === 0)
88
+ return undefined;
89
+ const defaultTier = allowedTiers.find((t) => t.isDefault);
90
+ return defaultTier?.id ?? allowedTiers[0]?.id;
91
+ }
92
+ /**
93
+ * Discover or provision a Google Cloud project for the user
94
+ */
95
+ async function discoverProject(accessToken, onProgress) {
96
+ const headers = {
97
+ Authorization: `Bearer ${accessToken}`,
98
+ "Content-Type": "application/json",
99
+ "User-Agent": "google-api-nodejs-client/9.15.1",
100
+ "X-Goog-Api-Client": "gl-node/22.17.0",
101
+ };
102
+ // Try to load existing project via loadCodeAssist
103
+ onProgress?.("Checking for existing Cloud Code Assist project...");
104
+ const loadResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {
105
+ method: "POST",
106
+ headers,
107
+ body: JSON.stringify({
108
+ metadata: {
109
+ ideType: "IDE_UNSPECIFIED",
110
+ platform: "PLATFORM_UNSPECIFIED",
111
+ pluginType: "GEMINI",
112
+ },
113
+ }),
114
+ });
115
+ if (loadResponse.ok) {
116
+ const data = (await loadResponse.json());
117
+ // If we have an existing project, use it
118
+ if (data.cloudaicompanionProject) {
119
+ return data.cloudaicompanionProject;
120
+ }
121
+ // Otherwise, try to onboard with the FREE tier
122
+ const tierId = getDefaultTierId(data.allowedTiers) ?? "FREE";
123
+ onProgress?.("Provisioning Cloud Code Assist project (this may take a moment)...");
124
+ // Onboard with retries (the API may take time to provision)
125
+ for (let attempt = 0; attempt < 10; attempt++) {
126
+ const onboardResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {
127
+ method: "POST",
128
+ headers,
129
+ body: JSON.stringify({
130
+ tierId,
131
+ metadata: {
132
+ ideType: "IDE_UNSPECIFIED",
133
+ platform: "PLATFORM_UNSPECIFIED",
134
+ pluginType: "GEMINI",
135
+ },
136
+ }),
137
+ });
138
+ if (onboardResponse.ok) {
139
+ const onboardData = (await onboardResponse.json());
140
+ const projectId = onboardData.response?.cloudaicompanionProject?.id;
141
+ if (onboardData.done && projectId) {
142
+ return projectId;
143
+ }
144
+ }
145
+ // Wait before retrying
146
+ if (attempt < 9) {
147
+ onProgress?.(`Waiting for project provisioning (attempt ${attempt + 2}/10)...`);
148
+ await wait(3000);
149
+ }
150
+ }
151
+ }
152
+ throw new Error("Could not discover or provision a Google Cloud project. " +
153
+ "Please ensure you have access to Google Cloud Code Assist (Gemini CLI).");
154
+ }
155
+ /**
156
+ * Get user email from the access token
157
+ */
158
+ async function getUserEmail(accessToken) {
159
+ try {
160
+ const response = await fetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", {
161
+ headers: {
162
+ Authorization: `Bearer ${accessToken}`,
163
+ },
164
+ });
165
+ if (response.ok) {
166
+ const data = (await response.json());
167
+ return data.email;
168
+ }
169
+ }
170
+ catch {
171
+ // Ignore errors, email is optional
172
+ }
173
+ return undefined;
174
+ }
175
+ /**
176
+ * Refresh Google Cloud Code Assist token
177
+ */
178
+ export async function refreshGoogleCloudToken(refreshToken, projectId) {
179
+ const response = await fetch(TOKEN_URL, {
180
+ method: "POST",
181
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
182
+ body: new URLSearchParams({
183
+ client_id: CLIENT_ID,
184
+ client_secret: CLIENT_SECRET,
185
+ refresh_token: refreshToken,
186
+ grant_type: "refresh_token",
187
+ }),
188
+ });
189
+ if (!response.ok) {
190
+ const error = await response.text();
191
+ throw new Error(`Google Cloud token refresh failed: ${error}`);
192
+ }
193
+ const data = (await response.json());
194
+ return {
195
+ type: "oauth",
196
+ refresh: data.refresh_token || refreshToken,
197
+ access: data.access_token,
198
+ expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
199
+ projectId,
200
+ };
201
+ }
202
+ /**
203
+ * Login with Gemini CLI (Google Cloud Code Assist) OAuth
204
+ *
205
+ * @param onAuth - Callback with URL and optional instructions
206
+ * @param onProgress - Optional progress callback
207
+ */
208
+ export async function loginGeminiCli(onAuth, onProgress) {
209
+ const { verifier, challenge } = generatePKCE();
210
+ // Start local server for callback
211
+ onProgress?.("Starting local server for OAuth callback...");
212
+ const { server, getCode } = await startCallbackServer();
213
+ try {
214
+ // Build authorization URL
215
+ const authParams = new URLSearchParams({
216
+ client_id: CLIENT_ID,
217
+ response_type: "code",
218
+ redirect_uri: REDIRECT_URI,
219
+ scope: SCOPES.join(" "),
220
+ code_challenge: challenge,
221
+ code_challenge_method: "S256",
222
+ state: verifier,
223
+ access_type: "offline",
224
+ prompt: "consent",
225
+ });
226
+ const authUrl = `${AUTH_URL}?${authParams.toString()}`;
227
+ // Notify caller with URL to open
228
+ onAuth({
229
+ url: authUrl,
230
+ instructions: "Complete the sign-in in your browser. The callback will be captured automatically.",
231
+ });
232
+ // Wait for the callback
233
+ onProgress?.("Waiting for OAuth callback...");
234
+ const { code, state } = await getCode();
235
+ // Verify state matches
236
+ if (state !== verifier) {
237
+ throw new Error("OAuth state mismatch - possible CSRF attack");
238
+ }
239
+ // Exchange code for tokens
240
+ onProgress?.("Exchanging authorization code for tokens...");
241
+ const tokenResponse = await fetch(TOKEN_URL, {
242
+ method: "POST",
243
+ headers: {
244
+ "Content-Type": "application/x-www-form-urlencoded",
245
+ },
246
+ body: new URLSearchParams({
247
+ client_id: CLIENT_ID,
248
+ client_secret: CLIENT_SECRET,
249
+ code,
250
+ grant_type: "authorization_code",
251
+ redirect_uri: REDIRECT_URI,
252
+ code_verifier: verifier,
253
+ }),
254
+ });
255
+ if (!tokenResponse.ok) {
256
+ const error = await tokenResponse.text();
257
+ throw new Error(`Token exchange failed: ${error}`);
258
+ }
259
+ const tokenData = (await tokenResponse.json());
260
+ if (!tokenData.refresh_token) {
261
+ throw new Error("No refresh token received. Please try again.");
262
+ }
263
+ // Get user email
264
+ onProgress?.("Getting user info...");
265
+ const email = await getUserEmail(tokenData.access_token);
266
+ // Discover project
267
+ const projectId = await discoverProject(tokenData.access_token, onProgress);
268
+ // Calculate expiry time (current time + expires_in seconds - 5 min buffer)
269
+ const expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;
270
+ const credentials = {
271
+ type: "oauth",
272
+ refresh: tokenData.refresh_token,
273
+ access: tokenData.access_token,
274
+ expires: expiresAt,
275
+ projectId,
276
+ email,
277
+ };
278
+ saveOAuthCredentials("google-gemini-cli", credentials);
279
+ return credentials;
280
+ }
281
+ finally {
282
+ server.close();
283
+ }
284
+ }
285
+ //# 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\nimport { createHash, randomBytes } from \"crypto\";\nimport { createServer, type Server } from \"http\";\nimport { type OAuthCredentials, saveOAuthCredentials } from \"./storage.js\";\n\nconst CLIENT_ID = \"681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com\";\nconst CLIENT_SECRET = \"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\";\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\nexport interface GoogleCloudCredentials extends OAuthCredentials {\n\tprojectId: string;\n\temail?: string;\n}\n\n/**\n * Generate PKCE code verifier and challenge\n */\nfunction generatePKCE(): { verifier: string; challenge: string } {\n\tconst verifier = randomBytes(32).toString(\"base64url\");\n\tconst challenge = createHash(\"sha256\").update(verifier).digest(\"base64url\");\n\treturn { verifier, challenge };\n}\n\n/**\n * Start a local HTTP server to receive the OAuth callback\n */\nfunction startCallbackServer(): Promise<{ server: Server; getCode: () => Promise<{ code: string; state: string }> }> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet codeResolve: (value: { code: string; state: string }) => void;\n\t\tlet codeReject: (error: Error) => void;\n\n\t\tconst codePromise = new Promise<{ code: string; state: string }>((res, rej) => {\n\t\t\tcodeResolve = res;\n\t\t\tcodeReject = rej;\n\t\t});\n\n\t\tconst server = createServer((req, res) => {\n\t\t\tconst url = new URL(req.url || \"\", `http://localhost:8085`);\n\n\t\t\tif (url.pathname === \"/oauth2callback\") {\n\t\t\t\tconst code = url.searchParams.get(\"code\");\n\t\t\t\tconst state = url.searchParams.get(\"state\");\n\t\t\t\tconst error = url.searchParams.get(\"error\");\n\n\t\t\t\tif (error) {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\t`<html><body><h1>Authentication Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`,\n\t\t\t\t\t);\n\t\t\t\t\tcodeReject(new Error(`OAuth error: ${error}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (code && state) {\n\t\t\t\t\tres.writeHead(200, { \"Content-Type\": \"text/html\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\t`<html><body><h1>Authentication Successful</h1><p>You can close this window and return to the terminal.</p></body></html>`,\n\t\t\t\t\t);\n\t\t\t\t\tcodeResolve({ code, state });\n\t\t\t\t} else {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"text/html\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\t`<html><body><h1>Authentication Failed</h1><p>Missing code or state parameter.</p></body></html>`,\n\t\t\t\t\t);\n\t\t\t\t\tcodeReject(new Error(\"Missing code or state in callback\"));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tres.writeHead(404);\n\t\t\t\tres.end();\n\t\t\t}\n\t\t});\n\n\t\tserver.on(\"error\", (err) => {\n\t\t\treject(err);\n\t\t});\n\n\t\tserver.listen(8085, \"127.0.0.1\", () => {\n\t\t\tresolve({\n\t\t\t\tserver,\n\t\t\t\tgetCode: () => codePromise,\n\t\t\t});\n\t\t});\n\t});\n}\n\ninterface LoadCodeAssistPayload {\n\tcloudaicompanionProject?: string;\n\tcurrentTier?: { id?: string };\n\tallowedTiers?: Array<{ id?: string; isDefault?: boolean }>;\n}\n\ninterface OnboardUserPayload {\n\tdone?: boolean;\n\tresponse?: {\n\t\tcloudaicompanionProject?: { id?: string };\n\t};\n}\n\n/**\n * Wait helper for onboarding retries\n */\nfunction wait(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Get default tier ID from allowed tiers\n */\nfunction getDefaultTierId(allowedTiers?: Array<{ id?: string; isDefault?: boolean }>): string | undefined {\n\tif (!allowedTiers || allowedTiers.length === 0) return undefined;\n\tconst defaultTier = allowedTiers.find((t) => t.isDefault);\n\treturn defaultTier?.id ?? allowedTiers[0]?.id;\n}\n\n/**\n * Discover or provision a Google Cloud project for the user\n */\nasync function discoverProject(accessToken: string, onProgress?: (message: string) => void): Promise<string> {\n\tconst headers = {\n\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"User-Agent\": \"google-api-nodejs-client/9.15.1\",\n\t\t\"X-Goog-Api-Client\": \"gl-node/22.17.0\",\n\t};\n\n\t// Try to load existing project via loadCodeAssist\n\tonProgress?.(\"Checking for existing Cloud Code Assist project...\");\n\tconst loadResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {\n\t\tmethod: \"POST\",\n\t\theaders,\n\t\tbody: JSON.stringify({\n\t\t\tmetadata: {\n\t\t\t\tideType: \"IDE_UNSPECIFIED\",\n\t\t\t\tplatform: \"PLATFORM_UNSPECIFIED\",\n\t\t\t\tpluginType: \"GEMINI\",\n\t\t\t},\n\t\t}),\n\t});\n\n\tif (loadResponse.ok) {\n\t\tconst data = (await loadResponse.json()) as LoadCodeAssistPayload;\n\n\t\t// If we have an existing project, use it\n\t\tif (data.cloudaicompanionProject) {\n\t\t\treturn data.cloudaicompanionProject;\n\t\t}\n\n\t\t// Otherwise, try to onboard with the FREE tier\n\t\tconst tierId = getDefaultTierId(data.allowedTiers) ?? \"FREE\";\n\n\t\tonProgress?.(\"Provisioning Cloud Code Assist project (this may take a moment)...\");\n\n\t\t// Onboard with retries (the API may take time to provision)\n\t\tfor (let attempt = 0; attempt < 10; attempt++) {\n\t\t\tconst onboardResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders,\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\ttierId,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\tideType: \"IDE_UNSPECIFIED\",\n\t\t\t\t\t\tplatform: \"PLATFORM_UNSPECIFIED\",\n\t\t\t\t\t\tpluginType: \"GEMINI\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t});\n\n\t\t\tif (onboardResponse.ok) {\n\t\t\t\tconst onboardData = (await onboardResponse.json()) as OnboardUserPayload;\n\t\t\t\tconst projectId = onboardData.response?.cloudaicompanionProject?.id;\n\n\t\t\t\tif (onboardData.done && projectId) {\n\t\t\t\t\treturn projectId;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Wait before retrying\n\t\t\tif (attempt < 9) {\n\t\t\t\tonProgress?.(`Waiting for project provisioning (attempt ${attempt + 2}/10)...`);\n\t\t\t\tawait wait(3000);\n\t\t\t}\n\t\t}\n\t}\n\n\tthrow new Error(\n\t\t\"Could not discover or provision a Google Cloud project. \" +\n\t\t\t\"Please ensure you have access to Google Cloud Code Assist (Gemini CLI).\",\n\t);\n}\n\n/**\n * Get user email from the access token\n */\nasync function getUserEmail(accessToken: string): Promise<string | undefined> {\n\ttry {\n\t\tconst response = await fetch(\"https://www.googleapis.com/oauth2/v1/userinfo?alt=json\", {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\n\t\tif (response.ok) {\n\t\t\tconst data = (await response.json()) as { email?: string };\n\t\t\treturn data.email;\n\t\t}\n\t} catch {\n\t\t// Ignore errors, email is optional\n\t}\n\treturn undefined;\n}\n\n/**\n * Refresh Google Cloud Code Assist token\n */\nexport async function refreshGoogleCloudToken(refreshToken: string, projectId: string): Promise<OAuthCredentials> {\n\tconst response = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\tbody: new URLSearchParams({\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tclient_secret: CLIENT_SECRET,\n\t\t\trefresh_token: refreshToken,\n\t\t\tgrant_type: \"refresh_token\",\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tconst error = await response.text();\n\t\tthrow new Error(`Google Cloud token refresh failed: ${error}`);\n\t}\n\n\tconst data = (await response.json()) as {\n\t\taccess_token: string;\n\t\texpires_in: number;\n\t\trefresh_token?: string;\n\t};\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: data.refresh_token || refreshToken,\n\t\taccess: data.access_token,\n\t\texpires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,\n\t\tprojectId,\n\t};\n}\n\n/**\n * Login with Gemini CLI (Google Cloud Code Assist) OAuth\n *\n * @param onAuth - Callback with URL and optional instructions\n * @param onProgress - Optional progress callback\n */\nexport async function loginGeminiCli(\n\tonAuth: (info: { url: string; instructions?: string }) => void,\n\tonProgress?: (message: string) => void,\n): Promise<GoogleCloudCredentials> {\n\tconst { verifier, challenge } = generatePKCE();\n\n\t// Start local server for callback\n\tonProgress?.(\"Starting local server for OAuth callback...\");\n\tconst { server, getCode } = await startCallbackServer();\n\n\ttry {\n\t\t// Build authorization URL\n\t\tconst authParams = new URLSearchParams({\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tresponse_type: \"code\",\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tscope: SCOPES.join(\" \"),\n\t\t\tcode_challenge: challenge,\n\t\t\tcode_challenge_method: \"S256\",\n\t\t\tstate: verifier,\n\t\t\taccess_type: \"offline\",\n\t\t\tprompt: \"consent\",\n\t\t});\n\n\t\tconst authUrl = `${AUTH_URL}?${authParams.toString()}`;\n\n\t\t// Notify caller with URL to open\n\t\tonAuth({\n\t\t\turl: authUrl,\n\t\t\tinstructions: \"Complete the sign-in in your browser. The callback will be captured automatically.\",\n\t\t});\n\n\t\t// Wait for the callback\n\t\tonProgress?.(\"Waiting for OAuth callback...\");\n\t\tconst { code, state } = await getCode();\n\n\t\t// Verify state matches\n\t\tif (state !== verifier) {\n\t\t\tthrow new Error(\"OAuth state mismatch - possible CSRF attack\");\n\t\t}\n\n\t\t// Exchange code for tokens\n\t\tonProgress?.(\"Exchanging authorization code for tokens...\");\n\t\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t},\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\tclient_secret: CLIENT_SECRET,\n\t\t\t\tcode,\n\t\t\t\tgrant_type: \"authorization_code\",\n\t\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\t\tcode_verifier: verifier,\n\t\t\t}),\n\t\t});\n\n\t\tif (!tokenResponse.ok) {\n\t\t\tconst error = await tokenResponse.text();\n\t\t\tthrow new Error(`Token exchange failed: ${error}`);\n\t\t}\n\n\t\tconst tokenData = (await tokenResponse.json()) as {\n\t\t\taccess_token: string;\n\t\t\trefresh_token: string;\n\t\t\texpires_in: number;\n\t\t};\n\n\t\tif (!tokenData.refresh_token) {\n\t\t\tthrow new Error(\"No refresh token received. Please try again.\");\n\t\t}\n\n\t\t// Get user email\n\t\tonProgress?.(\"Getting user info...\");\n\t\tconst email = await getUserEmail(tokenData.access_token);\n\n\t\t// Discover project\n\t\tconst projectId = await discoverProject(tokenData.access_token, onProgress);\n\n\t\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\t\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\t\tconst credentials: GoogleCloudCredentials = {\n\t\t\ttype: \"oauth\",\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\tsaveOAuthCredentials(\"google-gemini-cli\", credentials);\n\n\t\treturn credentials;\n\t} finally {\n\t\tserver.close();\n\t}\n}\n"]}
@@ -0,0 +1,54 @@
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
+ export { loginAnthropic, refreshAnthropicToken } from "./anthropic.js";
12
+ export { enableAllGitHubCopilotModels, enableGitHubCopilotModel, getBaseUrlFromToken, getGitHubCopilotBaseUrl, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.js";
13
+ export { type AntigravityCredentials, loginAntigravity, refreshAntigravityToken, } from "./google-antigravity.js";
14
+ export { type GoogleCloudCredentials, loginGeminiCli, refreshGoogleCloudToken, } from "./google-gemini-cli.js";
15
+ export { getOAuthPath, hasOAuthCredentials, listOAuthProviders, loadOAuthCredentials, loadOAuthStorage, type OAuthCredentials, type OAuthProvider, type OAuthStorage, type OAuthStorageBackend, removeOAuthCredentials, resetOAuthStorage, saveOAuthCredentials, setOAuthStorage, } from "./storage.js";
16
+ import type { OAuthProvider } from "./storage.js";
17
+ /**
18
+ * Refresh token for any OAuth provider.
19
+ * Saves the new credentials and returns the new access token.
20
+ */
21
+ export declare function refreshToken(provider: OAuthProvider): Promise<string>;
22
+ /**
23
+ * Get API key for a provider from OAuth credentials.
24
+ * Automatically refreshes expired tokens.
25
+ *
26
+ * For google-gemini-cli and antigravity, returns JSON-encoded { token, projectId }
27
+ *
28
+ * @returns API key string, or null if no credentials
29
+ */
30
+ export declare function getOAuthApiKey(provider: OAuthProvider): Promise<string | null>;
31
+ /**
32
+ * Map model provider to OAuth provider.
33
+ * Returns undefined if the provider doesn't use OAuth.
34
+ */
35
+ export declare function getOAuthProviderForModelProvider(modelProvider: string): OAuthProvider | undefined;
36
+ export type OAuthPrompt = {
37
+ message: string;
38
+ placeholder?: string;
39
+ allowEmpty?: boolean;
40
+ };
41
+ export type OAuthAuthInfo = {
42
+ url: string;
43
+ instructions?: string;
44
+ };
45
+ export interface OAuthProviderInfo {
46
+ id: OAuthProvider;
47
+ name: string;
48
+ available: boolean;
49
+ }
50
+ /**
51
+ * Get list of OAuth providers
52
+ */
53
+ export declare function getOAuthProviders(): OAuthProviderInfo[];
54
+ //# 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,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEvE,OAAO,EACN,4BAA4B,EAC5B,wBAAwB,EACxB,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACN,KAAK,sBAAsB,EAC3B,gBAAgB,EAChB,uBAAuB,GACvB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACN,KAAK,sBAAsB,EAC3B,cAAc,EACd,uBAAuB,GACvB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACN,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,GACf,MAAM,cAAc,CAAC;AAUtB,OAAO,KAAK,EAAoB,aAAa,EAAE,MAAM,cAAc,CAAC;AAGpE;;;GAGG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAiC3E;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuCpF;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAAC,aAAa,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAQjG;AAMD,MAAM,MAAM,WAAW,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,WAAW,iBAAiB;IACjC,EAAE,EAAE,aAAa,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,EAAE,CAuBvD","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n * - Google Cloud Code Assist (Gemini CLI)\n * - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)\n */\n\n// Anthropic\nexport { loginAnthropic, refreshAnthropicToken } from \"./anthropic.js\";\n// GitHub Copilot\nexport {\n\tenableAllGitHubCopilotModels,\n\tenableGitHubCopilotModel,\n\tgetBaseUrlFromToken,\n\tgetGitHubCopilotBaseUrl,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.js\";\n// Google Antigravity\nexport {\n\ttype AntigravityCredentials,\n\tloginAntigravity,\n\trefreshAntigravityToken,\n} from \"./google-antigravity.js\";\n// Google Gemini CLI\nexport {\n\ttype GoogleCloudCredentials,\n\tloginGeminiCli,\n\trefreshGoogleCloudToken,\n} from \"./google-gemini-cli.js\";\n// Storage\nexport {\n\tgetOAuthPath,\n\thasOAuthCredentials,\n\tlistOAuthProviders,\n\tloadOAuthCredentials,\n\tloadOAuthStorage,\n\ttype OAuthCredentials,\n\ttype OAuthProvider,\n\ttype OAuthStorage,\n\ttype OAuthStorageBackend,\n\tremoveOAuthCredentials,\n\tresetOAuthStorage,\n\tsaveOAuthCredentials,\n\tsetOAuthStorage,\n} from \"./storage.js\";\n\n// ============================================================================\n// High-level API\n// ============================================================================\n\nimport { refreshAnthropicToken } from \"./anthropic.js\";\nimport { refreshGitHubCopilotToken } from \"./github-copilot.js\";\nimport { refreshAntigravityToken } from \"./google-antigravity.js\";\nimport { refreshGoogleCloudToken } from \"./google-gemini-cli.js\";\nimport type { OAuthCredentials, OAuthProvider } from \"./storage.js\";\nimport { loadOAuthCredentials, removeOAuthCredentials, saveOAuthCredentials } from \"./storage.js\";\n\n/**\n * Refresh token for any OAuth provider.\n * Saves the new credentials and returns the new access token.\n */\nexport async function refreshToken(provider: OAuthProvider): Promise<string> {\n\tconst credentials = loadOAuthCredentials(provider);\n\tif (!credentials) {\n\t\tthrow new Error(`No OAuth credentials found for ${provider}`);\n\t}\n\n\tlet newCredentials: OAuthCredentials;\n\n\tswitch (provider) {\n\t\tcase \"anthropic\":\n\t\t\tnewCredentials = await refreshAnthropicToken(credentials.refresh);\n\t\t\tbreak;\n\t\tcase \"github-copilot\":\n\t\t\tnewCredentials = await refreshGitHubCopilotToken(credentials.refresh, credentials.enterpriseUrl);\n\t\t\tbreak;\n\t\tcase \"google-gemini-cli\":\n\t\t\tif (!credentials.projectId) {\n\t\t\t\tthrow new Error(\"Google Cloud credentials missing projectId\");\n\t\t\t}\n\t\t\tnewCredentials = await refreshGoogleCloudToken(credentials.refresh, credentials.projectId);\n\t\t\tbreak;\n\t\tcase \"google-antigravity\":\n\t\t\tif (!credentials.projectId) {\n\t\t\t\tthrow new Error(\"Antigravity credentials missing projectId\");\n\t\t\t}\n\t\t\tnewCredentials = await refreshAntigravityToken(credentials.refresh, credentials.projectId);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown OAuth provider: ${provider}`);\n\t}\n\n\tsaveOAuthCredentials(provider, newCredentials);\n\treturn newCredentials.access;\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * For google-gemini-cli and antigravity, returns JSON-encoded { token, projectId }\n *\n * @returns API key string, or null if no credentials\n */\nexport async function getOAuthApiKey(provider: OAuthProvider): Promise<string | null> {\n\tconst credentials = loadOAuthCredentials(provider);\n\tif (!credentials) {\n\t\treturn null;\n\t}\n\n\t// Providers that need projectId in the API key\n\tconst needsProjectId = provider === \"google-gemini-cli\" || provider === \"google-antigravity\";\n\n\t// Check if expired\n\tif (Date.now() >= credentials.expires) {\n\t\ttry {\n\t\t\tconst newToken = await refreshToken(provider);\n\n\t\t\t// For providers that need projectId, return JSON\n\t\t\tif (needsProjectId) {\n\t\t\t\tconst refreshedCreds = loadOAuthCredentials(provider);\n\t\t\t\tif (refreshedCreds?.projectId) {\n\t\t\t\t\treturn JSON.stringify({ token: newToken, projectId: refreshedCreds.projectId });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn newToken;\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to refresh OAuth token for ${provider}:`, error);\n\t\t\tremoveOAuthCredentials(provider);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t// For providers that need projectId, return JSON\n\tif (needsProjectId) {\n\t\tif (!credentials.projectId) {\n\t\t\treturn null;\n\t\t}\n\t\treturn JSON.stringify({ token: credentials.access, projectId: credentials.projectId });\n\t}\n\n\treturn credentials.access;\n}\n\n/**\n * Map model provider to OAuth provider.\n * Returns undefined if the provider doesn't use OAuth.\n */\nexport function getOAuthProviderForModelProvider(modelProvider: string): OAuthProvider | undefined {\n\tconst mapping: Record<string, OAuthProvider> = {\n\t\tanthropic: \"anthropic\",\n\t\t\"github-copilot\": \"github-copilot\",\n\t\t\"google-gemini-cli\": \"google-gemini-cli\",\n\t\t\"google-antigravity\": \"google-antigravity\",\n\t};\n\treturn mapping[modelProvider];\n}\n\n// ============================================================================\n// Login/Logout types for convenience\n// ============================================================================\n\nexport type OAuthPrompt = {\n\tmessage: string;\n\tplaceholder?: string;\n\tallowEmpty?: boolean;\n};\n\nexport type OAuthAuthInfo = {\n\turl: string;\n\tinstructions?: string;\n};\n\nexport interface OAuthProviderInfo {\n\tid: OAuthProvider;\n\tname: string;\n\tavailable: boolean;\n}\n\n/**\n * Get list of OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInfo[] {\n\treturn [\n\t\t{\n\t\t\tid: \"anthropic\",\n\t\t\tname: \"Anthropic (Claude Pro/Max)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"github-copilot\",\n\t\t\tname: \"GitHub Copilot\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"google-gemini-cli\",\n\t\t\tname: \"Google Cloud Code Assist (Gemini CLI)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"google-antigravity\",\n\t\t\tname: \"Antigravity (Gemini 3, Claude, GPT-OSS)\",\n\t\t\tavailable: true,\n\t\t},\n\t];\n}\n"]}
@@ -0,0 +1,147 @@
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
+ // Anthropic
12
+ export { loginAnthropic, refreshAnthropicToken } from "./anthropic.js";
13
+ // GitHub Copilot
14
+ export { enableAllGitHubCopilotModels, enableGitHubCopilotModel, getBaseUrlFromToken, getGitHubCopilotBaseUrl, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.js";
15
+ // Google Antigravity
16
+ export { loginAntigravity, refreshAntigravityToken, } from "./google-antigravity.js";
17
+ // Google Gemini CLI
18
+ export { loginGeminiCli, refreshGoogleCloudToken, } from "./google-gemini-cli.js";
19
+ // Storage
20
+ export { getOAuthPath, hasOAuthCredentials, listOAuthProviders, loadOAuthCredentials, loadOAuthStorage, removeOAuthCredentials, resetOAuthStorage, saveOAuthCredentials, setOAuthStorage, } from "./storage.js";
21
+ // ============================================================================
22
+ // High-level API
23
+ // ============================================================================
24
+ import { refreshAnthropicToken } from "./anthropic.js";
25
+ import { refreshGitHubCopilotToken } from "./github-copilot.js";
26
+ import { refreshAntigravityToken } from "./google-antigravity.js";
27
+ import { refreshGoogleCloudToken } from "./google-gemini-cli.js";
28
+ import { loadOAuthCredentials, removeOAuthCredentials, saveOAuthCredentials } from "./storage.js";
29
+ /**
30
+ * Refresh token for any OAuth provider.
31
+ * Saves the new credentials and returns the new access token.
32
+ */
33
+ export async function refreshToken(provider) {
34
+ const credentials = loadOAuthCredentials(provider);
35
+ if (!credentials) {
36
+ throw new Error(`No OAuth credentials found for ${provider}`);
37
+ }
38
+ let newCredentials;
39
+ switch (provider) {
40
+ case "anthropic":
41
+ newCredentials = await refreshAnthropicToken(credentials.refresh);
42
+ break;
43
+ case "github-copilot":
44
+ newCredentials = await refreshGitHubCopilotToken(credentials.refresh, credentials.enterpriseUrl);
45
+ break;
46
+ case "google-gemini-cli":
47
+ if (!credentials.projectId) {
48
+ throw new Error("Google Cloud credentials missing projectId");
49
+ }
50
+ newCredentials = await refreshGoogleCloudToken(credentials.refresh, credentials.projectId);
51
+ break;
52
+ case "google-antigravity":
53
+ if (!credentials.projectId) {
54
+ throw new Error("Antigravity credentials missing projectId");
55
+ }
56
+ newCredentials = await refreshAntigravityToken(credentials.refresh, credentials.projectId);
57
+ break;
58
+ default:
59
+ throw new Error(`Unknown OAuth provider: ${provider}`);
60
+ }
61
+ saveOAuthCredentials(provider, newCredentials);
62
+ return newCredentials.access;
63
+ }
64
+ /**
65
+ * Get API key for a provider from OAuth credentials.
66
+ * Automatically refreshes expired tokens.
67
+ *
68
+ * For google-gemini-cli and antigravity, returns JSON-encoded { token, projectId }
69
+ *
70
+ * @returns API key string, or null if no credentials
71
+ */
72
+ export async function getOAuthApiKey(provider) {
73
+ const credentials = loadOAuthCredentials(provider);
74
+ if (!credentials) {
75
+ return null;
76
+ }
77
+ // Providers that need projectId in the API key
78
+ const needsProjectId = provider === "google-gemini-cli" || provider === "google-antigravity";
79
+ // Check if expired
80
+ if (Date.now() >= credentials.expires) {
81
+ try {
82
+ const newToken = await refreshToken(provider);
83
+ // For providers that need projectId, return JSON
84
+ if (needsProjectId) {
85
+ const refreshedCreds = loadOAuthCredentials(provider);
86
+ if (refreshedCreds?.projectId) {
87
+ return JSON.stringify({ token: newToken, projectId: refreshedCreds.projectId });
88
+ }
89
+ }
90
+ return newToken;
91
+ }
92
+ catch (error) {
93
+ console.error(`Failed to refresh OAuth token for ${provider}:`, error);
94
+ removeOAuthCredentials(provider);
95
+ return null;
96
+ }
97
+ }
98
+ // For providers that need projectId, return JSON
99
+ if (needsProjectId) {
100
+ if (!credentials.projectId) {
101
+ return null;
102
+ }
103
+ return JSON.stringify({ token: credentials.access, projectId: credentials.projectId });
104
+ }
105
+ return credentials.access;
106
+ }
107
+ /**
108
+ * Map model provider to OAuth provider.
109
+ * Returns undefined if the provider doesn't use OAuth.
110
+ */
111
+ export function getOAuthProviderForModelProvider(modelProvider) {
112
+ const mapping = {
113
+ anthropic: "anthropic",
114
+ "github-copilot": "github-copilot",
115
+ "google-gemini-cli": "google-gemini-cli",
116
+ "google-antigravity": "google-antigravity",
117
+ };
118
+ return mapping[modelProvider];
119
+ }
120
+ /**
121
+ * Get list of OAuth providers
122
+ */
123
+ export function getOAuthProviders() {
124
+ return [
125
+ {
126
+ id: "anthropic",
127
+ name: "Anthropic (Claude Pro/Max)",
128
+ available: true,
129
+ },
130
+ {
131
+ id: "github-copilot",
132
+ name: "GitHub Copilot",
133
+ available: true,
134
+ },
135
+ {
136
+ id: "google-gemini-cli",
137
+ name: "Google Cloud Code Assist (Gemini CLI)",
138
+ available: true,
139
+ },
140
+ {
141
+ id: "google-antigravity",
142
+ name: "Antigravity (Gemini 3, Claude, GPT-OSS)",
143
+ available: true,
144
+ },
145
+ ];
146
+ }
147
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,YAAY;AACZ,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvE,iBAAiB;AACjB,OAAO,EACN,4BAA4B,EAC5B,wBAAwB,EACxB,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,qBAAqB;AACrB,OAAO,EAEN,gBAAgB,EAChB,uBAAuB,GACvB,MAAM,yBAAyB,CAAC;AACjC,oBAAoB;AACpB,OAAO,EAEN,cAAc,EACd,uBAAuB,GACvB,MAAM,wBAAwB,CAAC;AAChC,UAAU;AACV,OAAO,EACN,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EAKhB,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,GACf,MAAM,cAAc,CAAC;AAEtB,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEjE,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAElG;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAuB,EAAmB;IAC5E,MAAM,WAAW,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,WAAW,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,kCAAkC,QAAQ,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,cAAgC,CAAC;IAErC,QAAQ,QAAQ,EAAE,CAAC;QAClB,KAAK,WAAW;YACf,cAAc,GAAG,MAAM,qBAAqB,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YAClE,MAAM;QACP,KAAK,gBAAgB;YACpB,cAAc,GAAG,MAAM,yBAAyB,CAAC,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,aAAa,CAAC,CAAC;YACjG,MAAM;QACP,KAAK,mBAAmB;YACvB,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;YAC/D,CAAC;YACD,cAAc,GAAG,MAAM,uBAAuB,CAAC,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;YAC3F,MAAM;QACP,KAAK,oBAAoB;YACxB,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;YAC9D,CAAC;YACD,cAAc,GAAG,MAAM,uBAAuB,CAAC,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;YAC3F,MAAM;QACP;YACC,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,oBAAoB,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IAC/C,OAAO,cAAc,CAAC,MAAM,CAAC;AAAA,CAC7B;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAuB,EAA0B;IACrF,MAAM,WAAW,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,WAAW,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,+CAA+C;IAC/C,MAAM,cAAc,GAAG,QAAQ,KAAK,mBAAmB,IAAI,QAAQ,KAAK,oBAAoB,CAAC;IAE7F,mBAAmB;IACnB,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;QACvC,IAAI,CAAC;YACJ,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;YAE9C,iDAAiD;YACjD,IAAI,cAAc,EAAE,CAAC;gBACpB,MAAM,cAAc,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;gBACtD,IAAI,cAAc,EAAE,SAAS,EAAE,CAAC;oBAC/B,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,CAAC,SAAS,EAAE,CAAC,CAAC;gBACjF,CAAC;YACF,CAAC;YAED,OAAO,QAAQ,CAAC;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,qCAAqC,QAAQ,GAAG,EAAE,KAAK,CAAC,CAAC;YACvE,sBAAsB,CAAC,QAAQ,CAAC,CAAC;YACjC,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IAED,iDAAiD;IACjD,IAAI,cAAc,EAAE,CAAC;QACpB,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC;QACb,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,SAAS,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,OAAO,WAAW,CAAC,MAAM,CAAC;AAAA,CAC1B;AAED;;;GAGG;AACH,MAAM,UAAU,gCAAgC,CAAC,aAAqB,EAA6B;IAClG,MAAM,OAAO,GAAkC;QAC9C,SAAS,EAAE,WAAW;QACtB,gBAAgB,EAAE,gBAAgB;QAClC,mBAAmB,EAAE,mBAAmB;QACxC,oBAAoB,EAAE,oBAAoB;KAC1C,CAAC;IACF,OAAO,OAAO,CAAC,aAAa,CAAC,CAAC;AAAA,CAC9B;AAuBD;;GAEG;AACH,MAAM,UAAU,iBAAiB,GAAwB;IACxD,OAAO;QACN;YACC,EAAE,EAAE,WAAW;YACf,IAAI,EAAE,4BAA4B;YAClC,SAAS,EAAE,IAAI;SACf;QACD;YACC,EAAE,EAAE,gBAAgB;YACpB,IAAI,EAAE,gBAAgB;YACtB,SAAS,EAAE,IAAI;SACf;QACD;YACC,EAAE,EAAE,mBAAmB;YACvB,IAAI,EAAE,uCAAuC;YAC7C,SAAS,EAAE,IAAI;SACf;QACD;YACC,EAAE,EAAE,oBAAoB;YACxB,IAAI,EAAE,yCAAyC;YAC/C,SAAS,EAAE,IAAI;SACf;KACD,CAAC;AAAA,CACF","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n * - Google Cloud Code Assist (Gemini CLI)\n * - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)\n */\n\n// Anthropic\nexport { loginAnthropic, refreshAnthropicToken } from \"./anthropic.js\";\n// GitHub Copilot\nexport {\n\tenableAllGitHubCopilotModels,\n\tenableGitHubCopilotModel,\n\tgetBaseUrlFromToken,\n\tgetGitHubCopilotBaseUrl,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.js\";\n// Google Antigravity\nexport {\n\ttype AntigravityCredentials,\n\tloginAntigravity,\n\trefreshAntigravityToken,\n} from \"./google-antigravity.js\";\n// Google Gemini CLI\nexport {\n\ttype GoogleCloudCredentials,\n\tloginGeminiCli,\n\trefreshGoogleCloudToken,\n} from \"./google-gemini-cli.js\";\n// Storage\nexport {\n\tgetOAuthPath,\n\thasOAuthCredentials,\n\tlistOAuthProviders,\n\tloadOAuthCredentials,\n\tloadOAuthStorage,\n\ttype OAuthCredentials,\n\ttype OAuthProvider,\n\ttype OAuthStorage,\n\ttype OAuthStorageBackend,\n\tremoveOAuthCredentials,\n\tresetOAuthStorage,\n\tsaveOAuthCredentials,\n\tsetOAuthStorage,\n} from \"./storage.js\";\n\n// ============================================================================\n// High-level API\n// ============================================================================\n\nimport { refreshAnthropicToken } from \"./anthropic.js\";\nimport { refreshGitHubCopilotToken } from \"./github-copilot.js\";\nimport { refreshAntigravityToken } from \"./google-antigravity.js\";\nimport { refreshGoogleCloudToken } from \"./google-gemini-cli.js\";\nimport type { OAuthCredentials, OAuthProvider } from \"./storage.js\";\nimport { loadOAuthCredentials, removeOAuthCredentials, saveOAuthCredentials } from \"./storage.js\";\n\n/**\n * Refresh token for any OAuth provider.\n * Saves the new credentials and returns the new access token.\n */\nexport async function refreshToken(provider: OAuthProvider): Promise<string> {\n\tconst credentials = loadOAuthCredentials(provider);\n\tif (!credentials) {\n\t\tthrow new Error(`No OAuth credentials found for ${provider}`);\n\t}\n\n\tlet newCredentials: OAuthCredentials;\n\n\tswitch (provider) {\n\t\tcase \"anthropic\":\n\t\t\tnewCredentials = await refreshAnthropicToken(credentials.refresh);\n\t\t\tbreak;\n\t\tcase \"github-copilot\":\n\t\t\tnewCredentials = await refreshGitHubCopilotToken(credentials.refresh, credentials.enterpriseUrl);\n\t\t\tbreak;\n\t\tcase \"google-gemini-cli\":\n\t\t\tif (!credentials.projectId) {\n\t\t\t\tthrow new Error(\"Google Cloud credentials missing projectId\");\n\t\t\t}\n\t\t\tnewCredentials = await refreshGoogleCloudToken(credentials.refresh, credentials.projectId);\n\t\t\tbreak;\n\t\tcase \"google-antigravity\":\n\t\t\tif (!credentials.projectId) {\n\t\t\t\tthrow new Error(\"Antigravity credentials missing projectId\");\n\t\t\t}\n\t\t\tnewCredentials = await refreshAntigravityToken(credentials.refresh, credentials.projectId);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown OAuth provider: ${provider}`);\n\t}\n\n\tsaveOAuthCredentials(provider, newCredentials);\n\treturn newCredentials.access;\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * For google-gemini-cli and antigravity, returns JSON-encoded { token, projectId }\n *\n * @returns API key string, or null if no credentials\n */\nexport async function getOAuthApiKey(provider: OAuthProvider): Promise<string | null> {\n\tconst credentials = loadOAuthCredentials(provider);\n\tif (!credentials) {\n\t\treturn null;\n\t}\n\n\t// Providers that need projectId in the API key\n\tconst needsProjectId = provider === \"google-gemini-cli\" || provider === \"google-antigravity\";\n\n\t// Check if expired\n\tif (Date.now() >= credentials.expires) {\n\t\ttry {\n\t\t\tconst newToken = await refreshToken(provider);\n\n\t\t\t// For providers that need projectId, return JSON\n\t\t\tif (needsProjectId) {\n\t\t\t\tconst refreshedCreds = loadOAuthCredentials(provider);\n\t\t\t\tif (refreshedCreds?.projectId) {\n\t\t\t\t\treturn JSON.stringify({ token: newToken, projectId: refreshedCreds.projectId });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn newToken;\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to refresh OAuth token for ${provider}:`, error);\n\t\t\tremoveOAuthCredentials(provider);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t// For providers that need projectId, return JSON\n\tif (needsProjectId) {\n\t\tif (!credentials.projectId) {\n\t\t\treturn null;\n\t\t}\n\t\treturn JSON.stringify({ token: credentials.access, projectId: credentials.projectId });\n\t}\n\n\treturn credentials.access;\n}\n\n/**\n * Map model provider to OAuth provider.\n * Returns undefined if the provider doesn't use OAuth.\n */\nexport function getOAuthProviderForModelProvider(modelProvider: string): OAuthProvider | undefined {\n\tconst mapping: Record<string, OAuthProvider> = {\n\t\tanthropic: \"anthropic\",\n\t\t\"github-copilot\": \"github-copilot\",\n\t\t\"google-gemini-cli\": \"google-gemini-cli\",\n\t\t\"google-antigravity\": \"google-antigravity\",\n\t};\n\treturn mapping[modelProvider];\n}\n\n// ============================================================================\n// Login/Logout types for convenience\n// ============================================================================\n\nexport type OAuthPrompt = {\n\tmessage: string;\n\tplaceholder?: string;\n\tallowEmpty?: boolean;\n};\n\nexport type OAuthAuthInfo = {\n\turl: string;\n\tinstructions?: string;\n};\n\nexport interface OAuthProviderInfo {\n\tid: OAuthProvider;\n\tname: string;\n\tavailable: boolean;\n}\n\n/**\n * Get list of OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInfo[] {\n\treturn [\n\t\t{\n\t\t\tid: \"anthropic\",\n\t\t\tname: \"Anthropic (Claude Pro/Max)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"github-copilot\",\n\t\t\tname: \"GitHub Copilot\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"google-gemini-cli\",\n\t\t\tname: \"Google Cloud Code Assist (Gemini CLI)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"google-antigravity\",\n\t\t\tname: \"Antigravity (Gemini 3, Claude, GPT-OSS)\",\n\t\t\tavailable: true,\n\t\t},\n\t];\n}\n"]}