From f99d45cb5547c8a95718e8ad2856bca4d46604ee Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 28 Jul 2026 01:29:37 -0400 Subject: [PATCH] fix: Google invalid keys no longer stop model fallback (#114887) * fix(agents): classify Google invalid API keys Fixes #114784.\n\nCo-authored-by: 1052326311 <65798732+1052326311@users.noreply.github.com> * fix(agents): bound invalid key matcher --- .../failover-matches.test.ts | 15 +++++++++---- .../failover-matches.ts | 9 +++++--- .../result-fallback-classifier.test.ts | 21 +++++++++++++++++++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/agents/embedded-agent-helpers/failover-matches.test.ts b/src/agents/embedded-agent-helpers/failover-matches.test.ts index b2e3917f8983..90b7293f3384 100644 --- a/src/agents/embedded-agent-helpers/failover-matches.test.ts +++ b/src/agents/embedded-agent-helpers/failover-matches.test.ts @@ -107,7 +107,7 @@ 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", () => { + it("classifies Google Generative AI's 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]"; @@ -115,8 +115,14 @@ describe("Google invalid API key errors (#114784)", () => { expect(classifyFailoverReason(raw)).toBe("auth"); }); - it("classifies the structured API_KEY_INVALID variant as auth", () => { - expect(isAuthErrorMessage('{"code":"API_KEY_INVALID"}')).toBe(true); + it.each([ + "invalid_api_key_error", + "API key is invalid", + '{"code":"API_KEY_INVALID"}', + '{"code":"API_KEY_INVALID_ERROR"}', + ])("classifies the %s variant as auth", (raw) => { + expect(isAuthErrorMessage(raw)).toBe(true); + expect(classifyFailoverReason(raw)).toBe("auth"); }); it("does not treat unrelated Google invalid arguments as auth", () => { @@ -125,9 +131,10 @@ describe("Google invalid API key errors (#114784)", () => { expect(isAuthErrorMessage(raw)).toBe(false); expect(classifyFailoverReason(raw)).toBeNull(); + expect(isAuthErrorMessage("API key invalidation policy updated")).toBe(false); + expect(isAuthErrorMessage("INVALID API KEYSTORE configuration")).toBe(false); }); }); - 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 65a728a95886..ccc5a687632b 100644 --- a/src/agents/embedded-agent-helpers/failover-matches.ts +++ b/src/agents/embedded-agent-helpers/failover-matches.ts @@ -17,10 +17,13 @@ const HIGH_CONFIDENCE_AUTH_PERMANENT_PATTERNS = [ "not allowed for this organization", ] as const satisfies readonly ErrorPattern[]; +// Providers use both "invalid API key" and "API key is/not valid" word order. +// Keep them in one matcher so every result/exception classifier agrees on auth failover. +const INVALID_API_KEY_RE = + /(?:invalid[_ ]?api[_ ]?key(?![a-z0-9])|api[_ ]?key(?:[_ ]?(?:is[_ ]?)?(?:invalid(?![a-z0-9])|not[_ ]?valid(?![a-z0-9]))))/i; + 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, + INVALID_API_KEY_RE, /could not (?:authenticate|validate).*(?:api[_ ]?key|credentials)/i, "permission_error", ] as const satisfies readonly ErrorPattern[]; diff --git a/src/agents/embedded-agent-runner/result-fallback-classifier.test.ts b/src/agents/embedded-agent-runner/result-fallback-classifier.test.ts index 03582772e401..685bd74aba38 100644 --- a/src/agents/embedded-agent-runner/result-fallback-classifier.test.ts +++ b/src/agents/embedded-agent-runner/result-fallback-classifier.test.ts @@ -50,6 +50,27 @@ describe("classifyEmbeddedAgentRunResultForModelFallback", () => { }); }); + it("classifies Google invalid-key result payloads before fallback settlement", () => { + const rawError = + "Google Generative AI API error (400): API key not valid. Please pass a valid API key. [code=INVALID_ARGUMENT]"; + + const result = classifyEmbeddedAgentRunResultForModelFallback({ + provider: "google", + model: "gemini-3.1-pro-preview", + result: { + payloads: [{ isError: true, text: rawError }], + meta: { durationMs: 42 }, + }, + }); + + expect(result).toEqual({ + message: `google/gemini-3.1-pro-preview ended with a provider error: ${rawError}`, + reason: "auth", + code: "embedded_error_payload", + rawError, + }); + }); + it("classifies structured provider upstream_error payloads as fallback-worthy", () => { const rawError = '{"error":{"message":"Upstream request failed","type":"upstream_error","param":"","code":null}}';