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 = [
|
CHANGELOG = [
|
||||||
("3.9.7", "2026-05-25", [
|
("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",
|
"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",
|
"RateLimitError carries upstream message through session + chat error paths",
|
||||||
"BrokenPipeError crash fix on 'all accounts exhausted' response",
|
"BrokenPipeError crash fix on 'all accounts exhausted' response",
|
||||||
"Fix 3 SyntaxWarnings for invalid escape sequences in docstrings",
|
"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", [
|
("3.9.6", "2026-05-25", [
|
||||||
"Fix Gemini follow-up turns returning text-only instead of tool calls",
|
"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",
|
"Smart tool output compaction: old=3000, recent=20000 chars",
|
||||||
"Follow-through guardrail system instruction for autonomous agent behavior",
|
"Follow-through guardrail system instruction for autonomous agent behavior",
|
||||||
"Stream hang fix for function-call-only responses",
|
"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",
|
"/v1/accounts endpoint for account pool status",
|
||||||
]),
|
]),
|
||||||
("3.9.0", "2026-05-24", [
|
("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",
|
"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)",
|
"Google OAuth: supports multiple token files (google-*-oauth-token-N.json)",
|
||||||
"API keys: comma-separated keys rotate on 429 errors",
|
"API keys: comma-separated keys rotate on 429 errors",
|
||||||
"New /v1/accounts endpoint shows account pool status",
|
"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", [
|
("3.8.4", "2026-05-24", [
|
||||||
"FIXED: Freebuff streaming — SSE events now reach Codex client",
|
"FIXED: Codebuff streaming — SSE events now reach Codex client",
|
||||||
"Root cause: stream_buffered_events was never called for freebuff",
|
"Root cause: stream_buffered_events was never called for codebuff",
|
||||||
"Freebuff stream uses buffered flushing (30ms / 4KB / urgent)",
|
"Codebuff stream uses buffered flushing (30ms / 4KB / urgent)",
|
||||||
"Freebuff OAuth — built-in login flow (no external CLI needed)",
|
"Codebuff OAuth — built-in login flow (no external CLI needed)",
|
||||||
"Freebuff API: reverse-engineered www.codebuff.com endpoints",
|
"Codebuff API: reverse-engineered www.codebuff.com endpoints",
|
||||||
"Freebuff session management with instance ID (waiting room)",
|
"Codebuff session management with instance ID (waiting room)",
|
||||||
"Freebuff agent run lifecycle (start/finish) with model routing",
|
"Codebuff agent run lifecycle (start/finish) with model routing",
|
||||||
"Free DeepSeek V4 Pro, V4 Flash, Kimi K2.6, MiniMax M2.7",
|
"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: Sandbox mode selector (Read-only / Workspace / Full Access)",
|
||||||
"GUI: Approval mode selector (Untrusted / On Request / Full Auto)",
|
"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 _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", [
|
("3.8.1", "2026-05-24", [
|
||||||
"Freebuff integration — free DeepSeek V4 Pro, V4 Flash, Kimi K2.6, MiniMax M2.7",
|
"Codebuff integration — free DeepSeek V4 Pro, V4 Flash, Kimi K2.6, MiniMax M2.7",
|
||||||
"Freebuff backend: auto agent-run lifecycle, credential detection, model routing",
|
"Codebuff backend: auto agent-run lifecycle, credential detection, model routing",
|
||||||
"Restored all provider presets (Command Code, Crof, OpenAdapter, OpenRouter, etc.)",
|
"Restored all provider presets (Command Code, Crof, OpenAdapter, OpenRouter, etc.)",
|
||||||
"AI Monitoring — self-healing watchdog with 3-tier response system",
|
"AI Monitoring — self-healing watchdog with 3-tier response system",
|
||||||
"HealthWatcher: monitors proxy health every 5s, auto-restarts on crash",
|
"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",
|
"GLM-4-Flash", "GLM-4-FlashX", "GLM-Z1-Flash",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"Freebuff (Free DeepSeek/Kimi)": {
|
"Codebuff (Free DeepSeek/Kimi)": {
|
||||||
"backend_type": "freebuff",
|
"backend_type": "freebuff",
|
||||||
"base_url": "https://freebuff.com",
|
"base_url": "https://freebuff.com",
|
||||||
"oauth_provider": "freebuff",
|
"oauth_provider": "freebuff",
|
||||||
@@ -386,7 +386,7 @@ def label_for_backend(backend_type):
|
|||||||
"openai-compat": "OpenAI-compatible",
|
"openai-compat": "OpenAI-compatible",
|
||||||
"anthropic": "Anthropic",
|
"anthropic": "Anthropic",
|
||||||
"command-code": "Command Code",
|
"command-code": "Command Code",
|
||||||
"freebuff": "Freebuff (Free AI)",
|
"freebuff": "Codebuff (Free AI)",
|
||||||
"native": "Native",
|
"native": "Native",
|
||||||
}.get(backend_type, backend_type)
|
}.get(backend_type, backend_type)
|
||||||
|
|
||||||
@@ -3010,7 +3010,7 @@ class EditEndpointDialog(Gtk.Dialog):
|
|||||||
for val, lab in [("openai-compat", "OpenAI-compatible (needs proxy)"),
|
for val, lab in [("openai-compat", "OpenAI-compatible (needs proxy)"),
|
||||||
("anthropic", "Anthropic (needs proxy)"),
|
("anthropic", "Anthropic (needs proxy)"),
|
||||||
("command-code", "Command Code (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-cli", "Gemini CLI OAuth (needs proxy)"),
|
||||||
("gemini-oauth-antigravity", "Antigravity OAuth (needs proxy)"),
|
("gemini-oauth-antigravity", "Antigravity OAuth (needs proxy)"),
|
||||||
("native", "Native OpenAI (no proxy)")]:
|
("native", "Native OpenAI (no proxy)")]:
|
||||||
@@ -3176,8 +3176,8 @@ class EditEndpointDialog(Gtk.Dialog):
|
|||||||
is_oauth = bool(oauth_provider)
|
is_oauth = bool(oauth_provider)
|
||||||
self._oauth_btn.set_visible(is_oauth)
|
self._oauth_btn.set_visible(is_oauth)
|
||||||
if oauth_provider == "freebuff":
|
if oauth_provider == "freebuff":
|
||||||
self._oauth_btn.set_label("Freebuff Login")
|
self._oauth_btn.set_label("Codebuff Login")
|
||||||
self._entry_key.set_placeholder_text("Auto-filled by freebuff login")
|
self._entry_key.set_placeholder_text("Auto-filled by codebuff login")
|
||||||
elif is_oauth:
|
elif is_oauth:
|
||||||
self._oauth_btn.set_label("OAuth Login")
|
self._oauth_btn.set_label("OAuth Login")
|
||||||
self._entry_key.set_placeholder_text("Auto-filled by OAuth")
|
self._entry_key.set_placeholder_text("Auto-filled by OAuth")
|
||||||
@@ -3489,7 +3489,7 @@ class EditEndpointDialog(Gtk.Dialog):
|
|||||||
dlg.run()
|
dlg.run()
|
||||||
|
|
||||||
def _freebuff_oauth_flow(self):
|
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.add_button("Cancel", Gtk.ResponseType.CANCEL)
|
||||||
dlg.set_default_size(500, 240)
|
dlg.set_default_size(500, 240)
|
||||||
area = dlg.get_content_area()
|
area = dlg.get_content_area()
|
||||||
@@ -3499,7 +3499,7 @@ class EditEndpointDialog(Gtk.Dialog):
|
|||||||
area.set_margin_bottom(12)
|
area.set_margin_bottom(12)
|
||||||
area.set_spacing(8)
|
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 = Gtk.Label(label="Requesting login URL…", xalign=0)
|
||||||
self._oauth_status.set_line_wrap(True)
|
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}
|
self._fb_oauth_result = {"success": False, "user": None, "error": None}
|
||||||
|
|
||||||
def _freebuff_auth_thread():
|
def _codebuff_auth_thread():
|
||||||
try:
|
try:
|
||||||
fingerprint_id = str(uuid.uuid4())
|
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()
|
body = json.dumps({"fingerprintId": fingerprint_id}).encode()
|
||||||
req = urllib.request.Request(auth_url, data=body,
|
req = urllib.request.Request(auth_url, data=body,
|
||||||
headers={"Content-Type": "application/json", "User-Agent": "codex-launcher/3.9.7"})
|
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)
|
expires_at = data.get("expiresAt", 0) or data.get("expires_at", 0)
|
||||||
if not login_url:
|
if not login_url:
|
||||||
self._fb_oauth_result["error"] = "Server returned no 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
|
return
|
||||||
|
|
||||||
def _set_link():
|
def _set_link():
|
||||||
@@ -3545,7 +3545,7 @@ class EditEndpointDialog(Gtk.Dialog):
|
|||||||
|
|
||||||
webbrowser.open(login_url)
|
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
|
deadline = time.time() + 300
|
||||||
while time.time() < deadline:
|
while time.time() < deadline:
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
@@ -3558,23 +3558,23 @@ class EditEndpointDialog(Gtk.Dialog):
|
|||||||
if user and user.get("authToken"):
|
if user and user.get("authToken"):
|
||||||
self._fb_oauth_result["success"] = True
|
self._fb_oauth_result["success"] = True
|
||||||
self._fb_oauth_result["user"] = user
|
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
|
return
|
||||||
except urllib.error.HTTPError:
|
except urllib.error.HTTPError:
|
||||||
pass
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
self._fb_oauth_result["error"] = "Login timed out after 5 minutes."
|
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:
|
except Exception as e:
|
||||||
self._fb_oauth_result["error"] = str(e)[:200]
|
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.connect("response", lambda d, r: d.destroy())
|
||||||
dlg.run()
|
dlg.run()
|
||||||
|
|
||||||
def _freebuff_oauth_done(self, dlg, spinner):
|
def _codebuff_oauth_done(self, dlg, spinner):
|
||||||
spinner.stop()
|
spinner.stop()
|
||||||
if self._fb_oauth_result["success"] and self._fb_oauth_result["user"]:
|
if self._fb_oauth_result["success"] and self._fb_oauth_result["user"]:
|
||||||
user = self._fb_oauth_result["user"]
|
user = self._fb_oauth_result["user"]
|
||||||
@@ -3593,7 +3593,7 @@ class EditEndpointDialog(Gtk.Dialog):
|
|||||||
os.chmod(creds_path, 0o600)
|
os.chmod(creds_path, 0o600)
|
||||||
self._entry_key.set_text(user.get("authToken", ""))
|
self._entry_key.set_text(user.get("authToken", ""))
|
||||||
self._oauth_status.set_markup('<span foreground="#27ae60" weight="bold">Authorization successful! Credentials saved.</span>')
|
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))
|
GLib.timeout_add(1500, lambda: dlg.response(Gtk.ResponseType.OK))
|
||||||
else:
|
else:
|
||||||
self._oauth_status.set_markup(f'<span foreground="#e74c3c">{self._fb_oauth_result["error"] or "Login failed."}</span>')
|
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": [
|
"anthropic": [
|
||||||
{"id": "claude-sonnet-4-20250514", "object": "model", "created": 1700000000, "owned_by": "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-pro", "object": "model", "created": 1700000000, "owned_by": "freebuff"},
|
||||||
{"id": "deepseek/deepseek-v4-flash", "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"},
|
{"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 = argparse.ArgumentParser(description="Responses API translation proxy")
|
||||||
p.add_argument("--config", help="JSON config file path")
|
p.add_argument("--config", help="JSON config file path")
|
||||||
p.add_argument("--port", type=int, default=None)
|
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("--target-url", default=None)
|
||||||
p.add_argument("--api-key", default=None)
|
p.add_argument("--api-key", default=None)
|
||||||
p.add_argument("--models-file", default=None, help="JSON file with model list array")
|
p.add_argument("--models-file", default=None, help="JSON file with model list array")
|
||||||
@@ -294,43 +294,43 @@ _conn_pool = {}
|
|||||||
|
|
||||||
_STREAM_IDLE_TIMEOUT = 300
|
_STREAM_IDLE_TIMEOUT = 300
|
||||||
|
|
||||||
_FREEBUFF_AUTH_URL = "https://freebuff.com"
|
_CODEBUFF_AUTH_URL = "https://freebuff.com"
|
||||||
_FREEBUFF_API_URL = "https://www.codebuff.com"
|
_CODEBUFF_API_URL = "https://www.codebuff.com"
|
||||||
_FREEBUFF_AGENT_MAP = {
|
_CODEBUFF_AGENT_MAP = {
|
||||||
"deepseek/deepseek-v4-pro": "base2-free-deepseek",
|
"deepseek/deepseek-v4-pro": "base2-free-deepseek",
|
||||||
"deepseek/deepseek-v4-flash": "base2-free-deepseek-flash",
|
"deepseek/deepseek-v4-flash": "base2-free-deepseek-flash",
|
||||||
"moonshotai/kimi-k2.6": "base2-free-kimi",
|
"moonshotai/kimi-k2.6": "base2-free-kimi",
|
||||||
"minimax/minimax-m2.7": "base2-free",
|
"minimax/minimax-m2.7": "base2-free",
|
||||||
}
|
}
|
||||||
_FREEBUFF_CREDS_PATH = os.path.join(os.path.expanduser("~"), ".config", "manicode", "credentials.json")
|
_CODEBUFF_CREDS_PATH = os.path.join(os.path.expanduser("~"), ".config", "manicode", "credentials.json")
|
||||||
_freebuff_token_cache = {"token": None, "checked": 0}
|
_codebuff_token_cache = {"token": None, "checked": 0}
|
||||||
_freebuff_session_cache = {"instance_id": None, "expires": 0, "model": None}
|
_codebuff_session_cache = {"instance_id": None, "expires": 0, "model": None}
|
||||||
_freebuff_token_lock = threading.Lock()
|
_codebuff_token_lock = threading.Lock()
|
||||||
|
|
||||||
def _get_freebuff_token():
|
def _get_codebuff_token():
|
||||||
with _freebuff_token_lock:
|
with _codebuff_token_lock:
|
||||||
if _freebuff_token_cache["token"] and _freebuff_token_cache["checked"] > time.time() - 300:
|
if _codebuff_token_cache["token"] and _codebuff_token_cache["checked"] > time.time() - 300:
|
||||||
return _freebuff_token_cache["token"]
|
return _codebuff_token_cache["token"]
|
||||||
try:
|
try:
|
||||||
with open(_FREEBUFF_CREDS_PATH) as f:
|
with open(_CODEBUFF_CREDS_PATH) as f:
|
||||||
creds = json.load(f)
|
creds = json.load(f)
|
||||||
default_account = creds.get("default", {})
|
default_account = creds.get("default", {})
|
||||||
token = default_account.get("authToken") or creds.get("apiKey") or ""
|
token = default_account.get("authToken") or creds.get("apiKey") or ""
|
||||||
with _freebuff_token_lock:
|
with _codebuff_token_lock:
|
||||||
_freebuff_token_cache["token"] = token
|
_codebuff_token_cache["token"] = token
|
||||||
_freebuff_token_cache["checked"] = time.time()
|
_codebuff_token_cache["checked"] = time.time()
|
||||||
return token
|
return token
|
||||||
except Exception as e:
|
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 ""
|
return ""
|
||||||
|
|
||||||
def _freebuff_get_session(token, model):
|
def _codebuff_get_session(token, model):
|
||||||
with _freebuff_token_lock:
|
with _codebuff_token_lock:
|
||||||
sc = _freebuff_session_cache
|
sc = _codebuff_session_cache
|
||||||
if sc["instance_id"] and sc["expires"] > time.time() + 60 and sc["model"] == model:
|
if sc["instance_id"] and sc["expires"] > time.time() + 60 and sc["model"] == model:
|
||||||
return sc["instance_id"]
|
return sc["instance_id"]
|
||||||
try:
|
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()
|
body = json.dumps({"model": model}).encode()
|
||||||
req = urllib.request.Request(url, data=body, headers={
|
req = urllib.request.Request(url, data=body, headers={
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -358,27 +358,27 @@ def _freebuff_get_session(token, model):
|
|||||||
if not user_msg:
|
if not user_msg:
|
||||||
user_msg = _sanitize_err_body(err_body)
|
user_msg = _sanitize_err_body(err_body)
|
||||||
raise RateLimitError(retry_s, user_msg)
|
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
|
return None
|
||||||
data = json.loads(resp.read())
|
data = json.loads(resp.read())
|
||||||
instance_id = data.get("instanceId", data.get("data", {}).get("instance_id", ""))
|
instance_id = data.get("instanceId", data.get("data", {}).get("instance_id", ""))
|
||||||
expires_at = data.get("remainingMs", 0)
|
expires_at = data.get("remainingMs", 0)
|
||||||
if instance_id:
|
if instance_id:
|
||||||
with _freebuff_token_lock:
|
with _codebuff_token_lock:
|
||||||
_freebuff_session_cache["instance_id"] = instance_id
|
_codebuff_session_cache["instance_id"] = instance_id
|
||||||
_freebuff_session_cache["expires"] = time.time() + min(expires_at / 1000, 3600)
|
_codebuff_session_cache["expires"] = time.time() + min(expires_at / 1000, 3600)
|
||||||
_freebuff_session_cache["model"] = model
|
_codebuff_session_cache["model"] = model
|
||||||
print(f"[freebuff] session active, instance={instance_id[:8]}...", file=sys.stderr)
|
print(f"[codebuff] session active, instance={instance_id[:8]}...", file=sys.stderr)
|
||||||
return instance_id
|
return instance_id
|
||||||
return None
|
return None
|
||||||
except RateLimitError:
|
except RateLimitError:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[freebuff] session failed: {e}", file=sys.stderr)
|
print(f"[codebuff] session failed: {e}", file=sys.stderr)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _freebuff_start_run(token, agent_id):
|
def _codebuff_start_run(token, agent_id):
|
||||||
url = f"{_FREEBUFF_API_URL}/api/v1/agent-runs"
|
url = f"{_CODEBUFF_API_URL}/api/v1/agent-runs"
|
||||||
body = json.dumps({"action": "START", "agentId": agent_id, "ancestorRunIds": []}).encode()
|
body = json.dumps({"action": "START", "agentId": agent_id, "ancestorRunIds": []}).encode()
|
||||||
req = urllib.request.Request(url, data=body, headers={
|
req = urllib.request.Request(url, data=body, headers={
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -389,11 +389,11 @@ def _freebuff_start_run(token, agent_id):
|
|||||||
resp = urllib.request.urlopen(req, timeout=15)
|
resp = urllib.request.urlopen(req, timeout=15)
|
||||||
data = json.loads(resp.read())
|
data = json.loads(resp.read())
|
||||||
run_id = data.get("runId")
|
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
|
return run_id, None
|
||||||
except urllib.error.HTTPError as e:
|
except urllib.error.HTTPError as e:
|
||||||
err = e.read().decode()[:500]
|
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:
|
if e.code == 429:
|
||||||
retry_s = 120
|
retry_s = 120
|
||||||
try:
|
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, ("rate_limit_error", 429, retry_s, _sanitize_err_body(err))
|
||||||
return None, ("upstream_error", e.code, 0, _sanitize_err_body(err))
|
return None, ("upstream_error", e.code, 0, _sanitize_err_body(err))
|
||||||
except Exception as e:
|
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))
|
return None, ("proxy_error", 502, 0, str(e))
|
||||||
|
|
||||||
def _freebuff_finish_run(token, run_id, status="completed"):
|
def _codebuff_finish_run(token, run_id, status="completed"):
|
||||||
url = f"{_FREEBUFF_API_URL}/api/v1/agent-runs"
|
url = f"{_CODEBUFF_API_URL}/api/v1/agent-runs"
|
||||||
body = json.dumps({"action": "FINISH", "runId": run_id, "status": status,
|
body = json.dumps({"action": "FINISH", "runId": run_id, "status": status,
|
||||||
"totalSteps": 1, "directCredits": 0, "totalCredits": 0}).encode()
|
"totalSteps": 1, "directCredits": 0, "totalCredits": 0}).encode()
|
||||||
req = urllib.request.Request(url, data=body, headers={
|
req = urllib.request.Request(url, data=body, headers={
|
||||||
@@ -421,7 +421,7 @@ def _freebuff_finish_run(token, run_id, status="completed"):
|
|||||||
try:
|
try:
|
||||||
urllib.request.urlopen(req, timeout=10)
|
urllib.request.urlopen(req, timeout=10)
|
||||||
except Exception as e:
|
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
|
# Multi-account rotation system
|
||||||
@@ -514,12 +514,12 @@ class AccountPool:
|
|||||||
result.append(info)
|
result.append(info)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
class FreebuffAccountPool(AccountPool):
|
class CodebuffAccountPool(AccountPool):
|
||||||
def _do_load(self):
|
def _do_load(self):
|
||||||
if not os.path.exists(_FREEBUFF_CREDS_PATH):
|
if not os.path.exists(_CODEBUFF_CREDS_PATH):
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
with open(_FREEBUFF_CREDS_PATH) as f:
|
with open(_CODEBUFF_CREDS_PATH) as f:
|
||||||
creds = json.load(f)
|
creds = json.load(f)
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
@@ -588,14 +588,14 @@ class APIKeyPool(AccountPool):
|
|||||||
def load_accounts(self, force=False):
|
def load_accounts(self, force=False):
|
||||||
return len(self._accounts)
|
return len(self._accounts)
|
||||||
|
|
||||||
_fb_pool = FreebuffAccountPool("freebuff")
|
_cb_pool = CodebuffAccountPool("codebuff")
|
||||||
_google_antigravity_pool = GoogleAccountPool("antigravity")
|
_google_antigravity_pool = GoogleAccountPool("antigravity")
|
||||||
_google_cli_pool = GoogleAccountPool("cli")
|
_google_cli_pool = GoogleAccountPool("cli")
|
||||||
|
|
||||||
def _get_freebuff_account():
|
def _get_codebuff_account():
|
||||||
"""Return (token, account_dict) for best available freebuff account."""
|
"""Return (token, account_dict) for best available codebuff account."""
|
||||||
_fb_pool.load_accounts()
|
_cb_pool.load_accounts()
|
||||||
acct = _fb_pool.get()
|
acct = _cb_pool.get()
|
||||||
if not acct:
|
if not acct:
|
||||||
return "", None
|
return "", None
|
||||||
return acct["token"], acct
|
return acct["token"], acct
|
||||||
@@ -764,7 +764,7 @@ def _init_runtime():
|
|||||||
REASONING_EFFORT = CONFIG.get("reasoning_effort", "medium")
|
REASONING_EFFORT = CONFIG.get("reasoning_effort", "medium")
|
||||||
BGP_ROUTES = CONFIG.get("bgp_routes", [])
|
BGP_ROUTES = CONFIG.get("bgp_routes", [])
|
||||||
_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 ("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)
|
_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)
|
print(f"[multi-account] API key pool: {len(_api_key_pool._accounts)} keys for {BACKEND}", file=sys.stderr)
|
||||||
if OAUTH_PROVIDER == "google-antigravity":
|
if OAUTH_PROVIDER == "google-antigravity":
|
||||||
@@ -1087,9 +1087,9 @@ def _fb_get_any_reasoning():
|
|||||||
return _fb_reasoning_store[k]["reasoning"]
|
return _fb_reasoning_store[k]["reasoning"]
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def _freebuff_hard_disable_reasoning(messages):
|
def _codebuff_hard_disable_reasoning(messages):
|
||||||
"""Strip all reasoning/thinking fields from every message.
|
"""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."""
|
The final chat body must be clean before POST."""
|
||||||
for msg in messages:
|
for msg in messages:
|
||||||
if not isinstance(msg, dict):
|
if not isinstance(msg, dict):
|
||||||
@@ -1150,7 +1150,7 @@ def _ds_rebuild_tool_history(messages):
|
|||||||
rebuilt.append(msg)
|
rebuilt.append(msg)
|
||||||
return rebuilt
|
return rebuilt
|
||||||
|
|
||||||
def _fb_input_to_messages(input_data, instructions=""):
|
def _cb_input_to_messages(input_data, instructions=""):
|
||||||
msgs = []
|
msgs = []
|
||||||
tool_name_by_id = {}
|
tool_name_by_id = {}
|
||||||
pending_tool_calls = []
|
pending_tool_calls = []
|
||||||
@@ -4037,9 +4037,9 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
self.send_json(200, {"object": "list", "data": MODELS})
|
self.send_json(200, {"object": "list", "data": MODELS})
|
||||||
elif self.path in ("/v1/accounts", "/accounts"):
|
elif self.path in ("/v1/accounts", "/accounts"):
|
||||||
info = {"provider": BACKEND, "oauth_provider": OAUTH_PROVIDER}
|
info = {"provider": BACKEND, "oauth_provider": OAUTH_PROVIDER}
|
||||||
if BACKEND == "freebuff":
|
if BACKEND == "codebuff":
|
||||||
info["accounts"] = _fb_pool.status()
|
info["accounts"] = _cb_pool.status()
|
||||||
info["total"] = len(_fb_pool._accounts)
|
info["total"] = len(_cb_pool._accounts)
|
||||||
elif OAUTH_PROVIDER and OAUTH_PROVIDER.startswith("google"):
|
elif OAUTH_PROVIDER and OAUTH_PROVIDER.startswith("google"):
|
||||||
pool = _google_antigravity_pool if OAUTH_PROVIDER == "google-antigravity" else _google_cli_pool
|
pool = _google_antigravity_pool if OAUTH_PROVIDER == "google-antigravity" else _google_cli_pool
|
||||||
info["accounts"] = pool.status()
|
info["accounts"] = pool.status()
|
||||||
@@ -4149,8 +4149,8 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
self._handle_anthropic(body, model, stream, tracker)
|
self._handle_anthropic(body, model, stream, tracker)
|
||||||
elif BACKEND == "command-code":
|
elif BACKEND == "command-code":
|
||||||
self._handle_command_code(body, model, stream, tracker)
|
self._handle_command_code(body, model, stream, tracker)
|
||||||
elif BACKEND == "freebuff":
|
elif BACKEND == "codebuff":
|
||||||
self._handle_freebuff(body, model, stream, tracker)
|
self._handle_codebuff(body, model, stream, tracker)
|
||||||
elif (BACKEND or "").startswith("gemini-oauth"):
|
elif (BACKEND or "").startswith("gemini-oauth"):
|
||||||
self._handle_gemini_oauth(body, model, stream, tracker)
|
self._handle_gemini_oauth(body, model, stream, tracker)
|
||||||
else:
|
else:
|
||||||
@@ -5213,69 +5213,69 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
if rid:
|
if rid:
|
||||||
store_response(rid, body.get("input", ""), result.get("output", []))
|
store_response(rid, body.get("input", ""), result.get("output", []))
|
||||||
|
|
||||||
def _handle_freebuff(self, body, model, stream, tracker=None):
|
def _handle_codebuff(self, body, model, stream, tracker=None):
|
||||||
agent_id = _FREEBUFF_AGENT_MAP.get(model)
|
agent_id = _CODEBUFF_AGENT_MAP.get(model)
|
||||||
if not agent_id:
|
if not agent_id:
|
||||||
matched = None
|
matched = None
|
||||||
for m in _FREEBUFF_AGENT_MAP:
|
for m in _CODEBUFF_AGENT_MAP:
|
||||||
if model.lower().replace("/", "").replace("-", "") in m.lower().replace("/", "").replace("-", ""):
|
if model.lower().replace("/", "").replace("-", "") in m.lower().replace("/", "").replace("-", ""):
|
||||||
matched = m
|
matched = m
|
||||||
break
|
break
|
||||||
if matched:
|
if matched:
|
||||||
agent_id = _FREEBUFF_AGENT_MAP[matched]
|
agent_id = _CODEBUFF_AGENT_MAP[matched]
|
||||||
model = matched
|
model = matched
|
||||||
else:
|
else:
|
||||||
fallback_model = "deepseek/deepseek-v4-flash"
|
fallback_model = "deepseek/deepseek-v4-flash"
|
||||||
agent_id = _FREEBUFF_AGENT_MAP.get(fallback_model, "base2-free-deepseek-flash")
|
agent_id = _CODEBUFF_AGENT_MAP.get(fallback_model, "base2-free-deepseek-flash")
|
||||||
print(f"[freebuff] unknown model '{model}', falling back to {fallback_model}", file=sys.stderr)
|
print(f"[codebuff] unknown model '{model}', falling back to {fallback_model}", file=sys.stderr)
|
||||||
model = fallback_model
|
model = fallback_model
|
||||||
|
|
||||||
_fb_pool.load_accounts()
|
_cb_pool.load_accounts()
|
||||||
pool_status = _fb_pool.status()
|
pool_status = _cb_pool.status()
|
||||||
n_accounts = len(pool_status)
|
n_accounts = len(pool_status)
|
||||||
if n_accounts == 0:
|
if n_accounts == 0:
|
||||||
return self.send_json(401, {"error": {"type": "auth_error",
|
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
|
last_err = None
|
||||||
for attempt in range(n_accounts):
|
for attempt in range(n_accounts):
|
||||||
token, acct = _get_freebuff_account()
|
token, acct = _get_codebuff_account()
|
||||||
if not token:
|
if not token:
|
||||||
return self.send_json(401, {"error": {"type": "auth_error",
|
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 "?"
|
acct_id = acct.get("id", "?") if acct else "?"
|
||||||
if attempt > 0:
|
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 not run_id:
|
||||||
if run_err and run_err[0] == "rate_limit_error":
|
if run_err and run_err[0] == "rate_limit_error":
|
||||||
retry_s = run_err[2]
|
retry_s = run_err[2]
|
||||||
_fb_pool.mark_rate_limited(acct, retry_s)
|
_cb_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]}")
|
last_err = ("rate_limit_error", run_err[1], f"Account {acct_id} rate-limited by Codebuff: {run_err[3]}")
|
||||||
else:
|
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,
|
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'}")
|
f"Failed to start agent run for {acct_id}: {run_err[3] if run_err else 'unknown error'}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
instance_id = _freebuff_get_session(token, model)
|
instance_id = _codebuff_get_session(token, model)
|
||||||
except RateLimitError as rle:
|
except RateLimitError as rle:
|
||||||
retry_s = rle.retry_seconds
|
retry_s = rle.retry_seconds
|
||||||
fb_msg = rle.message
|
fb_msg = rle.message
|
||||||
mins = int(retry_s // 60)
|
mins = int(retry_s // 60)
|
||||||
user_msg = fb_msg if fb_msg else f"Daily session limit reached. Resets in {mins}m."
|
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)
|
print(f"[codebuff] session 429 for {acct_id}, retry after {retry_s:.0f}s", file=sys.stderr)
|
||||||
_fb_pool.mark_rate_limited(acct, retry_s)
|
_cb_pool.mark_rate_limited(acct, retry_s)
|
||||||
_freebuff_finish_run(token, run_id, "completed")
|
_codebuff_finish_run(token, run_id, "completed")
|
||||||
last_err = ("rate_limit_error", 429, user_msg)
|
last_err = ("rate_limit_error", 429, user_msg)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
input_data = body.get("input", "")
|
input_data = body.get("input", "")
|
||||||
instructions = body.get("instructions", "").strip()
|
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)
|
messages = _ds_rebuild_tool_history(messages)
|
||||||
|
|
||||||
metadata = {
|
metadata = {
|
||||||
@@ -5301,7 +5301,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
if body.get("tool_choice"):
|
if body.get("tool_choice"):
|
||||||
chat_body["tool_choice"] = body["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 = {
|
headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Authorization": f"Bearer {token}",
|
"Authorization": f"Bearer {token}",
|
||||||
@@ -5311,7 +5311,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
if instance_id:
|
if instance_id:
|
||||||
headers["x-freebuff-instance-id"] = 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()
|
chat_body_b = json.dumps(chat_body).encode()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -5319,7 +5319,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
upstream = urllib.request.urlopen(req, timeout=_upstream_timeout(body, stream))
|
upstream = urllib.request.urlopen(req, timeout=_upstream_timeout(body, stream))
|
||||||
except urllib.error.HTTPError as e:
|
except urllib.error.HTTPError as e:
|
||||||
err_body = e.read().decode()[:1000]
|
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):
|
if e.code in (429, 426):
|
||||||
reset_ms = 0
|
reset_ms = 0
|
||||||
fb_msg = ""
|
fb_msg = ""
|
||||||
@@ -5336,18 +5336,18 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
if not fb_msg:
|
if not fb_msg:
|
||||||
fb_msg = _sanitize_err_body(err_body)
|
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."
|
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)
|
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
|
continue
|
||||||
if _is_reasoning_content_error(err_body):
|
if _is_reasoning_content_error(err_body):
|
||||||
print(f"[freebuff] reasoning_content error, retrying with thinking disabled", file=sys.stderr)
|
print(f"[codebuff] 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)
|
result = self._cb_retry_thinking_disabled(body, model, token, agent_id, stream, tracker, input_data, instructions, err_body, acct)
|
||||||
return result
|
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)}})
|
return self.send_json(e.code, {"error": {"type": "upstream_error", "message": _sanitize_err_body(err_body)}})
|
||||||
except Exception as e:
|
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)}})
|
return self.send_json(502, {"error": {"type": "proxy_error", "message": str(e)}})
|
||||||
|
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
@@ -5392,11 +5392,11 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
_reasoning_out=reasoning_out),
|
_reasoning_out=reasoning_out),
|
||||||
on_event=_on_fb_event)
|
on_event=_on_fb_event)
|
||||||
except (ConnectionResetError, BrokenPipeError, ConnectionAbortedError):
|
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
|
return
|
||||||
|
|
||||||
success = finish_reason[0] != "length"
|
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:
|
if last_resp_id[0] and input_data is not None:
|
||||||
store_response(last_resp_id[0], input_data, last_output[0])
|
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"):
|
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"):
|
if reasoning_out.get("text"):
|
||||||
asm["reasoning_content"] = reasoning_out["text"]
|
asm["reasoning_content"] = reasoning_out["text"]
|
||||||
_ds_store_assistant(last_resp_id[0], asm)
|
_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:
|
else:
|
||||||
raw = upstream.read().decode()
|
raw = upstream.read().decode()
|
||||||
chat_resp = json.loads(raw)
|
chat_resp = json.loads(raw)
|
||||||
@@ -5415,9 +5415,9 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
rid = result.get("id")
|
rid = result.get("id")
|
||||||
if rid:
|
if rid:
|
||||||
store_response(rid, input_data, result.get("output", []))
|
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:
|
finally:
|
||||||
_freebuff_finish_run(token, run_id, "completed")
|
_codebuff_finish_run(token, run_id, "completed")
|
||||||
return
|
return
|
||||||
|
|
||||||
if last_err:
|
if last_err:
|
||||||
@@ -5447,15 +5447,15 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
|
except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
|
||||||
return
|
return
|
||||||
|
|
||||||
def _fb_retry_thinking_disabled(self, body, model, token, agent_id, stream, tracker, input_data, instructions, original_error, acct=None):
|
def _cb_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)
|
run_id, run_err = _codebuff_start_run(token, agent_id)
|
||||||
if not run_id:
|
if not run_id:
|
||||||
msg = run_err[3] if run_err else "unknown error"
|
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",
|
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}"}})
|
"message": f"Failed to start agent run for retry: {msg}"}})
|
||||||
instance_id = _freebuff_get_session(token, model)
|
instance_id = _codebuff_get_session(token, model)
|
||||||
messages = _fb_input_to_messages(input_data, instructions)
|
messages = _cb_input_to_messages(input_data, instructions)
|
||||||
_freebuff_hard_disable_reasoning(messages)
|
_codebuff_hard_disable_reasoning(messages)
|
||||||
metadata = {"run_id": run_id, "cost_mode": "free"}
|
metadata = {"run_id": run_id, "cost_mode": "free"}
|
||||||
if instance_id:
|
if instance_id:
|
||||||
metadata["freebuff_instance_id"] = instance_id
|
metadata["freebuff_instance_id"] = instance_id
|
||||||
@@ -5473,22 +5473,22 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
chat_body["tools"] = tools
|
chat_body["tools"] = tools
|
||||||
if body.get("tool_choice"):
|
if body.get("tool_choice"):
|
||||||
chat_body["tool_choice"] = body["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}
|
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}", "User-Agent": "codex-launcher/3.9.7", "x-freebuff-model": model}
|
||||||
if instance_id:
|
if instance_id:
|
||||||
headers["x-freebuff-instance-id"] = 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:
|
try:
|
||||||
req = urllib.request.Request(target, data=json.dumps(chat_body).encode(), headers=headers)
|
req = urllib.request.Request(target, data=json.dumps(chat_body).encode(), headers=headers)
|
||||||
upstream = urllib.request.urlopen(req, timeout=_upstream_timeout(body, stream))
|
upstream = urllib.request.urlopen(req, timeout=_upstream_timeout(body, stream))
|
||||||
except urllib.error.HTTPError as e:
|
except urllib.error.HTTPError as e:
|
||||||
err_body = e.read().decode()[:500]
|
err_body = e.read().decode()[:500]
|
||||||
_freebuff_finish_run(token, run_id, "failed")
|
_codebuff_finish_run(token, run_id, "failed")
|
||||||
print(f"[freebuff] thinking-disabled retry failed: HTTP {e.code}: {err_body[:300]}", file=sys.stderr)
|
print(f"[codebuff] 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",
|
return self.send_json(e.code, {"error": {"type": "codebuff_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)}})
|
"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:
|
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)}})
|
return self.send_json(502, {"error": {"type": "proxy_error", "message": str(e)}})
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
try:
|
try:
|
||||||
@@ -5531,7 +5531,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
except (ConnectionResetError, BrokenPipeError, ConnectionAbortedError):
|
except (ConnectionResetError, BrokenPipeError, ConnectionAbortedError):
|
||||||
return
|
return
|
||||||
success = finish_reason[0] != "length"
|
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:
|
if last_resp_id[0] and input_data is not None:
|
||||||
store_response(last_resp_id[0], input_data, last_output[0])
|
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"):
|
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"):
|
if reasoning_out.get("text"):
|
||||||
asm["reasoning_content"] = reasoning_out["text"]
|
asm["reasoning_content"] = reasoning_out["text"]
|
||||||
_ds_store_assistant(last_resp_id[0], asm)
|
_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:
|
else:
|
||||||
raw = upstream.read().decode()
|
raw = upstream.read().decode()
|
||||||
chat_resp = json.loads(raw)
|
chat_resp = json.loads(raw)
|
||||||
@@ -5550,9 +5550,9 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
rid = result.get("id")
|
rid = result.get("id")
|
||||||
if rid:
|
if rid:
|
||||||
store_response(rid, input_data, result.get("output", []))
|
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:
|
finally:
|
||||||
_freebuff_finish_run(token, run_id, "completed")
|
_codebuff_finish_run(token, run_id, "completed")
|
||||||
|
|
||||||
def _handle_auto(self, body, model, stream, tracker=None):
|
def _handle_auto(self, body, model, stream, tracker=None):
|
||||||
"""Auto-sensing backend: probe schema, adapt, retry on errors.
|
"""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"translate-proxy ({BACKEND}) listening on http://127.0.0.1:{PORT}", flush=True)
|
||||||
print(f"Target: {TARGET_URL}", flush=True)
|
print(f"Target: {TARGET_URL}", flush=True)
|
||||||
print(f"Models: {[m['id'] for m in MODELS]}", flush=True)
|
print(f"Models: {[m['id'] for m in MODELS]}", flush=True)
|
||||||
if BACKEND == "freebuff":
|
if BACKEND == "codebuff":
|
||||||
_fb_pool.load_accounts(force=True)
|
_cb_pool.load_accounts(force=True)
|
||||||
fb_status = _fb_pool.status()
|
fb_status = _cb_pool.status()
|
||||||
print(f"[multi-account] freebuff: {len(fb_status)} accounts loaded {[a['id'] for a in fb_status]}", flush=True)
|
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"):
|
if OAUTH_PROVIDER and OAUTH_PROVIDER.startswith("google"):
|
||||||
pool = _google_antigravity_pool if OAUTH_PROVIDER == "google-antigravity" else _google_cli_pool
|
pool = _google_antigravity_pool if OAUTH_PROVIDER == "google-antigravity" else _google_cli_pool
|
||||||
pool.load_accounts(force=True)
|
pool.load_accounts(force=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user