Merge pull request #3 from fix/gui-crof-consolidation

fix: GUI buttons, CROF gate, data dir consolidation, sticky proxy port
This commit is contained in:
Roman | RyzenAdvanced
2026-05-25 17:49:53 +04:00
committed by GitHub
Unverified
4 changed files with 77 additions and 21 deletions

View File

@@ -15,12 +15,12 @@ IS_WINDOWS = sys.platform == "win32"
if IS_WINDOWS: if IS_WINDOWS:
_local = os.environ.get("LOCALAPPDATA", str(Path.home() / "AppData" / "Local")) _local = os.environ.get("LOCALAPPDATA", str(Path.home() / "AppData" / "Local"))
PID_REGISTRY = Path(_local) / "codex-launcher" / "pids.json" PID_REGISTRY = Path(_local) / "codex-proxy" / "pids.json"
CODEX_DIR = Path.home() / ".codex" CODEX_DIR = Path.home() / ".codex"
_local_share = Path(_local) _local_share = Path(_local)
_cache = Path(_local) _cache = Path(_local)
else: else:
PID_REGISTRY = Path.home() / ".cache" / "codex-launcher" / "pids.json" PID_REGISTRY = Path.home() / ".cache" / "codex-proxy" / "pids.json"
CODEX_DIR = Path.home() / ".codex" CODEX_DIR = Path.home() / ".codex"
_local_share = Path.home() / ".local" / "share" _local_share = Path.home() / ".local" / "share"
_cache = Path.home() / ".cache" _cache = Path.home() / ".cache"

View File

@@ -2058,10 +2058,13 @@ class LauncherWin:
# Bottom bar # Bottom bar
bb = ttk.Frame(main) bb = ttk.Frame(main)
bb.pack(fill="x", pady=(6, 0)) bb.pack(fill="x", pady=(6, 0))
ttk.Button(bb, text="AI Assistant", command=self._open_assistant).pack(side="left") ttk.Button(bb, text="Clear Log", command=self._clear_log).pack(side="left")
self._restart_btn = ttk.Button(bb, text="Restart Proxy", command=self._restart_proxy, state="disabled")
self._restart_btn.pack(side="left", padx=(4, 0))
ttk.Button(bb, text="AI Assistant", command=self._open_assistant).pack(side="left", padx=(4, 0))
self._kill_btn = ttk.Button(bb, text="Kill && Cleanup", command=self._kill, state="disabled") self._kill_btn = ttk.Button(bb, text="Kill && Cleanup", command=self._kill, state="disabled")
self._kill_btn.pack(side="left", fill="x", expand=True, padx=(8, 0)) self._kill_btn.pack(side="left", fill="x", expand=True, padx=(8, 0))
ttk.Button(bb, text="View Log", command=lambda: open_file(str(LAUNCH_LOG))).pack(side="left") ttk.Button(bb, text="View Log", command=self._open_proxy_log_dir).pack(side="left")
ttk.Button(bb, text="Close", command=self._do_close).pack(side="left", padx=(8, 0)) ttk.Button(bb, text="Close", command=self._do_close).pack(side="left", padx=(8, 0))
self._rebuild_combo() self._rebuild_combo()
@@ -2079,6 +2082,25 @@ class LauncherWin:
self._log_text.see("end") self._log_text.see("end")
self._log_text.configure(state="disabled") self._log_text.configure(state="disabled")
def _clear_log(self):
self._log_text.configure(state="normal")
self._log_text.delete("1.0", "end")
self._log_text.configure(state="disabled")
def _restart_proxy(self):
self._kill()
ep_name = load_endpoints().get("default")
if not ep_name:
self.log("No default endpoint set.")
return
for ep in load_endpoints().get("endpoints", []):
if ep.get("name") == ep_name:
time.sleep(0.3)
start_proxy_for(ep, self.log)
self.log(f"Proxy restarted for {ep_name}")
return
self.log(f"Endpoint '{ep_name}' not found.")
def _log_dependency_status(self): def _log_dependency_status(self):
if self._cli_info: if self._cli_info:
_, ver = self._cli_info _, ver = self._cli_info
@@ -2191,6 +2213,18 @@ class LauncherWin:
def _open_benchmark(self): def _open_benchmark(self):
BenchmarkWindow(self._root) BenchmarkWindow(self._root)
def _open_proxy_log_dir(self):
log_dir = str(PROXY_CONFIG_DIR)
req_log = PROXY_CONFIG_DIR / "requests.log"
if IS_WINDOWS:
if req_log.exists():
os.startfile(str(req_log))
else:
os.startfile(log_dir)
else:
import subprocess as _sp
_sp.Popen(["xdg-open", log_dir])
def _open_assistant(self): def _open_assistant(self):
assist_path = str(Path(__file__).resolve().parent / "flet-codex-assist.py") assist_path = str(Path(__file__).resolve().parent / "flet-codex-assist.py")
if Path(assist_path).exists(): if Path(assist_path).exists():
@@ -2452,6 +2486,7 @@ class LauncherWin:
self._btn_codex_desktop.configure(state="disabled" if busy or not has_desk else "normal") self._btn_codex_desktop.configure(state="disabled" if busy or not has_desk else "normal")
self._btn_codex_cli.configure(state="disabled" if busy or not has_cli else "normal") self._btn_codex_cli.configure(state="disabled" if busy or not has_cli else "normal")
self._kill_btn.configure(state="normal" if busy else "disabled") self._kill_btn.configure(state="normal" if busy else "disabled")
self._restart_btn.configure(state="normal" if busy else "disabled")
self._root.after(0, _update) self._root.after(0, _update)
def _launch(self, target): def _launch(self, target):

