v3.9.7 — Forward real FreeBuff error messages, fix BrokenPipeError crash, fix SyntaxWarnings

This commit is contained in:
Roman
2026-05-25 11:07:02 +04:00
Unverified
parent bec34079c6
commit 72ebfa3ef8
5 changed files with 165 additions and 292 deletions

View File

@@ -70,9 +70,9 @@ FIX 6: Double-wrapped arguments (nested {"cmd": "{\"cmd\": \"curl...\"}"}")
FIX 7: _extract_field can't read values starting with \"
Symptom: sandbox_permissions="allow_all" passes through unnormalized because
_extract_field sees val_start=\ (backslash) which != " or { → returns None
_extract_field sees val_start=\\ (backslash) which != \" or { → returns None
Fix: Skip leading backslash before checking for " or { value type.
Location: _extract_field() leading-\ skip
Location: _extract_field() leading-backslash skip
FIX 8: Adaptive probing caused format mismatch (REVERTED)
Symptom: Probe system discovered OpenAI tool_calls+role=tool format but CC API couldn't
@@ -335,10 +335,31 @@ def _freebuff_get_session(token, model):
req = urllib.request.Request(url, data=body, headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {token}",
"User-Agent": "codex-launcher/3.9.0",
"User-Agent": "codex-launcher/3.9.7",
"x-freebuff-model": model,
})
resp = urllib.request.urlopen(req, timeout=15)
try:
resp = urllib.request.urlopen(req, timeout=15)
except urllib.error.HTTPError as e:
err_body = e.read().decode()[:1000]
if e.code == 429:
retry_s = 120
user_msg = ""
try:
err_data = json.loads(err_body)
retry_ms = err_data.get("retryAfterMs", 0)
if retry_ms:
retry_s = retry_ms / 1000
user_msg = err_data.get("message", err_data.get("error", ""))
if isinstance(user_msg, dict):
user_msg = user_msg.get("message", "")
except Exception:
pass
if not user_msg:
user_msg = _sanitize_err_body(err_body)
raise RateLimitError(retry_s, user_msg)
print(f"[freebuff] session HTTP {e.code}: {err_body[:200]}", file=sys.stderr)
return None
data = json.loads(resp.read())
instance_id = data.get("instanceId", data.get("data", {}).get("instance_id", ""))
expires_at = data.get("remainingMs", 0)
@@ -350,6 +371,8 @@ def _freebuff_get_session(token, model):
print(f"[freebuff] session active, instance={instance_id[:8]}...", file=sys.stderr)
return instance_id
return None
except RateLimitError:
raise
except Exception as e:
print(f"[freebuff] session failed: {e}", file=sys.stderr)
return None
@@ -360,21 +383,31 @@ def _freebuff_start_run(token, agent_id):
req = urllib.request.Request(url, data=body, headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {token}",
"User-Agent": "codex-launcher/3.9.0",
"User-Agent": "codex-launcher/3.9.7",
})
try:
resp = urllib.request.urlopen(req, timeout=15)
data = json.loads(resp.read())
run_id = data.get("runId")
print(f"[freebuff] started run {run_id} for agent {agent_id}", file=sys.stderr)
return run_id
return run_id, None
except urllib.error.HTTPError as e:
err = e.read().decode()[:300]
err = e.read().decode()[:500]
print(f"[freebuff] start run failed: HTTP {e.code}: {err}", file=sys.stderr)
return None
if e.code == 429:
retry_s = 120
try:
err_data = json.loads(err)
retry_ms = err_data.get("retryAfterMs", 0)
if retry_ms:
retry_s = retry_ms / 1000
except Exception:
pass
return None, ("rate_limit_error", 429, retry_s, _sanitize_err_body(err))
return None, ("upstream_error", e.code, 0, _sanitize_err_body(err))
except Exception as e:
print(f"[freebuff] start run error: {e}", file=sys.stderr)
return None
return None, ("proxy_error", 502, 0, str(e))
def _freebuff_finish_run(token, run_id, status="completed"):
url = f"{_FREEBUFF_API_URL}/api/v1/agent-runs"
@@ -383,7 +416,7 @@ def _freebuff_finish_run(token, run_id, status="completed"):
req = urllib.request.Request(url, data=body, headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {token}",
"User-Agent": "codex-launcher/3.9.0",
"User-Agent": "codex-launcher/3.9.7",
})
try:
urllib.request.urlopen(req, timeout=10)
@@ -392,6 +425,12 @@ def _freebuff_finish_run(token, run_id, status="completed"):
# ═══════════════════════════════════════════════════════════════════
# Multi-account rotation system
class RateLimitError(Exception):
def __init__(self, retry_seconds, message=""):
self.retry_seconds = retry_seconds
self.message = message
super().__init__(f"rate-limited for {retry_seconds:.0f}s: {message}")
# ═══════════════════════════════════════════════════════════════════
class AccountPool:
@@ -2804,7 +2843,7 @@ def _parse_commandcode_text_tool_calls(text):
Delegates to _extract_args() for the arguments field (handles unescaped + escaped JSON).
Delegates to _extract_field() for name/id/sandbox_permissions/justification
(with FIX 7 for leading-\ handling).
(with FIX 7 for leading-backslash handling).
Normalizes sandbox_permissions to valid values (use_default|require_escalated|with_user_approval)
[FIX 6] Prevents double-wrapped args: {"cmd": "{\"cmd\": \"curl...\"}"}
@@ -5209,13 +5248,30 @@ class Handler(http.server.BaseHTTPRequestHandler):
if attempt > 0:
print(f"[freebuff] rotation attempt {attempt+1}/{n_accounts}, trying account {acct_id}", file=sys.stderr)
run_id = _freebuff_start_run(token, agent_id)
run_id, run_err = _freebuff_start_run(token, agent_id)
if not run_id:
_fb_pool.mark_rate_limited(acct, 60)
last_err = ("upstream_error", 502, "Failed to start freebuff agent run. Check credentials and network.")
if run_err and run_err[0] == "rate_limit_error":
retry_s = run_err[2]
_fb_pool.mark_rate_limited(acct, retry_s)
last_err = ("rate_limit_error", run_err[1], f"Account {acct_id} rate-limited by FreeBuff: {run_err[3]}")
else:
_fb_pool.mark_rate_limited(acct, 60)
last_err = ("upstream_error", run_err[1] if run_err else 502,
f"Failed to start agent run for {acct_id}: {run_err[3] if run_err else 'unknown error'}")
continue
instance_id = _freebuff_get_session(token, model)
try:
instance_id = _freebuff_get_session(token, model)
except RateLimitError as rle:
retry_s = rle.retry_seconds
fb_msg = rle.message
mins = int(retry_s // 60)
user_msg = fb_msg if fb_msg else f"Daily session limit reached. Resets in {mins}m."
print(f"[freebuff] session 429 for {acct_id}, retry after {retry_s:.0f}s", file=sys.stderr)
_fb_pool.mark_rate_limited(acct, retry_s)
_freebuff_finish_run(token, run_id, "completed")
last_err = ("rate_limit_error", 429, user_msg)
continue
input_data = body.get("input", "")
instructions = body.get("instructions", "").strip()
@@ -5249,7 +5305,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {token}",
"User-Agent": "codex-launcher/3.9.0",
"User-Agent": "codex-launcher/3.9.7",
"x-freebuff-model": model,
}
if instance_id:
@@ -5266,14 +5322,22 @@ class Handler(http.server.BaseHTTPRequestHandler):
_freebuff_finish_run(token, run_id, "failed")
if e.code in (429, 426):
reset_ms = 0
fb_msg = ""
try:
err_json = json.loads(err_body)
reset_ms = err_json.get("retryAfterMs", 0)
fb_msg = err_json.get("message", err_json.get("error", ""))
if isinstance(fb_msg, dict):
fb_msg = fb_msg.get("message", "")
except Exception:
pass
duration = max(reset_ms / 1000, 120) if reset_ms else 120
mins = int(duration // 60)
if not fb_msg:
fb_msg = _sanitize_err_body(err_body)
user_msg = f"{fb_msg} (resets in {mins}m)" if fb_msg else f"Rate limited. Resets in {mins}m."
_fb_pool.mark_rate_limited(acct, duration)
last_err = ("upstream_error", e.code, _sanitize_err_body(err_body))
last_err = ("rate_limit_error", e.code, user_msg)
print(f"[freebuff] account {acct_id} got HTTP {e.code}, rotating", file=sys.stderr)
continue
if _is_reasoning_content_error(err_body):
@@ -5357,13 +5421,38 @@ class Handler(http.server.BaseHTTPRequestHandler):
return
if last_err:
return self.send_json(last_err[1], {"error": {"type": last_err[0], "message": f"All {n_accounts} accounts exhausted. {last_err[2]}"}})
msg = last_err[2]
resp_id = f"resp_{uuid.uuid4().hex[:24]}"
result = {
"id": resp_id,
"object": "response",
"created_at": int(time.time()),
"model": model,
"status": "completed",
"output": [{
"id": f"msg_{uuid.uuid4().hex[:24]}",
"type": "message",
"role": "assistant",
"content": [{
"type": "output_text",
"text": msg,
"annotations": [],
}],
"status": "completed",
}],
"usage": {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0},
}
try:
return self.send_json(200, result)
except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
return
def _fb_retry_thinking_disabled(self, body, model, token, agent_id, stream, tracker, input_data, instructions, original_error, acct=None):
run_id = _freebuff_start_run(token, agent_id)
run_id, run_err = _freebuff_start_run(token, agent_id)
if not run_id:
return self.send_json(502, {"error": {"type": "upstream_error",
"message": "Failed to start freebuff agent run for retry."}})
msg = run_err[3] if run_err else "unknown error"
return self.send_json(run_err[1] if run_err else 502, {"error": {"type": run_err[0] if run_err else "upstream_error",
"message": f"Failed to start agent run for retry: {msg}"}})
instance_id = _freebuff_get_session(token, model)
messages = _fb_input_to_messages(input_data, instructions)
_freebuff_hard_disable_reasoning(messages)
@@ -5385,7 +5474,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
if body.get("tool_choice"):
chat_body["tool_choice"] = body["tool_choice"]
target = f"{_FREEBUFF_API_URL}/api/v1/chat/completions"
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}", "User-Agent": "codex-launcher/3.9.0", "x-freebuff-model": model}
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}", "User-Agent": "codex-launcher/3.9.7", "x-freebuff-model": model}
if instance_id:
headers["x-freebuff-instance-id"] = instance_id
print(f"[freebuff] retry POST {target} model={model} stream={stream} run={run_id} (thinking disabled via DeepSeek native)", file=sys.stderr)