#!/usr/bin/env python3 """LobeHub Proxy v2 — OpenAI-compatible API using lh CLI. 601 accounts, token rotation, :20140""" import json, time, subprocess, threading, os, sys from http.server import HTTPServer, BaseHTTPRequestHandler from pathlib import Path from datetime import datetime from urllib.parse import urlparse ACCOUNTS_FILE = Path(r"C:\Users\User\lobehub_accounts.json") PORT = 20140 MODELS = [ "openai/gpt-5.5", "openai/gpt-5.4", "openai/gpt-5.4-mini", "openai/gpt-5.3-codex-spark", "anthropic/claude-opus-4.8", "anthropic/claude-opus-4.7", "anthropic/claude-opus-4.6", "anthropic/claude-opus-4.5", "anthropic/claude-sonnet-5", "anthropic/claude-sonnet-4.6", "anthropic/claude-sonnet-4.5", "anthropic/claude-haiku-4.5", "deepseek/deepseek-v4-pro", "deepseek/deepseek-v4-flash", "z-ai/glm-5.2", "z-ai/glm-5.1", "moonshotai/kimi-k2.7-code", "moonshotai/kimi-k2.6", ] pool_lock = threading.Lock() accounts = [] account_index = 0 stats = {"total_requests": 0, "errors": 0, "started": datetime.now().isoformat()} def init(): global accounts with open(ACCOUNTS_FILE) as f: raw = json.load(f) with pool_lock: accounts = [{ "email": a["email"], "token": a.get("token", ""), "used": 0, "last_used": None, "errors": 0, "cooldown_until": 0 } for a in raw] print(f"[Pool] {len(accounts)} accounts loaded") def get_next_account(): global account_index with pool_lock: now = time.time() for _ in range(len(accounts)): acc = accounts[account_index] account_index = (account_index + 1) % len(accounts) if acc["cooldown_until"] < now: acc["used"] += 1 acc["last_used"] = datetime.now().isoformat() return acc time.sleep(2) acc = accounts[0] acc["used"] += 1 acc["last_used"] = datetime.now().isoformat() return acc def cooldown_account(email, seconds=30): with pool_lock: for a in accounts: if a["email"] == email: a["cooldown_until"] = time.time() + seconds a["errors"] += 1 def generate(model, messages, max_tokens=4096, temperature=0.7): system = "" user_prompt = "" for m in messages: if m["role"] == "system": system = m["content"] elif m["role"] == "user": user_prompt = m["content"] if not user_prompt: user_prompt = " ".join(m["content"] for m in messages if m["role"] != "system") acc = get_next_account() token = acc["token"] env = os.environ.copy() env["LOBEHUB_JWT"] = token # Use node directly to avoid Windows cmd/batch issues NODE = r"C:\Users\User\.openagents\nodejs\node" CLI = r"C:\Users\User\AppData\Roaming\npm\node_modules\@lobehub\cli\dist\index.js" args = [NODE, CLI, "generate", "text", user_prompt, "--model", model, "--max-tokens", str(max_tokens), "--json"] if system: args.extend(["--system", system]) if temperature != 0.7: args.extend(["--temperature", str(temperature)]) try: proc = subprocess.run(args, capture_output=True, text=True, timeout=120, env=env) if proc.returncode != 0: stderr = proc.stderr.strip() if "rate" in stderr.lower() or "429" in stderr: cooldown_account(acc["email"], 60) elif "unauthorized" in stderr.lower() or "401" in stderr: cooldown_account(acc["email"], 300) else: cooldown_account(acc["email"], 30) raise Exception(f"CLI error: {stderr[:200]}") output = proc.stdout.strip() if not output: raise Exception("Empty CLI output") result = json.loads(output) with pool_lock: stats["total_requests"] += 1 return { "id": result.get("id", f"chatcmpl-{int(time.time())}"), "object": "chat.completion", "created": int(time.time()), "model": model, "choices": [{ "index": 0, "message": { "role": "assistant", "content": result["content"][0]["text"] if "content" in result else result.get("text", "") }, "finish_reason": result.get("stop_reason", "stop") }], "usage": { "prompt_tokens": result.get("usage", {}).get("input_tokens", 0), "completion_tokens": result.get("usage", {}).get("output_tokens", 0), "total_tokens": (result.get("usage", {}).get("input_tokens", 0) + result.get("usage", {}).get("output_tokens", 0)) } } except json.JSONDecodeError: cooldown_account(acc["email"], 30) raise Exception("JSON parse error") except Exception as e: with pool_lock: stats["errors"] += 1 raise class LobeHubHandler(BaseHTTPRequestHandler): def log_message(self, format, *args): pass def _send_json(self, data, status=200): self.send_response(status) self.send_header("Content-Type", "application/json") self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(json.dumps(data).encode()) def _send_error(self, msg, status=500): self._send_json({"error": {"message": msg, "type": "server_error"}}, status) def do_OPTIONS(self): self.send_response(200) self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") self.send_header("Access-Control-Allow-Headers", "*") self.end_headers() def do_GET(self): path = urlparse(self.path).path if path == "/v1/models": models_list = [{"id": m, "object": "model", "created": 0, "owned_by": "lobehub"} for m in MODELS] self._send_json({"object": "list", "data": models_list}) elif path == "/health": self._send_json({"status": "ok", "accounts": len(accounts), "stats": stats}) elif path == "/stats": self._send_json(stats) else: self._send_error("Not found", 404) def do_POST(self): path = urlparse(self.path).path if path != "/v1/chat/completions": self._send_error("Not found", 404) return try: content_length = int(self.headers.get("Content-Length", 0)) body = self.rfile.read(content_length) data = json.loads(body) except: self._send_error("Invalid JSON", 400) return model = data.get("model", "anthropic/claude-opus-4.8") messages = data.get("messages", []) max_tokens = data.get("max_tokens", 4096) temperature = data.get("temperature", 0.7) if not messages: self._send_error("No messages provided", 400) return try: result = generate(model, messages, max_tokens, temperature) self._send_json(result) except Exception as e: self._send_error(str(e), 500) # Add npm bin to PATH (hardcoded - Windows) NPM_BIN = r"C:\Users\User\AppData\Roaming\npm" if NPM_BIN not in os.environ.get("PATH", ""): os.environ["PATH"] = NPM_BIN + os.pathsep + os.environ.get("PATH", "") os.environ["Path"] = NPM_BIN + os.pathsep + os.environ.get("Path", "") if __name__ == "__main__": print(f"[LobeHub Proxy v2] Starting on :{PORT}") init() server = HTTPServer(("0.0.0.0", PORT), LobeHubHandler) print(f"[LobeHub Proxy v2] Ready - {len(accounts)} accounts, {len(MODELS)} models") try: server.serve_forever() except KeyboardInterrupt: print("\n[LobeHub Proxy v2] Shutting down") server.shutdown()