View File

@@ -41,8 +41,8 @@ if IS_WINDOWS:
PROXY_CONFIG_DIR = _LOCAL_APPDATA / "codex-proxy" PROXY_CONFIG_DIR = _LOCAL_APPDATA / "codex-proxy"
CONFIG_DIR = HOME / ".codex" CONFIG_DIR = HOME / ".codex"
BIN_DIR = _LOCAL_APPDATA / "Programs" / "Codex-Launcher" BIN_DIR = _LOCAL_APPDATA / "Programs" / "Codex-Launcher"
LOG_DIR = _LOCAL_APPDATA / "codex-desktop" LOG_DIR = _LOCAL_APPDATA / "codex-proxy"
PID_REGISTRY = _LOCAL_APPDATA / "codex-launcher" / "pids.json" PID_REGISTRY = _LOCAL_APPDATA / "codex-proxy" / "pids.json"
_USAGE_STATS_FILE = _LOCAL_APPDATA / "codex-proxy" / "usage-stats.json" _USAGE_STATS_FILE = _LOCAL_APPDATA / "codex-proxy" / "usage-stats.json"
MONITORING_FILE = _LOCAL_APPDATA / "codex-proxy" / "monitoring-config.json" MONITORING_FILE = _LOCAL_APPDATA / "codex-proxy" / "monitoring-config.json"
INCIDENT_STORE_FILE = _LOCAL_APPDATA / "codex-proxy" / "incident-store.json" INCIDENT_STORE_FILE = _LOCAL_APPDATA / "codex-proxy" / "incident-store.json"
@@ -52,8 +52,8 @@ else:
PROXY_CONFIG_DIR = HOME / ".cache/codex-proxy" PROXY_CONFIG_DIR = HOME / ".cache/codex-proxy"
CONFIG_DIR = HOME / ".codex" CONFIG_DIR = HOME / ".codex"
BIN_DIR = HOME / ".local/bin" BIN_DIR = HOME / ".local/bin"
LOG_DIR = HOME / ".cache/codex-desktop" LOG_DIR = HOME / ".cache/codex-proxy"
PID_REGISTRY = HOME / ".cache" / "codex-launcher" / "pids.json" PID_REGISTRY = HOME / ".cache/codex-proxy" / "pids.json"
_USAGE_STATS_FILE = HOME / ".cache/codex-proxy/usage-stats.json" _USAGE_STATS_FILE = HOME / ".cache/codex-proxy/usage-stats.json"
MONITORING_FILE = HOME / ".cache/codex-proxy/monitoring-config.json" MONITORING_FILE = HOME / ".cache/codex-proxy/monitoring-config.json"
INCIDENT_STORE_FILE = HOME / ".cache/codex-proxy/incident-store.json" INCIDENT_STORE_FILE = HOME / ".cache/codex-proxy/incident-store.json"
@@ -1388,9 +1388,18 @@ def safe_cleanup_owned(logfn=None):
_proxy_proc = None _proxy_proc = None
_proxy_port = None _proxy_port = None
_PROXY_PORT_FILE = PROXY_CONFIG_DIR / ".last-proxy-port"
def _pick_free_port(): def _pick_free_port():
saved = None
try:
saved = int(_PROXY_PORT_FILE.read_text().strip())
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("127.0.0.1", saved))
return saved
except (ValueError, OSError, FileNotFoundError):
pass
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("127.0.0.1", 0)) s.bind(("127.0.0.1", 0))
return s.getsockname()[1] return s.getsockname()[1]
@@ -1421,6 +1430,8 @@ def start_proxy_for(endpoint, logfn):
stop_proxy() stop_proxy()
port = _pick_free_port() port = _pick_free_port()
_proxy_port = port _proxy_port = port
_PROXY_PORT_FILE.parent.mkdir(parents=True, exist_ok=True)
_PROXY_PORT_FILE.write_text(str(port))
model_list = endpoint.get("models", []) model_list = endpoint.get("models", [])
if (endpoint.get("backend_type") or "").startswith("gemini-oauth") and (endpoint.get("oauth_provider") or "").startswith("google"): if (endpoint.get("backend_type") or "").startswith("gemini-oauth") and (endpoint.get("oauth_provider") or "").startswith("google"):
@@ -1507,6 +1518,8 @@ def start_bgp_proxy(pool, model, logfn):
stop_proxy() stop_proxy()
port = _pick_free_port() port = _pick_free_port()
_proxy_port = port _proxy_port = port
_PROXY_PORT_FILE.parent.mkdir(parents=True, exist_ok=True)
_PROXY_PORT_FILE.write_text(str(port))
bgp_ep = { bgp_ep = {
"name": pool["name"], "name": pool["name"],

View File

@@ -260,11 +260,6 @@ try:
for _f in os.listdir(_REQUESTS_DIR): for _f in os.listdir(_REQUESTS_DIR):
if _f.endswith(".tmp"): if _f.endswith(".tmp"):
os.remove(os.path.join(_REQUESTS_DIR, _f)) os.remove(os.path.join(_REQUESTS_DIR, _f))
for _f in os.listdir(_LOG_DIR):
if _f.startswith("proxy-") and _f.endswith(".json"):
os.remove(os.path.join(_LOG_DIR, _f))
if _f.startswith("models-") and _f.endswith(".json"):
os.remove(os.path.join(_LOG_DIR, _f))
except Exception: except Exception:
pass pass
_stats_path = os.path.join(_LOG_DIR, "usage-stats.json") _stats_path = os.path.join(_LOG_DIR, "usage-stats.json")
@@ -724,7 +719,6 @@ _GEMINI_AGENT_GUARDRAIL = (
"Always emit the actual tool call in the same response." "Always emit the actual tool call in the same response."
) )
_LOG_FILE = None
_LOG_FILE_LOCK = threading.Lock() _LOG_FILE_LOCK = threading.Lock()
def _fetch_antigravity_version(): def _fetch_antigravity_version():
@@ -1303,8 +1297,8 @@ _COMPACT_KEEP_RECENT = 10
_CROF_ADAPTIVE = { _CROF_ADAPTIVE = {
"fail_history": [], "fail_history": [],
"model_limits": {}, "model_limits": {},
"global_item_limit": 30, "global_item_limit": 80,
"min_keep_recent": 4, "min_keep_recent": 6,
} }
_BGP_STATS_PATH = os.path.join(_LOG_DIR, "bgp-route-stats.json") _BGP_STATS_PATH = os.path.join(_LOG_DIR, "bgp-route-stats.json")
@@ -1372,6 +1366,8 @@ def _sorted_bgp_routes():
return sorted(BGP_ROUTES, key=lambda r: _score_route(r, stats)) return sorted(BGP_ROUTES, key=lambda r: _score_route(r, stats))
def _crof_record(model, n_items, success): def _crof_record(model, n_items, success):
if TARGET_URL and "crof.ai" not in TARGET_URL:
return
if not isinstance(n_items, int) or n_items < 1: if not isinstance(n_items, int) or n_items < 1:
return return
entry = {"model": model, "items": n_items, "ok": success} entry = {"model": model, "items": n_items, "ok": success}
@@ -1397,7 +1393,8 @@ def _crof_record(model, n_items, success):
global_limit = v["limit"] global_limit = v["limit"]
_CROF_ADAPTIVE["global_item_limit"] = global_limit _CROF_ADAPTIVE["global_item_limit"] = global_limit
print(f"[crof-adaptive] model={model} items={n_items} {'OK' if success else 'FAIL'} -> limit={ml.get('limit',30)} global={global_limit}", file=sys.stderr) if TARGET_URL and "crof.ai" in TARGET_URL:
print(f"[crof-adaptive] model={model} items={n_items} {'OK' if success else 'FAIL'} -> limit={ml.get('limit',30)} global={global_limit}", file=sys.stderr)
def _crof_item_limit(model): def _crof_item_limit(model):
ml = _CROF_ADAPTIVE["model_limits"].get(model, {}) ml = _CROF_ADAPTIVE["model_limits"].get(model, {})
@@ -1442,7 +1439,8 @@ def _crof_compact_for_retry(input_data, model):
summary_lines.append(_item_summary(item, max_len=120)) summary_lines.append(_item_summary(item, max_len=120))
summary_msg = {"type": "message", "role": "user", "content": [{"type": "input_text", "text": "\n".join(summary_lines)}]} summary_msg = {"type": "message", "role": "user", "content": [{"type": "input_text", "text": "\n".join(summary_lines)}]}
print(f"[crof-adaptive] RETRY compact: {len(input_data)} -> {len(head)+1+len(tail)} (limit={limit}, keep={len(tail)})", file=sys.stderr) if TARGET_URL and "crof.ai" in TARGET_URL:
print(f"[crof-adaptive] RETRY compact: {len(input_data)} -> {len(head)+1+len(tail)} (limit={limit}, keep={len(tail)})", file=sys.stderr)
return head + [summary_msg] + tail return head + [summary_msg] + tail
def _item_summary(item, max_len=200): def _item_summary(item, max_len=200):
@@ -1653,7 +1651,7 @@ def _estimate_tokens(obj):
def _adaptive_compact(input_data, model, policy=None): def _adaptive_compact(input_data, model, policy=None):
policy = policy or {} policy = policy or {}
context_size = int(policy.get("context_size", _context_limit_for_model(model))) context_size = int(policy.get("context_size", _context_limit_for_model(model)))
input_budget = int(context_size * 0.60) input_budget = int(context_size * 0.80)
estimated = _estimate_tokens(input_data) estimated = _estimate_tokens(input_data)
if estimated <= input_budget: if estimated <= input_budget:
return input_data, False return input_data, False
@@ -4297,7 +4295,8 @@ class Handler(http.server.BaseHTTPRequestHandler):
body["input"] = input_data body["input"] = input_data
crof_limit = _crof_item_limit(model) crof_limit = _crof_item_limit(model)
if not compacted and isinstance(input_data, list) and len(input_data) > crof_limit: _crof_eligible = TARGET_URL and "crof.ai" in TARGET_URL
if _crof_eligible and not compacted and isinstance(input_data, list) and len(input_data) > crof_limit:
print(f"[crof-adaptive] proactive compact: {len(input_data)} items > limit {crof_limit}", file=sys.stderr) print(f"[crof-adaptive] proactive compact: {len(input_data)} items > limit {crof_limit}", file=sys.stderr)
input_data = _crof_compact_for_retry(input_data, model) input_data = _crof_compact_for_retry(input_data, model)
body = dict(body) body = dict(body)
@@ -5091,7 +5090,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
print(f"[provider-sensor] synthetic retry failed: {e}", file=sys.stderr) print(f"[provider-sensor] synthetic retry failed: {e}", file=sys.stderr)
# Auto-retry on finish_reason=length with no content due to too much context. # Auto-retry on finish_reason=length with no content due to too much context.
if finish_reason == "length" and not has_content and isinstance(input_data, list) and len(input_data) > 5: if finish_reason == "length" and not has_content and isinstance(input_data, list) and len(input_data) > 5 and TARGET_URL and "crof.ai" in TARGET_URL:
print(f"[crof-adaptive] RETRY: finish_reason=length with no content, compacting {n_items} items", file=sys.stderr) print(f"[crof-adaptive] RETRY: finish_reason=length with no content, compacting {n_items} items", file=sys.stderr)
new_input = _crof_compact_for_retry(input_data, model) new_input = _crof_compact_for_retry(input_data, model)
if len(new_input) < len(input_data): if len(new_input) < len(input_data):
@@ -6030,6 +6029,15 @@ def main():
global SERVER, _START_TIME global SERVER, _START_TIME
_START_TIME = time.time() _START_TIME = time.time()
_init_runtime() _init_runtime()
try:
_current_cfg = os.path.basename(args.config) if args.config else ""
for _f in os.listdir(_LOG_DIR):
if _f.startswith("proxy-") and _f.endswith(".json") and _f != _current_cfg:
os.remove(os.path.join(_LOG_DIR, _f))
if _f.startswith("models-") and _f.endswith(".json"):
os.remove(os.path.join(_LOG_DIR, _f))
except Exception:
pass
signal.signal(signal.SIGINT, _handle_shutdown_signal) signal.signal(signal.SIGINT, _handle_shutdown_signal)
if _IS_WINDOWS: if _IS_WINDOWS:
if hasattr(signal, "SIGBREAK"): if hasattr(signal, "SIGBREAK"):