v2.3.2: add Google Gemini provider with OAuth support
- Two presets: API Key and OAuth modes - OAuth Login button: full Google OAuth2 flow with auto-refresh - Auto-refreshes expired access tokens using refresh_token - Gemini OpenAI-compatible endpoint works with existing proxy - Models: gemini-2.5-flash, gemini-2.5-pro, gemini-2.0-flash, etc.
This commit is contained in:
@@ -79,11 +79,47 @@ PORT = CONFIG["port"]
|
||||
BACKEND = CONFIG["backend_type"]
|
||||
TARGET_URL = CONFIG["target_url"].rstrip("/")
|
||||
API_KEY = CONFIG["api_key"]
|
||||
OAUTH_PROVIDER = CONFIG.get("oauth_provider", "")
|
||||
MODELS = CONFIG["models"]
|
||||
CC_VERSION = CONFIG.get("cc_version", "")
|
||||
REASONING_ENABLED = CONFIG.get("reasoning_enabled", True)
|
||||
REASONING_EFFORT = CONFIG.get("reasoning_effort", "medium")
|
||||
|
||||
def _refresh_oauth_token():
|
||||
if OAUTH_PROVIDER != "google":
|
||||
return API_KEY
|
||||
token_path = os.path.join(os.path.expanduser("~"), ".cache", "codex-proxy", "google-oauth-token.json")
|
||||
if not os.path.exists(token_path):
|
||||
return API_KEY
|
||||
try:
|
||||
with open(token_path) as f:
|
||||
tokens = json.load(f)
|
||||
if tokens.get("expires_at", 0) > time.time() + 60:
|
||||
return tokens.get("access_token", API_KEY)
|
||||
client_id = tokens.get("client_id", "")
|
||||
client_secret = tokens.get("client_secret", "")
|
||||
refresh_token = tokens.get("refresh_token", "")
|
||||
if not all([client_id, client_secret, refresh_token]):
|
||||
return tokens.get("access_token", API_KEY)
|
||||
print("[oauth] refreshing Google access token...", file=sys.stderr)
|
||||
data = urllib.parse.urlencode({
|
||||
"client_id": client_id, "client_secret": client_secret,
|
||||
"refresh_token": refresh_token, "grant_type": "refresh_token",
|
||||
}).encode()
|
||||
req = urllib.request.Request("https://oauth2.googleapis.com/token", data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"})
|
||||
resp = urllib.request.urlopen(req, timeout=30)
|
||||
new_tokens = json.loads(resp.read())
|
||||
tokens["access_token"] = new_tokens.get("access_token", tokens.get("access_token"))
|
||||
tokens["expires_at"] = time.time() + new_tokens.get("expires_in", 3600)
|
||||
with open(token_path, "w") as f:
|
||||
json.dump(tokens, f, indent=2)
|
||||
print("[oauth] token refreshed OK", file=sys.stderr)
|
||||
return tokens["access_token"]
|
||||
except Exception as e:
|
||||
print(f"[oauth] refresh failed: {e}", file=sys.stderr)
|
||||
return API_KEY
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Shared helpers
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
@@ -1000,9 +1036,10 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
chat_body["reasoning_effort"] = REASONING_EFFORT
|
||||
|
||||
target = upstream_target(TARGET_URL, "/chat/completions")
|
||||
effective_key = _refresh_oauth_token()
|
||||
fwd = forwarded_headers(self.headers, {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {API_KEY}",
|
||||
"Authorization": f"Bearer {effective_key}",
|
||||
}, browser_ua=True)
|
||||
print(f"[translate-proxy] POST {target} model={model} stream={stream} items={len(input_data) if isinstance(input_data,list) else 1} ua={fwd.get('User-Agent','')[:50]}", file=sys.stderr)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user