mofox-plugin-dev-toolkit 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mofox_plugin_dev_toolkit-0.2.1.dist-info/METADATA +409 -0
- mofox_plugin_dev_toolkit-0.2.1.dist-info/RECORD +43 -0
- mofox_plugin_dev_toolkit-0.2.1.dist-info/WHEEL +5 -0
- mofox_plugin_dev_toolkit-0.2.1.dist-info/entry_points.txt +2 -0
- mofox_plugin_dev_toolkit-0.2.1.dist-info/licenses/LICENSE +674 -0
- mofox_plugin_dev_toolkit-0.2.1.dist-info/top_level.txt +1 -0
- mpdt/__init__.py +15 -0
- mpdt/__main__.py +8 -0
- mpdt/cli.py +314 -0
- mpdt/commands/__init__.py +9 -0
- mpdt/commands/check.py +316 -0
- mpdt/commands/dev.py +550 -0
- mpdt/commands/generate.py +366 -0
- mpdt/commands/init.py +487 -0
- mpdt/dev/bridge_plugin/__init__.py +17 -0
- mpdt/dev/bridge_plugin/discovery_server.py +126 -0
- mpdt/dev/bridge_plugin/plugin.py +258 -0
- mpdt/templates/__init__.py +165 -0
- mpdt/templates/action_template.py +102 -0
- mpdt/templates/adapter_template.py +129 -0
- mpdt/templates/chatter_template.py +103 -0
- mpdt/templates/event_template.py +116 -0
- mpdt/templates/plus_command_template.py +150 -0
- mpdt/templates/prompt_template.py +92 -0
- mpdt/templates/router_template.py +175 -0
- mpdt/templates/tool_template.py +98 -0
- mpdt/utils/__init__.py +10 -0
- mpdt/utils/color_printer.py +99 -0
- mpdt/utils/config_loader.py +171 -0
- mpdt/utils/config_manager.py +297 -0
- mpdt/utils/file_ops.py +203 -0
- mpdt/utils/license_generator.py +980 -0
- mpdt/utils/plugin_parser.py +196 -0
- mpdt/utils/template_engine.py +112 -0
- mpdt/validators/__init__.py +26 -0
- mpdt/validators/auto_fix_validator.py +182 -0
- mpdt/validators/base.py +121 -0
- mpdt/validators/component_validator.py +415 -0
- mpdt/validators/config_validator.py +173 -0
- mpdt/validators/metadata_validator.py +125 -0
- mpdt/validators/structure_validator.py +70 -0
- mpdt/validators/style_validator.py +125 -0
- mpdt/validators/type_validator.py +223 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DevBridge 插件 - 为 mpdt dev 提供 WebSocket 桥接
|
|
3
|
+
临时注入到主程序,提供插件重载接口
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
from typing import ClassVar, Set
|
|
8
|
+
|
|
9
|
+
from fastapi import WebSocket, WebSocketDisconnect
|
|
10
|
+
from src.common.logger import get_logger
|
|
11
|
+
from src.common.security import VerifiedDep
|
|
12
|
+
from src.common.server import get_global_server
|
|
13
|
+
from src.plugin_system import (
|
|
14
|
+
BasePlugin,
|
|
15
|
+
register_plugin,
|
|
16
|
+
)
|
|
17
|
+
from src.plugin_system.apis.plugin_info_api import list_plugins
|
|
18
|
+
from src.plugin_system.base.base_http_component import BaseRouterComponent
|
|
19
|
+
from src.plugin_system.base.component_types import ComponentInfo
|
|
20
|
+
|
|
21
|
+
logger = get_logger("dev_bridge")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DevBridgeRouter(BaseRouterComponent):
|
|
25
|
+
"""开发模式 WebSocket 路由组件"""
|
|
26
|
+
|
|
27
|
+
component_name = "dev_bridge_router"
|
|
28
|
+
component_description = "开发模式 WebSocket 桥接,提供插件重载接口"
|
|
29
|
+
component_version = "1.0.0"
|
|
30
|
+
|
|
31
|
+
def __init__(self, plugin_config: dict | None = None):
|
|
32
|
+
"""初始化路由组件"""
|
|
33
|
+
self.active_connections: Set[WebSocket] = set()
|
|
34
|
+
super().__init__(plugin_config)
|
|
35
|
+
|
|
36
|
+
def register_endpoints(self) -> None:
|
|
37
|
+
"""注册 HTTP 端点"""
|
|
38
|
+
|
|
39
|
+
@self.router.websocket("/ws")
|
|
40
|
+
async def websocket_endpoint(websocket: WebSocket):
|
|
41
|
+
"""WebSocket 端点 - 与 mpdt dev 通信
|
|
42
|
+
|
|
43
|
+
完整路径: ws://{host}:{port}/plugin-api/dev_bridge/dev_bridge_router/ws
|
|
44
|
+
|
|
45
|
+
消息格式:
|
|
46
|
+
客户端 → 服务器:
|
|
47
|
+
{"command": "reload", "plugin_name": "xxx"}
|
|
48
|
+
{"command": "status"}
|
|
49
|
+
{"command": "ping"}
|
|
50
|
+
{"command": "get_loaded_plugins"}
|
|
51
|
+
|
|
52
|
+
服务器 → 客户端:
|
|
53
|
+
{"type": "reload_result", "success": true, "message": "..."}
|
|
54
|
+
{"type": "status", "loaded_plugins": [...], "failed_plugins": [...]}
|
|
55
|
+
{"type": "pong"}
|
|
56
|
+
{"type": "plugins_loaded", "loaded": [...], "failed": [...]}
|
|
57
|
+
"""
|
|
58
|
+
await websocket.accept()
|
|
59
|
+
self.active_connections.add(websocket)
|
|
60
|
+
logger.info("开发模式客户端已连接")
|
|
61
|
+
|
|
62
|
+
# 立即发送插件加载状态
|
|
63
|
+
try:
|
|
64
|
+
status = self._get_plugin_status()
|
|
65
|
+
await websocket.send_json({
|
|
66
|
+
"type": "plugins_loaded",
|
|
67
|
+
"loaded": status["loaded_plugins"],
|
|
68
|
+
"failed": status["failed_plugins"]
|
|
69
|
+
})
|
|
70
|
+
logger.info(f"已发送插件状态: {len(status['loaded_plugins'])} 个已加载")
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error(f"发送插件状态失败: {e}")
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
while True:
|
|
76
|
+
data = await websocket.receive_json()
|
|
77
|
+
command = data.get("command")
|
|
78
|
+
|
|
79
|
+
if command == "reload":
|
|
80
|
+
plugin_name = data.get("plugin_name")
|
|
81
|
+
if not plugin_name:
|
|
82
|
+
await websocket.send_json({"type": "error", "message": "缺少 plugin_name 参数"})
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
# 执行插件重载
|
|
86
|
+
success, message = await self._reload_plugin(plugin_name)
|
|
87
|
+
await websocket.send_json(
|
|
88
|
+
{
|
|
89
|
+
"type": "reload_result",
|
|
90
|
+
"success": success,
|
|
91
|
+
"plugin_name": plugin_name,
|
|
92
|
+
"message": message,
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
elif command == "status":
|
|
97
|
+
# 返回插件状态
|
|
98
|
+
logger.info("返回插件状态")
|
|
99
|
+
status = self._get_plugin_status()
|
|
100
|
+
await websocket.send_json({"type": "status", **status})
|
|
101
|
+
|
|
102
|
+
elif command == "ping":
|
|
103
|
+
await websocket.send_json({"type": "pong"})
|
|
104
|
+
|
|
105
|
+
elif command == "get_loaded_plugins":
|
|
106
|
+
status = self._get_plugin_status()
|
|
107
|
+
await websocket.send_json(
|
|
108
|
+
{
|
|
109
|
+
"type": "loaded_plugins",
|
|
110
|
+
"loaded": status["loaded_plugins"],
|
|
111
|
+
"failed": status["failed_plugins"],
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
else:
|
|
116
|
+
await websocket.send_json({"type": "error", "message": f"未知命令: {command}"})
|
|
117
|
+
|
|
118
|
+
except WebSocketDisconnect:
|
|
119
|
+
logger.info("开发模式客户端已断开")
|
|
120
|
+
except Exception as e:
|
|
121
|
+
logger.error(f"WebSocket 通信错误: {e}")
|
|
122
|
+
finally:
|
|
123
|
+
self.active_connections.discard(websocket)
|
|
124
|
+
|
|
125
|
+
@self.router.get("/status")
|
|
126
|
+
async def get_status():
|
|
127
|
+
"""HTTP 状态查询端点
|
|
128
|
+
|
|
129
|
+
完整路径: http://{host}:{port}/plugin-api/dev_bridge/dev_bridge_router/status
|
|
130
|
+
"""
|
|
131
|
+
return self._get_plugin_status()
|
|
132
|
+
|
|
133
|
+
@self.router.post("/reload/{plugin_name}")
|
|
134
|
+
async def reload_plugin(plugin_name: str):
|
|
135
|
+
"""HTTP 重载端点
|
|
136
|
+
|
|
137
|
+
完整路径: http://{host}:{port}/plugin-api/dev_bridge/dev_bridge_router/reload/{plugin_name}
|
|
138
|
+
"""
|
|
139
|
+
success, message = await self._reload_plugin(plugin_name)
|
|
140
|
+
return {"success": success, "plugin_name": plugin_name, "message": message}
|
|
141
|
+
|
|
142
|
+
async def _reload_plugin(self, plugin_name: str) -> tuple[bool, str]:
|
|
143
|
+
"""重载插件
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
plugin_name: 插件名称(不是目录名)
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
(成功, 消息)
|
|
150
|
+
"""
|
|
151
|
+
from src.plugin_system.apis import (
|
|
152
|
+
plugin_manage_api,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
logger.info(f"开始重载插件: {plugin_name}")
|
|
157
|
+
success = await plugin_manage_api.reload_plugin(plugin_name)
|
|
158
|
+
|
|
159
|
+
# 广播重载成功消息
|
|
160
|
+
await self._broadcast({"type": "plugin_reloaded", "plugin_name": plugin_name, "success": success})
|
|
161
|
+
|
|
162
|
+
return True, f"插件 {plugin_name} 重载成功"
|
|
163
|
+
except Exception as e:
|
|
164
|
+
error_msg = f"插件重载失败: {e}"
|
|
165
|
+
logger.error(error_msg)
|
|
166
|
+
|
|
167
|
+
# 广播重载失败消息
|
|
168
|
+
await self._broadcast(
|
|
169
|
+
{"type": "plugin_reloaded", "plugin_name": plugin_name, "success": False, "error": str(e)}
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
return False, error_msg
|
|
173
|
+
|
|
174
|
+
def _get_plugin_status(self) -> dict:
|
|
175
|
+
"""获取插件状态"""
|
|
176
|
+
# 使用 plugin_info_api 获取插件列表
|
|
177
|
+
from src.plugin_system.apis import (
|
|
178
|
+
plugin_info_api,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
loaded_plugins = plugin_info_api.list_plugins("loaded")
|
|
182
|
+
failed_plugins = plugin_info_api.list_plugins("failed")
|
|
183
|
+
|
|
184
|
+
return {"loaded_plugins": loaded_plugins, "failed_plugins": failed_plugins}
|
|
185
|
+
|
|
186
|
+
async def _broadcast(self, message: dict) -> None:
|
|
187
|
+
"""向所有连接的客户端广播消息"""
|
|
188
|
+
if not self.active_connections:
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
disconnected = set()
|
|
192
|
+
for connection in self.active_connections:
|
|
193
|
+
try:
|
|
194
|
+
await connection.send_json(message)
|
|
195
|
+
except Exception as e:
|
|
196
|
+
logger.warning(f"广播消息失败: {e}")
|
|
197
|
+
disconnected.add(connection)
|
|
198
|
+
|
|
199
|
+
# 清理断开的连接
|
|
200
|
+
self.active_connections -= disconnected
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@register_plugin
|
|
204
|
+
class DevBridgePlugin(BasePlugin):
|
|
205
|
+
"""开发模式桥接插件
|
|
206
|
+
|
|
207
|
+
这是一个特殊的插件,在开发模式下临时注入到主程序。
|
|
208
|
+
通过 WebSocket 与 mpdt dev 通信,提供插件重载等功能。
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
plugin_name = "dev_bridge"
|
|
212
|
+
enable_plugin = True
|
|
213
|
+
config_file_name = "config.toml"
|
|
214
|
+
dependencies: ClassVar = []
|
|
215
|
+
python_dependencies: ClassVar = []
|
|
216
|
+
|
|
217
|
+
def __init__(self, *args, **kwargs):
|
|
218
|
+
super().__init__(*args, **kwargs)
|
|
219
|
+
self._router_component: DevBridgeRouter | None = None
|
|
220
|
+
self._discovery_task: asyncio.Task | None = None
|
|
221
|
+
|
|
222
|
+
def get_plugin_components(self) -> list[tuple[ComponentInfo, type]]:
|
|
223
|
+
"""注册路由组件"""
|
|
224
|
+
return [(DevBridgeRouter.get_router_info(), DevBridgeRouter)]
|
|
225
|
+
|
|
226
|
+
async def on_plugin_loaded(self):
|
|
227
|
+
"""插件加载完成后启动发现服务器"""
|
|
228
|
+
from .discovery_server import start_discovery_server
|
|
229
|
+
|
|
230
|
+
# 获取主程序的 host 和 port
|
|
231
|
+
# 从 app_state 或配置中获取
|
|
232
|
+
try:
|
|
233
|
+
server = get_global_server()
|
|
234
|
+
main_host = server.host
|
|
235
|
+
main_port = server.port
|
|
236
|
+
except Exception:
|
|
237
|
+
main_host = "127.0.0.1"
|
|
238
|
+
main_port = 8000
|
|
239
|
+
|
|
240
|
+
# 启动发现服务器
|
|
241
|
+
self._discovery_task = asyncio.create_task(start_discovery_server(main_host, main_port))
|
|
242
|
+
|
|
243
|
+
logger.info(f"DevBridge 插件已加载,发现服务器: http://127.0.0.1:12318")
|
|
244
|
+
logger.info(f"WebSocket 端点: ws://{main_host}:{main_port}/plugin-api/dev_bridge/dev_bridge_router/ws")
|
|
245
|
+
|
|
246
|
+
async def on_plugin_unload(self):
|
|
247
|
+
"""插件卸载时停止发现服务器"""
|
|
248
|
+
from .discovery_server import stop_discovery_server
|
|
249
|
+
|
|
250
|
+
if self._discovery_task:
|
|
251
|
+
self._discovery_task.cancel()
|
|
252
|
+
try:
|
|
253
|
+
await self._discovery_task
|
|
254
|
+
except asyncio.CancelledError:
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
await stop_discovery_server()
|
|
258
|
+
logger.info("DevBridge 插件已卸载")
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
组件模板索引
|
|
3
|
+
|
|
4
|
+
此模块导出所有组件模板的获取函数。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
from mpdt.templates.action_template import get_action_template
|
|
10
|
+
from mpdt.templates.adapter_template import get_adapter_template
|
|
11
|
+
from mpdt.templates.chatter_template import get_chatter_template
|
|
12
|
+
from mpdt.templates.event_template import get_event_handler_template
|
|
13
|
+
from mpdt.templates.plus_command_template import get_plus_command_template
|
|
14
|
+
from mpdt.templates.prompt_template import get_prompt_template
|
|
15
|
+
from mpdt.templates.router_template import get_router_template
|
|
16
|
+
from mpdt.templates.tool_template import get_tool_template
|
|
17
|
+
|
|
18
|
+
# 导出所有模板获取函数
|
|
19
|
+
__all__ = [
|
|
20
|
+
"get_action_template",
|
|
21
|
+
"get_tool_template",
|
|
22
|
+
"get_event_handler_template",
|
|
23
|
+
"get_adapter_template",
|
|
24
|
+
"get_prompt_template",
|
|
25
|
+
"get_plus_command_template",
|
|
26
|
+
"get_chatter_template",
|
|
27
|
+
"get_router_template",
|
|
28
|
+
"get_component_template",
|
|
29
|
+
"prepare_component_context",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_component_template(component_type: str) -> str:
|
|
34
|
+
"""
|
|
35
|
+
根据组件类型获取对应的模板
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
component_type: 组件类型 (action, tool, event, adapter, prompt, plus_command, chatter, router)
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
模板字符串
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
ValueError: 不支持的组件类型
|
|
45
|
+
"""
|
|
46
|
+
template_map = {
|
|
47
|
+
"action": get_action_template,
|
|
48
|
+
"tool": get_tool_template,
|
|
49
|
+
"event": get_event_handler_template,
|
|
50
|
+
"adapter": get_adapter_template,
|
|
51
|
+
"prompt": get_prompt_template,
|
|
52
|
+
"plus_command": get_plus_command_template,
|
|
53
|
+
"chatter": get_chatter_template,
|
|
54
|
+
"router": get_router_template,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if component_type not in template_map:
|
|
58
|
+
raise ValueError(
|
|
59
|
+
f"不支持的组件类型: {component_type}. "
|
|
60
|
+
f"支持的类型: {', '.join(template_map.keys())}"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return template_map[component_type]()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def prepare_component_context(
|
|
67
|
+
component_type: str,
|
|
68
|
+
component_name: str,
|
|
69
|
+
plugin_name: str,
|
|
70
|
+
author: str = "",
|
|
71
|
+
description: str = "",
|
|
72
|
+
is_async: bool = False,
|
|
73
|
+
) -> dict[str, str]:
|
|
74
|
+
"""
|
|
75
|
+
准备组件模板上下文
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
component_type: 组件类型 (action, tool, event, adapter, prompt, plus_command,router,chatter)
|
|
79
|
+
component_name: 组件名称 (snake_case)
|
|
80
|
+
plugin_name: 插件名称
|
|
81
|
+
author: 作者
|
|
82
|
+
description: 描述
|
|
83
|
+
is_async: 是否异步
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
模板上下文字典
|
|
87
|
+
"""
|
|
88
|
+
from mpdt.utils.file_ops import to_pascal_case
|
|
89
|
+
|
|
90
|
+
# 转换为 PascalCase 并添加类型后缀
|
|
91
|
+
class_name = to_pascal_case(component_name)
|
|
92
|
+
|
|
93
|
+
# 根据组件类型添加合适的后缀
|
|
94
|
+
suffix_map = {
|
|
95
|
+
"action": "Action",
|
|
96
|
+
"tool": "Tool",
|
|
97
|
+
"event": "EventHandler",
|
|
98
|
+
"adapter": "Adapter",
|
|
99
|
+
"prompt": "Prompt",
|
|
100
|
+
"plus_command": "PlusCommand",
|
|
101
|
+
"chatter": "Chatter",
|
|
102
|
+
"router": "Router",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
suffix = suffix_map.get(component_type, "")
|
|
106
|
+
if suffix and not class_name.endswith(suffix):
|
|
107
|
+
class_name = f"{class_name}{suffix}"
|
|
108
|
+
|
|
109
|
+
date = datetime.now().strftime("%Y-%m-%d")
|
|
110
|
+
|
|
111
|
+
# 基础上下文
|
|
112
|
+
context = {
|
|
113
|
+
"component_name": component_name,
|
|
114
|
+
"class_name": class_name,
|
|
115
|
+
"plugin_name": plugin_name,
|
|
116
|
+
"author": author,
|
|
117
|
+
"description": description or f"{class_name} 组件",
|
|
118
|
+
"date": date,
|
|
119
|
+
"async_keyword": "async " if is_async else "",
|
|
120
|
+
"await_keyword": "await " if is_async else "",
|
|
121
|
+
"component_type": component_type + "s", # actions, tools, etc.
|
|
122
|
+
"module_name": component_name,
|
|
123
|
+
"method_name": _get_method_name(component_type),
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# 特定组件类型的额外字段
|
|
127
|
+
if component_type == "plus_command":
|
|
128
|
+
context["command_name"] = component_name
|
|
129
|
+
elif component_type == "tool":
|
|
130
|
+
context["tool_name"] = component_name
|
|
131
|
+
elif component_type == "event":
|
|
132
|
+
context["event_type"] = component_name.replace("_handler", "").replace("_event", "")
|
|
133
|
+
elif component_type == "adapter":
|
|
134
|
+
context["adapter_name"] = component_name
|
|
135
|
+
elif component_type == "prompt":
|
|
136
|
+
context["prompt_name"] = component_name
|
|
137
|
+
elif component_type == "chatter":
|
|
138
|
+
context["chatter_name"] = component_name
|
|
139
|
+
elif component_type == "router":
|
|
140
|
+
context["router_name"] = component_name
|
|
141
|
+
|
|
142
|
+
return context
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _get_method_name(component_type: str) -> str:
|
|
146
|
+
"""
|
|
147
|
+
根据组件类型获取主要方法名
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
component_type: 组件类型
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
方法名
|
|
154
|
+
"""
|
|
155
|
+
method_map = {
|
|
156
|
+
"action": "execute",
|
|
157
|
+
"plus_command": "execute",
|
|
158
|
+
"tool": "run",
|
|
159
|
+
"event": "handle",
|
|
160
|
+
"adapter": "connect",
|
|
161
|
+
"prompt": "build",
|
|
162
|
+
"chatter": "chat",
|
|
163
|
+
"router": "route",
|
|
164
|
+
}
|
|
165
|
+
return method_map.get(component_type, "execute")
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Action 组件模板
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
ACTION_TEMPLATE = '''"""
|
|
6
|
+
{description}
|
|
7
|
+
|
|
8
|
+
Created by: {author}
|
|
9
|
+
Created at: {date}
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from src.common.logger import get_logger
|
|
13
|
+
from src.plugin_system import BaseAction, ActionActivationType, ChatMode
|
|
14
|
+
|
|
15
|
+
logger = get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class {class_name}(BaseAction):
|
|
19
|
+
"""
|
|
20
|
+
{description}
|
|
21
|
+
|
|
22
|
+
Action 组件用于执行聊天中的具体动作任务。
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# Action 元数据
|
|
26
|
+
action_name: str = "{component_name}"
|
|
27
|
+
action_description: str = "{description}"
|
|
28
|
+
|
|
29
|
+
# 激活配置
|
|
30
|
+
mode_enable: list[ChatMode] = [ChatMode.FOCUS, ChatMode.NORMAL] # 支持的聊天模式
|
|
31
|
+
parallel_action: bool = False # 是否允许与其他 Action 并行执行
|
|
32
|
+
|
|
33
|
+
# 专注模式激活配置
|
|
34
|
+
focus_activation_type: ActionActivationType = ActionActivationType.KEYWORD
|
|
35
|
+
# 普通模式激活配置
|
|
36
|
+
normal_activation_type: ActionActivationType = ActionActivationType.LLM_JUDGE
|
|
37
|
+
|
|
38
|
+
# 激活条件
|
|
39
|
+
activation_keywords: list[str] = ["关键词1", "关键词2"] # 关键词激活时使用
|
|
40
|
+
keyword_case_sensitive: bool = False # 关键词是否区分大小写
|
|
41
|
+
|
|
42
|
+
# LLM 判断激活的提示词
|
|
43
|
+
llm_judge_prompt: str = """
|
|
44
|
+
判断用户是否需要执行某个特定操作。
|
|
45
|
+
如果需要,返回 true,否则返回 false。
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
async def go_activate(self, llm_judge_model=None) -> bool:
|
|
49
|
+
"""
|
|
50
|
+
自定义激活逻辑(推荐方式)
|
|
51
|
+
|
|
52
|
+
可以组合使用以下工具函数:
|
|
53
|
+
- await self._keyword_match(["关键词"]) # 关键词匹配
|
|
54
|
+
- await self._random_activation(0.3) # 随机激活(30%概率)
|
|
55
|
+
- await self._llm_judge_activation(prompt, llm_judge_model) # LLM判断
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
是否激活此 Action
|
|
59
|
+
"""
|
|
60
|
+
# 示例:关键词匹配
|
|
61
|
+
return await self._keyword_match(self.activation_keywords, self.keyword_case_sensitive)
|
|
62
|
+
|
|
63
|
+
async def execute(self) -> tuple[bool, str]:
|
|
64
|
+
"""
|
|
65
|
+
执行 Action 的主要逻辑
|
|
66
|
+
|
|
67
|
+
可以使用以下方法:
|
|
68
|
+
- await self.send_text("文本内容") # 发送文本消息
|
|
69
|
+
- await self.send_image(image_base64) # 发送图片
|
|
70
|
+
- await self.send_command("command_name", args) # 调用命令
|
|
71
|
+
- await self.call_action("action_name", data) # 调用其他 Action
|
|
72
|
+
- await self.wait_for_new_message(timeout) # 等待用户回复
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
(是否成功, 结果消息)
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
logger.info(f"执行 Action: {{self.action_name}}")
|
|
79
|
+
|
|
80
|
+
# TODO: 实现 Action 的核心逻辑
|
|
81
|
+
|
|
82
|
+
# 示例:发送消息
|
|
83
|
+
await self.send_text("Action 执行成功!")
|
|
84
|
+
|
|
85
|
+
# 存储 Action 信息到上下文
|
|
86
|
+
await self.store_action_info(
|
|
87
|
+
action_build_into_prompt=True,
|
|
88
|
+
action_prompt_display=f"执行了 {{self.action_name}}",
|
|
89
|
+
action_done=True
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return True, "执行成功"
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error(f"Action 执行失败: {{e}}")
|
|
96
|
+
return False, f"执行失败: {{e}}"
|
|
97
|
+
'''
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_action_template() -> str:
|
|
101
|
+
"""获取 Action 组件模板"""
|
|
102
|
+
return ACTION_TEMPLATE
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Adapter 组件模板
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
ADAPTER_TEMPLATE = '''"""
|
|
6
|
+
{description}
|
|
7
|
+
|
|
8
|
+
Created by: {author}
|
|
9
|
+
Created at: {date}
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from mofox_wire import MessageEnvelope
|
|
15
|
+
from src.common.logger import get_logger
|
|
16
|
+
from src.plugin_system import BaseAdapter
|
|
17
|
+
|
|
18
|
+
logger = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class {class_name}(BaseAdapter):
|
|
22
|
+
"""
|
|
23
|
+
{description}
|
|
24
|
+
|
|
25
|
+
Adapter 组件用于连接不同的平台或服务。
|
|
26
|
+
|
|
27
|
+
支持的场景:
|
|
28
|
+
- QQ/微信等聊天平台
|
|
29
|
+
- Discord/Telegram 等国际平台
|
|
30
|
+
- 自定义 API 服务
|
|
31
|
+
- WebSocket/HTTP 协议适配
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# Adapter 元数据
|
|
35
|
+
adapter_name: str = "{adapter_name}"
|
|
36
|
+
adapter_version: str = "1.0.0"
|
|
37
|
+
adapter_author: str = "{author}"
|
|
38
|
+
adapter_description: str = "{description}"
|
|
39
|
+
|
|
40
|
+
# 是否在子进程中运行
|
|
41
|
+
run_in_subprocess: bool = False
|
|
42
|
+
# 子进程启动脚本路径(相对于插件目录)
|
|
43
|
+
subprocess_entry: str | None = None
|
|
44
|
+
|
|
45
|
+
async def from_platform_message(self, raw: Any) -> MessageEnvelope:
|
|
46
|
+
"""
|
|
47
|
+
将平台原始消息转换为标准 MessageEnvelope 格式
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
raw: 平台原始消息对象
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
MessageEnvelope: 标准消息信封
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
logger.debug(f"转换平台消息: {{raw}}")
|
|
57
|
+
|
|
58
|
+
# TODO: 解析平台消息并转换为 MessageEnvelope
|
|
59
|
+
# 示例:
|
|
60
|
+
# message_id = raw.get("message_id")
|
|
61
|
+
# user_id = raw.get("user_id")
|
|
62
|
+
# content = raw.get("content")
|
|
63
|
+
# timestamp = raw.get("timestamp")
|
|
64
|
+
#
|
|
65
|
+
# return MessageEnvelope(
|
|
66
|
+
# message_id=message_id,
|
|
67
|
+
# user_id=user_id,
|
|
68
|
+
# content=content,
|
|
69
|
+
# timestamp=timestamp,
|
|
70
|
+
# platform="your_platform"
|
|
71
|
+
# )
|
|
72
|
+
|
|
73
|
+
raise NotImplementedError("需要实现 from_platform_message 方法")
|
|
74
|
+
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.error(f"转换消息失败: {{e}}")
|
|
77
|
+
raise
|
|
78
|
+
|
|
79
|
+
async def _send_platform_message(self, envelope: MessageEnvelope) -> None:
|
|
80
|
+
"""
|
|
81
|
+
发送消息到平台
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
envelope: 要发送的消息信封
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
logger.info(f"发送消息: {{envelope.message_id}}")
|
|
88
|
+
|
|
89
|
+
# TODO: 实现发送消息逻辑
|
|
90
|
+
# 将 MessageEnvelope 转换为平台格式并发送
|
|
91
|
+
# 示例:
|
|
92
|
+
# platform_message = {{
|
|
93
|
+
# "target_id": envelope.target_id,
|
|
94
|
+
# "content": envelope.content,
|
|
95
|
+
# "message_type": envelope.message_type
|
|
96
|
+
# }}
|
|
97
|
+
# await self.platform_api.send(platform_message)
|
|
98
|
+
|
|
99
|
+
raise NotImplementedError("需要实现 _send_platform_message 方法")
|
|
100
|
+
|
|
101
|
+
except Exception as e:
|
|
102
|
+
logger.error(f"发送消息失败: {{e}}")
|
|
103
|
+
raise
|
|
104
|
+
|
|
105
|
+
async def on_adapter_loaded(self) -> None:
|
|
106
|
+
"""
|
|
107
|
+
适配器加载时的钩子
|
|
108
|
+
可以在这里执行初始化逻辑
|
|
109
|
+
"""
|
|
110
|
+
logger.info(f"{{self.adapter_name}} 适配器加载完成")
|
|
111
|
+
|
|
112
|
+
# TODO: 初始化逻辑
|
|
113
|
+
# 例如:建立连接、加载配置、启动后台任务等
|
|
114
|
+
|
|
115
|
+
async def on_adapter_unloaded(self) -> None:
|
|
116
|
+
"""
|
|
117
|
+
适配器卸载时的钩子
|
|
118
|
+
可以在这里执行清理逻辑
|
|
119
|
+
"""
|
|
120
|
+
logger.info(f"{{self.adapter_name}} 适配器卸载")
|
|
121
|
+
|
|
122
|
+
# TODO: 清理逻辑
|
|
123
|
+
# 例如:关闭连接、保存状态、停止后台任务等
|
|
124
|
+
'''
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def get_adapter_template() -> str:
|
|
128
|
+
"""获取 Adapter 组件模板"""
|
|
129
|
+
return ADAPTER_TEMPLATE
|