336 lines
12 KiB
Markdown
336 lines
12 KiB
Markdown
# 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 IDs**: Stored locally in `~/.config/codex-launcher/oauth-secrets.json` (not in repo)
|
|
- **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": "<gcp-project-id>",
|
|
"model": "<rest-model-id>",
|
|
"requestType": "agent",
|
|
"userAgent": "antigravity",
|
|
"requestId": "agent-<uuid>",
|
|
"request": {
|
|
"contents": [...],
|
|
"systemInstruction": {...},
|
|
"generationConfig": {...},
|
|
"tools": [...]
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5.2 Required Headers
|
|
|
|
```
|
|
Content-Type: application/json
|
|
Authorization: Bearer <access_token>
|
|
User-Agent: antigravity/<version> 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-<session>.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
|