from __future__ import annotations

# Copyright (c) 2024-2026 xiefujin <490021684@qq.com>
# Licensed under GNU GPLv3, see LICENSE file for full license terms.

# 配置管理
# config.py
"""
配置管理
"""

import os
import shutil
from pathlib import Path
from typing import Dict, Any, Optional
from dataclasses import dataclass, asdict, field
from typing import Dict, Any, Optional, List
import yaml
import platformdirs
from aacode.i18n import t


@dataclass
class ModelConfig:
    """模型配置"""

    name: str = "deepseek-chat"
    api_key: Optional[str] = None
    base_url: Optional[str] = None
    temperature: float = 0.1
    max_tokens: int = 96000
    gateway: str = "openai"  # openai, anthropic
    multimodal: bool = False  # 是否supports multimodal

    def __post_init__(self):
        # 环境变量优先，yaml配置作为后备
        self.api_key = (
            os.getenv("LLM_API_KEY") or os.getenv("OPENAI_API_KEY") or self.api_key
        )
        # 先不设置默认base_url，让_auto_detect_model_config根据模型名称决定
        self.base_url = (
            os.getenv("LLM_API_URL") or os.getenv("OPENAI_BASE_URL") or self.base_url
        )
        self.name = os.getenv("LLM_MODEL_NAME") or self.name or "deepseek-chat"
        self.gateway = os.getenv("LLM_GATEWAY") or self.gateway or "openai"

        # 多模态配置（环境变量优先）
        llm_multimodal = os.getenv("LLM_MULTIMODAL")
        if llm_multimodal is not None:
            # 环境变量可以覆盖自动检测
            self.multimodal = llm_multimodal.lower() in ["true", "1", "yes", "on"]
        else:
            # 根据模型名称自动设置多模态和网关
            self._auto_detect_model_config()

    def _auto_detect_model_config(self):
        """根据模型名称自动检测配置"""
        model_lower = self.name.lower()

        # 检查 user是否通过环境变量显式设置了网关
        user_explicit_gateway = os.getenv("LLM_GATEWAY")

        # Multimodal model检测（只有明确supports multimodal的模型）
        # 重置为默认值，然后根据模型名称检测
        self.multimodal = False  # 默认不是多模态

        # 更灵活的Multimodal model检测，支持多种名称格式
        model_lower_clean = model_lower.replace("-", "_").replace(" ", "_")

        # 检测Kimi模型（支持多种格式）
        kimi_patterns = ["kimi", "moonshot"]
        is_kimi = any(pattern in model_lower_clean for pattern in kimi_patterns)

        # 检测MiniMax模型
        minimax_patterns = ["minimax", "minimaxi"]
        is_minimax = any(pattern in model_lower_clean for pattern in minimax_patterns)

        # 检测DeepSeek模型
        is_deepseek = "deepseek" in model_lower

        if is_kimi or is_minimax:
            self.multimodal = True
            print(f"🔍 Multimodal model detected: {self.name} (format: {model_lower_clean})")

        # 根据 user是否显式设置网关来决定 lines为
        if user_explicit_gateway:
            #  user显式设置了网关，只根据网关类型选择URL，不覆盖网关本身
            gateway = user_explicit_gateway.lower()
            if is_minimax:
                if gateway == "anthropic":
                    if not self.base_url or "openai" in self.base_url:
                        self.base_url = "https://api.minimax.chat/anthropic"
                    elif "minimax" in self.base_url:
                        self.base_url = self._adjust_url_for_anthropic(self.base_url)
                else:  # openai
                    if not self.base_url or "anthropic" in self.base_url:
                        self.base_url = "https://api.minimax.chat/v1"
            elif is_kimi:
                if gateway == "anthropic":
                    if not self.base_url or "openai" in self.base_url:
                        self.base_url = "https://api.moonshot.cn/anthropic"
                    elif "moonshot" in self.base_url:
                        self.base_url = self._adjust_url_for_anthropic(self.base_url)
                else:  # openai
                    if not self.base_url or "anthropic" in self.base_url:
                        self.base_url = "https://api.moonshot.cn/v1"
            elif is_deepseek:
                if gateway == "anthropic":
                    if not self.base_url or "openai" in self.base_url:
                        self.base_url = "https://api.deepseek.com/anthropic"
                    elif "deepseek" in self.base_url:
                        self.base_url = self._adjust_url_for_anthropic(self.base_url)
                else:  # openai
                    if not self.base_url or "anthropic" in self.base_url:
                        self.base_url = "https://api.deepseek.com/v1"
            elif "gpt" in model_lower and gateway == "anthropic":
                # GPT模型使 with Anthropic网关时，指向Anthropic官方API
                if not self.base_url or "openai" in self.base_url:
                    self.base_url = "https://api.anthropic.com"
            elif "claude" in model_lower:
                # Claude模型默认使 with Anthropic网关
                if gateway == "anthropic":
                    if not self.base_url or "openai" in self.base_url:
                        self.base_url = "https://api.anthropic.com"
                else:  # openai
                    if not self.base_url or "anthropic" in self.base_url:
                        self.base_url = "https://api.openai.com/v1"
        else:
            #  user没有显式设置网关，使 with 默认 lines为
            if is_minimax:
                self.gateway = "anthropic"
                if not self.base_url or "openai" in self.base_url:
                    self.base_url = "https://api.minimax.chat/anthropic"
                elif "minimax" in self.base_url:
                    self.base_url = self._adjust_url_for_anthropic(self.base_url)
            elif is_kimi:
                self.gateway = "openai"
                if not self.base_url or "anthropic" in self.base_url:
                    self.base_url = "https://api.moonshot.cn/v1"
            elif is_deepseek:
                self.gateway = "openai"
                if not self.base_url or "anthropic" in self.base_url:
                    self.base_url = "https://api.deepseek.com/v1"
            elif "claude" in model_lower:
                # Claude模型默认使 with Anthropic网关
                self.gateway = "anthropic"
                if not self.base_url or "openai" in self.base_url:
                    self.base_url = "https://api.anthropic.com"
            elif "gpt" in model_lower:
                self.gateway = "openai"
                if (
                    not self.base_url
                    or "minimax" in self.base_url
                    or "moonshot" in self.base_url
                ):
                    self.base_url = "https://api.openai.com/v1"

    def _adjust_url_for_anthropic(self, base_url: str) -> str:
        """调整URL以适配Anthropic SDK"""
        if base_url.endswith("/v1"):
            return base_url[:-3] + "/anthropic"
        elif base_url.endswith("/v1/anthropic"):
            return base_url.replace("/v1/anthropic", "/anthropic")
        elif not base_url.endswith("/anthropic"):
            return base_url.rstrip("/") + "/anthropic"
        return base_url


