"""Configuration management.""" import json import os from typing import Any, Callable DEFAULT_CONFIG = { "port": 7860, "host": "0.0.0.0", "retry_attempts": 3, "retry_delay_sec": 2, "request_timeout_sec": 180, "gemini_bl": "boq_assistant-bard-web-server_20260525.09_p0", "auth_user": None, "xsrf_token": None, "default_model": "gemini-3.5-flash", "log_requests": True, "cookie_file": None, "proxy": None, "api_keys": [], } CONFIG = dict(DEFAULT_CONFIG) def _env_value(*names: str) -> str | None: """Return the first non-empty environment variable value.""" for name in names: value = os.environ.get(name) if value is not None and value != "": return value return None def _to_bool(value: str) -> bool: return value.strip().lower() in {"1", "true", "yes", "y", "on"} def _to_int(value: str) -> int: return int(value.strip()) def _to_nullable_str(value: str) -> str | None: value = value.strip() if value.lower() in {"", "none", "null"}: return None return value def _to_list(value: str) -> list[str]: return [item.strip() for item in value.split(",") if item.strip()] def load_config(path: str = None): """Load config from JSON file.""" if path and os.path.exists(path): with open(path, encoding="utf-8") as f: CONFIG.update(json.load(f)) return CONFIG def apply_env_overrides(): """Apply environment variables with highest priority. Supported variables are documented in .env.example. Short names such as PORT are accepted first, and GEMINI_WEB2API_* aliases are also supported. """ scalar_mappings: list[tuple[str, Callable[[str], Any], tuple[str, ...]]] = [ ("port", _to_int, ("PORT", "GEMINI_WEB2API_PORT")), ("host", str, ("HOST", "GEMINI_WEB2API_HOST")), ("retry_attempts", _to_int, ("RETRY_ATTEMPTS", "GEMINI_WEB2API_RETRY_ATTEMPTS")), ("retry_delay_sec", _to_int, ("RETRY_DELAY_SEC", "GEMINI_WEB2API_RETRY_DELAY_SEC")), ("request_timeout_sec", _to_int, ("REQUEST_TIMEOUT_SEC", "GEMINI_WEB2API_REQUEST_TIMEOUT_SEC")), ("gemini_bl", str, ("GEMINI_BL", "GEMINI_WEB2API_GEMINI_BL")), ("auth_user", _to_nullable_str, ("AUTH_USER", "GEMINI_AUTH_USER", "GEMINI_WEB2API_AUTH_USER")), ("xsrf_token", _to_nullable_str, ("XSRF_TOKEN", "GEMINI_XSRF_TOKEN", "GEMINI_WEB2API_XSRF_TOKEN")), ("default_model", str, ("DEFAULT_MODEL", "GEMINI_DEFAULT_MODEL", "GEMINI_WEB2API_DEFAULT_MODEL")), ("log_requests", _to_bool, ("LOG_REQUESTS", "GEMINI_WEB2API_LOG_REQUESTS")), ("cookie_file", _to_nullable_str, ("COOKIE_FILE", "GEMINI_COOKIE_FILE", "GEMINI_WEB2API_COOKIE_FILE")), ("proxy", _to_nullable_str, ("PROXY", "HTTP_PROXY_URL", "GEMINI_WEB2API_PROXY")), ] for key, caster, names in scalar_mappings: value = _env_value(*names) if value is None: continue CONFIG[key] = caster(value) # API_KEY is a convenience single-key value and intentionally wins over API_KEYS. api_key = _env_value("API_KEY", "GEMINI_API_KEY", "GEMINI_WEB2API_API_KEY") if api_key is not None: CONFIG["api_keys"] = [api_key.strip()] if api_key.strip() else [] return CONFIG api_keys = _env_value("API_KEYS", "GEMINI_API_KEYS", "GEMINI_WEB2API_API_KEYS") if api_keys is not None: CONFIG["api_keys"] = _to_list(api_keys) return CONFIG def find_config(): """Search for config file in standard locations.""" for p in ["./config.json", os.path.expanduser("~/.config/gemini-web2api/config.json")]: if os.path.exists(p): return p return None