feat: Windows native support — tkinter GUI, cross-platform proxy
Based on v3.10.4 upstream. New files: - src/codex-launcher-gui.py: full tkinter GUI (10 classes) replacing GTK on Windows - src/codex_launcher_lib.py: shared cross-platform module (~1870 lines) - src/cleanup-codex-stale.py: cross-platform process cleanup translate-proxy.py Windows adaptations: - Platform-aware paths: %LOCALAPPDATA% for cache, %APPDATA% for config - Platform-aware credentials, signals, memory reporting, shell commands - UTF-8 encoding on all file writes (Windows cp1252 default) - FORCE_MODEL: proxy overrides Codex Desktop model selection - DeepSeek reasoning_content round-trip for multi-turn tool sessions - Cleanup orphan .tmp files at startup - requests.log rotation (2000 lines cap) - User-Agent: codex-launcher/1.0 in outbound HTTP requests
This commit is contained in:
101
src/cleanup-codex-stale.py
Normal file
101
src/cleanup-codex-stale.py
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Cleanup stale Codex Launcher processes and artifacts — cross-platform.
|
||||
|
||||
Kills registered process groups and removes stale PID/socket files left
|
||||
by previous Codex Launcher sessions.
|
||||
|
||||
Windows: uses taskkill /F /T /PID
|
||||
Linux: uses kill -TERM -- -PGID
|
||||
"""
|
||||
|
||||
import json, os, sys, subprocess, time
|
||||
from pathlib import Path
|
||||
|
||||
IS_WINDOWS = sys.platform == "win32"
|
||||
|
||||
if IS_WINDOWS:
|
||||
_local = os.environ.get("LOCALAPPDATA", str(Path.home() / "AppData" / "Local"))
|
||||
PID_REGISTRY = Path(_local) / "codex-launcher" / "pids.json"
|
||||
CODEX_DIR = Path.home() / ".codex"
|
||||
_local_share = Path(_local)
|
||||
_cache = Path(_local)
|
||||
else:
|
||||
PID_REGISTRY = Path.home() / ".cache" / "codex-launcher" / "pids.json"
|
||||
CODEX_DIR = Path.home() / ".codex"
|
||||
_local_share = Path.home() / ".local" / "share"
|
||||
_cache = Path.home() / ".cache"
|
||||
|
||||
|
||||
def kill_group(pid):
|
||||
if IS_WINDOWS:
|
||||
subprocess.run(["taskkill", "/F", "/T", "/PID", str(pid)],
|
||||
capture_output=True, timeout=10)
|
||||
else:
|
||||
import signal
|
||||
try:
|
||||
pgid = os.getpgid(pid)
|
||||
os.killpg(pgid, signal.SIGTERM)
|
||||
time.sleep(0.5)
|
||||
try:
|
||||
os.killpg(pgid, signal.SIGKILL)
|
||||
except OSError:
|
||||
pass
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
print("[cleanup] Cleaning up stale Codex Launcher processes...", file=sys.stderr)
|
||||
|
||||
if PID_REGISTRY.exists():
|
||||
try:
|
||||
with open(PID_REGISTRY) as f:
|
||||
registry = json.load(f)
|
||||
except Exception as e:
|
||||
print(f"[cleanup] Failed to read PID registry: {e}", file=sys.stderr)
|
||||
registry = {}
|
||||
|
||||
for kind, info in registry.items():
|
||||
pid = info.get("pid") if isinstance(info, dict) else info
|
||||
if pid and isinstance(pid, int):
|
||||
print(f"[cleanup] Killing {kind} (PID {pid})", file=sys.stderr)
|
||||
kill_group(pid)
|
||||
|
||||
try:
|
||||
PID_REGISTRY.unlink()
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
print("[cleanup] No PID registry found — nothing to stop", file=sys.stderr)
|
||||
|
||||
stale_files = []
|
||||
if IS_WINDOWS:
|
||||
stale_files = [
|
||||
_cache / "codex-desktop" / ".codex-desktop-pid",
|
||||
_cache / "codex-desktop" / ".webview-pid",
|
||||
]
|
||||
else:
|
||||
stale_files = [
|
||||
CODEX_DIR / ".launch-action-socket",
|
||||
CODEX_DIR / ".codex-desktop-launch-action",
|
||||
CODEX_DIR / ".codex-desktop-pid",
|
||||
CODEX_DIR / ".webview-pid",
|
||||
_local_share / "codex-desktop" / ".codex-desktop-pid",
|
||||
_local_share / "codex-desktop" / ".webview-pid",
|
||||
_cache / "codex-desktop" / ".codex-desktop-pid",
|
||||
_cache / "codex-desktop" / ".webview-pid",
|
||||
]
|
||||
|
||||
for fp in stale_files:
|
||||
try:
|
||||
if fp.exists():
|
||||
fp.unlink()
|
||||
print(f"[cleanup] Removed {fp}", file=sys.stderr)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
print("[cleanup] Done", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
2700
src/codex-launcher-gui.py
Normal file
2700
src/codex-launcher-gui.py
Normal file
File diff suppressed because it is too large
Load Diff
1972
src/codex_launcher_lib.py
Normal file
1972
src/codex_launcher_lib.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -160,6 +160,9 @@ import time, uuid, os, sys, argparse, threading, socket, collections, contextlib
|
||||
import dataclasses
|
||||
import http.client
|
||||
import selectors
|
||||
import tempfile
|
||||
|
||||
_IS_WINDOWS = sys.platform == "win32"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Config
|
||||
@@ -241,13 +244,28 @@ MODELS = []
|
||||
CC_VERSION = ""
|
||||
REASONING_ENABLED = True
|
||||
REASONING_EFFORT = "medium"
|
||||
FORCE_MODEL = ""
|
||||
BGP_ROUTES = []
|
||||
SERVER = None
|
||||
|
||||
_LOG_DIR = os.path.join(os.path.expanduser("~"), ".cache", "codex-proxy")
|
||||
if _IS_WINDOWS:
|
||||
_LOG_DIR = os.path.join(os.environ.get("LOCALAPPDATA", os.path.expanduser("~")), "codex-proxy")
|
||||
else:
|
||||
_LOG_DIR = os.path.join(os.path.expanduser("~"), ".cache", "codex-proxy")
|
||||
os.makedirs(_LOG_DIR, exist_ok=True)
|
||||
_REQUESTS_DIR = os.path.join(_LOG_DIR, "requests")
|
||||
os.makedirs(_REQUESTS_DIR, exist_ok=True)
|
||||
try:
|
||||
for _f in os.listdir(_REQUESTS_DIR):
|
||||
if _f.endswith(".tmp"):
|
||||
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:
|
||||
pass
|
||||
_stats_path = os.path.join(_LOG_DIR, "usage-stats.json")
|
||||
_provider_caps_path = os.path.join(_LOG_DIR, "provider-caps.json")
|
||||
_stats_lock = threading.Lock()
|
||||
@@ -257,7 +275,7 @@ _STATS_FLUSH_INTERVAL = 5.0
|
||||
_STATS = {}
|
||||
|
||||
try:
|
||||
_LOG_FILE = open(os.path.join(_LOG_DIR, "proxy.log"), "a")
|
||||
_LOG_FILE = open(os.path.join(_LOG_DIR, "proxy.log"), "a", encoding="utf-8")
|
||||
except Exception:
|
||||
_LOG_FILE = None
|
||||
|
||||
@@ -273,6 +291,9 @@ _deepseek_reasoning_store = {}
|
||||
_deepseek_reasoning_lock = threading.Lock()
|
||||
_MAX_DS_STORED = 100
|
||||
|
||||
_last_reasoning_store = {}
|
||||
_last_reasoning_lock = threading.Lock()
|
||||
|
||||
_crof_lock = threading.Lock()
|
||||
_provider_caps_lock = threading.Lock()
|
||||
_provider_caps = None
|
||||
@@ -302,7 +323,10 @@ _CODEBUFF_AGENT_MAP = {
|
||||
"moonshotai/kimi-k2.6": "base2-free-kimi",
|
||||
"minimax/minimax-m2.7": "base2-free",
|
||||
}
|
||||
_CODEBUFF_CREDS_PATH = os.path.join(os.path.expanduser("~"), ".config", "manicode", "credentials.json")
|
||||
if _IS_WINDOWS:
|
||||
_CODEBUFF_CREDS_PATH = os.path.join(os.environ.get("APPDATA", os.path.expanduser("~")), "manicode", "credentials.json")
|
||||
else:
|
||||
_CODEBUFF_CREDS_PATH = os.path.join(os.path.expanduser("~"), ".config", "manicode", "credentials.json")
|
||||
_codebuff_token_cache = {"token": None, "checked": 0}
|
||||
_codebuff_session_cache = {"instance_id": None, "expires": 0, "model": None}
|
||||
_codebuff_token_lock = threading.Lock()
|
||||
@@ -634,7 +658,7 @@ def _refresh_google_token(token_data, token_path):
|
||||
new_tokens = json.loads(resp.read())
|
||||
token_data["access_token"] = new_tokens.get("access_token", token_data.get("access_token"))
|
||||
token_data["expires_at"] = time.time() + new_tokens.get("expires_in", 3600)
|
||||
with open(token_path, "w") as f:
|
||||
with open(token_path, "w", encoding="utf-8") as f:
|
||||
json.dump(token_data, f, indent=2)
|
||||
print("[oauth] token refreshed OK", file=sys.stderr)
|
||||
return token_data["access_token"]
|
||||
@@ -727,7 +751,7 @@ def _fetch_antigravity_version():
|
||||
version = m.group(0)
|
||||
try:
|
||||
os.makedirs(os.path.dirname(cache_path), exist_ok=True)
|
||||
with open(cache_path, "w") as f:
|
||||
with open(cache_path, "w", encoding="utf-8") as f:
|
||||
json.dump({"version": version, "checked_at": time.time()}, f)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -762,6 +786,7 @@ def _init_runtime():
|
||||
CC_VERSION = CONFIG.get("cc_version", "")
|
||||
REASONING_ENABLED = CONFIG.get("reasoning_enabled", True)
|
||||
REASONING_EFFORT = CONFIG.get("reasoning_effort", "medium")
|
||||
FORCE_MODEL = (CONFIG.get("force_model") or "").strip()
|
||||
BGP_ROUTES = CONFIG.get("bgp_routes", [])
|
||||
_api_key_pool = None
|
||||
if API_KEY and "," in API_KEY and not OAUTH_PROVIDER.startswith("google") and BACKEND not in ("codebuff", "freebuff"):
|
||||
@@ -903,7 +928,7 @@ def _load_provider_caps():
|
||||
def _save_provider_caps():
|
||||
try:
|
||||
os.makedirs(os.path.dirname(_provider_caps_path), exist_ok=True)
|
||||
with open(_provider_caps_path, "w") as f:
|
||||
with open(_provider_caps_path, "w", encoding="utf-8") as f:
|
||||
json.dump(_provider_caps or {}, f, indent=2)
|
||||
except Exception as e:
|
||||
print(f"[provider-sensor] failed to save caps: {e}", file=sys.stderr)
|
||||
@@ -959,7 +984,7 @@ def _refresh_oauth_token_for(api_key, oauth_provider):
|
||||
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:
|
||||
with open(token_path, "w", encoding="utf-8") as f:
|
||||
json.dump(tokens, f, indent=2)
|
||||
print("[oauth] token refreshed OK", file=sys.stderr)
|
||||
return tokens["access_token"]
|
||||
@@ -983,7 +1008,7 @@ def _load_stats():
|
||||
|
||||
def _atomic_write_json(path, obj):
|
||||
tmp = path + ".tmp"
|
||||
with open(tmp, "w") as f:
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
json.dump(obj, f, indent=2, ensure_ascii=False)
|
||||
os.replace(tmp, path)
|
||||
|
||||
@@ -1297,7 +1322,7 @@ def _load_bgp_stats():
|
||||
|
||||
def _save_bgp_stats(stats):
|
||||
tmp = _BGP_STATS_PATH + ".tmp"
|
||||
with open(tmp, "w") as f:
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
json.dump(stats, f, indent=2)
|
||||
os.replace(tmp, _BGP_STATS_PATH)
|
||||
|
||||
@@ -1790,7 +1815,7 @@ def save_request_snapshot(request_id, body):
|
||||
}
|
||||
path = os.path.join(_REQUESTS_DIR, f"{request_id}.json")
|
||||
tmp = path + ".tmp"
|
||||
with open(tmp, "w") as f:
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
json.dump(snapshot, f, ensure_ascii=False, indent=2)
|
||||
os.replace(tmp, path)
|
||||
_rotate_snapshots()
|
||||
@@ -1813,7 +1838,7 @@ def update_snapshot_response(request_id, status, duration_s=None, error=None):
|
||||
meta["error"] = str(error)[:200]
|
||||
snapshot["_meta"] = meta
|
||||
tmp = path + ".tmp"
|
||||
with open(tmp, "w") as f:
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
json.dump(snapshot, f, ensure_ascii=False, indent=2)
|
||||
os.replace(tmp, path)
|
||||
except Exception:
|
||||
@@ -1865,6 +1890,27 @@ def _bucket_for_route(route):
|
||||
# OpenAI-compat backend
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
def _inject_stored_reasoning(messages):
|
||||
with _last_reasoning_lock:
|
||||
snapshot = dict(_last_reasoning_store)
|
||||
if not snapshot:
|
||||
return messages
|
||||
expired = [k for k, v in snapshot.items() if time.time() - v["ts"] > _RESPONSE_TTL]
|
||||
for k in expired:
|
||||
with _last_reasoning_lock:
|
||||
_last_reasoning_store.pop(k, None)
|
||||
snapshot.pop(k, None)
|
||||
if not snapshot:
|
||||
return messages
|
||||
latest = max(snapshot.values(), key=lambda v: v["ts"])
|
||||
reasoning = latest.get("reasoning", "")
|
||||
if not reasoning:
|
||||
return messages
|
||||
for msg in messages:
|
||||
if msg.get("role") == "assistant" and "reasoning_content" not in msg and msg.get("tool_calls"):
|
||||
msg["reasoning_content"] = reasoning
|
||||
return messages
|
||||
|
||||
def oa_input_to_messages(input_data):
|
||||
msgs = []
|
||||
tool_name_by_id = {}
|
||||
@@ -2384,10 +2430,10 @@ def an_stream_to_sse(stream, model, req_id):
|
||||
"status": status, "created": int(time.time()), "output": completed}})
|
||||
|
||||
_DEFAULT_CC_CONFIG = {
|
||||
"workingDir": "/tmp",
|
||||
"workingDir": tempfile.gettempdir(),
|
||||
"date": "",
|
||||
"environment": "linux",
|
||||
"shell": "bash",
|
||||
"environment": "windows" if _IS_WINDOWS else "linux",
|
||||
"shell": "powershell" if _IS_WINDOWS else "bash",
|
||||
"files": [],
|
||||
"structure": [],
|
||||
"isGitRepo": False,
|
||||
@@ -2462,13 +2508,24 @@ def _build_explore_cmd(text_for_url):
|
||||
api_base = repo_url.replace("/admin/", "/api/v1/repos/")
|
||||
else:
|
||||
api_base = repo_url
|
||||
cmd = (
|
||||
f"cd /tmp && "
|
||||
f"curl -sL --max-time 15 '{api_base}/contents/README.md' 2>/dev/null | "
|
||||
f"python3 -c \"import sys,json,base64; d=json.load(sys.stdin); print(base64.b64decode(d['content']).decode())\" 2>/dev/null | head -600 && "
|
||||
f"curl -sL --max-time 15 '{api_base}/contents' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print('\\n'.join(f'{{x.get(\'path\')}} {{x.get(\'type\')}}' for x in d[:50]))\" 2>/dev/null && "
|
||||
f"curl -sL --max-time 15 '{api_base}/releases' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(json.dumps(d[:3], indent=2)[:2000])\" 2>/dev/null"
|
||||
)
|
||||
if _IS_WINDOWS:
|
||||
cmd = (
|
||||
f"cd $env:TEMP; "
|
||||
f"$r = Invoke-WebRequest -Uri '{api_base}/contents/README.md' -UseBasicParsing -TimeoutSec 15 2>$null; "
|
||||
f"if ($r) {{ $j = $r.Content | ConvertFrom-Json; [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($j.content)) | Select-Object -First 600 }}; "
|
||||
f"$r2 = Invoke-WebRequest -Uri '{api_base}/contents' -UseBasicParsing -TimeoutSec 15 2>$null; "
|
||||
f"if ($r2) {{ $j2 = $r2.Content | ConvertFrom-Json; $j2 | Select-Object -First 50 | ForEach-Object {{ $_.path + ' ' + $_.type }} }}; "
|
||||
f"$r3 = Invoke-WebRequest -Uri '{api_base}/releases' -UseBasicParsing -TimeoutSec 15 2>$null; "
|
||||
f"if ($r3) {{ ($r3.Content | ConvertFrom-Json | Select-Object -First 3 | ConvertTo-Json).Substring(0, [Math]::Min(2000, ($r3.Content | ConvertFrom-Json | Select-Object -First 3 | ConvertTo-Json).Length)) }}"
|
||||
)
|
||||
else:
|
||||
cmd = (
|
||||
f"cd /tmp && "
|
||||
f"curl -sL --max-time 15 '{api_base}/contents/README.md' 2>/dev/null | "
|
||||
f"python3 -c \"import sys,json,base64; d=json.load(sys.stdin); print(base64.b64decode(d['content']).decode())\" 2>/dev/null | head -600 && "
|
||||
f"curl -sL --max-time 15 '{api_base}/contents' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print('\\n'.join(f'{{x.get(\'path\')}} {{x.get(\'type\')}}' for x in d[:50]))\" 2>/dev/null && "
|
||||
f"curl -sL --max-time 15 '{api_base}/releases' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(json.dumps(d[:3], indent=2)[:2000])\" 2>/dev/null"
|
||||
)
|
||||
return cmd, "Explore repository to understand the app and gather README, root contents, and releases for the landing page."
|
||||
|
||||
def _parse_commandcode_text_tool_calls(text):
|
||||
@@ -3322,7 +3379,10 @@ def cc_stream_to_sse(cc_stream, model, req_id):
|
||||
_url_in_text = re.search(r"https?://[^\s\]'\\>\",]+", text_buf)
|
||||
if _url_in_text:
|
||||
_synth_url = _url_in_text.group(0).rstrip(")].,;'\\\"")
|
||||
_synth_cmd = f"curl -sL --max-time 15 '{_synth_url}' 2>/dev/null | head -200"
|
||||
if _IS_WINDOWS:
|
||||
_synth_cmd = f"Invoke-WebRequest -Uri '{_synth_url}' -UseBasicParsing -TimeoutSec 15 | Select-Object -ExpandProperty Content | Select-Object -First 200"
|
||||
else:
|
||||
_synth_cmd = f"curl -sL --max-time 15 '{_synth_url}' 2>/dev/null | head -200"
|
||||
_synth_just = "Auto-synthesized: URL detected in text, fetching"
|
||||
|
||||
# Heuristic 2: File path references → list or read
|
||||
@@ -3330,7 +3390,10 @@ def cc_stream_to_sse(cc_stream, model, req_id):
|
||||
_file_m = re.search(r"(?:read|open|view|check|examine|cat|show)\s+(?:the\s+)?(?:file\s+)?[`'\"]?(/[^\s'\"]+\.\w+)", _tl)
|
||||
if _file_m:
|
||||
_fpath = _file_m.group(1)
|
||||
_synth_cmd = f"cat '{_fpath}' 2>/dev/null | head -200 || ls -la '{_fpath}'"
|
||||
if _IS_WINDOWS:
|
||||
_synth_cmd = f"Get-Content '{_fpath}' -ErrorAction SilentlyContinue | Select-Object -First 200; if (-not $?) {{ Get-Item '{_fpath}' | Select-Object Name,Length,LastWriteTime }}"
|
||||
else:
|
||||
_synth_cmd = f"cat '{_fpath}' 2>/dev/null | head -200 || ls -la '{_fpath}'"
|
||||
_synth_just = f"Auto-synthesized: file reference detected ({_fpath})"
|
||||
|
||||
# Heuristic 3: Shell command mentioned in backticks or quotes
|
||||
@@ -3358,7 +3421,10 @@ def cc_stream_to_sse(cc_stream, model, req_id):
|
||||
if _intent_m:
|
||||
_intent_text = _intent_m.group(1).strip()
|
||||
if len(_intent_text) > 10 and len(_intent_text) < 200:
|
||||
_synth_cmd = f"echo 'Stuck recovery: model intent was: {_intent_text[:100]}'"
|
||||
if _IS_WINDOWS:
|
||||
_synth_cmd = f"Write-Output 'Stuck recovery: model intent was: {_intent_text[:100]}'"
|
||||
else:
|
||||
_synth_cmd = f"echo 'Stuck recovery: model intent was: {_intent_text[:100]}'"
|
||||
_synth_just = f"Auto-synthesized from intent text: {_intent_text[:80]}"
|
||||
|
||||
if _synth_cmd:
|
||||
@@ -3891,11 +3957,13 @@ def _extract_text(content):
|
||||
# HTTP Server
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
_MAX_REQLOG_LINES = 2000
|
||||
|
||||
def _log_resp(resp_id, status, output):
|
||||
try:
|
||||
import datetime as _dt
|
||||
_lp = os.path.join(_LOG_DIR, "requests.log")
|
||||
with open(_lp, "a") as _f:
|
||||
with open(_lp, "a", encoding="utf-8") as _f:
|
||||
_f.write(f" RESPONSE id={resp_id} status={status}\n")
|
||||
if output:
|
||||
for o in output:
|
||||
@@ -3908,6 +3976,11 @@ def _log_resp(resp_id, status, output):
|
||||
_f.write(f" -> {ot}\n")
|
||||
_f.write(f"{'='*60}\n")
|
||||
_f.flush()
|
||||
_f.seek(0)
|
||||
lines = _f.readlines()
|
||||
if len(lines) > _MAX_REQLOG_LINES:
|
||||
with open(_lp, "w", encoding="utf-8") as _f2:
|
||||
_f2.writelines(lines[-_MAX_REQLOG_LINES:])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -4064,10 +4137,26 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
info["total"] = 0
|
||||
self.send_json(200, info)
|
||||
elif self.path in ("/health", "/v1/health"):
|
||||
import resource as _res
|
||||
_mem_mb = 0
|
||||
try:
|
||||
_mem_mb = _res.getrusage(_res.RUSAGE_SELF).ru_maxrss / 1024
|
||||
if _IS_WINDOWS:
|
||||
import ctypes
|
||||
class _PMI(ctypes.Structure):
|
||||
_fields_ = [("cb", ctypes.c_ulong), ("PageFaultCount", ctypes.c_ulong),
|
||||
("PeakWorkingSetSize", ctypes.c_size_t), ("WorkingSetSize", ctypes.c_size_t),
|
||||
("QuotaPeakPagedPoolUsage", ctypes.c_size_t), ("QuotaPagedPoolUsage", ctypes.c_size_t),
|
||||
("QuotaPeakNonPagedPoolUsage", ctypes.c_size_t), ("QuotaNonPagedPoolUsage", ctypes.c_size_t),
|
||||
("PagefileUsage", ctypes.c_size_t), ("PeakPagefileUsage", ctypes.c_size_t)]
|
||||
_pmi = _PMI()
|
||||
_pmi.cb = ctypes.sizeof(_PMI)
|
||||
ctypes.windll.psapi.GetProcessMemoryInfo.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_ulong]
|
||||
ctypes.windll.psapi.GetProcessMemoryInfo.restype = ctypes.c_int
|
||||
ctypes.windll.psapi.GetProcessMemoryInfo(
|
||||
ctypes.windll.kernel32.GetCurrentProcess(), ctypes.byref(_pmi), _pmi.cb)
|
||||
_mem_mb = _pmi.PeakWorkingSetSize / (1024 * 1024)
|
||||
else:
|
||||
import resource as _res
|
||||
_mem_mb = _res.getrusage(_res.RUSAGE_SELF).ru_maxrss / 1024
|
||||
except Exception:
|
||||
pass
|
||||
_uptime = time.time() - _START_TIME if '_START_TIME' in dir() else 0
|
||||
@@ -4122,12 +4211,12 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
resolved_types = [i.get("type") for i in input_data] if isinstance(input_data, list) else "str"
|
||||
|
||||
print(f"[{_sid}] prev_id={prev_id} raw={raw_types} resolved={resolved_types}", file=sys.stderr)
|
||||
with open(_log_path, "a") as _lf:
|
||||
with open(_log_path, "a", encoding="utf-8") as _lf:
|
||||
_lf.write(f"\n{'='*60}\n{_ts} [session={_sid}] REQUEST {self.path}\n")
|
||||
_lf.write(f" prev_id={prev_id}\n")
|
||||
_lf.write(f" raw_input_types={raw_types}\n")
|
||||
_lf.write(f" resolved_input_types={resolved_types}\n")
|
||||
_lf.write(f" stream={body.get('stream')} model={body.get('model')}\n")
|
||||
_lf.write(f" stream={body.get('stream')} model={body.get('model')} force_model={FORCE_MODEL}\n")
|
||||
_lf.write(f" store_keys={list(_response_store.keys())}\n")
|
||||
if isinstance(input_data, list):
|
||||
for i, item in enumerate(input_data):
|
||||
@@ -4143,6 +4232,9 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
_lf.flush()
|
||||
|
||||
model = body.get("model", MODELS[0]["id"] if MODELS else "unknown")
|
||||
if FORCE_MODEL:
|
||||
model = FORCE_MODEL
|
||||
body["model"] = FORCE_MODEL
|
||||
stream = body.get("stream", False)
|
||||
_desktop_forced_models = {"gpt-5.4-mini", "gpt-5.4", "gpt-5.5", "gpt-5-codex", "gpt-5.3-codex"}
|
||||
_launcher_model = os.environ.get("CODEX_LAUNCHER_MODEL", "")
|
||||
@@ -4211,6 +4303,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
body["input"] = input_data
|
||||
|
||||
messages = oa_input_to_messages(input_data)
|
||||
messages = _inject_stored_reasoning(messages)
|
||||
instructions = body.get("instructions", "").strip()
|
||||
if instructions:
|
||||
messages.insert(0, {"role": "system", "content": instructions})
|
||||
@@ -4612,7 +4705,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
if n_contents > 10:
|
||||
debug_path = os.path.join(_LOG_DIR, f"gemini-long-ctx-{self._session_id}.json")
|
||||
try:
|
||||
with open(debug_path, "w") as dbg:
|
||||
with open(debug_path, "w", encoding="utf-8") as dbg:
|
||||
json.dump({"contents_count": n_contents, "contents_roles": [c.get("role") for c in contents], "has_tools": has_tools, "model": model, "wrapped_size": len(body_b)}, dbg, indent=2)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -4628,7 +4721,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
if e.code == 400 and OAUTH_PROVIDER.startswith("google"):
|
||||
try:
|
||||
debug_path = os.path.join(_LOG_DIR, "gemini-last-400-request.json")
|
||||
with open(debug_path, "w") as dbg:
|
||||
with open(debug_path, "w", encoding="utf-8") as dbg:
|
||||
json.dump({"endpoint": ep, "model": model, "wrapped": wrapped, "error": err_body}, dbg, indent=2)
|
||||
print(f"[{self._session_id}] saved 400 debug request to {debug_path}", file=sys.stderr)
|
||||
except Exception:
|
||||
@@ -4940,7 +5033,8 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
pass
|
||||
|
||||
try:
|
||||
for event in oa_stream_to_sse(upstream, model, body.get("request_id") or body.get("id")):
|
||||
reasoning_out = {}
|
||||
for event in oa_stream_to_sse(upstream, model, body.get("request_id") or body.get("id"), _reasoning_out=reasoning_out):
|
||||
if tracker and tracker.cancelled.is_set():
|
||||
print("[translate-proxy] stream cancelled", file=sys.stderr)
|
||||
break
|
||||
@@ -4958,6 +5052,16 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
_log_resp(last_resp_id, last_status, last_output)
|
||||
if last_resp_id and input_data is not None:
|
||||
store_response(last_resp_id, input_data, last_output)
|
||||
if reasoning_out.get("text"):
|
||||
with _last_reasoning_lock:
|
||||
_last_reasoning_store[last_resp_id or ""] = {
|
||||
"reasoning": reasoning_out["text"],
|
||||
"tool_calls": reasoning_out.get("tool_calls", []),
|
||||
"ts": time.time(),
|
||||
}
|
||||
while len(_last_reasoning_store) > _MAX_STORED:
|
||||
oldest = next(iter(_last_reasoning_store))
|
||||
del _last_reasoning_store[oldest]
|
||||
_record_usage(provider, model, success, time.time() - t0, error_type="length" if not success else None)
|
||||
|
||||
# Auto-learn provider quirks before flushing the bad response to Codex.
|
||||
@@ -5924,8 +6028,14 @@ def main():
|
||||
global SERVER, _START_TIME
|
||||
_START_TIME = time.time()
|
||||
_init_runtime()
|
||||
signal.signal(signal.SIGTERM, _handle_shutdown_signal)
|
||||
signal.signal(signal.SIGINT, _handle_shutdown_signal)
|
||||
if _IS_WINDOWS:
|
||||
if hasattr(signal, "SIGBREAK"):
|
||||
signal.signal(signal.SIGBREAK, _handle_shutdown_signal)
|
||||
import atexit
|
||||
atexit.register(lambda: setattr(sys.modules[__name__], '_SHUTDOWN_REQUESTED', True))
|
||||
else:
|
||||
signal.signal(signal.SIGTERM, _handle_shutdown_signal)
|
||||
try:
|
||||
from http.server import ThreadingHTTPServer as _BaseSrv
|
||||
except ImportError:
|
||||
@@ -6132,7 +6242,7 @@ Postamble text."""
|
||||
_check("FIX23 explore nested JSON: parsed", len(_calls_m) == 1, f"got {len(_calls_m)} calls")
|
||||
if _calls_m:
|
||||
_args_m = json.loads(_calls_m[0].get("arguments", "{}"))
|
||||
_check("FIX23 explore nested JSON: cmd has curl", "curl" in _args_m.get("cmd", ""), f"got {_args_m.get('cmd')!r}")
|
||||
_check("FIX23 explore nested JSON: cmd has fetch cmd", "curl" in _args_m.get("cmd", "") or "Invoke-WebRequest" in _args_m.get("cmd", ""), f"got {_args_m.get('cmd')!r}")
|
||||
_check("FIX23 explore nested JSON: URL in cmd", "github.rommark.dev" in _args_m.get("cmd", ""), f"missing URL in cmd")
|
||||
|
||||
# Pattern N: require_escalation block (FIX 24)
|
||||
@@ -6142,7 +6252,7 @@ Postamble text."""
|
||||
if _calls_n:
|
||||
_args_n = json.loads(_calls_n[0].get("arguments", "{}"))
|
||||
_check("FIX24 require_escalation: name is exec_command", _calls_n[0].get("name") == "exec_command", f"got {_calls_n[0].get('name')}")
|
||||
_check("FIX24 require_escalation: cmd has curl or echo", "curl" in _args_n.get("cmd", "") or "echo" in _args_n.get("cmd", ""), f"got {_args_n.get('cmd')!r}")
|
||||
_check("FIX24 require_escalation: cmd has fetch or echo", "curl" in _args_n.get("cmd", "") or "echo" in _args_n.get("cmd", "") or "Invoke-WebRequest" in _args_n.get("cmd", "") or "Write-Output" in _args_n.get("cmd", ""), f"got {_args_n.get('cmd')!r}")
|
||||
|
||||
# Pattern N2: bare request_escalation_permission tag (FIX 24b)
|
||||
_esc_bare = 'I want to proceed.\n<request_escalation_permission />\nPlease let me continue.'
|
||||
@@ -6154,13 +6264,13 @@ Postamble text."""
|
||||
# Pattern O: _build_explore_cmd module-level function (FIX 23/25)
|
||||
_cmd_o, _just_o = _build_explore_cmd("https://github.rommark.dev/admin/Z.AI-Chat-for-Android")
|
||||
_check("FIX23/25 _build_explore_cmd: returns cmd", _cmd_o is not None, "returned None")
|
||||
_check("FIX23/25 _build_explore_cmd: has curl", _cmd_o and "curl" in _cmd_o, f"no curl in {_cmd_o!r}")
|
||||
_check("FIX23/25 _build_explore_cmd: has fetch cmd", _cmd_o and ("curl" in _cmd_o or "Invoke-WebRequest" in _cmd_o), f"no fetch cmd in {_cmd_o!r}")
|
||||
_check("FIX23/25 _build_explore_cmd: has api path", _cmd_o and "/api/v1/repos/" in _cmd_o, f"no api path in {_cmd_o!r}")
|
||||
|
||||
# Pattern O2: _build_explore_cmd with JSON array containing URL
|
||||
_cmd_o2, _ = _build_explore_cmd('[{"content": "https://github.rommark.dev/admin/Z.AI-Chat-for-Android"}]')
|
||||
_check("FIX23/25 _build_explore_cmd from JSON array: returns cmd", _cmd_o2 is not None, "returned None")
|
||||
_check("FIX23/25 _build_explore_cmd from JSON array: has curl", _cmd_o2 and "curl" in _cmd_o2, f"no curl in {_cmd_o2!r}")
|
||||
_check("FIX23/25 _build_explore_cmd from JSON array: has fetch cmd", _cmd_o2 and ("curl" in _cmd_o2 or "Invoke-WebRequest" in _cmd_o2), f"no fetch cmd in {_cmd_o2!r}")
|
||||
|
||||
print(f"[CC-SELF-TEST] Results: {_counts[0]} passed, {_counts[1]} failed",
|
||||
file=sys.stderr)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user