Skip to content

feat(auth): add Google Antigravity browser OAuth - #70

Open
yan-ad wants to merge 1 commit into
Blankeos:mainfrom
yan-ad:feat/gemini-browser-auth
Open

yan-ad wants to merge 1 commit into
Blankeos:mainfrom
yan-ad:feat/gemini-browser-auth

Conversation

@yan-ad

@yan-ad yan-ad commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add Google Antigravity browser OAuth under /connect, using a generated Google authorization URL followed by a pasted full localhost callback URL.
  • Show open/copy-link controls, callback input, inline validation errors, submission state, and explicit cancellation. Keep the API-key option available and retain internal provider ID google for config/credential compatibility.
  • Validate callback origin/path/state, use PKCE, exchange and persist credentials, discover/onboard a managed project, and refresh credentials before outbound generations (including subagents).
  • Add the Antigravity Gemini transport with streaming text/reasoning/tool calls, image inputs, usage/finish handling, bounded stream reads, cancellation, and persisted thought-signature replay across tool loops and later turns.
  • Rename provider display labels to Google Antigravity, including old cached catalog/snapshot views, and expose OAuth-compatible Gemini model IDs using existing catalog metadata.
  • Add terminal ACP login support and document the flow and unofficial-service/account-eligibility caveat.

OAuth client configuration

No Google OAuth client ID or secret is bundled, persisted in config, or compiled into the binary. Set CRABCODE_ANTIGRAVITY_CLIENT_ID and CRABCODE_ANTIGRAVITY_CLIENT_SECRET in the runtime environment. The OAuth client must be authorized for the requested Google scopes/service and use http://localhost:51121/oauth-callback as its redirect URI. An arbitrary new Google Cloud OAuth client does not guarantee Antigravity access. Existing refresh tokens remain bound to their original client.

Missing/invalid settings fail with setup instructions without echoing values. Unit tests inject fake clients and do not mutate global environment variables.

Validation

  • 1,896 unit tests and 7 ACP integration tests passed (3 pre-existing ignored tests).

  • cargo test --offline -- --test-threads=1

  • cargo build --offline --bin crabcode

  • cargo fmt --all -- --check

  • bash scripts/check-aisdk-boundary.sh

  • git diff --check

OAuth, refresh, project onboarding, HTTP/SSE, signed-history, dialog, and cancellation tests use local fixtures; no Google token exchange or account authorization was performed during development. Live Google/Antigravity account eligibility and service availability still require a manual smoke test.

Two existing test fixtures were made environment-independent while running the suite: the edit-rendering fixture now uses the current checkout path, and the ACP stdio fixture explicitly avoids live Ollama discovery.

Manual smoke test

  1. Run /connect → Google Antigravity → Gemini via Antigravity (browser).
  2. Open/copy the generated authorization link and authorize an eligible Google account.
  3. Copy the full http://localhost:51121/oauth-callback?... URL from the address bar even if the localhost page cannot load.
  4. Paste into the callback input and submit; confirm Gemini models are selectable and a text/tool turn works.
  5. Confirm invalid state is rejected inline and Esc cancels without saving credentials.

This integration uses an unofficial Antigravity transport; Google account eligibility, service terms, and restrictions apply.

@Blankeos

Blankeos commented Nov 7, 2026 •

Copy link
Copy Markdown
Owner

feat/gemini-browser-auth → main
Model: claude-sonnet-5-5, effort unknown

Confidence: 3/5

The code is careful and well tested, and the offline paths I could exercise behave as described. It does not merge cleanly into current main. The live Google/Antigravity flow cannot be verified, and the integration depends on a reverse-engineered third-party client.

