AstrBot 3.5.6__py3-none-any.whl → 4.7.0__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.
Files changed (288) hide show
  1. astrbot/api/__init__.py +16 -4
  2. astrbot/api/all.py +2 -1
  3. astrbot/api/event/__init__.py +5 -6
  4. astrbot/api/event/filter/__init__.py +37 -34
  5. astrbot/api/platform/__init__.py +7 -8
  6. astrbot/api/provider/__init__.py +8 -7
  7. astrbot/api/star/__init__.py +3 -4
  8. astrbot/api/util/__init__.py +2 -2
  9. astrbot/cli/__init__.py +1 -0
  10. astrbot/cli/__main__.py +18 -197
  11. astrbot/cli/commands/__init__.py +6 -0
  12. astrbot/cli/commands/cmd_conf.py +209 -0
  13. astrbot/cli/commands/cmd_init.py +56 -0
  14. astrbot/cli/commands/cmd_plug.py +245 -0
  15. astrbot/cli/commands/cmd_run.py +62 -0
  16. astrbot/cli/utils/__init__.py +18 -0
  17. astrbot/cli/utils/basic.py +76 -0
  18. astrbot/cli/utils/plugin.py +246 -0
  19. astrbot/cli/utils/version_comparator.py +90 -0
  20. astrbot/core/__init__.py +17 -19
  21. astrbot/core/agent/agent.py +14 -0
  22. astrbot/core/agent/handoff.py +38 -0
  23. astrbot/core/agent/hooks.py +30 -0
  24. astrbot/core/agent/mcp_client.py +385 -0
  25. astrbot/core/agent/message.py +175 -0
  26. astrbot/core/agent/response.py +14 -0
  27. astrbot/core/agent/run_context.py +22 -0
  28. astrbot/core/agent/runners/__init__.py +3 -0
  29. astrbot/core/agent/runners/base.py +65 -0
  30. astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
  31. astrbot/core/agent/runners/coze/coze_api_client.py +324 -0
  32. astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
  33. astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
  34. astrbot/core/agent/runners/dify/dify_api_client.py +195 -0
  35. astrbot/core/agent/runners/tool_loop_agent_runner.py +400 -0
  36. astrbot/core/agent/tool.py +285 -0
  37. astrbot/core/agent/tool_executor.py +17 -0
  38. astrbot/core/astr_agent_context.py +19 -0
  39. astrbot/core/astr_agent_hooks.py +36 -0
  40. astrbot/core/astr_agent_run_util.py +80 -0
  41. astrbot/core/astr_agent_tool_exec.py +246 -0
  42. astrbot/core/astrbot_config_mgr.py +275 -0
  43. astrbot/core/config/__init__.py +2 -2
  44. astrbot/core/config/astrbot_config.py +60 -20
  45. astrbot/core/config/default.py +1972 -453
  46. astrbot/core/config/i18n_utils.py +110 -0
  47. astrbot/core/conversation_mgr.py +285 -75
  48. astrbot/core/core_lifecycle.py +167 -62
  49. astrbot/core/db/__init__.py +305 -102
  50. astrbot/core/db/migration/helper.py +69 -0
  51. astrbot/core/db/migration/migra_3_to_4.py +357 -0
  52. astrbot/core/db/migration/migra_45_to_46.py +44 -0
  53. astrbot/core/db/migration/migra_webchat_session.py +131 -0
  54. astrbot/core/db/migration/shared_preferences_v3.py +48 -0
  55. astrbot/core/db/migration/sqlite_v3.py +497 -0
  56. astrbot/core/db/po.py +259 -55
  57. astrbot/core/db/sqlite.py +773 -528
  58. astrbot/core/db/vec_db/base.py +73 -0
  59. astrbot/core/db/vec_db/faiss_impl/__init__.py +3 -0
  60. astrbot/core/db/vec_db/faiss_impl/document_storage.py +392 -0
  61. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +93 -0
  62. astrbot/core/db/vec_db/faiss_impl/sqlite_init.sql +17 -0
  63. astrbot/core/db/vec_db/faiss_impl/vec_db.py +204 -0
  64. astrbot/core/event_bus.py +26 -22
  65. astrbot/core/exceptions.py +9 -0
  66. astrbot/core/file_token_service.py +98 -0
  67. astrbot/core/initial_loader.py +19 -10
  68. astrbot/core/knowledge_base/chunking/__init__.py +9 -0
  69. astrbot/core/knowledge_base/chunking/base.py +25 -0
  70. astrbot/core/knowledge_base/chunking/fixed_size.py +59 -0
  71. astrbot/core/knowledge_base/chunking/recursive.py +161 -0
  72. astrbot/core/knowledge_base/kb_db_sqlite.py +301 -0
  73. astrbot/core/knowledge_base/kb_helper.py +642 -0
  74. astrbot/core/knowledge_base/kb_mgr.py +330 -0
  75. astrbot/core/knowledge_base/models.py +120 -0
  76. astrbot/core/knowledge_base/parsers/__init__.py +13 -0
  77. astrbot/core/knowledge_base/parsers/base.py +51 -0
  78. astrbot/core/knowledge_base/parsers/markitdown_parser.py +26 -0
  79. astrbot/core/knowledge_base/parsers/pdf_parser.py +101 -0
  80. astrbot/core/knowledge_base/parsers/text_parser.py +42 -0
  81. astrbot/core/knowledge_base/parsers/url_parser.py +103 -0
  82. astrbot/core/knowledge_base/parsers/util.py +13 -0
  83. astrbot/core/knowledge_base/prompts.py +65 -0
  84. astrbot/core/knowledge_base/retrieval/__init__.py +14 -0
  85. astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
  86. astrbot/core/knowledge_base/retrieval/manager.py +276 -0
  87. astrbot/core/knowledge_base/retrieval/rank_fusion.py +142 -0
  88. astrbot/core/knowledge_base/retrieval/sparse_retriever.py +136 -0
  89. astrbot/core/log.py +21 -15
  90. astrbot/core/message/components.py +413 -287
  91. astrbot/core/message/message_event_result.py +35 -24
  92. astrbot/core/persona_mgr.py +192 -0
  93. astrbot/core/pipeline/__init__.py +14 -14
  94. astrbot/core/pipeline/content_safety_check/stage.py +13 -9
  95. astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
  96. astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +13 -14
  97. astrbot/core/pipeline/content_safety_check/strategies/keywords.py +2 -1
  98. astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
  99. astrbot/core/pipeline/context.py +7 -1
  100. astrbot/core/pipeline/context_utils.py +107 -0
  101. astrbot/core/pipeline/preprocess_stage/stage.py +63 -36
  102. astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
  103. astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +464 -0
  104. astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
  105. astrbot/core/pipeline/process_stage/method/star_request.py +26 -32
  106. astrbot/core/pipeline/process_stage/stage.py +21 -15
  107. astrbot/core/pipeline/process_stage/utils.py +125 -0
  108. astrbot/core/pipeline/rate_limit_check/stage.py +34 -36
  109. astrbot/core/pipeline/respond/stage.py +142 -101
  110. astrbot/core/pipeline/result_decorate/stage.py +124 -57
  111. astrbot/core/pipeline/scheduler.py +21 -16
  112. astrbot/core/pipeline/session_status_check/stage.py +37 -0
  113. astrbot/core/pipeline/stage.py +11 -76
  114. astrbot/core/pipeline/waking_check/stage.py +69 -33
  115. astrbot/core/pipeline/whitelist_check/stage.py +10 -7
  116. astrbot/core/platform/__init__.py +6 -6
  117. astrbot/core/platform/astr_message_event.py +107 -129
  118. astrbot/core/platform/astrbot_message.py +32 -12
  119. astrbot/core/platform/manager.py +62 -18
  120. astrbot/core/platform/message_session.py +30 -0
  121. astrbot/core/platform/platform.py +16 -24
  122. astrbot/core/platform/platform_metadata.py +9 -4
  123. astrbot/core/platform/register.py +12 -7
  124. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +136 -60
  125. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +126 -46
  126. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +63 -31
  127. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +30 -26
  128. astrbot/core/platform/sources/discord/client.py +129 -0
  129. astrbot/core/platform/sources/discord/components.py +139 -0
  130. astrbot/core/platform/sources/discord/discord_platform_adapter.py +473 -0
  131. astrbot/core/platform/sources/discord/discord_platform_event.py +313 -0
  132. astrbot/core/platform/sources/lark/lark_adapter.py +27 -18
  133. astrbot/core/platform/sources/lark/lark_event.py +39 -13
  134. astrbot/core/platform/sources/misskey/misskey_adapter.py +770 -0
  135. astrbot/core/platform/sources/misskey/misskey_api.py +964 -0
  136. astrbot/core/platform/sources/misskey/misskey_event.py +163 -0
  137. astrbot/core/platform/sources/misskey/misskey_utils.py +550 -0
  138. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +149 -33
  139. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
  140. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
  141. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
  142. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +14 -8
  143. astrbot/core/platform/sources/satori/satori_adapter.py +792 -0
  144. astrbot/core/platform/sources/satori/satori_event.py +432 -0
  145. astrbot/core/platform/sources/slack/client.py +164 -0
  146. astrbot/core/platform/sources/slack/slack_adapter.py +416 -0
  147. astrbot/core/platform/sources/slack/slack_event.py +253 -0
  148. astrbot/core/platform/sources/telegram/tg_adapter.py +100 -43
  149. astrbot/core/platform/sources/telegram/tg_event.py +136 -36
  150. astrbot/core/platform/sources/webchat/webchat_adapter.py +72 -22
  151. astrbot/core/platform/sources/webchat/webchat_event.py +46 -22
  152. astrbot/core/platform/sources/webchat/webchat_queue_mgr.py +35 -0
  153. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +926 -0
  154. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +178 -0
  155. astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +159 -0
  156. astrbot/core/platform/sources/wecom/wecom_adapter.py +169 -27
  157. astrbot/core/platform/sources/wecom/wecom_event.py +162 -77
  158. astrbot/core/platform/sources/wecom/wecom_kf.py +279 -0
  159. astrbot/core/platform/sources/wecom/wecom_kf_message.py +196 -0
  160. astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +297 -0
  161. astrbot/core/platform/sources/wecom_ai_bot/__init__.py +15 -0
  162. astrbot/core/platform/sources/wecom_ai_bot/ierror.py +19 -0
  163. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +472 -0
  164. astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +417 -0
  165. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +152 -0
  166. astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +153 -0
  167. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +168 -0
  168. astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +209 -0
  169. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +306 -0
  170. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +186 -0
  171. astrbot/core/platform_message_history_mgr.py +49 -0
  172. astrbot/core/provider/__init__.py +2 -3
  173. astrbot/core/provider/entites.py +8 -8
  174. astrbot/core/provider/entities.py +154 -98
  175. astrbot/core/provider/func_tool_manager.py +446 -458
  176. astrbot/core/provider/manager.py +345 -207
  177. astrbot/core/provider/provider.py +188 -73
  178. astrbot/core/provider/register.py +9 -7
  179. astrbot/core/provider/sources/anthropic_source.py +295 -115
  180. astrbot/core/provider/sources/azure_tts_source.py +224 -0
  181. astrbot/core/provider/sources/bailian_rerank_source.py +236 -0
  182. astrbot/core/provider/sources/dashscope_tts.py +138 -14
  183. astrbot/core/provider/sources/edge_tts_source.py +24 -19
  184. astrbot/core/provider/sources/fishaudio_tts_api_source.py +58 -13
  185. astrbot/core/provider/sources/gemini_embedding_source.py +61 -0
  186. astrbot/core/provider/sources/gemini_source.py +310 -132
  187. astrbot/core/provider/sources/gemini_tts_source.py +81 -0
  188. astrbot/core/provider/sources/groq_source.py +15 -0
  189. astrbot/core/provider/sources/gsv_selfhosted_source.py +151 -0
  190. astrbot/core/provider/sources/gsvi_tts_source.py +14 -7
  191. astrbot/core/provider/sources/minimax_tts_api_source.py +159 -0
  192. astrbot/core/provider/sources/openai_embedding_source.py +40 -0
  193. astrbot/core/provider/sources/openai_source.py +241 -145
  194. astrbot/core/provider/sources/openai_tts_api_source.py +18 -7
  195. astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
  196. astrbot/core/provider/sources/vllm_rerank_source.py +71 -0
  197. astrbot/core/provider/sources/volcengine_tts.py +115 -0
  198. astrbot/core/provider/sources/whisper_api_source.py +18 -13
  199. astrbot/core/provider/sources/whisper_selfhosted_source.py +19 -12
  200. astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
  201. astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
  202. astrbot/core/provider/sources/zhipu_source.py +6 -73
  203. astrbot/core/star/__init__.py +43 -11
  204. astrbot/core/star/config.py +17 -18
  205. astrbot/core/star/context.py +362 -138
  206. astrbot/core/star/filter/__init__.py +4 -3
  207. astrbot/core/star/filter/command.py +111 -35
  208. astrbot/core/star/filter/command_group.py +46 -34
  209. astrbot/core/star/filter/custom_filter.py +6 -5
  210. astrbot/core/star/filter/event_message_type.py +4 -2
  211. astrbot/core/star/filter/permission.py +4 -2
  212. astrbot/core/star/filter/platform_adapter_type.py +45 -12
  213. astrbot/core/star/filter/regex.py +4 -2
  214. astrbot/core/star/register/__init__.py +19 -15
  215. astrbot/core/star/register/star.py +41 -13
  216. astrbot/core/star/register/star_handler.py +236 -86
  217. astrbot/core/star/session_llm_manager.py +280 -0
  218. astrbot/core/star/session_plugin_manager.py +170 -0
  219. astrbot/core/star/star.py +36 -43
  220. astrbot/core/star/star_handler.py +47 -85
  221. astrbot/core/star/star_manager.py +442 -260
  222. astrbot/core/star/star_tools.py +167 -45
  223. astrbot/core/star/updator.py +17 -20
  224. astrbot/core/umop_config_router.py +106 -0
  225. astrbot/core/updator.py +38 -13
  226. astrbot/core/utils/astrbot_path.py +39 -0
  227. astrbot/core/utils/command_parser.py +1 -1
  228. astrbot/core/utils/io.py +119 -60
  229. astrbot/core/utils/log_pipe.py +1 -1
  230. astrbot/core/utils/metrics.py +11 -10
  231. astrbot/core/utils/migra_helper.py +73 -0
  232. astrbot/core/utils/path_util.py +63 -62
  233. astrbot/core/utils/pip_installer.py +37 -15
  234. astrbot/core/utils/session_lock.py +29 -0
  235. astrbot/core/utils/session_waiter.py +19 -20
  236. astrbot/core/utils/shared_preferences.py +174 -34
  237. astrbot/core/utils/t2i/__init__.py +4 -1
  238. astrbot/core/utils/t2i/local_strategy.py +386 -238
  239. astrbot/core/utils/t2i/network_strategy.py +109 -49
  240. astrbot/core/utils/t2i/renderer.py +29 -14
  241. astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
  242. astrbot/core/utils/t2i/template_manager.py +111 -0
  243. astrbot/core/utils/tencent_record_helper.py +115 -1
  244. astrbot/core/utils/version_comparator.py +10 -13
  245. astrbot/core/zip_updator.py +112 -65
  246. astrbot/dashboard/routes/__init__.py +20 -13
  247. astrbot/dashboard/routes/auth.py +20 -9
  248. astrbot/dashboard/routes/chat.py +297 -141
  249. astrbot/dashboard/routes/config.py +652 -55
  250. astrbot/dashboard/routes/conversation.py +107 -37
  251. astrbot/dashboard/routes/file.py +26 -0
  252. astrbot/dashboard/routes/knowledge_base.py +1244 -0
  253. astrbot/dashboard/routes/log.py +27 -2
  254. astrbot/dashboard/routes/persona.py +202 -0
  255. astrbot/dashboard/routes/plugin.py +197 -139
  256. astrbot/dashboard/routes/route.py +27 -7
  257. astrbot/dashboard/routes/session_management.py +354 -0
  258. astrbot/dashboard/routes/stat.py +85 -18
  259. astrbot/dashboard/routes/static_file.py +5 -2
  260. astrbot/dashboard/routes/t2i.py +233 -0
  261. astrbot/dashboard/routes/tools.py +184 -120
  262. astrbot/dashboard/routes/update.py +59 -36
  263. astrbot/dashboard/server.py +96 -36
  264. astrbot/dashboard/utils.py +165 -0
  265. astrbot-4.7.0.dist-info/METADATA +294 -0
  266. astrbot-4.7.0.dist-info/RECORD +274 -0
  267. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/WHEEL +1 -1
  268. astrbot/core/db/plugin/sqlite_impl.py +0 -112
  269. astrbot/core/db/sqlite_init.sql +0 -50
  270. astrbot/core/pipeline/platform_compatibility/stage.py +0 -56
  271. astrbot/core/pipeline/process_stage/method/llm_request.py +0 -606
  272. astrbot/core/platform/sources/gewechat/client.py +0 -806
  273. astrbot/core/platform/sources/gewechat/downloader.py +0 -55
  274. astrbot/core/platform/sources/gewechat/gewechat_event.py +0 -255
  275. astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py +0 -103
  276. astrbot/core/platform/sources/gewechat/xml_data_parser.py +0 -110
  277. astrbot/core/provider/sources/dashscope_source.py +0 -203
  278. astrbot/core/provider/sources/dify_source.py +0 -281
  279. astrbot/core/provider/sources/llmtuner_source.py +0 -132
  280. astrbot/core/rag/embedding/openai_source.py +0 -20
  281. astrbot/core/rag/knowledge_db_mgr.py +0 -94
  282. astrbot/core/rag/store/__init__.py +0 -9
  283. astrbot/core/rag/store/chroma_db.py +0 -42
  284. astrbot/core/utils/dify_api_client.py +0 -152
  285. astrbot-3.5.6.dist-info/METADATA +0 -249
  286. astrbot-3.5.6.dist-info/RECORD +0 -158
  287. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/entry_points.txt +0 -0
  288. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,246 @@
