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.
- astrbot/api/__init__.py +16 -4
- astrbot/api/all.py +2 -1
- astrbot/api/event/__init__.py +5 -6
- astrbot/api/event/filter/__init__.py +37 -34
- astrbot/api/platform/__init__.py +7 -8
- astrbot/api/provider/__init__.py +8 -7
- astrbot/api/star/__init__.py +3 -4
- astrbot/api/util/__init__.py +2 -2
- astrbot/cli/__init__.py +1 -0
- astrbot/cli/__main__.py +18 -197
- astrbot/cli/commands/__init__.py +6 -0
- astrbot/cli/commands/cmd_conf.py +209 -0
- astrbot/cli/commands/cmd_init.py +56 -0
- astrbot/cli/commands/cmd_plug.py +245 -0
- astrbot/cli/commands/cmd_run.py +62 -0
- astrbot/cli/utils/__init__.py +18 -0
- astrbot/cli/utils/basic.py +76 -0
- astrbot/cli/utils/plugin.py +246 -0
- astrbot/cli/utils/version_comparator.py +90 -0
- astrbot/core/__init__.py +17 -19
- astrbot/core/agent/agent.py +14 -0
- astrbot/core/agent/handoff.py +38 -0
- astrbot/core/agent/hooks.py +30 -0
- astrbot/core/agent/mcp_client.py +385 -0
- astrbot/core/agent/message.py +175 -0
- astrbot/core/agent/response.py +14 -0
- astrbot/core/agent/run_context.py +22 -0
- astrbot/core/agent/runners/__init__.py +3 -0
- astrbot/core/agent/runners/base.py +65 -0
- astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
- astrbot/core/agent/runners/coze/coze_api_client.py +324 -0
- astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
- astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
- astrbot/core/agent/runners/dify/dify_api_client.py +195 -0
- astrbot/core/agent/runners/tool_loop_agent_runner.py +400 -0
- astrbot/core/agent/tool.py +285 -0
- astrbot/core/agent/tool_executor.py +17 -0
- astrbot/core/astr_agent_context.py +19 -0
- astrbot/core/astr_agent_hooks.py +36 -0
- astrbot/core/astr_agent_run_util.py +80 -0
- astrbot/core/astr_agent_tool_exec.py +246 -0
- astrbot/core/astrbot_config_mgr.py +275 -0
- astrbot/core/config/__init__.py +2 -2
- astrbot/core/config/astrbot_config.py +60 -20
- astrbot/core/config/default.py +1972 -453
- astrbot/core/config/i18n_utils.py +110 -0
- astrbot/core/conversation_mgr.py +285 -75
- astrbot/core/core_lifecycle.py +167 -62
- astrbot/core/db/__init__.py +305 -102
- astrbot/core/db/migration/helper.py +69 -0
- astrbot/core/db/migration/migra_3_to_4.py +357 -0
- astrbot/core/db/migration/migra_45_to_46.py +44 -0
- astrbot/core/db/migration/migra_webchat_session.py +131 -0
- astrbot/core/db/migration/shared_preferences_v3.py +48 -0
- astrbot/core/db/migration/sqlite_v3.py +497 -0
- astrbot/core/db/po.py +259 -55
- astrbot/core/db/sqlite.py +773 -528
- astrbot/core/db/vec_db/base.py +73 -0
- astrbot/core/db/vec_db/faiss_impl/__init__.py +3 -0
- astrbot/core/db/vec_db/faiss_impl/document_storage.py +392 -0
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +93 -0
- astrbot/core/db/vec_db/faiss_impl/sqlite_init.sql +17 -0
- astrbot/core/db/vec_db/faiss_impl/vec_db.py +204 -0
- astrbot/core/event_bus.py +26 -22
- astrbot/core/exceptions.py +9 -0
- astrbot/core/file_token_service.py +98 -0
- astrbot/core/initial_loader.py +19 -10
- astrbot/core/knowledge_base/chunking/__init__.py +9 -0
- astrbot/core/knowledge_base/chunking/base.py +25 -0
- astrbot/core/knowledge_base/chunking/fixed_size.py +59 -0
- astrbot/core/knowledge_base/chunking/recursive.py +161 -0
- astrbot/core/knowledge_base/kb_db_sqlite.py +301 -0
- astrbot/core/knowledge_base/kb_helper.py +642 -0
- astrbot/core/knowledge_base/kb_mgr.py +330 -0
- astrbot/core/knowledge_base/models.py +120 -0
- astrbot/core/knowledge_base/parsers/__init__.py +13 -0
- astrbot/core/knowledge_base/parsers/base.py +51 -0
- astrbot/core/knowledge_base/parsers/markitdown_parser.py +26 -0
- astrbot/core/knowledge_base/parsers/pdf_parser.py +101 -0
- astrbot/core/knowledge_base/parsers/text_parser.py +42 -0
- astrbot/core/knowledge_base/parsers/url_parser.py +103 -0
- astrbot/core/knowledge_base/parsers/util.py +13 -0
- astrbot/core/knowledge_base/prompts.py +65 -0
- astrbot/core/knowledge_base/retrieval/__init__.py +14 -0
- astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
- astrbot/core/knowledge_base/retrieval/manager.py +276 -0
- astrbot/core/knowledge_base/retrieval/rank_fusion.py +142 -0
- astrbot/core/knowledge_base/retrieval/sparse_retriever.py +136 -0
- astrbot/core/log.py +21 -15
- astrbot/core/message/components.py +413 -287
- astrbot/core/message/message_event_result.py +35 -24
- astrbot/core/persona_mgr.py +192 -0
- astrbot/core/pipeline/__init__.py +14 -14
- astrbot/core/pipeline/content_safety_check/stage.py +13 -9
- astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
- astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +13 -14
- astrbot/core/pipeline/content_safety_check/strategies/keywords.py +2 -1
- astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
- astrbot/core/pipeline/context.py +7 -1
- astrbot/core/pipeline/context_utils.py +107 -0
- astrbot/core/pipeline/preprocess_stage/stage.py +63 -36
- astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +464 -0
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
- astrbot/core/pipeline/process_stage/method/star_request.py +26 -32
- astrbot/core/pipeline/process_stage/stage.py +21 -15
- astrbot/core/pipeline/process_stage/utils.py +125 -0
- astrbot/core/pipeline/rate_limit_check/stage.py +34 -36
- astrbot/core/pipeline/respond/stage.py +142 -101
- astrbot/core/pipeline/result_decorate/stage.py +124 -57
- astrbot/core/pipeline/scheduler.py +21 -16
- astrbot/core/pipeline/session_status_check/stage.py +37 -0
- astrbot/core/pipeline/stage.py +11 -76
- astrbot/core/pipeline/waking_check/stage.py +69 -33
- astrbot/core/pipeline/whitelist_check/stage.py +10 -7
- astrbot/core/platform/__init__.py +6 -6
- astrbot/core/platform/astr_message_event.py +107 -129
- astrbot/core/platform/astrbot_message.py +32 -12
- astrbot/core/platform/manager.py +62 -18
- astrbot/core/platform/message_session.py +30 -0
- astrbot/core/platform/platform.py +16 -24
- astrbot/core/platform/platform_metadata.py +9 -4
- astrbot/core/platform/register.py +12 -7
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +136 -60
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +126 -46
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +63 -31
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +30 -26
- astrbot/core/platform/sources/discord/client.py +129 -0
- astrbot/core/platform/sources/discord/components.py +139 -0
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +473 -0
- astrbot/core/platform/sources/discord/discord_platform_event.py +313 -0
- astrbot/core/platform/sources/lark/lark_adapter.py +27 -18
- astrbot/core/platform/sources/lark/lark_event.py +39 -13
- astrbot/core/platform/sources/misskey/misskey_adapter.py +770 -0
- astrbot/core/platform/sources/misskey/misskey_api.py +964 -0
- astrbot/core/platform/sources/misskey/misskey_event.py +163 -0
- astrbot/core/platform/sources/misskey/misskey_utils.py +550 -0
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +149 -33
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +14 -8
- astrbot/core/platform/sources/satori/satori_adapter.py +792 -0
- astrbot/core/platform/sources/satori/satori_event.py +432 -0
- astrbot/core/platform/sources/slack/client.py +164 -0
- astrbot/core/platform/sources/slack/slack_adapter.py +416 -0
- astrbot/core/platform/sources/slack/slack_event.py +253 -0
- astrbot/core/platform/sources/telegram/tg_adapter.py +100 -43
- astrbot/core/platform/sources/telegram/tg_event.py +136 -36
- astrbot/core/platform/sources/webchat/webchat_adapter.py +72 -22
- astrbot/core/platform/sources/webchat/webchat_event.py +46 -22
- astrbot/core/platform/sources/webchat/webchat_queue_mgr.py +35 -0
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +926 -0
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +178 -0
- astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +159 -0
- astrbot/core/platform/sources/wecom/wecom_adapter.py +169 -27
- astrbot/core/platform/sources/wecom/wecom_event.py +162 -77
- astrbot/core/platform/sources/wecom/wecom_kf.py +279 -0
- astrbot/core/platform/sources/wecom/wecom_kf_message.py +196 -0
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +297 -0
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +15 -0
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +19 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +472 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +417 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +152 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +153 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +168 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +209 -0
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +306 -0
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +186 -0
- astrbot/core/platform_message_history_mgr.py +49 -0
- astrbot/core/provider/__init__.py +2 -3
- astrbot/core/provider/entites.py +8 -8
- astrbot/core/provider/entities.py +154 -98
- astrbot/core/provider/func_tool_manager.py +446 -458
- astrbot/core/provider/manager.py +345 -207
- astrbot/core/provider/provider.py +188 -73
- astrbot/core/provider/register.py +9 -7
- astrbot/core/provider/sources/anthropic_source.py +295 -115
- astrbot/core/provider/sources/azure_tts_source.py +224 -0
- astrbot/core/provider/sources/bailian_rerank_source.py +236 -0
- astrbot/core/provider/sources/dashscope_tts.py +138 -14
- astrbot/core/provider/sources/edge_tts_source.py +24 -19
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +58 -13
- astrbot/core/provider/sources/gemini_embedding_source.py +61 -0
- astrbot/core/provider/sources/gemini_source.py +310 -132
- astrbot/core/provider/sources/gemini_tts_source.py +81 -0
- astrbot/core/provider/sources/groq_source.py +15 -0
- astrbot/core/provider/sources/gsv_selfhosted_source.py +151 -0
- astrbot/core/provider/sources/gsvi_tts_source.py +14 -7
- astrbot/core/provider/sources/minimax_tts_api_source.py +159 -0
- astrbot/core/provider/sources/openai_embedding_source.py +40 -0
- astrbot/core/provider/sources/openai_source.py +241 -145
- astrbot/core/provider/sources/openai_tts_api_source.py +18 -7
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
- astrbot/core/provider/sources/vllm_rerank_source.py +71 -0
- astrbot/core/provider/sources/volcengine_tts.py +115 -0
- astrbot/core/provider/sources/whisper_api_source.py +18 -13
- astrbot/core/provider/sources/whisper_selfhosted_source.py +19 -12
- astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
- astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
- astrbot/core/provider/sources/zhipu_source.py +6 -73
- astrbot/core/star/__init__.py +43 -11
- astrbot/core/star/config.py +17 -18
- astrbot/core/star/context.py +362 -138
- astrbot/core/star/filter/__init__.py +4 -3
- astrbot/core/star/filter/command.py +111 -35
- astrbot/core/star/filter/command_group.py +46 -34
- astrbot/core/star/filter/custom_filter.py +6 -5
- astrbot/core/star/filter/event_message_type.py +4 -2
- astrbot/core/star/filter/permission.py +4 -2
- astrbot/core/star/filter/platform_adapter_type.py +45 -12
- astrbot/core/star/filter/regex.py +4 -2
- astrbot/core/star/register/__init__.py +19 -15
- astrbot/core/star/register/star.py +41 -13
- astrbot/core/star/register/star_handler.py +236 -86
- astrbot/core/star/session_llm_manager.py +280 -0
- astrbot/core/star/session_plugin_manager.py +170 -0
- astrbot/core/star/star.py +36 -43
- astrbot/core/star/star_handler.py +47 -85
- astrbot/core/star/star_manager.py +442 -260
- astrbot/core/star/star_tools.py +167 -45
- astrbot/core/star/updator.py +17 -20
- astrbot/core/umop_config_router.py +106 -0
- astrbot/core/updator.py +38 -13
- astrbot/core/utils/astrbot_path.py +39 -0
- astrbot/core/utils/command_parser.py +1 -1
- astrbot/core/utils/io.py +119 -60
- astrbot/core/utils/log_pipe.py +1 -1
- astrbot/core/utils/metrics.py +11 -10
- astrbot/core/utils/migra_helper.py +73 -0
- astrbot/core/utils/path_util.py +63 -62
- astrbot/core/utils/pip_installer.py +37 -15
- astrbot/core/utils/session_lock.py +29 -0
- astrbot/core/utils/session_waiter.py +19 -20
- astrbot/core/utils/shared_preferences.py +174 -34
- astrbot/core/utils/t2i/__init__.py +4 -1
- astrbot/core/utils/t2i/local_strategy.py +386 -238
- astrbot/core/utils/t2i/network_strategy.py +109 -49
- astrbot/core/utils/t2i/renderer.py +29 -14
- astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
- astrbot/core/utils/t2i/template_manager.py +111 -0
- astrbot/core/utils/tencent_record_helper.py +115 -1
- astrbot/core/utils/version_comparator.py +10 -13
- astrbot/core/zip_updator.py +112 -65
- astrbot/dashboard/routes/__init__.py +20 -13
- astrbot/dashboard/routes/auth.py +20 -9
- astrbot/dashboard/routes/chat.py +297 -141
- astrbot/dashboard/routes/config.py +652 -55
- astrbot/dashboard/routes/conversation.py +107 -37
- astrbot/dashboard/routes/file.py +26 -0
- astrbot/dashboard/routes/knowledge_base.py +1244 -0
- astrbot/dashboard/routes/log.py +27 -2
- astrbot/dashboard/routes/persona.py +202 -0
- astrbot/dashboard/routes/plugin.py +197 -139
- astrbot/dashboard/routes/route.py +27 -7
- astrbot/dashboard/routes/session_management.py +354 -0
- astrbot/dashboard/routes/stat.py +85 -18
- astrbot/dashboard/routes/static_file.py +5 -2
- astrbot/dashboard/routes/t2i.py +233 -0
- astrbot/dashboard/routes/tools.py +184 -120
- astrbot/dashboard/routes/update.py +59 -36
- astrbot/dashboard/server.py +96 -36
- astrbot/dashboard/utils.py +165 -0
- astrbot-4.7.0.dist-info/METADATA +294 -0
- astrbot-4.7.0.dist-info/RECORD +274 -0
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/WHEEL +1 -1
- astrbot/core/db/plugin/sqlite_impl.py +0 -112
- astrbot/core/db/sqlite_init.sql +0 -50
- astrbot/core/pipeline/platform_compatibility/stage.py +0 -56
- astrbot/core/pipeline/process_stage/method/llm_request.py +0 -606
- astrbot/core/platform/sources/gewechat/client.py +0 -806
- astrbot/core/platform/sources/gewechat/downloader.py +0 -55
- astrbot/core/platform/sources/gewechat/gewechat_event.py +0 -255
- astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py +0 -103
- astrbot/core/platform/sources/gewechat/xml_data_parser.py +0 -110
- astrbot/core/provider/sources/dashscope_source.py +0 -203
- astrbot/core/provider/sources/dify_source.py +0 -281
- astrbot/core/provider/sources/llmtuner_source.py +0 -132
- astrbot/core/rag/embedding/openai_source.py +0 -20
- astrbot/core/rag/knowledge_db_mgr.py +0 -94
- astrbot/core/rag/store/__init__.py +0 -9
- astrbot/core/rag/store/chroma_db.py +0 -42
- astrbot/core/utils/dify_api_client.py +0 -152
- astrbot-3.5.6.dist-info/METADATA +0 -249
- astrbot-3.5.6.dist-info/RECORD +0 -158
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
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(
|
|
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
|
-
|
|
24
|
-
|
|
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
|
+
): ...
|