diff --git a/apps/server/src/provider/Drivers/AntigravityDriver.ts b/apps/server/src/provider/Drivers/AntigravityDriver.ts index 7bb1f25b67ce7..5e6d444e9f2f6 100644 --- a/apps/server/src/provider/Drivers/AntigravityDriver.ts +++ b/apps/server/src/provider/Drivers/AntigravityDriver.ts @@ -146,6 +146,7 @@ export const AntigravityDriver: ProviderDriver< apiKey: settingsSecretLiteral(settings.apiKey), gcpProject: settings.gcpProject, gcpLocation: settings.gcpLocation, + credentialsFile: settings.credentialsFile, }; const authConfigIssue = antigravityAuthConfigIssue(auth); const processEnvironment = yield* mergeProviderInstanceEnvironment(environment); diff --git a/apps/server/src/provider/antigravityAuthSupport.test.ts b/apps/server/src/provider/antigravityAuthSupport.test.ts index 0220f9fd153c7..4d2dd058b7fd5 100644 --- a/apps/server/src/provider/antigravityAuthSupport.test.ts +++ b/apps/server/src/provider/antigravityAuthSupport.test.ts @@ -139,28 +139,62 @@ describe("Antigravity process environment", () => { apiKey: "gemini-secret", gcpProject: "proj", gcpLocation: "us-central1", + credentialsFile: "/ignored.json", }); expect(geminiKey.GEMINI_API_KEY).toBe("gemini-secret"); expect(geminiKey.GOOGLE_API_KEY).toBeUndefined(); expect(geminiKey.GOOGLE_CLOUD_PROJECT).toBeUndefined(); + expect(geminiKey.GOOGLE_APPLICATION_CREDENTIALS).toBeUndefined(); + // An API key belongs to one project, so it wins over a credentials file. const vertexKey = spawnFor({ authMethod: "agent-platform", apiKey: "vertex-secret", gcpProject: "", gcpLocation: "", + credentialsFile: "/ignored.json", }); expect(vertexKey.GOOGLE_API_KEY).toBe("vertex-secret"); expect(vertexKey.GEMINI_API_KEY).toBeUndefined(); + expect(vertexKey.GOOGLE_APPLICATION_CREDENTIALS).toBeUndefined(); + + const vertexAdc = spawnFor({ + authMethod: "agent-platform", + apiKey: "", + gcpProject: "proj", + gcpLocation: "global", + credentialsFile: "/keys/service-account.json", + }); + expect(vertexAdc.GOOGLE_APPLICATION_CREDENTIALS).toBe("/keys/service-account.json"); + expect(vertexAdc.GOOGLE_API_KEY).toBeUndefined(); + + // When credentialsFile is blank, ambient GOOGLE_APPLICATION_CREDENTIALS is ignored. + const vertexBlankAdc = + buildAntigravityAcpSpawnInput({ + installation: { executablePath: "/release/acp", harnessPath: "/release/harness" }, + profile, + cwd: "/project", + baseEnv: { PATH: "/usr/bin", GOOGLE_APPLICATION_CREDENTIALS: "/env/adc.json" }, + auth: { + authMethod: "agent-platform", + apiKey: "", + gcpProject: "proj", + gcpLocation: "global", + credentialsFile: "", + }, + }).env ?? {}; + expect(vertexBlankAdc.GOOGLE_APPLICATION_CREDENTIALS).toBeUndefined(); const business = spawnFor({ authMethod: "oauth-business", apiKey: "ignored", gcpProject: "proj", gcpLocation: "us-central1", + credentialsFile: "/ignored.json", }); expect(business.GEMINI_API_KEY).toBeUndefined(); expect(business.GOOGLE_API_KEY).toBeUndefined(); + expect(business.GOOGLE_APPLICATION_CREDENTIALS).toBeUndefined(); }); it("isolates TEMP and TMP to the profile directory on Windows", () => { @@ -661,6 +695,7 @@ it.layer(NodeServices.layer)("Antigravity profile preparation", (it) => { apiKey: "vertex-secret", gcpProject: "proj", gcpLocation: "us-central1", + credentialsFile: "", }, }); const settingsPath = path.join(profile.acpDirectory, "settings.json"); diff --git a/apps/server/src/provider/antigravityAuthSupport.ts b/apps/server/src/provider/antigravityAuthSupport.ts index 6a926b12f7c90..e9d546df480e3 100644 --- a/apps/server/src/provider/antigravityAuthSupport.ts +++ b/apps/server/src/provider/antigravityAuthSupport.ts @@ -95,6 +95,7 @@ export interface AntigravityAuthConfig { readonly apiKey: string; readonly gcpProject: string; readonly gcpLocation: string; + readonly credentialsFile: string; } const ANTIGRAVITY_PERSONAL_AUTH: AntigravityAuthConfig = { @@ -102,6 +103,7 @@ const ANTIGRAVITY_PERSONAL_AUTH: AntigravityAuthConfig = { apiKey: "", gcpProject: "", gcpLocation: "", + credentialsFile: "", }; export function antigravityAuthUsesBrowser(authMethod: AntigravityAuthMethod): boolean { @@ -210,7 +212,9 @@ function antigravityEnvironment( ? { GEMINI_API_KEY: auth.apiKey } : auth.authMethod === "agent-platform" && auth.apiKey ? { GOOGLE_API_KEY: auth.apiKey } - : {}; + : auth.authMethod === "agent-platform" && auth.credentialsFile + ? { GOOGLE_APPLICATION_CREDENTIALS: auth.credentialsFile } + : {}; // Each launch unpacks about 1 GB; use T3-owned temp so the driver can reclaim files after force-kill. const tempDirectory = runtimeTempDirectory ?? profile.tempDirectory; return { diff --git a/docs/user/providers-antigravity.md b/docs/user/providers-antigravity.md index c3177c29a12c1..786d0f7dd2512 100644 --- a/docs/user/providers-antigravity.md +++ b/docs/user/providers-antigravity.md @@ -36,16 +36,18 @@ T3 Code's confirmation. Choose **Sign-in method** in the Antigravity provider settings: -| Method | Credentials | -| -------------------------- | -------------------------------------------------------------------------------------------- | -| Google account | Personal Google sign-in in the browser | -| Gemini Enterprise | Browser sign-in, GCP project, and GCP location | -| Gemini API key | API key; choose Connect without a browser | -| Agent Platform / Vertex AI | API key, or GCP project and location with Application Default Credentials on the environment | +| Method | Credentials | +| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| Google account | Personal Google sign-in in the browser | +| Gemini Enterprise | Browser sign-in, GCP project, and GCP location | +| Gemini API key | API key; choose Connect without a browser | +| Agent Platform / Vertex AI | API key; with no API key, GCP project and location with Application Default Credentials from **Credentials file** or the environment's default | The environment stores the Antigravity API key outside its settings file, and clients only see whether one is saved. The agent uses the method and credentials selected for this instance; ambient `GEMINI_API_KEY` and Google credential variables do not override them. +That includes `GOOGLE_APPLICATION_CREDENTIALS` in the instance's **Environment variables**; set the +path in **Credentials file** instead. Changing the method stops the instance's sessions. Sign out before replacing an account. diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index 346213992e4b2..50f84a4744bde 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -893,6 +893,15 @@ export const AntigravitySettings = makeProviderSettingsSchema( providerSettingsForm: { placeholder: "us-central1", clearWhenEmpty: "omit" }, }), ), + credentialsFile: TrimmedString.pipe( + Schema.withDecodingDefault(Effect.succeed("")), + Schema.annotateKey({ + title: "Credentials file", + description: + "Application Default Credentials or service account JSON. Set the file path here; ambient GOOGLE_APPLICATION_CREDENTIALS environment variables are ignored.", + providerSettingsForm: { placeholder: "Default ADC", clearWhenEmpty: "omit" }, + }), + ), binaryPath: TrimmedString.pipe( Schema.withDecodingDefault(Effect.succeed("")), Schema.annotateKey({ @@ -906,7 +915,7 @@ export const AntigravitySettings = makeProviderSettingsSchema( Schema.annotateKey({ providerSettingsForm: { hidden: true } }), ), }, - { order: ["authMethod", "apiKey", "gcpProject", "gcpLocation", "binaryPath"] }, + { order: ["authMethod", "apiKey", "credentialsFile", "gcpProject", "gcpLocation", "binaryPath"] }, ); export type AntigravitySettings = typeof AntigravitySettings.Type;