#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Environment config helpers with light validation and normalization.""" import os import warnings from dataclasses import dataclass from typing import Optional FALSEY_ENV_VALUES = {"0", "false", "no", "off"} PROVIDER_ALIASES = { "openai_compatible": "OPENAI", "openai_compat": "OPENAI", "claude": "ANTHROPIC", "google": "GEMINI", "vertex": "GEMINI", "vertexai": "GEMINI", } # Default dev CORS origins (localhost variants used by common dev servers) _LOCALHOST_ORIGINS = [ "http://localhost:5173", "http://localhost:3000", "http://localhost:8000", "http://127.0.0.1:5173", "http://127.0.0.1:3000", "http://127.0.0.1:8000", ] def get_cors_origins() -> list[str]: """Get CORS allowed origins from environment. Reads CORS_ALLOWED_ORIGINS env var (comma-separated). Falls back to localhost dev origins if not set. Warns if "*" is configured (only acceptable for local dev). """ origins = get_env_list("CORS_ALLOWED_ORIGINS", default=[]) if origins: if "*" in origins: warnings.warn( "CORS_ALLOWED_ORIGINS contains '*' — this allows any origin. " "Only use in local development, never in production.", UserWarning, ) return origins # Fallback: local dev only return _LOCALHOST_ORIGINS @dataclass(frozen=True) class AgentModelConfig: """Resolved model config for one agent.""" model_name: str provider: str def _get_env_raw(key: str) -> Optional[str]: value = os.getenv(key) if value is None: return None value = value.strip() return value or None def get_env_str(key: str, default: str = "") -> str: """Get trimmed string from env.""" value = _get_env_raw(key) return value if value is not None else default def get_env_list(key: str, default: list = None) -> list: """Get comma-separated list from env.""" value = _get_env_raw(key) if not value: return default or [] return [item.strip() for item in value.split(",") if item.strip()] def get_env_float(key: str, default: float = 0.0) -> float: """Get float from env.""" value = _get_env_raw(key) if value is None: return default try: return float(value) except ValueError: return default def get_env_int(key: str, default: int = 0) -> int: """Get int from env.""" value = _get_env_raw(key) if value is None: return default try: return int(value) except ValueError: return default def get_env_bool(key: str, default: bool = False) -> bool: """Parse common truthy/falsey env values.""" value = _get_env_raw(key) if value is None: return default return value.lower() not in FALSEY_ENV_VALUES def canonicalize_model_provider(provider: Optional[str]) -> str: """Normalize provider labels to stable uppercase names.""" if not provider: return "OPENAI" normalized = provider.strip().lower().replace("-", "_") normalized = PROVIDER_ALIASES.get(normalized, normalized) return normalized.upper() def get_agent_model_config(agent_id: str) -> AgentModelConfig: """Resolve model config with agent-specific override and global fallback.""" agent_key = agent_id.upper().replace("-", "_") model_name = get_env_str(f"AGENT_{agent_key}_MODEL_NAME") provider = get_env_str(f"AGENT_{agent_key}_MODEL_PROVIDER") if not model_name: model_name = get_env_str("MODEL_NAME", "gpt-4o") if not provider: provider = get_env_str("MODEL_PROVIDER", "OPENAI") return AgentModelConfig( model_name=model_name, provider=canonicalize_model_provider(provider), )