1
+ import shutil
2
+ import tempfile
3
+ from enum import Enum
4
+ from io import BytesIO
5
+ from pathlib import Path
6
+ from zipfile import ZipFile
7
+
8
+ import click
9
+ import httpx
10
+ import yaml
11
+
12
+ from .version_comparator import VersionComparator
13
+
14
+
15
+ class PluginStatus(str, Enum):
16
+ INSTALLED = "已安装"
17
+ NEED_UPDATE = "需更新"
18
+ NOT_INSTALLED = "未安装"
19
+ NOT_PUBLISHED = "未发布"
20
+
21
+
22
+ def get_git_repo(url: str, target_path: Path, proxy: str | None = None):
23
+ """从 Git 仓库下载代码并解压到指定路径"""
24
+ temp_dir = Path(tempfile.mkdtemp())
25
+ try:
26
+ # 解析仓库信息
27
+ repo_namespace = url.split("/")[-2:]
28
+ author = repo_namespace[0]
29
+ repo = repo_namespace[1]
30
+
31
+ # 尝试获取最新的 release
32
+ release_url = f"https://api.github.com/repos/{author}/{repo}/releases"
33
+ try:
34
+ with httpx.Client(
35
+ proxy=proxy if proxy else None,
36
+ follow_redirects=True,
37
+ ) as client:
38
+ resp = client.get(release_url)
39
+ resp.raise_for_status()
40
+ releases = resp.json()
41
+
42
+ if releases:
43
+ # 使用最新的 release
44
+ download_url = releases[0]["zipball_url"]
45
+ else:
46
+ # 没有 release,使用默认分支
47
+ click.echo(f"正在从默认分支下载 {author}/{repo}")
48
+ download_url = f"https://github.com/{author}/{repo}/archive/refs/heads/master.zip"
49
+ except Exception as e:
50
+ click.echo(f"获取 release 信息失败: {e},将直接使用提供的 URL")
51
+ download_url = url
52
+
53
+ # 应用代理
54
+ if proxy:
55
+ download_url = f"{proxy}/{download_url}"
56
+
57
+ # 下载并解压
58
+ with httpx.Client(
59
+ proxy=proxy if proxy else None,
60
+ follow_redirects=True,
61
+ ) as client:
62
+ resp = client.get(download_url)
63
+ if (
64
+ resp.status_code == 404
65
+ and "archive/refs/heads/master.zip" in download_url
66
+ ):
67
+ alt_url = download_url.replace("master.zip", "main.zip")
68
+ click.echo("master 分支不存在,尝试下载 main 分支")
69
+ resp = client.get(alt_url)
70
+ resp.raise_for_status()
71
+ else:
72
+ resp.raise_for_status()
73
+ zip_content = BytesIO(resp.content)
74
+ with ZipFile(zip_content) as z:
75
+ z.extractall(temp_dir)
76
+ namelist = z.namelist()
77
+ root_dir = Path(namelist[0]).parts[0] if namelist else ""
78
+ if target_path.exists():
79
+ shutil.rmtree(target_path)
80
+ shutil.move(temp_dir / root_dir, target_path)
81
+ finally:
82
+ if temp_dir.exists():
83
+ shutil.rmtree(temp_dir, ignore_errors=True)
84
+
85
+
86
+ def load_yaml_metadata(plugin_dir: Path) -> dict:
87
+ """从 metadata.yaml 文件加载插件元数据
88
+
89
+ Args:
90
+ plugin_dir: 插件目录路径
91
+
92
+ Returns:
93
+ dict: 包含元数据的字典,如果读取失败则返回空字典
94
+
95
+ """
96
+ yaml_path = plugin_dir / "metadata.yaml"
97
+ if yaml_path.exists():
98
+ try:
99
+ return yaml.safe_load(yaml_path.read_text(encoding="utf-8")) or {}
100
+ except Exception as e:
101
+ click.echo(f"读取 {yaml_path} 失败: {e}", err=True)
102
+ return {}
103
+
104
+
105
+ def build_plug_list(plugins_dir: Path) -> list:
106
+ """构建插件列表,包含本地和在线插件信息
107
+
108
+ Args:
109
+ plugins_dir (Path): 插件目录路径
110
+
111
+ Returns:
112
+ list: 包含插件信息的字典列表
113
+
114
+ """
115
+ # 获取本地插件信息
116
+ result = []
117
+ if plugins_dir.exists():
118
+ for plugin_name in [d.name for d in plugins_dir.glob("*") if d.is_dir()]:
119
+ plugin_dir = plugins_dir / plugin_name
120
+
121
+ # 从 metadata.yaml 加载元数据
122
+ metadata = load_yaml_metadata(plugin_dir)
123
+
124
+ if "desc" not in metadata and "description" in metadata:
125
+ metadata["desc"] = metadata["description"]
126
+
127
+ # 如果成功加载元数据,添加到结果列表
128
+ if metadata and all(
129
+ k in metadata for k in ["name", "desc", "version", "author", "repo"]
130
+ ):
131
+ result.append(
132
+ {
133
+ "name": str(metadata.get("name", "")),
134
+ "desc": str(metadata.get("desc", "")),
135
+ "version": str(metadata.get("version", "")),
136
+ "author": str(metadata.get("author", "")),
137
+ "repo": str(metadata.get("repo", "")),
138
+ "status": PluginStatus.INSTALLED,
139
+ "local_path": str(plugin_dir),
140
+ },
141
+ )
142
+
143
+ # 获取在线插件列表
144
+ online_plugins = []
145
+ try:
146
+ with httpx.Client() as client:
147
+ resp = client.get("https://api.soulter.top/astrbot/plugins")
148
+ resp.raise_for_status()
149
+ data = resp.json()
150
+ for plugin_id, plugin_info in data.items():
151
+ online_plugins.append(
152
+ {
153
+ "name": str(plugin_id),
154
+ "desc": str(plugin_info.get("desc", "")),
155
+ "version": str(plugin_info.get("version", "")),
156
+ "author": str(plugin_info.get("author", "")),
157
+ "repo": str(plugin_info.get("repo", "")),
158
+ "status": PluginStatus.NOT_INSTALLED,
159
+ "local_path": None,
160
+ },
161
+ )
162
+ except Exception as e:
163
+ click.echo(f"获取在线插件列表失败: {e}", err=True)
164
+
165
+ # 与在线插件比对,更新状态
166
+ online_plugin_names = {plugin["name"] for plugin in online_plugins}
167
+ for local_plugin in result:
168
+ if local_plugin["name"] in online_plugin_names:
169
+ # 查找对应的在线插件
170
+ online_plugin = next(
171
+ p for p in online_plugins if p["name"] == local_plugin["name"]
172
+ )
173
+ if (
174
+ VersionComparator.compare_version(
175
+ local_plugin["version"],
176
+ online_plugin["version"],
177
+ )
178
+ < 0
179
+ ):
180
+ local_plugin["status"] = PluginStatus.NEED_UPDATE
181
+ else:
182
+ # 本地插件未在线上发布
183
+ local_plugin["status"] = PluginStatus.NOT_PUBLISHED
184
+
185
+ # 添加未安装的在线插件
186
+ for online_plugin in online_plugins:
187
+ if not any(plugin["name"] == online_plugin["name"] for plugin in result):
188
+ result.append(online_plugin)
189
+
190
+ return result
191
+
192
+
193
+ def manage_plugin(
194
+ plugin: dict,
195
+ plugins_dir: Path,
196
+ is_update: bool = False,
197
+ proxy: str | None = None,
198
+ ) -> None:
199
+ """安装或更新插件
200
+
201
+ Args:
202
+ plugin (dict): 插件信息字典
203
+ plugins_dir (Path): 插件目录
204
+ is_update (bool, optional): 是否为更新操作. 默认为 False
205
+ proxy (str, optional): 代理服务器地址
206
+
207
+ """
208
+ plugin_name = plugin["name"]
209
+ repo_url = plugin["repo"]
210
+
211
+ # 如果是更新且有本地路径,直接使用本地路径
212
+ if is_update and plugin.get("local_path"):
213
+ target_path = Path(plugin["local_path"])
214
+ else:
215
+ target_path = plugins_dir / plugin_name
216
+
217
+ backup_path = Path(f"{target_path}_backup") if is_update else None
218
+
219
+ # 检查插件是否存在
220
+ if is_update and not target_path.exists():
221
+ raise click.ClickException(f"插件 {plugin_name} 未安装,无法更新")
222
+
223
+ # 备份现有插件
224
+ if is_update and backup_path is not None and backup_path.exists():
225
+ shutil.rmtree(backup_path)
226
+ if is_update and backup_path is not None:
227
+ shutil.copytree(target_path, backup_path)
228
+
229
+ try:
230
+ click.echo(
231
+ f"正在从 {repo_url} {'更新' if is_update else '下载'}插件 {plugin_name}...",
232
+ )
233
+ get_git_repo(repo_url, target_path, proxy)
234
+
235
+ # 更新成功,删除备份
236
+ if is_update and backup_path is not None and backup_path.exists():
237
+ shutil.rmtree(backup_path)
238
+ click.echo(f"插件 {plugin_name} {'更新' if is_update else '安装'}成功")
239
+ except Exception as e:
240
+ if target_path.exists():
241
+ shutil.rmtree(target_path, ignore_errors=True)
242
+ if is_update and backup_path is not None and backup_path.exists():
243
+ shutil.move(backup_path, target_path)
244
+ raise click.ClickException(
245
+ f"{'更新' if is_update else '安装'}插件 {plugin_name} 时出错: {e}",
246
+ )
@@ -0,0 +1,90 @@
1
+ """拷贝自 astrbot.core.utils.version_comparator"""
2
+
3
+ import re
4
+
5
+
6
+ class VersionComparator:
7
+ @staticmethod
8
+ def compare_version(v1: str, v2: str) -> int:
9
+ """根据 Semver 语义版本规范来比较版本号的大小。支持不仅局限于 3 个数字的版本号,并处理预发布标签。
10
+
11
+ 参考: https://semver.org/lang/zh-CN/
12
+
13
+ 返回 1 表示 v1 > v2,返回 -1 表示 v1 < v2,返回 0 表示 v1 = v2。
14
+ """
15
+ v1 = v1.lower().replace("v", "")
16
+ v2 = v2.lower().replace("v", "")
17
+
18
+ def split_version(version):
19
+ match = re.match(
20
+ r"^([0-9]+(?:\.[0-9]+)*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+(.+))?$",
21
+ version,
22
+ )
23
+ if not match:
24
+ return [], None
25
+ major_minor_patch = match.group(1).split(".")
26
+ prerelease = match.group(2)
27
+ # buildmetadata = match.group(3) # 构建元数据在比较时忽略
28
+ parts = [int(x) for x in major_minor_patch]
29
+ prerelease = VersionComparator._split_prerelease(prerelease)
30
+ return parts, prerelease
31
+
32
+ v1_parts, v1_prerelease = split_version(v1)
33
+ v2_parts, v2_prerelease = split_version(v2)
34
+
35
+ # 比较数字部分
36
+ length = max(len(v1_parts), len(v2_parts))
37
+ v1_parts.extend([0] * (length - len(v1_parts)))
38
+ v2_parts.extend([0] * (length - len(v2_parts)))
39
+
40
+ for i in range(length):
41
+ if v1_parts[i] > v2_parts[i]:
42
+ return 1
43
+ if v1_parts[i] < v2_parts[i]:
44
+ return -1
45
+
46
+ # 比较预发布标签
47
+ if v1_prerelease is None and v2_prerelease is not None:
48
+ return 1 # 没有预发布标签的版本高于有预发布标签的版本
49
+ if v1_prerelease is not None and v2_prerelease is None:
50
+ return -1 # 有预发布标签的版本低于没有预发布标签的版本
51
+ if v1_prerelease is not None and v2_prerelease is not None:
52
+ len_pre = max(len(v1_prerelease), len(v2_prerelease))
53
+ for i in range(len_pre):
54
+ p1 = v1_prerelease[i] if i < len(v1_prerelease) else None
55
+ p2 = v2_prerelease[i] if i < len(v2_prerelease) else None
56
+
57
+ if p1 is None and p2 is not None:
58
+ return -1
59
+ if p1 is not None and p2 is None:
60
+ return 1
61
+ if isinstance(p1, int) and isinstance(p2, str):
62
+ return -1
63
+ if isinstance(p1, str) and isinstance(p2, int):
64
+ return 1
65
+ if isinstance(p1, int) and isinstance(p2, int):
66
+ if p1 > p2:
67
+ return 1
68
+ if p1 < p2:
69
+ return -1
70
+ elif isinstance(p1, str) and isinstance(p2, str):
71
+ if p1 > p2:
72
+ return 1
73
+ if p1 < p2:
74
+ return -1
75
+ return 0 # 预发布标签完全相同
76
+
77
+ return 0 # 数字部分和预发布标签都相同
78
+
79
+ @staticmethod
80
+ def _split_prerelease(prerelease):
81
+ if not prerelease:
82
+ return None
83
+ parts = prerelease.split(".")
84
+ result = []
85
+ for part in parts:
86
+ if part.isdigit():
87
+ result.append(int(part))
88
+ else:
89
+ result.append(part)
90
+ return result
astrbot/core/__init__.py CHANGED
@@ -1,33 +1,31 @@
1
1
  import os
