wangzhibo
2026-08-30 c8515170fd399b4d2dfaebb59128d4674e58915e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""服务端插件工具执行器"""
 
import asyncio
from typing import Dict, Any, TYPE_CHECKING
 
if TYPE_CHECKING:
    from core.connection import ConnectionHandler
from ..base import ToolType, ToolDefinition, ToolExecutor
from plugins_func.register import all_function_registry, module_func_map, Action, ActionResponse
 
 
class ServerPluginExecutor(ToolExecutor):
    """服务端插件工具执行器"""
 
    def __init__(self, conn: "ConnectionHandler"):
        self.conn = conn
        self.config = conn.config
 
    async def execute(
        self, conn: "ConnectionHandler", tool_name: str, arguments: Dict[str, Any]
    ) -> ActionResponse:
        """执行服务端插件工具"""
        func_item = all_function_registry.get(tool_name)
        if not func_item:
            return ActionResponse(
                action=Action.NOTFOUND, response=f"插件函数 {tool_name} 不存在"
            )
 
        try:
            # 根据工具类型决定如何调用
            if hasattr(func_item, "type"):
                func_type = func_item.type
                if func_type.code in [4, 5]:  # SYSTEM_CTL, IOT_CTL (需要conn参数)
                    result = func_item.func(conn, **arguments)
                elif func_type.code == 2:  # WAIT
                    result = func_item.func(**arguments)
                elif func_type.code == 3:  # CHANGE_SYS_PROMPT
                    result = func_item.func(conn, **arguments)
                else:
                    result = func_item.func(**arguments)
            else:
                # 默认不传conn参数
                result = func_item.func(**arguments)
 
            # 兼容 async def 工具函数
            if asyncio.iscoroutine(result):
                result = await result
 
            return result
 
        except Exception as e:
            return ActionResponse(
                action=Action.ERROR,
                response=str(e),
            )
 
    def _expand_plugin_names(self, config_functions):
        """将模块级别的插件名展开为具体函数名。
 
        在一个 function 文件中可能注册多个 @register_function,
        配置中如果使用的是模块名(文件名),需要展开为具体的函数名列表。
        """
        if not isinstance(config_functions, list):
            try:
                config_functions = list(config_functions)
            except TypeError:
                return []
 
        expanded = []
        for name in config_functions:
            if name in module_func_map:
                # 模块名,展开为该模块下所有注册函数名
                expanded.extend(module_func_map[name])
            elif name in all_function_registry:
                # 精确匹配函数名,直接保留
                expanded.append(name)
            else:
                # 未知名称,保留原值(可能是 MCP 或其他工具)
                expanded.append(name)
        return expanded
 
    def _get_plugin_description(self, func_name):
        """获取插件函数的描述,优先精确匹配函数名,其次匹配模块名。"""
        plugins = self.config.get("plugins", {})
        # 精确匹配函数名
        if func_name in plugins:
            return plugins[func_name].get("description", "")
        # 通过 module_func_map 反向查找模块名
        for module_name, func_names in module_func_map.items():
            if func_name in func_names and module_name in plugins:
                return plugins[module_name].get("description", "")
        return ""
 
    def get_tools(self) -> Dict[str, ToolDefinition]:
        """获取所有注册的服务端插件工具"""
        tools = {}
 
        # 获取必要的函数
        necessary_functions = ["handle_exit_intent", "get_lunar"]
 
        # 获取配置中的函数
        config_functions = self.config["Intent"][
            self.config["selected_module"]["Intent"]
        ].get("functions", [])
 
        # 将模块级别的插件名展开为具体函数名(兜底机制)
        config_functions = self._expand_plugin_names(config_functions)
 
        # 合并所有需要的函数
        all_required_functions = list(set(necessary_functions + config_functions))
 
        for func_name in all_required_functions:
            func_item = all_function_registry.get(func_name)
            if func_item:
                # 从函数注册中获取描述(支持模块名和函数名的双层查找)
                fun_description = self._get_plugin_description(func_name)
                if fun_description is not None and len(fun_description) > 0:
                    if "function" in func_item.description and isinstance(
                        func_item.description["function"], dict
                    ):
                        func_item.description["function"][
                            "description"
                        ] = fun_description
 
                # 新闻插件:根据配置更新新闻源参数描述
                if func_name == "get_news_from_newsnow":
                    self._init_news_source_description(func_item, func_name)
 
                tools[func_name] = ToolDefinition(
                    name=func_name,
                    description=func_item.description,
                    tool_type=ToolType.SERVER_PLUGIN,
                )
 
        return tools
 
    def has_tool(self, tool_name: str) -> bool:
        """检查是否有指定的服务端插件工具"""
        return tool_name in all_function_registry
 
    def _init_news_source_description(self, func_item, func_name):
        """根据连接配置初始化新闻工具的参数描述"""
        news_sources = (
            self.config.get("plugins", {})
            .get(func_name, {})
            .get("news_sources", "")
        )
        if not news_sources:
            news_sources = "澎湃新闻;百度热搜;财联社"
        sources_str = news_sources.replace(";", "、")
        try:
            func_item.description["function"]["parameters"]["properties"]["source"][
                "description"
            ] = f"新闻源的标准中文名称,例如{sources_str}等。可选参数,如果不提供则使用默认新闻源"
        except (KeyError, TypeError):
            pass