feat(proxy): Gemini Native API proxy integration#1918
feat(proxy): Gemini Native API proxy integration#1918
Conversation
…pport
Replace inline `buffer.find("\n\n")` SSE splitting logic across streaming,
streaming_responses, response_handler, and response_processor with a shared
`take_sse_block` function that handles both `\n\n` and `\r\n\r\n` delimiters.
Introduce gemini_url module that normalizes legacy Gemini/OpenAI-compatible base URLs into canonical models/*:generateContent endpoints. Supports both structured Gemini URLs (auto-normalized) and opaque relay URLs (pass-through with query params only).
…treaming - gemini_schema: Gemini generateContent request/response type definitions - gemini_shadow: session-scoped shadow store for thinking signature and tool-call state replay across streaming chunks - transform_gemini: bidirectional Anthropic Messages ↔ Gemini Native request/response conversion with thinking block and tool-use support - streaming_gemini: Gemini SSE → Anthropic SSE streaming adapter with incremental thinking/text/tool_use delta emission
…pter Integrate gemini_native api_format throughout the proxy pipeline: - ClaudeAdapter: detect Gemini provider type, Google/GoogleOAuth auth strategies, and suppress Anthropic-specific headers for Gemini targets - Forwarder: Gemini URL resolution, shadow store threading, endpoint rewriting to models/*:generateContent with stream/non-stream variants - Handlers: route Gemini streaming through streaming_gemini adapter and non-streaming through transform_gemini converter - Server/State: add GeminiShadowStore to shared ProxyState - StreamCheck: support gemini_native health check with proper auth headers
- Add gemini_native to ClaudeApiFormat type and ProviderMeta.apiFormat - Add "Gemini Native" provider preset with default Google AI endpoints - Show Gemini-specific endpoint hints and full-URL mode guidance - Add gemini_native option to API format selector in ClaudeFormFields - Add i18n strings for zh/en/ja
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7cf554c011
ℹ️ 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".
| .iter() | ||
| .filter(|message| message.get("role").and_then(|value| value.as_str()) == Some("assistant")) | ||
| .count(); | ||
| let shadow_start_index = total_assistant_messages.saturating_sub(shadow_turns.len()); |
There was a problem hiding this comment.
Align shadow replay to latest assistant turns
When a request contains fewer assistant messages than are cached in shadow_turns (for example after client-side history truncation), this offset calculation makes replay start from the oldest cached turn, so later assistant messages get mismatched snapshots. That can inject stale functionCall IDs/thought signatures into the reconstructed context and then cause downstream tool_result name resolution to fail. Use the tail of shadow_turns (latest N turns) when mapping cached turns to current assistant messages.
Useful? React with 👍 / 👎.
src-tauri/src/proxy/gemini_url.rs
Outdated
| let path = path.trim_end_matches('/'); | ||
| path.contains("/v1beta/models/") | ||
| || path.contains("/v1/models/") | ||
| || path.contains("/models/") |
There was a problem hiding this comment.
Narrow Gemini full-URL normalization matching
This heuristic classifies any full URL containing /models/ as a structured Gemini endpoint and rewrites it, which breaks opaque relay URLs that happen to include that segment (for example custom routes like /custom/models/invoke). In full-URL mode those relay paths are expected to be preserved, so this broad match can route requests to a non-existent rewritten endpoint. Restrict normalization detection to canonical Gemini patterns instead of a generic /models/ substring.
Useful? React with 👍 / 👎.
|
Hi, I owe you an apology for the long silence. As the sole maintainer of this project, I've been overwhelmed and fell behind on reviews — but that's on me, not on you. Your contribution matters and I will review this PR. If it needs a rebase, just let me know or I can handle it. Thank you for your time and patience. |
# Conflicts: # src-tauri/src/proxy/providers/claude.rs # src-tauri/src/proxy/sse.rs # src-tauri/src/services/stream_check.rs
Resolve conflict in claudeProviderPresets.ts: keep both Gemini Native and Shengsuanyun presets.
Restore .unwrap_or(&provider.id) fallback for cache_key to match main branch behavior. Only gemini_native related changes should be in this branch.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9bbc8bb789
ℹ️ 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".
| let text = String::from_utf8_lossy(&bytes); | ||
| buffer.push_str(&text); |
There was a problem hiding this comment.
Preserve UTF-8 boundaries when buffering Gemini SSE
create_anthropic_sse_stream_from_gemini decodes each incoming byte chunk with String::from_utf8_lossy before appending to the SSE buffer. When a multibyte character is split across network chunks (common in streamed Chinese/Japanese text), lossy decoding inserts replacement characters and permanently corrupts content (and can distort downstream JSON payload text). The other SSE converters already avoid this by using append_utf8_safe with a remainder buffer; this path should do the same to prevent user-visible text corruption.
Useful? React with 👍 / 👎.
Summary
Adds full Gemini Native (
generateContent) API support to the local proxy server, enabling Claude Code to transparently use Gemini models as upstream providers with bidirectional protocol translation.Closes #1899
Motivation
Currently the proxy only supports OpenAI-compatible and Anthropic-native upstream formats. Users who want to use Google's Gemini models must go through an OpenAI-compatible shim, which loses Gemini-specific features (native tool calling, thought signatures, safety feedback). This PR adds first-class Gemini Native support so the proxy can speak Gemini's own
generateContent/streamGenerateContentprotocol directly.Changes
New Modules (Backend)
proxy/gemini_url.rsmodels/*:generateContentformat. Supports both structured (official) and opaque (custom relay) full URLs.proxy/providers/gemini_schema.rsFunctionDeclaration, intelligently choosing betweenparameters(restricted) andparametersJsonSchema(full JSON Schema) based on complexity.proxy/providers/gemini_shadow.rsproxy/providers/streaming_gemini.rsstreamGenerateContent?alt=ssechunks into Anthropic-style SSE events (message_start,content_block_delta,message_delta,message_stop).proxy/providers/transform_gemini.rsgenerateContentconversion including system prompts, messages, tools, tool results, tool choice, and usage metadata.Modified Modules (Backend)
forwarder.rs— Endpoint rewriting (/v1/messages→ Gemini generateContent), conditional Anthropic header filtering, query parameter merging, shadow store propagation.handlers.rs— Gemini streaming/non-streaming response routing, tool schema hint extraction from original request body.providers/claude.rs— Gemini Native format detection, provider type mapping (Gemini/GeminiCli), Google OAuth + API key auth strategy, request transformation delegation.server.rs—GeminiShadowStoreinitialization inProxyState.sse.rs— Extracted sharedtake_sse_block()helper with CRLF delimiter support.stream_check.rs— Gemini Native health check: URL resolution, auth header generation, request body transformation.Frontend
generativelanguage.googleapis.com, default models:gemini-2.5-pro/gemini-2.5-flash).gemini_nativeoption in API format selector with conditional hints for base URL and full URL mode."gemini_native"added toClaudeApiFormatunion.Architecture
Transformation Pipeline
Request (Anthropic → Gemini):
system→systemInstruction.parts[].textmessages[]→contents[](role mapping: user/model)tools[]→tools.functionDeclarations[](schema conversion)tool_choice→functionCallingConfigmax_tokens→generationConfig.maxOutputTokensResponse (Gemini → Anthropic):
candidates[].content.parts[]→content[](text / tool_use blocks)usageMetadata→usage(input_tokens / output_tokens)finishReason→stop_reason(STOP→end_turn, MAX_TOKENS→max_tokens, SAFETY→refusal)Shadow Store
The shadow store (
GeminiShadowStore) maintains per-session state to solve two problems:functionResponserequires the function name, but Anthropic'stool_resultonly providestool_use_id. The shadow store maps IDs to names.Bounded LRU cache prevents unbounded memory growth (max 200 sessions, 64 turns each).
URL Handling
Dual-mode URL normalization:
v1beta/models/{model}:generateContentformat, preventing path duplication.Testing
40+ new unit tests covering:
Commits
abbf43ea— refactor(proxy): extracttake_sse_blockhelper with CRLF delimiter support3ce8245f— feat(proxy): add Gemini Native URL builder and full-URL resolver8c09d8c7— feat(proxy): add Gemini Native schema, shadow store, transform, and streaming8603d4a3— feat(proxy): wire Gemini Native format into proxy core and Claude adapter70886d98— feat(ui): add Gemini Native provider preset and api format option8f300f45— feat(proxy): add Gemini Native tool argument rectification7cf554c0— feat(proxy): update Gemini streaming and transformation logic