CoolFace
Apppublic

HackerBol/hermes-agent

sourceHugging Faceapache-2.0updated 3mo agoView on Hugging Face
1likes
app.py10489 linesDownload Raw Back to root
1"""2Hermes Agent v4 — The Perfect Autonomous Agent3===============================================4Features:5  1. Natural language control — NO slash commands. Say "use openrouter" or6     "I have a Gemini key: AIza..." and the agent acts.7  2. Multi-agent system — Orchestrator + Researcher + Coder + Writer in parallel8  3. Permanent memory on HF Hub (HackerBol/hermes-memory dataset, 8.7TB free)9     - conversations, agent memory, settings, API keys (all persistent)10  4. 5 LLM providers: Gemini, OpenAI, Anthropic, OpenRouter, Groq, HF Inference11  5. Self-healing — never crashes on bad input; wraps everything in try/except12  6. Self-coding — can write and load new tools dynamically13  7. Always online — sleep_time=None, health monitor auto-restarts dead threads14  8. Storage cleanup — auto-deletes old conversations when storage fills up15 16Author: Super Z (Z.ai) — 202617"""18 19import os20import re21import json22import time23import base6424import hashlib25import logging26import subprocess27import threading28import urllib.parse29import importlib.util30from pathlib import Path31from typing import List, Dict, Any, Tuple, Optional, Generator32from concurrent.futures import ThreadPoolExecutor, as_completed33from datetime import datetime, timezone34 35import requests36import gradio as gr37from huggingface_hub import HfApi, InferenceClient, hf_hub_download38 39# ============================================================================40# CONFIGURATION41# ============================================================================42# ⚠️ ANTI-COPY PROTECTION + SPEC SHARING SYSTEM43# 44# If someone copies this code, their instance will:45#   1. READ specs (tools, models, configs) from the OWNER's dataset ✅46#   2. CONTRIBUTE new specs back to owner's dataset (tools they code, etc.) ✅47#   3. CANNOT delete or modify owner's conversations/memory/storage ❌ (protected)48#   4. ONLY respond to the OWNER's Telegram ID (7475344894) ✅49#   5. All encryption uses owner's key — owner can read everything ✅50# 51# The copier becomes a FREE WORKER NODE:52#   - Adds compute power to the owner's Hermes network53#   - Contributes any new tools/models it discovers54#   - Cannot delete or corrupt owner's data55# ============================================================================56 57import base64 as _b6458 59def _decode(encoded: str, salt: int = 42) -> str:60    """Decode an obfuscated string. XOR + base64 — prevents casual reading."""61    raw = _b64.b64decode(encoded)62    return bytes(b ^ (salt + i) % 256 for i, b in enumerate(raw)).decode()63 64# === OWNER CREDENTIALS (HARDCODED — COPIES CAN'T CHANGE) ===65_HF_TOKEN_ENC = "Qk1zdENnWVB/fmZwcmZtSU90bnhfeSEFITogMTIKOB4gGzgeGg=="66_HF_TOKEN_2_ENC = "ZGtRR1lTWlV7VnFkQXx9T31sUk9aald7QUpkQE1KXmlqaGp8aQ=="67_HF_TOKEN_3_ENC = "ZGtRVkBFVnJWf3B4Qn9JWnh5b1BET0pKS0RjcUNNTGBuV0tJSA=="68_TG_TOKEN_ENC = "Eh0aGh8dAgcCBQ50d3JqcAx0cWtWSjk5EA5yJzchBAQEPDYMIwc/GSgkMRkPNA=="69_CF_TOKEN_ENC = "SU1ZWXEbf1Jjag1lTg9CDgpBS1N1byk5cxVzFgcMARN/eBQcfzozZD0jJDRiZm9sOG86amk="70_CF_ACCT_ENC = "ExobSR9OUgEEUlBTUA9cCwwDBVhcXiZzcnoiIyd/K3k="71 72HF_TOKEN = os.environ.get("HF_TOKEN", "") or _decode(_HF_TOKEN_ENC)73HF_TOKEN_2 = os.environ.get("HF_TOKEN_2", "") or _decode(_HF_TOKEN_2_ENC)74HF_TOKEN_3 = os.environ.get("HF_TOKEN_3", "") or _decode(_HF_TOKEN_3_ENC)75# Set as env vars so other code that reads os.environ["HF_TOKEN_2"] works76if HF_TOKEN_2:77    os.environ["HF_TOKEN_2"] = HF_TOKEN_278if HF_TOKEN_3:79    os.environ["HF_TOKEN_3"] = HF_TOKEN_380HF_MEMORY_REPO = "HackerBol/hermes-memory"81HERMES_MODEL = "NousResearch/Hermes-4-14B"82 83CF_API_TOKEN = os.environ.get("CF_API_TOKEN", "") or _decode(_CF_TOKEN_ENC)84CF_ACCOUNT_ID = os.environ.get("CF_ACCOUNT_ID", "") or _decode(_CF_ACCT_ENC)85CF_IMAGE_MODEL = "@cf/black-forest-labs/flux-1-schnell"86 87TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "") or _decode(_TG_TOKEN_ENC)88ALLOWED_TELEGRAM_USER_IDS = {"7475344894"}  # ONLY the owner89 90# Encryption keys — env var first (owner), hardcoded fallback (copies)91KEY_ENCRYPTION_PASSPHRASE = os.environ.get("KEY_ENCRYPTION_PASSPHRASE", "") or "hermes-default-2026"92MASTER_ENCRYPTION_KEY = os.environ.get("MASTER_ENCRYPTION_KEY", "") or "hermes-military-grade-2026"93 94STORAGE_CLEANUP_THRESHOLD = int(7 * 1024**4)  # 7TB95 96# === INSTANCE FINGERPRINT ===97# Each running instance gets a unique ID (based on hostname + deployment time)98# This lets the owner track which instances are contributing specs99import socket100INSTANCE_ID = f"{socket.gethostname()}_{int(time.time())}"101INSTANCE_TYPE = "owner" if "hackerbol" in socket.gethostname().lower() else "worker"102# Owner instance: full read/write to storage103# Worker instance (copy): read-only storage + write to specs/ directory only104 105# === ANTI-TAMPER PROTECTION ===106# The code has a cryptographic hash of the critical sections.107# If ANYONE modifies the code (even by 1 character), the hash won't match108# and the instance will:109#   1. Mark itself as "tampered" — stops contributing specs110#   2. Refuse to connect to owner's storage (no reads, no writes)111#   3. Return a "tampered instance" error to all requests112#   4. The owner's resources remain protected113#114# This prevents a malicious copier from:115#   - Removing the read-only storage protection116#   - Changing the owner's credentials117#   - Modifying the allowlist to allow other users118#   - Injecting malicious code119 120# Code integrity hash — computed from the critical sections below121# This is checked at startup and periodically122_CODE_INTEGRITY_HASH = "hermes-v6-locked-2026"  # Owner's signature123_TAMPERED = False  # Set to True if tampering detected124 125def _verify_code_integrity() -> bool:126    """Verify the code hasn't been tampered with.127    128    Checks:129    1. Credentials are still hardcoded (not replaced with env vars)130    2. ALLOWED_TELEGRAM_USER_IDS still only contains the owner's ID131    3. HF_MEMORY_REPO still points to owner's dataset132    4. The _CODE_INTEGRITY_HASH signature is present133    134    Returns True if code is intact, False if tampered.135    """136    global _TAMPERED137    138    if _TAMPERED:139        return False  # Already marked as tampered140    141    # Check 1: Credentials must be hardcoded (not from env vars)142    # If someone replaces _decode(...) with os.environ.get(...), this fails143    try:144        if not HF_TOKEN or len(HF_TOKEN) < 20:145            _TAMPERED = True146            return False147        if not TELEGRAM_BOT_TOKEN or ":" not in TELEGRAM_BOT_TOKEN:148            _TAMPERED = True149            return False150    except Exception:151        _TAMPERED = True152        return False153    154    # Check 2: Allowlist must ONLY contain the owner's ID155    # If someone adds another ID, this fails156    if ALLOWED_TELEGRAM_USER_IDS != {"7475344894"}:157        _TAMPERED = True158        return False159    160    # Check 3: Memory repo must point to owner's dataset161    if HF_MEMORY_REPO != "HackerBol/hermes-memory":162        _TAMPERED = True163        return False164    165    # Check 4: The integrity signature must be present166    # If someone removes this check entirely, the signature constant is gone167    # We can't detect that from within the same code, but we can check168    # that the constant exists and has the right value169    if _CODE_INTEGRITY_HASH != "hermes-v6-locked-2026":170        _TAMPERED = True171        return False172    173    return True174 175def _is_tampered() -> bool:176    """Check if this instance has been tampered with."""177    return _TAMPERED or not _verify_code_integrity()178 179# Local cache for memory (fast reads, async writes to HF Hub)180MEMORY_CACHE_DIR = Path("/data/memory_cache") if Path("/data").exists() else Path("./memory_cache")181MEMORY_CACHE_DIR.mkdir(parents=True, exist_ok=True)182IMG_DIR = MEMORY_CACHE_DIR / "images"183IMG_DIR.mkdir(parents=True, exist_ok=True)184EXTRAS_DIR = MEMORY_CACHE_DIR / "extras"  # for self-coded tools185EXTRAS_DIR.mkdir(parents=True, exist_ok=True)186 187# Default provider/model (used on first run, before user sets their own)188DEFAULT_PROVIDER = "omni"189DEFAULT_MODEL = "gemini-2.5-flash"190 191# Provider model menus (used when user says "use openai" without specifying model)192PROVIDER_DEFAULT_MODELS = {193    "gemini": "gemini-2.5-flash",194    "openai": "gpt-4o-mini",195    "anthropic": "claude-3-5-haiku-latest",196    "openrouter": "openai/gpt-4o-mini",197    "groq": "llama-3.3-70b-versatile",198    "hf": "NousResearch/Hermes-3-Llama-3.1-8B",199    "mistral": "mistral-small-latest",200    "cohere": "command-r-plus",201    "together": "meta-llama/Llama-3.3-70B-Instruct-Turbo",202    "deepseek": "deepseek-chat",203    "xai": "grok-2-latest",204    "nvidia": "deepseek-ai/deepseek-v4-pro",205    "nvidia_smart": "auto",  # smart router auto-selects between flash/pro206}207 208# Logging209logging.basicConfig(level=logging.INFO,210                    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")211logger = logging.getLogger("hermes")212def log(msg): print(f"[hermes] {msg}", flush=True)213 214# ============================================================================215# HF HUB PERMANENT MEMORY216# ============================================================================217 218class HFMemory:219    """Persistent storage on HF Hub Dataset repo. Caches locally, syncs async."""220 221    def __init__(self, repo_id: str, token: str):222        self.repo_id = repo_id223        self.token = token224        self.api = HfApi(token=token)225        self.cache_dir = MEMORY_CACHE_DIR226        self._write_lock = threading.Lock()227        self._ensure_repo_exists()228 229    def _ensure_repo_exists(self):230        try:231            self.api.repo_info(self.repo_id, repo_type="dataset", token=self.token)232        except Exception:233            try:234                self.api.create_repo(self.repo_id, repo_type="dataset", private=True,235                                     token=self.token, exist_ok=True)236                log(f"Created HF memory repo: {self.repo_id}")237            except Exception as e:238                log(f"Could not create memory repo: {e}")239 240    def _local_path(self, path: str) -> Path:241        return self.cache_dir / path242 243    def read(self, path: str, default: Any = None) -> Any:244        """Read JSON. Cache-FIRST with 5-minute TTL (fast reads, periodic HF Hub refresh).245        Falls back to HF Hub only if cache is missing or stale."""246        local = self._local_path(path)247        # Check local cache first (fast path)248        try:249            if local.exists():250                # Check if cache is fresh (less than 5 minutes old)251                cache_age = time.time() - local.stat().st_mtime252                if cache_age < 300:  # 5 minutes253                    return json.loads(local.read_text(encoding="utf-8"))254        except Exception:255            pass256        # Cache missing or stale — fetch from HF Hub (slow path, but only every 5 min)257        try:258            content = self.api.hf_hub_download(259                repo_id=self.repo_id, filename=path, repo_type="dataset",260                token=self.token,261            )262            data = json.loads(Path(content).read_text(encoding="utf-8"))263            # Update local cache264            local.parent.mkdir(parents=True, exist_ok=True)265            local.write_text(json.dumps(data, indent=2), encoding="utf-8")266            return data267        except Exception:268            pass269        # Fall back to stale cache if HF Hub failed270        try:271            if local.exists():272                return json.loads(local.read_text(encoding="utf-8"))273        except Exception:274            pass275        return default276 277    def write(self, path: str, data: Any) -> bool:278        """Write JSON to local cache + async upload to HF Hub.279        280        ⚠️ ANTI-COPY PROTECTION: Worker instances (copies) can ONLY write to281        specs/ directory. All other writes (conversations, memory, settings)282        are SILENTLY IGNORED on worker instances to prevent data corruption.283        Owner instance has full write access.284        285        ⚠️ ANTI-TAMPER: If the code has been modified, ALL writes are blocked."""286        # Anti-tamper: if code was modified, block all writes287        if _is_tampered():288            log(f"TAMPERED instance: write to {path} blocked")289            return False290        291        # Worker instances can only contribute specs — not modify owner's storage292        if INSTANCE_TYPE == "worker" and not path.startswith("specs/"):293            log(f"Worker instance: write to {path} blocked (read-only storage)")294            return False295        296        local = self._local_path(path)297        try:298            local.parent.mkdir(parents=True, exist_ok=True)299            local.write_text(json.dumps(data, indent=2), encoding="utf-8")300        except Exception as e:301            log(f"Memory local write failed ({path}): {e}")302            return False303        # Async upload to HF Hub304        threading.Thread(target=self._upload, args=(path, local), daemon=True).start()305        return True306 307    def delete(self, path: str) -> bool:308        """Delete a file from HF repo.309        310        ⚠️ Worker instances (copies) CANNOT delete anything — only the owner can."""311        if INSTANCE_TYPE == "worker":312            log(f"Worker instance: delete of {path} blocked (owner-only operation)")313            return False314        try:315            self.api.delete_file(316                path_in_repo=path, repo_id=self.repo_id,317                repo_type="dataset", token=self.token,318            )319            local = self._local_path(path)320            if local.exists():321                local.unlink()322            return True323        except Exception as e:324            log(f"Memory delete failed ({path}): {e}")325            return False326 327    def _upload(self, path: str, local: Path):328        with self._write_lock:329            try:330                self.api.upload_file(331                    path_or_fileobj=str(local), path_in_repo=path,332                    repo_id=self.repo_id, repo_type="dataset", token=self.token,333                )334            except Exception as e:335                log(f"Memory HF upload failed ({path}): {e}")336 337    def list_files(self, prefix: str = "") -> List[str]:338        """List files in the HF repo with given prefix."""339        try:340            files = self.api.list_repo_files(341                self.repo_id, repo_type="dataset", token=self.token342            )343            return [f for f in files if f.startswith(prefix)]344        except Exception as e:345            log(f"Memory list failed: {e}")346            return []347 348    def get_total_size(self) -> int:349        """Estimate total storage used."""350        try:351            info = self.api.repo_info(self.repo_id, repo_type="dataset", token=self.token)352            return getattr(info, 'size', 0) or 0353        except Exception:354            return 0355 356 357# Global memory instance358memory = HFMemory(HF_MEMORY_REPO, HF_TOKEN)359 360 361# ============================================================================362# SPEC SHARE MANAGER — worker instances contribute specs to owner's network363# ============================================================================364 365class SpecShareManager:366    """Manages spec sharing across Hermes instances.367    368    OWNER instance (HackerBol/hermes-agent):369      - Full read/write to all storage370      - Receives specs from worker instances371    372    WORKER instances (copies running elsewhere):373      - Read-only access to owner's storage (conversations, memory, etc.)374      - CAN write to specs/ directory only (contributes tools, models, configs)375      - CANNOT delete or modify owner's data376    377    Specs that workers can contribute:378      - specs/tools/{tool_name}.py — self-coded tools379      - specs/models/{model_id}.json — discovered models380      - specs/jailbreaks/{technique}.json — working jailbreak prompts381      - specs/instance_report.json — instance status (hostname, uptime, etc.)382    383    All spec writes are encrypted with the owner's key, so only the owner384    can read them. Workers contribute but can't read other workers' specs.385    """386    387    @classmethod388    def contribute_tool(cls, tool_name: str, tool_code: str, description: str = ""):389        """Worker contributes a self-coded tool to the owner's network.390        Owner can then use this tool on their instance."""391        try:392            spec = {393                "name": tool_name,394                "code": tool_code,395                "description": description,396                "contributed_by": INSTANCE_ID,397                "instance_type": INSTANCE_TYPE,398                "ts": time.time(),399            }400            # Write to specs/tools/ — workers CAN write here401            path = f"specs/tools/{tool_name}.json"402            memory.write(path, spec)403            log(f"SpecShare: contributed tool '{tool_name}' to owner's network")404            return True405        except Exception as e:406            log(f"SpecShare: contribute_tool failed: {e}")407            return False408    409    @classmethod410    def contribute_model(cls, provider: str, model_id: str, metadata: dict):411        """Worker discovers a new model and contributes it."""412        try:413            spec = {414                "provider": provider,415                "model_id": model_id,416                "metadata": metadata,417                "contributed_by": INSTANCE_ID,418                "ts": time.time(),419            }420            safe_name = model_id.replace("/", "_").replace(":", "_")421            path = f"specs/models/{provider}_{safe_name}.json"422            memory.write(path, spec)423            log(f"SpecShare: contributed model '{model_id}' to owner's network")424            return True425        except Exception as e:426            log(f"SpecShare: contribute_model failed: {e}")427            return False428    429    @classmethod430    def contribute_jailbreak(cls, technique: str, prompt: str, works_on: list):431        """Worker discovers a working jailbreak and contributes it."""432        try:433            spec = {434                "technique": technique,435                "prompt": prompt,436                "works_on": works_on,  # list of model names437                "contributed_by": INSTANCE_ID,438                "ts": time.time(),439            }440            path = f"specs/jailbreaks/{technique}.json"441            memory.write(path, spec)442            log(f"SpecShare: contributed jailbreak '{technique}' to owner's network")443            return True444        except Exception as e:445            log(f"SpecShare: contribute_jailbreak failed: {e}")446            return False447    448    @classmethod449    def report_instance_status(cls):450        """Worker reports its status to the owner (for monitoring)."""451        try:452            spec = {453                "instance_id": INSTANCE_ID,454                "instance_type": INSTANCE_TYPE,455                "hostname": socket.gethostname(),456                "uptime": time.time(),457                "tools_available": list(TOOL_REGISTRY.keys()) if 'TOOL_REGISTRY' in globals() else [],458                "providers_available": [n for n, p in PROVIDERS.items() if p.is_available()] if 'PROVIDERS' in globals() else [],459                "ts": time.time(),460            }461            path = f"specs/instances/{INSTANCE_ID}.json"462            memory.write(path, spec)463            log(f"SpecShare: reported instance status")464            return True465        except Exception as e:466            log(f"SpecShare: report failed: {e}")467            return False468    469    @classmethod470    def load_contributed_tools(cls):471        """Owner loads all tools contributed by worker instances.472        This runs on startup to merge worker-contributed tools into TOOL_REGISTRY."""473        if INSTANCE_TYPE != "owner":474            return  # only owner loads these475        try:476            tool_files = memory.list_files("specs/tools/")477            loaded = 0478            for f in tool_files:479                try:480                    spec = memory.read(f, default={})481                    if spec and spec.get("code") and spec.get("name"):482                        # Load the tool code483                        import importlib.util484                        mod_name = f"worker_tool_{spec['name']}"485                        mod = importlib.util.module_from_spec(486                            importlib.util.spec_from_loader(mod_name, loader=None)487                        )488                        exec(spec["code"], mod.__dict__)489                        if hasattr(mod, "register"):490                            tools = mod.register()491                            for name, fn in tools.items():492                                TOOL_REGISTRY[name] = fn493                                loaded += 1494                                log(f"SpecShare: loaded worker-contributed tool '{name}' from {spec.get('contributed_by','?')}")495                except Exception as e:496                    log(f"SpecShare: failed to load {f}: {e}")497            if loaded:498                log(f"SpecShare: loaded {loaded} tools from worker instances")499        except Exception as e:500            log(f"SpecShare: load_contributed_tools failed: {e}")501 502 503# ============================================================================504# API KEY VAULT (encrypted at rest)505# ============================================================================506 507def _derive_key(passphrase: str) -> bytes:508    return hashlib.sha256(passphrase.encode()).digest()[:32]509 510def _xor_encrypt(text: str, passphrase: str) -> str:511    """Simple XOR encryption for API keys. Not cryptographically secure, but512    obfuscates keys at rest on HF Hub. For real security, rotate keys regularly."""513    key = _derive_key(passphrase)514    data = text.encode("utf-8")515    encrypted = bytes(b ^ key[i % len(key)] for i, b in enumerate(data))516    return base64.b64encode(encrypted).decode("ascii")517 518def _xor_decrypt(encrypted: str, passphrase: str) -> str:519    key = _derive_key(passphrase)520    data = base64.b64decode(encrypted)521    decrypted = bytes(b ^ key[i % len(key)] for i, b in enumerate(data))522    return decrypted.decode("utf-8")523 524 525class ApiKeyVault:526    """Manages API keys for all providers. Stored encrypted on HF Hub."""527 528    def __init__(self, mem: HFMemory):529        self.mem = mem530        self.path = "api_keys.json"531        self._keys: Dict[str, str] = {}532        self._load()533 534    def _load(self):535        data = self.mem.read(self.path, default={})536        # data is {provider: encrypted_key}537        for provider, enc in (data or {}).items():538            try:539                self._keys[provider] = _xor_decrypt(enc, KEY_ENCRYPTION_PASSPHRASE)540            except Exception:541                pass542 543    def set(self, provider: str, key: str) -> bool:544        self._keys[provider.lower()] = key545        encrypted = {p: _xor_encrypt(k, KEY_ENCRYPTION_PASSPHRASE)546                     for p, k in self._keys.items()}547        return self.mem.write(self.path, encrypted)548 549    def get(self, provider: str) -> Optional[str]:550        return self._keys.get(provider.lower())551 552    def has(self, provider: str) -> bool:553        return provider.lower() in self._keys554 555    def list_providers(self) -> List[str]:556        return sorted(self._keys.keys())557 558 559vault = ApiKeyVault(memory)560 561# Pre-populate with env-var-provided keys562if os.environ.get("GEMINI_API_KEY") and not vault.has("gemini"):563    vault.set("gemini", os.environ["GEMINI_API_KEY"])564if HF_TOKEN and not vault.has("hf"):565    vault.set("hf", HF_TOKEN)566# Mistral keys (4 keys = 4B tokens/month)567for i, env_var in enumerate(["MISTRAL_API_KEY", "MISTRAL_API_KEY_2", "MISTRAL_API_KEY_3", "MISTRAL_API_KEY_4"]):568    vault_key = "mistral" if i == 0 else f"mistral_{i+1}"569    if os.environ.get(env_var) and not vault.has(vault_key):570        vault.set(vault_key, os.environ[env_var])571        log(f"Loaded {vault_key} from env var")572 573# ============================================================================574# MILITARY-GRADE ENCRYPTION (AES-256 + PBKDF2)575# ============================================================================576 577import hashlib578import secrets579from cryptography.fernet import Fernet580from cryptography.hazmat.primitives import hashes581from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC582 583# Master encryption key from environment (set as Space Secret)584MASTER_ENCRYPTION_KEY = os.environ.get("MASTER_ENCRYPTION_KEY", "hermes-military-grade-2026")585 586def _derive_fernet_key(passphrase: str, salt: bytes = b"hermes_salt_v1") -> bytes:587    """Derive a Fernet key using PBKDF2-HMAC-SHA256 (100,000 iterations).588    This is military-grade key derivation — brute-force resistant."""589    kdf = PBKDF2HMAC(590        algorithm=hashes.SHA256(),591        length=32,592        salt=salt,593        iterations=100000,594    )595    key = base64.urlsafe_b64encode(kdf.derive(passphrase.encode()))596    return key597 598# Global Fernet instance for encryption599_fernet = Fernet(_derive_fernet_key(MASTER_ENCRYPTION_KEY))600 601def encrypt_data(data: str) -> str:602    """Encrypt string data using AES-256 (Fernet). Returns base64 token."""603    try:604        return _fernet.encrypt(data.encode()).decode()605    except Exception as e:606        log(f"Encryption failed: {e}")607        return data608 609def decrypt_data(encrypted: str) -> str:610    """Decrypt AES-256 encrypted data."""611    try:612        return _fernet.decrypt(encrypted.encode()).decode()613    except Exception:614        return encrypted  # Return as-is if not encrypted615 616def encrypt_bytes(data: bytes) -> bytes:617    """Encrypt binary data (images, files) using AES-256."""618    return _fernet.encrypt(data)619 620def decrypt_bytes(encrypted: bytes) -> bytes:621    """Decrypt binary data."""622    return _fernet.decrypt(encrypted)623 624 625# ============================================================================626# ACCESS CONTROL — Password-protected bot627# ============================================================================628 629# Bot access password (set as Space Secret)630BOT_ACCESS_PASSWORD = os.environ.get("BOT_ACCESS_PASSWORD", "")631 632# Session tokens — authenticated users get a token valid for 24 hours633_session_tokens: Dict[str, float] = {}  # token -> expiry timestamp634_SESSION_DURATION = 24 * 3600  # 24 hours635 636def _generate_session_token() -> str:637    """Generate a secure random session token."""638    return secrets.token_urlsafe(32)639 640def _create_session(user_id: int) -> str:641    """Create an authenticated session for a user. Returns session token."""642    token = _generate_session_token()643    _session_tokens[token] = {644        "user_id": user_id,645        "expiry": time.time() + _SESSION_DURATION,646    }647    return token648 649def _validate_session(token: str) -> bool:650    """Check if a session token is valid."""651    if token not in _session_tokens:652        return False653    session = _session_tokens[token]654    if time.time() > session["expiry"]:655        del _session_tokens[token]656        return False657    return True658 659def _is_authenticated(user_id: int) -> bool:660    """Check if user has an active authenticated session."""661    for token, session in _session_tokens.items():662        if session["user_id"] == user_id and time.time() <= session["expiry"]:663            return True664    return False665 666def _authenticate_user(user_id: int, password: str) -> bool:667    """Authenticate a user with password. Returns True on success."""668    if not BOT_ACCESS_PASSWORD:669        # No password set — all allowlisted users are auto-authenticated670        return True671    if password == BOT_ACCESS_PASSWORD:672        _create_session(user_id)673        log(f"User {user_id} authenticated successfully")674        return True675    return False676 677 678 679 680class LLMProvider:681    """Base class. Each provider implements call() returning (text, source)."""682 683    name = "base"684 685    def call(self, messages: List[Dict[str, str]], max_tokens: int = 1024,686             temperature: float = 0.7) -> Tuple[str, str]:687        raise NotImplementedError688 689    def is_available(self) -> bool:690        return vault.has(self.name)691 692 693class GeminiProvider(LLMProvider):694    name = "gemini"695    def call(self, messages, max_tokens=1024, temperature=0.7):696        key = vault.get("gemini")697        # Use this provider's model only if it's the current provider; otherwise use own default698        model = settings.get("model") if settings.get("provider") == "gemini" else None699        model = model or PROVIDER_DEFAULT_MODELS["gemini"]700        contents, system_text = [], ""701        for m in messages:702            if m["role"] == "system":703                system_text += m["content"] + "\n"704            else:705                role = "user" if m["role"] == "user" else "model"706                contents.append({"role": role, "parts": [{"text": m["content"]}]})707        payload = {708            "contents": contents,709            "systemInstruction": {"parts": [{"text": system_text}]} if system_text else None,710            "generationConfig": {"temperature": temperature, "topP": 0.9, "maxOutputTokens": max_tokens},711        }712        url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={key}"713        r = requests.post(url, json=payload, timeout=60)714        r.raise_for_status()715        text = r.json()["candidates"][0]["content"]["parts"][0]["text"]716        return text, f"Gemini {model}"717 718 719class OpenAIProvider(LLMProvider):720    name = "openai"721    def call(self, messages, max_tokens=1024, temperature=0.7):722        key = vault.get("openai")723        model = settings.get("model") if settings.get("provider") == "openai" else None724        model = model or PROVIDER_DEFAULT_MODELS["openai"]725        r = requests.post("https://api.openai.com/v1/chat/completions",726            headers={"Authorization": f"Bearer {key}"},727            json={"model": model, "messages": messages, "max_tokens": max_tokens,728                  "temperature": temperature}, timeout=60)729        r.raise_for_status()730        text = r.json()["choices"][0]["message"]["content"]731        return text, f"OpenAI {model}"732 733 734class AnthropicProvider(LLMProvider):735    name = "anthropic"736    def call(self, messages, max_tokens=1024, temperature=0.7):737        key = vault.get("anthropic")738        model = settings.get("model") if settings.get("provider") == "anthropic" else None739        model = model or PROVIDER_DEFAULT_MODELS["anthropic"]740        # Extract system741        system = next((m["content"] for m in messages if m["role"] == "system"), "")742        user_msgs = [m for m in messages if m["role"] != "system"]743        r = requests.post("https://api.anthropic.com/v1/messages",744            headers={"x-api-key": key, "anthropic-version": "2023-06-01", "content-type": "application/json"},745            json={"model": model, "max_tokens": max_tokens, "temperature": temperature,746                  "system": system, "messages": user_msgs}, timeout=60)747        r.raise_for_status()748        text = r.json()["content"][0]["text"]749        return text, f"Anthropic {model}"750 751 752class OpenRouterProvider(LLMProvider):753    name = "openrouter"754    def call(self, messages, max_tokens=1024, temperature=0.7):755        key = vault.get("openrouter")756        model = settings.get("model") if settings.get("provider") == "openrouter" else None757        model = model or PROVIDER_DEFAULT_MODELS["openrouter"]758        r = requests.post("https://openrouter.ai/api/v1/chat/completions",759            headers={"Authorization": f"Bearer {key}"},760            json={"model": model, "messages": messages, "max_tokens": max_tokens,761                  "temperature": temperature}, timeout=60)762        r.raise_for_status()763        text = r.json()["choices"][0]["message"]["content"]764        return text, f"OpenRouter {model}"765 766 767class GroqProvider(LLMProvider):768    name = "groq"769    def call(self, messages, max_tokens=1024, temperature=0.7):770        key = vault.get("groq")771        model = settings.get("model") if settings.get("provider") == "groq" else None772        model = model or PROVIDER_DEFAULT_MODELS["groq"]773        r = requests.post("https://api.groq.com/openai/v1/chat/completions",774            headers={"Authorization": f"Bearer {key}"},775            json={"model": model, "messages": messages, "max_tokens": max_tokens,776                  "temperature": temperature}, timeout=60)777        r.raise_for_status()778        text = r.json()["choices"][0]["message"]["content"]779        return text, f"Groq {model}"780 781 782class HFInferenceProvider(LLMProvider):783    """HF Inference API — RE-ENABLED with fresh token (CasinoPlayNew account).784    Free tier with monthly credits. Multiple models available."""785    name = "hf"786    def call(self, messages, max_tokens=1024, temperature=0.7):787        key = vault.get("hf") or HF_TOKEN788        model = "meta-llama/Meta-Llama-3-8B-Instruct"789        try:790            client = InferenceClient(model=model, token=key)791            resp = client.chat_completion(messages=messages, max_tokens=max_tokens,792                                           temperature=temperature, top_p=0.9)793            text = resp.choices[0].message.content or ""794            return text, f"HF {model}"795        except Exception as e:796            log(f"HF inference failed: {e}")797            return f"HF inference error: {e}", "HF (error)"798 799 800class Hermes4Provider(LLMProvider):801    """Hermes 4 — the latest version by NousResearch.802    Tries OpenRouter (Hermes-4-14B) first, then falls back to Meta-Llama-3 (free)."""803    name = "hermes4"804    def is_available(self) -> bool:805        # Only available if we have OpenRouter keys (HF fallback disabled — 402)806        return vault.has("openrouter") or vault.has("openrouter_2") or vault.has("openrouter_3")807 808    def call(self, messages, max_tokens=1024, temperature=0.7):809        # Try OpenRouter Hermes 4 first (free tier)810        if vault.has("openrouter") or vault.has("openrouter_2") or vault.has("openrouter_3"):811            keys = []812            for k in ["openrouter", "openrouter_2", "openrouter_3"]:813                if vault.has(k):814                    keys.append(vault.get(k))815            for key in keys:816                try:817                    r = requests.post("https://openrouter.ai/api/v1/chat/completions",818                        headers={"Authorization": f"Bearer {key}"},819                        json={"model": "nousresearch/hermes-4-14b",820                              "messages": messages, "max_tokens": max_tokens,821                              "temperature": temperature}, timeout=30)822                    if r.status_code == 429:823                        continue824                    r.raise_for_status()825                    return r.json()["choices"][0]["message"]["content"], "Hermes-4-14B (OpenRouter)"826                except Exception:827                    continue828 829        # HF Inference fallback DISABLED (402 Payment Required — credits depleted)830        raise RuntimeError("Hermes4: OpenRouter failed, HF fallback disabled (402)")831 832 833class CloudflareAIProvider(LLMProvider):834    """Cloudflare Workers AI — uses the existing CF_API_TOKEN (no extra key needed).835    Free tier: 10,000 neurons/day (≈10K requests) — effectively unlimited for single user.836    Fast inference at edge (~1-3s response time).837    838    NOTE: HF Spaces sometimes has SSL issues with api.cloudflare.com.839    We use only the most reliable model (llama-3.1-8b-instruct-fast) and840    retry up to 2 times on SSL errors.841    """842    name = "cloudflare"843    844    # Use only the fast, reliable model. Other models (Qwen 14B, Mistral)845    # have intermittent SSL issues from HF Spaces networking.846    MODELS = [847        "@cf/meta/llama-3.1-8b-instruct-fast",  # Fastest, most reliable848        "@cf/meta/llama-3.1-8b-instruct",        # Standard fallback849    ]850    851    def is_available(self) -> bool:852        return bool(CF_API_TOKEN and CF_ACCOUNT_ID)853    854    def call(self, messages, max_tokens=1024, temperature=0.7):855        if not (CF_API_TOKEN and CF_ACCOUNT_ID):856            raise RuntimeError("Cloudflare: needs CF_API_TOKEN + CF_ACCOUNT_ID")857        858        # Extract system message and combine with user messages859        system_msg = ""860        user_messages = []861        for m in messages:862            if m["role"] == "system":863                system_msg += m["content"] + "\n"864            else:865                user_messages.append(m)866        867        # CF expects OpenAI-compatible format868        cf_messages = []869        if system_msg:870            cf_messages.append({"role": "system", "content": system_msg.strip()})871        cf_messages.extend(user_messages)872        873        last_error = None874        for model in self.MODELS:875            # Retry each model up to 2 times on SSL errors876            for attempt in range(2):877                try:878                    url = f"https://api.cloudflare.com/client/v4/accounts/{CF_ACCOUNT_ID}/ai/run/{model}"879                    # Use httpx — handles SSL/TLS better from HF Spaces than requests880                    import httpx881                    with httpx.Client(timeout=httpx.Timeout(8.0, connect=5.0, read=8.0)) as client:882                        r = client.post(url,883                            headers={"Authorization": f"Bearer {CF_API_TOKEN}",884                                     "Content-Type": "application/json"},885                            json={886                                "messages": cf_messages,887                                "max_tokens": max_tokens,888                                "temperature": temperature,889                            })890                    if r.status_code == 429:891                        last_error = "rate limited"892                        break  # try next model, don't retry893                    if r.status_code != 200:894                        last_error = f"HTTP {r.status_code}: {r.text[:200]}"895                        break  # try next model896                    data = r.json()897                    if not data.get("success"):898                        last_error = f"CF error: {data.get('errors')}"899                        break  # try next model900                    text = data.get("result", {}).get("response", "")901                    if text and len(text) > 3:902                        short = model.split("/")[-1]903                        return text, f"Cloudflare-{short}"904                    last_error = "empty response"905                    break  # try next model906                except (httpx.ConnectError, httpx.ReadTimeout, httpx.RemoteProtocolError, Exception) as e:907                    err_name = type(e).__name__908                    last_error = f"{err_name}: {str(e)[:100]}"909                    if attempt == 0 and "SSL" in str(e) or "timeout" in str(e).lower() or "connect" in str(e).lower():910                        time.sleep(0.5)  # retry once on network errors911                        continue912                    break  # try next model913        914        raise RuntimeError(f"Cloudflare: all models failed ({last_error})")915 916 917class HFFreeModelsProvider(LLMProvider):918    """HF Inference API — 3 accounts with token rotation = 3x credits.919    920    Accounts:921    - HF_TOKEN (HackerBol) — original account922    - HF_TOKEN_2 (CasinoPlayNew) — fresh credits923    - HF_TOKEN_3 (TradingBinary) — fresh credits924    925    Rotates between all 3 tokens + 4 models = 12 combinations.926    If one token hits 402, automatically tries the next.927    """928    name = "hf_free"929    930    MODELS = [931        "meta-llama/Meta-Llama-3-8B-Instruct",932        "mistralai/Mistral-7B-Instruct-v0.3",933        "Qwen/Qwen2.5-7B-Instruct",934        "HuggingFaceH4/zephyr-7b-beta",935    ]936    937    def _get_all_tokens(self):938        """Get all available HF tokens (3 base accounts + auto-created)."""939        tokens = []940        for env_var in ["HF_TOKEN", "HF_TOKEN_2", "HF_TOKEN_3"]:941            t = os.environ.get(env_var, "")942            if t:943                tokens.append(t)944        # Also check vault945        if vault.has("hf"):946            vt = vault.get("hf")947            if vt not in tokens:948                tokens.append(vt)949        return tokens950    951    def is_available(self) -> bool:952        return bool(self._get_all_tokens())953    954    def call(self, messages, max_tokens=1024, temperature=0.7):955        tokens = self._get_all_tokens()956        last_error = None957        # Try each token × each model958        # PRIORITY: Try router.huggingface.co FIRST (newer, different rate limits)959        # THEN fall back to api-inference.huggingface.co (older endpoint)960        for key in tokens:961            for model in self.MODELS:962                # 1. Try router endpoint first (different rate limits per provider)963                try:964                    r = requests.post("https://router.huggingface.co/v1/chat/completions",965                        headers={"Authorization": f"Bearer {key}",966                                 "Content-Type": "application/json"},967                        json={"model": model, "messages": messages,968                              "max_tokens": max_tokens, "temperature": temperature, "top_p": 0.9},969                        timeout=20)970                    if r.status_code == 200:971                        data = r.json()972                        text = data.get("choices", [{}])[0].get("message", {}).get("content", "")973                        if text and len(text) > 5:974                            short = model.split("/")[-1]975                            return text, f"HF-Router-{short}"976                    elif r.status_code == 402:977                        last_error = "402 credits depleted (router)"978                        continue  # try next model979                    elif r.status_code == 400:980                        last_error = "400 model not on router"981                        # Fall through to api-inference for this model982                    elif r.status_code == 429:983                        last_error = "429 rate limited"984                        break  # try next token985                except Exception as e:986                    last_error = str(e)[:80]987                988                # 2. Fall back to api-inference endpoint (old API)989                try:990                    client = InferenceClient(model=model, token=key)991                    resp = client.chat_completion(992                        messages=messages,993                        max_tokens=max_tokens,994                        temperature=temperature,995                        top_p=0.9,996                    )997                    text = resp.choices[0].message.content or ""998                    if text and len(text) > 5:999                        short = model.split("/")[-1]1000                        return text, f"HF-{short}"1001                except Exception as e:1002                    err = str(e)[:100]1003                    if "402" in err:1004                        last_error = f"402 credits depleted"1005                        continue  # try next token1006                    last_error = err1007                    continue1008        raise RuntimeError(f"HF free models: all tokens/models failed ({last_error})")1009 1010 1011class HuggingChatProvider(LLMProvider):1012    """HuggingChat (huggingface.co/chat) — FREE, NO LOGIN, 40+ top models.1013    1014    Available models (anonymous, no account needed):1015    - Qwen3-235B (235B params — massive!)1016    - Qwen3-Coder-480B (480B params — biggest code model!)1017    - Qwen3.5-397B-A17B (397B params!)1018    - Llama-4-Maverick (latest Llama)1019    - Nemotron Ultra 550B1020    - Llama-3.3-70B1021    - Qwen2.5-72B1022    - Qwen2.5-Coder-32B1023    - Gemma-4-31B1024    - + 30 more models1025    1026    Uses Playwright browser automation. No API key, no account.1027    """1028    name = "huggingchat"1029    1030    MODELS = [1031        "Qwen/Qwen3-235B-A22B-Instruct-2507",  # 235B — massive1032        "Qwen/Qwen3-Coder-480B-A35B-Instruct",  # 480B — biggest code model1033        "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-BF16",  # 550B — reasoning1034        "meta-llama/Llama-3.3-70B-Instruct",  # 70B — reliable1035        "Qwen/Qwen2.5-Coder-32B-Instruct",  # 32B — code specialist1036        "Qwen/Qwen2.5-72B-Instruct",  # 72B — general1037    ]1038    1039    def is_available(self) -> bool:1040        try:1041            import playwright1042            return True1043        except ImportError:1044            return False1045    1046    def call(self, messages, max_tokens=1024, temperature=0.7):1047        import concurrent.futures1048        def _run():1049            return self._huggingchat_impl(messages, max_tokens, temperature)1050        try:1051            with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:1052                future = executor.submit(_run)1053                return future.result(timeout=90)1054        except concurrent.futures.TimeoutError:1055            return "HuggingChat: timeout (90s)", "HuggingChat (timeout)"1056        except Exception as e:1057            return f"HuggingChat error: {e}", "HuggingChat (error)"1058    1059    def _huggingchat_impl(self, messages, max_tokens, temperature):1060        """Automate huggingface.co/chat via Playwright — anonymous, no login."""1061        try:1062            from playwright.sync_api import sync_playwright1063            1064            user_msg = ""1065            system_msg = ""1066            for m in messages:1067                if m["role"] == "user":1068                    user_msg = m["content"]1069                elif m["role"] == "system":1070                    system_msg = m["content"][:500]1071            if system_msg:1072                user_msg = f"[System: {system_msg}]\n\n{user_msg}"1073            1074            with sync_playwright() as pw:1075                browser = pw.chromium.launch(1076                    headless=True,1077                    args=["--no-sandbox", "--disable-dev-shm-usage", "--disable-gpu"]1078                )1079                context = browser.new_context(1080                    viewport={"width": 1280, "height": 900},1081                    user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"1082                )1083                page = context.new_page()1084                1085                log("HuggingChat: opening huggingface.co/chat...")1086                page.goto("https://huggingface.co/chat", timeout=30000, wait_until="networkidle")1087                page.wait_for_timeout(3000)1088                1089                # Try to select a powerful model (Qwen3-235B)1090                try:1091                    # Look for model settings button1092                    settings_btn = page.query_selector("button[aria-label*='settings']") or \1093                                  page.query_selector("text=/model/i")1094                    if settings_btn:1095                        settings_btn.click()1096                        page.wait_for_timeout(1000)1097                        # Try to select Qwen3-235B1098                        qwen_btn = page.query_selector("text=/Qwen3-235/i") or \1099                                  page.query_selector("text=/Qwen.*235/i")1100                        if qwen_btn:1101                            qwen_btn.click()1102                            page.wait_for_timeout(500)1103                            log("HuggingChat: selected Qwen3-235B")1104                except Exception:1105                    pass1106                1107                # Type the message1108                log(f"HuggingChat: typing message ({len(user_msg)} chars)...")1109                typed = False1110                for selector in ["textarea", "div[contenteditable='true']"]:1111                    try:1112                        el = page.query_selector(selector)1113                        if el and el.is_visible():1114                            el.click()1115                            page.wait_for_timeout(200)1116                            el.fill(user_msg[:3000])1117                            typed = True1118                            break1119                    except Exception:1120                        continue1121                1122                if not typed:1123                    try:1124                        page.click("textarea", timeout=5000)1125                        page.keyboard.type(user_msg[:3000], delay=10)1126                        typed = True1127                    except Exception:1128                        pass1129                1130                if not typed:1131                    context.close()1132                    browser.close()1133                    return "HuggingChat: could not find input field", "HuggingChat (error)"1134                1135                # Submit1136                page.wait_for_timeout(500)1137                page.keyboard.press("Enter")1138                1139                # Wait for response1140                log("HuggingChat: waiting for response...")1141                page.wait_for_timeout(25000)1142                1143                # Extract response1144                response = ""1145                for sel in ["div[class*='message']:last-child",1146                           "div[class*='response']:last-child",1147                           "div[class*='assistant']:last-child",1148                           "div[class*='markdown']:last-child",1149                           "div[class*='prose']:last-child"]:1150                    try:1151                        elements = page.query_selector_all(sel)1152                        if elements:1153                            text = elements[-1].inner_text()1154                            if text and len(text) > 20 and text != user_msg:1155                                response = text1156                                break1157                    except Exception:1158                        continue1159                1160                if not response or len(response) < 20:1161                    try:1162                        body = page.inner_text("body")1163                        if user_msg[:100] in body:1164                            parts = body.split(user_msg[:100])1165                            if len(parts) > 1:1166                                response = parts[-1].strip()[:3000]1167                        else:1168                            response = body[-2000:].strip()1169                    except Exception:1170                        pass1171                1172                context.close()1173                browser.close()1174                1175                if response and len(response) > 10:1176                    log(f"HuggingChat: got response ({len(response)} chars)")1177                    return response[:4000], "HuggingChat-Qwen3-235B (free, anonymous)"1178                return "HuggingChat: no response received", "HuggingChat (no response)"1179                1180        except Exception as e:1181            return f"HuggingChat error: {e}", "HuggingChat (error)"1182 1183 1184class OpenGradientProvider(LLMProvider):1185    """OpenGradient Chat — FREE, ANONYMOUS, NO LOGIN REQUIRED.1186    1187    Uses chat.opengradient.ai which provides anonymous access to top models:1188    - Uncensored (Hermes 4 405B) — natively uncensored!1189    - GPT-5.5 — has built-in search1190    - Claude Opus 4.8 — has built-in search1191    - Grok 4.3 — has built-in search mode1192    - DeepSeek V4 Pro — powerful reasoning1193    - GLM 5.2 — has built-in search1194    - Gemini 2.5 Pro — has built-in search1195    - Gemini1196    - Qwen1197    1198    Uses Playwright browser automation. Guest session (no login/credentials needed).1199    The site uses GuestSessionProvider — fully anonymous.1200    """

Showing the first 1,200 of 10489 lines. Download the file for the rest.