sync: PR #28 - tkinter GUI restore, proxy feature toggles, CROF guard fixes, gRPC test skip
This commit is contained in:
@@ -8,6 +8,7 @@ the tkinter GUI (Windows). No pip dependencies. No GTK/PyGObject imports.
|
|||||||
import base64
|
import base64
|
||||||
import collections
|
import collections
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import copy
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@@ -68,6 +69,9 @@ BGP_POOLS_FILE = CONFIG_DIR / "bgp-pools.json"
|
|||||||
LAUNCH_LOG = LOG_DIR / "launcher.log"
|
LAUNCH_LOG = LOG_DIR / "launcher.log"
|
||||||
OAUTH_SECRETS_PATH = HOME / ".config" / "codex-launcher" / "oauth-secrets.json"
|
OAUTH_SECRETS_PATH = HOME / ".config" / "codex-launcher" / "oauth-secrets.json"
|
||||||
|
|
||||||
|
GEMINI_OAUTH_CLIENT_ID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com"
|
||||||
|
GEMINI_OAUTH_CLIENT_SECRET = "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"
|
||||||
|
|
||||||
if IS_WINDOWS:
|
if IS_WINDOWS:
|
||||||
PROXY = BIN_DIR / "translate-proxy.py"
|
PROXY = BIN_DIR / "translate-proxy.py"
|
||||||
CLEANUP = BIN_DIR / "cleanup-codex-stale.py"
|
CLEANUP = BIN_DIR / "cleanup-codex-stale.py"
|
||||||
@@ -82,6 +86,51 @@ model_provider = ""
|
|||||||
model_catalog_json = ""
|
model_catalog_json = ""
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_MODULE_DIR = Path(__file__).resolve().parent
|
||||||
|
if str(_MODULE_DIR) not in sys.path:
|
||||||
|
sys.path.insert(0, str(_MODULE_DIR))
|
||||||
|
try:
|
||||||
|
import universal_runtime as _universal_runtime
|
||||||
|
except (ImportError, ModuleNotFoundError):
|
||||||
|
_universal_runtime = None
|
||||||
|
|
||||||
|
|
||||||
|
def detect_runtime_environment():
|
||||||
|
if _universal_runtime is None:
|
||||||
|
return {"profile": "unknown", "fallback_mode": "builtin"}
|
||||||
|
return _universal_runtime.detect_environment()
|
||||||
|
|
||||||
|
|
||||||
|
def build_cross_platform_profile(mode="basic", overrides=None):
|
||||||
|
if _universal_runtime is None:
|
||||||
|
return {"profile": "legacy", "mode": mode, "overrides": overrides or {}}
|
||||||
|
return _universal_runtime.build_runtime_profile(mode=mode, overrides=overrides)
|
||||||
|
|
||||||
|
|
||||||
|
def run_doctor_plus():
|
||||||
|
if _universal_runtime is None:
|
||||||
|
return {"health": "unknown", "checks": []}
|
||||||
|
deps = ["python3" if not IS_WINDOWS else "python", "curl"]
|
||||||
|
return _universal_runtime.doctor_plus(deps, [CONFIG, ENDPOINTS_FILE, PROXY_CONFIG_DIR / "probe"])
|
||||||
|
|
||||||
|
|
||||||
|
def choose_policy_route(routes, policy=None):
|
||||||
|
if _universal_runtime is None:
|
||||||
|
return routes[0] if routes else {}
|
||||||
|
return _universal_runtime.select_policy_route(routes, policy=policy)
|
||||||
|
|
||||||
|
|
||||||
|
def create_session_portability_pack(destination, metadata=None, files=None):
|
||||||
|
if _universal_runtime is None:
|
||||||
|
raise RuntimeError("universal runtime unavailable")
|
||||||
|
return _universal_runtime.export_session_pack(Path(destination), metadata or {}, [Path(p) for p in (files or [])])
|
||||||
|
|
||||||
|
|
||||||
|
def restore_session_portability_pack(bundle_path, destination_dir):
|
||||||
|
if _universal_runtime is None:
|
||||||
|
raise RuntimeError("universal runtime unavailable")
|
||||||
|
return _universal_runtime.import_session_pack(Path(bundle_path), Path(destination_dir))
|
||||||
|
|
||||||
CHANGELOG = [
|
CHANGELOG = [
|
||||||
("10.13.8", "2026-05-27", [
|
("10.13.8", "2026-05-27", [
|
||||||
"Fix: force_finalize skips Gemini call entirely (was hallucinating tool calls without tools)",
|
"Fix: force_finalize skips Gemini call entirely (was hallucinating tool calls without tools)",
|
||||||
@@ -456,7 +505,7 @@ CHANGELOG = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════
|
||||||
# Provider presets (17 providers)
|
# Provider presets (25+ providers)
|
||||||
# ═══════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
PROVIDER_PRESETS = {
|
PROVIDER_PRESETS = {
|
||||||
@@ -562,6 +611,41 @@ PROVIDER_PRESETS = {
|
|||||||
"base_url": "https://openrouter.ai/api/v1",
|
"base_url": "https://openrouter.ai/api/v1",
|
||||||
"models": [],
|
"models": [],
|
||||||
},
|
},
|
||||||
|
"Perplexity": {
|
||||||
|
"backend_type": "openai-compat",
|
||||||
|
"base_url": "https://api.perplexity.ai",
|
||||||
|
"models": [
|
||||||
|
"sonar",
|
||||||
|
"sonar-pro",
|
||||||
|
"sonar-reasoning-pro",
|
||||||
|
"sonar-deep-research",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"Cohere": {
|
||||||
|
"backend_type": "openai-compat",
|
||||||
|
"base_url": "https://api.cohere.ai/compatibility/v1",
|
||||||
|
"models": [],
|
||||||
|
},
|
||||||
|
"Hugging Face": {
|
||||||
|
"backend_type": "openai-compat",
|
||||||
|
"base_url": "https://router.huggingface.co/v1",
|
||||||
|
"models": [],
|
||||||
|
},
|
||||||
|
"Together AI": {
|
||||||
|
"backend_type": "openai-compat",
|
||||||
|
"base_url": "https://api.together.xyz/v1",
|
||||||
|
"models": [],
|
||||||
|
},
|
||||||
|
"Groq": {
|
||||||
|
"backend_type": "openai-compat",
|
||||||
|
"base_url": "https://api.groq.com/openai/v1",
|
||||||
|
"models": [],
|
||||||
|
},
|
||||||
|
"Fireworks AI": {
|
||||||
|
"backend_type": "openai-compat",
|
||||||
|
"base_url": "https://api.fireworks.ai/inference/v1",
|
||||||
|
"models": [],
|
||||||
|
},
|
||||||
"Google Gemini (API Key)": {
|
"Google Gemini (API Key)": {
|
||||||
"backend_type": "openai-compat",
|
"backend_type": "openai-compat",
|
||||||
"base_url": "https://generativelanguage.googleapis.com/v1beta/openai",
|
"base_url": "https://generativelanguage.googleapis.com/v1beta/openai",
|
||||||
@@ -626,6 +710,16 @@ PROVIDER_PRESETS = {
|
|||||||
"base_url": "http://localhost:11434/v1",
|
"base_url": "http://localhost:11434/v1",
|
||||||
"models": [],
|
"models": [],
|
||||||
},
|
},
|
||||||
|
"LM Studio (local)": {
|
||||||
|
"backend_type": "openai-compat",
|
||||||
|
"base_url": "http://127.0.0.1:1234/v1",
|
||||||
|
"models": [],
|
||||||
|
},
|
||||||
|
"vLLM / OpenAI-Compatible (self-hosted)": {
|
||||||
|
"backend_type": "openai-compat",
|
||||||
|
"base_url": "http://localhost:8000/v1",
|
||||||
|
"models": [],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════
|
||||||
@@ -861,29 +955,33 @@ def apply_provider_preset(endpoint, preset_name):
|
|||||||
def load_endpoints():
|
def load_endpoints():
|
||||||
if ENDPOINTS_FILE.exists():
|
if ENDPOINTS_FILE.exists():
|
||||||
try:
|
try:
|
||||||
return json.loads(ENDPOINTS_FILE.read_text())
|
return json.loads(ENDPOINTS_FILE.read_text(encoding="utf-8"))
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
pass
|
print(f"[lib] failed to load endpoints: {exc}", file=sys.stderr)
|
||||||
return {"default": None, "endpoints": []}
|
return {"default": None, "endpoints": []}
|
||||||
|
|
||||||
|
|
||||||
def save_endpoints(data):
|
def save_endpoints(data):
|
||||||
ENDPOINTS_FILE.parent.mkdir(parents=True, exist_ok=True)
|
ENDPOINTS_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||||
ENDPOINTS_FILE.write_text(json.dumps(data, indent=2))
|
tmp = ENDPOINTS_FILE.with_suffix(".json.tmp")
|
||||||
|
tmp.write_text(json.dumps(data, indent=2))
|
||||||
|
os.replace(str(tmp), str(ENDPOINTS_FILE))
|
||||||
|
|
||||||
|
|
||||||
def load_bgp_pools():
|
def load_bgp_pools():
|
||||||
if BGP_POOLS_FILE.exists():
|
if BGP_POOLS_FILE.exists():
|
||||||
try:
|
try:
|
||||||
return json.loads(BGP_POOLS_FILE.read_text())
|
return json.loads(BGP_POOLS_FILE.read_text(encoding="utf-8"))
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
pass
|
print(f"[lib] failed to load bgp pools: {exc}", file=sys.stderr)
|
||||||
return {"pools": []}
|
return {"pools": []}
|
||||||
|
|
||||||
|
|
||||||
def save_bgp_pools(data):
|
def save_bgp_pools(data):
|
||||||
BGP_POOLS_FILE.parent.mkdir(parents=True, exist_ok=True)
|
BGP_POOLS_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||||
BGP_POOLS_FILE.write_text(json.dumps(data, indent=2))
|
tmp = BGP_POOLS_FILE.with_suffix(".json.tmp")
|
||||||
|
tmp.write_text(json.dumps(data, indent=2))
|
||||||
|
os.replace(str(tmp), str(BGP_POOLS_FILE))
|
||||||
|
|
||||||
|
|
||||||
def get_endpoint(name):
|
def get_endpoint(name):
|
||||||
@@ -949,10 +1047,28 @@ def write_secure_text(path, text):
|
|||||||
# ═══════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
def backup_config():
|
def backup_config():
|
||||||
if CONFIG.exists():
|
if not CONFIG.exists():
|
||||||
|
return
|
||||||
tmp = CONFIG_BAK.with_suffix(".tmp")
|
tmp = CONFIG_BAK.with_suffix(".tmp")
|
||||||
shutil.copy2(str(CONFIG), str(tmp))
|
shutil.copy2(str(CONFIG), str(tmp))
|
||||||
os.replace(str(tmp), str(CONFIG_BAK))
|
os.replace(str(tmp), str(CONFIG_BAK))
|
||||||
|
ts = time.strftime("%Y%m%d_%H%M%S")
|
||||||
|
rot = CONFIG.parent / f"config.toml.{ts}.bak"
|
||||||
|
try:
|
||||||
|
shutil.copy2(str(CONFIG), str(rot))
|
||||||
|
_rotate_backups(CONFIG.parent, "config.toml.*.bak", max_backups=10)
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"[lib] backup rotation failed: {exc}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def _rotate_backups(directory, pattern, max_backups=10):
|
||||||
|
import glob as _glob
|
||||||
|
files = sorted(_glob.glob(str(directory / pattern)), key=os.path.getmtime, reverse=True)
|
||||||
|
for old in files[max_backups:]:
|
||||||
|
try:
|
||||||
|
os.remove(old)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def restore_config():
|
def restore_config():
|
||||||
@@ -979,7 +1095,7 @@ def recover_config_if_needed(logfn=None):
|
|||||||
if not CONFIG_TXN.exists():
|
if not CONFIG_TXN.exists():
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
txn = json.loads(CONFIG_TXN.read_text())
|
txn = json.loads(CONFIG_TXN.read_text(encoding="utf-8"))
|
||||||
if txn.get("config_existed") and CONFIG_BAK.exists():
|
if txn.get("config_existed") and CONFIG_BAK.exists():
|
||||||
restore_config()
|
restore_config()
|
||||||
if logfn:
|
if logfn:
|
||||||
@@ -1219,6 +1335,24 @@ def endpoint_model_headers(endpoint):
|
|||||||
return headers
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
def check_provider_latency(endpoint, timeout=5):
|
||||||
|
bt = endpoint.get("backend_type", "")
|
||||||
|
if bt in ("native", "codex-default", "gemini-oauth-antigravity"):
|
||||||
|
return None
|
||||||
|
base = endpoint.get("base_url", "").strip()
|
||||||
|
if not base:
|
||||||
|
return None
|
||||||
|
url = base.rstrip("/") + "/models"
|
||||||
|
try:
|
||||||
|
headers = endpoint_model_headers(endpoint)
|
||||||
|
req = urllib.request.Request(url, headers=headers, method="GET")
|
||||||
|
t0 = time.time()
|
||||||
|
urllib.request.urlopen(req, timeout=timeout)
|
||||||
|
return time.time() - t0
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def fetch_models_for_endpoint(endpoint, timeout=10):
|
def fetch_models_for_endpoint(endpoint, timeout=10):
|
||||||
bt = endpoint.get("backend_type", "")
|
bt = endpoint.get("backend_type", "")
|
||||||
if bt == "gemini-oauth-antigravity":
|
if bt == "gemini-oauth-antigravity":
|
||||||
@@ -1276,9 +1410,16 @@ ANTIGRAVITY_MODELS = [
|
|||||||
def load_oauth_secrets():
|
def load_oauth_secrets():
|
||||||
try:
|
try:
|
||||||
with open(OAUTH_SECRETS_PATH, encoding="utf-8") as f:
|
with open(OAUTH_SECRETS_PATH, encoding="utf-8") as f:
|
||||||
return json.load(f)
|
data = json.load(f)
|
||||||
except Exception:
|
except Exception:
|
||||||
return {}
|
data = {}
|
||||||
|
for key in ("antigravity", "gemini_cli"):
|
||||||
|
sec = data.get(key, {})
|
||||||
|
if not sec.get("client_id"):
|
||||||
|
data.setdefault(key, {})["client_id"] = GEMINI_OAUTH_CLIENT_ID
|
||||||
|
if not sec.get("client_secret"):
|
||||||
|
data.setdefault(key, {})["client_secret"] = GEMINI_OAUTH_CLIENT_SECRET
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def save_oauth_secrets(data):
|
def save_oauth_secrets(data):
|
||||||
@@ -1462,7 +1603,7 @@ def run_endpoint_doctor(endpoint):
|
|||||||
token_path = PROXY_CONFIG_DIR / token_name
|
token_path = PROXY_CONFIG_DIR / token_name
|
||||||
if token_path.exists():
|
if token_path.exists():
|
||||||
try:
|
try:
|
||||||
td = json.loads(token_path.read_text())
|
td = json.loads(token_path.read_text(encoding="utf-8"))
|
||||||
exp = td.get("expires_at", 0)
|
exp = td.get("expires_at", 0)
|
||||||
if exp > time.time():
|
if exp > time.time():
|
||||||
remaining = exp - time.time()
|
remaining = exp - time.time()
|
||||||
@@ -1531,9 +1672,9 @@ def run_endpoint_doctor(endpoint):
|
|||||||
def _load_pid_registry():
|
def _load_pid_registry():
|
||||||
if PID_REGISTRY.exists():
|
if PID_REGISTRY.exists():
|
||||||
try:
|
try:
|
||||||
return json.loads(PID_REGISTRY.read_text())
|
return json.loads(PID_REGISTRY.read_text(encoding="utf-8"))
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
pass
|
print(f"[lib] failed to load pid registry: {exc}", file=sys.stderr)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
@@ -1576,7 +1717,7 @@ _PROXY_PORT_FILE = PROXY_CONFIG_DIR / ".last-proxy-port"
|
|||||||
def _pick_free_port():
|
def _pick_free_port():
|
||||||
saved = None
|
saved = None
|
||||||
try:
|
try:
|
||||||
saved = int(_PROXY_PORT_FILE.read_text().strip())
|
saved = int(_PROXY_PORT_FILE.read_text(encoding="utf-8").strip())
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
s.bind(("127.0.0.1", saved))
|
s.bind(("127.0.0.1", saved))
|
||||||
@@ -1626,8 +1767,8 @@ def start_proxy_for(endpoint, logfn):
|
|||||||
discovered = [] if endpoint.get("oauth_provider") == "google-antigravity" else td.get("available_models", [])
|
discovered = [] if endpoint.get("oauth_provider") == "google-antigravity" else td.get("available_models", [])
|
||||||
if discovered:
|
if discovered:
|
||||||
model_list = discovered
|
model_list = discovered
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
pass
|
print(f"[lib] oauth token discovery: {exc}", file=sys.stderr)
|
||||||
|
|
||||||
pcfg = {
|
pcfg = {
|
||||||
"port": port,
|
"port": port,
|
||||||
@@ -1639,6 +1780,11 @@ def start_proxy_for(endpoint, logfn):
|
|||||||
"reasoning_enabled": endpoint.get("reasoning_enabled", True),
|
"reasoning_enabled": endpoint.get("reasoning_enabled", True),
|
||||||
"reasoning_effort": endpoint.get("reasoning_effort", "medium"),
|
"reasoning_effort": endpoint.get("reasoning_effort", "medium"),
|
||||||
"force_model": endpoint.get("default_model") or "",
|
"force_model": endpoint.get("default_model") or "",
|
||||||
|
"caveman_mode": endpoint.get("caveman_mode", False),
|
||||||
|
"rtk_compression": endpoint.get("rtk_compression", False),
|
||||||
|
"auto_compact": endpoint.get("auto_compact", False),
|
||||||
|
"adaptive_compact": endpoint.get("adaptive_compact", False),
|
||||||
|
"tool_output_truncation": endpoint.get("tool_output_truncation", True),
|
||||||
"models": [{"id": m, "object": "model", "created": 1700000000, "owned_by": endpoint["name"]}
|
"models": [{"id": m, "object": "model", "created": 1700000000, "owned_by": endpoint["name"]}
|
||||||
for m in model_list],
|
for m in model_list],
|
||||||
}
|
}
|
||||||
@@ -1726,6 +1872,11 @@ def start_bgp_proxy(pool, model, logfn):
|
|||||||
"target_url": "http://bgp.placeholder",
|
"target_url": "http://bgp.placeholder",
|
||||||
"api_key": "",
|
"api_key": "",
|
||||||
"bgp_routes": pool.get("routes", []),
|
"bgp_routes": pool.get("routes", []),
|
||||||
|
"caveman_mode": endpoint.get("caveman_mode", False) if endpoint else False,
|
||||||
|
"rtk_compression": endpoint.get("rtk_compression", False) if endpoint else False,
|
||||||
|
"auto_compact": endpoint.get("auto_compact", False) if endpoint else False,
|
||||||
|
"adaptive_compact": endpoint.get("adaptive_compact", False) if endpoint else False,
|
||||||
|
"tool_output_truncation": endpoint.get("tool_output_truncation", True) if endpoint else True,
|
||||||
"models": [{"id": m, "object": "model", "created": 1700000000, "owned_by": "bgp"} for m in bgp_ep["models"]],
|
"models": [{"id": m, "object": "model", "created": 1700000000, "owned_by": "bgp"} for m in bgp_ep["models"]],
|
||||||
}
|
}
|
||||||
pcfg_path = PROXY_CONFIG_DIR / f"proxy-{safe_name(pool['name'])}-{port}.json"
|
pcfg_path = PROXY_CONFIG_DIR / f"proxy-{safe_name(pool['name'])}-{port}.json"
|
||||||
@@ -1751,6 +1902,12 @@ def detect_codex_cli():
|
|||||||
|
|
||||||
|
|
||||||
def detect_codex_desktop():
|
def detect_codex_desktop():
|
||||||
|
"""Detect Codex Desktop installation.
|
||||||
|
|
||||||
|
Returns (path_or_aumid, is_msix) tuple on Windows, path string on Linux.
|
||||||
|
For MSIX installs, returns the AppUserModelId since the exe cannot be
|
||||||
|
launched directly via subprocess from WindowsApps.
|
||||||
|
"""
|
||||||
if IS_WINDOWS:
|
if IS_WINDOWS:
|
||||||
la = os.environ.get("LOCALAPPDATA", "")
|
la = os.environ.get("LOCALAPPDATA", "")
|
||||||
pf = os.environ.get("PROGRAMFILES", "")
|
pf = os.environ.get("PROGRAMFILES", "")
|
||||||
@@ -1763,8 +1920,8 @@ def detect_codex_desktop():
|
|||||||
]
|
]
|
||||||
for p in desktop_paths:
|
for p in desktop_paths:
|
||||||
if p.exists():
|
if p.exists():
|
||||||
return str(p)
|
return str(p), False
|
||||||
# MSIX / Microsoft Store install: locate via Get-AppxPackage
|
# MSIX / Microsoft Store install
|
||||||
try:
|
try:
|
||||||
r = subprocess.run(
|
r = subprocess.run(
|
||||||
["powershell", "-NoProfile", "-Command",
|
["powershell", "-NoProfile", "-Command",
|
||||||
@@ -1775,13 +1932,70 @@ def detect_codex_desktop():
|
|||||||
if loc:
|
if loc:
|
||||||
msix_exe = Path(loc) / "app" / "Codex.exe"
|
msix_exe = Path(loc) / "app" / "Codex.exe"
|
||||||
if msix_exe.exists():
|
if msix_exe.exists():
|
||||||
return str(msix_exe)
|
r2 = subprocess.run(
|
||||||
|
["powershell", "-NoProfile", "-Command",
|
||||||
|
"(Get-AppxPackage *OpenAI.Codex*).PackageFamilyName"],
|
||||||
|
capture_output=True, text=True, timeout=10,
|
||||||
|
)
|
||||||
|
family = r2.stdout.strip() if r2.returncode == 0 else ""
|
||||||
|
if family:
|
||||||
|
return f"{family}!App", True
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return None
|
return None, False
|
||||||
if START_SH and START_SH.exists():
|
if START_SH and START_SH.exists():
|
||||||
return str(START_SH)
|
return str(START_SH), False
|
||||||
return None
|
return None, False
|
||||||
|
|
||||||
|
|
||||||
|
def launch_codex_desktop(desktop_info):
|
||||||
|
"""Launch Codex Desktop process.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
desktop_info: (path_or_aumid, is_msix) tuple from detect_codex_desktop()
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
subprocess.Popen object or None
|
||||||
|
"""
|
||||||
|
path, is_msix = desktop_info
|
||||||
|
if IS_WINDOWS:
|
||||||
|
if is_msix:
|
||||||
|
return subprocess.Popen(
|
||||||
|
["cmd", "/c", "start", "", f"shell:AppsFolder\\{path}"],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||||
|
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
|
||||||
|
return subprocess.Popen(
|
||||||
|
[path],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||||
|
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
|
||||||
|
else:
|
||||||
|
return subprocess.Popen(
|
||||||
|
[path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||||
|
preexec_fn=os.setsid)
|
||||||
|
|
||||||
|
|
||||||
|
def is_codex_desktop_running():
|
||||||
|
"""Check if Codex Desktop (or MSIX Codex) is currently running."""
|
||||||
|
if IS_WINDOWS:
|
||||||
|
try:
|
||||||
|
for name in ("Codex Desktop.exe", "Codex.exe"):
|
||||||
|
out = subprocess.run(
|
||||||
|
["tasklist", "/FI", f"IMAGENAME eq {name}", "/FO", "CSV", "/NH"],
|
||||||
|
capture_output=True, text=True, timeout=5,
|
||||||
|
)
|
||||||
|
for line in out.stdout.strip().splitlines():
|
||||||
|
parts = line.split(",")
|
||||||
|
if len(parts) >= 2 and parts[1].strip('"').isdigit():
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
out = subprocess.run(["pgrep", "-f", "/opt/codex-desktop/electron"], capture_output=True, text=True, timeout=5)
|
||||||
|
return bool(out.stdout.strip())
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def check_codex_auth():
|
def check_codex_auth():
|
||||||
@@ -1813,7 +2027,7 @@ def check_codex_auth():
|
|||||||
|
|
||||||
def last_log_lines(n=15):
|
def last_log_lines(n=15):
|
||||||
try:
|
try:
|
||||||
t = LAUNCH_LOG.read_text()
|
t = LAUNCH_LOG.read_text(encoding="utf-8")
|
||||||
return "\n".join(t.splitlines()[-n:])
|
return "\n".join(t.splitlines()[-n:])
|
||||||
except Exception:
|
except Exception:
|
||||||
return "(no log file)"
|
return "(no log file)"
|
||||||
@@ -1824,9 +2038,10 @@ def last_log_lines(n=15):
|
|||||||
|
|
||||||
def kill_existing_desktop(logfn=None):
|
def kill_existing_desktop(logfn=None):
|
||||||
if IS_WINDOWS:
|
if IS_WINDOWS:
|
||||||
|
for img in ("Codex Desktop.exe", "Codex.exe"):
|
||||||
try:
|
try:
|
||||||
out = subprocess.run(
|
out = subprocess.run(
|
||||||
["tasklist", "/FI", "IMAGENAME eq Codex Desktop.exe", "/FO", "CSV", "/NH"],
|
["tasklist", "/FI", f"IMAGENAME eq {img}", "/FO", "CSV", "/NH"],
|
||||||
capture_output=True, text=True, timeout=5,
|
capture_output=True, text=True, timeout=5,
|
||||||
)
|
)
|
||||||
for line in out.stdout.strip().splitlines():
|
for line in out.stdout.strip().splitlines():
|
||||||
@@ -1929,9 +2144,9 @@ _DIAGNOSTIC_SYSTEM_PROMPT = (
|
|||||||
def load_monitoring_config():
|
def load_monitoring_config():
|
||||||
if MONITORING_FILE.exists():
|
if MONITORING_FILE.exists():
|
||||||
try:
|
try:
|
||||||
return json.loads(MONITORING_FILE.read_text())
|
return json.loads(MONITORING_FILE.read_text(encoding="utf-8"))
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
pass
|
print(f"[lib] failed to load monitoring config: {exc}", file=sys.stderr)
|
||||||
return {
|
return {
|
||||||
"enabled": False,
|
"enabled": False,
|
||||||
"provider_url": "",
|
"provider_url": "",
|
||||||
@@ -1951,9 +2166,9 @@ def save_monitoring_config(cfg):
|
|||||||
def load_incident_store():
|
def load_incident_store():
|
||||||
if INCIDENT_STORE_FILE.exists():
|
if INCIDENT_STORE_FILE.exists():
|
||||||
try:
|
try:
|
||||||
return json.loads(INCIDENT_STORE_FILE.read_text())
|
return json.loads(INCIDENT_STORE_FILE.read_text(encoding="utf-8"))
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
pass
|
print(f"[lib] failed to load incident store: {exc}", file=sys.stderr)
|
||||||
return {"version": 1, "incidents": {}, "stats": {"ai_calls": 0, "tokens_used": 0}}
|
return {"version": 1, "incidents": {}, "stats": {"ai_calls": 0, "tokens_used": 0}}
|
||||||
|
|
||||||
|
|
||||||
@@ -1966,16 +2181,18 @@ def monitoring_log(msg):
|
|||||||
try:
|
try:
|
||||||
with open(str(MONITORING_LOG), "a") as f:
|
with open(str(MONITORING_LOG), "a") as f:
|
||||||
f.write(f"[{time.strftime('%H:%M:%S')}] {msg}\n")
|
f.write(f"[{time.strftime('%H:%M:%S')}] {msg}\n")
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
pass
|
print(f"[lib] monitoring_log write failed: {exc}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
class IncidentStore:
|
class IncidentStore:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._store = load_incident_store()
|
self._store = load_incident_store()
|
||||||
self._dirty = False
|
self._dirty = False
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
def lookup(self, pattern):
|
def lookup(self, pattern):
|
||||||
|
with self._lock:
|
||||||
inc = self._store.get("incidents", {}).get(pattern)
|
inc = self._store.get("incidents", {}).get(pattern)
|
||||||
if inc and inc.get("success_count", 0) > 0:
|
if inc and inc.get("success_count", 0) > 0:
|
||||||
rate = inc["success_count"] / max(inc["success_count"] + inc.get("fail_count", 0), 1)
|
rate = inc["success_count"] / max(inc["success_count"] + inc.get("fail_count", 0), 1)
|
||||||
@@ -1984,34 +2201,45 @@ class IncidentStore:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def record(self, pattern, fix, success=True):
|
def record(self, pattern, fix, success=True):
|
||||||
incs = self._store.setdefault("incidents", {})
|
with self._lock:
|
||||||
|
new_store = copy.deepcopy(self._store)
|
||||||
|
incs = new_store.setdefault("incidents", {})
|
||||||
inc = incs.setdefault(pattern, {
|
inc = incs.setdefault(pattern, {
|
||||||
"fix": fix, "success_count": 0, "fail_count": 0,
|
"fix": fix, "success_count": 0, "fail_count": 0,
|
||||||
"last_seen": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
"last_seen": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||||
"occurrences": 0,
|
"occurrences": 0,
|
||||||
})
|
})
|
||||||
|
inc = dict(inc)
|
||||||
inc["last_seen"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
inc["last_seen"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||||
inc["occurrences"] = inc.get("occurrences", 0) + 1
|
inc["occurrences"] = inc.get("occurrences", 0) + 1
|
||||||
if success:
|
if success:
|
||||||
inc["success_count"] = inc.get("success_count", 0) + 1
|
inc["success_count"] = inc.get("success_count", 0) + 1
|
||||||
else:
|
else:
|
||||||
inc["fail_count"] = inc.get("fail_count", 0) + 1
|
inc["fail_count"] = inc.get("fail_count", 0) + 1
|
||||||
|
incs[pattern] = inc
|
||||||
|
self._store = new_store
|
||||||
self._dirty = True
|
self._dirty = True
|
||||||
|
|
||||||
def record_ai_call(self, tokens=0):
|
def record_ai_call(self, tokens=0):
|
||||||
stats = self._store.setdefault("stats", {"ai_calls": 0, "tokens_used": 0})
|
with self._lock:
|
||||||
|
new_store = copy.deepcopy(self._store)
|
||||||
|
stats = dict(new_store.get("stats", {"ai_calls": 0, "tokens_used": 0}))
|
||||||
stats["ai_calls"] = stats.get("ai_calls", 0) + 1
|
stats["ai_calls"] = stats.get("ai_calls", 0) + 1
|
||||||
stats["tokens_used"] = stats.get("tokens_used", 0) + tokens
|
stats["tokens_used"] = stats.get("tokens_used", 0) + tokens
|
||||||
|
new_store["stats"] = stats
|
||||||
|
self._store = new_store
|
||||||
self._dirty = True
|
self._dirty = True
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
|
with self._lock:
|
||||||
if self._dirty:
|
if self._dirty:
|
||||||
save_incident_store(self._store)
|
save_incident_store(self._store)
|
||||||
self._dirty = False
|
self._dirty = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stats(self):
|
def stats(self):
|
||||||
return self._store.get("stats", {"ai_calls": 0, "tokens_used": 0})
|
with self._lock:
|
||||||
|
return dict(self._store.get("stats", {"ai_calls": 0, "tokens_used": 0}))
|
||||||
|
|
||||||
|
|
||||||
class AIDiagnosticAgent:
|
class AIDiagnosticAgent:
|
||||||
@@ -2132,10 +2360,10 @@ class HealthWatcher(threading.Thread):
|
|||||||
try:
|
try:
|
||||||
cfg_path = PROXY_CONFIG_DIR / "proxy-config.json"
|
cfg_path = PROXY_CONFIG_DIR / "proxy-config.json"
|
||||||
if cfg_path.exists():
|
if cfg_path.exists():
|
||||||
d = json.loads(cfg_path.read_text())
|
d = json.loads(cfg_path.read_text(encoding="utf-8"))
|
||||||
return d.get("port")
|
return d.get("port")
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
pass
|
print(f"[lib] _get_proxy_port: {exc}", file=sys.stderr)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _check_health(self, port):
|
def _check_health(self, port):
|
||||||
@@ -2200,7 +2428,7 @@ class HealthWatcher(threading.Thread):
|
|||||||
for log_name in ["cc-debug.log", "proxy.log"]:
|
for log_name in ["cc-debug.log", "proxy.log"]:
|
||||||
log_path = PROXY_CONFIG_DIR / log_name
|
log_path = PROXY_CONFIG_DIR / log_name
|
||||||
try:
|
try:
|
||||||
text = log_path.read_text()
|
text = log_path.read_text(encoding="utf-8")
|
||||||
lines.extend(text.splitlines()[-20:])
|
lines.extend(text.splitlines()[-20:])
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -2250,9 +2478,9 @@ class _LogAnalyzerThread(threading.Thread):
|
|||||||
def load_usage_stats():
|
def load_usage_stats():
|
||||||
try:
|
try:
|
||||||
if _USAGE_STATS_FILE.exists():
|
if _USAGE_STATS_FILE.exists():
|
||||||
return json.loads(_USAGE_STATS_FILE.read_text())
|
return json.loads(_USAGE_STATS_FILE.read_text(encoding="utf-8"))
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
pass
|
print(f"[lib] failed to load usage stats: {exc}", file=sys.stderr)
|
||||||
return {"providers": {}, "updated": None}
|
return {"providers": {}, "updated": None}
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
@@ -311,6 +311,9 @@ FORCE_MODEL = ""
|
|||||||
BGP_ROUTES = []
|
BGP_ROUTES = []
|
||||||
CAVEMAN_MODE = False
|
CAVEMAN_MODE = False
|
||||||
RTK_COMPRESSION = False
|
RTK_COMPRESSION = False
|
||||||
|
AUTO_COMPACT = False
|
||||||
|
ADAPTIVE_COMPACT = False
|
||||||
|
TOOL_OUTPUT_TRUNCATION = True
|
||||||
PROMPT_ENHANCER = False
|
PROMPT_ENHANCER = False
|
||||||
PROMPT_ENHANCER_MODE = "offline"
|
PROMPT_ENHANCER_MODE = "offline"
|
||||||
PROMPT_ENHANCER_MODEL = ""
|
PROMPT_ENHANCER_MODEL = ""
|
||||||
@@ -1179,7 +1182,7 @@ def _init_runtime():
|
|||||||
global MODELS, CC_VERSION, REASONING_ENABLED, REASONING_EFFORT, BGP_ROUTES
|
global MODELS, CC_VERSION, REASONING_ENABLED, REASONING_EFFORT, BGP_ROUTES
|
||||||
global _api_key_pool, PROMPT_ENHANCER
|
global _api_key_pool, PROMPT_ENHANCER
|
||||||
global VISION_FALLBACK_URL, VISION_FALLBACK_MODEL, VISION_FALLBACK_KEY
|
global VISION_FALLBACK_URL, VISION_FALLBACK_MODEL, VISION_FALLBACK_KEY
|
||||||
global CAVEMAN_MODE, RTK_COMPRESSION
|
global CAVEMAN_MODE, RTK_COMPRESSION, AUTO_COMPACT, ADAPTIVE_COMPACT, TOOL_OUTPUT_TRUNCATION
|
||||||
|
|
||||||
CONFIG = load_config()
|
CONFIG = load_config()
|
||||||
PORT = CONFIG["port"]
|
PORT = CONFIG["port"]
|
||||||
@@ -1215,6 +1218,11 @@ def _init_runtime():
|
|||||||
BGP_ROUTES = CONFIG.get("bgp_routes", [])
|
BGP_ROUTES = CONFIG.get("bgp_routes", [])
|
||||||
CAVEMAN_MODE = CONFIG.get("caveman_mode", False) or os.environ.get("CAVEMAN_MODE") == "1"
|
CAVEMAN_MODE = CONFIG.get("caveman_mode", False) or os.environ.get("CAVEMAN_MODE") == "1"
|
||||||
RTK_COMPRESSION = CONFIG.get("rtk_compression", False) or os.environ.get("RTK_COMPRESSION") == "1"
|
RTK_COMPRESSION = CONFIG.get("rtk_compression", False) or os.environ.get("RTK_COMPRESSION") == "1"
|
||||||
|
AUTO_COMPACT = CONFIG.get("auto_compact", False) or os.environ.get("AUTO_COMPACT") == "1"
|
||||||
|
ADAPTIVE_COMPACT = CONFIG.get("adaptive_compact", False) or os.environ.get("ADAPTIVE_COMPACT") == "1"
|
||||||
|
TOOL_OUTPUT_TRUNCATION = CONFIG.get("tool_output_truncation", True)
|
||||||
|
if os.environ.get("TOOL_OUTPUT_TRUNCATION") == "0":
|
||||||
|
TOOL_OUTPUT_TRUNCATION = False
|
||||||
_api_key_pool = None
|
_api_key_pool = None
|
||||||
if API_KEY and "," in API_KEY and not OAUTH_PROVIDER.startswith("google") and BACKEND not in ("codebuff", "freebuff"):
|
if API_KEY and "," in API_KEY and not OAUTH_PROVIDER.startswith("google") and BACKEND not in ("codebuff", "freebuff"):
|
||||||
_api_key_pool = APIKeyPool(BACKEND, API_KEY)
|
_api_key_pool = APIKeyPool(BACKEND, API_KEY)
|
||||||
@@ -1962,6 +1970,8 @@ def _crof_item_limit(model):
|
|||||||
return min(per_model, _CROF_ADAPTIVE["global_item_limit"])
|
return min(per_model, _CROF_ADAPTIVE["global_item_limit"])
|
||||||
|
|
||||||
def _crof_compact_for_retry(input_data, model, aggression=0):
|
def _crof_compact_for_retry(input_data, model, aggression=0):
|
||||||
|
if "crof.ai" not in TARGET_URL:
|
||||||
|
return input_data
|
||||||
limit = _crof_item_limit(model)
|
limit = _crof_item_limit(model)
|
||||||
if not isinstance(input_data, list) or len(input_data) < 2:
|
if not isinstance(input_data, list) or len(input_data) < 2:
|
||||||
return input_data
|
return input_data
|
||||||
@@ -2149,10 +2159,11 @@ def _compact_input(input_data):
|
|||||||
compressed_data.append(item)
|
compressed_data.append(item)
|
||||||
input_data = compressed_data
|
input_data = compressed_data
|
||||||
|
|
||||||
if not isinstance(input_data, list) or len(input_data) <= _MAX_INPUT_ITEMS:
|
# Skip compaction entirely — just do optional tool output truncation
|
||||||
|
if not AUTO_COMPACT or not isinstance(input_data, list) or len(input_data) <= _MAX_INPUT_ITEMS:
|
||||||
out = []
|
out = []
|
||||||
for item in input_data:
|
for item in input_data:
|
||||||
if isinstance(item, dict) and item.get("type") == "function_call_output":
|
if TOOL_OUTPUT_TRUNCATION and isinstance(item, dict) and item.get("type") == "function_call_output":
|
||||||
o = item.get("output", "")
|
o = item.get("output", "")
|
||||||
if len(o) > _MAX_TOOL_OUTPUT_CHARS:
|
if len(o) > _MAX_TOOL_OUTPUT_CHARS:
|
||||||
item = dict(item)
|
item = dict(item)
|
||||||
@@ -2191,7 +2202,7 @@ def _compact_input(input_data):
|
|||||||
return head + tail
|
return head + tail
|
||||||
|
|
||||||
for item in tail:
|
for item in tail:
|
||||||
if isinstance(item, dict) and item.get("type") == "function_call_output":
|
if TOOL_OUTPUT_TRUNCATION and isinstance(item, dict) and item.get("type") == "function_call_output":
|
||||||
o = item.get("output", "")
|
o = item.get("output", "")
|
||||||
if len(o) > _MAX_TOOL_OUTPUT_CHARS:
|
if len(o) > _MAX_TOOL_OUTPUT_CHARS:
|
||||||
item["output"] = o[:_MAX_TOOL_OUTPUT_CHARS] + f"\n... [truncated {len(o) - _MAX_TOOL_OUTPUT_CHARS} chars]"
|
item["output"] = o[:_MAX_TOOL_OUTPUT_CHARS] + f"\n... [truncated {len(o) - _MAX_TOOL_OUTPUT_CHARS} chars]"
|
||||||
@@ -5703,7 +5714,6 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
raw_types = [i.get("type") for i in raw_input] if isinstance(raw_input, list) else "str"
|
raw_types = [i.get("type") for i in raw_input] if isinstance(raw_input, list) else "str"
|
||||||
resolved_types = [i.get("type") for i in input_data] if isinstance(input_data, list) else "str"
|
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", encoding="utf-8") 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"\n{'='*60}\n{_ts} [session={_sid}] REQUEST {self.path}\n")
|
||||||
_lf.write(f" prev_id={prev_id}\n")
|
_lf.write(f" prev_id={prev_id}\n")
|
||||||
@@ -5807,7 +5817,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
body["input"] = input_data
|
body["input"] = input_data
|
||||||
|
|
||||||
compacted = False
|
compacted = False
|
||||||
if policy.get("compaction") and isinstance(input_data, list) and "claude" not in model.lower():
|
if ADAPTIVE_COMPACT and policy.get("compaction") and isinstance(input_data, list) and "claude" not in model.lower():
|
||||||
input_data, compacted = _adaptive_compact(input_data, model, policy)
|
input_data, compacted = _adaptive_compact(input_data, model, policy)
|
||||||
if compacted:
|
if compacted:
|
||||||
body = dict(body)
|
body = dict(body)
|
||||||
@@ -5819,7 +5829,7 @@ 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)
|
||||||
_crof_eligible = True
|
_crof_eligible = "crof.ai" in TARGET_URL
|
||||||
if _crof_eligible and not compacted and isinstance(input_data, list):
|
if _crof_eligible and not compacted and isinstance(input_data, list):
|
||||||
_needs_compact = len(input_data) > crof_limit
|
_needs_compact = len(input_data) > crof_limit
|
||||||
max_tok = _get_model_max_tokens(model)
|
max_tok = _get_model_max_tokens(model)
|
||||||
@@ -6771,7 +6781,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
body["input"] = input_data
|
body["input"] = input_data
|
||||||
|
|
||||||
compacted = False
|
compacted = False
|
||||||
if policy.get("compaction") and isinstance(input_data, list) and "claude" not in model.lower():
|
if ADAPTIVE_COMPACT and policy.get("compaction") and isinstance(input_data, list) and "claude" not in model.lower():
|
||||||
input_data, compacted = _adaptive_compact(input_data, model, policy)
|
input_data, compacted = _adaptive_compact(input_data, model, policy)
|
||||||
if compacted:
|
if compacted:
|
||||||
body = dict(body)
|
body = dict(body)
|
||||||
@@ -7662,9 +7672,10 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
has_content = False
|
has_content = False
|
||||||
has_message = False
|
has_message = False
|
||||||
has_tool_call = False
|
has_tool_call = False
|
||||||
|
_last_stream_usage = {}
|
||||||
|
|
||||||
def _observe_event(event):
|
def _observe_event(event):
|
||||||
nonlocal last_resp_id, last_output, last_status, finish_reason, has_content, has_message, has_tool_call
|
nonlocal last_resp_id, last_output, last_status, finish_reason, has_content, has_message, has_tool_call, _last_stream_usage
|
||||||
for line in event.strip().split("\n"):
|
for line in event.strip().split("\n"):
|
||||||
if line.startswith("data: "):
|
if line.startswith("data: "):
|
||||||
try:
|
try:
|
||||||
@@ -7677,6 +7688,9 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
has_tool_call = any(o.get("type") == "function_call" for o in (last_output or []))
|
has_tool_call = any(o.get("type") == "function_call" for o in (last_output or []))
|
||||||
has_message = any(o.get("type") == "message" for o in (last_output or []))
|
has_message = any(o.get("type") == "message" for o in (last_output or []))
|
||||||
has_content = has_message or has_tool_call
|
has_content = has_message or has_tool_call
|
||||||
|
resp_usage = d.get("response", {}).get("usage")
|
||||||
|
if resp_usage:
|
||||||
|
_last_stream_usage = resp_usage
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -9044,7 +9058,7 @@ def main():
|
|||||||
allow_reuse_address = True
|
allow_reuse_address = True
|
||||||
daemon_threads = True
|
daemon_threads = True
|
||||||
request_queue_size = 64
|
request_queue_size = 64
|
||||||
BIND_HOST = getattr(_args, "host", None) or os.environ.get("CODEX_HOST", "127.0.0.1")
|
BIND_HOST = os.environ.get("CODEX_HOST", "127.0.0.1")
|
||||||
SERVER = ReusableHTTPServer((BIND_HOST, PORT), Handler)
|
SERVER = ReusableHTTPServer((BIND_HOST, PORT), Handler)
|
||||||
print(f"translate-proxy ({BACKEND}) listening on http://{BIND_HOST}:{PORT}", flush=True)
|
print(f"translate-proxy ({BACKEND}) listening on http://{BIND_HOST}:{PORT}", flush=True)
|
||||||
print(f"Target: {TARGET_URL}", flush=True)
|
print(f"Target: {TARGET_URL}", flush=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user