fix(provider): support Google native Gemini API in fetch_models#2608
fix(provider): support Google native Gemini API in fetch_models#2608leave1206 wants to merge 2 commits into
Conversation
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).
|
@codex review |
There was a problem hiding this comment.
💡 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".
| if base_url.contains(GOOGLE_GEMINI_HOST) { | ||
| return true; | ||
| } | ||
| if let Some(url) = models_url_override { | ||
| if url.contains(GOOGLE_GEMINI_HOST) { |
There was a problem hiding this comment.
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 👍 / 👎.
| let mut models: Vec<FetchedModel> = resp | ||
| .models | ||
| .unwrap_or_default() | ||
| .into_iter() |
There was a problem hiding this comment.
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).
|
@codex review |
|
To use Codex here, create a Codex account and connect to github. |
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.rsalways sendsAuthorization: Bearer <api_key>against an OpenAI-compatible/v1/modelspath. Google parses theBearerheader as an OAuth 2 access token and rejects API keys with401 ACCESS_TOKEN_TYPE_UNSUPPORTED. The key itself is fine — the protocol is wrong.This PR detects Google's native host and routes to
/v1beta/modelswith thex-goog-api-keyheader instead. Response shape (models[].name = "models/<id>",supportedGenerationMethods) is parsed natively, filtered to models supportinggenerateContent, and themodels/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.Curl evidence / 实证
Related Issue / 关联 Issue
No prior issue filed; observed while configuring
Google OfficialGemini provider with an AI Studio API key locally.Screenshots / 截图
Backend-only behavior change. Demoed via the curl evidence above.
API Key 无效或无权限whenever clicking "获取模型列表" against Google official endpoint with API key成功获取 N 个模型, dropdown filled withgemini-3.1-pro-preview/gemini-3-pro-preview/gemini-2.5-pro/ ...Checklist / 检查清单
pnpm typecheckpasses / 通过 TypeScript 类型检查 (no TS changes; ran anyway)pnpm format:checkpasses / 通过代码格式检查 (no JS/TS changes)cargo fmt --checkpasses / Rust 代码格式检查cargo clippy --lib --no-deps -- -D warningspassescargo test --lib model_fetch— 23 passed (4 new: host detection × 2 + URL builder + response parsing)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 withGOOGLE_GEMINI_BASE_URL=https://generativelanguage.googleapis.com+ a real AI Studio API key. Got 200 + populated model list (~30 models includinggemini-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 whengenerativelanguage.googleapis.comis in the URL.Design notes / 设计说明
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.supportedGenerationMethodscontaininggenerateContentto 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.