From eb92a0bf76eac9a03fa7b503fddf3bd12e5a6cf3 Mon Sep 17 00:00:00 2001 From: Shakker Date: Mon, 15 Jun 2026 19:23:42 +0100 Subject: [PATCH] fix: accept Google API keys for Gemini CLI --- extensions/google/cli-backend.ts | 10 ++- extensions/google/setup-api.test.ts | 6 +- .../command/attempt-execution.cli.test.ts | 71 +++++++++++++++++++ 3 files changed, 81 insertions(+), 6 deletions(-) diff --git a/extensions/google/cli-backend.ts b/extensions/google/cli-backend.ts index f5bd3b0b20f..dd0817906c0 100644 --- a/extensions/google/cli-backend.ts +++ b/extensions/google/cli-backend.ts @@ -14,6 +14,7 @@ const GEMINI_MODEL_ALIASES: Record = { }; const GEMINI_CLI_DEFAULT_MODEL_REF = "google-gemini-cli/gemini-3-flash-preview"; const GEMINI_CLI_PROVIDER_ID = "google-gemini-cli"; +const GOOGLE_PROVIDER_ID = "google"; const VERCEL_AI_GATEWAY_PROVIDER_ID = "vercel-ai-gateway"; const GEMINI_CLI_CREDENTIALS_FILENAME = "gemini-credentials.json"; const GEMINI_CLI_GCA_AUTH_ENV = [ @@ -68,7 +69,7 @@ type GeminiOAuthCredential = GeminiAuthProfileCredential & { type GeminiApiKeyCredential = GeminiAuthProfileCredential & { type: "api_key"; - provider: typeof GEMINI_CLI_PROVIDER_ID; + provider: typeof GEMINI_CLI_PROVIDER_ID | typeof GOOGLE_PROVIDER_ID; key: string; }; @@ -154,7 +155,10 @@ function requireGeminiApiKeyCredential( if (credential.type !== "api_key") { return null; } - if (credential.provider !== GEMINI_CLI_PROVIDER_ID) { + if ( + credential.provider !== GEMINI_CLI_PROVIDER_ID && + credential.provider !== GOOGLE_PROVIDER_ID + ) { throwUnsupportedGeminiCredential(credential); } @@ -166,7 +170,7 @@ function requireGeminiApiKeyCredential( return { ...credential, type: "api_key", - provider: GEMINI_CLI_PROVIDER_ID, + provider: credential.provider, key, }; } diff --git a/extensions/google/setup-api.test.ts b/extensions/google/setup-api.test.ts index 765f7de72d5..b9343853539 100644 --- a/extensions/google/setup-api.test.ts +++ b/extensions/google/setup-api.test.ts @@ -52,11 +52,11 @@ function buildGeminiApiKeyPrepareContext(workspaceDir: string): GeminiPrepareCon agentDir, provider: "google-gemini-cli", modelId: "gemini-3.1-flash-lite", - authProfileId: "google-gemini-cli:api-key", + authProfileId: "google:api-key", // Private bundled-runtime bridge, not public Plugin SDK surface. authCredential: { type: "api_key", - provider: "google-gemini-cli", + provider: "google", key: "gemini-api-key", email: "user@example.test", }, @@ -141,7 +141,7 @@ describe("google gemini cli backend auth bridge", () => { } }); - it("prepares selected Gemini API-key credentials and removes stale OAuth state for that profile home", async () => { + it("prepares selected canonical Google API-key credentials and removes stale OAuth state for that profile home", async () => { const backend = buildGoogleGeminiCliBackend(); const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-workspace-")); let home: string | undefined; diff --git a/src/agents/command/attempt-execution.cli.test.ts b/src/agents/command/attempt-execution.cli.test.ts index 35daa1911bf..2a6485c9455 100644 --- a/src/agents/command/attempt-execution.cli.test.ts +++ b/src/agents/command/attempt-execution.cli.test.ts @@ -806,6 +806,77 @@ describe("CLI attempt execution", () => { expect(firstRunCliAgentArg().authProfileId).toBe("google-gemini-cli:user@example.test"); }); + it("forwards pinned canonical Google API-key profiles to Google models routed through Gemini CLI", async () => { + const sessionKey = "agent:main:direct:gemini-cli-google-api-key"; + const sessionEntry: SessionEntry = { + sessionId: "openclaw-session-gemini-api-key", + updatedAt: Date.now(), + authProfileOverride: "google:api-key", + authProfileOverrideSource: "user", + }; + const sessionStore: Record = { [sessionKey]: sessionEntry }; + await fs.writeFile(storePath, JSON.stringify(sessionStore, null, 2), "utf-8"); + saveAuthProfileStore( + { + version: 1, + profiles: { + "google:api-key": { + type: "api_key", + provider: "google", + key: "gemini-api-key", + }, + }, + }, + tmpDir, + { filterExternalAuthProfiles: false, syncExternalCli: false }, + ); + runCliAgentMock.mockResolvedValueOnce(makeCliResult("gemini cli api-key response")); + + await runAgentAttempt({ + providerOverride: "google", + originalProvider: "google", + modelOverride: "gemini-3.1-pro-preview", + cfg: { + agents: { + defaults: { + models: { + "google/gemini-3.1-pro-preview": { + agentRuntime: { id: "google-gemini-cli" }, + }, + }, + }, + }, + } as OpenClawConfig, + sessionEntry, + sessionId: sessionEntry.sessionId, + sessionKey, + sessionAgentId: "main", + sessionFile: path.join(tmpDir, "session.jsonl"), + workspaceDir: tmpDir, + body: "continue", + isFallbackRetry: false, + resolvedThinkLevel: "medium", + timeoutMs: 1_000, + runId: "run-gemini-cli-google-api-key", + opts: {} as Parameters[0]["opts"], + runContext: {} as Parameters[0]["runContext"], + spawnedBy: undefined, + messageChannel: undefined, + skillsSnapshot: undefined, + resolvedVerboseLevel: undefined, + agentDir: tmpDir, + onAgentEvent: vi.fn(), + authProfileProvider: "google", + sessionStore, + storePath, + sessionHasHistory: false, + }); + + expect(runCliAgentMock).toHaveBeenCalledTimes(1); + expect(firstRunCliAgentArg().provider).toBe("google-gemini-cli"); + expect(firstRunCliAgentArg().authProfileId).toBe("google:api-key"); + }); + it("persists CLI replies into the session transcript", async () => { const sessionKey = "agent:main:subagent:cli-transcript"; const sessionFile = path.join(tmpDir, "session-cli-transcript.jsonl");