diff --git a/extensions/google/cli-backend.ts b/extensions/google/cli-backend.ts index 9a3249ace39c..41593706147d 100644 --- a/extensions/google/cli-backend.ts +++ b/extensions/google/cli-backend.ts @@ -28,6 +28,7 @@ const GEMINI_CLI_API_KEY_AUTH_ENV = [ "GOOGLE_API_KEY", "GOOGLE_CLOUD_PROJECT", "GOOGLE_CLOUD_PROJECT_ID", + "GOOGLE_CLOUD_QUOTA_PROJECT", "GOOGLE_CLOUD_LOCATION", "GOOGLE_GEMINI_BASE_URL", "GEMINI_CLI_CUSTOM_HEADERS", @@ -54,6 +55,7 @@ type GeminiAuthProfileCredential = { refresh?: string; expires?: number; idToken?: string; + projectId?: string; }; type GeminiOAuthCredential = GeminiAuthProfileCredential & { @@ -118,6 +120,7 @@ function requireGeminiOAuthCredential( refresh, expires: credential.expires, idToken: normalizeString(credential.idToken), + projectId: normalizeString(credential.projectId), }; } @@ -197,6 +200,18 @@ async function clearGeminiCliCachedCredentials(geminiDir: string): Promise await fs.rm(path.join(geminiDir, GEMINI_CLI_CREDENTIALS_FILENAME), { force: true }); } +function buildGeminiCliProjectEnv(projectId: string | undefined): Record { + const normalized = normalizeString(projectId); + if (!normalized) { + return {}; + } + return { + GOOGLE_CLOUD_PROJECT: normalized, + GOOGLE_CLOUD_PROJECT_ID: normalized, + GOOGLE_CLOUD_QUOTA_PROJECT: normalized, + }; +} + async function prepareGeminiCliOAuthHome( ctx: GeminiCliAuthHomeContext, credential: GeminiAuthProfileCredential | undefined, @@ -227,6 +242,7 @@ async function prepareGeminiCliOAuthHome( env: { GEMINI_CLI_HOME: home, GEMINI_FORCE_FILE_STORAGE: "true", + ...buildGeminiCliProjectEnv(oauth.projectId), }, clearEnv: [...GEMINI_CLI_PROFILE_AUTH_ENV], }; diff --git a/extensions/google/setup-api.test.ts b/extensions/google/setup-api.test.ts index ac99631f63d7..c55b4c696472 100644 --- a/extensions/google/setup-api.test.ts +++ b/extensions/google/setup-api.test.ts @@ -17,6 +17,7 @@ type GeminiPrepareContext = Parameters< refresh?: string; expires?: number; idToken?: string; + projectId?: string; key?: string; email?: string; }; @@ -38,6 +39,7 @@ function buildGeminiOAuthPrepareContext(workspaceDir: string): GeminiPrepareCont refresh: "refresh-token", expires: 1_800_000_000_000, idToken: "id-token", + projectId: "profile-project", email: "user@example.test", }, }; @@ -101,6 +103,9 @@ describe("google gemini cli backend auth bridge", () => { home = prepared?.env?.GEMINI_CLI_HOME; expect(home).toBeTruthy(); expect(prepared?.env?.GEMINI_FORCE_FILE_STORAGE).toBe("true"); + expect(prepared?.env?.GOOGLE_CLOUD_PROJECT).toBe("profile-project"); + expect(prepared?.env?.GOOGLE_CLOUD_PROJECT_ID).toBe("profile-project"); + expect(prepared?.env?.GOOGLE_CLOUD_QUOTA_PROJECT).toBe("profile-project"); expect(home).toContain(path.join(context.agentDir, "cli-runtimes", "google-gemini-cli")); expect(home).not.toContain("user@example.test"); @@ -214,6 +219,7 @@ describe("google gemini cli backend auth bridge", () => { const originalForceEncryptedFileStorage = process.env.GEMINI_FORCE_ENCRYPTED_FILE_STORAGE; const originalGeminiApiKey = process.env.GEMINI_API_KEY; const originalGoogleApiKey = process.env.GOOGLE_API_KEY; + const originalQuotaProject = process.env.GOOGLE_CLOUD_QUOTA_PROJECT; let prepared: | Awaited>> | null @@ -224,6 +230,7 @@ describe("google gemini cli backend auth bridge", () => { process.env.GEMINI_FORCE_ENCRYPTED_FILE_STORAGE = "true"; process.env.GEMINI_API_KEY = "ambient-gemini-key"; process.env.GOOGLE_API_KEY = "ambient-google-key"; + process.env.GOOGLE_CLOUD_QUOTA_PROJECT = "ambient-project"; try { prepared = await backend.prepareExecution?.(buildGeminiOAuthPrepareContext(workspaceDir)); @@ -238,6 +245,7 @@ describe("google gemini cli backend auth bridge", () => { "GOOGLE_API_KEY", "GOOGLE_CLOUD_PROJECT", "GOOGLE_CLOUD_PROJECT_ID", + "GOOGLE_CLOUD_QUOTA_PROJECT", "GOOGLE_CLOUD_LOCATION", "GOOGLE_GEMINI_BASE_URL", "GEMINI_CLI_CUSTOM_HEADERS", @@ -250,6 +258,7 @@ describe("google gemini cli backend auth bridge", () => { restoreEnv("GEMINI_FORCE_ENCRYPTED_FILE_STORAGE", originalForceEncryptedFileStorage); restoreEnv("GEMINI_API_KEY", originalGeminiApiKey); restoreEnv("GOOGLE_API_KEY", originalGoogleApiKey); + restoreEnv("GOOGLE_CLOUD_QUOTA_PROJECT", originalQuotaProject); await prepared?.cleanup?.(); await fs.rm(workspaceDir, { recursive: true, force: true }); }