2
- import asyncio
3
- from .log import LogManager, LogBroker # noqa
4
- from astrbot.core.utils.t2i.renderer import HtmlRenderer
5
- from astrbot.core.utils.shared_preferences import SharedPreferences
6
- from astrbot.core.utils.pip_installer import PipInstaller
7
- from astrbot.core.db.sqlite import SQLiteDatabase
8
- from astrbot.core.config.default import DB_PATH
2
+
9
3
  from astrbot.core.config import AstrBotConfig
4
+ from astrbot.core.config.default import DB_PATH
5
+ from astrbot.core.db.sqlite import SQLiteDatabase
6
+ from astrbot.core.file_token_service import FileTokenService
7
+ from astrbot.core.utils.pip_installer import PipInstaller
8
+ from astrbot.core.utils.shared_preferences import SharedPreferences
9
+ from astrbot.core.utils.t2i.renderer import HtmlRenderer
10
+
11
+ from .log import LogBroker, LogManager # noqa
12
+ from .utils.astrbot_path import get_astrbot_data_path
10
13
 
11
14
  # 初始化数据存储文件夹
12
- os.makedirs("data", exist_ok=True)
15
+ os.makedirs(get_astrbot_data_path(), exist_ok=True)
16
+
17
+ DEMO_MODE = os.getenv("DEMO_MODE", False)
13
18
 
