Skip to content

feat(proxy): Gemini Native API proxy integration#1918

Open
yovinchen wants to merge 11 commits intomainfrom
feat/gemini-proxy-integration
Open

feat(proxy): Gemini Native API proxy integration#1918
yovinchen wants to merge 11 commits intomainfrom
feat/gemini-proxy-integration

Conversation

@yovinchen
Copy link
Copy Markdown
Collaborator

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 / streamGenerateContent protocol directly.


Changes

New Modules (Backend)

File Purpose
proxy/gemini_url.rs URL normalization — converts legacy OpenAI-compatible paths to canonical Gemini models/*:generateContent format. Supports both structured (official) and opaque (custom relay) full URLs.
proxy/providers/gemini_schema.rs Tool schema conversion — maps Anthropic JSON Schema tool definitions to Gemini FunctionDeclaration, intelligently choosing between parameters (restricted) and parametersJsonSchema (full JSON Schema) based on complexity.
proxy/providers/gemini_shadow.rs Session state shadow store — thread-safe LRU cache (200 sessions × 64 turns) tracking assistant content snapshots and tool call metadata for thought signature replay and tool result name resolution.
proxy/providers/streaming_gemini.rs Streaming converter — transforms Gemini streamGenerateContent?alt=sse chunks into Anthropic-style SSE events (message_start, content_block_delta, message_delta, message_stop).
proxy/providers/transform_gemini.rs Bidirectional request/response transformation — full Anthropic Messages API ↔ Gemini Native generateContent conversion 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.rsGeminiShadowStore initialization in ProxyState.
  • sse.rs — Extracted shared take_sse_block() helper with CRLF delimiter support.
  • stream_check.rs — Gemini Native health check: URL resolution, auth header generation, request body transformation.

Frontend

  • Provider Presets — New "Gemini Native" preset (generativelanguage.googleapis.com, default models: gemini-2.5-pro / gemini-2.5-flash).
  • Form Fieldsgemini_native option in API format selector with conditional hints for base URL and full URL mode.
  • Types"gemini_native" added to ClaudeApiFormat union.
  • i18n — Chinese, English, Japanese translations for all new UI strings.

Architecture

Transformation Pipeline

┌─────────────────────┐     ┌──────────────────────┐     ┌─────────────────────┐
│  Claude Code CLI    │────▶│  CC Switch Proxy      │────▶│  Gemini Native API  │
│  (Messages API)     │◀────│  (Bidirectional Xform)│◀────│  (generateContent)  │
└─────────────────────┘     └──────────────────────┘     └─────────────────────┘

Request (Anthropic → Gemini):

  • systemsystemInstruction.parts[].text
  • messages[]contents[] (role mapping: user/model)
  • tools[]tools.functionDeclarations[] (schema conversion)
  • tool_choicefunctionCallingConfig
  • max_tokensgenerationConfig.maxOutputTokens
  • Tool results resolved via shadow store (tool_use_id → function name)

Response (Gemini → Anthropic):

  • candidates[].content.parts[]content[] (text / tool_use blocks)
  • usageMetadatausage (input_tokens / output_tokens)
  • finishReasonstop_reason (STOP→end_turn, MAX_TOKENS→max_tokens, SAFETY→refusal)
  • Blocked prompts → refusal with safety filter reason

Shadow Store

The shadow store (GeminiShadowStore) maintains per-session state to solve two problems:

  1. Tool result name resolution — Gemini's functionResponse requires the function name, but Anthropic's tool_result only provides tool_use_id. The shadow store maps IDs to names.
  2. Thought signature replay — Preserves assistant turn snapshots for multi-turn context without bloating the main proxy flow.

Bounded LRU cache prevents unbounded memory growth (max 200 sessions, 64 turns each).

URL Handling

Dual-mode URL normalization:

  • Structured (official Gemini endpoints): Normalized to canonical v1beta/models/{model}:generateContent format, preventing path duplication.
  • Opaque (custom relays): Used as-is with only query parameters appended, supporting arbitrary proxy setups.

Testing

40+ new unit tests covering:

  • URL normalization (structured and opaque endpoints)
  • Schema conversion (simple parameters and full JSON Schema)
  • Streaming conversion (text deltas, function calls, CRLF delimiters)
  • Shadow store lifecycle (record, read, eviction, clear)
  • Request/response transformation (messages, tools, tool results, blocked prompts)
  • Tool argument rectification from schema hints

Commits

  1. abbf43ea — refactor(proxy): extract take_sse_block helper with CRLF delimiter support
  2. 3ce8245f — feat(proxy): add Gemini Native URL builder and full-URL resolver
  3. 8c09d8c7 — feat(proxy): add Gemini Native schema, shadow store, transform, and streaming
  4. 8603d4a3 — feat(proxy): wire Gemini Native format into proxy core and Claude adapter
  5. 70886d98 — feat(ui): add Gemini Native provider preset and api format option
  6. 8f300f45 — feat(proxy): add Gemini Native tool argument rectification
  7. 7cf554c0 — feat(proxy): update Gemini streaming and transformation logic

…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
@yovinchen
Copy link
Copy Markdown
Collaborator Author

@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: 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());
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 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 👍 / 👎.

let path = path.trim_end_matches('/');
path.contains("/v1beta/models/")
|| path.contains("/v1/models/")
|| path.contains("/models/")
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 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 👍 / 👎.

@farion1231
Copy link
Copy Markdown
Owner

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.
@yovinchen
Copy link
Copy Markdown
Collaborator Author

@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: 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".

Comment on lines +205 to +206
let text = String::from_utf8_lossy(&bytes);
buffer.push_str(&text);
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 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 👍 / 👎.

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.

google ai stadio api接入claude code失败

2 participants