Compare commits
48 Commits
v3.10.4
...
850c7d1e82
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,7 +11,3 @@ config.toml
|
||||
.DS_Store
|
||||
DEBIAN/
|
||||
usr/
|
||||
oauth-secrets.json
|
||||
secrets/
|
||||
*.secret
|
||||
.env
|
||||
|
||||
96
CHANGELOG.md
96
CHANGELOG.md
@@ -1,101 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## v3.10.4 (2026-05-25)
|
||||
|
||||
**Security: OAuth Secrets Editor + Import JSON**
|
||||
|
||||
### Security
|
||||
- **All hardcoded OAuth secrets removed from source code and git history**
|
||||
- OAuth client IDs and secrets now stored locally in `~/.config/codex-launcher/oauth-secrets.json`
|
||||
- Git history rewritten to scrub all leaked credentials (0 matches verified)
|
||||
- Pre-push hook blocks any future commit containing secrets
|
||||
- All old Gitea releases deleted (contained leaked secrets in .deb files)
|
||||
|
||||
### New Features
|
||||
- **OAuth Secrets editor** in GUI — "OAuth Secrets" button in header bar
|
||||
- **Import JSON** button — import `client_secret_*.json` downloaded from Google Cloud Console
|
||||
- Supports both `"installed"` and `"web"` JSON formats from Google
|
||||
|
||||
### Antigravity Fix (from v3.10.3)
|
||||
- Antigravity REST API uses slug IDs, not display names
|
||||
- Verified all model IDs with live API testing
|
||||
|
||||
## v3.10.3 (2026-05-25)
|
||||
|
||||
**Fix Antigravity 404 Errors — Verified REST Model IDs**
|
||||
|
||||
### Critical Fix
|
||||
- Antigravity REST API (`v1internal:generateContent`) uses slug IDs, not display names
|
||||
- Verified all model IDs with live API testing against `daily-cloudcode-pa.sandbox.googleapis.com`
|
||||
- Display names map to closest working REST model (e.g. `Gemini 3.5 Flash (High)` → `gemini-3-flash`)
|
||||
- Model list now matches agy CLI: Gemini 3.5 Flash (H/M/L), Gemini 3.1 Pro (H/L), Claude Sonnet/Opus 4.6, GPT-OSS 120B
|
||||
|
||||
### Working REST Model IDs
|
||||
| Display Name | REST ID |
|
||||
|---|---|
|
||||
| Gemini 3.5 Flash (High) | gemini-3-flash |
|
||||
| Gemini 3.5 Flash (Medium) | gemini-3-flash |
|
||||
| Gemini 3.5 Flash (Low) | gemini-3.5-flash-low |
|
||||
| Gemini 3.1 Pro (High) | gemini-3.1-pro-low |
|
||||
| Gemini 3.1 Pro (Low) | gemini-3.1-pro-low |
|
||||
| Claude Sonnet 4.6 (Thinking) | claude-sonnet-4-6 |
|
||||
| Claude Opus 4.6 (Thinking) | claude-opus-4-6-thinking |
|
||||
| GPT-OSS 120B (Medium) | gpt-oss-120b-medium |
|
||||
|
||||
## v3.10.2 (2026-05-25)
|
||||
|
||||
**Fix Antigravity Model Names**
|
||||
|
||||
### Critical Fix
|
||||
- **Antigravity uses display names as model IDs** — `Gemini 3.5 Flash (High)` not `gemini-3.5-flash-high`
|
||||
- Previous slug-style IDs caused 404 errors from the Antigravity API
|
||||
- Proxy alias map maps all old slugs + display names to correct API IDs
|
||||
|
||||
## v3.10.0 (2026-05-25)
|
||||
|
||||
**Provider Model Editor + Antigravity Model Refresh**
|
||||
|
||||
### Provider Editor
|
||||
- **Remove Selected** button to remove highlighted model(s) from provider
|
||||
- **Clear All** button to empty model list
|
||||
- **Sync from Preset** button to refresh model list from current preset definition
|
||||
- Preset sync now replaces (not appends) models — fixes stale saved model lists
|
||||
|
||||
### Antigravity Models Updated
|
||||
- **Gemini 3.5 Flash** (High / Medium)
|
||||
- **Gemini 3.1 Pro** (High / Low)
|
||||
- **Claude Sonnet 4.6 Thinking**
|
||||
- **Claude Opus 4.6 Thinking**
|
||||
- **GPT-OSS 120B Medium**
|
||||
|
||||
## v3.9.9 (2026-05-25)
|
||||
|
||||
**Antigravity Model Refresh**
|
||||
|
||||
### Updated Models
|
||||
- **Gemini 3.5 Flash** (High / Medium) — new flagship flash model
|
||||
- **Gemini 3.1 Pro** (High / Low) — tiered reasoning control
|
||||
- **Claude Sonnet 4.6 Thinking** — Anthropic partner model via Antigravity
|
||||
- **Claude Opus 4.6 Thinking** — Anthropic partner model via Antigravity
|
||||
- **GPT-OSS 120B Medium** — open-weight GPT model via Antigravity
|
||||
- Removed stale `antigravity-*` prefixed IDs and old preview models
|
||||
|
||||
### Proxy Updates
|
||||
- Alias map updated for tiered model IDs (high/medium/low/thinking)
|
||||
- Context sizes added for all new Antigravity models
|
||||
|
||||
## v3.9.8 (2026-05-25)
|
||||
|
||||
**Codex Desktop Model Fix & Global BrokenPipeError Protection**
|
||||
|
||||
### Desktop Model Fix
|
||||
- **Codex Desktop sending wrong model** (gpt-5.4-mini) instead of user-selected model — now remapped via `CODEX_LAUNCHER_MODEL` env var
|
||||
- **Config.toml** now writes `review_model`, `wire_api`, `request_max_retries`, `stream_max_retries`, `stream_idle_timeout_ms` for Desktop compatibility
|
||||
- **Proxy model remap** intercepts Desktop forced models (`gpt-5.4-mini`, `gpt-5.5`, etc.) and routes to the user's selected model
|
||||
|
||||
### Global Crash Fix
|
||||
- **`send_json()` globally catches BrokenPipeError** — no more crashes on client disconnect across all backends
|
||||
|
||||
## v3.9.7 (2026-05-25)
|
||||
|
||||
**Codebuff Error Forwarding & Crash Fixes**
|
||||
|
||||
5073
codex-launcher-gui
Executable file
5073
codex-launcher-gui
Executable file
File diff suppressed because it is too large
Load Diff
BIN
codex-launcher_3.0.0_all.deb
Normal file
BIN
codex-launcher_3.0.0_all.deb
Normal file
Binary file not shown.
Binary file not shown.
BIN
codex-launcher_3.3.0_all.deb
Normal file
BIN
codex-launcher_3.3.0_all.deb
Normal file
Binary file not shown.
BIN
codex-launcher_3.5.0_all.deb
Normal file
BIN
codex-launcher_3.5.0_all.deb
Normal file
Binary file not shown.
BIN
codex-launcher_3.6.0_all.deb
Normal file
BIN
codex-launcher_3.6.0_all.deb
Normal file
Binary file not shown.
BIN
codex-launcher_3.7.0_all.deb
Normal file
BIN
codex-launcher_3.7.0_all.deb
Normal file
Binary file not shown.
BIN
codex-launcher_3.8.0_all.deb
Normal file
BIN
codex-launcher_3.8.0_all.deb
Normal file
Binary file not shown.
BIN
codex-launcher_3.8.1_all.deb
Normal file
BIN
codex-launcher_3.8.1_all.deb
Normal file
Binary file not shown.
BIN
codex-launcher_3.8.3_all.deb
Normal file
BIN
codex-launcher_3.8.3_all.deb
Normal file
Binary file not shown.
BIN
codex-launcher_3.8.4_all.deb
Normal file
BIN
codex-launcher_3.8.4_all.deb
Normal file
Binary file not shown.
BIN
codex-launcher_3.9.7_all.deb
Normal file
BIN
codex-launcher_3.9.7_all.deb
Normal file
Binary file not shown.
@@ -1,335 +0,0 @@
|
||||
# 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
|
||||
@@ -3,11 +3,11 @@ set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
if [ -f "$SCRIPT_DIR/codex-launcher_3.10.4_all.deb" ]; then
|
||||
echo "Installing codex-launcher_3.10.4_all.deb ..."
|
||||
sudo dpkg -i "$SCRIPT_DIR/codex-launcher_3.10.4_all.deb"
|
||||
if [ -f "$SCRIPT_DIR/codex-launcher_3.9.7_all.deb" ]; then
|
||||
echo "Installing codex-launcher_3.9.7_all.deb ..."
|
||||
sudo dpkg -i "$SCRIPT_DIR/codex-launcher_3.9.7_all.deb"
|
||||
echo ""
|
||||
echo "Installed v3.10.4 via .deb package."
|
||||
echo "Installed v3.9.7 via .deb package."
|
||||
echo " translate-proxy.py -> /usr/bin/translate-proxy.py"
|
||||
echo " codex-launcher-gui -> /usr/bin/codex-launcher-gui"
|
||||
echo " cleanup-codex-stale -> /usr/bin/cleanup-codex-stale.sh"
|
||||
|
||||
@@ -26,34 +26,6 @@ model_catalog_json = ""
|
||||
"""
|
||||
|
||||
CHANGELOG = [
|
||||
("3.10.4", "2026-05-25", [
|
||||
"OAuth Secrets editor in GUI — update client ID/secret without editing files",
|
||||
"Secrets stored in ~/.config/codex-launcher/oauth-secrets.json (not in repo)",
|
||||
]),
|
||||
("3.10.3", "2026-05-25", [
|
||||
"Fix Antigravity 404: map display names to verified REST API model IDs",
|
||||
"REST API uses slugs (gemini-3-flash) not display names (Gemini 3.5 Flash)",
|
||||
"Match agy CLI model list: Gemini 3.5 Flash (H/M/L), 3.1 Pro (H/L), Claude 4.6, GPT-OSS",
|
||||
]),
|
||||
("3.10.2", "2026-05-25", [
|
||||
"Fetch from API now works for Antigravity — returns current model list",
|
||||
]),
|
||||
("3.10.0", "2026-05-25", [
|
||||
"Provider editor: Remove Selected, Clear All, Sync from Preset buttons for model list",
|
||||
"Sync from Preset replaces model list with current preset models",
|
||||
"Stale saved Antigravity models auto-refreshed on preset sync",
|
||||
]),
|
||||
("3.9.9", "2026-05-25", [
|
||||
"Refresh Antigravity preset: Gemini 3.5 Flash, Gemini 3.1 Pro, Claude Sonnet/Opus 4.6, GPT-OSS 120B",
|
||||
"Fix Antigravity alias map for new tiered model IDs (high/medium/low/thinking)",
|
||||
"Add model context sizes for Gemini 3.5 Flash, Gemini 3.1 Pro, Claude 4.6, GPT-OSS 120B",
|
||||
]),
|
||||
("3.9.8", "2026-05-25", [
|
||||
"Fix Codex Desktop sending wrong model (gpt-5.4-mini) instead of selected model",
|
||||
"Proxy remaps Desktop forced models to user-selected model via CODEX_LAUNCHER_MODEL",
|
||||
"Write review_model + wire_api + retries to config.toml for Desktop compatibility",
|
||||
"send_json() globally catches BrokenPipeError — no more crashes on disconnect",
|
||||
]),
|
||||
("3.9.7", "2026-05-25", [
|
||||
"Forward real Codebuff error messages to user (not generic 429)",
|
||||
"Return HTTP 200 with Responses API format for rate limits so Codex displays message",
|
||||
@@ -364,11 +336,13 @@ PROVIDER_PRESETS = {
|
||||
"base_url": "https://daily-cloudcode-pa.sandbox.googleapis.com",
|
||||
"oauth_provider": "google-antigravity",
|
||||
"models": [
|
||||
"Gemini 3.5 Flash (High)", "Gemini 3.5 Flash (Medium)", "Gemini 3.5 Flash (Low)",
|
||||
"Gemini 3.1 Pro (High)", "Gemini 3.1 Pro (Low)",
|
||||
"Claude Sonnet 4.6 (Thinking)",
|
||||
"Claude Opus 4.6 (Thinking)",
|
||||
"GPT-OSS 120B (Medium)",
|
||||
"antigravity-gemini-3-flash",
|
||||
"antigravity-gemini-3-pro",
|
||||
"antigravity-gemini-3.1-pro",
|
||||
"antigravity-claude-sonnet-4-6",
|
||||
"antigravity-claude-opus-4-6-thinking",
|
||||
"gemini-2.5-flash", "gemini-2.5-pro",
|
||||
"gemini-3-flash-preview", "gemini-3-pro-preview", "gemini-3.1-pro-preview",
|
||||
],
|
||||
},
|
||||
"OpenAdapter": {
|
||||
@@ -761,18 +735,7 @@ def endpoint_model_headers(endpoint):
|
||||
headers["Authorization"] = f"Bearer {key}"
|
||||
return headers
|
||||
|
||||
_ANTIGRAVITY_MODELS = [
|
||||
"Gemini 3.5 Flash (High)", "Gemini 3.5 Flash (Medium)", "Gemini 3.5 Flash (Low)",
|
||||
"Gemini 3.1 Pro (High)", "Gemini 3.1 Pro (Low)",
|
||||
"Claude Sonnet 4.6 (Thinking)",
|
||||
"Claude Opus 4.6 (Thinking)",
|
||||
"GPT-OSS 120B (Medium)",
|
||||
]
|
||||
|
||||
def fetch_models_for_endpoint(endpoint, timeout=10):
|
||||
bt = endpoint.get("backend_type", "")
|
||||
if bt == "gemini-oauth-antigravity":
|
||||
return list(_ANTIGRAVITY_MODELS), None
|
||||
url = endpoint_models_url(endpoint)
|
||||
if not url:
|
||||
return None, "Base URL is empty"
|
||||
@@ -973,21 +936,15 @@ def write_config_for_translated(endpoint, selected_model, proxy_port=8080):
|
||||
|
||||
lines = [
|
||||
f'model = "{_toml_safe(selected_model)}"\n',
|
||||
f'review_model = "{_toml_safe(selected_model)}"\n',
|
||||
f'model_provider = "{_toml_safe(endpoint["name"])}"\n',
|
||||
f'model_catalog_json = "{mc_path}"\n',
|
||||
f'\n[model_providers."{endpoint["name"]}"]\n',
|
||||
f'name = "{_toml_safe(endpoint["name"])}"\n',
|
||||
f'base_url = "http://127.0.0.1:{proxy_port}"\n',
|
||||
f'experimental_bearer_token = "codex-launcher-local"\n',
|
||||
f'wire_api = "responses"\n',
|
||||
f'request_max_retries = 1\n',
|
||||
f'stream_max_retries = 0\n',
|
||||
f'stream_idle_timeout_ms = 600000\n',
|
||||
f'\n[profiles."{endpoint["name"]}"]\n',
|
||||
f'model_provider = "{_toml_safe(endpoint["name"])}"\n',
|
||||
f'model = "{_toml_safe(selected_model)}"\n',
|
||||
f'review_model = "{_toml_safe(selected_model)}"\n',
|
||||
f'model_catalog_json = "{mc_path}"\n',
|
||||
f'service_tier = "fast"\n',
|
||||
f'approvals_reviewer = "user"\n',
|
||||
@@ -1780,7 +1737,7 @@ class LauncherWin(Gtk.Window):
|
||||
# header row
|
||||
hdr = Gtk.Box(spacing=8)
|
||||
vbox.pack_start(hdr, False, False, 0)
|
||||
lbl = Gtk.Label(label="<b>Codex Launcher v3.10.4</b>")
|
||||
lbl = Gtk.Label(label="<b>Codex Launcher v3.9.7</b>")
|
||||
lbl.set_use_markup(True)
|
||||
hdr.pack_start(lbl, False, False, 0)
|
||||
changelog_btn = Gtk.Button(label="Changelog")
|
||||
@@ -1804,9 +1761,6 @@ class LauncherWin(Gtk.Window):
|
||||
mgr_btn = Gtk.Button(label="Manage Endpoints")
|
||||
mgr_btn.connect("clicked", lambda b: self._open_mgr())
|
||||
hdr.pack_end(mgr_btn, False, False, 0)
|
||||
oauth_btn = Gtk.Button(label="OAuth Secrets")
|
||||
oauth_btn.connect("clicked", lambda b: self._edit_oauth_secrets())
|
||||
hdr.pack_end(oauth_btn, False, False, 0)
|
||||
|
||||
# verification status bar
|
||||
self._cli_info = _detect_codex_cli()
|
||||
@@ -2480,7 +2434,6 @@ class LauncherWin(Gtk.Window):
|
||||
|
||||
if needs_proxy:
|
||||
self.log("Starting translation proxy…")
|
||||
os.environ["CODEX_LAUNCHER_MODEL"] = model
|
||||
try:
|
||||
proxy_port = _start_proxy_for(ep, self.log)
|
||||
except RuntimeError as e:
|
||||
@@ -2790,98 +2743,6 @@ class LauncherWin(Gtk.Window):
|
||||
_stop_proxy()
|
||||
Gtk.main_quit()
|
||||
|
||||
def _edit_oauth_secrets(self):
|
||||
secrets_path = os.path.expanduser("~/.config/codex-launcher/oauth-secrets.json")
|
||||
try:
|
||||
with open(secrets_path) as f:
|
||||
data = json.load(f)
|
||||
except Exception:
|
||||
data = {"antigravity": {"client_id": "", "client_secret": ""},
|
||||
"gemini_cli": {"client_id": "", "client_secret": ""}}
|
||||
|
||||
dlg = Gtk.Dialog(title="OAuth 2.0 Client Secrets", parent=self, modal=True)
|
||||
dlg.add_button("Cancel", Gtk.ResponseType.CANCEL)
|
||||
dlg.add_button("Save", Gtk.ResponseType.OK)
|
||||
dlg.set_default_size(540, 420)
|
||||
area = dlg.get_content_area()
|
||||
area.set_margin_start(16)
|
||||
area.set_margin_end(16)
|
||||
area.set_margin_top(12)
|
||||
area.set_margin_bottom(12)
|
||||
area.set_spacing(6)
|
||||
|
||||
area.pack_start(Gtk.Label(label="<b>Google OAuth 2.0 credentials</b>\n<small>Stored locally in ~/.config/codex-launcher/oauth-secrets.json</small>", use_markup=True, xalign=0), False, False, 4)
|
||||
|
||||
fields = {}
|
||||
for section_key, section_label in [("antigravity", "Antigravity (CloudCode)"), ("gemini_cli", "Gemini CLI")]:
|
||||
section_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
|
||||
hdr_row = Gtk.Box(spacing=6)
|
||||
hdr_row.pack_start(Gtk.Label(label=f"\n<b>{section_label}</b>", use_markup=True, xalign=0), True, True, 0)
|
||||
import_btn = Gtk.Button(label="Import JSON")
|
||||
import_btn.set_size_request(100, -1)
|
||||
hdr_row.pack_end(import_btn, False, False, 0)
|
||||
section_box.pack_start(hdr_row, False, False, 2)
|
||||
sec = data.get(section_key, {})
|
||||
for fk, fl in [("client_id", "Client ID"), ("client_secret", "Client Secret")]:
|
||||
row = Gtk.Box(spacing=6)
|
||||
lbl = Gtk.Label(label=fl + ":", xalign=0)
|
||||
lbl.set_size_request(100, -1)
|
||||
entry = Gtk.Entry()
|
||||
entry.set_text(sec.get(fk, ""))
|
||||
entry.set_size_request(380, -1)
|
||||
if fk == "client_secret":
|
||||
entry.set_visibility(False)
|
||||
entry.set_invisible_char("*")
|
||||
row.pack_start(lbl, False, False, 0)
|
||||
row.pack_start(entry, True, True, 0)
|
||||
section_box.pack_start(row, False, False, 2)
|
||||
fields[(section_key, fk)] = entry
|
||||
import_btn.connect("clicked", lambda b, sk=section_key: self._import_oauth_json(fields, sk))
|
||||
area.pack_start(section_box, False, False, 0)
|
||||
|
||||
area.pack_start(Gtk.Label(label="\n<small>Import a client_secret_*.json from Google Cloud Console\nor edit fields manually. console.cloud.google.com → Credentials</small>", use_markup=True, xalign=0), False, False, 4)
|
||||
area.show_all()
|
||||
|
||||
if dlg.run() == Gtk.ResponseType.OK:
|
||||
for (sk, fk), entry in fields.items():
|
||||
if sk not in data:
|
||||
data[sk] = {}
|
||||
data[sk][fk] = entry.get_text().strip()
|
||||
try:
|
||||
os.makedirs(os.path.dirname(secrets_path), exist_ok=True)
|
||||
with open(secrets_path, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
os.chmod(secrets_path, 0o600)
|
||||
except Exception as e:
|
||||
self._show_error_dialog("Save failed", str(e))
|
||||
dlg.destroy()
|
||||
|
||||
def _import_oauth_json(self, fields, section_key):
|
||||
chooser = Gtk.FileChooserDialog(
|
||||
title="Import Google OAuth Client Secret JSON",
|
||||
parent=self, action=Gtk.FileChooserAction.OPEN)
|
||||
chooser.add_button("Cancel", Gtk.ResponseType.CANCEL)
|
||||
chooser.add_button("Open", Gtk.ResponseType.OK)
|
||||
filt = Gtk.FileFilter()
|
||||
filt.set_name("JSON files")
|
||||
filt.add_pattern("*.json")
|
||||
chooser.add_filter(filt)
|
||||
if chooser.run() == Gtk.ResponseType.OK:
|
||||
path = chooser.get_filename()
|
||||
try:
|
||||
with open(path) as f:
|
||||
raw = json.load(f)
|
||||
creds = raw.get("installed") or raw.get("web") or raw
|
||||
cid = creds.get("client_id", "")
|
||||
csec = creds.get("client_secret", "")
|
||||
if not cid or not csec:
|
||||
raise ValueError("JSON does not contain client_id and client_secret")
|
||||
fields[(section_key, "client_id")].set_text(cid)
|
||||
fields[(section_key, "client_secret")].set_text(csec)
|
||||
except Exception as e:
|
||||
self._show_error_dialog("Import failed", str(e))
|
||||
chooser.destroy()
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Endpoint manager dialog
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
@@ -3261,18 +3122,6 @@ class EditEndpointDialog(Gtk.Dialog):
|
||||
sw.add(self._model_tree)
|
||||
self._model_tree.connect("row-activated", lambda t, p, c: self._remove_model(p))
|
||||
|
||||
model_btn_box = Gtk.Box(spacing=6)
|
||||
area.pack_start(model_btn_box, False, False, 0)
|
||||
self._remove_model_btn = Gtk.Button(label="Remove Selected")
|
||||
self._remove_model_btn.connect("clicked", lambda b: self._remove_selected_model())
|
||||
model_btn_box.pack_start(self._remove_model_btn, False, False, 0)
|
||||
self._clear_models_btn = Gtk.Button(label="Clear All")
|
||||
self._clear_models_btn.connect("clicked", lambda b: self._clear_all_models())
|
||||
model_btn_box.pack_start(self._clear_models_btn, False, False, 0)
|
||||
self._sync_preset_btn = Gtk.Button(label="Sync from Preset")
|
||||
self._sync_preset_btn.connect("clicked", lambda b: self._apply_selected_preset())
|
||||
model_btn_box.pack_start(self._sync_preset_btn, False, False, 0)
|
||||
|
||||
for m in self._data.get("models", []):
|
||||
self._model_store.append([m])
|
||||
|
||||
@@ -3342,12 +3191,10 @@ class EditEndpointDialog(Gtk.Dialog):
|
||||
cc_ver = preset.get("cc_version", "")
|
||||
if cc_ver and not self._entry_cc_ver.get_text().strip():
|
||||
self._entry_cc_ver.set_text(cc_ver)
|
||||
if preset.get("models") and (not initial or len(self._model_store) == 0):
|
||||
current = self._combo_default.get_active_text()
|
||||
self._model_store.clear()
|
||||
for mid in preset["models"]:
|
||||
self._model_store.append([mid])
|
||||
self._refresh_default_combo(current or preset["models"][0])
|
||||
if preset.get("models") and len(self._model_store) == 0:
|
||||
for mid in preset["models"]:
|
||||
self._model_store.append([mid])
|
||||
self._refresh_default_combo(preset["models"][0])
|
||||
if initial and self._data.get("models"):
|
||||
self._refresh_default_combo(self._data.get("default_model", ""))
|
||||
|
||||
@@ -3372,17 +3219,9 @@ class EditEndpointDialog(Gtk.Dialog):
|
||||
is_antigravity = oauth_provider == "google-antigravity"
|
||||
token_path = os.path.expanduser("~/.cache/codex-proxy/google-antigravity-oauth-token.json" if is_antigravity else "~/.cache/codex-proxy/google-cli-oauth-token.json")
|
||||
|
||||
_oauth_secrets_path = os.path.expanduser("~/.config/codex-launcher/oauth-secrets.json")
|
||||
try:
|
||||
with open(_oauth_secrets_path) as _f:
|
||||
_oauth_secrets = json.load(_f)
|
||||
except Exception:
|
||||
_oauth_secrets = {}
|
||||
|
||||
if is_antigravity:
|
||||
_sec = _oauth_secrets.get("antigravity", {})
|
||||
CLIENT_ID = _sec.get("client_id", "")
|
||||
CLIENT_SECRET = _sec.get("client_secret", "")
|
||||
CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com"
|
||||
CLIENT_SECRET = "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"
|
||||
SCOPES = [
|
||||
"https://www.googleapis.com/auth/cloud-platform",
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
@@ -3395,9 +3234,8 @@ class EditEndpointDialog(Gtk.Dialog):
|
||||
callback_path = "/oauth-callback"
|
||||
provider_kind = "antigravity"
|
||||
else:
|
||||
_sec = _oauth_secrets.get("gemini_cli", {})
|
||||
CLIENT_ID = _sec.get("client_id", "")
|
||||
CLIENT_SECRET = _sec.get("client_secret", "")
|
||||
CLIENT_ID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com"
|
||||
CLIENT_SECRET = "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"
|
||||
SCOPES = [
|
||||
"https://www.googleapis.com/auth/cloud-platform",
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
@@ -3688,7 +3526,7 @@ class EditEndpointDialog(Gtk.Dialog):
|
||||
auth_url = "https://codebuff.com/api/auth/cli/code"
|
||||
body = json.dumps({"fingerprintId": fingerprint_id}).encode()
|
||||
req = urllib.request.Request(auth_url, data=body,
|
||||
headers={"Content-Type": "application/json", "User-Agent": "codex-launcher/3.10.4"})
|
||||
headers={"Content-Type": "application/json", "User-Agent": "codex-launcher/3.9.7"})
|
||||
resp = urllib.request.urlopen(req, timeout=30)
|
||||
data = json.loads(resp.read())
|
||||
login_url = data.get("loginUrl", "") or data.get("login_url", "")
|
||||
@@ -3713,7 +3551,7 @@ class EditEndpointDialog(Gtk.Dialog):
|
||||
time.sleep(2)
|
||||
try:
|
||||
poll_req = urllib.request.Request(poll_url,
|
||||
headers={"User-Agent": "codex-launcher/3.10.4"})
|
||||
headers={"User-Agent": "codex-launcher/3.9.7"})
|
||||
poll_resp = urllib.request.urlopen(poll_req, timeout=10)
|
||||
poll_data = json.loads(poll_resp.read())
|
||||
user = poll_data.get("user")
|
||||
@@ -3778,21 +3616,6 @@ class EditEndpointDialog(Gtk.Dialog):
|
||||
self._model_store.remove(self._model_store.get_iter(path))
|
||||
self._refresh_default_combo(current)
|
||||
|
||||
def _remove_selected_model(self):
|
||||
sel = self._model_tree.get_selection()
|
||||
model, paths = sel.get_selected_rows()
|
||||
if not paths:
|
||||
return
|
||||
current = self._combo_default.get_active_text()
|
||||
for p in reversed(paths):
|
||||
self._model_store.remove(self._model_store.get_iter(p))
|
||||
self._refresh_default_combo(current)
|
||||
|
||||
def _clear_all_models(self):
|
||||
current = self._combo_default.get_active_text()
|
||||
self._model_store.clear()
|
||||
self._refresh_default_combo(current)
|
||||
|
||||
def _refresh_default_combo(self, active=None):
|
||||
if active is None:
|
||||
active = self._combo_default.get_active_text()
|
||||
|
||||
@@ -187,7 +187,7 @@ def load_config():
|
||||
p = argparse.ArgumentParser(description="Responses API translation proxy")
|
||||
p.add_argument("--config", help="JSON config file path")
|
||||
p.add_argument("--port", type=int, default=None)
|
||||
p.add_argument("--backend", default=None, choices=["openai-compat", "anthropic", "command-code", "codebuff", "freebuff", "auto"])
|
||||
p.add_argument("--backend", default=None, choices=["openai-compat", "anthropic", "command-code", "codebuff", "auto"])
|
||||
p.add_argument("--target-url", default=None)
|
||||
p.add_argument("--api-key", default=None)
|
||||
p.add_argument("--models-file", default=None, help="JSON file with model list array")
|
||||
@@ -335,7 +335,7 @@ def _codebuff_get_session(token, model):
|
||||
req = urllib.request.Request(url, data=body, headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {token}",
|
||||
"User-Agent": "codex-launcher/3.10.4",
|
||||
"User-Agent": "codex-launcher/3.9.7",
|
||||
"x-codebuff-model": model,
|
||||
})
|
||||
try:
|
||||
@@ -383,7 +383,7 @@ def _codebuff_start_run(token, agent_id):
|
||||
req = urllib.request.Request(url, data=body, headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {token}",
|
||||
"User-Agent": "codex-launcher/3.10.4",
|
||||
"User-Agent": "codex-launcher/3.9.7",
|
||||
})
|
||||
try:
|
||||
resp = urllib.request.urlopen(req, timeout=15)
|
||||
@@ -416,7 +416,7 @@ def _codebuff_finish_run(token, run_id, status="completed"):
|
||||
req = urllib.request.Request(url, data=body, headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {token}",
|
||||
"User-Agent": "codex-launcher/3.10.4",
|
||||
"User-Agent": "codex-launcher/3.9.7",
|
||||
})
|
||||
try:
|
||||
urllib.request.urlopen(req, timeout=10)
|
||||
@@ -764,7 +764,7 @@ def _init_runtime():
|
||||
REASONING_EFFORT = CONFIG.get("reasoning_effort", "medium")
|
||||
BGP_ROUTES = CONFIG.get("bgp_routes", [])
|
||||
_api_key_pool = None
|
||||
if API_KEY and "," in API_KEY and not OAUTH_PROVIDER.startswith("google") and BACKEND not in ("codebuff", "freebuff"):
|
||||
if API_KEY and "," in API_KEY and not OAUTH_PROVIDER.startswith("google") and BACKEND not in ("codebuff",):
|
||||
_api_key_pool = APIKeyPool(BACKEND, API_KEY)
|
||||
print(f"[multi-account] API key pool: {len(_api_key_pool._accounts)} keys for {BACKEND}", file=sys.stderr)
|
||||
if OAUTH_PROVIDER == "google-antigravity":
|
||||
@@ -1588,12 +1588,6 @@ _MODEL_CONTEXT = {
|
||||
"claude-sonnet": 200000, "claude-haiku": 200000,
|
||||
"glm-5.1": 128000, "glm-5": 128000, "glm-4": 128000,
|
||||
"deepseek": 64000, "gemini-2.5-flash": 1000000, "gemini-2.5-pro": 2000000,
|
||||
"gemini-3.5-flash": 1000000, "gemini-3.1-pro": 2000000,
|
||||
"Gemini 3.5 Flash": 1000000, "Gemini 3.1 Pro": 2000000,
|
||||
"Claude Sonnet 4.6": 200000, "Claude Opus 4.6": 200000,
|
||||
"GPT-OSS 120B": 128000,
|
||||
"claude-sonnet-4.6-thinking": 200000, "claude-opus-4.6-thinking": 200000,
|
||||
"gpt-oss-120b": 128000,
|
||||
"mimo": 32768, "minimax": 32768, "kimi": 128000,
|
||||
"_default": 32768,
|
||||
}
|
||||
@@ -4043,7 +4037,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
self.send_json(200, {"object": "list", "data": MODELS})
|
||||
elif self.path in ("/v1/accounts", "/accounts"):
|
||||
info = {"provider": BACKEND, "oauth_provider": OAUTH_PROVIDER}
|
||||
if BACKEND in ("codebuff", "freebuff"):
|
||||
if BACKEND == "codebuff":
|
||||
info["accounts"] = _cb_pool.status()
|
||||
info["total"] = len(_cb_pool._accounts)
|
||||
elif OAUTH_PROVIDER and OAUTH_PROVIDER.startswith("google"):
|
||||
@@ -4138,12 +4132,6 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
|
||||
model = body.get("model", MODELS[0]["id"] if MODELS else "unknown")
|
||||
stream = body.get("stream", False)
|
||||
_desktop_forced_models = {"gpt-5.4-mini", "gpt-5.4", "gpt-5.5", "gpt-5-codex", "gpt-5.3-codex"}
|
||||
_launcher_model = os.environ.get("CODEX_LAUNCHER_MODEL", "")
|
||||
if _launcher_model and model in _desktop_forced_models:
|
||||
print(f"[{_sid}] remap desktop model {model} -> {_launcher_model}", file=sys.stderr)
|
||||
model = _launcher_model
|
||||
body["model"] = model
|
||||
request_id = body.get("request_id") or body.get("id") or uid("req")
|
||||
if isinstance(input_data, list):
|
||||
for item in input_data:
|
||||
@@ -4161,7 +4149,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
self._handle_anthropic(body, model, stream, tracker)
|
||||
elif BACKEND == "command-code":
|
||||
self._handle_command_code(body, model, stream, tracker)
|
||||
elif BACKEND in ("codebuff", "freebuff"):
|
||||
elif BACKEND == "codebuff":
|
||||
self._handle_codebuff(body, model, stream, tracker)
|
||||
elif (BACKEND or "").startswith("gemini-oauth"):
|
||||
self._handle_gemini_oauth(body, model, stream, tracker)
|
||||
@@ -4315,41 +4303,16 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
|
||||
if OAUTH_PROVIDER == "google-antigravity":
|
||||
alias_map = {
|
||||
"Gemini 3.5 Flash (High)": "gemini-3-flash",
|
||||
"Gemini 3.5 Flash (Medium)": "gemini-3-flash",
|
||||
"Gemini 3.5 Flash (Low)": "gemini-3.5-flash-low",
|
||||
"gemini-3.5-flash-high": "gemini-3-flash",
|
||||
"gemini-3.5-flash-medium": "gemini-3-flash",
|
||||
"gemini-3.5-flash-low": "gemini-3.5-flash-low",
|
||||
"gemini-3-flash-preview": "gemini-3-flash",
|
||||
"gemini-3-flash": "gemini-3-flash",
|
||||
"antigravity-gemini-3-flash": "gemini-3-flash",
|
||||
"Gemini 3.1 Pro (High)": "gemini-3.1-pro-low",
|
||||
"Gemini 3.1 Pro (Low)": "gemini-3.1-pro-low",
|
||||
"gemini-3.1-pro-high": "gemini-3.1-pro-low",
|
||||
"gemini-3.1-pro-low": "gemini-3.1-pro-low",
|
||||
"gemini-3.1-pro-preview": "gemini-3.1-pro-low",
|
||||
"gemini-3.1-pro": "gemini-3.1-pro-low",
|
||||
"gemini-3-pro-preview": "gemini-3.1-pro-low",
|
||||
"gemini-3-pro": "gemini-3.1-pro-low",
|
||||
"gemini-3-pro-low": "gemini-3.1-pro-low",
|
||||
"gemini-3-pro-high": "gemini-3.1-pro-low",
|
||||
"antigravity-gemini-3-pro": "gemini-3.1-pro-low",
|
||||
"antigravity-gemini-3-pro": "gemini-3-pro-low",
|
||||
"antigravity-gemini-3.1-pro": "gemini-3.1-pro-low",
|
||||
"Claude Sonnet 4.6 (Thinking)": "claude-sonnet-4-6",
|
||||
"Claude Sonnet 4.6 Thinking": "claude-sonnet-4-6",
|
||||
"claude-sonnet-4.6-thinking": "claude-sonnet-4-6",
|
||||
"gemini-3-flash-preview": "gemini-3-flash",
|
||||
"gemini-3-pro-preview": "gemini-3-pro-low",
|
||||
"gemini-3.1-pro-preview": "gemini-3.1-pro-low",
|
||||
"gemini-3-pro": "gemini-3-pro-low",
|
||||
"gemini-3.1-pro": "gemini-3.1-pro-low",
|
||||
"antigravity-claude-sonnet-4-6": "claude-sonnet-4-6",
|
||||
"Claude Opus 4.6 (Thinking)": "claude-opus-4-6-thinking",
|
||||
"Claude Opus 4.6 Thinking": "claude-opus-4-6-thinking",
|
||||
"claude-opus-4.6-thinking": "claude-opus-4-6-thinking",
|
||||
"antigravity-claude-opus-4-6-thinking": "claude-opus-4-6-thinking",
|
||||
"GPT-OSS 120B (Medium)": "gpt-oss-120b-medium",
|
||||
"GPT-OSS 120B Medium": "gpt-oss-120b-medium",
|
||||
"gpt-oss-120b": "gpt-oss-120b-medium",
|
||||
"gemini-2.5-flash": "gemini-2.5-flash",
|
||||
"gemini-2.5-pro": "gemini-2.5-pro",
|
||||
"gemini-2.5-flash-lite": "gemini-2.5-flash-lite",
|
||||
}
|
||||
model = alias_map.get(model, model)
|
||||
if model != original_model:
|
||||
@@ -5342,7 +5305,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {token}",
|
||||
"User-Agent": "codex-launcher/3.10.4",
|
||||
"User-Agent": "codex-launcher/3.9.7",
|
||||
"x-codebuff-model": model,
|
||||
}
|
||||
if instance_id:
|
||||
@@ -5479,7 +5442,10 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
}],
|
||||
"usage": {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0},
|
||||
}
|
||||
return self.send_json(200, result)
|
||||
try:
|
||||
return self.send_json(200, result)
|
||||
except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
|
||||
return
|
||||
|
||||
def _cb_retry_thinking_disabled(self, body, model, token, agent_id, stream, tracker, input_data, instructions, original_error, acct=None):
|
||||
run_id, run_err = _codebuff_start_run(token, agent_id)
|
||||
@@ -5508,7 +5474,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
if body.get("tool_choice"):
|
||||
chat_body["tool_choice"] = body["tool_choice"]
|
||||
target = f"{_CODEBUFF_API_URL}/api/v1/chat/completions"
|
||||
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}", "User-Agent": "codex-launcher/3.10.4", "x-codebuff-model": model}
|
||||
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}", "User-Agent": "codex-launcher/3.9.7", "x-codebuff-model": model}
|
||||
if instance_id:
|
||||
headers["x-codebuff-instance-id"] = instance_id
|
||||
print(f"[codebuff] retry POST {target} model={model} stream={stream} run={run_id} (thinking disabled via DeepSeek native)", file=sys.stderr)
|
||||
@@ -5864,15 +5830,12 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
store_response(rid, input_data, result.get("output", []))
|
||||
|
||||
def send_json(self, status, data):
|
||||
try:
|
||||
body = json.dumps(data).encode()
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
|
||||
pass
|
||||
body = json.dumps(data).encode()
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def stream_buffered_events(self, event_iter, flush_interval=0.03, max_bytes=4096, on_event=None):
|
||||
buf = bytearray()
|
||||
@@ -5933,7 +5896,7 @@ def main():
|
||||
print(f"translate-proxy ({BACKEND}) listening on http://127.0.0.1:{PORT}", flush=True)
|
||||
print(f"Target: {TARGET_URL}", flush=True)
|
||||
print(f"Models: {[m['id'] for m in MODELS]}", flush=True)
|
||||
if BACKEND in ("codebuff", "freebuff"):
|
||||
if BACKEND == "codebuff":
|
||||
_cb_pool.load_accounts(force=True)
|
||||
fb_status = _cb_pool.status()
|
||||
print(f"[multi-account] codebuff: {len(fb_status)} accounts loaded {[a['id'] for a in fb_status]}", flush=True)
|
||||
|
||||
Reference in New Issue
Block a user