mirror of
https://github.com/usamashehab/antigravity-proxy.git
synced 2026-10-25 01:14:18 +00:00
Initial release: OpenAI-compatible proxy for Google Antigravity
- OpenAI /v1/chat/completions endpoint (streaming + non-streaming)
- Native Gemini passthrough /v1beta/models/{model}:generateContent endpoint
- Tool/function calling support with round-trip encoding
- Auto model name mapping (gemini-3.5-flash -> gemini-3.5-flash-low)
- OAuth token auto-refresh
- Zero pip dependencies (Python 3.10+ stdlib)
- systemd service file included
- Models: Gemini 3.1 Pro, 3 Flash, 3.5 Flash, 2.5 Pro/Flash, Claude Sonnet/Opus 4.6
This commit is contained in:
commit
3e664884af
5 files changed
+2091
No files matched your search
+15
@@ -0,0 +1,15 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.env
|
||||||
|
*.token
|
||||||
|
antigravity-oauth-token*
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Usama Shehab
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,327 @@
|
|||||||
|
# Antigravity Proxy
|
||||||
|
|
||||||
|
An OpenAI-compatible proxy that bridges any OpenAI-compatible client to **Google Antigravity's** internal Cloud Code Assist API. Uses the OAuth token from the [Antigravity CLI](https://antigravity.google/) (`agy`) — no API key, no billing.
|
||||||
|
|
||||||
|
Access **Gemini 3.5 Flash**, **Gemini 3.1 Pro**, **Claude Sonnet 4.6**, **Claude Opus 4.6**, and more through a single local endpoint.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **OpenAI-compatible** `/v1/chat/completions` endpoint (streaming + non-streaming)
|
||||||
|
- **Native Gemini passthrough** `/v1beta/models/{model}:generateContent` endpoint
|
||||||
|
- **Tool/function calling** support (OpenAI tools → Gemini functionDeclarations, round-tripped)
|
||||||
|
- **Model auto-mapping** — use friendly names (`gemini-3.5-flash`), the proxy translates to backend names automatically
|
||||||
|
- **Token auto-refresh** — OAuth token refreshed automatically when expired
|
||||||
|
- **Zero dependencies** — pure Python 3.10+, stdlib only
|
||||||
|
- **systemd integration** — run as a background service
|
||||||
|
|
||||||
|
## Available Models
|
||||||
|
|
||||||
|
| Friendly Name | Backend Model | Notes |
|
||||||
|
|----------------------|----------------------------|--------------------------------|
|
||||||
|
| `gemini-3.1-pro` | `gemini-3.1-pro-low` | Thinking level: low |
|
||||||
|
| `gemini-3.1-pro-high`| `gemini-3.1-pro-low` | Thinking level: high |
|
||||||
|
| `gemini-3-flash` | `gemini-3-flash` | |
|
||||||
|
| `gemini-3.5-flash` | `gemini-3.5-flash-low` | |
|
||||||
|
| `gemini-2.5-pro` | `gemini-2.5-pro` | |
|
||||||
|
| `gemini-2.5-flash` | `gemini-2.5-flash` | |
|
||||||
|
| `claude-sonnet-4.6` | `claude-sonnet-4-6` | Via Antigravity backend |
|
||||||
|
| `claude-opus-4.6` | `claude-opus-4-6-thinking` | Thinking enabled |
|
||||||
|
|
||||||
|
Model names are mapped automatically on both endpoints. You always use the friendly name in your client config.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
You need the **Antigravity CLI** (`agy`) installed and authenticated. The proxy reads the OAuth token that `agy` stores on disk.
|
||||||
|
|
||||||
|
### Install Antigravity CLI
|
||||||
|
|
||||||
|
**macOS / Linux:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://storage.googleapis.com/antigravity-releases/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
Download from [antigravity.google](https://antigravity.google/) or use `winget`:
|
||||||
|
```
|
||||||
|
winget install Google.Antigravity
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authenticate
|
||||||
|
|
||||||
|
Run the CLI once to complete the Google OAuth flow:
|
||||||
|
```bash
|
||||||
|
agy
|
||||||
|
```
|
||||||
|
This opens a browser, you sign in with your Google account, and the token is saved to `~/.gemini/antigravity-cli/antigravity-oauth-token`.
|
||||||
|
|
||||||
|
> **Note:** The proxy supports both consumer Google accounts and Google Workspace accounts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Option A: Run directly
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/usamashehab/antigravity-proxy.git
|
||||||
|
cd antigravity-proxy
|
||||||
|
python3 antigravity_proxy.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The proxy starts on `http://127.0.0.1:8877`.
|
||||||
|
|
||||||
|
### Option B: Run as a systemd service (recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Clone to a permanent location
|
||||||
|
git clone https://github.com/usamashehab/antigravity-proxy.git ~/antigravity-proxy
|
||||||
|
|
||||||
|
# 2. Copy the service file
|
||||||
|
mkdir -p ~/.config/systemd/user
|
||||||
|
cp ~/antigravity-proxy/antigravity-proxy.service ~/.config/systemd/user/
|
||||||
|
|
||||||
|
# 3. If you use a venv, edit the ExecStart path in the service file to point to your python
|
||||||
|
|
||||||
|
# 4. Reload systemd and start
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user enable antigravity-proxy
|
||||||
|
systemctl --user start antigravity-proxy
|
||||||
|
|
||||||
|
# 5. Verify
|
||||||
|
systemctl --user status antigravity-proxy
|
||||||
|
curl http://127.0.0.1:8877/health
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option C: Run with custom host/port
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 antigravity_proxy.py --port 9000 --host 0.0.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Warning:** Binding to `0.0.0.0` exposes the proxy to your network. The proxy has no authentication layer — anyone who can reach it can use your Antigravity quota. Use `127.0.0.1` unless you know what you're doing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Point your client at the proxy
|
||||||
|
|
||||||
|
Any OpenAI-compatible client works. Set:
|
||||||
|
|
||||||
|
| Setting | Value |
|
||||||
|
|-------------|--------------------------------|
|
||||||
|
| Base URL | `http://127.0.0.1:8877/v1` |
|
||||||
|
| API Key | `antigravity` (any string) |
|
||||||
|
| Model | Any name from the table above |
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
**OpenAI Python SDK:**
|
||||||
|
```python
|
||||||
|
from openai import OpenAI
|
||||||
|
client = OpenAI(
|
||||||
|
base_url="http://127.0.0.1:8877/v1",
|
||||||
|
api_key="antigravity",
|
||||||
|
)
|
||||||
|
response = client.chat.completions.create(
|
||||||
|
model="gemini-3.5-flash",
|
||||||
|
messages=[{"role": "user", "content": "Hello!"}],
|
||||||
|
)
|
||||||
|
print(response.choices[0].message.content)
|
||||||
|
```
|
||||||
|
|
||||||
|
**curl:**
|
||||||
|
```bash
|
||||||
|
curl http://127.0.0.1:8877/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"model": "gemini-3.5-flash",
|
||||||
|
"messages": [{"role": "user", "content": "Hello!"}]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Hermes Agent:**
|
||||||
|
```bash
|
||||||
|
hermes config set model.provider custom
|
||||||
|
hermes config set model.base_url http://127.0.0.1:8877/v1
|
||||||
|
hermes config set model.api_key antigravity
|
||||||
|
hermes config set model.default gemini-3.5-flash
|
||||||
|
```
|
||||||
|
|
||||||
|
**Claude Code:**
|
||||||
|
```bash
|
||||||
|
export ANTHROPIC_BASE_URL=http://127.0.0.1:8877/v1
|
||||||
|
claude # uses claude-sonnet-4.6 through the proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
**Continue / Aider / any OpenAI-compatible tool:** Set base URL to `http://127.0.0.1:8877/v1` and use any model from the table.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Two Endpoints
|
||||||
|
|
||||||
|
The proxy serves two distinct API surfaces:
|
||||||
|
|
||||||
|
### 1. OpenAI Chat Completions (default)
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /v1/chat/completions
|
||||||
|
```
|
||||||
|
|
||||||
|
Standard OpenAI format. Messages, tools, streaming — all supported. Use this for any OpenAI-compatible client.
|
||||||
|
|
||||||
|
### 2. Native Gemini Passthrough
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /v1beta/models/{model}:generateContent
|
||||||
|
```
|
||||||
|
|
||||||
|
Accepts the exact same request format as Google's `generativelanguage.googleapis.com` API. Supports `inline_data` (images), `responseSchema` (structured output), `thinkingConfig`, and everything else — all passed through untouched.
|
||||||
|
|
||||||
|
Use this when you need features that don't map to OpenAI format (image inputs, structured JSON schema output, etc.).
|
||||||
|
|
||||||
|
**Example — structured output with a response schema:**
|
||||||
|
```bash
|
||||||
|
curl http://127.0.0.1:8877/v1beta/models/gemini-3.5-flash:generateContent \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"contents": [{"parts": [{"text": "Extract: Ahmed is 30 years old"}]}],
|
||||||
|
"generationConfig": {
|
||||||
|
"temperature": 0,
|
||||||
|
"responseMimeType": "application/json",
|
||||||
|
"responseSchema": {
|
||||||
|
"type": "OBJECT",
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "STRING"},
|
||||||
|
"age": {"type": "NUMBER"}
|
||||||
|
},
|
||||||
|
"required": ["name", "age"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Management
|
||||||
|
|
||||||
|
### Check health
|
||||||
|
```bash
|
||||||
|
curl http://127.0.0.1:8877/health
|
||||||
|
# {"status": "ok", "service": "antigravity-proxy"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### List available models
|
||||||
|
```bash
|
||||||
|
curl http://127.0.0.1:8877/v1/models
|
||||||
|
```
|
||||||
|
|
||||||
|
### View logs (systemd)
|
||||||
|
```bash
|
||||||
|
journalctl --user -u antigravity-proxy -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restart
|
||||||
|
```bash
|
||||||
|
systemctl --user restart antigravity-proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stop
|
||||||
|
```bash
|
||||||
|
systemctl --user stop antigravity-proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Token expired?
|
||||||
|
|
||||||
|
If the proxy logs show authentication errors, re-authenticate with the Antigravity CLI:
|
||||||
|
```bash
|
||||||
|
agy # opens browser for re-login
|
||||||
|
systemctl --user restart antigravity-proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Adding / Updating Models
|
||||||
|
|
||||||
|
Edit `MODEL_MAP` at the top of `antigravity_proxy.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
MODEL_MAP = {
|
||||||
|
"gemini-3.5-flash": ("gemini-3.5-flash-low", None),
|
||||||
|
# format: "friendly-name": ("backend-name", thinking_level_or_None)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Friendly name**: what clients send (e.g. `gemini-3.5-flash`)
|
||||||
|
- **Backend name**: the model ID Antigravity's API expects (e.g. `gemini-3.5-flash-low`)
|
||||||
|
- **Thinking level**: `"low"`, `"high"`, or `None`
|
||||||
|
|
||||||
|
After editing, restart the proxy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
```
|
||||||
|
Your Client (OpenAI format)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Antigravity Proxy (localhost:8877)
|
||||||
|
│ ┌─ Translates OpenAI → Gemini format
|
||||||
|
│ ├─ Manages OAuth token (auto-refresh)
|
||||||
|
│ └─ Wraps in Antigravity envelope
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
cloudcode-pa.googleapis.com (Google's internal Code Assist API)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Gemini / Claude models
|
||||||
|
```
|
||||||
|
|
||||||
|
The proxy:
|
||||||
|
1. Reads the OAuth token from `~/.gemini/antigravity-cli/antigravity-oauth-token`
|
||||||
|
2. Refreshes it automatically via `oauth2.googleapis.com` when expired
|
||||||
|
3. Discovers your Cloud Code Assist project ID via `v1internal:loadCodeAssist`
|
||||||
|
4. Wraps each request in the Antigravity envelope (`{project, model, request, requestType}`)
|
||||||
|
5. Forwards to `v1internal:generateContent` (or `streamGenerateContent`)
|
||||||
|
6. Unwraps the response and converts back to OpenAI format
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Python 3.10+
|
||||||
|
- Antigravity CLI (`agy`) installed and authenticated
|
||||||
|
- No pip dependencies — stdlib only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
**Is this legal?**
|
||||||
|
|
||||||
|
The proxy uses the same OAuth token and API endpoints that the Antigravity CLI itself uses. It does not bypass authentication or payment — you still need a valid Google account with Antigravity access. Multiple similar projects exist on GitHub. That said, Google's terms of service may restrict programmatic API access. Use at your own discretion.
|
||||||
|
|
||||||
|
**Does it support streaming?**
|
||||||
|
|
||||||
|
Yes. Both SSE streaming (`stream: true`) on the OpenAI endpoint and the native Gemini stream endpoint.
|
||||||
|
|
||||||
|
**Can I use it with images?**
|
||||||
|
|
||||||
|
Yes, through the native Gemini passthrough endpoint (`/v1beta/models/{model}:generateContent`). Send `inline_data` with base64-encoded images, exactly like Google's API. The OpenAI chat endpoint does not currently support image inputs.
|
||||||
|
|
||||||
|
**Is there a rate limit?**
|
||||||
|
|
||||||
|
Rate limits are determined by Google's Antigravity quota, not the proxy. The proxy itself is multithreaded and has no concurrency limit.
|
||||||
|
|
||||||
|
**Does the token expire?**
|
||||||
|
|
||||||
|
Yes, the OAuth access token expires (~1 hour). The proxy auto-refreshes it using the refresh token. The refresh token itself can expire if unused for extended periods — re-run `agy` if that happens.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Antigravity OpenAI-Compatible Proxy
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/python3 %h/antigravity-proxy/antigravity_proxy.py --port 8877 --host 127.0.0.1
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
Environment=HOME=%h
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user