feat(gemini): add Google Gemini (OAuth) inference provider - #11250
feat(gemini): add Google Gemini (OAuth) inference provider#11250teknium1 wants to merge 1 commit into
Conversation
Adds 'google-gemini-cli' as a first-class inference provider using Authorization Code + PKCE (S256) OAuth against Google's accounts.google.com, hitting the OpenAI-compatible Gemini endpoint (v1beta/openai) with a Bearer access token. Users sign in with their Google account — no API-key copy-paste. Synthesized from three competing PRs per multi-PR design analysis: - Clean PKCE module structure shaped after #10176 (thanks @sliverp) - Cross-process file lock (fcntl POSIX / msvcrt Windows) with thread-local re-entrancy counter from #10779 (thanks @newarthur) - Rejects #6745's subprocess approach entirely (different paradigm) Improvements over the competing PRs: - Port fallback: if 8085 is taken, bind ephemeral port instead of failing - Preserves refresh_token when Google omits one (correct per Google spec) - Accepts both full redirect URL and bare code in paste fallback - doctor.py health check (neither PR had this) - No regression in _OAUTH_CAPABLE_PROVIDERS (#10779 dropped anthropic/nous) - No bundled unrelated features (#10779 mixed in persona/personality routing) Storage: - ~/.hermes/auth/google_oauth.json (0o600, atomic write via fsync+replace) - Cross-process fcntl/msvcrt lock with 30s timeout - Refresh 5 min before expiry on every request via get_valid_access_token Provider registration (9-point checklist): - auth.py: PROVIDER_REGISTRY entry, aliases (gemini-cli, gemini-oauth), resolve_gemini_oauth_runtime_credentials, get_gemini_oauth_auth_status, get_auth_status() dispatch - models.py: _PROVIDER_MODELS catalog, CANONICAL_PROVIDERS entry, aliases - providers.py: HermesOverlay, ALIASES entries - runtime_provider.py: resolve_runtime_provider() dispatch branch - config.py: OPTIONAL_ENV_VARS for HERMES_GEMINI_CLIENT_ID/_SECRET/_BASE_URL - main.py: _model_flow_google_gemini_cli, select_provider_and_model dispatch - auth_commands.py: add-to-pool handler, _OAUTH_CAPABLE_PROVIDERS - doctor.py: 'Google Gemini OAuth' status line Client ID: Not shipped. Users register a Desktop OAuth client in Google Cloud Console (Generative Language API) and set HERMES_GEMINI_CLIENT_ID in ~/.hermes/.env. Documented in website/docs/integrations/providers.md. Tests: 44 new unit tests covering PKCE S256 roundtrip, credential I/O (permissions + atomic write), cross-process lock, port fallback, paste fallback (URL + bare code), token exchange/refresh, rotation handling, get_valid_access_token refresh semantics, runtime provider dispatch, alias resolution, and regression guards for _OAUTH_CAPABLE_PROVIDERS. Docs: new 'Google Gemini via OAuth' section in providers.md with full walkthrough including GCP Desktop OAuth client registration, and env var table updated in environment-variables.md. Closes partial work in #6745, #10176, #10779 (to be closed with credit once this merges).
|
|
Closing unmerged — pivoting to a full Code Assist integration instead. Follow-up research showed this PR (OAuth against the paid
Reference implementation: jenslys/opencode-gemini-auth (MIT). Elements from this PR that will carry forward into the new one:
New elements coming:
New PR incoming. Thanks again to @sliverp (#10176), @newarthur (#10779), @Protect (#6745) for the groundwork this builds on. |
Summary
Adds
google-gemini-clias a first-class inference provider using Authorization Code + PKCE (S256) OAuth against Google, hitting the OpenAI-compatible Gemini endpoint (v1beta/openai) with a Bearer access token. Users sign in with their Google account — no API key to copy-paste, and the access token is auto-refreshed 5 minutes before expiry on every request.Synthesized from three competing open PRs per the multi-PR design doc. Direction A from the synthesis (OAuth against paid v1beta/openai endpoint). Free-tier Code Assist endpoint (
cloudcode-pa.googleapis.com/v1internal:generateContent) would be a larger follow-up PR introducing a newapi_mode.Credit to the competing PRs
agent/google_oauth.py_credentials_lock()andsave_credentials()Improvements over all three
refresh_tokenwhen Google omits one in the refresh response (correct per Google spec) and rotates when a new one is returned._OAUTH_CAPABLE_PROVIDERS. feat(auth): add Google Gemini CLI OAuth PKCE provider integration #10779 droppedanthropicandnous; we keep all existing entries and appendgoogle-gemini-cli.Architecture
start_oauth_flow()generates PKCE (verifier, S256 challenge), spins up a 127.0.0.1 callback server (with ephemeral-port fallback), opens the browser.exchange_code()swaps the auth code + verifier for access + refresh tokens.save_credentials()writes~/.hermes/auth/google_oauth.jsonatomically (tmp + fsync + replace) with 0o600 perms, holding a cross-process fcntl/msvcrt lock.get_valid_access_token()loads the file, refreshes if within 5 min of expiry, and returns a fresh Bearer.api_mode:
chat_completions(reuses existing path — no new api_mode, norun_agent.pychanges).Provider registration — 9/9 touchpoints covered
auth.py,models.py,providers.py,runtime_provider.py,config.py,main.py,auth_commands.py,doctor.py, plus the newagent/google_oauth.pymodule.Client ID
Not shipped. Users register a Desktop OAuth client in Google Cloud Console themselves and set
HERMES_GEMINI_CLIENT_IDin~/.hermes/.env. Documented inwebsite/docs/integrations/providers.mdwith a full walkthrough. This avoids shipping someone else's OAuth client ID and keeps quota scoped to the user's org.If Nous maintainers want to register an official 'Hermes Agent' desktop client later, drop the ID into
_DEFAULT_CLIENT_IDinagent/google_oauth.py— that's the only code change needed.Test plan
44 new tests in
tests/agent/test_google_oauth.pycovering:google_oauth_client_id_missingget_valid_access_token: cached when fresh, refresh when near expiry, force_refresh, not-logged-in error, preserves refresh_token when Google omits, rotates when Google returns newresolve_runtime_provider, base URL env overridedetermine_api_mode→chat_completions,_OAUTH_CAPABLE_PROVIDERSregression guard, configOPTIONAL_ENV_VARSrun_gemini_oauth_login_purereturns pool-compatible dictAll 44 pass. Plus 243 existing tests in related files still pass — no regressions.
Usage
Closure plan
Once merged, close with credit: #6745, #10176, #10779.