"""Runtime configuration from environment and arguments.""" import os import re from tai_pkg.consts import Consts __all__ = ["Config"] class Config: def __init__(self, args): self.home = os.environ.get("TAI_HOME") or os.path.join(os.path.expanduser("~"), ".tai") self.profiles_dir = os.path.join(self.home, "profiles") self.audio_dir = os.path.join(self.home, "audio") self.db_path = os.path.join(self.home, "memory.db") self.history_path = os.path.join(self.home, "history") self.model = os.environ.get("TAI_MODEL") or Consts.DEFAULT_MODEL self.explicit_model = os.environ.get("TAI_MODEL") or "" self.backends = Config.parse_backends(os.environ.get("TAI_BACKENDS")) self.openrouter_key = os.environ.get("OPENROUTER_API_KEY") or "" self.openrouter_models = Config.split_models(os.environ.get("TAI_OPENROUTER_MODELS")) self.retry_seconds = float(os.environ.get("TAI_RETRY_SECONDS") or 0) if re.match(r"^\d+(\.\d+)?$", (os.environ.get("TAI_RETRY_SECONDS") or "0").strip()) else 0.0 self.opencode_enabled = (os.environ.get("TAI_OPENCODE") or "1").strip().lower() not in ("0", "false", "no", "off") self.opencode_models = Config.split_models(os.environ.get("TAI_OPENCODE_MODELS")) self.devplace_key = os.environ.get("DEVPLACE_API_KEY") or "" self.devplace_base = (os.environ.get("TAI_DEVPLACE_BASE") or Consts.DEVPLACE_BASE).rstrip("/") self.devplace_models = Config.split_models(os.environ.get("TAI_DEVPLACE_MODELS")) self.openrouter_enabled = (os.environ.get("TAI_OPENROUTER") or "1").strip().lower() not in ("0", "false", "no", "off") self.pollinations_enabled = (os.environ.get("TAI_POLLINATIONS") or "1").strip().lower() not in ("0", "false", "no", "off") self.pollinations_models = Config.split_models(os.environ.get("TAI_POLLINATIONS_MODELS")) or list(Consts.POLLINATIONS_MODELS) self.zen_enabled = (os.environ.get("TAI_ZEN") or "").strip().lower() in ("1", "true", "yes", "on") self.zen_models = Config.split_models(os.environ.get("TAI_ZEN_MODELS")) or list(Consts.ZEN_FREE_MODELS) self.voice = os.environ.get("TAI_VOICE") or Consts.DEFAULT_VOICE self.auto_approve = bool(args.yes) self.profile = args.profile or "default" os.makedirs(self.profiles_dir, exist_ok=True) os.makedirs(self.audio_dir, exist_ok=True) @staticmethod def split_models(spec): return [item.strip() for item in (spec or "").replace("\n", ",").split(",") if item.strip()] @staticmethod def parse_backends(spec): found = [] for entry in (spec or "").replace("\n", ";").split(";"): fields = [field.strip() for field in entry.split("|")] if len(fields) < 3: continue label, base, key = fields[0], fields[1].rstrip("/"), fields[2] if key.startswith("$"): key = os.environ.get(key[1:], "") model = fields[3].strip() if len(fields) > 3 else "" profile = fields[4].strip().lower() if len(fields) > 4 else "" if profile not in ("", "opencode"): profile = "" if not label or not base: continue found.append({"label": label, "base": base, "key": key, "model": model or "", "profile": profile}) return found