feat(gemini_oauth): full Gemini CLI OAuth integration with Cloud Code API - #1356
Conversation
- Add gemini_oauth.rs: full OAuth flow with PKCE, token refresh, and Cloud Code project discovery (loadCodeAssist + onboardUser) - Route preview/gemini-3 models through cloudcode-pa.googleapis.com with proper project ID injection in request payload - Trigger OAuth login during onboarding wizard (not first chat message) - Support manual redirect URL paste as fallback (tokio::select race) - Parse 429 rate-limit errors with retry_after from Google response - Add static model list: gemini-1.5/2.0/2.5/3.0/3.1 variants - Add GeminiOauthConfig with default credentials path (~/.gemini/)
…e models - Implement function calling support (functionDeclarations, functionResponse) - Add functionCall SSE parsing and empty stream retry support - Add generationConfig (temperature, maxOutputTokens) - Add thinkingConfig for Gemini 3 and thinking models - Add toolConfig (functionCallingConfig.mode) - Fix .expect() panics with .ok_or_else() - Restrict oauth credentials file permissions to 0600 - Update docs and FEATURE_PARITY.md - Update wizard to current Gemini 3.1 and 2.5 models
- Add cache_read_input_tokens/cache_creation_input_tokens fields (value 0) - Implement manual Debug for OAuthCredential to redact tokens - Fix hardcoded /tmp: use GeminiOauthConfig::default_credentials_path() - Replace emoji output with plain text markers - Propagate Client::builder() errors instead of silent fallback - Use tokio::fs for all file I/O in CredentialManager (was std::fs) - Use if let Some(ref pid) to avoid consuming credential.project_id - Extract uses_cloud_code_api() helper; route by major version (gemini-2+) - Concatenate multiple system messages into systemInstruction - Include functionCall parts in assistant message conversion - Add 401 retry loop with allow_retry flag for auth failures - Remove biased from tokio::select! in OAuth callback handler - Remove hardcoded context_length 1M; vary by model family - Change GOOG_API_CLIENT from Node.js spoof to gl-rust/1.0.0 - Implement list_models() with static model list - Move create_gemini_oauth_provider() before test module (clippy) - Fix 9 additional clippy warnings (collapsible_if, map_or, needless_borrow) - Run cargo fmt
- Add force_refresh() for 401 retry (bypass timestamp check)
- Standardize Gemini model list across docs, wizard, and provider
- Restore gemini-3 check for thinkingConfig
- Redact sensitive tokens in GoogleTokenRefreshResponse Debug output
- Use dynamic version for GOOG_API_CLIENT
- Improve model_metadata() context length heuristics
- Use strip_prefix("data:") for safer SSE parsing
- Skip re-auth in wizard if keeping existing provider
…overy - Register gemini_oauth as a dedicated backend in config/llm.rs (skip registry fallback, preserve backend name, suppress unknown-backend warning) - Fix app.rs credential guard to exclude backends with dedicated configs (gemini_oauth, bedrock) from the provider.is_none() check - Auto-discover Cloud Code project_id via loadCodeAssist when credentials lack it (e.g. created by the original Gemini CLI) - Persist discovered project_id to credentials file for subsequent runs - Add safety settings (BLOCK_NONE), gated behind GEMINI_SAFETY_BLOCK_NONE env - Add thinkingConfig: budget-based for Gemini 2.5, level-based for Gemini 3.x (without includeThoughts to avoid empty responses from reasoning.rs stripping) - Add thought signature injection for Gemini 3.x preview APIs - Add history curation to filter invalid model outputs before re-sending - Add extended generationConfig env vars (topP, topK, seed, penalties, responseMimeType, responseJsonSchema, cachedContent) - Add custom headers support via GEMINI_CLI_CUSTOM_HEADERS - Add API key auth mode (GEMINI_API_KEY + GEMINI_API_KEY_AUTH_MECHANISM) - Add SSE metadata extraction (modelVersion, credits, promptFeedback, groundingMetadata, citationMetadata, cachedContentTokenCount) - Add countTokens API support - Add new models to wizard (gemini-3.1-pro-preview-customtools, gemini-3-pro-preview, gemini-3.1-flash-lite-preview) - Update docs/LLM_PROVIDERS.md with new models and routing rules - Rewrite regression tests with comprehensive coverage (23 unit tests pass)
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the LLM provider capabilities by introducing a comprehensive Google Gemini integration using OAuth. It enables seamless authentication and interaction with the Gemini API, including advanced features like Cloud Code API routing, project ID management, and various generation configurations. The changes ensure a robust and feature-rich experience for users leveraging Gemini models, aligning the system's capabilities with the official Gemini CLI. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
- Add '// safety: hardcoded literal' to regex .expect() to satisfy the no-panic-in-prod CI check - Fix cargo fmt whitespace in collapsible if-let chain
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive integration for Google Gemini via OAuth, aligning it with the official Gemini CLI's features and using the Cloud Code API. The changes are extensive, including a new gemini_oauth provider, credential management with an interactive OAuth flow, and updates to configuration, documentation, and the setup wizard.
Overall, the implementation is thorough and well-integrated into the existing application structure. I've identified a few areas for improvement:
- A bug in custom header parsing that could lead to incorrect behavior.
- Some opportunities for performance improvement and code simplification.
- Fragile logic for determining model metadata that could be made more robust.
- An inconsistency in a new regression test.
My detailed comments are below. Addressing these points will enhance the correctness and maintainability of this new provider.
| _ => return headers, | ||
| }; | ||
| // Split on commas followed by a header-key pattern (word chars + colon) | ||
| for entry in env_val.split(',') { |
There was a problem hiding this comment.
The implementation of parse_custom_headers does not match its documentation on lines 91-92. The comment states that commas within header values are preserved, but the code uses a simple split(','). This will incorrectly split header values that contain commas (e.g., X-My-Header:value,with,commas).
To fix this, you could use a regular expression to split the string only on commas that are followed by a valid header key pattern.
References
- The current header parsing logic is flawed, preventing proper handling of custom headers with commas, which is critical for supporting service-specific requirements as outlined in the rule.
| if self.config.llm.backend != "nearai" | ||
| && self.config.llm.backend != "gemini_oauth" | ||
| && self.config.llm.backend != "bedrock" | ||
| && self.config.llm.provider.is_none() |
There was a problem hiding this comment.
This conditional is getting long. Using a matches! macro would make it more concise and readable.
| if self.config.llm.backend != "nearai" | |
| && self.config.llm.backend != "gemini_oauth" | |
| && self.config.llm.backend != "bedrock" | |
| && self.config.llm.provider.is_none() | |
| if !matches!(self.config.llm.backend.as_str(), "nearai" | "gemini_oauth" | "bedrock") | |
| && self.config.llm.provider.is_none() |
| for line in body_str.lines() { | ||
| let Some(json_str) = line.strip_prefix("data:") else { |
There was a problem hiding this comment.
| Some(2_000_000) | ||
| } else if self.config.model.contains("flash") { | ||
| Some(1_000_000) | ||
| } else { | ||
| None | ||
| }; | ||
|
|
There was a problem hiding this comment.
The logic for determining context_length based on substrings like "pro" and "flash" is fragile and may not be accurate for all current and future Gemini models. A model name could contain these substrings without having the assumed context length. Consider using a more robust method, such as a match statement on known model prefixes or a map from model ID to its metadata, to avoid incorrect assumptions.
References
- The logic for determining
context_lengthrelies on fragile substring containment ("pro", "flash") which can lead to false positives and incorrect assumptions about model capabilities, similar to how simple substring checks for commands can lead to false positives. A more robust, token-based approach is needed.
| /// Wizard, list_models(), and LLM_PROVIDERS.md all return the same 5 models. | ||
| #[test] | ||
| fn test_regression_standardized_model_list() { | ||
| let expected_models = [ | ||
| "gemini-3.1-pro-preview", | ||
| "gemini-3-flash-preview", | ||
| "gemini-2.5-pro", | ||
| "gemini-2.5-flash", | ||
| "gemini-2.5-flash-lite", | ||
| ]; |
There was a problem hiding this comment.
The comment on line 65 states that the wizard and list_models return 5 models, but they actually return 8. This test is also missing three models that are present in GeminiOauthProvider::list_models and the setup wizard:
gemini-3.1-pro-preview-customtoolsgemini-3-pro-previewgemini-3.1-flash-lite-preview
The test should be updated to include all standardized models to ensure consistency and correct routing for all supported models.
References
- The regression test is incomplete, missing coverage for several models that are part of the new Gemini OAuth integration. This indicates a gap in test coverage for the new functionality, similar to how refactoring can lead to lost coverage if tests aren't updated.
- Fix parse_custom_headers to preserve commas in values by splitting
only on commas followed by a header-name:colon pattern (manual scan
instead of simple split(','))
- Use matches! macro for backend exclusion check in app.rs
- Merge SSE metadata extraction into single pass (was iterating twice)
- Replace fragile substring-based context_length with explicit match
on known Gemini model IDs via gemini_context_length()
- Add missing models to regression test (8 models, not 5)
|
Please change the target branch to staging. Thanks! |
There was a problem hiding this comment.
Pull request overview
Adds a dedicated gemini_oauth LLM backend that integrates Gemini CLI–compatible OAuth (PKCE) with Google’s Cloud Code API routing, plus setup-wizard support, docs, and regression tests.
Changes:
- Introduces
GeminiOauthProvider+ OAuth credential manager, Cloud Code routing logic, SSE parsing, token refresh, and token counting support. - Extends configuration + provider creation paths to treat
gemini_oauthas a first-class backend (no openai_compatible fallback / no “unknown backend” warning). - Updates setup wizard flows, documentation, and adds regression/unit tests for routing and model list consistency.
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/gemini_oauth_regression.rs | Adds regression tests for Cloud Code routing, preview matching, standardized model list, and ChatMessage helpers. |
| src/setup/wizard.rs | Adds “Gemini CLI (OAuth)” provider option and a setup flow that validates OAuth creds; extends model selection defaults for Gemini. |
| src/llm/models.rs | Ensures NearAI model-discovery config includes the new gemini_oauth field. |
| src/llm/mod.rs | Registers the new module and adds provider factory routing for gemini_oauth. |
| src/llm/gemini_oauth.rs | Implements the Gemini OAuth provider, credential persistence/refresh, Cloud Code routing, SSE parsing, generation config env overrides, etc. |
| src/llm/config.rs | Adds GeminiOauthConfig and attaches it to LlmConfig. |
| src/config/mod.rs | Re-exports GeminiOauthConfig from the config module surface. |
| src/config/llm.rs | Resolves gemini_oauth config and suppresses unknown-backend warning/fallback behavior. |
| src/app.rs | Adjusts post-init credential guard to exclude dedicated-config backends (incl. gemini_oauth). |
| docs/LLM_PROVIDERS.md | Documents Gemini OAuth backend, models, and Cloud Code vs standard API routing rules. |
| FEATURE_PARITY.md | Updates parity tables to mark Gemini and related OAuth functionality as implemented. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| |---|---|---| | ||
| | Function calling | ✅ | `functionDeclarations` / `functionCall` / `functionResponse` | | ||
| | `generationConfig` | ✅ | `temperature`, `maxOutputTokens` passed from request | | ||
| | `thinkingConfig` | ✅ | `includeThoughts: true` for `gemini-3`/`thinking` models | |
| | Skill routing blocks | ✅ | 🚧 | ActivationCriteria (keywords, patterns, tags) but no "Use when / Don't use when" blocks | | ||
| | Skill path compaction | ✅ | ❌ | ~ prefix to reduce prompt tokens | | ||
| | Thinking modes (off/minimal/low/medium/high/xhigh/adaptive) | ✅ | ❌ | Configurable reasoning depth | | ||
| | Thinking modes (off/minimal/low/medium/high/xhigh/adaptive) | ✅ | 🚧 | thinkingConfig for Gemini models (includeThoughts); no per-level control yet | |
| Role::Assistant => { | ||
| let mut parts = vec![serde_json::json!({ "text": msg.content })]; | ||
| if let Some(ref calls) = msg.tool_calls { | ||
| for call in calls { | ||
| parts.push(serde_json::json!({ | ||
| "functionCall": { |
| cache_read_input_tokens: 0, | ||
| cache_creation_input_tokens: 0, |
| if let Some(pid) = self.discover_project_id(&updated.access_token).await { | ||
| info!(project_id = %pid, "Discovered Cloud Code project"); | ||
| updated.project_id = Some(pid); | ||
| let _ = self.save_credential(&updated).await; |
| /// Parse `GEMINI_CLI_CUSTOM_HEADERS` env var in format `key:value,key:value`. | ||
| /// Commas inside values are preserved — splits only on commas followed by a | ||
| /// valid HTTP header name pattern (ASCII alphanumeric/hyphen, then `:`). | ||
| fn parse_custom_headers() -> std::collections::HashMap<String, String> { | ||
| let mut headers = std::collections::HashMap::new(); | ||
| let env_val = match std::env::var("GEMINI_CLI_CUSTOM_HEADERS") { | ||
| Ok(v) if !v.is_empty() => v, | ||
| _ => return headers, | ||
| }; | ||
|
|
||
| // Manual split: a comma is a separator only when followed (after optional | ||
| // whitespace) by `<header-name>:` where header-name is `[A-Za-z0-9\-]+`. | ||
| let bytes = env_val.as_bytes(); | ||
| let mut start = 0; | ||
| let mut i = 0; | ||
| while i < bytes.len() { | ||
| if bytes[i] == b',' { | ||
| // Check if the text after the comma looks like a header name + colon | ||
| let rest = &env_val[i + 1..]; | ||
| let trimmed = rest.trim_start(); | ||
| let hdr_len = trimmed | ||
| .bytes() | ||
| .take_while(|b| b.is_ascii_alphanumeric() || *b == b'-' || *b == b'_') | ||
| .count(); |
| match current.as_str() { | ||
| "nearai" => "NEAR AI".to_string(), | ||
| "gemini_oauth" => "Gemini API (OAuth)".to_string(), | ||
| _ => { | ||
| if let Some(def) = registry.find(¤t) { | ||
| def.setup | ||
| .as_ref() | ||
| .map(|s| s.display_name().to_string()) | ||
| .unwrap_or_else(|| def.id.clone()) | ||
| } else { | ||
| current.clone() | ||
| } | ||
| } | ||
| } |
- Fix empty text part for assistant messages with tool calls (curate_contents could drop entire model turn) - Propagate cache_read/creation_input_tokens in complete_with_tools - Log warning on save_credential failure instead of silently ignoring - Fix doc comment to mention underscore in header name pattern - Handle gemini-oauth (hyphen variant) in setup wizard display - Fix docs: thinkingConfig uses thinkingBudget/thinkingLevel, not includeThoughts
Merge staging to pick up GitHub Copilot provider, OpenAI Codex provider, and other recent changes. Both gemini_oauth and openai_codex backends are now registered as dedicated configs with proper credential guards. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add explicit gemini_oauth handling in create_cheap_provider_for_backend() to create a GeminiOauthProvider with the cheap model swapped in. Without this, setting LLM_CHEAP_MODEL with gemini_oauth backend would fail with a confusing "no registry provider config available" error. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Document GEMINI_MODEL, GEMINI_CREDENTIALS_PATH, GEMINI_API_KEY, and all extended generation config env vars in the example config file. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code reviewFound 8 issues:
|
… API (nearai#1356) * feat: integrate Gemini CLI OAuth with Cloud Code API - Add gemini_oauth.rs: full OAuth flow with PKCE, token refresh, and Cloud Code project discovery (loadCodeAssist + onboardUser) - Route preview/gemini-3 models through cloudcode-pa.googleapis.com with proper project ID injection in request payload - Trigger OAuth login during onboarding wizard (not first chat message) - Support manual redirect URL paste as fallback (tokio::select race) - Parse 429 rate-limit errors with retry_after from Google response - Add static model list: gemini-1.5/2.0/2.5/3.0/3.1 variants - Add GeminiOauthConfig with default credentials path (~/.gemini/) * feat(gemini): implement function calling, generationConfig, and update models - Implement function calling support (functionDeclarations, functionResponse) - Add functionCall SSE parsing and empty stream retry support - Add generationConfig (temperature, maxOutputTokens) - Add thinkingConfig for Gemini 3 and thinking models - Add toolConfig (functionCallingConfig.mode) - Fix .expect() panics with .ok_or_else() - Restrict oauth credentials file permissions to 0600 - Update docs and FEATURE_PARITY.md - Update wizard to current Gemini 3.1 and 2.5 models * fix: address code review issues in gemini-cli OAuth integration - Add cache_read_input_tokens/cache_creation_input_tokens fields (value 0) - Implement manual Debug for OAuthCredential to redact tokens - Fix hardcoded /tmp: use GeminiOauthConfig::default_credentials_path() - Replace emoji output with plain text markers - Propagate Client::builder() errors instead of silent fallback - Use tokio::fs for all file I/O in CredentialManager (was std::fs) - Use if let Some(ref pid) to avoid consuming credential.project_id - Extract uses_cloud_code_api() helper; route by major version (gemini-2+) - Concatenate multiple system messages into systemInstruction - Include functionCall parts in assistant message conversion - Add 401 retry loop with allow_retry flag for auth failures - Remove biased from tokio::select! in OAuth callback handler - Remove hardcoded context_length 1M; vary by model family - Change GOOG_API_CLIENT from Node.js spoof to gl-rust/1.0.0 - Implement list_models() with static model list - Move create_gemini_oauth_provider() before test module (clippy) - Fix 9 additional clippy warnings (collapsible_if, map_or, needless_borrow) - Run cargo fmt * Add dedicated regression tests for Gemini OAuth fixes * style: fix formatting in Gemini OAuth regression tests * feat(gemini-oauth): implement code review v3 refinements - Add force_refresh() for 401 retry (bypass timestamp check) - Standardize Gemini model list across docs, wizard, and provider - Restore gemini-3 check for thinkingConfig - Redact sensitive tokens in GoogleTokenRefreshResponse Debug output - Use dynamic version for GOOG_API_CLIENT - Improve model_metadata() context length heuristics - Use strip_prefix("data:") for safer SSE parsing - Skip re-auth in wizard if keeping existing provider * feat(gemini_oauth): full Cloud Code API integration with project discovery - Register gemini_oauth as a dedicated backend in config/llm.rs (skip registry fallback, preserve backend name, suppress unknown-backend warning) - Fix app.rs credential guard to exclude backends with dedicated configs (gemini_oauth, bedrock) from the provider.is_none() check - Auto-discover Cloud Code project_id via loadCodeAssist when credentials lack it (e.g. created by the original Gemini CLI) - Persist discovered project_id to credentials file for subsequent runs - Add safety settings (BLOCK_NONE), gated behind GEMINI_SAFETY_BLOCK_NONE env - Add thinkingConfig: budget-based for Gemini 2.5, level-based for Gemini 3.x (without includeThoughts to avoid empty responses from reasoning.rs stripping) - Add thought signature injection for Gemini 3.x preview APIs - Add history curation to filter invalid model outputs before re-sending - Add extended generationConfig env vars (topP, topK, seed, penalties, responseMimeType, responseJsonSchema, cachedContent) - Add custom headers support via GEMINI_CLI_CUSTOM_HEADERS - Add API key auth mode (GEMINI_API_KEY + GEMINI_API_KEY_AUTH_MECHANISM) - Add SSE metadata extraction (modelVersion, credits, promptFeedback, groundingMetadata, citationMetadata, cachedContentTokenCount) - Add countTokens API support - Add new models to wizard (gemini-3.1-pro-preview-customtools, gemini-3-pro-preview, gemini-3.1-flash-lite-preview) - Update docs/LLM_PROVIDERS.md with new models and routing rules - Rewrite regression tests with comprehensive coverage (23 unit tests pass) * fix: CI violations — add safety comment on expect, fix fmt - Add '// safety: hardcoded literal' to regex .expect() to satisfy the no-panic-in-prod CI check - Fix cargo fmt whitespace in collapsible if-let chain * fix: address PR review feedback from gemini-code-assist - Fix parse_custom_headers to preserve commas in values by splitting only on commas followed by a header-name:colon pattern (manual scan instead of simple split(',')) - Use matches! macro for backend exclusion check in app.rs - Merge SSE metadata extraction into single pass (was iterating twice) - Replace fragile substring-based context_length with explicit match on known Gemini model IDs via gemini_context_length() - Add missing models to regression test (8 models, not 5) * fix: address Copilot PR review feedback - Fix empty text part for assistant messages with tool calls (curate_contents could drop entire model turn) - Propagate cache_read/creation_input_tokens in complete_with_tools - Log warning on save_credential failure instead of silently ignoring - Fix doc comment to mention underscore in header name pattern - Handle gemini-oauth (hyphen variant) in setup wizard display - Fix docs: thinkingConfig uses thinkingBudget/thinkingLevel, not includeThoughts * fix: add missing allow_always field after staging merge * fix(gemini_oauth): align header parser doc with implementation [skip-regression-check] Update parse_custom_headers doc comments to include underscore in the header-name character class, matching the actual implementation. Also fix formatting from merge. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(gemini_oauth): curate_contents per-part filtering and dead code removal Fix curate_contents to filter invalid parts individually instead of dropping entire model turn sequences. Previously a single empty text part would discard all consecutive model turns including valid functionCall parts, breaking the tool-call flow. Also remove unused MID_STREAM_* constants. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style(gemini_oauth): rustfmt formatting [skip-regression-check] Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(llm): support smart routing cheap model for gemini_oauth backend Add explicit gemini_oauth handling in create_cheap_provider_for_backend() to create a GeminiOauthProvider with the cheap model swapped in. Without this, setting LLM_CHEAP_MODEL with gemini_oauth backend would fail with a confusing "no registry provider config available" error. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add Gemini OAuth env vars to .env.example [skip-regression-check] Document GEMINI_MODEL, GEMINI_CREDENTIALS_PATH, GEMINI_API_KEY, and all extended generation config env vars in the example config file. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: ilblackdragon@gmail.com <ilblackdragon@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… API (nearai#1356) * feat: integrate Gemini CLI OAuth with Cloud Code API - Add gemini_oauth.rs: full OAuth flow with PKCE, token refresh, and Cloud Code project discovery (loadCodeAssist + onboardUser) - Route preview/gemini-3 models through cloudcode-pa.googleapis.com with proper project ID injection in request payload - Trigger OAuth login during onboarding wizard (not first chat message) - Support manual redirect URL paste as fallback (tokio::select race) - Parse 429 rate-limit errors with retry_after from Google response - Add static model list: gemini-1.5/2.0/2.5/3.0/3.1 variants - Add GeminiOauthConfig with default credentials path (~/.gemini/) * feat(gemini): implement function calling, generationConfig, and update models - Implement function calling support (functionDeclarations, functionResponse) - Add functionCall SSE parsing and empty stream retry support - Add generationConfig (temperature, maxOutputTokens) - Add thinkingConfig for Gemini 3 and thinking models - Add toolConfig (functionCallingConfig.mode) - Fix .expect() panics with .ok_or_else() - Restrict oauth credentials file permissions to 0600 - Update docs and FEATURE_PARITY.md - Update wizard to current Gemini 3.1 and 2.5 models * fix: address code review issues in gemini-cli OAuth integration - Add cache_read_input_tokens/cache_creation_input_tokens fields (value 0) - Implement manual Debug for OAuthCredential to redact tokens - Fix hardcoded /tmp: use GeminiOauthConfig::default_credentials_path() - Replace emoji output with plain text markers - Propagate Client::builder() errors instead of silent fallback - Use tokio::fs for all file I/O in CredentialManager (was std::fs) - Use if let Some(ref pid) to avoid consuming credential.project_id - Extract uses_cloud_code_api() helper; route by major version (gemini-2+) - Concatenate multiple system messages into systemInstruction - Include functionCall parts in assistant message conversion - Add 401 retry loop with allow_retry flag for auth failures - Remove biased from tokio::select! in OAuth callback handler - Remove hardcoded context_length 1M; vary by model family - Change GOOG_API_CLIENT from Node.js spoof to gl-rust/1.0.0 - Implement list_models() with static model list - Move create_gemini_oauth_provider() before test module (clippy) - Fix 9 additional clippy warnings (collapsible_if, map_or, needless_borrow) - Run cargo fmt * Add dedicated regression tests for Gemini OAuth fixes * style: fix formatting in Gemini OAuth regression tests * feat(gemini-oauth): implement code review v3 refinements - Add force_refresh() for 401 retry (bypass timestamp check) - Standardize Gemini model list across docs, wizard, and provider - Restore gemini-3 check for thinkingConfig - Redact sensitive tokens in GoogleTokenRefreshResponse Debug output - Use dynamic version for GOOG_API_CLIENT - Improve model_metadata() context length heuristics - Use strip_prefix("data:") for safer SSE parsing - Skip re-auth in wizard if keeping existing provider * feat(gemini_oauth): full Cloud Code API integration with project discovery - Register gemini_oauth as a dedicated backend in config/llm.rs (skip registry fallback, preserve backend name, suppress unknown-backend warning) - Fix app.rs credential guard to exclude backends with dedicated configs (gemini_oauth, bedrock) from the provider.is_none() check - Auto-discover Cloud Code project_id via loadCodeAssist when credentials lack it (e.g. created by the original Gemini CLI) - Persist discovered project_id to credentials file for subsequent runs - Add safety settings (BLOCK_NONE), gated behind GEMINI_SAFETY_BLOCK_NONE env - Add thinkingConfig: budget-based for Gemini 2.5, level-based for Gemini 3.x (without includeThoughts to avoid empty responses from reasoning.rs stripping) - Add thought signature injection for Gemini 3.x preview APIs - Add history curation to filter invalid model outputs before re-sending - Add extended generationConfig env vars (topP, topK, seed, penalties, responseMimeType, responseJsonSchema, cachedContent) - Add custom headers support via GEMINI_CLI_CUSTOM_HEADERS - Add API key auth mode (GEMINI_API_KEY + GEMINI_API_KEY_AUTH_MECHANISM) - Add SSE metadata extraction (modelVersion, credits, promptFeedback, groundingMetadata, citationMetadata, cachedContentTokenCount) - Add countTokens API support - Add new models to wizard (gemini-3.1-pro-preview-customtools, gemini-3-pro-preview, gemini-3.1-flash-lite-preview) - Update docs/LLM_PROVIDERS.md with new models and routing rules - Rewrite regression tests with comprehensive coverage (23 unit tests pass) * fix: CI violations — add safety comment on expect, fix fmt - Add '// safety: hardcoded literal' to regex .expect() to satisfy the no-panic-in-prod CI check - Fix cargo fmt whitespace in collapsible if-let chain * fix: address PR review feedback from gemini-code-assist - Fix parse_custom_headers to preserve commas in values by splitting only on commas followed by a header-name:colon pattern (manual scan instead of simple split(',')) - Use matches! macro for backend exclusion check in app.rs - Merge SSE metadata extraction into single pass (was iterating twice) - Replace fragile substring-based context_length with explicit match on known Gemini model IDs via gemini_context_length() - Add missing models to regression test (8 models, not 5) * fix: address Copilot PR review feedback - Fix empty text part for assistant messages with tool calls (curate_contents could drop entire model turn) - Propagate cache_read/creation_input_tokens in complete_with_tools - Log warning on save_credential failure instead of silently ignoring - Fix doc comment to mention underscore in header name pattern - Handle gemini-oauth (hyphen variant) in setup wizard display - Fix docs: thinkingConfig uses thinkingBudget/thinkingLevel, not includeThoughts * fix: add missing allow_always field after staging merge * fix(gemini_oauth): align header parser doc with implementation [skip-regression-check] Update parse_custom_headers doc comments to include underscore in the header-name character class, matching the actual implementation. Also fix formatting from merge. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(gemini_oauth): curate_contents per-part filtering and dead code removal Fix curate_contents to filter invalid parts individually instead of dropping entire model turn sequences. Previously a single empty text part would discard all consecutive model turns including valid functionCall parts, breaking the tool-call flow. Also remove unused MID_STREAM_* constants. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style(gemini_oauth): rustfmt formatting [skip-regression-check] Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(llm): support smart routing cheap model for gemini_oauth backend Add explicit gemini_oauth handling in create_cheap_provider_for_backend() to create a GeminiOauthProvider with the cheap model swapped in. Without this, setting LLM_CHEAP_MODEL with gemini_oauth backend would fail with a confusing "no registry provider config available" error. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add Gemini OAuth env vars to .env.example [skip-regression-check] Document GEMINI_MODEL, GEMINI_CREDENTIALS_PATH, GEMINI_API_KEY, and all extended generation config env vars in the example config file. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: ilblackdragon@gmail.com <ilblackdragon@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Implements full Google Gemini integration via OAuth (Gemini CLI compatible) with the Cloud Code API.
Backend registration & routing
gemini_oauthas a dedicated LLM backend inconfig/llm.rs— prevents fallback toopenai_compatible, preserves backend name, suppresses unknown-backend warningapp.rscredential guard to exclude backends with dedicated configs (gemini_oauth,bedrock) from theprovider.is_none()early-exit checkCloud Code project discovery
project_idvialoadCodeAssistAPI when credentials lack it (e.g. credentials created by the original Gemini CLI which doesn't persistproject_id)project_idto~/.gemini/oauth_creds.jsonfor subsequent runsFeature parity with official Gemini CLI
BLOCK_NONEfor all harm categories), gated behindGEMINI_SAFETY_BLOCK_NONEenv varthinkingBudget: 8192) for Gemini 2.5, level-based (thinkingLevel: HIGH) for Gemini 3.x. Deliberately does NOT setincludeThoughts: trueto avoid conflicts withreasoning.rsthinking-tag strippingGEMINI_TOP_P,GEMINI_TOP_K,GEMINI_SEED,GEMINI_PRESENCE_PENALTY,GEMINI_FREQUENCY_PENALTY,GEMINI_RESPONSE_MIME_TYPE,GEMINI_RESPONSE_JSON_SCHEMA,GEMINI_CACHED_CONTENTGEMINI_CLI_CUSTOM_HEADERS(format:key:value,key:value)GEMINI_API_KEY+GEMINI_API_KEY_AUTH_MECHANISMModel catalog
gemini-3.1-pro-preview-customtools,gemini-3-pro-preview,gemini-3.1-flash-lite-previewdocs/LLM_PROVIDERS.mdwith new models and routing rulesTests
Test plan
cargo fmt— cleancargo clippy --all --tests -- -D warnings— zero warningscargo test gemini— 23 passed, 0 failedGEMINI_MODEL=gemini-2.5-pro cargo run→ successful chat with Cloud Code APIgemini-2.5-flashmodel worksgemini-3.1-pro-previewmodel works (currently 500/rate-limited on Google side)