14
19
  astrbot_config = AstrBotConfig()
15
20
  t2i_base_url = astrbot_config.get("t2i_endpoint", "https://t2i.soulter.top/text2img")
16
21
  html_renderer = HtmlRenderer(t2i_base_url)
17
22
  logger = LogManager.GetLogger(log_name="astrbot")
18
-
19
- if os.environ.get("TESTING", ""):
20
- logger.setLevel("DEBUG")
21
-
22
23
  db_helper = SQLiteDatabase(DB_PATH)
23
- sp = (
24
- SharedPreferences()
25
- ) # 简单的偏好设置存储, 这里后续应该存储到数据库中, 一些部分可以存储到配置中
24
+ # 简单的偏好设置存储, 这里后续应该存储到数据库中, 一些部分可以存储到配置中
25
+ sp = SharedPreferences(db_helper=db_helper)
26
+ # 文件令牌服务
27
+ file_token_service = FileTokenService()
26
28
  pip_installer = PipInstaller(
27
29
  astrbot_config.get("pip_install_arg", ""),
28
30
  astrbot_config.get("pypi_index_url", None),
29
31
  )
30
- web_chat_queue = asyncio.Queue(maxsize=32)
31
- web_chat_back_queue = asyncio.Queue(maxsize=32)
32
- WEBUI_SK = "Advanced_System_for_Text_Response_and_Bot_Operations_Tool"
33
- DEMO_MODE = os.getenv("DEMO_MODE", False)
@@ -0,0 +1,14 @@
1
+ from dataclasses import dataclass
2
+ from typing import Generic
3
+
4
+ from .hooks import BaseAgentRunHooks
5
+ from .run_context import TContext
6
+ from .tool import FunctionTool
7
+
8
+
9
+ @dataclass
10
+ class Agent(Generic[TContext]):
11
+ name: str
12
+ instructions: str | None = None
13
+ tools: list[str | FunctionTool] | None = None
14
+ run_hooks: BaseAgentRunHooks[TContext] | None = None
@@ -0,0 +1,38 @@
1
+ from typing import Generic
2
+
3
+ from .agent import Agent
4
+ from .run_context import TContext
5
+ from .tool import FunctionTool
6
+
7
+
8
+ class HandoffTool(FunctionTool, Generic[TContext]):
9
+ """Handoff tool for delegating tasks to another agent."""
10
+
11
+ def __init__(
12
+ self,
13
+ agent: Agent[TContext],
14
+ parameters: dict | None = None,
15
+ **kwargs,
16
+ ):
17
+ self.agent = agent
18
+ super().__init__(
19
+ name=f"transfer_to_{agent.name}",
20
+ parameters=parameters or self.default_parameters(),
21
+ description=agent.instructions or self.default_description(agent.name),
22
+ **kwargs,
23
+ )
24
+
25
+ def default_parameters(self) -> dict:
26
+ return {
27
+ "type": "object",
28
+ "properties": {
29
+ "input": {
30
+ "type": "string",
31
+ "description": "The input to be handed off to another agent. This should be a clear and concise request or task.",
32
+ },
33
+ },
34
+ }
35
+
36
+ def default_description(self, agent_name: str | None) -> str:
37
+ agent_name = agent_name or "another"
38
+ return f"Delegate tasks to {self.name} agent to handle the request."
@@ -0,0 +1,30 @@
1
+ from typing import Generic
2
+
3
+ import mcp
4
+
5
+ from astrbot.core.agent.tool import FunctionTool
6
+ from astrbot.core.provider.entities import LLMResponse
7
+
8
+ from .run_context import ContextWrapper, TContext
9
+
10
+
11
+ class BaseAgentRunHooks(Generic[TContext]):
12
+ async def on_agent_begin(self, run_context: ContextWrapper[TContext]): ...
13
+ async def on_tool_start(
14
+ self,
15
+ run_context: ContextWrapper[TContext],
16
+ tool: FunctionTool,
17
+ tool_args: dict | None,
18
+ ): ...
19
+ async def on_tool_end(
20
+ self,
21
+ run_context: ContextWrapper[TContext],
22
+ tool: FunctionTool,
23
+ tool_args: dict | None,
24
+ tool_result: mcp.types.CallToolResult | None,
25
+ ): ...
26
+ async def on_agent_done(
27
+ self,
28
+ run_context: ContextWrapper[TContext],
29
+ llm_response: LLMResponse,
30
+ ): ...