fix(cli): explain Gemini API key rejections

This commit is contained in:
marius-kilocode
2026-07-13 11:13:39 +02:00
parent bf2b33b87b
commit 3ee91448ee
7 changed files with 107 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
---
"kilo-code": patch
"@kilocode/cli": patch
---
Show troubleshooting and migration guidance when Google Gemini rejects API credentials.
@@ -17,6 +17,14 @@ Kilo Code supports Google's Gemini family of models through the Google AI Gemini
3. **Create API Key:** Click on "Create API key" in the left-hand menu.
4. **Copy API Key:** Copy the generated API key.
## API key requirements
Google AI Studio creates auth keys by default. Kilo sends these keys in the `x-goog-api-key` header required by the Gemini API. An auth key is not an OAuth access token, so you do not need to configure OAuth.
Google began rejecting unrestricted Standard keys on June 19, 2026. If Gemini returns `Request had invalid authentication credentials`, open the key in [Google AI Studio](https://aistudio.google.com/api-keys) and check its type and status. Replace a Standard key with a new auth key. If the rejected key is already an auth key, check its Gemini API access or create a replacement before updating Kilo.
You can temporarily keep a Standard key working by restricting it to the Gemini API (`generativelanguage.googleapis.com`), but Google will reject all Standard keys in September 2026. See [Google's Gemini API key documentation](https://ai.google.dev/gemini-api/docs/api-key) for restriction and migration steps.
## Configuration in Kilo Code
{% tabs %}
+4
View File
@@ -54,6 +54,8 @@
<!-- packages/opencode/src/plugin/digitalocean.ts -->
- <https://cloudflare.com/cdn-cgi/trace>
<!-- packages/opencode/src/session/network.ts -->
- <https://developers.google.com/identity/sign-in/web/devconsole-project>
<!-- packages/opencode/src/kilocode/provider/error.ts -->
- <https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services>
<!-- packages/opencode/src/cli/cmd/github.ts -->
- <https://docs.inceptionlabs.ai/capabilities/next-edit>
@@ -126,6 +128,8 @@
<!-- packages/opencode/src/kilocode/cli/cmd/tui/app.tsx -->
- <https://kilo.ai/docs/ai-providers/cloudflare>
<!-- packages/opencode/src/cli/cmd/providers.ts -->
- <https://kilo.ai/docs/ai-providers/gemini>
<!-- packages/opencode/src/kilocode/provider/error.ts -->
- <https://kilo.ai/docs/ai-providers#custom-provider>
<!-- packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx -->
- <https://kilo.ai/docs/automate/mcp/what-is-mcp>
@@ -0,0 +1,13 @@
import type { APICallError } from "ai"
import { ProviderID } from "@/provider/schema"
const AUTH_ERROR =
"Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project."
export function hint(provider: ProviderID, error: APICallError) {
if (provider !== ProviderID.google) return
if (error.statusCode !== 401) return
if (error.message !== AUTH_ERROR) return
return "Google Gemini rejected this API key. Check its type and status in Google AI Studio. Replace a Standard key with a new auth key; if it is already an auth key, check its Gemini API access or create a replacement. Restricted Standard keys work only until September 2026. See https://kilo.ai/docs/ai-providers/gemini."
}
+3
View File
@@ -1,6 +1,7 @@
import { APICallError } from "ai"
import { STATUS_CODES } from "http"
import { iife } from "@/util/iife"
import * as KiloError from "@/kilocode/provider/error" // kilocode_change
import type { ProviderID } from "./schema"
export class HeaderTimeoutError extends Error {
@@ -63,6 +64,8 @@ function isOverflow(message: string) {
function message(providerID: ProviderID, e: APICallError) {
return iife(() => {
const hint = KiloError.hint(providerID, e) // kilocode_change
if (hint) return hint // kilocode_change
// kilocode_change start - surface a branded reauth hint for expired Copilot tokens
if (providerID.includes("github-copilot") && e.statusCode === 403) {
return "Please reauthenticate with the copilot provider to ensure your credentials work properly with Kilo."
@@ -1,7 +1,41 @@
import { describe, expect, test } from "bun:test"
import { APICallError } from "ai"
import { MessageV2 } from "@/session/message-v2"
import { ProviderID } from "@/provider/schema"
const googleAuthError =
"Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project."
function apiError(message = googleAuthError, reason?: string) {
return new APICallError({
message,
url: "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent",
requestBodyValues: {},
statusCode: 401,
responseHeaders: { "content-type": "application/json" },
responseBody: JSON.stringify({
error: {
code: 401,
message,
status: "UNAUTHENTICATED",
...(reason
? {
details: [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
reason,
domain: "googleapis.com",
metadata: { service: "generativelanguage.googleapis.com" },
},
],
}
: {}),
},
}),
isRetryable: false,
})
}
describe("provider stream errors", () => {
test("normalizes empty rate-limit messages", () => {
const body = {
@@ -43,3 +77,37 @@ describe("provider stream errors", () => {
expect(result.data.isRetryable).toBe(true)
})
})
describe("Google Gemini authentication errors", () => {
test("explains how to troubleshoot the rejected API key", () => {
const error = apiError(googleAuthError, "ACCESS_TOKEN_TYPE_UNSUPPORTED")
const result = MessageV2.fromError(error, { providerID: ProviderID.google })
expect(MessageV2.APIError.isInstance(result)).toBe(true)
if (!MessageV2.APIError.isInstance(result)) throw new Error("expected APIError")
expect(result.data.message).toBe(
"Google Gemini rejected this API key. Check its type and status in Google AI Studio. Replace a Standard key with a new auth key; if it is already an auth key, check its Gemini API access or create a replacement. Restricted Standard keys work only until September 2026. See https://kilo.ai/docs/ai-providers/gemini.",
)
expect(result.data.statusCode).toBe(401)
expect(result.data.isRetryable).toBe(false)
expect(result.data.responseBody).toBe(error.responseBody)
})
test("preserves other Google authentication errors", () => {
const error = apiError("API key not valid. Please pass a valid API key.")
const result = MessageV2.fromError(error, { providerID: ProviderID.google })
expect(MessageV2.APIError.isInstance(result)).toBe(true)
if (!MessageV2.APIError.isInstance(result)) throw new Error("expected APIError")
expect(result.data.message).toBe(error.message)
})
test("does not rewrite Google Vertex errors", () => {
const error = apiError()
const result = MessageV2.fromError(error, { providerID: ProviderID.googleVertex })
expect(MessageV2.APIError.isInstance(result)).toBe(true)
if (!MessageV2.APIError.isInstance(result)) throw new Error("expected APIError")
expect(result.data.message).toBe(error.message)
})
})
@@ -1889,6 +1889,11 @@ describe("session.llm.stream", () => {
| undefined
expect(capture.url.pathname).toBe(pathSuffix)
// kilocode_change start - auth keys use the same Google API key header as Standard keys
expect(capture.headers.get("x-goog-api-key")).toBe("test-google-key")
expect(capture.headers.get("authorization")).toBeNull()
expect(capture.url.searchParams.get("key")).toBeNull()
// kilocode_change end
expect(config?.temperature).toBe(0.3)
expect(config?.topP).toBe(0.8)
expect(config?.maxOutputTokens).toBe(ProviderTransform.maxOutputTokens(resolved))