v3.9.7 — Rename all freebuff references to codebuff in code and docs
This commit is contained in:
Binary file not shown.
@@ -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>')
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user