From 7653bcce58b371cb7ed496d258f421d511f00a4d Mon Sep 17 00:00:00 2001 From: xin zhuang <65798732+1052326311@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:26:56 +0800 Subject: [PATCH] fix(agents): classify Google invalid API keys as auth (#114825) --- .../failover-matches.test.ts | 22 ++++++++++++ .../failover-matches.ts | 2 ++ src/agents/model-fallback.test.ts | 36 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/src/agents/embedded-agent-helpers/failover-matches.test.ts b/src/agents/embedded-agent-helpers/failover-matches.test.ts index 8b983a29948b..b2e3917f8983 100644 --- a/src/agents/embedded-agent-helpers/failover-matches.test.ts +++ b/src/agents/embedded-agent-helpers/failover-matches.test.ts @@ -106,6 +106,28 @@ describe("Z.ai vendor error codes (#48988)", () => { }); }); +describe("Google invalid API key errors (#114784)", () => { + it("classifies the Google Generative AI invalid-key response as auth", () => { + const raw = + "Google Generative AI API error (400): API key not valid. Please pass a valid API key. [code=INVALID_ARGUMENT]"; + + expect(isAuthErrorMessage(raw)).toBe(true); + expect(classifyFailoverReason(raw)).toBe("auth"); + }); + + it("classifies the structured API_KEY_INVALID variant as auth", () => { + expect(isAuthErrorMessage('{"code":"API_KEY_INVALID"}')).toBe(true); + }); + + it("does not treat unrelated Google invalid arguments as auth", () => { + const raw = + "Google Generative AI API error (400): Request contains an invalid argument. [code=INVALID_ARGUMENT]"; + + expect(isAuthErrorMessage(raw)).toBe(false); + expect(classifyFailoverReason(raw)).toBeNull(); + }); +}); + describe("Chinese provider overload messages", () => { const ZHIPU_OVERLOAD = "[1305][该模型当前访问量过大,请您稍后再试]"; diff --git a/src/agents/embedded-agent-helpers/failover-matches.ts b/src/agents/embedded-agent-helpers/failover-matches.ts index ca2734099cf4..65a728a95886 100644 --- a/src/agents/embedded-agent-helpers/failover-matches.ts +++ b/src/agents/embedded-agent-helpers/failover-matches.ts @@ -19,6 +19,8 @@ const HIGH_CONFIDENCE_AUTH_PERMANENT_PATTERNS = [ const AMBIGUOUS_AUTH_ERROR_PATTERNS = [ /invalid[_ ]?api[_ ]?key/, + // Google returns HTTP 400 with these variants for an invalid Generative AI key (#114784). + /api[_ ]?key(?:[_ ]?(?:is )?(?:invalid|not valid))\b/i, /could not (?:authenticate|validate).*(?:api[_ ]?key|credentials)/i, "permission_error", ] as const satisfies readonly ErrorPattern[]; diff --git a/src/agents/model-fallback.test.ts b/src/agents/model-fallback.test.ts index 4f99be2e403a..675cf57c8213 100644 --- a/src/agents/model-fallback.test.ts +++ b/src/agents/model-fallback.test.ts @@ -1914,6 +1914,42 @@ describe("runWithModelFallback", () => { expect(result.provider).toBe("anthropic"); }); + it("continues to the next model after a Google invalid-key response (#114784)", async () => { + const cfg = makeCfg({ + agents: { + defaults: { + model: { + primary: "google/gemini-3.1-pro-preview", + fallbacks: ["anthropic/claude-sonnet-4-6"], + }, + }, + }, + }); + const googleInvalidKey = new Error( + "Google Generative AI API error (400): API key not valid. Please pass a valid API key. [code=INVALID_ARGUMENT]", + ); + const run = vi + .fn() + .mockRejectedValueOnce(googleInvalidKey) + .mockResolvedValueOnce("fallback ok"); + + const result = await runWithModelFallback({ + cfg, + provider: "google", + model: "gemini-3.1-pro-preview", + run, + }); + + expect(result.result).toBe("fallback ok"); + expect(result.provider).toBe("anthropic"); + expect(result.attempts[0]).toMatchObject({ + provider: "google", + model: "gemini-3.1-pro-preview", + reason: "auth", + }); + expect(run).toHaveBeenCalledTimes(2); + }); + it("keeps provider failover metadata authoritative over nested session locks", async () => { const cfg = makeCfg({ agents: {