@dataclass
class ToolConfig:
    """工具配置"""

    # 原子工具
    enable_shell: bool = True
    enable_search: bool = True

    # 沙箱工具
    enable_sandbox: bool = False
    sandbox_type: str = "docker"  # docker, vm, local

    # 网络工具
    enable_web_search: bool = True
    search_engine: str = "searxng"
    search_api_url: Optional[str] = None
    search_api_key: Optional[str] = None
    enable_fallback_scrape: bool = True

    # 代码工具
    enable_code_execution: bool = True
    enable_testing: bool = True
    max_execution_time: int = 60


@dataclass
class SafetyConfig:
    """安全配置"""

    enable_safety_guard: bool = True
    restrict_to_project: bool = False
    allow_network: bool = True
    max_file_size: int = 50 * 1024 * 1024  # 50MB
    dangerous_command_action: str = "ask"  # reject, ask, log — ask 模式下危险命令弹窗确认


@dataclass
class ContextConfig:
    """上下文配置"""

    strategy: str = "file_based"  # file_based, memory, hybrid
    max_context_length: int = 131072
    compact_threshold: int = 12000
    history_compression: bool = True
    use_vector_store: bool = False
    # 新增：上下文缩减配置
    compact_trigger_tokens: int = 256000  # 触发缩减的token数阈值
    compact_keep_rounds: int = 10  # 退化场景：缩减后保留的对话轮数（最近N轮）
    compact_summary_steps: int = 10  # 摘要包含的步骤数
    compact_protect_first_rounds: int = 1  # 保护前N轮（只保留 system prompt，其余由 AI 摘要承载）
    compact_protect_user_rounds: int = 2  # 保护最近N条用户消息及附带消息（确保最新上下文完整）


