Skip to content

feat(gemini_oauth): full Gemini CLI OAuth integration with Cloud Code API - #1356

Merged
ilblackdragon merged 20 commits into
nearai:stagingfrom
Mffff4:feat/gemini-cli-oauth
Mar 22, 2026
Merged

ilblackdragon merged 20 commits into
nearai:stagingfrom
Mffff4:feat/gemini-cli-oauth

Conversation

@Mffff4

@Mffff4 Mffff4 commented Mar 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Implements full Google Gemini integration via OAuth (Gemini CLI compatible) with the Cloud Code API.

Backend registration & routing

  • Register gemini_oauth as a dedicated LLM backend in config/llm.rs — prevents fallback to openai_compatible, preserves backend name, suppresses unknown-backend warning
  • Fix app.rs credential guard to exclude backends with dedicated configs (gemini_oauth, bedrock) from the provider.is_none() early-exit check

Cloud Code project discovery

  • Auto-discover Cloud Code project_id via loadCodeAssist API when credentials lack it (e.g. credentials created by the original Gemini CLI which doesn't persist project_id)
  • Persist discovered project_id to ~/.gemini/oauth_creds.json for subsequent runs
  • Discover on both initial load and token refresh paths

Feature parity with official Gemini CLI

  • Safety settings (BLOCK_NONE for all harm categories), gated behind GEMINI_SAFETY_BLOCK_NONE env var
  • ThinkingConfig: budget-based (thinkingBudget: 8192) for Gemini 2.5, level-based (thinkingLevel: HIGH) for Gemini 3.x. Deliberately does NOT set includeThoughts: true to avoid conflicts with reasoning.rs thinking-tag stripping
  • Thought signature injection for Gemini 3.x preview APIs (prevents 400 errors)
  • History curation — filters invalid model outputs before re-sending
  • Extended generationConfig via env vars: GEMINI_TOP_P, GEMINI_TOP_K, GEMINI_SEED, GEMINI_PRESENCE_PENALTY, GEMINI_FREQUENCY_PENALTY, GEMINI_RESPONSE_MIME_TYPE, GEMINI_RESPONSE_JSON_SCHEMA, GEMINI_CACHED_CONTENT
  • Custom headers via GEMINI_CLI_CUSTOM_HEADERS (format: key:value,key:value)
  • API key auth mode via GEMINI_API_KEY + GEMINI_API_KEY_AUTH_MECHANISM
  • SSE metadata extraction: modelVersion, credits, promptFeedback, groundingMetadata, citationMetadata, cachedContentTokenCount
  • countTokens API support

Model catalog

  • Add new models to setup 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

Tests

  • 23 unit tests pass (thinking config, stop sequences, Cloud Code routing, PKCE, callback parsing, response parsing, etc.)
  • Rewritten regression tests with comprehensive coverage

Test plan

  • cargo fmt — clean
  • cargo clippy --all --tests -- -D warnings — zero warnings
  • cargo test gemini — 23 passed, 0 failed
  • Manual E2E: GEMINI_MODEL=gemini-2.5-pro cargo run → successful chat with Cloud Code API
  • Verify gemini-2.5-flash model works
  • Verify gemini-3.1-pro-preview model works (currently 500/rate-limited on Google side)

Mffff4 added 9 commits March 2, 2026 23:50
- 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)
@github-actions github-actions Bot added scope: llm LLM integration scope: setup Onboarding / setup scope: docs Documentation size: XL 500+ changed lines risk: high Safety, secrets, auth, or critical infrastructure contributor: regular 2-5 merged PRs labels Mar 18, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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

  • Gemini CLI OAuth Integration: Implemented full Google Gemini integration via OAuth, making it compatible with the Gemini CLI and Cloud Code API. This includes backend registration, credential management, and project ID discovery.
  • Cloud Code Project Discovery: Added automatic discovery of the Cloud Code project_id via the loadCodeAssist API, persisting it to ~/.gemini/oauth_creds.json for subsequent runs and handling both initial load and token refresh paths.
  • Feature Parity with Official Gemini CLI: Achieved feature parity with the official Gemini CLI, incorporating safety settings (BLOCK_NONE), budget-based (thinkingBudget) and level-based (thinkingLevel) thinking configurations, thought signature injection for Gemini 3.x, history curation, extended generationConfig via environment variables, custom headers, API key authentication mode, SSE metadata extraction, and countTokens API support.
  • Model Catalog Expansion: Updated the setup wizard and documentation to include new Gemini models such as gemini-3.1-pro-preview-customtools, gemini-3-pro-preview, and gemini-3.1-flash-lite-preview, along with their routing rules.
  • Robust Testing: Ensured stability and correctness with 23 passing unit tests covering thinking config, stop sequences, Cloud Code routing, PKCE, callback parsing, and response parsing, alongside rewritten regression tests for comprehensive coverage.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

