diff --git a/extensions/google-antigravity-auth/index.ts b/extensions/google-antigravity-auth/index.ts index f349ada6a..56c583a06 100644 --- a/extensions/google-antigravity-auth/index.ts +++ b/extensions/google-antigravity-auth/index.ts @@ -2,6 +2,7 @@ import { createHash, randomBytes } from "node:crypto"; import { readFileSync } from "node:fs"; import { createServer } from "node:http"; import { emptyPluginConfigSchema } from "clawdbot/plugin-sdk"; +import { ProxyAgent } from "undici"; // OAuth constants - decoded from pi-ai's base64 encoded values to stay in sync const decode = (s: string) => Buffer.from(s, "base64").toString(); @@ -28,6 +29,37 @@ const CODE_ASSIST_ENDPOINTS = [ "https://daily-cloudcode-pa.sandbox.googleapis.com", ]; +const PROXY_ENV_KEYS = [ + "CLAWDBOT_HTTPS_PROXY", + "CLAWDBOT_HTTP_PROXY", + "HTTPS_PROXY", + "HTTP_PROXY", + "https_proxy", + "http_proxy", +]; + +function resolveProxyUrl(): string | undefined { + for (const key of PROXY_ENV_KEYS) { + const value = process.env[key]?.trim(); + if (value) return value; + } + return undefined; +} + +const proxyUrl = resolveProxyUrl(); +const proxyAgent = proxyUrl + ? new ProxyAgent({ + uri: proxyUrl, + requestTls: { maxVersion: "TLSv1.2" }, + }) + : undefined; + +function fetchWithProxy(input: RequestInfo | URL, init?: RequestInit) { + if (!proxyAgent) return fetch(input, init); + const nextInit = init ? { ...init, dispatcher: proxyAgent } : { dispatcher: proxyAgent }; + return fetch(input, nextInit); +} + const RESPONSE_PAGE = ` @@ -178,7 +210,7 @@ async function exchangeCode(params: { code: string; verifier: string; }): Promise<{ access: string; refresh: string; expires: number }> { - const response = await fetch(TOKEN_URL, { + const response = await fetchWithProxy(TOKEN_URL, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ @@ -215,7 +247,7 @@ async function exchangeCode(params: { async function fetchUserEmail(accessToken: string): Promise { try { - const response = await fetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", { + const response = await fetchWithProxy("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", { headers: { Authorization: `Bearer ${accessToken}` }, }); if (!response.ok) return undefined; @@ -241,7 +273,7 @@ async function fetchProjectId(accessToken: string): Promise { for (const endpoint of CODE_ASSIST_ENDPOINTS) { try { - const response = await fetch(`${endpoint}/v1internal:loadCodeAssist`, { + const response = await fetchWithProxy(`${endpoint}/v1internal:loadCodeAssist`, { method: "POST", headers, body: JSON.stringify({ diff --git a/extensions/google-gemini-cli-auth/oauth.ts b/extensions/google-gemini-cli-auth/oauth.ts index 405b94641..05af34497 100644 --- a/extensions/google-gemini-cli-auth/oauth.ts +++ b/extensions/google-gemini-cli-auth/oauth.ts @@ -2,6 +2,7 @@ import { createHash, randomBytes } from "node:crypto"; import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs"; import { createServer } from "node:http"; import { delimiter, dirname, join } from "node:path"; +import { ProxyAgent } from "undici"; const CLIENT_ID_KEYS = ["CLAWDBOT_GEMINI_OAUTH_CLIENT_ID", "GEMINI_CLI_OAUTH_CLIENT_ID"]; const CLIENT_SECRET_KEYS = [ @@ -23,6 +24,37 @@ const TIER_FREE = "free-tier"; const TIER_LEGACY = "legacy-tier"; const TIER_STANDARD = "standard-tier"; +const PROXY_ENV_KEYS = [ + "CLAWDBOT_HTTPS_PROXY", + "CLAWDBOT_HTTP_PROXY", + "HTTPS_PROXY", + "HTTP_PROXY", + "https_proxy", + "http_proxy", +]; + +function resolveProxyUrl(): string | undefined { + for (const key of PROXY_ENV_KEYS) { + const value = process.env[key]?.trim(); + if (value) return value; + } + return undefined; +} + +const proxyUrl = resolveProxyUrl(); +const proxyAgent = proxyUrl + ? new ProxyAgent({ + uri: proxyUrl, + requestTls: { maxVersion: "TLSv1.2" }, + }) + : undefined; + +function fetchWithProxy(input: RequestInfo | URL, init?: RequestInit) { + if (!proxyAgent) return fetch(input, init); + const nextInit = init ? { ...init, dispatcher: proxyAgent } : { dispatcher: proxyAgent }; + return fetch(input, nextInit); +} + export type GeminiCliOAuthCredentials = { access: string; refresh: string; @@ -312,7 +344,7 @@ async function exchangeCodeForTokens(code: string, verifier: string): Promise { try { - const response = await fetch(USERINFO_URL, { + const response = await fetchWithProxy(USERINFO_URL, { headers: { Authorization: `Bearer ${accessToken}` }, }); if (response.ok) { @@ -387,7 +419,7 @@ async function discoverProject(accessToken: string): Promise { } = {}; try { - const response = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, { + const response = await fetchWithProxy(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, { method: "POST", headers, body: JSON.stringify(loadBody), @@ -441,7 +473,7 @@ async function discoverProject(accessToken: string): Promise { (onboardBody.metadata as Record).duetProject = envProject; } - const onboardResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, { + const onboardResponse = await fetchWithProxy(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, { method: "POST", headers, body: JSON.stringify(onboardBody), @@ -495,7 +527,7 @@ async function pollOperation( ): Promise<{ done?: boolean; response?: { cloudaicompanionProject?: { id?: string } } }> { for (let attempt = 0; attempt < 24; attempt += 1) { await new Promise((resolve) => setTimeout(resolve, 5000)); - const response = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal/${operationName}`, { + const response = await fetchWithProxy(`${CODE_ASSIST_ENDPOINT}/v1internal/${operationName}`, { headers, }); if (!response.ok) continue;