Spaces:
Running
Running
| import threading | |
| from pathlib import Path | |
| from typing import Union, Iterable, Dict, Any, Optional | |
| from .grounding import GroundingConfig | |
| from .constants import ( | |
| CONFIG_GROUNDING, | |
| CONFIG_SECURITY, | |
| CONFIG_DEV, | |
| CONFIG_MCP, | |
| CONFIG_AGENTS | |
| ) | |
| from openspace.utils.logging import Logger | |
| from .utils import load_json_file, save_json_file as save_json | |
| logger = Logger.get_logger(__name__) | |
| CONFIG_DIR = Path(__file__).parent | |
| # Global configuration singleton | |
| _config: GroundingConfig | None = None | |
| _config_lock = threading.RLock() # Use RLock to support recursive locking | |
| def _deep_merge_dict(base: dict, update: dict) -> dict: | |
| """Deep merge two dictionaries, update's values will override base's values""" | |
| result = base.copy() | |
| for key, value in update.items(): | |
| if key in result and isinstance(result[key], dict) and isinstance(value, dict): | |
| result[key] = _deep_merge_dict(result[key], value) | |
| else: | |
| result[key] = value | |
| return result | |
| def _load_json_file(path: Path) -> Dict[str, Any]: | |
| """Load single JSON configuration file. | |
| This function wraps the generic load_json_file and adds global configuration specific error handling and logging. | |
| """ | |
| if not path.exists(): | |
| logger.debug(f"Configuration file does not exist, skipping: {path}") | |
| return {} | |
| try: | |
| data = load_json_file(path) | |
| logger.info(f"Loaded configuration file: {path}") | |
| return data | |
| except Exception as e: | |
| logger.warning(f"Failed to load configuration file {path}: {e}") | |
| return {} | |
| def _load_multiple_files(paths: Iterable[Path]) -> Dict[str, Any]: | |
| """Load configuration from multiple files""" | |
| merged = {} | |
| for path in paths: | |
| data = _load_json_file(path) | |
| if data: | |
| merged = _deep_merge_dict(merged, data) | |
| return merged | |
| def load_config(*config_paths: Union[str, Path]) -> GroundingConfig: | |
| """ | |
| Load configuration files | |
| """ | |
| global _config | |
| with _config_lock: | |
| if config_paths: | |
| paths = [Path(p) for p in config_paths] | |
| else: | |
| paths = [ | |
| CONFIG_DIR / CONFIG_GROUNDING, | |
| CONFIG_DIR / CONFIG_SECURITY, | |
| CONFIG_DIR / CONFIG_DEV, # Optional: development environment configuration | |
| ] | |
| # Load and merge configuration | |
| raw_data = _load_multiple_files(paths) | |
| # Load MCP configuration (separate processing) | |
| # Check if mcpServers already provided in merged custom configs | |
| has_custom_mcp_servers = "mcpServers" in raw_data | |
| if has_custom_mcp_servers: | |
| # Use mcpServers from custom config | |
| if "mcp" not in raw_data: | |
| raw_data["mcp"] = {} | |
| raw_data["mcp"]["servers"] = raw_data.pop("mcpServers") | |
| logger.debug(f"Using custom MCP servers from provided config ({len(raw_data['mcp']['servers'])} servers)") | |
| else: | |
| # Load default MCP servers from config_mcp.json | |
| mcp_data = _load_json_file(CONFIG_DIR / CONFIG_MCP) | |
| if mcp_data and "mcpServers" in mcp_data: | |
| if "mcp" not in raw_data: | |
| raw_data["mcp"] = {} | |
| raw_data["mcp"]["servers"] = mcp_data["mcpServers"] | |
| logger.debug(f"Loaded MCP servers from default config_mcp.json ({len(raw_data['mcp']['servers'])} servers)") | |
| # Validate and create configuration object | |
| try: | |
| _config = GroundingConfig.model_validate(raw_data) | |
| except Exception as e: | |
| logger.error(f"Validation failed, using default configuration: {e}") | |
| _config = GroundingConfig() | |
| # Adjust log level according to configuration | |
| if _config.debug: | |
| Logger.set_debug(2) | |
| elif _config.log_level: | |
| try: | |
| Logger.configure(level=_config.log_level) | |
| except Exception as e: | |
| logger.warning(f"Failed to set log level {_config.log_level}: {e}") | |
| return _config | |
| def get_config() -> GroundingConfig: | |
| """ | |
| Get global configuration instance. | |
| Usage: | |
| - Get configuration in Provider: get_config().get_backend_config('shell') | |
| - Get security policy in Tool: get_config().get_security_policy('shell') | |
| """ | |
| global _config | |
| if _config is None: | |
| with _config_lock: | |
| if _config is None: | |
| load_config() | |
| return _config | |
| def reset_config() -> None: | |
| """Reset configuration (for testing)""" | |
| global _config | |
| with _config_lock: | |
| _config = None | |
| def save_config(config: GroundingConfig, path: Union[str, Path]) -> None: | |
| save_json(config.model_dump(), path) | |
| logger.info(f"Configuration saved to: {path}") | |
| def load_agents_config() -> Dict[str, Any]: | |
| agents_config_path = CONFIG_DIR / CONFIG_AGENTS | |
| return _load_json_file(agents_config_path) | |
| def get_agent_config(agent_name: str) -> Optional[Dict[str, Any]]: | |
| """ | |
| Get the configuration of the specified agent | |
| """ | |
| agents_config = load_agents_config() | |
| if "agents" not in agents_config: | |
| logger.warning(f"No 'agents' key found in {CONFIG_AGENTS}") | |
| return None | |
| for agent_cfg in agents_config.get("agents", []): | |
| if agent_cfg.get("name") == agent_name: | |
| return agent_cfg | |
| logger.warning(f"Agent '{agent_name}' not found in {CONFIG_AGENTS}") | |
| return None | |
| __all__ = [ | |
| "CONFIG_DIR", | |
| "load_config", | |
| "get_config", | |
| "reset_config", | |
| "save_config", | |
| "load_agents_config", | |
| "get_agent_config" | |
| ] |