@dataclass
class AgentConfig:
    """Agent配置"""

    max_react_iterations: int = 50
    max_sub_agent_iterations: int = 30
    enable_auto_planning: bool = True
    enable_todo_tracking: bool = True


@dataclass
class MCPConfig:
    """MCP server配置"""

    enabled: bool = True
    # STD类型MCP server配置
    # 注意：具体配置从aacode_config.yaml文件加载，这里只保留空列表
    std_servers: List[Dict[str, Any]] = field(default_factory=list)
    # SSE类型MCP server配置
    # 注意：具体配置从aacode_config.yaml文件加载，这里只保留空列表
    sse_servers: List[Dict[str, Any]] = field(default_factory=list)
    # 通 with 配置
    auto_connect: bool = True
    connection_timeout: int = 30
    max_retries: int = 1  # 工具调用重试次数。设为1=不重试，失败由模型决策
@dataclass
class OutputConfig:
    """输出处理配置（自适应截断 — 基于剩余上下文预算）"""

    budget_ratio: float = 0.6  # 工具输出最多占用剩余预算的比例
    max_preview_tokens: int = 2000  # 预览最多 token 数
    display_preview_chars: int = 3000  # 用户终端显示预览字符数


@dataclass
class TimeoutConfig:
    """timeout配置"""

    tool_default: int = 300  # 通用工具执行timeout（秒），防挂起兜底
    shell_command: int = 30  # Shell命令executetimeout（秒）
    model_summary: int = 30  # 模型摘要生成timeout（秒）
    file_search: int = 5  # 文件搜索timeout（秒）
    code_execution: int = 60  # 代码executetimeout（秒）
    sandbox_command: int = 120  # 沙箱命令timeout（秒）
    web_request: int = 30  # 网络请求timeout（秒）


@dataclass
class LimitsConfig:
    """限制配置"""

    max_file_list_results: int = 100  # 文件列表最大结果数
    max_search_results: int = 20  # 搜索最大结果数
    max_retries: int = 1  # 工具调用重试次数。设为1=不重试，失败由模型自行决策
    skill_max_result_chars: int = 5000  # skill 返回结果最大字符数，超过则截断并存入 .aacode/extracts/
    shell_output_preview: int = 200  # Shell输出预览长度（字符）
    max_auto_read_lines: int = 200  # 超过此 lines数时提供分段建议
    structure_preview_lines: int = 50  # 结构预览显示的 lines数
    max_context_files: int = 50  # 上下文中显示的最大文件数
    prioritize_file_types: bool = True  # 是否优先显示重要文件类型


@dataclass
class SkillsConfig:
    """Skills配置"""

    enabled: bool = True
    skills_dir: str = "skills"
    auto_discover: bool = True


@dataclass
class MultimodalModelConfig:
    """单个Multimodal model配置"""

    name: str = ""
    provider: str = ""
    base_url: str = ""
    api_key: str = ""
    vision: bool = True
    video: bool = False
    max_image_size: int = 10485760  # 10MB
    max_video_size: int = 104857600  # 100MB
    supported_formats: Dict[str, List[str]] = field(
        default_factory=lambda: {
            "images": [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"],
            "videos": [".mp4", ".avi", ".mov", ".mkv", ".webm"],
        }
    )


@dataclass
class MultimodalConfig:
    """Multimodal model配置"""

    enabled: bool = True  # 是否启 with 多模态功能
    default_model: str = "moonshot_kimi_k2.5"  # 默认使 with 的Multimodal model
    models: Dict[str, Any] = field(
        default_factory=lambda: {
            "moonshot_kimi_k2.5": {
                "name": "kimi-k2.5",
                "provider": "moonshot",
                "gateway": "openai",
                "base_url": "https://api.moonshot.cn/v1",
                "api_key": "",
                "vision": True,
                "video": True,
                "max_image_size": 10485760,
                "max_video_size": 104857600,
                "supported_formats": {
                    "images": [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"],
                    "videos": [".mp4", ".avi", ".mov", ".mkv", ".webm"],
                },
            },
            "minimax_m2.5": {
                "name": "MiniMax-M2.5",
                "provider": "minimax",
                "gateway": "anthropic",
                "base_url": "https://api.minimax.chat/anthropic",
                "api_key": "",
                "vision": True,
                "video": True,
                "max_image_size": 10485760,
                "max_video_size": 104857600,
                "supported_formats": {
                    "images": [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"],
                    "videos": [".mp4", ".avi", ".mov", ".mkv", ".webm"],
                },
            },
        }
    )


class Settings:
    """全局设置"""

    @staticmethod
    def _find_config_file(
        config_file: Optional[str] = None,
    ) -> tuple[Optional[Path], str]:
        """
        多级回退查找配置文件
        返回 (config_path, source) 元组
        source 表示配置来源: "explicit", "cwd", "home", "package", "none"
        """
        if config_file:
            path = Path(config_file)
            if path.exists():
                return path, "explicit"
            return None, "none"

        # 环境变量指定配置文件（客户端开发模式 with ）
        env_config = os.getenv("AACODE_CONFIG_FILE")
        if env_config:
            path = Path(env_config)
            if path.exists():
                return path, "explicit"

        config_dir = Path(platformdirs.user_config_dir("com.aacode", roaming=True))
        candidates = [
            (Path.cwd() / "aacode_config.yaml", "cwd"),
            (config_dir / "aacode_config.yaml", "platformdirs"),
            (Path(__file__).parent / "aacode_config.yaml", "package"),
            (Path.home() / ".aacode" / "aacode_config.yaml", "home"),
        ]

        for path, source in candidates:
            if path.exists():
                return path, source

        return None, "none"

    def __init__(self, config_file: Optional[str] = None):
        self.config_path, self.config_source = self._find_config_file(config_file)
        self._explicit_config_file = config_file

        # 默认配置
        self.model = ModelConfig()
        self.tools = ToolConfig()
        self.safety = SafetyConfig()
        self.context = ContextConfig()
        self.agent = AgentConfig()
        self.mcp = MCPConfig()  # MCP配置
        self.output = OutputConfig()  # 输出配置
        self.timeouts = TimeoutConfig()  # timeout配置
        self.limits = LimitsConfig()  # 限制配置
        self.skills = SkillsConfig()  # Skills配置
        self.multimodal = MultimodalConfig()  # 多模态配置
        self.language: str = "en"    # 语言 en / zh

        # 从文件加载配置
        self.load_config()

        # 从环境变量更新配置（环境变量优先）
        self._load_from_env()

        # initialized i18n
        from aacode.i18n import init as i18n_init
        i18n_init(self.language)

    @staticmethod
    def _deep_merge(base: dict, override: dict) -> dict:
        """深度合并两个字典：override 的值优先，base 中缺失的 key 被补充进去"""
        result = override.copy()
        for key, value in base.items():
            if key not in result:
                result[key] = value
            elif isinstance(value, dict) and isinstance(result[key], dict):
                result[key] = Settings._deep_merge(value, result[key])
        return result

    def _merge_with_defaults(self, user_config: dict) -> dict:
        """将包内默认 YAML 中的缺失字段合并到用户配置中（不覆盖已有值）"""
        pkg_config_path = Path(__file__).parent / "aacode_config.yaml"
        if not pkg_config_path.exists():
            return user_config
        with open(pkg_config_path, "r", encoding="utf-8") as f:
            default_config = yaml.safe_load(f) or {}
        return self._deep_merge(default_config, user_config)

    def load_config(self):
        """从文件加载配置"""
        if not self.config_path or not self.config_path.exists():
            if self._explicit_config_file is None:
                pkg_config = Path(__file__).parent / "aacode_config.yaml"
                if pkg_config.exists():
                    target_dir = Path(platformdirs.user_config_dir("com.aacode", roaming=True))
                    target_dir.mkdir(parents=True, exist_ok=True)
                    target_path = target_dir / "aacode_config.yaml"
                    shutil.copy(pkg_config, target_path)
                    self.config_path = target_path
                    self.config_source = "platformdirs"
                    print(f"Config saved to {target_path}")

        if self.config_path and self.config_path.exists():
            try:
                with open(self.config_path, "r", encoding="utf-8") as f:
                    config_data = yaml.safe_load(f)

                if config_data is None:
                    config_data = {}

                # 对非 package 来源的配置做缺失字段合并（老用户升级兼容）
                if self.config_source not in ("package", "none"):
                    merged = self._merge_with_defaults(config_data)
                    if merged != config_data:
                        config_data = merged
                        try:
                            with open(self.config_path, "w", encoding="utf-8") as f:
                                yaml.dump(config_data, f, default_flow_style=False, allow_unicode=True)
                        except Exception as e:
                            print(t("config.save_error", e=str(e)))

                # 更新配置
                if config_data:
                    self._update_from_dict(config_data)
            except Exception as e:
                print(t("config.load_error", e=str(e)))

    def save_config(self):
        """保存配置到文件"""
        config_data = {
            "language": self.language,
            "model": asdict(self.model),
            "tools": asdict(self.tools),
            "safety": asdict(self.safety),
            "context": asdict(self.context),
            "agent": asdict(self.agent),
            "output": asdict(self.output),
            "timeouts": asdict(self.timeouts),
            "limits": asdict(self.limits),
            "mcp": asdict(self.mcp),
            "skills": asdict(self.skills),
            "multimodal": asdict(self.multimodal),
        }

        try:
            with open(self.config_path, "w", encoding="utf-8") as f:
                yaml.dump(config_data, f, default_flow_style=False, allow_unicode=True)
        except Exception as e:
            print(t("config.save_error", e=str(e)))

    def _load_from_env(self):
        """从环境变量加载配置（环境变量优先）"""
        # 环境变量优先，覆盖yaml配置

        # 模型配置
        llm_api_key = os.getenv("LLM_API_KEY")
        if llm_api_key:
            self.model.api_key = llm_api_key
        llm_api_url = os.getenv("LLM_API_URL")
        if llm_api_url:
            self.model.base_url = llm_api_url
        llm_model_name = os.getenv("LLM_MODEL_NAME")
        if llm_model_name:
            self.model.name = llm_model_name
        llm_gateway = os.getenv("LLM_GATEWAY")
        if llm_gateway:
            self.model.gateway = llm_gateway

        # 多模态配置
        llm_multimodal = os.getenv("LLM_MULTIMODAL")
        if llm_multimodal is not None:
            # 触发ModelConfig的__post_init__重新加载
            self.model.multimodal = llm_multimodal.lower() in ["true", "1", "yes", "on"]

        # Multimodal model选择
        multimodal_model = os.getenv("MULTIMODAL_MODEL")
        if multimodal_model and multimodal_model in self.multimodal.models:
            self.multimodal.default_model = multimodal_model

        # 多模态API密钥
        multimodal_api_key = os.getenv("MULTIMODAL_API_KEY")
        if multimodal_api_key and multimodal_model in self.multimodal.models:
            self.multimodal.models[multimodal_model]["api_key"] = multimodal_api_key

        # 搜索配置
        search_api_url = os.getenv("SEARCHXNG_URL")
        if search_api_url:
            self.tools.search_api_url = search_api_url
            self.tools.enable_web_search = True
        search_api_key = os.getenv("SEARCH_API_KEY") or os.getenv("SEARCHXNG_API_KEY")
        if search_api_key:
            self.tools.search_api_key = search_api_key

        # 语言配置
        lang = os.getenv("AACODE_LANG")
        if lang and lang in ("en", "zh"):
            self.language = lang

    def _update_from_dict(self, config_dict: Dict[str, Any]):
        """从字典更新配置"""
        for section, values in config_dict.items():
            if section == "timeouts":
                # 处理timeouts配置
                if isinstance(values, dict):
                    for key, value in values.items():
                        if hasattr(self.timeouts, key):
                            setattr(self.timeouts, key, value)
            elif section == "limits":
                # 处理limits配置
                if isinstance(values, dict):
                    for key, value in values.items():
                        if hasattr(self.limits, key):
                            setattr(self.limits, key, value)
            elif section == "mcp":
                # 处理MCP配置
                if isinstance(values, dict):
                    for key, value in values.items():
                        if hasattr(self.mcp, key):
                            setattr(self.mcp, key, value)
            elif section == "skills":
                # 处理Skills配置
                if isinstance(values, dict):
                    for key, value in values.items():
                        if hasattr(self.skills, key):
                            setattr(self.skills, key, value)

            if section == "multimodal":
                # 处理多模态配置
                if isinstance(values, dict):
                    for key, value in values.items():
                        if hasattr(self.multimodal, key):
                            setattr(self.multimodal, key, value)
            elif section == "model":
                # 处理model配置（支持列表+扁平两种结构）
                if isinstance(values, dict):
                    # 优先从列表结构读取（models + default_model）
                    if "models" in values and "default_model" in values:
                        default_key = values["default_model"]
                        models = values.get("models", {})
                        if isinstance(models, dict) and default_key in models:
                            model_entry = models[default_key]
                            for k, v in model_entry.items():
                                if hasattr(self.model, k):
                                    setattr(self.model, k, v)
                    # 扁平字段（temperature / max_tokens / 向后兼容）
                    for key, value in values.items():
                        if key in ("models", "default_model"):
                            continue
                        if hasattr(self.model, key):
                            setattr(self.model, key, value)
            elif section == "language":
                self.language = str(values) if values else "en"
            elif hasattr(self, section):
                section_obj = getattr(self, section)
                if hasattr(section_obj, "__dataclass_fields__"):
                    for key, value in values.items():
                        if hasattr(section_obj, key):
                            setattr(section_obj, key, value)

    @property
    def DEFAULT_MODEL(self):
        """Get 默认模型配置"""
        # 环境变量优先，yaml配置作为后备
        return {
            "name": os.getenv("LLM_MODEL_NAME") or self.model.name or "deepseek-chat",
            "api_key": os.getenv("LLM_API_KEY") or self.model.api_key,
            "base_url": os.getenv("LLM_API_URL") or self.model.base_url,
            "temperature": self.model.temperature,
            "max_tokens": self.model.max_tokens,
            "gateway": os.getenv("LLM_GATEWAY") or self.model.gateway or "openai",
            "multimodal": self.model.multimodal,
        }

    @property
    def MAX_REACT_ITERATIONS(self):
        """Get 最大React迭代次数"""
        return self.agent.max_react_iterations

    @property
    def MAX_SUB_AGENT_ITERATIONS(self):
        """Get SubAgent最大迭代次数"""
        return self.agent.max_sub_agent_iterations

    def validate(self) -> List[str]:
        """验证配置，返回错误消息列表"""
        errors = []

        # 检查必需的API密钥
        if (
            not self.model.api_key
            and not os.getenv("LLM_API_KEY")
            and not os.getenv("OPENAI_API_KEY")
        ):
            errors.append(
                "LLM API key not configured. Please set the environment variable LLM_API_KEY or OPENAI_API_KEY, or set model.api_key in the config file"
            )

        # 检查多模态配置（如果启 with 且当前模型是多模态的）
        # 只有当多模态功能启 with 且当前模型是Multimodal model时才检查多模态API密钥
        if self.multimodal.enabled and self.model.multimodal:
            for model_name, model_config in self.multimodal.models.items():
                # 只检查与当前模型相关的多模态配置
                model_lower = self.model.name.lower()
                config_name = model_config.get("name", "").lower()

                # 如果当前模型名称包含多模态配置的名称，则检查该配置
                if config_name in model_lower or model_lower in config_name:
                    if model_config.get("api_key") == "" and not os.getenv(
                        f"{model_config.get('provider', '').upper()}_API_KEY"
                    ):
                        errors.append(
                            f"Multimodal model {model_name} is missing an API key. Please set the environment variable {model_config.get('provider', '').upper()}_API_KEY or set it in the config file"
                        )

        # 检查搜索配置（如果启 with ）
        if (
            self.tools.enable_web_search
            and not self.tools.search_api_url
            and not os.getenv("SEARCHXNG_URL")
        ):
            errors.append(
                "Web search is enabled but the search API URL is not configured. Please set the SEARCHXNG_URL environment variable or set tools.search_api_url in the config file"
            )

        return errors

    def get_validated_config(self) -> Dict[str, Any]:
        """Get 验证后的配置（如果验证失败则抛出异常）"""
        errors = self.validate()
        if errors:
            raise ValueError(
                f"Configuration validation failed:\n" + "\n".join(f"  - {error}" for error in errors)
            )

        return {
            "model": asdict(self.model),
            "tools": asdict(self.tools),
            "safety": asdict(self.safety),
            "context": asdict(self.context),
            "agent": asdict(self.agent),
            "mcp": asdict(self.mcp),
            "output": asdict(self.output),
            "timeouts": asdict(self.timeouts),
            "limits": asdict(self.limits),
            "skills": asdict(self.skills),
            "multimodal": asdict(self.multimodal),
        }


# 全局设置实例
settings = Settings()
