mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-01 15:32:11 +08:00
fix(cli): explain Gemini API key rejections
This commit is contained in:
@@ -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 %}
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user