Skip to content

fix(provider): support Google native Gemini API in fetch_models#2608

Open
leave1206 wants to merge 2 commits into
farion1231:mainfrom
leave1206:fix/gemini-native-fetch-models
Open

fix(provider): support Google native Gemini API in fetch_models#2608
leave1206 wants to merge 2 commits into
farion1231:mainfrom
leave1206:fix/gemini-native-fetch-models

Conversation

@leave1206
Copy link
Copy Markdown

Summary / 概述

The "Fetch Models" button on Gemini providers fails with API Key 无效或无权限 whenever the base URL points at Google's official endpoint (generativelanguage.googleapis.com). Root cause: services/model_fetch.rs always sends Authorization: Bearer <api_key> against an OpenAI-compatible /v1/models path. Google parses the Bearer header as an OAuth 2 access token and rejects API keys with 401 ACCESS_TOKEN_TYPE_UNSUPPORTED. The key itself is fine — the protocol is wrong.

This PR detects Google's native host and routes to /v1beta/models with the x-goog-api-key header instead. Response shape (models[].name = "models/<id>", supportedGenerationMethods) is parsed natively, filtered to models supporting generateContent, and the models/ prefix is stripped so IDs (e.g. gemini-3.1-pro-preview) drop straight into the existing form. The OpenAI-compatible code path is untouched, so aggregator providers keep working as before.

中文:用 API Key 配 Gemini 官方端点时,"获取模型列表"按钮永远报"API Key 无效或无权限"。原因是 cc-switch 用 OpenAI 兼容协议(Authorization: Bearer ... + /v1/models)打 Google,但 Google 把 Bearer 当 OAuth token 解析,API Key 对它来说是无效凭证。本 PR 检测到 Google 原生 host 时改走 /v1beta/models + x-goog-api-key header,并解析 Gemini 原生响应格式(过滤 generateContent 模型 + 去掉 models/ 前缀)。其他第三方聚合站走的 OpenAI 兼容路径完全不动。

Curl evidence / 实证

# What cc-switch sends today (OpenAI-style):
$ curl -H "Authorization: Bearer <api_key>" \
       https://generativelanguage.googleapis.com/v1/models
HTTP 401
{"error":{"code":401,"status":"UNAUTHENTICATED",
 "message":"Request had invalid authentication credentials. Expected OAuth 2 access token...",
 "details":[{"reason":"ACCESS_TOKEN_TYPE_UNSUPPORTED",...}]}}

# Native Gemini protocol (what this PR sends):
$ curl -H "x-goog-api-key: <api_key>" \
       https://generativelanguage.googleapis.com/v1beta/models
HTTP 200
{"models":[{"name":"models/gemini-3.1-pro-preview", ...}, ...]}

Related Issue / 关联 Issue

No prior issue filed; observed while configuring Google Official Gemini provider with an AI Studio API key locally.

Screenshots / 截图

Backend-only behavior change. Demoed via the curl evidence above.

Before / 修改前 After / 修改后
Red banner: API Key 无效或无权限 whenever clicking "获取模型列表" against Google official endpoint with API key Toast: 成功获取 N 个模型, dropdown filled with gemini-3.1-pro-preview / gemini-3-pro-preview / gemini-2.5-pro / ...

Checklist / 检查清单

  • pnpm typecheck passes / 通过 TypeScript 类型检查 (no TS changes; ran anyway)
  • pnpm format:check passes / 通过代码格式检查 (no JS/TS changes)
  • cargo fmt --check passes / Rust 代码格式检查
  • cargo clippy --lib --no-deps -- -D warnings passes
  • cargo test --lib model_fetch — 23 passed (4 new: host detection × 2 + URL builder + response parsing)
  • No user-facing text changed / 无用户可见文本改动,i18n 文件不需更新

Verification / 验证

Built locally on macOS aarch64 (Tauri release bundle), installed in place of the official /Applications/CC Switch.app, and clicked "获取模型列表" on a Gemini provider configured with GOOGLE_GEMINI_BASE_URL=https://generativelanguage.googleapis.com + a real AI Studio API key. Got 200 + populated model list (~30 models including gemini-3.1-pro-preview). Aggregator providers (with OpenAI-compatible relay base URLs) were also re-tested and continue to work via the original code path — host detection only flips routing when generativelanguage.googleapis.com is in the URL.

Design notes / 设计说明

  • Detection lives in a tiny helper is_google_native_gemini(base_url, models_url_override) so the routing decision is cheap and explicit; no environmental sniffing or app-type plumbing required.
  • Response filter keeps only supportedGenerationMethods containing generateContent to avoid surfacing TTS / Lyria / embedding-only models in the text-model dropdown — matches what the Gemini CLI itself accepts.
  • models/<id> prefix is stripped so the returned ID can be pasted directly into the existing model-input field without further normalization.

The "Fetch Models" button hits Google's `generativelanguage.googleapis.com`
with `Authorization: Bearer <api_key>`, but Google parses Bearer header as
an OAuth 2 access token and rejects API keys with 401
`ACCESS_TOKEN_TYPE_UNSUPPORTED`. Net effect: any user configuring Gemini
with an API key against the official endpoint always sees "API Key
无效或无权限".

Detect Google's native host and route to `/v1beta/models` with
`x-goog-api-key` header. Parse the Gemini response shape
(`models[].name = "models/<id>"`, `supportedGenerationMethods`), filter to
models supporting `generateContent`, and strip the `models/` prefix so the
returned IDs (e.g. `gemini-3.1-pro-preview`) drop straight into the existing
form. The OpenAI-compatible path is untouched, so aggregator providers keep
working as before.

Tests: added 4 unit tests covering host detection (base_url + models_url),
URL builder against the four shapes of input (root / trailing slash /
`/v1beta` / `/v1beta/models`), and response parsing (filter + prefix strip).
@farion1231
Copy link
Copy Markdown
Owner

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f75cb117c2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/services/model_fetch.rs Outdated
Comment on lines +149 to +153
if base_url.contains(GOOGLE_GEMINI_HOST) {
return true;
}
if let Some(url) = models_url_override {
if url.contains(GOOGLE_GEMINI_HOST) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restrict Gemini-native detection to the URL host

is_google_native_gemini currently uses substring matching on the entire URL, so any proxy URL/path that merely contains generativelanguage.googleapis.com is forced down the native Gemini branch. In those cases we switch auth to x-goog-api-key and bypass the existing OpenAI-compatible /v1/models flow, which can break previously working relay endpoints that embed upstream hostnames in their path. Parse and compare the actual host instead of using contains on the full string.

Useful? React with 👍 / 👎.

Comment thread src-tauri/src/services/model_fetch.rs Outdated
Comment on lines +203 to +206
let mut models: Vec<FetchedModel> = resp
.models
.unwrap_or_default()
.into_iter()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Page through Gemini model listings before returning results

The native Gemini path reads a single models.list response and returns immediately, but the API is paginated (nextPageToken) with a default page size of 50. As written, the dropdown will silently omit models whenever the account/region response spans multiple pages, producing incomplete model lists. The native fetch should follow nextPageToken (or request a sufficiently large pageSize) before sorting and returning.

Useful? React with 👍 / 👎.

- Replace contains() with url::Url host parsing in is_google_native_gemini
  to avoid false positives when the domain appears in URL path
- Add nextPageToken pagination loop in fetch_models_gemini_native to
  collect all models across multiple pages (default pageSize=50)
- Add tests for host-in-path rejection and pagination token parsing

Addresses Codex review P1 (host detection) and P2 (pagination).
@leave1206
Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants