chore: merge windows/ into src/ and remove duplicate folder

Cross-platform support lives in src/ via _IS_WINDOWS checks.
Merges latest upstream additions from windows/ (OAuth secrets,
ANTIGRAVITY_MODELS, changelog v3.10.5) into src/ files, then
removes the redundant windows/ folder.
This commit is contained in:
cobra91
2026-05-25 15:09:07 +02:00
Unverified
parent c203cabb01
commit 17126bc111
4 changed files with 206 additions and 4878 deletions

View File

@@ -30,7 +30,8 @@ from codex_launcher_lib import (
IS_WINDOWS, HOME, CONFIG, CONFIG_BAK, CONFIG_TXN,
ENDPOINTS_FILE, BGP_POOLS_FILE, LAUNCH_LOG, LOG_DIR,
PROXY_CONFIG_DIR, BIN_DIR, PROXY, CLEANUP, PID_REGISTRY,
PROVIDER_PRESETS, CHANGELOG, DEFAULT_CONFIG,
PROVIDER_PRESETS, CHANGELOG, DEFAULT_CONFIG, OAUTH_SECRETS_PATH,
ANTIGRAVITY_MODELS,
safe_name, label_for_backend, normalize_model_id, normalize_base_url,
parse_model_list, now_utc_iso, apply_provider_preset,
load_endpoints, save_endpoints, load_bgp_pools, save_bgp_pools,
@@ -48,6 +49,7 @@ from codex_launcher_lib import (
load_incident_store, save_incident_store, load_usage_stats,
monitoring_log,
IncidentStore, AIDiagnosticAgent, HealthWatcher,
load_oauth_secrets, save_oauth_secrets,
_usage_theme, UA,
)
@@ -378,9 +380,11 @@ class EditEndpointDialog:
is_antigravity = oauth_provider == "google-antigravity"
token_path = str(PROXY_CONFIG_DIR / ("google-antigravity-oauth-token.json" if is_antigravity else "google-cli-oauth-token.json"))
_sec = load_oauth_secrets().get("antigravity" if is_antigravity else "gemini_cli", {})
CLIENT_ID = _sec.get("client_id", "")
CLIENT_SECRET = _sec.get("client_secret", "")
if is_antigravity:
CLIENT_ID = ""
CLIENT_SECRET = ""
SCOPES = [
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/userinfo.email",
@@ -393,8 +397,6 @@ class EditEndpointDialog:
callback_path = "/oauth-callback"
provider_kind = "antigravity"
else:
CLIENT_ID = ""
CLIENT_SECRET = ""
SCOPES = [
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/userinfo.email",
@@ -436,7 +438,7 @@ class EditEndpointDialog:
oauth_dlg.grab_set()
tk.Label(oauth_dlg, text="Sign in with Google", font=("Segoe UI", 11, "bold")).pack(padx=16, pady=(12, 0), anchor="w")
tk.Label(oauth_dlg, text="Emulating Gemini CLI OAuth -- no client_secret.json needed.").pack(padx=16, anchor="w")
tk.Label(oauth_dlg, text=f"Using OAuth credentials from {OAUTH_SECRETS_PATH}").pack(padx=16, anchor="w")
link_lbl = tk.Label(oauth_dlg, text="Click here to open Google authorization", fg="blue", cursor="hand2")
link_lbl.pack(padx=16, pady=(8, 0), anchor="w")
@@ -535,12 +537,7 @@ class EditEndpointDialog:
found_models = []
if is_antigravity:
found_models = [
"gemini-2.5-flash", "gemini-2.5-pro",
"gemini-3-flash-preview", "gemini-3-pro-preview", "gemini-3.1-pro-preview",
"antigravity-gemini-3-flash", "antigravity-gemini-3-pro",
"antigravity-claude-sonnet-4-6", "antigravity-claude-opus-4-6-thinking",
]
found_models = list(ANTIGRAVITY_MODELS)
else:
found_models = ["gemini-2.5-flash", "gemini-2.5-pro"]
if found_models:
@@ -1944,21 +1941,20 @@ class BenchmarkWindow:
class LauncherWin:
def __init__(self, root):
self._root = root
self._root.title("Codex Launcher")
self._root.geometry("580x520")
self._root.minsize(480, 400)
self._proc = None
self._endpoints_data = load_endpoints()
self._refresh_running = False
recover_config_if_needed()
main = ttk.Frame(root, padding=12)
main = ttk.Frame(root, padding=16)
main.pack(fill="both", expand=True)
main.pack_propagate(False)
# Title
hdr = ttk.Frame(main)
hdr.pack(fill="x")
ttk.Label(hdr, text="Codex Launcher v3.8.1", font=("Segoe UI", 13, "bold")).pack(side="left")
ttk.Label(hdr, text=f"Codex Launcher v{CHANGELOG[0][0]}", font=("Segoe UI", 13, "bold")).pack(side="left")
# Toolbar — two rows to fit all buttons
tb1 = ttk.Frame(main)
@@ -1969,6 +1965,7 @@ class LauncherWin:
ttk.Button(tb1, text="Usage", command=self._open_usage).pack(side="left", padx=(6, 0))
ttk.Button(tb1, text="Benchmark", command=self._open_benchmark).pack(side="left", padx=(6, 0))
ttk.Button(tb1, text="History", command=self._open_history).pack(side="left", padx=(6, 0))
ttk.Button(tb1, text="OAuth Secrets", command=self._edit_oauth_secrets).pack(side="left", padx=(6, 0))
ttk.Button(tb1, text="Changelog", command=self._show_changelog).pack(side="right")
# Detection status — one row per item so long paths don't truncate
@@ -2054,7 +2051,7 @@ class LauncherWin:
self._btn_codex_cli.pack(side="left", fill="x", expand=True)
# Log area
self._log_text = scrolledtext.ScrolledText(main, height=8, state="disabled", wrap="word",
self._log_text = scrolledtext.ScrolledText(main, height=10, state="disabled", wrap="word",
font=("Consolas", 9))
self._log_text.pack(fill="both", expand=True, pady=(8, 0))
@@ -2199,6 +2196,92 @@ class LauncherWin:
if Path(assist_path).exists():
subprocess.Popen([sys.executable, assist_path], creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if IS_WINDOWS else 0)
def _edit_oauth_secrets(self):
data = load_oauth_secrets()
if not data:
data = {"antigravity": {"client_id": "", "client_secret": ""},
"gemini_cli": {"client_id": "", "client_secret": ""}}
dlg = tk.Toplevel(self._root)
dlg.title("OAuth 2.0 Client Secrets")
dlg.geometry("600x450")
dlg.transient(self._root)
dlg.grab_set()
frame = ttk.Frame(dlg, padding=16)
frame.pack(fill="both", expand=True)
ttk.Label(frame, text="Google OAuth 2.0 credentials", font=("Segoe UI", 10, "bold")).pack(anchor="w")
ttk.Label(frame, text=f"Stored locally in {OAUTH_SECRETS_PATH}", foreground="gray").pack(anchor="w", pady=(0, 8))
fields = {}
nf = ttk.Frame(frame)
nf.pack(fill="x")
row = 0
for section_key, section_label in [("antigravity", "Antigravity (CloudCode)"), ("gemini_cli", "Gemini CLI")]:
ttk.Label(nf, text=f"\n{section_label}", font=("Segoe UI", 9, "bold")).grid(row=row, column=0, columnspan=3, sticky="w", pady=(8, 2))
row += 1
sec = data.get(section_key, {})
import_btn = ttk.Button(nf, text="Import JSON",
command=lambda sk=section_key: self._import_oauth_json(fields, sk))
import_btn.grid(row=row, column=2, padx=(4, 0), pady=2, sticky="e")
for fk, fl in [("client_id", "Client ID"), ("client_secret", "Client Secret")]:
ttk.Label(nf, text=fl + ":").grid(row=row, column=0, sticky="w", padx=(8, 4), pady=2)
entry = ttk.Entry(nf, width=60)
entry.insert(0, sec.get(fk, ""))
entry.grid(row=row, column=1, sticky="ew", pady=2)
if fk == "client_secret":
entry.configure(show="*")
fields[(section_key, fk)] = entry
row += 1
nf.columnconfigure(1, weight=1)
ttk.Label(frame, text="\nImport a client_secret_*.json from Google Cloud Console\nconsole.cloud.google.com → Credentials", foreground="gray").pack(anchor="w")
btnf = ttk.Frame(frame)
btnf.pack(fill="x", pady=(12, 0))
ttk.Button(btnf, text="Cancel", command=dlg.destroy).pack(side="right", padx=(4, 0))
save_btn = ttk.Button(btnf, text="Save")
save_btn.pack(side="right", padx=(4, 0))
def _save():
for (sk, fk), entry in fields.items():
if sk not in data:
data[sk] = {}
data[sk][fk] = entry.get().strip()
try:
save_oauth_secrets(data)
except Exception as e:
messagebox.showerror("Save failed", str(e), parent=dlg)
return
dlg.destroy()
save_btn.configure(command=_save)
def _import_oauth_json(self, fields, section_key):
path = filedialog.askopenfilename(
title="Import Google OAuth Client Secret JSON",
filetypes=[("JSON files", "*.json")])
if not path:
return
try:
with open(path, encoding="utf-8") 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")
if (section_key, "client_id") in fields:
fields[(section_key, "client_id")].delete(0, "end")
fields[(section_key, "client_id")].insert(0, cid)
if (section_key, "client_secret") in fields:
fields[(section_key, "client_secret")].delete(0, "end")
fields[(section_key, "client_secret")].insert(0, csec)
except Exception as e:
messagebox.showerror("Import failed", str(e))
# ── Watcher ──────────────────────────────────────────────────────
def _start_watcher(self):
@@ -2696,5 +2779,8 @@ if __name__ == "__main__":
create_default_endpoints()
root = tk.Tk()
root.title("Codex Launcher")
root.geometry("800x680")
root.minsize(640, 520)
app = LauncherWin(root)
root.mainloop()

View File

@@ -66,6 +66,7 @@ CONFIG_TXN = CONFIG_DIR / "config.toml.launcher-txn.json"
ENDPOINTS_FILE = CONFIG_DIR / "endpoints.json"
BGP_POOLS_FILE = CONFIG_DIR / "bgp-pools.json"
LAUNCH_LOG = LOG_DIR / "launcher.log"
OAUTH_SECRETS_PATH = HOME / ".config" / "codex-launcher" / "oauth-secrets.json"
if IS_WINDOWS:
PROXY = BIN_DIR / "translate-proxy.py"
@@ -82,6 +83,69 @@ model_catalog_json = ""
"""
CHANGELOG = [
("3.10.5", "2026-05-25", [
"Context compaction for Antigravity/Gemini OAuth — prevents token limit errors",
"Aggressive compaction policies at 60% of model context limit",
"Compaction for cloudcode-pa and googleapis provider policies",
"REST model IDs added to context size map (gemini-3-flash, etc.)",
"OAuth Secrets editor in GUI — update client ID/secret without editing files",
"Secrets stored in ~/.config/codex-launcher/oauth-secrets.json (not in repo)",
"Import JSON button — import client_secret_*.json from Google Cloud Console",
"All hardcoded OAuth secrets removed from source code",
"Antigravity model IDs fixed: display names → slug model IDs for REST API",
"Git history scrubbed of leaked credentials; pre-push hook installed",
"Antigravity REST API model IDs verified with live API testing",
"Gemini 3.5 Flash, 3.1 Pro, Claude 4.6, GPT-OSS 120B all working",
]),
("3.9.9", "2026-05-25", [
"Refresh Antigravity preset: Gemini 3.5 Flash, Gemini 3.1 Pro, Claude 4.6, GPT-OSS",
"Fix Antigravity alias map for tiered model IDs (high/medium/low/thinking)",
"Model context sizes for Gemini 3.5 Flash, 3.1 Pro, Claude 4.6, GPT-OSS 120B",
]),
("3.9.8", "2026-05-25", [
"Fix Desktop model leak — remap gpt-5.4-mini to user-selected model",
"send_json() catches BrokenPipeError globally — no crashes on disconnect",
"Proxy remaps Desktop forced models via CODEX_LAUNCHER_MODEL env",
]),
("3.9.7", "2026-05-25", [
"Forward real Codebuff error messages instead of generic 429",
"Return HTTP 200 with Responses API format for rate limits",
"Extract retryAfterMs from Codebuff 429 responses for cooldown",
"RateLimitError carries upstream message through all paths",
"BrokenPipeError fix on 'all accounts exhausted' response",
"Fix 3 SyntaxWarnings for invalid escape sequences",
"_codebuff_start_run returns actual error body",
]),
("3.9.6", "2026-05-25", [
"Fix Gemini follow-up turns: enforce latest user instruction as final turn",
"Edit-intent detection with tool-use nudge for file modifications",
"Thought signature preservation for Gemini 3 tool-call continuity",
"Smart tool output compaction: old=3000, recent=20000 chars",
"Multi-account rotation for codebuff, Google OAuth, API keys",
"/v1/accounts endpoint for account pool status",
]),
("3.9.0", "2026-05-24", [
"Multi-account rotation for OAuth providers (codebuff, Google, API keys)",
"Automatic failover on rate limit — next account used",
"Codebuff: accounts[] array in credentials.json",
"Google OAuth: multiple token files (google-*-oauth-token-N.json)",
"API keys: comma-separated keys rotate on 429 errors",
"/v1/accounts endpoint shows account pool status",
"x-codebuff-model and x-codebuff-instance-id headers",
]),
("3.8.4", "2026-05-24", [
"Codebuff streaming — SSE events reach Codex client",
"stream_buffered_events now called for codebuff",
"Codebuff OAuth built-in login flow (no external CLI)",
"Codebuff API: reverse-engineered www.codebuff.com endpoints",
"Codebuff session management with instance ID",
"Codebuff agent run lifecycle (start/finish) with model routing",
"Free DeepSeek V4 Pro, V4 Flash, Kimi K2.6, MiniMax M2.7",
"Reasoning mode works with codebuff (thinking tokens supported)",
"GUI: Sandbox mode selector (Read-only / Workspace / Full Access)",
"GUI: Approval mode selector (Untrusted / On Request / Full Auto)",
"GUI: Codebuff Login button in endpoint editor",
]),
("3.8.1", "2026-05-24", [
"Freebuff integration — free DeepSeek V4 Pro, V4 Flash, Kimi K2.6, MiniMax M2.7",
"Freebuff backend: auto agent-run lifecycle, credential detection, model routing",
@@ -974,6 +1038,9 @@ def endpoint_model_headers(endpoint):
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"
@@ -1006,6 +1073,40 @@ def refresh_endpoint_models(endpoint):
updated["default_model"] = ids[0]
return updated, None
# ═══════════════════════════════════════════════════════════════════════
# Antigravity model list (static — no /v1/models REST endpoint)
# ═══════════════════════════════════════════════════════════════════════
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)",
]
# ═══════════════════════════════════════════════════════════════════════
# OAuth secrets (local, never in repo)
# ═══════════════════════════════════════════════════════════════════════
def load_oauth_secrets():
try:
with open(OAUTH_SECRETS_PATH, encoding="utf-8") as f:
return json.load(f)
except Exception:
return {}
def save_oauth_secrets(data):
os.makedirs(os.path.dirname(OAUTH_SECRETS_PATH), exist_ok=True)
tmp = str(OAUTH_SECRETS_PATH) + ".tmp"
with open(tmp, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
os.replace(tmp, OAUTH_SECRETS_PATH)
# ═══════════════════════════════════════════════════════════════════════
# Doctor checks
# ═══════════════════════════════════════════════════════════════════════

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff