feat: add legacy Gemini 3 model support for Antigravity quota and implement E2E test suite

This commit is contained in:
tctinh
2026-01-01 09:25:15 +07:00
parent c3f0fa26fb
commit 219069a2a3
3 changed files with 280 additions and 18 deletions

View File

@@ -107,6 +107,21 @@ Create `~/.config/opencode/opencode.json`:
"limit": { "context": 1048576, "output": 65536 },
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
},
"gemini-3-pro-low": {
"name": "Gemini 3 Pro Low (Antigravity)",
"limit": { "context": 1048576, "output": 65535 },
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
},
"gemini-3-pro-high": {
"name": "Gemini 3 Pro High (Antigravity)",
"limit": { "context": 1048576, "output": 65535 },
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
},
"gemini-3-flash": {
"name": "Gemini 3 Flash (Antigravity)",
"limit": { "context": 1048576, "output": 65536 },
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
},
"antigravity-claude-sonnet-4-5": {
"name": "Claude Sonnet 4.5 (Antigravity)",
"limit": { "context": 200000, "output": 64000 },
@@ -141,7 +156,7 @@ Create `~/.config/opencode/opencode.json`:
"name": "Claude Opus 4.5 Think High (Antigravity)",
"limit": { "context": 200000, "output": 64000 },
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
},
}
}
}
}
@@ -175,17 +190,18 @@ Models with `antigravity-` prefix use Antigravity quota:
| `google/antigravity-claude-opus-4-5-thinking-medium` | Opus with 16K thinking budget |
| `google/antigravity-claude-opus-4-5-thinking-high` | Opus with 32K thinking budget |
> **Backward compatibility:** Old model names (`gemini-3-pro-low`, `gemini-3-pro-high`, `gemini-3-flash`) still work as a fallback. However, you should update to the `antigravity-` prefix for stability. See [Migration Guide](#migration-guide-v127).
### Gemini CLI Quota
Models without `antigravity-` prefix use Gemini CLI quota:
Models with `-preview` suffix use Gemini CLI quota:
| Model | Description |
|-------|-------------|
| `google/gemini-2.5-flash` | Gemini 2.5 Flash |
| `google/gemini-2.5-pro` | Gemini 2.5 Pro |
| `google/gemini-3-flash` | Gemini 3 Flash |
| `google/gemini-3-pro-low` | Gemini 3 Pro with low thinking |
| `google/gemini-3-pro-high` | Gemini 3 Pro with high thinking |
| `google/gemini-3-flash-preview` | Gemini 3 Flash (preview) |
| `google/gemini-3-pro-preview` | Gemini 3 Pro (preview) |
<details>
<summary><b>Full models configuration</b></summary>
@@ -212,6 +228,21 @@ Models without `antigravity-` prefix use Gemini CLI quota:
"limit": { "context": 1048576, "output": 65536 },
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
},
"gemini-3-pro-low": {
"name": "Gemini 3 Pro Low (Antigravity)",
"limit": { "context": 1048576, "output": 65535 },
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
},
"gemini-3-pro-high": {
"name": "Gemini 3 Pro High (Antigravity)",
"limit": { "context": 1048576, "output": 65535 },
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
},
"gemini-3-flash": {
"name": "Gemini 3 Flash (Antigravity)",
"limit": { "context": 1048576, "output": 65536 },
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
},
"antigravity-claude-sonnet-4-5": {
"name": "Claude Sonnet 4.5 (Antigravity)",
"limit": { "context": 200000, "output": 64000 },
@@ -247,6 +278,26 @@ Models without `antigravity-` prefix use Gemini CLI quota:
"limit": { "context": 200000, "output": 64000 },
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
},
"gemini-2.5-flash": {
"name": "Gemini 2.5 Flash (CLI)",
"limit": { "context": 1048576, "output": 65536 },
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
},
"gemini-2.5-pro": {
"name": "Gemini 2.5 Pro (CLI)",
"limit": { "context": 1048576, "output": 65536 },
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
},
"gemini-3-flash-preview": {
"name": "Gemini 3 Flash Preview (CLI)",
"limit": { "context": 1048576, "output": 65536 },
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
},
"gemini-3-pro-preview": {
"name": "Gemini 3 Pro Preview (CLI)",
"limit": { "context": 1048576, "output": 65535 },
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
}
}
}
}
@@ -415,14 +466,30 @@ When spawning parallel subagents, multiple processes may select the same account
If upgrading from v1.2.6 or earlier:
### Breaking Changes
### What Changed
| Old (v1.2.6) | New (v1.2.7+) |
|--------------|---------------|
| `gemini-3-pro` | `google/antigravity-gemini-3-pro-low` |
| `claude-sonnet-4-5` | `google/antigravity-claude-sonnet-4-5` |
v1.2.7+ uses explicit prefixes to distinguish quota sources:
### Step 1: Clear Old Tokens (Optional do this if you have issue cannot call models)
| Model Type | New Name (Recommended) | Old Name (Still Works) |
|------------|------------------------|------------------------|
| Gemini 3 (Antigravity) | `antigravity-gemini-3-pro-low` | `gemini-3-pro-low` |
| Gemini 3 (Antigravity) | `antigravity-gemini-3-pro-high` | `gemini-3-pro-high` |
| Gemini 3 (Antigravity) | `antigravity-gemini-3-flash` | `gemini-3-flash` |
| Gemini 3 (CLI) | `gemini-3-pro-preview` | N/A |
| Claude | `antigravity-claude-sonnet-4-5` | `claude-sonnet-4-5` |
### Action Required
**Update your config to use `antigravity-` prefix:**
```diff
- "gemini-3-pro-low": { ... }
+ "antigravity-gemini-3-pro-low": { ... }
```
> **Why update?** Old names work now as a fallback, but this depends on Gemini CLI using `-preview` suffix. If Google removes `-preview` in the future, old names may route to the wrong quota. The `antigravity-` prefix is explicit and stable.
### Step 1: Clear Old Tokens (Optional - do this if you have issues calling models)
```bash
rm -rf ~/.config/opencode/antigravity-account.json

171
script/test-gemini-cli-e2e.sh Executable file
View File

@@ -0,0 +1,171 @@
#!/bin/bash
# Gemini CLI E2E Test Suite
# Tests gemini-cli models routing through cloudcode-pa.googleapis.com/v1internal
#
# Models tested:
# 1. google/gemini-2.5-pro
# 2. google/gemini-2.5-flash
# 3. google/gemini-3-pro-preview
# 4. google/gemini-3-flash-preview
set -euo pipefail
PASS=0
FAIL=0
SKIP=0
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_pass() { echo -e "${GREEN}✓ PASS${NC}: $1"; ((PASS++)); }
log_fail() { echo -e "${RED}✗ FAIL${NC}: $1"; ((FAIL++)); }
log_skip() { echo -e "${YELLOW}○ SKIP${NC}: $1"; ((SKIP++)); }
log_info() { echo -e " ${BLUE}${NC} $1"; }
# Check for common errors
check_auth_error() {
grep -qiE "insufficient.*scope|authentication|unauthorized|403|401" "$1" 2>/dev/null && return 0 || return 1
}
check_quota_error() {
grep -qiE "quota|rate.limit|429|resource.exhausted" "$1" 2>/dev/null && return 0 || return 1
}
check_model_error() {
grep -qiE "model.*not.found|invalid.*model|404" "$1" 2>/dev/null && return 0 || return 1
}
# Test a single model
test_model() {
local model="$1"
local test_name="$2"
local log_file="/tmp/gemini-cli-e2e-${test_name}.log"
log_info "Testing $model..."
# Run opencode with a simple prompt
timeout 60 opencode run -m "$model" \
"Reply with exactly: GEMINI_CLI_OK" \
2>&1 > "$log_file" || true
# Check for various error conditions
if check_auth_error "$log_file"; then
log_fail "$test_name - Authentication/scope error (check OAuth scopes)"
log_info "This likely means routing to wrong endpoint"
return 1
elif check_quota_error "$log_file"; then
log_skip "$test_name - Quota exhausted (not a routing issue)"
return 0
elif check_model_error "$log_file"; then
log_fail "$test_name - Model not found"
return 1
elif grep -qi "GEMINI_CLI_OK\|working\|ok\|hello" "$log_file"; then
log_pass "$test_name"
return 0
elif grep -qi "error\|exception\|failed" "$log_file"; then
log_fail "$test_name - Unknown error"
log_info "Check $log_file for details"
return 1
else
# No obvious error, assume success
log_pass "$test_name"
return 0
fi
}
echo "════════════════════════════════════════════════════════════"
echo " Gemini CLI E2E Test Suite"
echo " Testing cloudcode-pa.googleapis.com/v1internal routing"
echo "════════════════════════════════════════════════════════════"
echo ""
echo "Test 1: google/gemini-2.5-flash"
test_model "google/gemini-2.5-flash" "gemini-2.5-flash" || true
echo ""
echo "Test 2: google/gemini-2.5-pro"
test_model "google/gemini-2.5-pro" "gemini-2.5-pro" || true
echo ""
echo "Test 3: google/gemini-3-flash-preview"
test_model "google/gemini-3-flash-preview" "gemini-3-flash-preview" || true
echo ""
echo "Test 4: google/gemini-3-pro-preview"
test_model "google/gemini-3-pro-preview" "gemini-3-pro-preview" || true
echo ""
# Test 5: Cross-model session (gemini-cli → antigravity)
echo "Test 5: Cross-model session (gemini-cli → antigravity-gemini)"
log_info "Step 1: Start with gemini-2.5-flash..."
timeout 60 opencode run -m google/gemini-2.5-flash \
"Say: SESSION_START" \
2>&1 > /tmp/gemini-cli-e2e-cross-s1.log || true
# Get session ID
sleep 1
SID=$(opencode session list 2>/dev/null | grep -oP 'ses_[a-zA-Z0-9]+' | head -1 || true)
if [ -z "$SID" ]; then
log_fail "Test 5 - No session ID created"
else
log_info "Session: $SID"
log_info "Step 2: Switch to antigravity-gemini-3-flash..."
timeout 60 opencode run -s "$SID" -m google/antigravity-gemini-3-flash \
"Say: SESSION_CONTINUE" \
2>&1 > /tmp/gemini-cli-e2e-cross-s2.log || true
if check_auth_error /tmp/gemini-cli-e2e-cross-s2.log; then
log_fail "Test 5 - Auth error on cross-model switch"
else
log_pass "Test 5 - Cross-model session (gemini-cli → antigravity)"
fi
fi
echo ""
# Test 6: Reverse cross-model (antigravity → gemini-cli)
echo "Test 6: Cross-model session (antigravity → gemini-cli)"
log_info "Step 1: Start with antigravity-gemini-3-pro-low..."
timeout 60 opencode run -m google/antigravity-gemini-3-pro-low \
"Say: ANTIGRAVITY_START" \
2>&1 > /tmp/gemini-cli-e2e-reverse-s1.log || true
sleep 1
SID=$(opencode session list 2>/dev/null | grep -oP 'ses_[a-zA-Z0-9]+' | head -1 || true)
if [ -z "$SID" ]; then
log_fail "Test 6 - No session ID created"
else
log_info "Session: $SID"
log_info "Step 2: Switch to gemini-2.5-pro..."
timeout 60 opencode run -s "$SID" -m google/gemini-2.5-pro \
"Say: GEMINI_CLI_CONTINUE" \
2>&1 > /tmp/gemini-cli-e2e-reverse-s2.log || true
if check_auth_error /tmp/gemini-cli-e2e-reverse-s2.log; then
log_fail "Test 6 - Auth error on reverse cross-model switch"
else
log_pass "Test 6 - Cross-model session (antigravity → gemini-cli)"
fi
fi
echo ""
echo "════════════════════════════════════════════════════════════"
echo " Test Results Summary"
echo "════════════════════════════════════════════════════════════"
echo -e " ${GREEN}Passed${NC}: $PASS"
echo -e " ${RED}Failed${NC}: $FAIL"
echo -e " ${YELLOW}Skipped${NC}: $SKIP"
echo ""
if [ $FAIL -gt 0 ]; then
echo -e "${RED}Some tests failed!${NC}"
echo "Log files: /tmp/gemini-cli-e2e-*.log"
exit 1
else
echo -e "${GREEN}All Gemini CLI tests passed!${NC}"
exit 0
fi

View File

@@ -71,6 +71,26 @@ const QUOTA_PREFIX_REGEX = /^antigravity-/i;
*/
const ANTIGRAVITY_ONLY_MODELS = /^(claude|gpt)/i;
/**
* Legacy Gemini 3 model names that should route to Antigravity quota.
*
* Backward compatibility: Since Gemini CLI now uses -preview suffix
* (gemini-3-pro-preview, gemini-3-flash-preview), old model names
* without -preview can safely route to Antigravity quota.
*
* Matches:
* - gemini-3-pro-low, gemini-3-pro-high
* - gemini-3-flash, gemini-3-flash-low, gemini-3-flash-medium, gemini-3-flash-high
*
* Does NOT match:
* - gemini-3-pro-preview (Gemini CLI)
* - gemini-3-flash-preview (Gemini CLI)
* - antigravity-gemini-3-* (already handled by prefix)
*
* WARNING: This may break if Google/Opencode removes the -preview suffix.
*/
const LEGACY_ANTIGRAVITY_GEMINI3 = /^gemini-3-(pro-(low|high)|flash(-low|-medium|-high)?)$/i;
/**
* Models that support thinking tier suffixes.
* Only these models should have -low/-medium/-high stripped as thinking tiers.
@@ -129,17 +149,20 @@ function isThinkingCapableModel(model: string): boolean {
/**
* Resolves a model name with optional tier suffix and quota prefix to its actual API model name
* and corresponding thinking configuration.
*
*
* Quota routing:
* - "antigravity-" prefix → Antigravity quota
* - Claude/GPT models → Antigravity quota (auto, these only exist on Antigravity)
* - Legacy Gemini 3 names (gemini-3-pro-low, gemini-3-flash, etc.) → Antigravity quota (backward compat)
* - Other models → Gemini CLI quota (default)
*
*
* Examples:
* - "gemini-2.5-flash" → { actualModel: "gemini-2.5-flash", quotaPreference: "gemini-cli" }
* - "antigravity-gemini-3-pro-high" → { actualModel: "gemini-3-pro", thinkingLevel: "high", quotaPreference: "antigravity" }
* - "claude-sonnet-4-5-thinking-medium" → { actualModel: "claude-sonnet-4-5-thinking", thinkingBudget: 16384, quotaPreference: "antigravity" }
*
* - "gemini-2.5-flash" → { quotaPreference: "gemini-cli" }
* - "gemini-3-pro-preview" → { quotaPreference: "gemini-cli" } (Gemini CLI uses -preview)
* - "gemini-3-pro-low" → { quotaPreference: "antigravity" } (legacy name, backward compat)
* - "antigravity-gemini-3-pro-high" → { quotaPreference: "antigravity" } (explicit prefix)
* - "claude-sonnet-4-5-thinking-medium" → { quotaPreference: "antigravity" } (Claude only on Antigravity)
*
* @param requestedModel - The model name from the request
* @returns Resolved model with thinking configuration
*/
@@ -151,7 +174,8 @@ export function resolveModelWithTier(requestedModel: string): ResolvedModel {
const baseName = tier ? modelWithoutQuota.replace(TIER_REGEX, "") : modelWithoutQuota;
const isAntigravityOnly = ANTIGRAVITY_ONLY_MODELS.test(modelWithoutQuota);
const quotaPreference = isAntigravity || isAntigravityOnly ? "antigravity" : "gemini-cli";
const isLegacyAntigravity = LEGACY_ANTIGRAVITY_GEMINI3.test(modelWithoutQuota);
const quotaPreference = isAntigravity || isAntigravityOnly || isLegacyAntigravity ? "antigravity" : "gemini-cli";
const explicitQuota = isAntigravity;
const isGemini3 = modelWithoutQuota.toLowerCase().startsWith("gemini-3");