from __future__ import annotations import os from dataclasses import dataclass from pathlib import Path ROOT_DIR = Path(__file__).resolve().parents[1] def _load_env_file(path: Path) -> dict[str, str]: values: dict[str, str] = {} if not path.exists(): return values for raw_line in path.read_text(encoding="utf-8").splitlines(): line = raw_line.strip() if not line or line.startswith("#") or "=" not in line: continue key, value = line.split("=", 1) key = key.strip() value = value.strip().strip('"').strip("'") if key: values[key] = value return values def _get_env(name: str, default: str, file_values: dict[str, str]) -> str: return os.environ.get(name, file_values.get(name, default)) def _as_float(value: str, default: float) -> float: try: return float(value) except (TypeError, ValueError): return default def _as_int(value: str, default: int) -> int: try: return int(value) except (TypeError, ValueError): return default def _as_bool(value: str, default: bool) -> bool: normalized = str(value).strip().lower() if normalized in {"1", "true", "yes", "on"}: return True if normalized in {"0", "false", "no", "off"}: return False return default def _as_int_list(value: str, default: list[int]) -> list[int]: ports: list[int] = [] for part in str(value).replace(";", ",").split(","): item = part.strip() if not item: continue try: port = int(item) except ValueError: continue if 1 <= port <= 65535: ports.append(port) return ports or default def _resolve_path(value: str) -> Path: path = Path(value) if path.is_absolute(): return path return ROOT_DIR / path @dataclass(frozen=True) class AgentConfig: root_dir: Path controller_url: str controller_discovery_enabled: bool controller_discovery_ports: list[int] controller_discovery_timeout_seconds: float state_path: Path model_config_path: Path runtime_state_path: Path memory_path: Path display_name: str deployment_type: str heartbeat_interval_seconds: float task_poll_interval_seconds: float controller_retry_interval_seconds: float request_timeout_seconds: float local_service_enabled: bool local_service_host: str local_service_port: int runtime_command: str @classmethod def from_env(cls) -> "AgentConfig": file_values = _load_env_file(ROOT_DIR / ".env") return cls( root_dir=ROOT_DIR, controller_url=_get_env("CONTROLLER_URL", "auto", file_values).rstrip("/"), controller_discovery_enabled=_as_bool( _get_env("AGENT_CONTROLLER_DISCOVERY_ENABLED", "true", file_values), True, ), controller_discovery_ports=_as_int_list( _get_env("AGENT_CONTROLLER_DISCOVERY_PORTS", "8001,8000", file_values), [8001, 8000], ), controller_discovery_timeout_seconds=_as_float( _get_env("AGENT_CONTROLLER_DISCOVERY_TIMEOUT_SECONDS", "0.35", file_values), 0.35, ), state_path=_resolve_path(_get_env("AGENT_STATE_PATH", "data/agent_state.json", file_values)), model_config_path=_resolve_path( _get_env("AGENT_MODEL_CONFIG_PATH", "data/model_config.json", file_values) ), runtime_state_path=_resolve_path( _get_env("AGENT_RUNTIME_STATE_PATH", "data/runtime_state.json", file_values) ), memory_path=_resolve_path( _get_env("AGENT_MEMORY_PATH", "data/agent_memory.json", file_values) ), display_name=_get_env("AGENT_DISPLAY_NAME", "", file_values), deployment_type=_get_env("AGENT_DEPLOYMENT_TYPE", "auto", file_values), heartbeat_interval_seconds=_as_float( _get_env("AGENT_HEARTBEAT_INTERVAL_SECONDS", "1", file_values), 1.0, ), task_poll_interval_seconds=_as_float( _get_env("AGENT_TASK_POLL_INTERVAL_SECONDS", "5", file_values), 5.0, ), controller_retry_interval_seconds=_as_float( _get_env("AGENT_CONTROLLER_RETRY_INTERVAL_SECONDS", "3", file_values), 3.0, ), request_timeout_seconds=_as_float( _get_env("AGENT_REQUEST_TIMEOUT_SECONDS", "5", file_values), 5.0, ), local_service_enabled=_as_bool( _get_env("AGENT_LOCAL_SERVICE_ENABLED", "true", file_values), True, ), local_service_host=_get_env( "AGENT_LOCAL_SERVICE_HOST", _get_env("LOG_WEB_HOST", "0.0.0.0", file_values), file_values, ), local_service_port=_as_int( _get_env( "AGENT_LOCAL_SERVICE_PORT", _get_env("LOG_WEB_PORT", "18081", file_values), file_values, ), 18081, ), runtime_command=_get_env("AGENT_RUNTIME_COMMAND", "", file_values), )