- 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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Comment thread src/llm/gemini_oauth.rs Outdated
_ => return headers,
};
// Split on commas followed by a header-key pattern (word chars + colon)
for entry in env_val.split(',') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

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
  1. 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.

Comment thread src/app.rs Outdated
Comment on lines +689 to +692
if self.config.llm.backend != "nearai"
&& self.config.llm.backend != "gemini_oauth"
&& self.config.llm.backend != "bedrock"
&& self.config.llm.provider.is_none()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This conditional is getting long. Using a matches! macro would make it more concise and readable.

Suggested change
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()

Comment thread src/llm/gemini_oauth.rs Outdated
Comment on lines +1381 to +1382
for line in body_str.lines() {
let Some(json_str) = line.strip_prefix("data:") else {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The SSE response body is being iterated over twice: once to collect the main content and a second time to collect metadata. This is inefficient as it requires re-parsing the entire response. All data (content and metadata) can be extracted in a single pass over the SSE stream.

Comment thread src/llm/gemini_oauth.rs Outdated
Comment on lines +1984 to +1990
Some(2_000_000)
} else if self.config.model.contains("flash") {
Some(1_000_000)
} else {
None
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

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
  1. The logic for determining context_length relies 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.

Comment thread tests/gemini_oauth_regression.rs Outdated
Comment on lines +65 to +74
/// 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",
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

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-customtools
  • gemini-3-pro-preview
  • gemini-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
  1. 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)
@henrypark133
henrypark133 requested a review from Copilot March 18, 2026 21:31
@henrypark133

Copy link
Copy Markdown
Collaborator

Please change the target branch to staging. Thanks!

@Mffff4
Mffff4 changed the base branch from main to staging March 18, 2026 21:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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_oauth as 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.

Comment thread docs/LLM_PROVIDERS.md Outdated
|---|---|---|
| Function calling | ✅ | `functionDeclarations` / `functionCall` / `functionResponse` |
| `generationConfig` | ✅ | `temperature`, `maxOutputTokens` passed from request |
| `thinkingConfig` | ✅ | `includeThoughts: true` for `gemini-3`/`thinking` models |
Comment thread FEATURE_PARITY.md Outdated
| 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 |
Comment thread src/llm/gemini_oauth.rs
Comment on lines +1619 to +1624
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": {
Comment thread src/llm/gemini_oauth.rs Outdated
Comment on lines +2083 to +2084
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
Comment thread src/llm/gemini_oauth.rs Outdated
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;
Comment thread src/llm/gemini_oauth.rs
Comment on lines +90 to +113
/// 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();
Comment thread src/setup/wizard.rs
Comment on lines +1038 to +1051
match current.as_str() {
"nearai" => "NEAR AI".to_string(),
"gemini_oauth" => "Gemini API (OAuth)".to_string(),
_ => {
if let Some(def) = registry.find(&current) {
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
@github-actions github-actions Bot added the scope: dependencies Dependency updates label Mar 19, 2026
@github-actions github-actions Bot added the scope: agent Agent core (agent loop, router, scheduler) label Mar 19, 2026
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>
ilblackdragon and others added 3 commits March 21, 2026 15:37
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>
@ilblackdragon
ilblackdragon merged commit 8638895 into nearai:staging Mar 22, 2026
14 checks passed
@claude

claude Bot commented Mar 22, 2026

Copy link
Copy Markdown

Code review

Found 8 issues:

  1. [CRITICAL:80] Unbounded clones in SSE parsing hot path clones entire JSON objects for every SSE event, causing O(N) allocations and memory pressure on long-running LLM calls

https://github.com/anthropics/ironclaw/blob/8638895879047fc900ee85720c0cafc6859c84d5/src/llm/gemini_oauth.rs#L1383-L1390

  1. [HIGH:85] Unbounded polling loop in OAuth project provisioning uses fixed 3-second sleep with max 15 attempts (45s total) and no timeout wrapper — polling hitting limit silently completes without project ID, leaving system degraded

https://github.com/anthropics/ironclaw/blob/8638895879047fc900ee85720c0cafc6859c84d5/src/llm/gemini_oauth.rs#L753-L793

  1. [HIGH:82] LRO polling loop condition uses incorrect default value: unwrap_or(true) should be unwrap_or(false) — missing "done" field incorrectly treated as operation complete, causes premature loop exit

https://github.com/anthropics/ironclaw/blob/8638895879047fc900ee85720c0cafc6859c84d5/src/llm/gemini_oauth.rs#L754-L758

  1. [HIGH:75] UTF-8 string slicing vulnerability in parse_custom_headers() — byte-index slicing on trimmed strings will panic if colon appears in multi-byte UTF-8 character boundaries

https://github.com/anthropics/ironclaw/blob/8638895879047fc900ee85720c0cafc6859c84d5/src/llm/gemini_oauth.rs#L111-L112

  1. [HIGH:75] std::sync::Mutex in hot async path violates tokio guidelines — synchronous lock acquisition inside async code after I/O blocks runtime thread; should use tokio::sync::RwLock

https://github.com/anthropics/ironclaw/blob/8638895879047fc900ee85720c0cafc6859c84d5/src/llm/gemini_oauth.rs#L923

  1. [MEDIUM:65] HTTP client timeout semantics inconsistent — 300s global timeout on all requests (appropriate for SSE streaming) but refresh_token() has separate 30s timeout and perform_oauth_login() has no timeout wrapping the loop

https://github.com/anthropics/ironclaw/blob/8638895879047fc900ee85720c0cafc6859c84d5/src/llm/gemini_oauth.rs#L930

  1. [MEDIUM:55] Unnecessary credential clones in token refresh path — credential.clone() at callsite, then cloned again inside refresh_token() when rebuilding object; pass by reference instead

https://github.com/anthropics/ironclaw/blob/8638895879047fc900ee85720c0cafc6859c84d5/src/llm/gemini_oauth.rs#L412

  1. [MEDIUM:50] Suspicious poisoned lock recovery pattern — unwrap_or_else(|e| e.into_inner()) masks state corruption issues; should use explicit error handling or document why poisoning is acceptable

https://github.com/anthropics/ironclaw/blob/8638895879047fc900ee85720c0cafc6859c84d5/src/llm/gemini_oauth.rs#L949

bkutasi pushed a commit to bkutasi/ironclaw that referenced this pull request Mar 28, 2026
… 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>
drchirag1991 pushed a commit to drchirag1991/ironclaw that referenced this pull request Apr 8, 2026
… 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: regular 2-5 merged PRs risk: high Safety, secrets, auth, or critical infrastructure scope: agent Agent core (agent loop, router, scheduler) scope: dependencies Dependency updates scope: docs Documentation scope: llm LLM integration scope: setup Onboarding / setup size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants