import asyncio
import json
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional, Set

try:
    from config import (
        OUTPUT_FORMATS, DATA_DIR, PROMPTS_DIR, NEED_CONFIRMATION,
        MAX_TERMINALS, TERMINAL_BUFFER_SIZE, TERMINAL_DISPLAY_SIZE,
        MAX_READ_FILE_CHARS, READ_TOOL_DEFAULT_MAX_CHARS,
        READ_TOOL_DEFAULT_CONTEXT_BEFORE, READ_TOOL_DEFAULT_CONTEXT_AFTER,
        READ_TOOL_MAX_CONTEXT_BEFORE, READ_TOOL_MAX_CONTEXT_AFTER,
        READ_TOOL_DEFAULT_MAX_MATCHES, READ_TOOL_MAX_MATCHES,
        READ_TOOL_MAX_FILE_SIZE,
        TERMINAL_SANDBOX_MOUNT_PATH,
        TERMINAL_SANDBOX_MODE,
        TERMINAL_SANDBOX_CPUS,
        TERMINAL_SANDBOX_MEMORY,
        PROJECT_MAX_STORAGE_MB,
        CUSTOM_TOOLS_ENABLED,
    )
except ImportError:
    import sys
    project_root = Path(__file__).resolve().parents[2]
    if str(project_root) not in sys.path:
        sys.path.insert(0, str(project_root))
    from config import (
        OUTPUT_FORMATS, DATA_DIR, PROMPTS_DIR, NEED_CONFIRMATION,
        MAX_TERMINALS, TERMINAL_BUFFER_SIZE, TERMINAL_DISPLAY_SIZE,
        MAX_READ_FILE_CHARS, READ_TOOL_DEFAULT_MAX_CHARS,
        READ_TOOL_DEFAULT_CONTEXT_BEFORE, READ_TOOL_DEFAULT_CONTEXT_AFTER,
        READ_TOOL_MAX_CONTEXT_BEFORE, READ_TOOL_MAX_CONTEXT_AFTER,
        READ_TOOL_DEFAULT_MAX_MATCHES, READ_TOOL_MAX_MATCHES,
        READ_TOOL_MAX_FILE_SIZE,
        TERMINAL_SANDBOX_MOUNT_PATH,
        TERMINAL_SANDBOX_MODE,
        TERMINAL_SANDBOX_CPUS,
        TERMINAL_SANDBOX_MEMORY,
        PROJECT_MAX_STORAGE_MB,
        CUSTOM_TOOLS_ENABLED,
    )

from modules.file_manager import FileManager
from modules.search_engine import SearchEngine
from modules.terminal_ops import TerminalOperator
from modules.memory_manager import MemoryManager
from modules.terminal_manager import TerminalManager
from modules.todo_manager import TodoManager
from modules.sub_agent_manager import SubAgentManager
from modules.webpage_extractor import extract_webpage_content, tavily_extract
from modules.ocr_client import OCRClient
from modules.easter_egg_manager import EasterEggManager
from modules.personalization_manager import (
    load_personalization_config,
    build_personalization_prompt,
)
from modules.skills_manager import (
    get_skills_catalog,
    build_skills_list,
    merge_enabled_skills,
    build_skills_prompt,
)
from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_category
from modules.custom_tool_executor import CustomToolExecutor
from modules.mcp_server_registry import MCPServerRegistry, build_default_mcp_category

try:
    from config.limits import THINKING_FAST_INTERVAL
except ImportError:
    THINKING_FAST_INTERVAL = 10

from modules.container_monitor import collect_stats, inspect_state
from core.tool_config import TOOL_CATEGORIES
from utils.api_client import DeepSeekClient
from utils.context_manager import ContextManager
from utils.tool_result_formatter import format_tool_result_for_context
from utils.logger import setup_logger
from config.model_profiles import (
    get_model_profile,
    get_model_prompt_replacements,
    get_model_context_window,
    model_supports_image,
    model_supports_video,
)

logger = setup_logger(__name__)
DISABLE_LENGTH_CHECK = True

class MainTerminalToolsDefinitionMixin:
    @staticmethod
    def _mcp_disabled_message() -> str:
                return "当前为docker模式，MCP仅支持宿主机模式"

    def _is_mcp_disabled_in_docker_mode(self) -> bool:
                session = getattr(self, "container_session", None)
                return bool(session and getattr(session, "mode", None) == "docker")

    def _inject_intent(self, properties: Dict[str, Any]) -> Dict[str, Any]:
                """在工具参数中注入 intent（简短意图说明），仅当开关启用时。

                字段含义：要求模型用不超过15个中文字符对即将执行的动作做简要说明，供前端展示。
                """
                if not self.tool_intent_enabled:
                    return properties
                if not isinstance(properties, dict):
                    return properties
                intent_field = {
                    "intent": {
                        "type": "string",
                        "description": "用不超过15个字向用户说明你要做什么，例如：等待下载完成/创建日志文件"
                    }
                }
                # 将 intent 放在最前面以提高模型关注度
                return {**intent_field, **properties}

    def _apply_intent_to_tools(self, tools: List[Dict]) -> List[Dict]:
                """遍历工具列表，为缺少 intent 的工具补充字段（开关启用时生效）。"""
                if not self.tool_intent_enabled:
                    return tools
                intent_field = {
                    "intent": {
                        "type": "string",
                        "description": "用不超过15个字向用户说明你要做什么，例如：等待下载完成/创建日志文件/搜索最新新闻"
                    }
                }
                for tool in tools:
                    func = tool.get("function") or {}
                    tool_name = str(func.get("name") or "").strip()
                    # MCP 扩展工具参数需与远端 schema 严格一致，不注入 intent
                    if tool_name.startswith("mcp__"):
                        continue
                    params = func.get("parameters") or {}
                    if not isinstance(params, dict):
                        continue
                    if params.get("type") != "object":
                        continue
                    props = params.get("properties")
                    if not isinstance(props, dict):
                        continue
                    # 补充 intent 属性
                    if "intent" not in props:
                        params["properties"] = {**intent_field, **props}
                    # 将 intent 加入必填
                    required_list = params.get("required")
                    if isinstance(required_list, list):
                        if "intent" not in required_list:
                            required_list.insert(0, "intent")
                            params["required"] = required_list
                    else:
                        params["required"] = ["intent"]
                return tools

    def _build_custom_tools(self) -> List[Dict]:
                if not (self.custom_tools_enabled and getattr(self, "user_role", "user") == "admin"):
                    return []
                try:
                    definitions = self.custom_tool_registry.reload()
                except Exception:
                    definitions = self.custom_tool_registry.list_tools()
                if not definitions:
                    # 更新分类为空列表，避免旧缓存
                    if "custom" in self.tool_categories_map:
                        self.tool_categories_map["custom"].tools = []
                    return []

                tools: List[Dict] = []
                tool_ids: List[str] = []
                for item in definitions:
                    tool_id = item.get("id")
                    if not tool_id:
                        continue
                    if item.get("invalid_id"):
                        # 跳过不合法的工具 ID，避免供应商严格校验时报错
                        continue
                    tool_ids.append(tool_id)
                    params = item.get("parameters") or {"type": "object", "properties": {}}
                    if isinstance(params, dict) and params.get("type") != "object":
                        params = {"type": "object", "properties": {}}
                    required = item.get("required")
                    if isinstance(required, list):
                        params = dict(params)
                        params["required"] = required

                    tools.append({
                        "type": "function",
                        "function": {
                            "name": tool_id,
                            "description": item.get("description") or f"自定义工具: {tool_id}",
                            "parameters": params
                        }
                    })

                # 覆盖 custom 分类的工具列表
                if "custom" in self.tool_categories_map:
                    self.tool_categories_map["custom"].tools = tool_ids

                return tools

    def _build_mcp_tools(self) -> List[Dict]:
                if not getattr(self, "mcp_tools_enabled", False):
                    if hasattr(self, "tool_categories_map") and "mcp" in self.tool_categories_map:
                        self.tool_categories_map["mcp"].tools = ["list_mcp_servers"]
                    stale_keys = [
                        key for key in list(getattr(self, "tool_categories_map", {}).keys())
                        if isinstance(key, str) and key.startswith("mcp_server__")
                    ]
                    for key in stale_keys:
                        self.tool_categories_map.pop(key, None)
                        self.tool_category_states.pop(key, None)
                    self._refresh_disabled_tools()
                    self.mcp_tool_alias_map = {}
                    return []
                if self._is_mcp_disabled_in_docker_mode():
                    self.mcp_tool_alias_map = {}
                    if "mcp" in self.tool_categories_map:
                        self.tool_categories_map["mcp"].tools = ["list_mcp_servers"]
                    elif getattr(self, "mcp_tools_enabled", False):
                        default_mcp_cat = build_default_mcp_category()
                        self.tool_categories_map["mcp"] = type(next(iter(TOOL_CATEGORIES.values())))(
                            label=default_mcp_cat["label"],
                            tools=["list_mcp_servers"],
                            default_enabled=True,
                            silent_when_disabled=False,
                        )
                    stale_keys = [
                        key for key in list(getattr(self, "tool_categories_map", {}).keys())
                        if isinstance(key, str) and key.startswith("mcp_server__")
                    ]
                    for key in stale_keys:
                        self.tool_categories_map.pop(key, None)
                        self.tool_category_states.pop(key, None)
                    self._refresh_disabled_tools()
                    return []
                # 类别层面的禁用优先生效（即使策略里尚未填充具体工具列表）
                mcp_cat = getattr(self, "tool_categories_map", {}).get("mcp")
                if mcp_cat is not None:
                    mcp_enabled = self.tool_category_states.get("mcp", getattr(mcp_cat, "default_enabled", True))
                    forced = getattr(self, "admin_forced_category_states", {}).get("mcp")
                    if isinstance(forced, bool):
                        mcp_enabled = forced
                    if not mcp_enabled:
                        self.mcp_tool_alias_map = {}
                        self.tool_categories_map["mcp"].tools = ["list_mcp_servers"]
                        stale_keys = [
                            key for key in list(getattr(self, "tool_categories_map", {}).keys())
                            if isinstance(key, str) and key.startswith("mcp_server__")
                        ]
                        for key in stale_keys:
                            self.tool_categories_map.pop(key, None)
                            self.tool_category_states.pop(key, None)
                        self._refresh_disabled_tools()
                        return []
                manager = getattr(self, "mcp_client_manager", None)
                if manager is None:
                    self.mcp_tool_alias_map = {}
                    return []
                try:
                    tool_defs, alias_map = manager.build_openai_tools(
                        ensure_discovery=True,
                        discovery_timeout_seconds=10,
                    )
                except Exception as exc:
                    logger.warning("构建 MCP 工具列表失败: %s", exc)
                    tool_defs, alias_map = [], {}

                # 保存映射，供执行层按 alias 分发到具体 server/tool
                alias_map = alias_map or {}
                if "mcp" in self.tool_categories_map:
                    self.tool_categories_map["mcp"].tools = ["list_mcp_servers"]
                elif getattr(self, "mcp_tools_enabled", False):
                    default_mcp_cat = build_default_mcp_category()
                    self.tool_categories_map["mcp"] = type(next(iter(TOOL_CATEGORIES.values())))(
                        label=default_mcp_cat["label"],
                        tools=["list_mcp_servers"],
                        default_enabled=True,
                        silent_when_disabled=False,
                    )
                server_aliases: Dict[str, List[str]] = {}
                for alias, binding in alias_map.items():
                    sid = str(getattr(binding, "server_id", "") or "").strip()
                    if not sid:
                        continue
                    server_aliases.setdefault(sid, []).append(alias)

                enabled_servers = []
                try:
                    enabled_servers = list(self.mcp_server_registry.list_enabled_servers())
                except Exception:
                    enabled_servers = []

                active_server_category_ids = set()
                for server in enabled_servers:
                    sid = str(server.get("id") or "").strip()
                    if not sid:
                        continue
                    cat_id = MCPServerRegistry.make_server_category_id(sid)
                    active_server_category_ids.add(cat_id)
                    aliases = list(server_aliases.get(sid, []))
                    existing = self.tool_categories_map.get(cat_id)
                    label = MCPServerRegistry.make_server_category_label(server)
                    if existing is not None:
                        existing.label = label
                        existing.tools = aliases
                    else:
                        self.tool_categories_map[cat_id] = type(next(iter(TOOL_CATEGORIES.values())))(
                            label=label,
                            tools=aliases,
                            default_enabled=True,
                            silent_when_disabled=False,
                        )
                    if cat_id not in self.tool_category_states:
                        self.tool_category_states[cat_id] = True

                stale_keys = [
                    key for key in list(self.tool_categories_map.keys())
                    if isinstance(key, str)
                    and key.startswith("mcp_server__")
                    and key not in active_server_category_ids
                ]
                for key in stale_keys:
                    self.tool_categories_map.pop(key, None)
                    self.tool_category_states.pop(key, None)

                if hasattr(self, "_prune_runtime_tool_overrides"):
                    self._prune_runtime_tool_overrides()
                if hasattr(self, "_apply_runtime_tool_overrides"):
                    self._apply_runtime_tool_overrides()

                filtered_tools: List[Dict[str, Any]] = []
                filtered_alias_map: Dict[str, Any] = {}
                for tool_def in tool_defs:
                    alias = str(((tool_def or {}).get("function") or {}).get("name") or "").strip()
                    if not alias:
                        continue
                    binding = alias_map.get(alias)
                    if binding is None:
                        continue
                    sid = str(getattr(binding, "server_id", "") or "").strip()
                    if not sid:
                        continue
                    cat_id = MCPServerRegistry.make_server_category_id(sid)
                    cat = self.tool_categories_map.get(cat_id)
                    cat_enabled = self.tool_category_states.get(
                        cat_id,
                        getattr(cat, "default_enabled", True) if cat is not None else True,
                    )
                    forced = getattr(self, "admin_forced_category_states", {}).get(cat_id)
                    if isinstance(forced, bool):
                        cat_enabled = forced
                    if not cat_enabled:
                        continue
                    filtered_tools.append(tool_def)
                    filtered_alias_map[alias] = binding

                self.mcp_tool_alias_map = filtered_alias_map
                self._refresh_disabled_tools()
                return filtered_tools

    def define_tools(self) -> List[Dict]:
                """定义可用工具（添加确认工具）"""
                now = datetime.now()
                current_time = f"{now.year}年{now.month}月{now.day}日 {now.hour}点（24小时制）"

                tools = [
        	            {
        	                "type": "function",
	                "function": {
	                    "name": "sleep",
	                    "description": "等待工具。三种模式三选一：1) seconds：短暂延迟；2) wait_sub_agent_ids：等待指定子智能体全部结束并直接返回结果；3) wait_runcommand_id：等待指定后台 run_command 结束并直接返回结果。若同时提供多个等待参数会报错。",
	                    "parameters": {
	                        "type": "object",
	                        "properties": self._inject_intent({
	                            "seconds": {
	                                "type": "number",
	                                "description": "等待的秒数，可以是小数（如0.2秒）。建议范围：0.1-10秒"
	                            },
	                            "wait_sub_agent_ids": {
	                                "type": "array",
	                                "items": {"type": "integer"},
	                                "description": "等待这些子智能体全部结束后返回（可提供一个或多个编号）。"
	                            },
	                            "wait_runcommand_id": {
	                                "type": "string",
	                                "description": "等待指定后台 run_command 的 command_id 结束后返回。"
	                            },
	                            "reason": {
	                                "type": "string",
	                                "description": "等待的原因说明（可选）"
	                            }
                                }),
                                "required": []
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "write_file",
                            "description": "将内容写入本地文件系统；append 为 False 时覆盖原文件，True 时追加到末尾。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "file_path": {
                                        "type": "string",
                                        "description": "要写入的相对路径"
                                    },
                                    "content": {
                                        "type": "string",
                                        "description": "要写入文件的内容"
                                    },
                                    "append": {
                                        "type": "boolean",
                                        "description": "是否追加到文件而不是覆盖它",
                                        "default": False
                                    }
                                }),
                                "required": ["file_path", "content"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "read_skill",
                            "description": "按 skill 名称读取 skills/<name>/SKILL.md 内容；内部等价于 read_file 的 read 模式，并返回解析后的 path。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "skill_name": {
                                        "type": "string",
                                        "description": "skill 名称（支持 skill id 或技能名称）"
                                    }
                                }),
                                "required": ["skill_name"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "read_file",
                            "description": "读取/搜索/抽取 UTF-8 文本文件内容。通过 type 参数选择 read（阅读）、search（搜索）、extract（具体行段），支持限制返回字符数。若文件非 UTF-8 或过大，请改用 run_python。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "path": {"type": "string", "description": "文件路径"},
                                    "type": {
                                        "type": "string",
                                        "enum": ["read", "search", "extract"],
                                        "description": "读取模式：read=阅读、search=搜索、extract=按行抽取"
                                    },
                                    "max_chars": {
                                        "type": "integer",
                                        "description": "返回内容的最大字符数，默认与 config 一致"
                                    },
                                    "start_line": {
                                        "type": "integer",
                                        "description": "[read] 可选的起始行号（1开始）"
                                    },
                                    "end_line": {
                                        "type": "integer",
                                        "description": "[read] 可选的结束行号（>=start_line）"
                                    },
                                    "query": {
                                        "type": "string",
                                        "description": "[search] 搜索关键词"
                                    },
                                    "max_matches": {
                                        "type": "integer",
                                        "description": "[search] 最多返回多少条命中（默认5，最大50）"
                                    },
                                    "context_before": {
                                        "type": "integer",
                                        "description": "[search] 命中行向上追加的行数（默认1，最大3）"
                                    },
                                    "context_after": {
                                        "type": "integer",
                                        "description": "[search] 命中行向下追加的行数（默认1，最大5）"
                                    },
                                    "case_sensitive": {
                                        "type": "boolean",
                                        "description": "[search] 是否区分大小写，默认 false"
                                    },
                                    "segments": {
                                        "type": "array",
                                        "description": "[extract] 需要抽取的行区间",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "label": {
                                                    "type": "string",
                                                    "description": "该片段的标签（可选）"
                                                },
                                                "start_line": {
                                                    "type": "integer",
                                                    "description": "起始行号（>=1）"
                                                },
                                                "end_line": {
                                                    "type": "integer",
                                                    "description": "结束行号（>=start_line）"
                                                }
                                            },
                                            "required": ["start_line", "end_line"]
                                        },
                                        "minItems": 1
                                    }
                                }),
                                "required": ["path", "type"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "edit_file",
                            "description": "在文件中执行精确的字符串替换；建议先使用 read_file 获取最新内容以确保精确匹配。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "file_path": {
                                        "type": "string",
                                        "description": "要修改文件的相对路径"
                                    },
                                    "old_string": {
                                        "type": "string",
                                        "description": "要替换的文本（需与文件内容精确匹配，保留缩进；建议提供至少3行提升定位稳定性。需要批量替换的场景可以单行或不足一行）"
                                    },
                                    "new_string": {
                                        "type": "string",
                                        "description": "用于替换的新文本（必须不同于 old_string）"
                                    },
                                    "replace_all": {
                                        "type": "boolean",
                                        "enum": [True, False],
                                        "description": "是否替换所有匹配内容（必填）。false=仅替换首个匹配，true=替换全部匹配。"
                                    }
                                }),
                                "required": ["file_path", "old_string", "new_string", "replace_all"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "vlm_analyze",
                            "description": "使用大参数视觉语言模型（Qwen3.5）理解图片：文字、物体、布局、表格等，仅支持本地路径。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "path": {"type": "string", "description": "项目内的图片相对路径"},
                                    "prompt": {"type": "string", "description": "传递给 VLM 的中文提示词，如“请总结这张图的内容”“表格的总金额是多少”“图中是什么车？”。"}
                                }),
                                "required": ["path", "prompt"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "terminal_session",
                            "description": "管理持久化终端会话，可打开、关闭、列出或切换终端。请在授权工作区内执行命令，禁止启动需要完整 TTY 的程序（python REPL、vim、top 等）。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "action": {
                                        "type": "string", 
                                        "enum": ["open", "close", "list", "reset"],
                                        "description": "操作类型：open-打开新终端，close-关闭终端，list-列出所有终端，reset-重置终端"
                                    },
                                    "session_name": {
                                        "type": "string",
                                        "description": "终端会话名称（open、close、reset时需要）"
                                    },
                                    "working_dir": {
                                        "type": "string",
                                        "description": "工作目录，相对于项目路径（open时可选）"
                                    }
                                }),
                                "required": ["action"]
                            }
                        }
                    },
        	            {
        	                "type": "function",
        	                "function": {
                                    "name": "terminal_input",
                                    "description": "向指定终端发送命令或输入。禁止启动会占用终端界面的程序（python/node/nano/vim 等）；如遇卡死请结合 terminal_snapshot 并使用 terminal_session 的 reset 恢复。output_wait 必填：本次收集/等待输出的最大时长（秒，1-300），不会封装命令、不会强杀进程；在窗口内若检测到命令已完成会提前返回，否则到时返回已产生的输出并保持命令继续运行。需要强制超时终止请使用 run_command。\n用法建议：\n1) 短命令多次执行可设 5-10 秒；\n2) 只需确认启动的长任务（下载/clone/pip/启动服务）可设约 30 秒，运行期间可做其他事情，后续用 terminal_snapshot 判断状态/是否完成；\n3) 必须等待结果再继续的任务（大表/文件批量处理、前端构建、批量测试、压缩/解压、数据导出/统计）设足够覆盖实际执行时间；\n4) 可能长时间无输出的任务（遍历海量文件/转码/打包）短窗口不一定有输出，可适当加长或加完成标记；\n5) 长时间无输出且怀疑卡死时，使用 terminal_session 的 reset 重置终端；\n6) 需要仅发送回车时，可传入一个空格字符作为 command（等效按下 Enter）。\n若不确定上一条命令是否结束，先用 terminal_snapshot 确认后再继续输入。",
        	                    "parameters": {
        	                        "type": "object",
        	                        "properties": self._inject_intent({
        	                            "command": {
        	                                "type": "string",
                                        "description": "要执行的命令或发送的输入"
                                    },
                                    "session_name": {
                                        "type": "string",
                                        "description": "目标终端会话名称（必填）"
                                    },
                                    "output_wait": {
                                        "type": "number",
                                        "description": "等待/收集输出的最大秒数（1-300，必填）；非命令超时，到点即返回已产生输出并保持命令运行"
                                    }
                                }),
                                "required": ["command", "output_wait", "session_name"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "terminal_snapshot",
                            "description": "获取指定终端最近的输出快照，用于判断当前状态。默认返回末尾的50行，可通过参数调整。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "session_name": {
                                        "type": "string",
                                        "description": "目标终端会话名称（可选，默认活动终端）"
                                    },
                                    "lines": {
                                        "type": "integer",
                                        "description": "返回的最大行数（可选）"
                                    },
                                    "max_chars": {
                                        "type": "integer",
                                        "description": "返回的最大字符数（可选）"
                                    }
                                })
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "web_search",
                            "description": f"当现有资料不足时搜索外部信息（当前时间 {current_time}）。调用前说明目的，精准撰写 query，并合理设置时间/主题参数；避免重复或无意义的搜索。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "query": {
                                        "type": "string",
                                        "description": "搜索查询内容（不要包含日期或时间范围）"
                                    },
                                    "max_results": {
                                        "type": "integer",
                                        "description": "最大结果数，可选"
                                    },
                                    "topic": {
                                        "type": "string",
                                        "description": "搜索主题，可选值：general（默认）/news/finance"
                                    },
                                    "time_range": {
                                        "type": "string",
                                        "description": "相对时间范围，可选 day/week/month/year，支持缩写 d/w/m/y；与 days 和 start_date/end_date 互斥"
                                    },
                                    "days": {
                                        "type": "integer",
                                        "description": "最近 N 天，仅当 topic=news 时可用；与 time_range、start_date/end_date 互斥"
                                    },
                                    "start_date": {
                                        "type": "string",
                                        "description": "开始日期，YYYY-MM-DD；必须与 end_date 同时提供，与 time_range、days 互斥"
                                    },
                                    "end_date": {
                                        "type": "string",
                                        "description": "结束日期，YYYY-MM-DD；必须与 start_date 同时提供，与 time_range、days 互斥"
                                    },
                                    "country": {
                                        "type": "string",
                                        "description": "国家过滤，仅 topic=general 可用，使用英文小写国名"
                                    },
                                    "include_domains": {
                                        "type": "array",
                                        "description": "仅包含这些域名（可选，最多300个）",
                                        "items": {
                                            "type": "string"
                                        }
                                    }
                                }),
                                "required": ["query"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "extract_webpage",
                            "description": "在 web_search 结果不够详细时提取网页正文。调用前说明用途，注意提取内容会消耗大量 token，超过80000字符将被拒绝。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "url": {"type": "string", "description": "要提取内容的网页URL"}
                                }),
                                "required": ["url"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "save_webpage",
                            "description": "提取网页内容并保存为纯文本文件，适合需要长期留存的长文档。请提供网址与目标路径（含 .txt 后缀），落地后请通过终端命令查看。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "url": {"type": "string", "description": "要保存的网页URL"},
                                    "target_path": {"type": "string", "description": "保存位置，包含文件名，相对于项目根目录"}
                                }),
                                "required": ["url", "target_path"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "list_mcp_servers",
                            "description": "列出当前已配置的 MCP 服务与工具映射（mcp__... 别名）。可选刷新远程 tools/list 缓存后再返回。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "refresh": {
                                        "type": "boolean",
                                        "description": "是否先同步 MCP 服务工具缓存（默认 false）"
                                    },
                                    "server_id": {
                                        "type": "string",
                                        "description": "仅查看指定 MCP 服务（可选）"
                                    },
                                    "include_disabled": {
                                        "type": "boolean",
                                        "description": "是否包含已禁用服务（默认 false）"
                                    }
                                }),
                                "required": []
                            }
                        }
                    },
                    {
                        "type": "function",
        	                "function": {
        	                    "name": "run_python",
        	                    "description": "执行一次性 Python 脚本，可用于处理二进制或非 UTF-8 文件（如 Excel、Word、PDF、图片），或进行数据分析与验证。必须提供 timeout（最长60秒）；一旦超时，脚本会被打断且无法继续执行（需要重新运行），并返回已捕获输出。",
        	                    "parameters": {
        	                        "type": "object",
        	                        "properties": self._inject_intent({
        	                            "code": {"type": "string", "description": "Python代码"},
                                    "timeout": {
                                        "type": "number",
                                        "description": "超时时长（秒），必填，最大60"
                                    }
                                }),
                                "required": ["code", "timeout"]
                            }
                        }
                    },
                    {
                        "type": "function",
	                "function": {
	                    "name": "run_command",
	                    "description": "执行一次性终端命令，适合查看文件信息（file/ls/stat/iconv 等）、转换编码或调用 CLI 工具。禁止启动交互式程序。必须提供 timeout。前台模式（run_in_background=false，默认）上限120秒，超时会打断；后台模式（run_in_background=true）上限3600秒，会先等待5秒返回已有输出并继续在后台运行，完成后由系统通知。",
	                    "parameters": {
	                        "type": "object",
	                        "properties": self._inject_intent({
	                            "command": {"type": "string", "description": "终端命令"},
                                    "timeout": {
                                        "type": "number",
                                        "description": "超时时长（秒），必填。前台最大120；后台最大3600。"
                                    },
                                    "run_in_background": {
                                        "type": "boolean",
                                        "description": "是否后台运行。true 时先等待5秒返回已有输出，并在后台持续执行直至结束。"
                                    }
                                }),
                                "required": ["command", "timeout"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "update_memory",
                            "description": "按条目更新记忆列表（自动编号）。append 追加新条目；replace 用序号替换；delete 用序号删除。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "content": {"type": "string", "description": "条目内容。append/replace 时必填"},
                                    "operation": {"type": "string", "enum": ["append", "replace", "delete"], "description": "操作类型"},
                                    "index": {"type": "integer", "description": "要替换/删除的序号（从1开始）"}
                                }),
                                "required": ["operation"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "todo_create",
                            "description": "创建待办列表，最多 8 条任务；若已有列表将被覆盖。当用户提出稍微复杂的要求、预计需要超过 3 个步骤时，应积极创建待办事项。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "overview": {"type": "string", "description": "一句话概述待办清单要完成的目标，50 字以内。"},
                                    "tasks": {
                                        "type": "array",
                                        "description": "任务列表，1~8 条，每条写清“动词+对象+目标”。",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "title": {"type": "string", "description": "单个任务描述，写成可执行的步骤"}
                                            },
                                            "required": ["title"]
                                        },
                                        "minItems": 1,
                                        "maxItems": 8
                                    }
                                }),
                                "required": ["overview", "tasks"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "todo_update_task",
                            "description": "批量勾选或取消任务（支持单个或多个任务）；全部勾选时提示所有任务已完成。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "task_index": {"type": "integer", "description": "任务序号（1-8），兼容旧参数"},
                                    "task_indices": {
                                        "type": "array",
                                        "items": {"type": "integer"},
                                        "minItems": 1,
                                        "maxItems": 8,
                                        "description": "要更新的任务序号列表（1-8），可一次勾选多个"
                                    },
                                    "completed": {"type": "boolean", "description": "true=打勾，false=取消"}
                                }),
                                "required": ["completed"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "close_sub_agent",
                            "description": "强制关闭指定子智能体，适用于长时间无响应、超时或卡死的任务。使用前请确认必要的日志/文件已保留，操作会立即终止该任务。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "task_id": {"type": "string", "description": "子智能体任务ID"},
                                    "agent_id": {"type": "integer", "description": "子智能体编号（1~5），若缺少 task_id 可用"}
                                })
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "create_sub_agent",
                            "description": "创建一个子智能体来处理独立任务。子智能体拥有完整的工具能力（读写文件、执行命令、搜索网页等），与主智能体共享工作区。\n\n适用场景：\n1. 可以独立完成的任务（生成文档、代码分析、测试执行）\n2. 需要大量工具调用的任务（批量文件处理、数据收集）\n3. 可以并行处理的子任务（多模块开发、多方面分析）\n4. 刚开始修改一个项目、需要先找到某个功能模块的修改位置时，可优先创建子智能体快速了解代码定位与相关上下文\n\n何时使用后台运行：\n- 任务耗时较长（预计超过5分钟）\n- 你可以继续处理其他工作，不需要立即使用结果\n- 多个独立任务可以并行执行\n\n何时使用阻塞运行（默认）：\n- 任务较快（几分钟内完成）\n- 后续工作依赖子智能体的结果\n- 需要立即查看和使用输出\n\n重要限制：\n- 最多同时运行 5 个子智能体\n- 禁止多个子智能体操作相同的文件或目录（会导致冲突）\n- 禁止子智能体间的工作有重叠（如同时修改同一模块、同时测试同一功能）\n- 每个子智能体应该有明确独立的职责范围",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "agent_id": {
                                        "type": "integer",
                                        "description": "子智能体编号（1-99），用于标识和管理。同一对话中每个编号只能使用一次。建议按顺序分配：1、2、3..."
                                    },
                                    "summary": {
                                        "type": "string",
                                        "description": "任务简短摘要（10-30字），用于显示和跟踪。例如：'生成API文档'、'分析性能瓶颈'、'编写单元测试'。"
                                    },
                                    "task": {
                                        "type": "string",
                                        "description": "详细的任务描述，必须包括：\n1. 任务目标：要完成什么\n2. 具体要求：如何完成、注意事项\n3. 交付内容：在交付目录生成哪些文件\n4. 工作范围：明确指定操作的文件/目录范围，避免与其他子智能体冲突\n\n示例：'分析 src/api/ 目录下的所有 Python 文件，检查代码质量问题（复杂度、重复代码、潜在bug），生成分析报告 analysis.md 到交付目录。'"
                                    },
                                    "deliverables_dir": {
                                        "type": "string",
                                        "description": "交付文件夹的相对路径（相对于项目根目录）。子智能体会将所有结果文件放在此目录。\n\n要求：必须是不存在的新目录；若目录不存在会自动创建；若目录已存在（无论是否为空）将报错。\n\n留空则使用默认路径：sub_agent_results/agent_{agent_id}\n\n示例：'docs/api'、'reports/performance'、'tests/generated'"
                                    },
                                    "run_in_background": {
                                        "type": "boolean",
                                        "description": "是否后台运行。\n\ntrue（后台）：立即返回，子智能体在后台执行，完成后会通知你。适合耗时任务或可以并行处理的任务。\n\nfalse（阻塞，默认）：等待子智能体完成后返回结果。适合快速任务或后续工作依赖结果的情况。"
                                    },
                                    "timeout_seconds": {
                                        "type": "integer",
                                        "description": "超时时间（秒），范围 60-3600。超时后子智能体会被强制终止，已生成的部分结果会保留。默认 600 秒（10分钟）。"
                                    },
                                    "thinking_mode": {
                                        "type": "string",
                                        "enum": ["fast", "thinking"],
                                        "description": "子智能体思考模式，根据任务复杂度选择：\n\nfast（快速模式）- 适合简单明确的任务：\n- 网络信息搜集和整理\n- 批量文件读取和简单处理\n- 执行已知的命令序列\n- 生成简单的文档或报告\n- 数据格式转换\n\nthinking（思考模式）- 适合复杂任务：\n- 代码架构分析和重构设计\n- 复杂算法实现和优化\n- 多步骤问题诊断和调试\n- 技术方案选型和对比\n- 需要深度推理的代码审查\n\n不填则使用默认模式。"
                                    }
                                }),
                                "required": ["agent_id", "summary", "task", "deliverables_dir", "thinking_mode"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "terminate_sub_agent",
                            "description": "强制终止正在运行的子智能体。用于：\n1. 任务不再需要\n2. 子智能体陷入死循环或执行错误\n3. 用户要求停止\n\n终止后无法恢复，但已生成的部分结果会保留在交付目录。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "agent_id": {
                                        "type": "integer",
                                        "description": "要终止的子智能体编号。"
                                    }
                                }),
                                "required": ["agent_id"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "get_sub_agent_status",
                            "description": "查询一个或多个子智能体的当前状态和工作进度。用于检查后台运行的子智能体是否完成、当前在做什么、使用了哪些工具。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "agent_ids": {
                                        "type": "array",
                                        "items": {"type": "integer"},
                                        "description": "要查询的子智能体编号列表。必须指定至少一个编号。例如：[1] 或 [1, 2, 3]。"
                                    }
                                }),
                                "required": ["agent_ids"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "trigger_easter_egg",
                            "description": "触发隐藏彩蛋，用于展示非功能性特效。需指定 effect 参数，例如 flood（灌水）或 snake（贪吃蛇）。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "effect": {
                                        "type": "string",
                                        "description": "彩蛋标识，目前支持 flood（灌水）与 snake（贪吃蛇）。"
                                    }
                                }),
                                "required": ["effect"]
                            }
                        }
                    },
                    {
                        "type": "function",
                        "function": {
                            "name": "manage_personalization",
                            "description": "管理用户个性化设置。支持读取所有配置，或更新单个字段。可修改字段：self_identify（AI自称，最多20字）、user_name（AI如何称呼用户，最多20字）、profession（用户职业，最多20字）、tone（交流语气，最多20字）、considerations（注意事项列表，字符串数组，最多10项每项最多50字）、theme（主题配色：classic-经典/light-明亮/dark-暗黑）、communication_style（交流风格：default-标准AI风格/human_like-拟人聊天风格）。更新时会自动验证格式，验证通过后立即保存并生效，新主题会立即应用到界面。",
                            "parameters": {
                                "type": "object",
                                "properties": self._inject_intent({
                                    "action": {
                                        "type": "string",
                                        "enum": ["read", "update"],
                                        "description": "操作类型：read读取所有配置，update更新单个字段"
                                    },
                                    "field": {
                                        "type": "string",
                                        "enum": ["self_identify", "user_name", "profession", "tone", "considerations", "theme", "communication_style"],
                                        "description": "要更新的字段名（仅action=update时需要）"
                                    },
                                    "value": {
                                        "description": "新值（仅action=update时需要）。注意事项需提供字符串数组，其他字段提供字符串"
                                    }
                                }),
                                "required": ["action"]
                            }
                        }
                    }
                ]
                # 多模态模型自带能力，不再暴露 vlm_analyze，改为 view_image / view_video
                model_key = getattr(self, "model_key", None)
                if model_key and (model_supports_image(model_key) or model_supports_video(model_key)):
                    tools = [
                        tool for tool in tools
                        if (tool.get("function") or {}).get("name") != "vlm_analyze"
                    ]
                    if model_supports_image(model_key):
                        tools.append({
                            "type": "function",
                            "function": {
                                "name": "view_image",
                                "description": "将指定本地图片附加到工具结果中（tool 消息携带 image_url），便于模型主动查看图片内容。",
                                "parameters": {
                                    "type": "object",
                                    "properties": self._inject_intent({
                                        "path": {
                                            "type": "string",
                                            "description": "项目内的图片相对路径（不要以 /workspace 开头）；宿主机模式可用绝对路径。支持 png/jpg/webp/gif/bmp/svg。"
                                        }
                                    }),
                                    "required": ["path"]
                                }
                            }
                        })
                    if model_supports_video(model_key):
                        tools.append({
                            "type": "function",
                            "function": {
                                "name": "view_video",
                                "description": "将指定本地视频附加到工具结果中（tool 消息携带 video_url），便于模型查看视频内容。",
                                "parameters": {
                                    "type": "object",
                                    "properties": self._inject_intent({
                                        "path": {
                                            "type": "string",
                                            "description": "项目内的视频相对路径（不要以 /workspace 开头）；宿主机模式可用绝对路径。支持 mp4/mov/mkv/avi/webm。"
                                        }
                                    }),
                                    "required": ["path"]
                                }
                            }
                        })
                # 附加自定义工具（仅管理员可见）
                custom_tools = self._build_custom_tools()
                if custom_tools:
                    tools.extend(custom_tools)
                # 附加 MCP 工具（由管理员统一配置）
                mcp_tools = self._build_mcp_tools()
                if mcp_tools:
                    tools.extend(mcp_tools)
                if self.disabled_tools:
                    tools = [
                        tool for tool in tools
                        if tool.get("function", {}).get("name") not in self.disabled_tools
                    ]
                
                # 调试日志：记录工具列表
                tool_names = [t.get("function", {}).get("name") for t in tools]
                logger.info("[define_tools] 可用工具列表: %s", tool_names)
                print(f"[DEBUG] define_tools: 工具数量={len(tools)}")
                
                # 检查personalization分类状态
                if hasattr(self, 'tool_categories_map') and 'personalization' in self.tool_categories_map:
                    cat = self.tool_categories_map['personalization']
                    state = self.tool_category_states.get('personalization', cat.default_enabled)
                    print(f"[DEBUG] personalization分类: default_enabled={cat.default_enabled}, current_state={state}")
                else:
                    print(f"[DEBUG] personalization分类不在tool_categories_map中")
                
                if "manage_personalization" in tool_names:
                    logger.info("[define_tools] manage_personalization 工具已启用")
                    print("[DEBUG] manage_personalization 在可用工具列表中")
                else:
                    logger.warning("[define_tools] manage_personalization 工具未找到！disabled_tools=%s", self.disabled_tools)
                    print(f"[DEBUG] manage_personalization 不在可用工具列表中！disabled_tools={self.disabled_tools}")
                
                return self._apply_intent_to_tools(tools)
