AstrBot 4.12.3__py3-none-any.whl → 4.13.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/builtin_stars/astrbot/process_llm_request.py +42 -1
- astrbot/builtin_stars/builtin_commands/commands/__init__.py +0 -2
- astrbot/builtin_stars/builtin_commands/commands/persona.py +68 -6
- astrbot/builtin_stars/builtin_commands/main.py +0 -26
- astrbot/cli/__init__.py +1 -1
- astrbot/core/agent/runners/tool_loop_agent_runner.py +91 -1
- astrbot/core/agent/tool.py +61 -20
- astrbot/core/astr_agent_hooks.py +3 -1
- astrbot/core/astr_agent_run_util.py +243 -1
- astrbot/core/astr_agent_tool_exec.py +2 -2
- astrbot/core/{sandbox → computer}/booters/base.py +4 -4
- astrbot/core/{sandbox → computer}/booters/boxlite.py +2 -2
- astrbot/core/computer/booters/local.py +234 -0
- astrbot/core/{sandbox → computer}/booters/shipyard.py +2 -2
- astrbot/core/computer/computer_client.py +102 -0
- astrbot/core/{sandbox → computer}/tools/__init__.py +2 -1
- astrbot/core/{sandbox → computer}/tools/fs.py +1 -1
- astrbot/core/computer/tools/python.py +94 -0
- astrbot/core/{sandbox → computer}/tools/shell.py +13 -5
- astrbot/core/config/default.py +90 -9
- astrbot/core/db/__init__.py +94 -1
- astrbot/core/db/po.py +46 -0
- astrbot/core/db/sqlite.py +248 -0
- astrbot/core/message/components.py +2 -2
- astrbot/core/persona_mgr.py +162 -2
- astrbot/core/pipeline/context_utils.py +2 -2
- astrbot/core/pipeline/preprocess_stage/stage.py +1 -1
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +73 -6
- astrbot/core/pipeline/process_stage/utils.py +31 -4
- astrbot/core/pipeline/scheduler.py +1 -1
- astrbot/core/pipeline/waking_check/stage.py +0 -1
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +3 -3
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +32 -14
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +61 -2
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +57 -11
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +5 -7
- astrbot/core/platform/sources/webchat/webchat_adapter.py +1 -0
- astrbot/core/platform/sources/webchat/webchat_event.py +24 -0
- astrbot/core/provider/manager.py +38 -0
- astrbot/core/provider/provider.py +54 -0
- astrbot/core/provider/sources/gemini_embedding_source.py +1 -1
- astrbot/core/provider/sources/gemini_source.py +12 -9
- astrbot/core/provider/sources/genie_tts.py +128 -0
- astrbot/core/provider/sources/openai_embedding_source.py +1 -1
- astrbot/core/skills/__init__.py +3 -0
- astrbot/core/skills/skill_manager.py +237 -0
- astrbot/core/star/command_management.py +1 -1
- astrbot/core/star/config.py +1 -1
- astrbot/core/star/context.py +9 -8
- astrbot/core/star/filter/command.py +1 -1
- astrbot/core/star/filter/custom_filter.py +2 -2
- astrbot/core/star/register/star_handler.py +2 -4
- astrbot/core/utils/astrbot_path.py +6 -0
- astrbot/dashboard/routes/__init__.py +2 -0
- astrbot/dashboard/routes/config.py +236 -2
- astrbot/dashboard/routes/live_chat.py +423 -0
- astrbot/dashboard/routes/persona.py +265 -1
- astrbot/dashboard/routes/skills.py +148 -0
- astrbot/dashboard/routes/util.py +102 -0
- astrbot/dashboard/server.py +21 -5
- {astrbot-4.12.3.dist-info → astrbot-4.13.0.dist-info}/METADATA +1 -1
- {astrbot-4.12.3.dist-info → astrbot-4.13.0.dist-info}/RECORD +69 -63
- astrbot/builtin_stars/builtin_commands/commands/tool.py +0 -31
- astrbot/core/sandbox/sandbox_client.py +0 -52
- astrbot/core/sandbox/tools/python.py +0 -74
- /astrbot/core/{sandbox → computer}/olayer/__init__.py +0 -0
- /astrbot/core/{sandbox → computer}/olayer/filesystem.py +0 -0
- /astrbot/core/{sandbox → computer}/olayer/python.py +0 -0
- /astrbot/core/{sandbox → computer}/olayer/shell.py +0 -0
- {astrbot-4.12.3.dist-info → astrbot-4.13.0.dist-info}/WHEEL +0 -0
- {astrbot-4.12.3.dist-info → astrbot-4.13.0.dist-info}/entry_points.txt +0 -0
- {astrbot-4.12.3.dist-info → astrbot-4.13.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,6 +2,7 @@ import asyncio
|
|
|
2
2
|
import inspect
|
|
3
3
|
import os
|
|
4
4
|
import traceback
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
from quart import request
|
|
@@ -20,11 +21,22 @@ from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
|
|
|
20
21
|
from astrbot.core.platform.register import platform_cls_map, platform_registry
|
|
21
22
|
from astrbot.core.provider import Provider
|
|
22
23
|
from astrbot.core.provider.register import provider_registry
|
|
23
|
-
from astrbot.core.star.star import star_registry
|
|
24
|
+
from astrbot.core.star.star import StarMetadata, star_registry
|
|
25
|
+
from astrbot.core.utils.astrbot_path import (
|
|
26
|
+
get_astrbot_plugin_data_path,
|
|
27
|
+
)
|
|
24
28
|
from astrbot.core.utils.llm_metadata import LLM_METADATAS
|
|
25
29
|
from astrbot.core.utils.webhook_utils import ensure_platform_webhook_config
|
|
26
30
|
|
|
27
31
|
from .route import Response, Route, RouteContext
|
|
32
|
+
from .util import (
|
|
33
|
+
config_key_to_folder,
|
|
34
|
+
get_schema_item,
|
|
35
|
+
normalize_rel_path,
|
|
36
|
+
sanitize_filename,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
MAX_FILE_BYTES = 500 * 1024 * 1024
|
|
28
40
|
|
|
29
41
|
|
|
30
42
|
def try_cast(value: Any, type_: str):
|
|
@@ -106,6 +118,32 @@ def validate_config(data, schema: dict, is_core: bool) -> tuple[list[str], dict]
|
|
|
106
118
|
_validate_template_list(value, meta, f"{path}{key}", errors, validate)
|
|
107
119
|
continue
|
|
108
120
|
|
|
121
|
+
if meta["type"] == "file":
|
|
122
|
+
if not _expect_type(value, list, f"{path}{key}", errors, "list"):
|
|
123
|
+
continue
|
|
124
|
+
for idx, item in enumerate(value):
|
|
125
|
+
if not isinstance(item, str):
|
|
126
|
+
errors.append(
|
|
127
|
+
f"Invalid type {path}{key}[{idx}]: expected string, got {type(item).__name__}",
|
|
128
|
+
)
|
|
129
|
+
continue
|
|
130
|
+
normalized = normalize_rel_path(item)
|
|
131
|
+
if not normalized or not normalized.startswith("files/"):
|
|
132
|
+
errors.append(
|
|
133
|
+
f"Invalid file path {path}{key}[{idx}]: {item}",
|
|
134
|
+
)
|
|
135
|
+
continue
|
|
136
|
+
key_path = f"{path}{key}"
|
|
137
|
+
expected_folder = config_key_to_folder(key_path)
|
|
138
|
+
expected_prefix = f"files/{expected_folder}/"
|
|
139
|
+
if not normalized.startswith(expected_prefix):
|
|
140
|
+
errors.append(
|
|
141
|
+
f"Invalid file path {path}{key}[{idx}]: {item}",
|
|
142
|
+
)
|
|
143
|
+
continue
|
|
144
|
+
value[idx] = normalized
|
|
145
|
+
continue
|
|
146
|
+
|
|
109
147
|
if meta["type"] == "list" and not isinstance(value, list):
|
|
110
148
|
errors.append(
|
|
111
149
|
f"错误的类型 {path}{key}: 期望是 list, 得到了 {type(value).__name__}",
|
|
@@ -218,6 +256,9 @@ class ConfigRoute(Route):
|
|
|
218
256
|
"/config/default": ("GET", self.get_default_config),
|
|
219
257
|
"/config/astrbot/update": ("POST", self.post_astrbot_configs),
|
|
220
258
|
"/config/plugin/update": ("POST", self.post_plugin_configs),
|
|
259
|
+
"/config/file/upload": ("POST", self.upload_config_file),
|
|
260
|
+
"/config/file/delete": ("POST", self.delete_config_file),
|
|
261
|
+
"/config/file/get": ("GET", self.get_config_file_list),
|
|
221
262
|
"/config/platform/new": ("POST", self.post_new_platform),
|
|
222
263
|
"/config/platform/update": ("POST", self.post_update_platform),
|
|
223
264
|
"/config/platform/delete": ("POST", self.post_delete_platform),
|
|
@@ -876,6 +917,193 @@ class ConfigRoute(Route):
|
|
|
876
917
|
except Exception as e:
|
|
877
918
|
return Response().error(str(e)).__dict__
|
|
878
919
|
|
|
920
|
+
def _get_plugin_metadata_by_name(self, plugin_name: str) -> StarMetadata | None:
|
|
921
|
+
for plugin_md in star_registry:
|
|
922
|
+
if plugin_md.name == plugin_name:
|
|
923
|
+
return plugin_md
|
|
924
|
+
return None
|
|
925
|
+
|
|
926
|
+
def _resolve_config_file_scope(
|
|
927
|
+
self,
|
|
928
|
+
) -> tuple[str, str, str, StarMetadata, AstrBotConfig]:
|
|
929
|
+
"""将请求参数解析为一个明确的配置作用域。
|
|
930
|
+
|
|
931
|
+
当前支持的 scope:
|
|
932
|
+
- scope=plugin:name=<plugin_name>,key=<config_key_path>
|
|
933
|
+
"""
|
|
934
|
+
|
|
935
|
+
scope = request.args.get("scope") or "plugin"
|
|
936
|
+
name = request.args.get("name")
|
|
937
|
+
key_path = request.args.get("key")
|
|
938
|
+
|
|
939
|
+
if scope != "plugin":
|
|
940
|
+
raise ValueError(f"Unsupported scope: {scope}")
|
|
941
|
+
if not name or not key_path:
|
|
942
|
+
raise ValueError("Missing name or key parameter")
|
|
943
|
+
|
|
944
|
+
md = self._get_plugin_metadata_by_name(name)
|
|
945
|
+
if not md or not md.config:
|
|
946
|
+
raise ValueError(f"Plugin {name} not found or has no config")
|
|
947
|
+
|
|
948
|
+
return scope, name, key_path, md, md.config
|
|
949
|
+
|
|
950
|
+
async def upload_config_file(self):
|
|
951
|
+
"""上传文件到插件数据目录(用于某个 file 类型配置项)。"""
|
|
952
|
+
|
|
953
|
+
try:
|
|
954
|
+
scope, name, key_path, md, config = self._resolve_config_file_scope()
|
|
955
|
+
except ValueError as e:
|
|
956
|
+
return Response().error(str(e)).__dict__
|
|
957
|
+
|
|
958
|
+
meta = get_schema_item(getattr(config, "schema", None), key_path)
|
|
959
|
+
if not meta or meta.get("type") != "file":
|
|
960
|
+
return Response().error("Config item not found or not file type").__dict__
|
|
961
|
+
|
|
962
|
+
file_types = meta.get("file_types")
|
|
963
|
+
allowed_exts: list[str] = []
|
|
964
|
+
if isinstance(file_types, list):
|
|
965
|
+
allowed_exts = [
|
|
966
|
+
str(ext).lstrip(".").lower() for ext in file_types if str(ext).strip()
|
|
967
|
+
]
|
|
968
|
+
|
|
969
|
+
files = await request.files
|
|
970
|
+
if not files:
|
|
971
|
+
return Response().error("No files uploaded").__dict__
|
|
972
|
+
|
|
973
|
+
storage_root_path = Path(get_astrbot_plugin_data_path()).resolve(strict=False)
|
|
974
|
+
plugin_root_path = (storage_root_path / name).resolve(strict=False)
|
|
975
|
+
try:
|
|
976
|
+
plugin_root_path.relative_to(storage_root_path)
|
|
977
|
+
except ValueError:
|
|
978
|
+
return Response().error("Invalid name parameter").__dict__
|
|
979
|
+
plugin_root_path.mkdir(parents=True, exist_ok=True)
|
|
980
|
+
|
|
981
|
+
uploaded: list[str] = []
|
|
982
|
+
folder = config_key_to_folder(key_path)
|
|
983
|
+
errors: list[str] = []
|
|
984
|
+
for file in files.values():
|
|
985
|
+
filename = sanitize_filename(file.filename or "")
|
|
986
|
+
if not filename:
|
|
987
|
+
errors.append("Invalid filename")
|
|
988
|
+
continue
|
|
989
|
+
|
|
990
|
+
file_size = getattr(file, "content_length", None)
|
|
991
|
+
if isinstance(file_size, int) and file_size > MAX_FILE_BYTES:
|
|
992
|
+
errors.append(f"File too large: {filename}")
|
|
993
|
+
continue
|
|
994
|
+
|
|
995
|
+
ext = os.path.splitext(filename)[1].lstrip(".").lower()
|
|
996
|
+
if allowed_exts and ext not in allowed_exts:
|
|
997
|
+
errors.append(f"Unsupported file type: {filename}")
|
|
998
|
+
continue
|
|
999
|
+
|
|
1000
|
+
rel_path = f"files/{folder}/{filename}"
|
|
1001
|
+
save_path = (plugin_root_path / rel_path).resolve(strict=False)
|
|
1002
|
+
try:
|
|
1003
|
+
save_path.relative_to(plugin_root_path)
|
|
1004
|
+
except ValueError:
|
|
1005
|
+
errors.append(f"Invalid path: {filename}")
|
|
1006
|
+
continue
|
|
1007
|
+
|
|
1008
|
+
save_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1009
|
+
await file.save(str(save_path))
|
|
1010
|
+
if save_path.is_file() and save_path.stat().st_size > MAX_FILE_BYTES:
|
|
1011
|
+
save_path.unlink()
|
|
1012
|
+
errors.append(f"File too large: {filename}")
|
|
1013
|
+
continue
|
|
1014
|
+
uploaded.append(rel_path)
|
|
1015
|
+
|
|
1016
|
+
if not uploaded:
|
|
1017
|
+
return (
|
|
1018
|
+
Response()
|
|
1019
|
+
.error(
|
|
1020
|
+
"Upload failed: " + ", ".join(errors)
|
|
1021
|
+
if errors
|
|
1022
|
+
else "Upload failed",
|
|
1023
|
+
)
|
|
1024
|
+
.__dict__
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
return Response().ok({"uploaded": uploaded, "errors": errors}).__dict__
|
|
1028
|
+
|
|
1029
|
+
async def delete_config_file(self):
|
|
1030
|
+
"""删除插件数据目录中的文件。"""
|
|
1031
|
+
|
|
1032
|
+
scope = request.args.get("scope") or "plugin"
|
|
1033
|
+
name = request.args.get("name")
|
|
1034
|
+
if not name:
|
|
1035
|
+
return Response().error("Missing name parameter").__dict__
|
|
1036
|
+
if scope != "plugin":
|
|
1037
|
+
return Response().error(f"Unsupported scope: {scope}").__dict__
|
|
1038
|
+
|
|
1039
|
+
data = await request.get_json()
|
|
1040
|
+
rel_path = data.get("path") if isinstance(data, dict) else None
|
|
1041
|
+
rel_path = normalize_rel_path(rel_path)
|
|
1042
|
+
if not rel_path or not rel_path.startswith("files/"):
|
|
1043
|
+
return Response().error("Invalid path parameter").__dict__
|
|
1044
|
+
|
|
1045
|
+
md = self._get_plugin_metadata_by_name(name)
|
|
1046
|
+
if not md:
|
|
1047
|
+
return Response().error(f"Plugin {name} not found").__dict__
|
|
1048
|
+
|
|
1049
|
+
storage_root_path = Path(get_astrbot_plugin_data_path()).resolve(strict=False)
|
|
1050
|
+
plugin_root_path = (storage_root_path / name).resolve(strict=False)
|
|
1051
|
+
try:
|
|
1052
|
+
plugin_root_path.relative_to(storage_root_path)
|
|
1053
|
+
except ValueError:
|
|
1054
|
+
return Response().error("Invalid name parameter").__dict__
|
|
1055
|
+
target_path = (plugin_root_path / rel_path).resolve(strict=False)
|
|
1056
|
+
try:
|
|
1057
|
+
target_path.relative_to(plugin_root_path)
|
|
1058
|
+
except ValueError:
|
|
1059
|
+
return Response().error("Invalid path parameter").__dict__
|
|
1060
|
+
if target_path.is_file():
|
|
1061
|
+
target_path.unlink()
|
|
1062
|
+
|
|
1063
|
+
return Response().ok(None, "Deleted").__dict__
|
|
1064
|
+
|
|
1065
|
+
async def get_config_file_list(self):
|
|
1066
|
+
"""获取配置项对应目录下的文件列表。"""
|
|
1067
|
+
|
|
1068
|
+
try:
|
|
1069
|
+
_, name, key_path, _, config = self._resolve_config_file_scope()
|
|
1070
|
+
except ValueError as e:
|
|
1071
|
+
return Response().error(str(e)).__dict__
|
|
1072
|
+
|
|
1073
|
+
meta = get_schema_item(getattr(config, "schema", None), key_path)
|
|
1074
|
+
if not meta or meta.get("type") != "file":
|
|
1075
|
+
return Response().error("Config item not found or not file type").__dict__
|
|
1076
|
+
|
|
1077
|
+
storage_root_path = Path(get_astrbot_plugin_data_path()).resolve(strict=False)
|
|
1078
|
+
plugin_root_path = (storage_root_path / name).resolve(strict=False)
|
|
1079
|
+
try:
|
|
1080
|
+
plugin_root_path.relative_to(storage_root_path)
|
|
1081
|
+
except ValueError:
|
|
1082
|
+
return Response().error("Invalid name parameter").__dict__
|
|
1083
|
+
|
|
1084
|
+
folder = config_key_to_folder(key_path)
|
|
1085
|
+
target_dir = (plugin_root_path / "files" / folder).resolve(strict=False)
|
|
1086
|
+
try:
|
|
1087
|
+
target_dir.relative_to(plugin_root_path)
|
|
1088
|
+
except ValueError:
|
|
1089
|
+
return Response().error("Invalid path parameter").__dict__
|
|
1090
|
+
|
|
1091
|
+
if not target_dir.exists() or not target_dir.is_dir():
|
|
1092
|
+
return Response().ok({"files": []}).__dict__
|
|
1093
|
+
|
|
1094
|
+
files: list[str] = []
|
|
1095
|
+
for path in target_dir.rglob("*"):
|
|
1096
|
+
if not path.is_file():
|
|
1097
|
+
continue
|
|
1098
|
+
try:
|
|
1099
|
+
rel_path = path.relative_to(plugin_root_path).as_posix()
|
|
1100
|
+
except ValueError:
|
|
1101
|
+
continue
|
|
1102
|
+
if rel_path.startswith("files/"):
|
|
1103
|
+
files.append(rel_path)
|
|
1104
|
+
|
|
1105
|
+
return Response().ok({"files": files}).__dict__
|
|
1106
|
+
|
|
879
1107
|
async def post_new_platform(self):
|
|
880
1108
|
new_platform_config = await request.json
|
|
881
1109
|
|
|
@@ -1130,8 +1358,14 @@ class ConfigRoute(Route):
|
|
|
1130
1358
|
raise ValueError(f"插件 {plugin_name} 不存在")
|
|
1131
1359
|
if not md.config:
|
|
1132
1360
|
raise ValueError(f"插件 {plugin_name} 没有注册配置")
|
|
1361
|
+
assert md.config is not None
|
|
1133
1362
|
|
|
1134
1363
|
try:
|
|
1135
|
-
|
|
1364
|
+
errors, post_configs = validate_config(
|
|
1365
|
+
post_configs, getattr(md.config, "schema", {}), is_core=False
|
|
1366
|
+
)
|
|
1367
|
+
if errors:
|
|
1368
|
+
raise ValueError(f"格式校验未通过: {errors}")
|
|
1369
|
+
md.config.save_config(post_configs)
|
|
1136
1370
|
except Exception as e:
|
|
1137
1371
|
raise e
|