v3.9.7 — Rename all freebuff references to codebuff in code and docs

This commit is contained in:
Roman
2026-05-25 11:31:05 +04:00
Unverified
parent 0ce57693c5
commit d89f65ffd1
3 changed files with 145 additions and 145 deletions

Binary file not shown.

View File

@@ -27,13 +27,13 @@ model_catalog_json = ""
CHANGELOG = [
("3.9.7", "2026-05-25", [
"Forward real FreeBuff error messages to user (not generic 429)",
"Forward real Codebuff error messages to user (not generic 429)",
"Return HTTP 200 with Responses API format for rate limits so Codex displays message",
"Extract retryAfterMs from FreeBuff 429 responses for accurate cooldown",
"Extract retryAfterMs from Codebuff 429 responses for accurate cooldown",
"RateLimitError carries upstream message through session + chat error paths",
"BrokenPipeError crash fix on 'all accounts exhausted' response",
"Fix 3 SyntaxWarnings for invalid escape sequences in docstrings",
"_freebuff_start_run returns actual error body instead of None",
"_codebuff_start_run returns actual error body instead of None",
]),
("3.9.6", "2026-05-25", [
"Fix Gemini follow-up turns returning text-only instead of tool calls",
@@ -45,37 +45,37 @@ CHANGELOG = [
"Smart tool output compaction: old=3000, recent=20000 chars",
"Follow-through guardrail system instruction for autonomous agent behavior",
"Stream hang fix for function-call-only responses",
"Multi-account rotation for freebuff, Google OAuth, API keys",
"Multi-account rotation for codebuff, Google OAuth, API keys",
"/v1/accounts endpoint for account pool status",
]),
("3.9.0", "2026-05-24", [
"Multi-account rotation for OAuth providers (freebuff, Google, API keys)",
"Multi-account rotation for OAuth providers (codebuff, Google, API keys)",
"Automatic failover: when one account hits rate limit, next is used",
"Freebuff: supports accounts[] array in credentials.json",
"Codebuff: supports accounts[] array in credentials.json",
"Google OAuth: supports multiple token files (google-*-oauth-token-N.json)",
"API keys: comma-separated keys rotate on 429 errors",
"New /v1/accounts endpoint shows account pool status",
"Added x-freebuff-model and x-freebuff-instance-id headers",
"Added x-codebuff-model and x-codebuff-instance-id headers",
]),
("3.8.4", "2026-05-24", [
"FIXED: Freebuff streaming — SSE events now reach Codex client",
"Root cause: stream_buffered_events was never called for freebuff",
"Freebuff stream uses buffered flushing (30ms / 4KB / urgent)",
"Freebuff OAuth — built-in login flow (no external CLI needed)",
"Freebuff API: reverse-engineered www.codebuff.com endpoints",
"Freebuff session management with instance ID (waiting room)",
"Freebuff agent run lifecycle (start/finish) with model routing",
"FIXED: Codebuff streaming — SSE events now reach Codex client",
"Root cause: stream_buffered_events was never called for codebuff",
"Codebuff stream uses buffered flushing (30ms / 4KB / urgent)",
"Codebuff OAuth — built-in login flow (no external CLI needed)",
"Codebuff API: reverse-engineered www.codebuff.com endpoints",
"Codebuff session management with instance ID (waiting room)",
"Codebuff agent run lifecycle (start/finish) with model routing",
"Free DeepSeek V4 Pro, V4 Flash, Kimi K2.6, MiniMax M2.7",
"Reasoning mode works with freebuff (thinking tokens supported)",
"Reasoning mode works with codebuff (thinking tokens supported)",
"GUI: Sandbox mode selector (Read-only / Workspace / Full Access)",
"GUI: Approval mode selector (Untrusted / On Request / Full Auto)",
"GUI: Freebuff Login button in endpoint editor",
"GUI: Codebuff Login button in endpoint editor",
"Fixed _STATS undefined error in /health endpoint",
"Fixed freebuff credential path (reads default account)",
"Fixed codebuff credential path (reads default account)",
]),
("3.8.1", "2026-05-24", [
"Freebuff integration — free DeepSeek V4 Pro, V4 Flash, Kimi K2.6, MiniMax M2.7",
"Freebuff backend: auto agent-run lifecycle, credential detection, model routing",
"Codebuff integration — free DeepSeek V4 Pro, V4 Flash, Kimi K2.6, MiniMax M2.7",
"Codebuff backend: auto agent-run lifecycle, credential detection, model routing",
"Restored all provider presets (Command Code, Crof, OpenAdapter, OpenRouter, etc.)",
"AI Monitoring — self-healing watchdog with 3-tier response system",
"HealthWatcher: monitors proxy health every 5s, auto-restarts on crash",
@@ -365,7 +365,7 @@ PROVIDER_PRESETS = {
"GLM-4-Flash", "GLM-4-FlashX", "GLM-Z1-Flash",
],
},
"Freebuff (Free DeepSeek/Kimi)": {
"Codebuff (Free DeepSeek/Kimi)": {
"backend_type": "freebuff",
"base_url": "https://freebuff.com",
"oauth_provider": "freebuff",
@@ -386,7 +386,7 @@ def label_for_backend(backend_type):
"openai-compat": "OpenAI-compatible",
"anthropic": "Anthropic",
"command-code": "Command Code",
"freebuff": "Freebuff (Free AI)",
"freebuff": "Codebuff (Free AI)",
"native": "Native",
}.get(backend_type, backend_type)
@@ -3010,7 +3010,7 @@ class EditEndpointDialog(Gtk.Dialog):
for val, lab in [("openai-compat", "OpenAI-compatible (needs proxy)"),
("anthropic", "Anthropic (needs proxy)"),
("command-code", "Command Code (needs proxy)"),
("freebuff", "Freebuff - Free DeepSeek/Kimi (needs proxy)"),
("freebuff", "Codebuff - Free DeepSeek/Kimi (needs proxy)"),
("gemini-oauth-cli", "Gemini CLI OAuth (needs proxy)"),
("gemini-oauth-antigravity", "Antigravity OAuth (needs proxy)"),
("native", "Native OpenAI (no proxy)")]:
@@ -3176,8 +3176,8 @@ class EditEndpointDialog(Gtk.Dialog):
is_oauth = bool(oauth_provider)
self._oauth_btn.set_visible(is_oauth)
if oauth_provider == "freebuff":
self._oauth_btn.set_label("Freebuff Login")
self._entry_key.set_placeholder_text("Auto-filled by freebuff login")
self._oauth_btn.set_label("Codebuff Login")
self._entry_key.set_placeholder_text("Auto-filled by codebuff login")
elif is_oauth:
self._oauth_btn.set_label("OAuth Login")
self._entry_key.set_placeholder_text("Auto-filled by OAuth")
@@ -3489,7 +3489,7 @@ class EditEndpointDialog(Gtk.Dialog):
dlg.run()
def _freebuff_oauth_flow(self):
dlg = Gtk.Dialog(title="Freebuff Login", parent=self, modal=True)
dlg = Gtk.Dialog(title="Codebuff Login", parent=self, modal=True)
dlg.add_button("Cancel", Gtk.ResponseType.CANCEL)
dlg.set_default_size(500, 240)
area = dlg.get_content_area()
@@ -3499,7 +3499,7 @@ class EditEndpointDialog(Gtk.Dialog):
area.set_margin_bottom(12)
area.set_spacing(8)
area.pack_start(Gtk.Label(label="<b>Sign in with GitHub via Freebuff</b>", use_markup=True, xalign=0), False, False, 0)
area.pack_start(Gtk.Label(label="<b>Sign in with GitHub via Codebuff</b>", use_markup=True, xalign=0), False, False, 0)
self._oauth_status = Gtk.Label(label="Requesting login URL…", xalign=0)
self._oauth_status.set_line_wrap(True)
@@ -3520,10 +3520,10 @@ class EditEndpointDialog(Gtk.Dialog):
self._fb_oauth_result = {"success": False, "user": None, "error": None}
def _freebuff_auth_thread():
def _codebuff_auth_thread():
try:
fingerprint_id = str(uuid.uuid4())
auth_url = "https://freebuff.com/api/auth/cli/code"
auth_url = "https://codebuff.com/api/auth/cli/code"
body = json.dumps({"fingerprintId": fingerprint_id}).encode()
req = urllib.request.Request(auth_url, data=body,
headers={"Content-Type": "application/json", "User-Agent": "codex-launcher/3.9.7"})
@@ -3534,7 +3534,7 @@ class EditEndpointDialog(Gtk.Dialog):
expires_at = data.get("expiresAt", 0) or data.get("expires_at", 0)
if not login_url:
self._fb_oauth_result["error"] = "Server returned no login URL"
GLib.idle_add(self._freebuff_oauth_done, dlg, spinner)
GLib.idle_add(self._codebuff_oauth_done, dlg, spinner)
return
def _set_link():
@@ -3545,7 +3545,7 @@ class EditEndpointDialog(Gtk.Dialog):
webbrowser.open(login_url)
poll_url = f"https://freebuff.com/api/auth/cli/status?fingerprintId={urllib.parse.quote(fingerprint_id)}&fingerprintHash={urllib.parse.quote(fingerprint_hash)}&expiresAt={expires_at}"
poll_url = f"https://codebuff.com/api/auth/cli/status?fingerprintId={urllib.parse.quote(fingerprint_id)}&fingerprintHash={urllib.parse.quote(fingerprint_hash)}&expiresAt={expires_at}"
deadline = time.time() + 300
while time.time() < deadline:
time.sleep(2)
@@ -3558,23 +3558,23 @@ class EditEndpointDialog(Gtk.Dialog):
if user and user.get("authToken"):
self._fb_oauth_result["success"] = True
self._fb_oauth_result["user"] = user
GLib.idle_add(self._freebuff_oauth_done, dlg, spinner)
GLib.idle_add(self._codebuff_oauth_done, dlg, spinner)
return
except urllib.error.HTTPError:
pass
except Exception:
pass
self._fb_oauth_result["error"] = "Login timed out after 5 minutes."
GLib.idle_add(self._freebuff_oauth_done, dlg, spinner)
GLib.idle_add(self._codebuff_oauth_done, dlg, spinner)
except Exception as e:
self._fb_oauth_result["error"] = str(e)[:200]
GLib.idle_add(self._freebuff_oauth_done, dlg, spinner)
GLib.idle_add(self._codebuff_oauth_done, dlg, spinner)
threading.Thread(target=_freebuff_auth_thread, daemon=True).start()
threading.Thread(target=_codebuff_auth_thread, daemon=True).start()
dlg.connect("response", lambda d, r: d.destroy())
dlg.run()
def _freebuff_oauth_done(self, dlg, spinner):
def _codebuff_oauth_done(self, dlg, spinner):
spinner.stop()
if self._fb_oauth_result["success"] and self._fb_oauth_result["user"]:
user = self._fb_oauth_result["user"]
@@ -3593,7 +3593,7 @@ class EditEndpointDialog(Gtk.Dialog):
os.chmod(creds_path, 0o600)
self._entry_key.set_text(user.get("authToken", ""))
self._oauth_status.set_markup('<span foreground="#27ae60" weight="bold">Authorization successful! Credentials saved.</span>')
dlg.set_title("Freebuff Login Success")
dlg.set_title("Codebuff Login Success")
GLib.timeout_add(1500, lambda: dlg.response(Gtk.ResponseType.OK))
else:
self._oauth_status.set_markup(f'<span foreground="#e74c3c">{self._fb_oauth_result["error"] or "Login failed."}</span>')

View File

@@ -172,7 +172,7 @@ DEFAULT_MODELS = {
"anthropic": [
{"id": "claude-sonnet-4-20250514", "object": "model", "created": 1700000000, "owned_by": "anthropic"},
],
"freebuff": [
"codebuff": [
{"id": "deepseek/deepseek-v4-pro", "object": "model", "created": 1700000000, "owned_by": "freebuff"},
{"id": "deepseek/deepseek-v4-flash", "object": "model", "created": 1700000000, "owned_by": "freebuff"},
{"id": "moonshotai/kimi-k2.6", "object": "model", "created": 1700000000, "owned_by": "freebuff"},
@@ -187,7 +187,7 @@ def load_config():
p = argparse.ArgumentParser(description="Responses API translation proxy")
p.add_argument("--config", help="JSON config file path")
p.add_argument("--port", type=int, default=None)
p.add_argument("--backend", default=None, choices=["openai-compat", "anthropic", "command-code", "freebuff", "auto"])
p.add_argument("--backend", default=None, choices=["openai-compat", "anthropic", "command-code", "codebuff", "auto"])
p.add_argument("--target-url", default=None)
p.add_argument("--api-key", default=None)
p.add_argument("--models-file", default=None, help="JSON file with model list array")
@@ -294,43 +294,43 @@ _conn_pool = {}
_STREAM_IDLE_TIMEOUT = 300
_FREEBUFF_AUTH_URL = "https://freebuff.com"
_FREEBUFF_API_URL = "https://www.codebuff.com"
_FREEBUFF_AGENT_MAP = {
_CODEBUFF_AUTH_URL = "https://freebuff.com"
_CODEBUFF_API_URL = "https://www.codebuff.com"
_CODEBUFF_AGENT_MAP = {
"deepseek/deepseek-v4-pro": "base2-free-deepseek",
"deepseek/deepseek-v4-flash": "base2-free-deepseek-flash",
"moonshotai/kimi-k2.6": "base2-free-kimi",
"minimax/minimax-m2.7": "base2-free",
}
_FREEBUFF_CREDS_PATH = os.path.join(os.path.expanduser("~"), ".config", "manicode", "credentials.json")
_freebuff_token_cache = {"token": None, "checked": 0}
_freebuff_session_cache = {"instance_id": None, "expires": 0, "model": None}
_freebuff_token_lock = threading.Lock()
_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()
def _get_freebuff_token():
with _freebuff_token_lock:
if _freebuff_token_cache["token"] and _freebuff_token_cache["checked"] > time.time() - 300:
return _freebuff_token_cache["token"]
def _get_codebuff_token():
with _codebuff_token_lock:
if _codebuff_token_cache["token"] and _codebuff_token_cache["checked"] > time.time() - 300:
return _codebuff_token_cache["token"]
try:
with open(_FREEBUFF_CREDS_PATH) as f:
with open(_CODEBUFF_CREDS_PATH) as f:
creds = json.load(f)
default_account = creds.get("default", {})
token = default_account.get("authToken") or creds.get("apiKey") or ""
with _freebuff_token_lock:
_freebuff_token_cache["token"] = token
_freebuff_token_cache["checked"] = time.time()
with _codebuff_token_lock:
_codebuff_token_cache["token"] = token
_codebuff_token_cache["checked"] = time.time()
return token
except Exception as e:
print(f"[freebuff] no credentials at {_FREEBUFF_CREDS_PATH}: {e}", file=sys.stderr)
print(f"[codebuff] no credentials at {_CODEBUFF_CREDS_PATH}: {e}", file=sys.stderr)
return ""
def _freebuff_get_session(token, model):
with _freebuff_token_lock:
sc = _freebuff_session_cache
def _codebuff_get_session(token, model):
with _codebuff_token_lock:
sc = _codebuff_session_cache
if sc["instance_id"] and sc["expires"] > time.time() + 60 and sc["model"] == model:
return sc["instance_id"]
try:
url = f"{_FREEBUFF_API_URL}/api/v1/freebuff/session"
url = f"{_CODEBUFF_API_URL}/api/v1/freebuff/session"
body = json.dumps({"model": model}).encode()
req = urllib.request.Request(url, data=body, headers={
"Content-Type": "application/json",
@@ -358,27 +358,27 @@ def _freebuff_get_session(token, model):
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)
print(f"[codebuff] 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)
if instance_id:
with _freebuff_token_lock:
_freebuff_session_cache["instance_id"] = instance_id
_freebuff_session_cache["expires"] = time.time() + min(expires_at / 1000, 3600)
_freebuff_session_cache["model"] = model
print(f"[freebuff] session active, instance={instance_id[:8]}...", file=sys.stderr)
with _codebuff_token_lock:
_codebuff_session_cache["instance_id"] = instance_id
_codebuff_session_cache["expires"] = time.time() + min(expires_at / 1000, 3600)
_codebuff_session_cache["model"] = model
print(f"[codebuff] 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)
print(f"[codebuff] session failed: {e}", file=sys.stderr)
return None
def _freebuff_start_run(token, agent_id):
url = f"{_FREEBUFF_API_URL}/api/v1/agent-runs"
def _codebuff_start_run(token, agent_id):
url = f"{_CODEBUFF_API_URL}/api/v1/agent-runs"
body = json.dumps({"action": "START", "agentId": agent_id, "ancestorRunIds": []}).encode()
req = urllib.request.Request(url, data=body, headers={
"Content-Type": "application/json",
@@ -389,11 +389,11 @@ def _freebuff_start_run(token, agent_id):
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)
print(f"[codebuff] started run {run_id} for agent {agent_id}", file=sys.stderr)
return run_id, None
except urllib.error.HTTPError as e:
err = e.read().decode()[:500]
print(f"[freebuff] start run failed: HTTP {e.code}: {err}", file=sys.stderr)
print(f"[codebuff] start run failed: HTTP {e.code}: {err}", file=sys.stderr)
if e.code == 429:
retry_s = 120
try:
@@ -406,11 +406,11 @@ def _freebuff_start_run(token, agent_id):
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)
print(f"[codebuff] start run error: {e}", file=sys.stderr)
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"
def _codebuff_finish_run(token, run_id, status="completed"):
url = f"{_CODEBUFF_API_URL}/api/v1/agent-runs"
body = json.dumps({"action": "FINISH", "runId": run_id, "status": status,
"totalSteps": 1, "directCredits": 0, "totalCredits": 0}).encode()
req = urllib.request.Request(url, data=body, headers={
@@ -421,7 +421,7 @@ def _freebuff_finish_run(token, run_id, status="completed"):
try:
urllib.request.urlopen(req, timeout=10)
except Exception as e:
print(f"[freebuff] finish run {run_id} error: {e}", file=sys.stderr)
print(f"[codebuff] finish run {run_id} error: {e}", file=sys.stderr)
# ═══════════════════════════════════════════════════════════════════
# Multi-account rotation system
@@ -514,12 +514,12 @@ class AccountPool:
result.append(info)
return result
class FreebuffAccountPool(AccountPool):
class CodebuffAccountPool(AccountPool):
def _do_load(self):
if not os.path.exists(_FREEBUFF_CREDS_PATH):
if not os.path.exists(_CODEBUFF_CREDS_PATH):
return None
try:
with open(_FREEBUFF_CREDS_PATH) as f:
with open(_CODEBUFF_CREDS_PATH) as f:
creds = json.load(f)
except Exception:
return None
@@ -588,14 +588,14 @@ class APIKeyPool(AccountPool):
def load_accounts(self, force=False):
return len(self._accounts)
_fb_pool = FreebuffAccountPool("freebuff")
_cb_pool = CodebuffAccountPool("codebuff")
_google_antigravity_pool = GoogleAccountPool("antigravity")
_google_cli_pool = GoogleAccountPool("cli")
def _get_freebuff_account():
"""Return (token, account_dict) for best available freebuff account."""
_fb_pool.load_accounts()
acct = _fb_pool.get()
def _get_codebuff_account():
"""Return (token, account_dict) for best available codebuff account."""
_cb_pool.load_accounts()
acct = _cb_pool.get()
if not acct:
return "", None
return acct["token"], acct
@@ -764,7 +764,7 @@ def _init_runtime():
REASONING_EFFORT = CONFIG.get("reasoning_effort", "medium")
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 ("freebuff",):
if API_KEY and "," in API_KEY and not OAUTH_PROVIDER.startswith("google") and BACKEND not in ("codebuff",):
_api_key_pool = APIKeyPool(BACKEND, API_KEY)
print(f"[multi-account] API key pool: {len(_api_key_pool._accounts)} keys for {BACKEND}", file=sys.stderr)
if OAUTH_PROVIDER == "google-antigravity":
@@ -1087,9 +1087,9 @@ def _fb_get_any_reasoning():
return _fb_reasoning_store[k]["reasoning"]
return ""
def _freebuff_hard_disable_reasoning(messages):
def _codebuff_hard_disable_reasoning(messages):
"""Strip all reasoning/thinking fields from every message.
FreeBuff rejects mixed reasoning_content histories.
Codebuff rejects mixed reasoning_content histories.
The final chat body must be clean before POST."""
for msg in messages:
if not isinstance(msg, dict):
@@ -1150,7 +1150,7 @@ def _ds_rebuild_tool_history(messages):
rebuilt.append(msg)
return rebuilt
def _fb_input_to_messages(input_data, instructions=""):
def _cb_input_to_messages(input_data, instructions=""):
msgs = []
tool_name_by_id = {}
pending_tool_calls = []
@@ -4037,9 +4037,9 @@ class Handler(http.server.BaseHTTPRequestHandler):
self.send_json(200, {"object": "list", "data": MODELS})
elif self.path in ("/v1/accounts", "/accounts"):
info = {"provider": BACKEND, "oauth_provider": OAUTH_PROVIDER}
if BACKEND == "freebuff":
info["accounts"] = _fb_pool.status()
info["total"] = len(_fb_pool._accounts)
if BACKEND == "codebuff":
info["accounts"] = _cb_pool.status()
info["total"] = len(_cb_pool._accounts)
elif OAUTH_PROVIDER and OAUTH_PROVIDER.startswith("google"):
pool = _google_antigravity_pool if OAUTH_PROVIDER == "google-antigravity" else _google_cli_pool
info["accounts"] = pool.status()
@@ -4149,8 +4149,8 @@ class Handler(http.server.BaseHTTPRequestHandler):
self._handle_anthropic(body, model, stream, tracker)
elif BACKEND == "command-code":
self._handle_command_code(body, model, stream, tracker)
elif BACKEND == "freebuff":
self._handle_freebuff(body, model, stream, tracker)
elif BACKEND == "codebuff":
self._handle_codebuff(body, model, stream, tracker)
elif (BACKEND or "").startswith("gemini-oauth"):
self._handle_gemini_oauth(body, model, stream, tracker)
else:
@@ -5213,69 +5213,69 @@ class Handler(http.server.BaseHTTPRequestHandler):
if rid:
store_response(rid, body.get("input", ""), result.get("output", []))
def _handle_freebuff(self, body, model, stream, tracker=None):
agent_id = _FREEBUFF_AGENT_MAP.get(model)
def _handle_codebuff(self, body, model, stream, tracker=None):
agent_id = _CODEBUFF_AGENT_MAP.get(model)
if not agent_id:
matched = None
for m in _FREEBUFF_AGENT_MAP:
for m in _CODEBUFF_AGENT_MAP:
if model.lower().replace("/", "").replace("-", "") in m.lower().replace("/", "").replace("-", ""):
matched = m
break
if matched:
agent_id = _FREEBUFF_AGENT_MAP[matched]
agent_id = _CODEBUFF_AGENT_MAP[matched]
model = matched
else:
fallback_model = "deepseek/deepseek-v4-flash"
agent_id = _FREEBUFF_AGENT_MAP.get(fallback_model, "base2-free-deepseek-flash")
print(f"[freebuff] unknown model '{model}', falling back to {fallback_model}", file=sys.stderr)
agent_id = _CODEBUFF_AGENT_MAP.get(fallback_model, "base2-free-deepseek-flash")
print(f"[codebuff] unknown model '{model}', falling back to {fallback_model}", file=sys.stderr)
model = fallback_model
_fb_pool.load_accounts()
pool_status = _fb_pool.status()
_cb_pool.load_accounts()
pool_status = _cb_pool.status()
n_accounts = len(pool_status)
if n_accounts == 0:
return self.send_json(401, {"error": {"type": "auth_error",
"message": "No freebuff credentials found. Add accounts to ~/.config/manicode/credentials.json"}})
"message": "No codebuff credentials found. Add accounts to ~/.config/manicode/credentials.json"}})
last_err = None
for attempt in range(n_accounts):
token, acct = _get_freebuff_account()
token, acct = _get_codebuff_account()
if not token:
return self.send_json(401, {"error": {"type": "auth_error",
"message": "No freebuff credentials found. All accounts exhausted."}})
"message": "No codebuff credentials found. All accounts exhausted."}})
acct_id = acct.get("id", "?") if acct else "?"
if attempt > 0:
print(f"[freebuff] rotation attempt {attempt+1}/{n_accounts}, trying account {acct_id}", file=sys.stderr)
print(f"[codebuff] rotation attempt {attempt+1}/{n_accounts}, trying account {acct_id}", file=sys.stderr)
run_id, run_err = _freebuff_start_run(token, agent_id)
run_id, run_err = _codebuff_start_run(token, agent_id)
if not run_id:
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]}")
_cb_pool.mark_rate_limited(acct, retry_s)
last_err = ("rate_limit_error", run_err[1], f"Account {acct_id} rate-limited by Codebuff: {run_err[3]}")
else:
_fb_pool.mark_rate_limited(acct, 60)
_cb_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
try:
instance_id = _freebuff_get_session(token, model)
instance_id = _codebuff_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")
print(f"[codebuff] session 429 for {acct_id}, retry after {retry_s:.0f}s", file=sys.stderr)
_cb_pool.mark_rate_limited(acct, retry_s)
_codebuff_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()
messages = _fb_input_to_messages(input_data, instructions)
messages = _cb_input_to_messages(input_data, instructions)
messages = _ds_rebuild_tool_history(messages)
metadata = {
@@ -5301,7 +5301,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"
target = f"{_CODEBUFF_API_URL}/api/v1/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {token}",
@@ -5311,7 +5311,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
if instance_id:
headers["x-freebuff-instance-id"] = instance_id
print(f"[{self._session_id}] [freebuff] POST {target} model={model} stream={stream} run={run_id} acct={acct_id}", file=sys.stderr)
print(f"[{self._session_id}] [codebuff] POST {target} model={model} stream={stream} run={run_id} acct={acct_id}", file=sys.stderr)
chat_body_b = json.dumps(chat_body).encode()
try:
@@ -5319,7 +5319,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
upstream = urllib.request.urlopen(req, timeout=_upstream_timeout(body, stream))
except urllib.error.HTTPError as e:
err_body = e.read().decode()[:1000]
_freebuff_finish_run(token, run_id, "failed")
_codebuff_finish_run(token, run_id, "failed")
if e.code in (429, 426):
reset_ms = 0
fb_msg = ""
@@ -5336,18 +5336,18 @@ class Handler(http.server.BaseHTTPRequestHandler):
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)
_cb_pool.mark_rate_limited(acct, duration)
last_err = ("rate_limit_error", e.code, user_msg)
print(f"[freebuff] account {acct_id} got HTTP {e.code}, rotating", file=sys.stderr)
print(f"[codebuff] account {acct_id} got HTTP {e.code}, rotating", file=sys.stderr)
continue
if _is_reasoning_content_error(err_body):
print(f"[freebuff] reasoning_content error, retrying with thinking disabled", file=sys.stderr)
result = self._fb_retry_thinking_disabled(body, model, token, agent_id, stream, tracker, input_data, instructions, err_body, acct)
print(f"[codebuff] reasoning_content error, retrying with thinking disabled", file=sys.stderr)
result = self._cb_retry_thinking_disabled(body, model, token, agent_id, stream, tracker, input_data, instructions, err_body, acct)
return result
print(f"[freebuff] HTTP {e.code}: {err_body[:300]}", file=sys.stderr)
print(f"[codebuff] HTTP {e.code}: {err_body[:300]}", file=sys.stderr)
return self.send_json(e.code, {"error": {"type": "upstream_error", "message": _sanitize_err_body(err_body)}})
except Exception as e:
_freebuff_finish_run(token, run_id, "failed")
_codebuff_finish_run(token, run_id, "failed")
return self.send_json(502, {"error": {"type": "proxy_error", "message": str(e)}})
t0 = time.time()
@@ -5392,11 +5392,11 @@ class Handler(http.server.BaseHTTPRequestHandler):
_reasoning_out=reasoning_out),
on_event=_on_fb_event)
except (ConnectionResetError, BrokenPipeError, ConnectionAbortedError):
print(f"[{self._session_id}] [freebuff] client disconnected", file=sys.stderr)
print(f"[{self._session_id}] [codebuff] client disconnected", file=sys.stderr)
return
success = finish_reason[0] != "length"
_record_usage("freebuff", model, success, time.time() - t0)
_record_usage("codebuff", model, success, time.time() - t0)
if last_resp_id[0] and input_data is not None:
store_response(last_resp_id[0], input_data, last_output[0])
if last_resp_id[0] and reasoning_out.get("text") or reasoning_out.get("tool_calls"):
@@ -5406,7 +5406,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
if reasoning_out.get("text"):
asm["reasoning_content"] = reasoning_out["text"]
_ds_store_assistant(last_resp_id[0], asm)
print(f"[{self._session_id}] [freebuff] stream done status={last_status[0]} in {time.time()-t0:.1f}s acct={acct_id}", file=sys.stderr)
print(f"[{self._session_id}] [codebuff] stream done status={last_status[0]} in {time.time()-t0:.1f}s acct={acct_id}", file=sys.stderr)
else:
raw = upstream.read().decode()
chat_resp = json.loads(raw)
@@ -5415,9 +5415,9 @@ class Handler(http.server.BaseHTTPRequestHandler):
rid = result.get("id")
if rid:
store_response(rid, input_data, result.get("output", []))
print(f"[{self._session_id}] [freebuff] non-stream done in {time.time()-t0:.1f}s acct={acct_id}", file=sys.stderr)
print(f"[{self._session_id}] [codebuff] non-stream done in {time.time()-t0:.1f}s acct={acct_id}", file=sys.stderr)
finally:
_freebuff_finish_run(token, run_id, "completed")
_codebuff_finish_run(token, run_id, "completed")
return
if last_err:
@@ -5447,15 +5447,15 @@ class Handler(http.server.BaseHTTPRequestHandler):
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, run_err = _freebuff_start_run(token, agent_id)
def _cb_retry_thinking_disabled(self, body, model, token, agent_id, stream, tracker, input_data, instructions, original_error, acct=None):
run_id, run_err = _codebuff_start_run(token, agent_id)
if not run_id:
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)
instance_id = _codebuff_get_session(token, model)
messages = _cb_input_to_messages(input_data, instructions)
_codebuff_hard_disable_reasoning(messages)
metadata = {"run_id": run_id, "cost_mode": "free"}
if instance_id:
metadata["freebuff_instance_id"] = instance_id
@@ -5473,22 +5473,22 @@ class Handler(http.server.BaseHTTPRequestHandler):
chat_body["tools"] = tools
if body.get("tool_choice"):
chat_body["tool_choice"] = body["tool_choice"]
target = f"{_FREEBUFF_API_URL}/api/v1/chat/completions"
target = f"{_CODEBUFF_API_URL}/api/v1/chat/completions"
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)
print(f"[codebuff] retry POST {target} model={model} stream={stream} run={run_id} (thinking disabled via DeepSeek native)", file=sys.stderr)
try:
req = urllib.request.Request(target, data=json.dumps(chat_body).encode(), headers=headers)
upstream = urllib.request.urlopen(req, timeout=_upstream_timeout(body, stream))
except urllib.error.HTTPError as e:
err_body = e.read().decode()[:500]
_freebuff_finish_run(token, run_id, "failed")
print(f"[freebuff] thinking-disabled retry failed: HTTP {e.code}: {err_body[:300]}", file=sys.stderr)
return self.send_json(e.code, {"error": {"type": "freebuff_deepseek_thinking_error",
"message": "FreeBuff/DeepSeek V4 requires reasoning_content round-trip for tool-call sessions. Use Command Code provider for this model instead.", "upstream_error": _sanitize_err_body(err_body)}})
_codebuff_finish_run(token, run_id, "failed")
print(f"[codebuff] thinking-disabled retry failed: HTTP {e.code}: {err_body[:300]}", file=sys.stderr)
return self.send_json(e.code, {"error": {"type": "codebuff_deepseek_thinking_error",
"message": "Codebuff/DeepSeek V4 requires reasoning_content round-trip for tool-call sessions. Use Command Code provider for this model instead.", "upstream_error": _sanitize_err_body(err_body)}})
except Exception as e:
_freebuff_finish_run(token, run_id, "failed")
_codebuff_finish_run(token, run_id, "failed")
return self.send_json(502, {"error": {"type": "proxy_error", "message": str(e)}})
t0 = time.time()
try:
@@ -5531,7 +5531,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
except (ConnectionResetError, BrokenPipeError, ConnectionAbortedError):
return
success = finish_reason[0] != "length"
_record_usage("freebuff", model, success, time.time() - t0)
_record_usage("codebuff", model, success, time.time() - t0)
if last_resp_id[0] and input_data is not None:
store_response(last_resp_id[0], input_data, last_output[0])
if last_resp_id[0] and reasoning_out.get("text") or reasoning_out.get("tool_calls"):
@@ -5541,7 +5541,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
if reasoning_out.get("text"):
asm["reasoning_content"] = reasoning_out["text"]
_ds_store_assistant(last_resp_id[0], asm)
print(f"[{self._session_id}] [freebuff] retry stream done status={last_status[0]} in {time.time()-t0:.1f}s", file=sys.stderr)
print(f"[{self._session_id}] [codebuff] retry stream done status={last_status[0]} in {time.time()-t0:.1f}s", file=sys.stderr)
else:
raw = upstream.read().decode()
chat_resp = json.loads(raw)
@@ -5550,9 +5550,9 @@ class Handler(http.server.BaseHTTPRequestHandler):
rid = result.get("id")
if rid:
store_response(rid, input_data, result.get("output", []))
print(f"[{self._session_id}] [freebuff] retry non-stream done in {time.time()-t0:.1f}s", file=sys.stderr)
print(f"[{self._session_id}] [codebuff] retry non-stream done in {time.time()-t0:.1f}s", file=sys.stderr)
finally:
_freebuff_finish_run(token, run_id, "completed")
_codebuff_finish_run(token, run_id, "completed")
def _handle_auto(self, body, model, stream, tracker=None):
"""Auto-sensing backend: probe schema, adapt, retry on errors.
@@ -5896,10 +5896,10 @@ def main():
print(f"translate-proxy ({BACKEND}) listening on http://127.0.0.1:{PORT}", flush=True)
print(f"Target: {TARGET_URL}", flush=True)
print(f"Models: {[m['id'] for m in MODELS]}", flush=True)
if BACKEND == "freebuff":
_fb_pool.load_accounts(force=True)
fb_status = _fb_pool.status()
print(f"[multi-account] freebuff: {len(fb_status)} accounts loaded {[a['id'] for a in fb_status]}", flush=True)
if BACKEND == "codebuff":
_cb_pool.load_accounts(force=True)
fb_status = _cb_pool.status()
print(f"[multi-account] codebuff: {len(fb_status)} accounts loaded {[a['id'] for a in fb_status]}", flush=True)
if OAUTH_PROVIDER and OAUTH_PROVIDER.startswith("google"):
pool = _google_antigravity_pool if OAUTH_PROVIDER == "google-antigravity" else _google_cli_pool
pool.load_accounts(force=True)