From b0bbf6034ac01d3348a33d5fbf6affec6bcf412e Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 25 May 2026 13:24:09 +0400 Subject: [PATCH] =?UTF-8?q?docs:=20Antigravity=20technical=20reference=20?= =?UTF-8?q?=E2=80=94=20REST=20API,=20model=20IDs,=20auth,=20debugging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ANTIGRAVITY.md | 336 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 docs/ANTIGRAVITY.md diff --git a/docs/ANTIGRAVITY.md b/docs/ANTIGRAVITY.md new file mode 100644 index 0000000..136148d --- /dev/null +++ b/docs/ANTIGRAVITY.md @@ -0,0 +1,336 @@ +# Antigravity (Google CloudCode) — Technical Reference + +Everything needed to understand, maintain, and debug the Antigravity OAuth provider integration in Codex Launcher. + +--- + +## 1. What Is Antigravity? + +Antigravity is Google's internal codename for **Google CloudCode** — a cloud-based AI coding agent powered by Gemini and other models. The CLI tool (`agy`) is a native Go binary that uses gRPC to communicate with Google's CloudCode backend. + +- **Official CLI binary**: `~/.local/bin/agy-core` (ELF x86-64 Go binary, ~183MB) +- **Wrapper script**: `~/.local/bin/agy` (Python, manages provider switching) +- **CLI settings**: `~/.gemini/antigravity-cli/settings.json` +- **Provider state**: `~/.gemini/antigravity-cli/agy_provider.json` + +--- + +## 2. Two API Protocols — REST vs gRPC + +### 2.1 What the agy CLI uses (gRPC) + +The native `agy-core` binary uses **gRPC** to communicate with the CloudCode backend: + +- **Service**: `google.internal.cloud.code.v1internal.PredictionService` +- **Methods**: + - `GenerateContent` — main inference + - `FetchAvailableModels` — list available models + - `CountTokens` — token counting + - `RetrieveUserQuota` — quota check +- **Other services**: `CloudCode`, `JetskiService` (settings, plugins, etc.) +- **Proto files**: `google/internal/cloud/code/v1internal/prediction_service.proto`, `cloudcode.proto` +- **Model IDs in gRPC**: Display names like `"Gemini 3.5 Flash (High)"` — verified from `settings.json` + +### 2.2 What our proxy uses (REST) + +Our Codex Launcher proxy does NOT use gRPC. It uses the **REST API** that the CloudCode backend also exposes: + +- **Endpoint path**: `v1internal:generateContent` (non-streaming) / `v1internal:streamGenerateContent?alt=sse` (streaming SSE) +- **This is NOT the standard Gemini REST API** — it's the CloudCode-internal REST gateway +- **Model IDs in REST**: Slug-style IDs like `gemini-3-flash` — NOT display names +- **The REST API is more limited** — fewer model variants available than gRPC + +### 2.3 Why not gRPC? + +The agy binary uses gRPC with protobuf serialization. Using gRPC from the proxy would require: +- Maintaining proto definitions (compiled from the binary) +- More complex streaming +- The `grpcio` Python library (not installed by default) + +The REST API works well enough for our use case. + +--- + +## 3. Endpoints + +The proxy tries these endpoints in order for Antigravity: + +``` +1. https://daily-cloudcode-pa.sandbox.googleapis.com (primary) +2. https://autopush-cloudcode-pa.sandbox.googleapis.com (fallback) +3. https://cloudcode-pa.googleapis.com (production fallback) +``` + +For regular Gemini CLI OAuth, only `cloudcode-pa.googleapis.com` is used. + +--- + +## 4. Authentication + +### 4.1 OAuth Flow + +- **Client ID**: `REDACTED_ANTIGRAVITY_CLIENT_ID_2` + (also `REDACTED_ANTIGRAVITY_CLIENT_ID`) +- **OAuth callback**: `https://antigravity.google/oauth-callback` +- **Token storage**: `~/.cache/codex-proxy/google-antigravity-oauth-token.json` +- **Token refresh**: via `https://oauth2.googleapis.com/token` +- **Scopes**: `email profile openid cloud-platform cclog experimentsandconfigs userinfo.email userinfo.profile` +- **Note**: The token does NOT have `auth/aicode` scope — it uses `cloud-platform` instead + +### 4.2 Multi-Account Support + +- `GoogleAccountPool("antigravity")` manages multiple Google accounts +- Token files: `google-antigravity-oauth-token.json`, `google-antigravity-oauth-token-2.json`, etc. +- Round-robin rotation across accounts + +--- + +## 5. Request Format + +### 5.1 REST Request Wrapper + +The proxy wraps the Gemini-format request body in an outer envelope: + +```json +{ + "project": "", + "model": "", + "requestType": "agent", + "userAgent": "antigravity", + "requestId": "agent-", + "request": { + "contents": [...], + "systemInstruction": {...}, + "generationConfig": {...}, + "tools": [...] + } +} +``` + +### 5.2 Required Headers + +``` +Content-Type: application/json +Authorization: Bearer +User-Agent: antigravity/ darwin/arm64 +``` + +The User-Agent version is auto-fetched from: +- `https://antigravity-auto-updater-974169037036.us-central1.run.app` +- Fallback: `https://antigravity.google/changelog` +- Cached in `~/.cache/codex-proxy/antigravity-version.json` +- Default: `1.18.3` + +--- + +## 6. Model ID Mapping (CRITICAL) + +### 6.1 The Problem + +The agy CLI shows models with display names: +- `Gemini 3.5 Flash (High)` +- `Claude Sonnet 4.6 (Thinking)` + +But the **REST API only accepts slug IDs**: +- `gemini-3-flash` +- `claude-sonnet-4-6` + +Sending display names to the REST API returns **HTTP 404 "Requested entity was not found"**. + +### 6.2 Verified Working Model IDs + +All tested with live API calls to `daily-cloudcode-pa.sandbox.googleapis.com/v1internal:generateContent` on 2026-05-25: + +| Display Name (agy CLI / GUI) | REST API Model ID | Status | +|---|---|---| +| Gemini 3.5 Flash (High) | `gemini-3-flash` | OK | +| Gemini 3.5 Flash (Medium) | `gemini-3-flash` | OK | +| Gemini 3.5 Flash (Low) | `gemini-3.5-flash-low` | OK | +| Gemini 3.1 Pro (High) | `gemini-3.1-pro-low` | OK (only low tier works via REST) | +| Gemini 3.1 Pro (Low) | `gemini-3.1-pro-low` | OK | +| Claude Sonnet 4.6 (Thinking) | `claude-sonnet-4-6` | OK | +| Claude Opus 4.6 (Thinking) | `claude-opus-4-6-thinking` | OK | +| GPT-OSS 120B (Medium) | `gpt-oss-120b-medium` | OK | +| Gemini 2.5 Flash | `gemini-2.5-flash` | OK | +| Gemini 2.5 Flash Lite | `gemini-2.5-flash-lite` | OK | +| Gemini 2.5 Pro | `gemini-2.5-pro` | 503 (exists, no capacity) | + +### 6.3 Models That Return 404 via REST + +These exist in gRPC but NOT in the REST API: + +``` +gemini-3-flash-high, gemini-3-flash-medium, gemini-3-flash-low +gemini-3.5-flash, gemini-3.5-flash-high, gemini-3.5-flash-medium +gemini-3.1-pro-high (400, not 404, but doesn't work) +gemini-3-pro, gemini-3-pro-high, gemini-3-pro-low (500) +gemini-3.1-flash, gemini-3.1-flash-high +claude-sonnet-4, claude-sonnet-4-5, claude-sonnet-4-6-thinking +claude-opus-4, claude-opus-4-5 +claude-haiku-4-5 +gpt-oss-120b, gpt-oss-120b-maas, gpt-oss-20b-maas +``` + +### 6.4 How the Mapping Works + +1. GUI shows display names (matching agy CLI): `Gemini 3.5 Flash (High)` +2. Codex CLI sends whatever model ID the user selected +3. Proxy `alias_map` translates: `"Gemini 3.5 Flash (High)" → "gemini-3-flash"` +4. Proxy sends REST request with `"model": "gemini-3-flash"` + +The alias map is in `_handle_gemini_oauth()` around line 4316 of `translate-proxy.py`. + +--- + +## 7. Response Format + +### 7.1 Non-Streaming + +```json +{ + "response": { + "candidates": [{ + "content": { + "role": "model", + "parts": [{"text": "..."}] + }, + "finishReason": "STOP" + }] + } +} +``` + +### 7.2 Streaming (SSE) + +Content-Type: `text/event-stream` + +Each SSE event contains a JSON chunk with the same structure. The proxy converts these to OpenAI Responses API format for Codex CLI. + +--- + +## 8. Context Sizes + +```python +"Gemini 3.5 Flash": 1000000, "Gemini 3.1 Pro": 2000000, +"gemini-3-flash": 1000000, "gemini-3.1-pro-low": 2000000, +"gemini-3.5-flash-low": 1000000, +"Claude Sonnet 4.6": 200000, "Claude Opus 4.6": 200000, +"claude-sonnet-4-6": 200000, "claude-opus-4-6-thinking": 200000, +"GPT-OSS 120B": 128000, "gpt-oss-120b-medium": 128000, +"gemini-2.5-flash": 1000000, "gemini-2.5-pro": 2000000, +``` + +--- + +## 9. Key Proxy Code Locations + +| Component | File | Line (approx) | +|---|---|---| +| Antigravity version | translate-proxy.py | 287-288 | +| Version fetcher | translate-proxy.py | 705-748 | +| Model alias map | translate-proxy.py | ~4316 | +| REST request building | translate-proxy.py | ~4563-4602 | +| Endpoint fallback loop | translate-proxy.py | ~4610 | +| SSE streaming handler | translate-proxy.py | `_forward_gemini_sse()` | +| Auto-continue for MAX_TOKENS | translate-proxy.py | `_auto_continue_gemini()` | +| OAuth token refresh | translate-proxy.py | `_refresh_oauth_token_for()` | +| Google account pool | translate-proxy.py | `_google_antigravity_pool` | +| GUI preset models | codex-launcher-gui | ~358 | +| GUI static model list | codex-launcher-gui | ~760 `_ANTIGRAVITY_MODELS` | +| GUI fetch_models shortcut | codex-launcher-gui | ~770 `fetch_models_for_endpoint()` | + +--- + +## 10. Debugging + +### 10.1 Debug Logs + +- **Proxy stderr**: Shows model mapping, request details, errors +- **400 error dump**: `~/.cache/codex-proxy/gemini-last-400-request.json` +- **Long context dump**: `~/.cache/codex-proxy/gemini-long-ctx-.json` + +### 10.2 Quick API Test + +```bash +TOKEN=$(python3 -c "import json; print(json.load(open('$HOME/.cache/codex-proxy/google-antigravity-oauth-token.json'))['access_token'])") + +curl -s "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:generateContent" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -H "User-Agent: antigravity/2.0.1 darwin/arm64" \ + -d '{ + "project": "voltaic-hangout-z1qhf", + "model": "gemini-3-flash", + "requestType": "agent", + "userAgent": "antigravity", + "requestId": "test-123", + "request": { + "contents": [{"role": "user", "parts": [{"text": "say hi"}]}] + } + }' +``` + +### 10.3 Token Info + +```bash +TOKEN=$(python3 -c "import json; print(json.load(open('$HOME/.cache/codex-proxy/google-antigravity-oauth-token.json'))['access_token'])") +curl -s "https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=$TOKEN" | python3 -m json.tool +``` + +### 10.4 Common Errors + +| Error | Cause | Fix | +|---|---|---| +| 404 "Requested entity was not found" | Wrong model ID (display name instead of slug) | Check alias_map | +| 404 on `/v1/models` | Antigravity has no models REST endpoint | Proxy returns static list | +| 404 on POST /responses | Codex CLI routing issue, not Antigravity | Check proxy is running | +| 503 "No capacity" | Model exists but overloaded | Try another model or endpoint | +| 500 "Unknown Error" | Model ID exists but broken on server | Known for gemini-3-pro-low | +| PERMISSION_DENIED (gRPC) | Token lacks scope or empty request body | Use REST API instead | + +--- + +## 11. Version History (Antigravity-specific) + +| Version | Date | Change | +|---|---|---| +| v3.10.3 | 2026-05-25 | **Fix 404**: Verified REST model IDs, display→slug mapping | +| v3.10.2 | 2026-05-25 | Wrong fix: tried display names (didn't work) | +| v3.10.0 | 2026-05-25 | Provider model editor, static Antigravity model list | +| v3.9.9 | 2026-05-25 | Refreshed Antigravity models (slugs were wrong) | +| v3.3.0 | Earlier | Initial Antigravity OAuth + tool calls + SSE streaming | + +--- + +## 12. Testing a New Model ID + +If new models appear in the agy CLI, verify them against the REST API before adding: + +```python +# Test a candidate model ID +import urllib.request, json, os + +token = json.load(open(os.path.expanduser("~/.cache/codex-proxy/google-antigravity-oauth-token.json")))["access_token"] +wrapped = { + "project": "voltaic-hangout-z1qhf", "model": "NEW-MODEL-ID", + "requestType": "agent", "userAgent": "antigravity", + "requestId": "test-123", + "request": {"contents": [{"role": "user", "parts": [{"text": "say hi"}]}]}, +} +req = urllib.request.Request( + "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:generateContent", + data=json.dumps(wrapped).encode(), + headers={"Content-Type": "application/json", "Authorization": f"Bearer {token}", "User-Agent": "antigravity/2.0.1 darwin/arm64"}, +) +try: + resp = urllib.request.urlopen(req, timeout=15) + print("OK:", resp.read().decode()[:200]) +except urllib.error.HTTPError as e: + print(f"{e.code}:", e.read().decode()[:200]) +``` + +Then update: +1. `alias_map` in `translate-proxy.py` — add display name → REST slug mapping +2. `_ANTIGRAVITY_MODELS` in `codex-launcher-gui` — add display name to list +3. Preset in `codex-launcher-gui` — add display name to `"Google Antigravity (OAuth)"` models +4. Context sizes in `translate-proxy.py` — add model ID to `_MODEL_CTX` dict