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
This commit is contained in:
Peter Steinberger
2026-07-28 01:29:37 -04:00
committed by GitHub
parent add9205d1a
commit f99d45cb55
3 changed files with 38 additions and 7 deletions
@@ -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][该模型当前访问量过大,请您稍后再试]";
@@ -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[];
@@ -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}}';