Skip to content

feat(gemini): ship gemini_oauth plugin + fix Code Assist streaming#523

Open
chucky5o wants to merge 1 commit into
mpfaffenberger:mainfrom
chucky5o:feat/gemini-oauth-plugin-and-streaming-fix
Open

feat(gemini): ship gemini_oauth plugin + fix Code Assist streaming#523
chucky5o wants to merge 1 commit into
mpfaffenberger:mainfrom
chucky5o:feat/gemini-oauth-plugin-and-streaming-fix

Conversation

@chucky5o

@chucky5o chucky5o commented Jul 4, 2026

Copy link
Copy Markdown

What & why

model_factory.py already references code_puppy.plugins.gemini_oauth for the
gemini_oauth model type, but the plugin was never shipped — so that model
type is a dangling import today. This PR ships the plugin and fixes two
streaming bugs, letting users run Gemini models through their local
Gemini / Antigravity CLI's
existing OAuth login — no API key.

Google has been rebranding the Gemini CLI tooling as "Antigravity". Both
write credentials to ~/.gemini/, so the same plugin handles either name.

Changes

1. New gemini_oauth plugin (code_puppy/plugins/gemini_oauth/)

Reuses the token the Gemini CLI stores at ~/.gemini/oauth_creds.json and calls
the Code Assist API (cloudcode-pa.googleapis.com). Exposes the three symbols
model_factory expects:

  • GEMINI_OAUTH_CONFIG — Code Assist base URL + API version
  • get_valid_access_token() — reads the CLI creds, auto-refreshes when expired, writes back chmod 600
  • get_project_id() — discovers the managed GCP project via loadCodeAssist and caches it

Client credentials (the public installed-app values from the Gemini CLI) are read
from GEMINI_OAUTH_CLIENT_ID / GEMINI_OAUTH_CLIENT_SECRET env vars — no
secret is committed
. The plugin README explains how to extract them from a
local gemini install to enable auto-refresh.

2. Fix streaming in gemini_code_assist.py

Two bugs broke the streaming path (which is what the agent runtime uses by default):

  • request_stream() was missing the run_context parameter the runtime passes
    TypeError: takes 4 positional arguments but 5 were given
  • the streaming response class didn't inherit from pydantic_ai's StreamedResponse
    ABC → 'StreamedResponse' object has no attribute 'get'. Replaced with
    CodeAssistStreamedResponse, a proper @dataclass subclass implementing
    _get_event_iterator() (mirrors the existing GeminiStreamingResponse).

3. Docs

  • docs/LOCAL_PROVIDERS.md — how to wire Ollama, Claude Code, and
    Gemini/Antigravity from their local CLIs, all switchable via /model.
  • code_puppy/plugins/gemini_oauth/README.md — plugin setup + troubleshooting.

Testing

Verified end-to-end through the code-puppy CLI against a real account:

  • /model antigravity-flash → live generateContent call returns a response
  • token auto-refresh from an expired ~/.gemini/oauth_creds.json works
  • managed project ID discovered via loadCodeAssist and cached
  • non-streaming and streaming paths both exercised

Notes for the maintainer

  • Personal model lists live in ~/.code_puppy/extra_models.json (not committed).
  • If you'd prefer to hardcode the public client credentials rather than use env
    vars, that's a one-line change — I used env vars to keep secret scanners happy.

🤖 Generated with Claude Code

model_factory.py already references code_puppy.plugins.gemini_oauth for
the "gemini_oauth" model type, but the plugin was never shipped. This adds
it, so Gemini models work through the local Gemini/Antigravity CLI's OAuth
login (~/.gemini/oauth_creds.json) with no API key.

The plugin exposes the three symbols model_factory expects:
  - GEMINI_OAUTH_CONFIG (Code Assist base URL + version)
  - get_valid_access_token() — reads ~/.gemini/oauth_creds.json,
    auto-refreshes expired tokens, writes back with chmod 600
  - get_project_id() — discovers the managed GCP project via the
    loadCodeAssist API and caches it

OAuth client credentials (the public installed-app values from the Gemini
CLI) are read from GEMINI_OAUTH_CLIENT_ID / GEMINI_OAUTH_CLIENT_SECRET env
vars rather than hardcoded, so no secret is committed. The README explains
how to extract them from a local gemini install to enable auto-refresh.

Also fixes two bugs in gemini_code_assist.py that broke streaming (the
default path the agent runtime uses):
  - request_stream() was missing the run_context parameter the runtime
    passes, raising "takes 4 positional arguments but 5 were given"
  - the streaming response class didn't inherit from pydantic_ai's
    StreamedResponse ABC, so the runtime hit "'StreamedResponse' object
    has no attribute 'get'". Replaced with CodeAssistStreamedResponse
    (a proper @DataClass subclass implementing _get_event_iterator).

Docs: adds docs/LOCAL_PROVIDERS.md (how to wire Ollama, Claude Code, and
Gemini/Antigravity from their local CLIs) and a plugin README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mpfaffenberger

Copy link
Copy Markdown
Owner

Please make linters + tests pass and remove claude code authorship

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.

2 participants