"""Environment bootstrap (``.env``) and cache path helpers.""" from __future__ import annotations import os from pathlib import Path from typing import Literal _BOOTSTRAPPED = False LLMProvider = Literal["google", "openrouter"] OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1" def bootstrap_env() -> None: """Load ``.env`` from the process cwd (non-fatal if missing). Safe to call twice.""" global _BOOTSTRAPPED if _BOOTSTRAPPED: return try: from dotenv import load_dotenv load_dotenv() except ImportError: pass _BOOTSTRAPPED = True def default_humeo_cache_root() -> Path: """Default cache root: ``~/.cache/humeo`` on Unix; ``%LOCALAPPDATA%/humeo`` on Windows.""" override = (os.environ.get("HUMEO_CACHE_ROOT") or "").strip() if override: return Path(override) if os.name == "nt": base = Path(os.environ.get("LOCALAPPDATA", str(Path.home() / "AppData" / "Local"))) return base / "humeo" return Path.home() / ".cache" / "humeo" def resolve_gemini_api_key() -> str: """Return an API key for Gemini, or raise if none is configured. Prefer ``GOOGLE_API_KEY``; fall back to ``GEMINI_API_KEY``. Values are read from the environment after ``bootstrap_env()`` (``.env`` in cwd). We require an explicit key so we do not fall back to Application Default Credentials (e.g. ``gcloud auth application-default login``), which often lack the Generative Language API scope and produce ``403 ACCESS_TOKEN_SCOPE_INSUFFICIENT``. """ bootstrap_env() for env_name in ("GOOGLE_API_KEY", "GEMINI_API_KEY"): val = (os.environ.get(env_name) or "").strip() if val: return val raise ValueError( "Set GOOGLE_API_KEY or GEMINI_API_KEY for Gemini clip selection. " "See docs/ENVIRONMENT.md. Without an API key the client may use ADC and fail " "with insufficient scopes (403)." ) def resolve_openrouter_api_key() -> str: """Return the OpenRouter API key, or raise if missing.""" return resolve_openrouter_api_keys()[0] def resolve_openrouter_api_keys() -> list[str]: """Return OpenRouter API keys in failover order, or raise if missing. ``OPENROUTER_API_KEY`` remains the primary key. Optional backups can be supplied with ``OPENROUTER_API_KEY_BACKUP``, ``OPENROUTER_API_KEY_2``, or a comma/newline-separated ``OPENROUTER_API_KEYS`` value. """ bootstrap_env() keys: list[str] = [] for env_name in ( "OPENROUTER_API_KEY", "OPENROUTER_API_KEY_BACKUP", "OPENROUTER_API_KEY_2", ): val = (os.environ.get(env_name) or "").strip() if val: keys.append(val) extra = (os.environ.get("OPENROUTER_API_KEYS") or "").strip() if extra: for item in extra.replace("\n", ",").split(","): val = item.strip() if val: keys.append(val) deduped = list(dict.fromkeys(keys)) if deduped: return deduped raise ValueError( "Set OPENROUTER_API_KEY to use OpenRouter as the backend for the Gemini stages. " "See docs/ENVIRONMENT.md." ) def current_llm_provider() -> LLMProvider | None: """Best-effort active backend detection from the environment. ``HUMEO_LLM_PROVIDER`` overrides key-based auto-detection when set. """ bootstrap_env() forced = (os.environ.get("HUMEO_LLM_PROVIDER") or "auto").strip().lower() if forced in ("google", "openrouter"): return forced # type: ignore[return-value] if (os.environ.get("GOOGLE_API_KEY") or "").strip(): return "google" if (os.environ.get("GEMINI_API_KEY") or "").strip(): return "google" if any( (os.environ.get(name) or "").strip() for name in ( "OPENROUTER_API_KEY", "OPENROUTER_API_KEY_BACKUP", "OPENROUTER_API_KEY_2", "OPENROUTER_API_KEYS", ) ): return "openrouter" return None def resolve_llm_provider() -> LLMProvider: """Return the active backend for Gemini-like stages, or raise if none is configured.""" provider = current_llm_provider() if provider is not None: if provider == "google": resolve_gemini_api_key() else: resolve_openrouter_api_key() return provider raise ValueError( "Set GOOGLE_API_KEY or GEMINI_API_KEY for the Google Gemini SDK, " "or set OPENROUTER_API_KEY to route these stages through OpenRouter. " "You can also force the backend with HUMEO_LLM_PROVIDER=google|openrouter." ) def model_name_for_provider(model_name: str, provider: LLMProvider) -> str: """Normalize model identifiers between Google Gemini SDK and OpenRouter. - Google SDK expects bare Gemini ids like ``gemini-3.1-flash-lite-preview``. - OpenRouter expects provider-qualified ids like ``google/gemini-3.1-flash-lite-preview``. """ name = model_name.strip() if provider == "openrouter": if "/" not in name and name.startswith(("gemini-", "gemma-")): return f"google/{name}" return name if provider == "google" and name.startswith("google/"): return name.split("/", 1)[1] return name def openrouter_default_headers() -> dict[str, str]: """Headers that help identify Humeo traffic to OpenRouter.""" return { "HTTP-Referer": "https://github.com/frenzy2004/shortform", "X-OpenRouter-Title": "Humeo", }