Merge pull request #2 from roman-ryzenadvanced/cleanup/remove-windows-folder
chore: merge windows/ into src/ and remove duplicate folder
This commit is contained in:
@@ -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,8 +2051,8 @@ 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",
|
||||
font=("Consolas", 9))
|
||||
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))
|
||||
|
||||
# Bottom bar
|
||||
@@ -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()
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user