Checks run (worktree of the PR head, separate target dir):

  • cargo fmt --check: clean.
  • cargo build: OK.
  • cargo test -- --test-threads=1 (whole suite): 1896 unit tests passed, 3 ignored, and 7 ACP integration tests passed.
  • cargo test with default parallelism: 2 unit tests fail, model::discovery::...custom_endpoint_failure_preserves_manual_models_and_is_cached and ui::...test_finalize_prefers_billed_output_over_sample_estimate. Both pass on their own. On unmodified origin/main the same two fail in parallel, plus test_edit_tool_renders_codex_style_diff_summary, which hardcodes /Users/carlo/... and which this PR fixes. So these are existing flaky tests, not caused by the PR.
  • cargo clippy --all-targets: 315 diagnostics in total. 3 land on lines this PR adds:
    • src/app.rs:262 enum_variant_names (GoogleMethodSelection joins the *Selection variants). It appears twice, for the debug and test builds.
    • src/agent/subagent.rs:1137 needless_borrow.
      All three are cosmetic.
  • Mergeable with main: no, conflicts. git merge-tree shows content conflicts in src/app.rs (2 hunks) and src/views/provider_oauth_flow.rs (6 hunks). The branch is based on 484c5b8 (feat(skills): add skill info view, mention insertion, and path-free catalog #68), and main has moved on, probably with the clicked-URL copy-menu changes. I did not resolve them. src/command/handlers.rs, src/main.rs and src/ui/components/chat.rs auto-merge.
  • Overlap with other open PRs:
  • Exercised the changed behavior with a real binary. I used a temp XDG_*, CRABCODE_TEST_MODE=1, a scratch cwd, and a stub open so no browser launched. I drove crabcode acp --login through a pty with expect:
    1. With no client env set, choosing google, then 1, gives Error: Set CRABCODE_ANTIGRAVITY_CLIENT_ID to a valid OAuth client value before connecting Google Antigravity. The value is not echoed.
    2. With fake CRABCODE_ANTIGRAVITY_CLIENT_ID/SECRET, it prints the unofficial-integration warning and a Google URL with PKCE S256, state, redirect http://localhost:51121/oauth-callback, access_type=offline and prompt=consent. It asks for the hidden callback. Pasting a callback with the wrong state gives OAuth login failed; retry provider login and nothing is written to auth.json.
  • Screenshots: not captured. The sandbox denied launching a Ghostty window, so I didn't try to work around it. The TUI /connect dialog states (open/copy link, inline errors, submitting) also need key input, so they are unreviewed visually.

Confidence bumps (can't do myself):

  • Live smoke test with an eligible Google account and the real Antigravity OAuth client (no credentials, and it is an unofficial service). Covers token exchange, loadCodeAssist/onboardUser project discovery, SSE streaming, and thought-signature replay against the real backend → 4/5.
  • Screenshot or visual pass of the /connect → Google Antigravity dialog in a real terminal → +0.25.
  • A rebase onto current main with the suite re-run → 4/5.

Summary

Adds a Google Antigravity browser OAuth login to /connect, crabcode acp --login and the README:

  • Login: PKCE and state are generated. The user opens or copies the link, then pastes the full localhost:51121 callback URL, which is validated for origin, path, state, duplicates and fragment. The token exchange runs, and a managed project is discovered or onboarded.
  • Storage: credentials are stored in the existing google auth entry, with a new optional projectId field that is backward compatible.
  • Transport: a new Antigravity Gemini transport (src/llm/antigravity.rs, about 1.3k lines) supports streaming text, reasoning and tool calls, image input, bounded stream reads, cancellation, and persisted thought-signature replay.
  • Refresh and cancel: credentials refresh before requests, including subagents. Cancellation is added around request-config preparation.
  • Labels and models: provider labels are renamed to "Google Antigravity", and an OAuth-only model list is exposed.

Positives:

  • No client id or secret is bundled. They come from env vars, and errors don't echo values.
  • Responses and callbacks are size-capped, with redirects disabled.
  • Refresh runs under a lock.
  • Signed replay parts are stripped when switching to other providers, and there is a test for it.
  • Test coverage is good and uses local fixtures.

Map

flowchart TD
  A[app.rs + views/provider_oauth_flow.rs<br/>/connect dialog, paste callback, cancel] --> B[auth/antigravity_oauth.rs<br/>PKCE, token, refresh, project onboarding]
  L[acp/login.rs<br/>terminal login] --> B
  B --> P[persistence/auth.rs<br/>OAuth projectId]
  C[llm/client.rs<br/>route google+OAuth, replay filtering] --> B
  C --> T[llm/antigravity.rs<br/>Gemini SSE transport]
  S[agent/subagent.rs] --> T
  M[model/discovery.rs + extensions/antigravity.rs<br/>labels, OAuth model list] --> C
  X[README, tests/acp_stdio.rs, chat.rs test fixes]
Loading

Start with auth/antigravity_oauth.rs, then llm/client.rs, then app.rs.

Regressions

  • Merge conflicts. See above; the branch needs a rebase on main (app.rs, provider_oauth_flow.rs).
  • Relabel affects API-key users. Any google provider is now called "Google Antigravity" everywhere: /connect, the model picker, the discovery cache and get_provider_name. That includes people who only use a normal Gemini API key against the official API. This is misleading, and the label should apply only when the stored auth is OAuth. The dialog code already branches on uses_oauth(), but rename_provider, get_provider_name and the cache view do not.
  • Hardcoded client fingerprint. user_agent() is a fixed string with os_type=darwin; arch=arm64; cl=1000335682 and version 2.24.0, sent from every OS. It is impersonating the Antigravity desktop client. Wherever it runs, this is the sort of thing that gets accounts flagged. Worth a conscious maintainer decision.
  • Practical usability and policy. The feature works only if the user supplies the Antigravity app's own OAuth client id and secret via env vars. A new Google Cloud client won't work, as the PR says. In effect users have to extract another vendor's client credentials. The unofficial-service caveat is documented in the README and printed at login, but the maintainer should decide whether to ship this in the main binary.
  • Wider scope than a login. Beyond the login it touches llm/client.rs, subagent.rs, discovery and cache normalisation (discovery.rs), the chat message model and ProviderReplay persistence (a new provider_replay message part), and two small test-fixture fixes. Each is reasonable, but the PR is about 4k lines and hard to review. Splitting it into OAuth, transport and UI would help.
  • acp login bails with a generic OAuth login failed for all failure causes, and pasted callback validation errors are hidden in the CLI path. Inline errors do exist in the TUI path. Minor.
  • The only automated coverage for the TUI dialog is state-level unit tests; I could not look at it rendered.

Migrations

Not needed. projectId is optional, with a serde default and skip-if-none, so existing auth.json files load unchanged. A legacy file round-trips without the key, and there is a test for this.

Before merging

@Blankeos

Blankeos commented Nov 7, 2026 •

Copy link
Copy Markdown
Owner

Thanks for the work here, but I might not be planning to merge this one. The Antigravity OAuth flow relies on an unofficial Google service and on Antigravity's own OAuth client, so it may go against Google's terms of service. I'd rather not ship that in crabcode.

I'll think about it more until there's probably a solution that won't go against Google's ToS changes. Have you checked out https://github.com/rynfar/meridian btw? (I have a branch in crabcode for it, feel free to check)

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