From a550b88e153082aacfa4de66a60565ba8ac08f07 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Mon, 30 Mar 2026 18:03:27 +0800 Subject: [PATCH] fix: type --- .gitignore | 2 +- astrbot/_internal/protocols/mcp/tool.py | 2 +- astrbot/api/tools.py | 9 +- .../agent/runners/dify/dify_agent_runner.py | 1 - .../agent/runners/tool_loop_agent_runner.py | 3 +- astrbot/core/computer/booters/boxlite.py | 8 +- astrbot/core/computer/booters/bwrap.py | 8 +- astrbot/core/computer/booters/shipyard.py | 2 +- astrbot/core/computer/booters/shipyard_neo.py | 20 +- .../core/computer/computer_tool_provider.py | 6 +- astrbot/core/computer/tools/browser.py | 6 +- astrbot/core/computer/tools/fs.py | 4 +- astrbot/core/computer/tools/neo_skills.py | 22 +- astrbot/core/computer/tools/python.py | 4 +- .../db/migration/shared_preferences_v3.py | 2 +- astrbot/core/db/sqlite.py | 6 +- .../db/vec_db/faiss_impl/embedding_storage.py | 4 +- astrbot/core/db/vec_db/faiss_impl/vec_db.py | 10 +- .../pipeline/content_safety_check/stage.py | 2 +- .../process_stage/method/agent_request.py | 4 +- .../method/agent_sub_stages/internal.py | 4 +- .../method/agent_sub_stages/third_party.py | 8 +- .../process_stage/method/star_request.py | 2 +- astrbot/core/pipeline/process_stage/stage.py | 2 +- .../core/pipeline/result_decorate/stage.py | 2 +- .../sources/wecom_ai_bot/wecomai_adapter.py | 14 +- .../sources/weixin_oc/weixin_oc_client.py | 26 +- astrbot/core/provider/provider.py | 54 +- .../core/provider/sources/anthropic_source.py | 8 +- .../core/provider/sources/gemini_source.py | 12 +- .../core/provider/sources/gsvi_tts_source.py | 1 - .../core/provider/sources/volcengine_tts.py | 33 +- astrbot/core/star/updator.py | 4 +- astrbot/core/updator.py | 6 +- astrbot/dashboard/routes/api_key.py | 4 +- astrbot/dashboard/routes/auth.py | 9 +- astrbot/dashboard/routes/chat.py | 14 +- astrbot/dashboard/routes/chatui_project.py | 6 +- astrbot/dashboard/routes/conversation.py | 10 +- astrbot/dashboard/routes/cron.py | 3 +- astrbot/dashboard/routes/live_chat.py | 1 + astrbot/dashboard/routes/log.py | 3 +- astrbot/dashboard/routes/open_api.py | 9 +- astrbot/dashboard/routes/persona.py | 33 +- astrbot/dashboard/routes/plugin.py | 42 +- .../dashboard/routes/session_management.py | 40 +- astrbot/dashboard/routes/skills.py | 51 +- astrbot/dashboard/routes/stat.py | 18 +- astrbot/dashboard/routes/tools.py | 54 +- astrbot/dashboard/routes/tui_chat.py | 12 +- astrbot/dashboard/routes/update.py | 12 +- astrbot/dashboard/server.py | 9 +- dashboard/src/views/SubAgentPage.vue | 818 ++++++++++++++---- examples/abp_aur_plugin.py | 257 ++++++ tests/test_dashboard.py | 3 +- tests/test_volcengine_tts.py | 20 + tests/test_weixin_oc_client.py | 11 + 57 files changed, 1353 insertions(+), 387 deletions(-) create mode 100644 examples/abp_aur_plugin.py create mode 100644 tests/test_volcengine_tts.py create mode 100644 tests/test_weixin_oc_client.py diff --git a/.gitignore b/.gitignore index 88977c18f..82b7e4ee4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ # Python related __pycache__ -.mypy_cache .venv* .conda/ uv.lock @@ -85,3 +84,4 @@ dist/ dashboard/src/assets/mdi-subset/*.woff dashboard/src/assets/mdi-subset/*.woff2 .planning +*cache diff --git a/astrbot/_internal/protocols/mcp/tool.py b/astrbot/_internal/protocols/mcp/tool.py index 7e1c12ff3..eae357c55 100644 --- a/astrbot/_internal/protocols/mcp/tool.py +++ b/astrbot/_internal/protocols/mcp/tool.py @@ -1,4 +1,5 @@ """MCP tool wrapper.""" + from __future__ import annotations from datetime import timedelta @@ -10,7 +11,6 @@ except (ModuleNotFoundError, ImportError): mcp: Any = None from astrbot._internal.tools.base import FunctionTool - from mcp.types import Tool as MCPTool_T if TYPE_CHECKING: diff --git a/astrbot/api/tools.py b/astrbot/api/tools.py index b4af23b07..856e2833b 100644 --- a/astrbot/api/tools.py +++ b/astrbot/api/tools.py @@ -25,7 +25,14 @@ from typing import Any from astrbot._internal.tools.base import FunctionTool, ToolSchema, ToolSet from astrbot._internal.tools.registry import FunctionToolManager -__all__ = ["FunctionTool", "ToolRegistry", "ToolSet", "get_registry", "tool", "ToolSchema"] +__all__ = [ + "FunctionTool", + "ToolRegistry", + "ToolSet", + "get_registry", + "tool", + "ToolSchema", +] class ToolRegistry: diff --git a/astrbot/core/agent/runners/dify/dify_agent_runner.py b/astrbot/core/agent/runners/dify/dify_agent_runner.py index b1ddc2531..d8547e6a7 100644 --- a/astrbot/core/agent/runners/dify/dify_agent_runner.py +++ b/astrbot/core/agent/runners/dify/dify_agent_runner.py @@ -1,7 +1,6 @@ import base64 import os import sys -from collections.abc import AsyncGenerator from typing import Any import astrbot.core.message.components as Comp diff --git a/astrbot/core/agent/runners/tool_loop_agent_runner.py b/astrbot/core/agent/runners/tool_loop_agent_runner.py index 211e3a4f8..7a5dc186b 100644 --- a/astrbot/core/agent/runners/tool_loop_agent_runner.py +++ b/astrbot/core/agent/runners/tool_loop_agent_runner.py @@ -6,7 +6,7 @@ import traceback from collections.abc import AsyncGenerator, AsyncIterator from contextlib import suppress from dataclasses import dataclass, field -from typing import Any, Literal, TypeVar, cast +from typing import Any, Literal, TypeVar from mcp.types import ( BlobResourceContents, @@ -1055,6 +1055,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]): async def _get_next(): return await anext(executor) + next_result_task = asyncio.create_task(_get_next()) abort_task = asyncio.create_task(self._abort_signal.wait()) self.tasks.add(next_result_task) diff --git a/astrbot/core/computer/booters/boxlite.py b/astrbot/core/computer/booters/boxlite.py index efea768cc..40204eaeb 100644 --- a/astrbot/core/computer/booters/boxlite.py +++ b/astrbot/core/computer/booters/boxlite.py @@ -199,17 +199,17 @@ class BoxliteBooter(ComputerBooter): sb_url=f"http://127.0.0.1:{random_port}" ) self._fs = ShipyardFileSystemComponent( - client=self.mocked, + client=self.mocked, # type: ignore[arg-type] ship_id=self.box.id, session_id=session_id, ) self._python = ShipyardPythonComponent( - client=self.mocked, + client=self.mocked, # type: ignore[arg-type] ship_id=self.box.id, session_id=session_id, ) self._shell = ShipyardShellComponent( - client=self.mocked, + client=self.mocked, # type: ignore[arg-type] ship_id=self.box.id, session_id=session_id, ) @@ -253,7 +253,7 @@ class BoxliteBooter(ComputerBooter): PythonTool, ) - return ( + return ( # type: ignore[return-value] ExecuteShellTool(), PythonTool(), FileUploadTool(), diff --git a/astrbot/core/computer/booters/bwrap.py b/astrbot/core/computer/booters/bwrap.py index 480ddde31..28b2b5c8d 100644 --- a/astrbot/core/computer/booters/bwrap.py +++ b/astrbot/core/computer/booters/bwrap.py @@ -331,8 +331,12 @@ class BwrapBooter(ComputerBooter): ) async def shutdown(self) -> None: - if self.config and await asyncio.to_thread(os.path.exists, self.config.workspace_dir): - await asyncio.to_thread(shutil.rmtree, self.config.workspace_dir, ignore_errors=True) + if self.config and await asyncio.to_thread( + os.path.exists, self.config.workspace_dir + ): + await asyncio.to_thread( + shutil.rmtree, self.config.workspace_dir, ignore_errors=True + ) async def upload_file(self, path: str, file_name: str) -> dict: if not self._fs or not self.config: diff --git a/astrbot/core/computer/booters/shipyard.py b/astrbot/core/computer/booters/shipyard.py index ab2f6a143..2e6939eaa 100644 --- a/astrbot/core/computer/booters/shipyard.py +++ b/astrbot/core/computer/booters/shipyard.py @@ -30,7 +30,7 @@ class ShipyardBooter(ComputerBooter): PythonTool, ) - return ( + return ( # type: ignore[return-value] ExecuteShellTool(), PythonTool(), FileUploadTool(), diff --git a/astrbot/core/computer/booters/shipyard_neo.py b/astrbot/core/computer/booters/shipyard_neo.py index b8a9be8d6..a572b8d5f 100644 --- a/astrbot/core/computer/booters/shipyard_neo.py +++ b/astrbot/core/computer/booters/shipyard_neo.py @@ -35,7 +35,7 @@ class NeoPythonComponent(PythonComponent): def __init__(self, sandbox: Any) -> None: self._sandbox = sandbox - async def exec( + async def exec( # type: ignore[override] self, code: str, kernel_id: str | None = None, @@ -77,7 +77,7 @@ class NeoShellComponent(ShellComponent): def __init__(self, sandbox: Any) -> None: self._sandbox = sandbox - async def exec( + async def exec( # type: ignore[override] self, command: str, cwd: str | None = None, @@ -196,7 +196,7 @@ class NeoBrowserComponent(BrowserComponent): async def exec( self, cmd: str, - timeout_sec: int = 30, + timeout: int = 30, description: str | None = None, tags: str | None = None, learn: bool = False, @@ -204,7 +204,7 @@ class NeoBrowserComponent(BrowserComponent): ) -> dict[str, Any]: result = await self._sandbox.browser.exec( cmd, - timeout_sec=timeout_sec, + timeout=timeout, description=description, tags=tags, learn=learn, @@ -215,7 +215,7 @@ class NeoBrowserComponent(BrowserComponent): async def exec_batch( self, commands: list[str], - timeout_sec: int = 60, + timeout: int = 60, stop_on_error: bool = True, description: str | None = None, tags: str | None = None, @@ -224,7 +224,7 @@ class NeoBrowserComponent(BrowserComponent): ) -> dict[str, Any]: result = await self._sandbox.browser.exec_batch( commands, - timeout_sec=timeout_sec, + timeout=timeout, stop_on_error=stop_on_error, description=description, tags=tags, @@ -236,7 +236,7 @@ class NeoBrowserComponent(BrowserComponent): async def run_skill( self, skill_key: str, - timeout_sec: int = 60, + timeout: int = 60, stop_on_error: bool = True, include_trace: bool = False, description: str | None = None, @@ -244,7 +244,7 @@ class NeoBrowserComponent(BrowserComponent): ) -> dict[str, Any]: result = await self._sandbox.browser.run_skill( skill_key=skill_key, - timeout_sec=timeout_sec, + timeout=timeout, stop_on_error=stop_on_error, include_trace=include_trace, description=description, @@ -554,7 +554,7 @@ class ShipyardNeoBooter(ComputerBooter): SyncSkillReleaseTool, ) - return ( + return ( # type: ignore[return-value] ExecuteShellTool(), PythonTool(), FileUploadTool(), @@ -581,7 +581,7 @@ class ShipyardNeoBooter(ComputerBooter): RunBrowserSkillTool, ) - return (BrowserExecTool(), BrowserBatchExecTool(), RunBrowserSkillTool()) + return (BrowserExecTool(), BrowserBatchExecTool(), RunBrowserSkillTool()) # type: ignore[return-value] @classmethod def get_default_tools(cls) -> list[FunctionTool]: diff --git a/astrbot/core/computer/computer_tool_provider.py b/astrbot/core/computer/computer_tool_provider.py index 36ced506f..104482fc4 100644 --- a/astrbot/core/computer/computer_tool_provider.py +++ b/astrbot/core/computer/computer_tool_provider.py @@ -34,11 +34,11 @@ def _get_local_tools() -> list[FunctionTool]: if _LOCAL_TOOLS_CACHE is None: from astrbot.core.computer.tools import ExecuteShellTool, LocalPythonTool - _LOCAL_TOOLS_CACHE = [ + _LOCAL_TOOLS_CACHE = [ # type: ignore[assignment] ExecuteShellTool(is_local=True), LocalPythonTool(), ] - return list(_LOCAL_TOOLS_CACHE) + return list(_LOCAL_TOOLS_CACHE) # type: ignore[arg-type] # --------------------------------------------------------------------------- @@ -114,7 +114,7 @@ class ComputerToolProvider: SyncSkillReleaseTool, ) - all_tools: list[FunctionTool] = [ + all_tools: list[FunctionTool] = [ # type: ignore[assignment] ExecuteShellTool(), PythonTool(), FileUploadTool(), diff --git a/astrbot/core/computer/tools/browser.py b/astrbot/core/computer/tools/browser.py index d8e5a6eb6..4799a3517 100644 --- a/astrbot/core/computer/tools/browser.py +++ b/astrbot/core/computer/tools/browser.py @@ -59,7 +59,7 @@ class BrowserExecTool(FunctionTool): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], cmd: str = "", @@ -122,7 +122,7 @@ class BrowserBatchExecTool(FunctionTool): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], commands: list[str] | None = None, @@ -171,7 +171,7 @@ class RunBrowserSkillTool(FunctionTool): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], skill_key: str = "", diff --git a/astrbot/core/computer/tools/fs.py b/astrbot/core/computer/tools/fs.py index 4928d4224..b24ef66a1 100644 --- a/astrbot/core/computer/tools/fs.py +++ b/astrbot/core/computer/tools/fs.py @@ -105,7 +105,7 @@ class FileUploadTool(FunctionTool): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], local_path: str, @@ -170,7 +170,7 @@ class FileDownloadTool(FunctionTool): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], remote_path: str, diff --git a/astrbot/core/computer/tools/neo_skills.py b/astrbot/core/computer/tools/neo_skills.py index 54dd4e107..abf6e2c05 100644 --- a/astrbot/core/computer/tools/neo_skills.py +++ b/astrbot/core/computer/tools/neo_skills.py @@ -84,7 +84,7 @@ class GetExecutionHistoryTool(NeoSkillToolBase): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], exec_type: str | None = None, @@ -127,7 +127,7 @@ class AnnotateExecutionTool(NeoSkillToolBase): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], execution_id: str, @@ -178,7 +178,7 @@ class CreateSkillPayloadTool(NeoSkillToolBase): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], payload: dict[str, Any] | list[Any], @@ -208,7 +208,7 @@ class GetSkillPayloadTool(NeoSkillToolBase): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], payload_ref: str, @@ -253,7 +253,7 @@ class CreateSkillCandidateTool(NeoSkillToolBase): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], skill_key: str, @@ -290,7 +290,7 @@ class ListSkillCandidatesTool(NeoSkillToolBase): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], status: str | None = None, @@ -328,7 +328,7 @@ class EvaluateSkillCandidateTool(NeoSkillToolBase): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], candidate_id: str, @@ -380,7 +380,7 @@ class PromoteSkillCandidateTool(NeoSkillToolBase): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], candidate_id: str, @@ -438,7 +438,7 @@ class ListSkillReleasesTool(NeoSkillToolBase): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], skill_key: str | None = None, @@ -474,7 +474,7 @@ class RollbackSkillReleaseTool(NeoSkillToolBase): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], release_id: str, @@ -504,7 +504,7 @@ class SyncSkillReleaseTool(NeoSkillToolBase): } ) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], release_id: str | None = None, diff --git a/astrbot/core/computer/tools/python.py b/astrbot/core/computer/tools/python.py index e1e79035f..e1451c37f 100644 --- a/astrbot/core/computer/tools/python.py +++ b/astrbot/core/computer/tools/python.py @@ -67,7 +67,7 @@ class PythonTool(FunctionTool): description: str = f"Run codes in an IPython shell. Current OS: {_OS_NAME}." parameters: dict = field(default_factory=lambda: param_schema) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], code: str, silent: bool = False ) -> ToolExecResult: if permission_error := check_admin_permission(context, "Python execution"): @@ -93,7 +93,7 @@ class LocalPythonTool(FunctionTool): parameters: dict = field(default_factory=lambda: param_schema) - async def call( + async def call( # type: ignore[invalid-method-override] self, context: ContextWrapper[AstrAgentContext], code: str, silent: bool = False ) -> ToolExecResult: if permission_error := check_admin_permission(context, "Python execution"): diff --git a/astrbot/core/db/migration/shared_preferences_v3.py b/astrbot/core/db/migration/shared_preferences_v3.py index 05b514583..f833a7fe0 100644 --- a/astrbot/core/db/migration/shared_preferences_v3.py +++ b/astrbot/core/db/migration/shared_preferences_v3.py @@ -28,7 +28,7 @@ class SharedPreferences: json.dump(self._data, f, indent=4, ensure_ascii=False) f.flush() - def get(self, key, default: _VT = None) -> _VT: + def get(self, key, default: _VT = None) -> _VT: # type: ignore[valid-type] return self._data.get(key, default) def put(self, key, value) -> None: diff --git a/astrbot/core/db/sqlite.py b/astrbot/core/db/sqlite.py index 42c3a892a..e40e7baca 100644 --- a/astrbot/core/db/sqlite.py +++ b/astrbot/core/db/sqlite.py @@ -617,7 +617,7 @@ class SQLiteDatabase(BaseDatabase): col(PlatformMessageHistory.created_at) < before, ), ) - return int(result.rowcount or 0) + return int(result.rowcount or 0) # type: ignore[union-attr] async def delete_platform_message_after( self, @@ -636,7 +636,7 @@ class SQLiteDatabase(BaseDatabase): col(PlatformMessageHistory.created_at) > after, ), ) - return int(result.rowcount or 0) + return int(result.rowcount or 0) # type: ignore[union-attr] async def delete_all_platform_message_history( self, @@ -653,7 +653,7 @@ class SQLiteDatabase(BaseDatabase): col(PlatformMessageHistory.user_id) == user_id, ), ) - return int(result.rowcount or 0) + return int(result.rowcount or 0) # type: ignore[union-attr] async def find_platform_message_history_by_idempotency_key( self, diff --git a/astrbot/core/db/vec_db/faiss_impl/embedding_storage.py b/astrbot/core/db/vec_db/faiss_impl/embedding_storage.py index 32d9d34d0..99e89664e 100644 --- a/astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +++ b/astrbot/core/db/vec_db/faiss_impl/embedding_storage.py @@ -35,7 +35,7 @@ class EmbeddingStorage: raise ValueError( f"向量维度不匹配, 期望: {self.dimension}, 实际: {vector.shape[0]}", ) - self.index.add_with_ids(vector.reshape(1, -1), np.array([id])) + self.index.add_with_ids(vector.reshape(1, -1), np.array([id])) # type: ignore[missing-argument] await self.save_index() async def insert_batch(self, vectors: np.ndarray, ids: list[int]) -> None: @@ -53,7 +53,7 @@ class EmbeddingStorage: raise ValueError( f"向量维度不匹配, 期望: {self.dimension}, 实际: {vectors.shape[1]}", ) - self.index.add_with_ids(vectors, np.array(ids)) + self.index.add_with_ids(vectors, np.array(ids)) # type: ignore[missing-argument] await self.save_index() async def search(self, vector: np.ndarray, k: int) -> tuple: diff --git a/astrbot/core/db/vec_db/faiss_impl/vec_db.py b/astrbot/core/db/vec_db/faiss_impl/vec_db.py index 7c6a908ed..b1a3cec63 100644 --- a/astrbot/core/db/vec_db/faiss_impl/vec_db.py +++ b/astrbot/core/db/vec_db/faiss_impl/vec_db.py @@ -35,7 +35,7 @@ class FaissVecDB(BaseVecDB): async def initialize(self) -> None: await self.document_storage.initialize() - async def insert( + async def insert( # type: ignore[invalid-method-override] self, content: str, metadata: dict | None = None, @@ -55,7 +55,7 @@ class FaissVecDB(BaseVecDB): await self.embedding_storage.insert(vector, int_id) return int_id - async def insert_batch( + async def insert_batch( # type: ignore[invalid-method-override] self, contents: list[str], metadatas: list[dict] | None = None, @@ -106,7 +106,7 @@ class FaissVecDB(BaseVecDB): await self.embedding_storage.insert_batch(vectors_array, int_ids) return int_ids - async def retrieve( + async def retrieve( # type: ignore[invalid-method-override] self, query: str, k: int = 5, @@ -171,7 +171,9 @@ class FaissVecDB(BaseVecDB): return top_k_results - async def delete(self, doc_id: str) -> None: + async def delete( # type: ignore[invalid-method-override] + self, doc_id: str + ) -> None: """删除一条文档块(chunk)""" # 获得对应的 int id result = await self.document_storage.get_document_by_doc_id(doc_id) diff --git a/astrbot/core/pipeline/content_safety_check/stage.py b/astrbot/core/pipeline/content_safety_check/stage.py index 475ee93a7..e336050ce 100644 --- a/astrbot/core/pipeline/content_safety_check/stage.py +++ b/astrbot/core/pipeline/content_safety_check/stage.py @@ -20,7 +20,7 @@ class ContentSafetyCheckStage(Stage): config = ctx.astrbot_config["content_safety"] self.strategy_selector = StrategySelector(config) - async def process( + async def process( # type: ignore[invalid-method-override] self, event: AstrMessageEvent, check_text: str | None = None, diff --git a/astrbot/core/pipeline/process_stage/method/agent_request.py b/astrbot/core/pipeline/process_stage/method/agent_request.py index 52cd400ce..e7efc2cb9 100644 --- a/astrbot/core/pipeline/process_stage/method/agent_request.py +++ b/astrbot/core/pipeline/process_stage/method/agent_request.py @@ -31,7 +31,9 @@ class AgentRequestSubStage(Stage): self.agent_sub_stage = ThirdPartyAgentSubStage() await self.agent_sub_stage.initialize(ctx) - async def process(self, event: AstrMessageEvent) -> AsyncGenerator[None, None]: + async def process( # type: ignore[invalid-method-override] + self, event: AstrMessageEvent + ) -> AsyncGenerator[None, None]: if not self.ctx.astrbot_config["provider_settings"]["enable"]: logger.debug( "This pipeline does not enable AI capability, skip processing." diff --git a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py index 78ab38ce2..4df0aad02 100644 --- a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +++ b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py @@ -151,7 +151,7 @@ class InternalAgentSubStage(Stage): max_quoted_fallback_images=settings.get("max_quoted_fallback_images", 20), ) - async def process( + async def process( # type: ignore[invalid-method-override] self, event: AstrMessageEvent, ) -> None | AsyncGenerator[None, None]: @@ -450,7 +450,7 @@ class InternalAgentSubStage(Stage): consumed_marked=follow_up_consumed_marked, ) - async def _save_to_history( + async def _save_to_history( # type: ignore[invalid-method-override] self, event: AstrMessageEvent, req: ProviderRequest, diff --git a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py index 687d15334..37c56d906 100644 --- a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +++ b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py @@ -181,7 +181,7 @@ class ThirdPartyAgentSubStage(Stage): source="Third-party runner config", ) - async def _resolve_persona_custom_error_message( + async def _resolve_persona_custom_error_message( # type: ignore[invalid-method-override] self, event: AstrMessageEvent ) -> str | None: try: @@ -199,7 +199,7 @@ class ThirdPartyAgentSubStage(Stage): logger.debug("Failed to resolve persona custom error message: %s", e) return None - async def _handle_streaming_response( + async def _handle_streaming_response( # type: ignore[invalid-method-override] self, *, runner: "BaseAgentRunner", @@ -246,7 +246,7 @@ class ThirdPartyAgentSubStage(Stage): ), ) - async def _handle_non_streaming_response( + async def _handle_non_streaming_response( # type: ignore[invalid-method-override] self, *, runner: "BaseAgentRunner", @@ -281,7 +281,7 @@ class ThirdPartyAgentSubStage(Stage): # Second yield keeps scheduler progress consistent after final result update. yield - async def process( + async def process( # type: ignore[invalid-method-override] self, event: AstrMessageEvent, ) -> None | AsyncGenerator[None, None]: diff --git a/astrbot/core/pipeline/process_stage/method/star_request.py b/astrbot/core/pipeline/process_stage/method/star_request.py index 6b1fa498b..03a021d35 100644 --- a/astrbot/core/pipeline/process_stage/method/star_request.py +++ b/astrbot/core/pipeline/process_stage/method/star_request.py @@ -19,7 +19,7 @@ class StarRequestSubStage(Stage): self.identifier = ctx.astrbot_config["provider_settings"]["identifier"] self.ctx = ctx - async def process( + async def process( # type: ignore[invalid-method-override] self, event: AstrMessageEvent, ) -> AsyncGenerator[Any, None]: diff --git a/astrbot/core/pipeline/process_stage/stage.py b/astrbot/core/pipeline/process_stage/stage.py index ebf28d750..f8051c31b 100644 --- a/astrbot/core/pipeline/process_stage/stage.py +++ b/astrbot/core/pipeline/process_stage/stage.py @@ -28,7 +28,7 @@ class ProcessStage(Stage): self.star_request_sub_stage = StarRequestSubStage() await self.star_request_sub_stage.initialize(ctx) - async def process( + async def process( # type: ignore[invalid-method-override] self, event: AstrMessageEvent, ) -> None | AsyncGenerator[None, None]: diff --git a/astrbot/core/pipeline/result_decorate/stage.py b/astrbot/core/pipeline/result_decorate/stage.py index 97a398d3c..3c567288a 100644 --- a/astrbot/core/pipeline/result_decorate/stage.py +++ b/astrbot/core/pipeline/result_decorate/stage.py @@ -122,7 +122,7 @@ class ResultDecorateStage(Stage): result.append(seg) return result if result else [text] - async def process( + async def process( # type: ignore[invalid-method-override] self, event: AstrMessageEvent, ) -> None | AsyncGenerator[None, None]: diff --git a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py index 2177375e7..c37834cba 100644 --- a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +++ b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py @@ -179,7 +179,7 @@ class WecomAIBotAdapter(Platform): except Exception as e: logger.error(f"处理队列消息时发生异常: {e}") - async def _process_message( + async def _process_message( # type: ignore[invalid-method-override] self, message_data: dict[str, Any], callback_params: dict[str, str], @@ -356,7 +356,7 @@ class WecomAIBotAdapter(Platform): logger.error("处理欢迎消息时发生异常: %s", e) return None - async def _process_long_connection_payload( + async def _process_long_connection_payload( # type: ignore[invalid-method-override] self, payload: dict[str, Any], ) -> None: @@ -425,7 +425,7 @@ class WecomAIBotAdapter(Platform): }, ) - async def _send_long_connection_respond_msg( + async def _send_long_connection_respond_msg( # type: ignore[invalid-method-override] self, req_id: str, body: dict[str, Any], @@ -451,7 +451,7 @@ class WecomAIBotAdapter(Platform): user_id = message_data.get("from", {}).get("userid", "default_user") return format_session_id("wecomai", user_id) - async def _enqueue_message( + async def _enqueue_message( # type: ignore[invalid-method-override] self, message_data: dict[str, Any], callback_params: dict[str, str], @@ -561,7 +561,7 @@ class WecomAIBotAdapter(Platform): logger.debug(f"WecomAIAdapter: {abm.message}") return abm - async def send_by_session( + async def send_by_session( # type: ignore[invalid-method-override] self, session: MessageSesion, message_chain: MessageChain, @@ -585,7 +585,9 @@ class WecomAIBotAdapter(Platform): ) await super().send_by_session(session, message_chain) - def run(self) -> Awaitable[Any]: + def run( # type: ignore[invalid-method-override] + self, + ) -> Awaitable[Any]: """运行适配器,同时启动HTTP服务器和队列监听器""" async def run_both() -> None: diff --git a/astrbot/core/platform/sources/weixin_oc/weixin_oc_client.py b/astrbot/core/platform/sources/weixin_oc/weixin_oc_client.py index 2d23a9771..b439c4288 100644 --- a/astrbot/core/platform/sources/weixin_oc/weixin_oc_client.py +++ b/astrbot/core/platform/sources/weixin_oc/weixin_oc_client.py @@ -91,6 +91,23 @@ class WeixinOCClient: return data return data[:-pad_len] + @staticmethod + def _build_media_cipher(key: bytes): + # Weixin OC CDN media transport only exchanges an `aeskey`; no IV is + # negotiated by the upstream API, so ECB is required for compatibility. + # codeql[py/weak-cryptographic-algorithm] + return AES.new(key, AES.MODE_ECB) + + @classmethod + def encrypt_cdn_payload(cls, data: bytes, key: bytes) -> bytes: + cipher = cls._build_media_cipher(key) + return cipher.encrypt(cls.pkcs7_pad(data)) + + @classmethod + def decrypt_cdn_payload(cls, encrypted: bytes, key: bytes) -> bytes: + cipher = cls._build_media_cipher(key) + return cls.pkcs7_unpad(cipher.decrypt(encrypted)) + @staticmethod def parse_media_aes_key(aes_key_value: str) -> bytes: normalized = aes_key_value.strip() @@ -133,12 +150,12 @@ class WeixinOCClient: hashlib.md5(raw_data).hexdigest(), file_key, ) - cipher = AES.new(bytes.fromhex(aes_key_hex), AES.MODE_ECB) - encrypted = cipher.encrypt(self.pkcs7_pad(raw_data)) + key = bytes.fromhex(aes_key_hex) + encrypted = self.encrypt_cdn_payload(raw_data, key) logger.debug( "weixin_oc(%s): encrypt done aes_key_len=%s plain_size=%s cipher_size=%s", self.adapter_id, - len(bytes.fromhex(aes_key_hex)), + len(key), len(raw_data), len(encrypted), ) @@ -200,8 +217,7 @@ class WeixinOCClient: ) -> bytes: encrypted = await self.download_cdn_bytes(encrypted_query_param) key = self.parse_media_aes_key(aes_key_value) - cipher = AES.new(key, AES.MODE_ECB) - return self.pkcs7_unpad(cipher.decrypt(encrypted)) + return self.decrypt_cdn_payload(encrypted, key) async def request_json( self, diff --git a/astrbot/core/provider/provider.py b/astrbot/core/provider/provider.py index daa07e320..c76de7c77 100644 --- a/astrbot/core/provider/provider.py +++ b/astrbot/core/provider/provider.py @@ -112,21 +112,21 @@ class Provider(AbstractProvider): ) -> LLMResponse: """获得 LLM 的文本对话结果。会使用当前的模型进行对话。 - Args: - prompt: 提示词,和 contexts 二选一使用,如果都指定,则会将 prompt(以及可能的 image_urls) 作为最新的一条记录添加到 contexts 中 - session_id: 会话 ID(此属性已经被废弃) - image_urls: 图片 URL 列表 - tools: tool set -<<<<<<< HEAD - contexts: 上下文,和 prompt 二选一使用 - tool_calls_result: 回传给 LLM 的工具调用结果。参考: https://platform.openai.com/docs/guides/function-calling - tool_choice: 工具调用策略,`auto` 表示由模型自行决定,`required` 表示要求模型必须调用工具 - extra_user_content_parts: 额外的内容块列表,用于在用户消息后添加额外的文本块(如系统提醒。指令等) - kwargs: 其他参数 + Args: + prompt: 提示词,和 contexts 二选一使用,如果都指定,则会将 prompt(以及可能的 image_urls) 作为最新的一条记录添加到 contexts 中 + session_id: 会话 ID(此属性已经被废弃) + image_urls: 图片 URL 列表 + tools: tool set + <<<<<<< HEAD + contexts: 上下文,和 prompt 二选一使用 + tool_calls_result: 回传给 LLM 的工具调用结果。参考: https://platform.openai.com/docs/guides/function-calling + tool_choice: 工具调用策略,`auto` 表示由模型自行决定,`required` 表示要求模型必须调用工具 + extra_user_content_parts: 额外的内容块列表,用于在用户消息后添加额外的文本块(如系统提醒。指令等) + kwargs: 其他参数 - Notes: - - 如果传入了 image_urls,将会在对话时附上图片。如果模型不支持图片输入,将会抛出错误。 - - 如果传入了 tools,将会使用 tools 进行 Function-calling。如果模型不支持 Function-calling,将会抛出错误。 + Notes: + - 如果传入了 image_urls,将会在对话时附上图片。如果模型不支持图片输入,将会抛出错误。 + - 如果传入了 tools,将会使用 tools 进行 Function-calling。如果模型不支持 Function-calling,将会抛出错误。 """ ... @@ -146,20 +146,20 @@ class Provider(AbstractProvider): ) -> AsyncGenerator[LLMResponse, None]: """获得 LLM 的流式文本对话结果。会使用当前的模型进行对话。在生成的最后会返回一次完整的结果。 - Args: - prompt: 提示词,和 contexts 二选一使用,如果都指定,则会将 prompt(以及可能的 image_urls) 作为最新的一条记录添加到 contexts 中 - session_id: 会话 ID(此属性已经被废弃) - image_urls: 图片 URL 列表 - tools: tool set -<<<<<<< HEAD - contexts: 上下文,和 prompt 二选一使用 - tool_calls_result: 回传给 LLM 的工具调用结果。参考: https://platform.openai.com/docs/guides/function-calling - tool_choice: 工具调用策略,`auto` 表示由模型自行决定,`required` 表示要求模型必须调用工具 - kwargs: 其他参数 + Args: + prompt: 提示词,和 contexts 二选一使用,如果都指定,则会将 prompt(以及可能的 image_urls) 作为最新的一条记录添加到 contexts 中 + session_id: 会话 ID(此属性已经被废弃) + image_urls: 图片 URL 列表 + tools: tool set + <<<<<<< HEAD + contexts: 上下文,和 prompt 二选一使用 + tool_calls_result: 回传给 LLM 的工具调用结果。参考: https://platform.openai.com/docs/guides/function-calling + tool_choice: 工具调用策略,`auto` 表示由模型自行决定,`required` 表示要求模型必须调用工具 + kwargs: 其他参数 - Notes: - - 如果传入了 image_urls,将会在对话时附上图片。如果模型不支持图片输入,将会抛出错误。 - - 如果传入了 tools,将会使用 tools 进行 Function-calling。如果模型不支持 Function-calling,将会抛出错误。 + Notes: + - 如果传入了 image_urls,将会在对话时附上图片。如果模型不支持图片输入,将会抛出错误。 + - 如果传入了 tools,将会使用 tools 进行 Function-calling。如果模型不支持 Function-calling,将会抛出错误。 """ if False: # pragma: no cover - make this an async generator for typing diff --git a/astrbot/core/provider/sources/anthropic_source.py b/astrbot/core/provider/sources/anthropic_source.py index e6eb3ffd2..41b85b6e9 100644 --- a/astrbot/core/provider/sources/anthropic_source.py +++ b/astrbot/core/provider/sources/anthropic_source.py @@ -356,7 +356,7 @@ class ProviderAnthropic(Provider): ) return llm_response - async def _query_stream( + async def _query_stream( # type: ignore[invalid-method-override] self, payloads: dict, tools: ToolSet | None, @@ -512,7 +512,7 @@ class ProviderAnthropic(Provider): ) yield final_response - async def text_chat( + async def text_chat( # type: ignore[invalid-method-override] self, prompt=None, session_id=None, @@ -572,7 +572,7 @@ class ProviderAnthropic(Provider): return llm_response - async def text_chat_stream( + async def text_chat_stream( # type: ignore[invalid-method-override] self, prompt=None, session_id=None, @@ -638,7 +638,7 @@ class ProviderAnthropic(Provider): return "image/webp" return "image/jpeg" - async def assemble_context( + async def assemble_context( # type: ignore[invalid-method-override] self, text: str, image_urls: list[str] | None = None, diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index 225ea28c3..2f22260ff 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -128,7 +128,7 @@ class ProviderGoogleGenAI(Provider): raise e - async def _prepare_query_config( + async def _prepare_query_config( # type: ignore[invalid-method-override] self, payloads: dict, tools: ToolSet | None = None, @@ -468,7 +468,6 @@ class ProviderGoogleGenAI(Provider): """处理内容部分并构建消息链""" if not candidate.content: logger.warning(f"收到的 candidate.content 为空: {candidate}") -<<<<<<< HEAD raise EmptyModelOutputError( "Gemini candidate content is empty. " f"finish_reason={candidate.finish_reason}" @@ -494,7 +493,6 @@ class ProviderGoogleGenAI(Provider): if not result_parts: logger.warning(f"收到的 candidate.content.parts 为空: {candidate}") -<<<<<<< HEAD raise EmptyModelOutputError( "Gemini candidate content parts are empty. " f"finish_reason={candidate.finish_reason}" @@ -644,7 +642,7 @@ class ProviderGoogleGenAI(Provider): llm_response.usage = self._extract_usage(result.usage_metadata) return llm_response - async def _query_stream( + async def _query_stream( # type: ignore[invalid-method-override] self, payloads: dict, tools: ToolSet | None, @@ -769,7 +767,7 @@ class ProviderGoogleGenAI(Provider): yield final_response - async def text_chat( + async def text_chat( # type: ignore[invalid-method-override] self, prompt=None, session_id=None, @@ -827,7 +825,7 @@ class ProviderGoogleGenAI(Provider): raise Exception("请求失败。") - async def text_chat_stream( + async def text_chat_stream( # type: ignore[invalid-method-override] self, prompt=None, session_id=None, @@ -908,7 +906,7 @@ class ProviderGoogleGenAI(Provider): self.chosen_api_key = key self._init_client() - async def assemble_context( + async def assemble_context( # type: ignore[invalid-method-override] self, text: str, image_urls: list[str] | None = None, diff --git a/astrbot/core/provider/sources/gsvi_tts_source.py b/astrbot/core/provider/sources/gsvi_tts_source.py index edd934621..f85c1b8fa 100644 --- a/astrbot/core/provider/sources/gsvi_tts_source.py +++ b/astrbot/core/provider/sources/gsvi_tts_source.py @@ -1,7 +1,6 @@ import uuid from pathlib import Path -import aiofiles import aiohttp from astrbot.core.provider.entities import ProviderType diff --git a/astrbot/core/provider/sources/volcengine_tts.py b/astrbot/core/provider/sources/volcengine_tts.py index 9ff7718ce..a5657e1f0 100644 --- a/astrbot/core/provider/sources/volcengine_tts.py +++ b/astrbot/core/provider/sources/volcengine_tts.py @@ -1,8 +1,8 @@ import base64 import json -import os import traceback import uuid +from pathlib import Path import aiohttp import anyio @@ -33,6 +33,19 @@ class ProviderVolcengineTTS(TTSProvider): ) self.timeout = provider_config.get("timeout", 20) + @staticmethod + def _build_loggable_payload(payload: dict) -> dict: + loggable_payload = { + "app": dict(payload.get("app", {})), + "user": dict(payload.get("user", {})), + "audio": dict(payload.get("audio", {})), + "request": dict(payload.get("request", {})), + } + app_payload = loggable_payload.get("app") + if isinstance(app_payload, dict) and app_payload.get("token"): + app_payload["token"] = "***" + return loggable_payload + def _build_request_payload(self, text: str) -> dict: return { "app": { @@ -66,10 +79,13 @@ class ProviderVolcengineTTS(TTSProvider): } payload = self._build_request_payload(text) + loggable_payload = self._build_loggable_payload(payload) - # Don't log headers as they contain sensitive API key info + # Keep the request metadata useful for debugging without exposing secrets. logger.debug(f"请求 URL: {self.api_base}") - logger.debug(f"请求体: {json.dumps(payload, ensure_ascii=False)[:100]}...") + logger.debug( + f"请求体: {json.dumps(loggable_payload, ensure_ascii=False)[:100]}..." + ) try: async with ( @@ -92,17 +108,14 @@ class ProviderVolcengineTTS(TTSProvider): if "data" in resp_data: audio_data = base64.b64decode(resp_data["data"]) - temp_dir = get_astrbot_temp_path() - os.makedirs(temp_dir, exist_ok=True) - file_path = os.path.join( - temp_dir, - f"volcengine_tts_{uuid.uuid4()}.mp3", - ) + temp_dir = Path(get_astrbot_temp_path()) + temp_dir.mkdir(parents=True, exist_ok=True) + file_path = temp_dir / f"volcengine_tts_{uuid.uuid4()}.mp3" async with await anyio.open_file(file_path, "wb") as audio_file: await audio_file.write(audio_data) - return file_path + return str(file_path) error_msg = resp_data.get("message", "未知错误") raise Exception(f"火山引擎 TTS API 返回错误: {error_msg}") raise Exception( diff --git a/astrbot/core/star/updator.py b/astrbot/core/star/updator.py index cf319e78d..529705d42 100644 --- a/astrbot/core/star/updator.py +++ b/astrbot/core/star/updator.py @@ -26,7 +26,9 @@ class PluginUpdator(RepoZipUpdator): return plugin_path - async def update(self, plugin: StarMetadata, proxy="") -> str: + async def update( # type: ignore[invalid-method-override] + self, plugin: StarMetadata, proxy="" + ) -> str: repo_url = plugin.repo if not repo_url: diff --git a/astrbot/core/updator.py b/astrbot/core/updator.py index 9e4606f12..9fdd08b3a 100644 --- a/astrbot/core/updator.py +++ b/astrbot/core/updator.py @@ -128,7 +128,7 @@ class AstrBotUpdator(RepoZipUpdator): logger.error(f"重启失败({executable}, {e}),请尝试手动重启。") raise e - async def check_update( + async def check_update( # type: ignore[invalid-method-override] self, url: str | None, current_version: str | None, @@ -144,7 +144,9 @@ class AstrBotUpdator(RepoZipUpdator): async def get_releases(self) -> list: return await self.fetch_release_info(self.ASTRBOT_RELEASE_API) - async def update(self, reboot=False, latest=True, version=None, proxy="") -> None: + async def update( # type: ignore[invalid-method-override] + self, reboot=False, latest=True, version=None, proxy="" + ) -> None: update_data = await self.fetch_release_info(self.ASTRBOT_RELEASE_API, latest) file_url = None diff --git a/astrbot/dashboard/routes/api_key.py b/astrbot/dashboard/routes/api_key.py index f4674ff77..28872c10c 100644 --- a/astrbot/dashboard/routes/api_key.py +++ b/astrbot/dashboard/routes/api_key.py @@ -84,7 +84,9 @@ class ApiKeyRoute(Route): ] normalized_scopes = list(dict.fromkeys(normalized_scopes)) if not normalized_scopes: - return Response().error("At least one valid scope is required").to_json() + return ( + Response().error("At least one valid scope is required").to_json() + ) else: return Response().error("Invalid scopes").to_json() diff --git a/astrbot/dashboard/routes/auth.py b/astrbot/dashboard/routes/auth.py index 646a05571..eea036914 100644 --- a/astrbot/dashboard/routes/auth.py +++ b/astrbot/dashboard/routes/auth.py @@ -48,7 +48,8 @@ class AuthRoute(Route): await asyncio.sleep(3) return ( Response() - .error("管理员密码未设置,请先运行 'astrbot conf admin' 命令设置密码").to_json() + .error("管理员密码未设置,请先运行 'astrbot conf admin' 命令设置密码") + .to_json() ) # Normal login flow - credentials must match stored admin account @@ -64,7 +65,8 @@ class AuthRoute(Route): "username": stored_username, "change_pwd_hint": False, }, - ).to_json() + ) + .to_json() ) # Security: Don't reveal whether it's username or password error @@ -75,7 +77,8 @@ class AuthRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) stored_password_hash = self.config["dashboard"]["password"] diff --git a/astrbot/dashboard/routes/chat.py b/astrbot/dashboard/routes/chat.py index 089e632bc..c4a0556c6 100644 --- a/astrbot/dashboard/routes/chat.py +++ b/astrbot/dashboard/routes/chat.py @@ -95,8 +95,10 @@ class ChatRoute(Route): self.supported_imgs = ["jpg", "jpeg", "png", "gif", "webp"] self.conv_mgr = core_lifecycle.conversation_manager self.platform_history_mgr = core_lifecycle.platform_message_history_manager + assert self.platform_history_mgr self.db = db self.umop_config_router = core_lifecycle.umop_config_router + assert self.umop_config_router self.running_convs: dict[str, bool] = {} @@ -198,7 +200,8 @@ class ChatRoute(Route): "filename": filename, "type": attach_type, } - ).to_json() + ) + .to_json() ) async def _build_user_message_parts(self, message: str | list) -> list[dict]: @@ -343,7 +346,8 @@ class ChatRoute(Route): if not webchat_message_parts_have_content(message_parts): return ( Response() - .error("Message content is empty (reply only is not allowed)").to_json() + .error("Message content is empty (reply only is not allowed)") + .to_json() ) message_id = str(uuid.uuid4()) @@ -712,7 +716,8 @@ class ChatRoute(Route): "failed_count": len(failed_items), "failed_items": failed_items, } - ).to_json() + ) + .to_json() ) def _extract_attachment_ids(self, history_list) -> list[str]: @@ -771,7 +776,8 @@ class ChatRoute(Route): "session_id": session.session_id, "platform_id": session.platform_id, } - ).to_json() + ) + .to_json() ) async def get_sessions(self): diff --git a/astrbot/dashboard/routes/chatui_project.py b/astrbot/dashboard/routes/chatui_project.py index 947e0b5e4..2b9c6b6c0 100644 --- a/astrbot/dashboard/routes/chatui_project.py +++ b/astrbot/dashboard/routes/chatui_project.py @@ -55,7 +55,8 @@ class ChatUIProjectRoute(Route): "created_at": to_utc_isoformat(project.created_at), "updated_at": to_utc_isoformat(project.updated_at), } - ).to_json() + ) + .to_json() ) async def list_projects(self): @@ -105,7 +106,8 @@ class ChatUIProjectRoute(Route): "created_at": to_utc_isoformat(project.created_at), "updated_at": to_utc_isoformat(project.updated_at), } - ).to_json() + ) + .to_json() ) async def update_chatui_project(self): diff --git a/astrbot/dashboard/routes/conversation.py b/astrbot/dashboard/routes/conversation.py index a3a388d0c..74c8ac5dc 100644 --- a/astrbot/dashboard/routes/conversation.py +++ b/astrbot/dashboard/routes/conversation.py @@ -133,7 +133,8 @@ class ConversationRoute(Route): "created_at": conversation.created_at, "updated_at": conversation.updated_at, }, - ).to_json() + ) + .to_json() ) except Exception as e: @@ -183,7 +184,9 @@ class ConversationRoute(Route): conversations = data.get("conversations", []) if not conversations: return ( - Response().error("批量删除时conversations参数不能为空").to_json() + Response() + .error("批量删除时conversations参数不能为空") + .to_json() ) deleted_count = 0 @@ -221,7 +224,8 @@ class ConversationRoute(Route): "failed_count": len(failed_items), "failed_items": failed_items, }, - ).to_json() + ) + .to_json() ) # 单个删除 user_id = data.get("user_id") diff --git a/astrbot/dashboard/routes/cron.py b/astrbot/dashboard/routes/cron.py index c6ae3182c..8b1344f2d 100644 --- a/astrbot/dashboard/routes/cron.py +++ b/astrbot/dashboard/routes/cron.py @@ -84,7 +84,8 @@ class CronRoute(Route): if (not run_once) and not cron_expression: return jsonify( Response() - .error("cron_expression is required when run_once=false").to_json() + .error("cron_expression is required when run_once=false") + .to_json() ) if run_once and cron_expression: cron_expression = None # ignore cron when run_once specified diff --git a/astrbot/dashboard/routes/live_chat.py b/astrbot/dashboard/routes/live_chat.py index a5e3de076..53153b196 100644 --- a/astrbot/dashboard/routes/live_chat.py +++ b/astrbot/dashboard/routes/live_chat.py @@ -139,6 +139,7 @@ class LiveChatRoute(Route): self.db = db self.plugin_manager = core_lifecycle.plugin_manager self.platform_history_mgr = core_lifecycle.platform_message_history_manager + assert self.platform_history_mgr self.sessions: dict[str, LiveChatSession] = {} self.attachments_dir = os.path.join(get_astrbot_data_path(), "attachments") self.legacy_img_dir = os.path.join(get_astrbot_data_path(), "webchat", "imgs") diff --git a/astrbot/dashboard/routes/log.py b/astrbot/dashboard/routes/log.py index c6e589c04..ca41483f0 100644 --- a/astrbot/dashboard/routes/log.py +++ b/astrbot/dashboard/routes/log.py @@ -110,7 +110,8 @@ class LogRoute(Route): data={ "logs": logs, }, - ).to_json() + ) + .to_json() ) except Exception as e: logger.error(f"获取日志历史失败: {e}") diff --git a/astrbot/dashboard/routes/open_api.py b/astrbot/dashboard/routes/open_api.py index 71d8f7e7a..8cd2d61de 100644 --- a/astrbot/dashboard/routes/open_api.py +++ b/astrbot/dashboard/routes/open_api.py @@ -191,7 +191,8 @@ class OpenApiRoute(Route): ) return ( Response() - .error(f"Failed to update chat config route: {e}").to_json() + .error(f"Failed to update chat config route: {e}") + .to_json() ) try: return await self.chat_route.chat(post_data=post_data) @@ -627,7 +628,8 @@ class OpenApiRoute(Route): "page_size": page_size, "total": total, } - ).to_json() + ) + .to_json() ) async def get_chat_configs(self): @@ -671,7 +673,8 @@ class OpenApiRoute(Route): if not platform_inst: return ( Response() - .error(f"Bot not found or not running for platform: {platform_id}").to_json() + .error(f"Bot not found or not running for platform: {platform_id}") + .to_json() ) try: diff --git a/astrbot/dashboard/routes/persona.py b/astrbot/dashboard/routes/persona.py index 055ba9558..8ecf22ef0 100644 --- a/astrbot/dashboard/routes/persona.py +++ b/astrbot/dashboard/routes/persona.py @@ -71,7 +71,8 @@ class PersonaRoute(Route): } for persona in personas ], - ).to_json() + ) + .to_json() ) except Exception as e: logger.error(f"获取人格列表失败: {e!s}\n{traceback.format_exc()}") @@ -109,7 +110,8 @@ class PersonaRoute(Route): if persona.updated_at else None, }, - ).to_json() + ) + .to_json() ) except Exception as e: logger.error(f"获取人格详情失败: {e!s}\n{traceback.format_exc()}") @@ -143,7 +145,8 @@ class PersonaRoute(Route): if begin_dialogs and len(begin_dialogs) % 2 != 0: return ( Response() - .error("预设对话数量必须为偶数(用户和助手轮流对话)").to_json() + .error("预设对话数量必须为偶数(用户和助手轮流对话)") + .to_json() ) persona = await self.persona_mgr.create_persona( @@ -179,7 +182,8 @@ class PersonaRoute(Route): else None, }, }, - ).to_json() + ) + .to_json() ) except ValueError as e: return Response().error(str(e)).to_json() @@ -216,7 +220,8 @@ class PersonaRoute(Route): if begin_dialogs is not None and len(begin_dialogs) % 2 != 0: return ( Response() - .error("预设对话数量必须为偶数(用户和助手轮流对话)").to_json() + .error("预设对话数量必须为偶数(用户和助手轮流对话)") + .to_json() ) update_kwargs = { @@ -298,7 +303,8 @@ class PersonaRoute(Route): else None, }, }, - ).to_json() + ) + .to_json() ) except ValueError as e: return Response().error(str(e)).to_json() @@ -356,7 +362,8 @@ class PersonaRoute(Route): } for folder in folders ], - ).to_json() + ) + .to_json() ) except Exception as e: logger.error(f"获取文件夹列表失败: {e!s}\n{traceback.format_exc()}") @@ -400,7 +407,8 @@ class PersonaRoute(Route): if folder.updated_at else None, }, - ).to_json() + ) + .to_json() ) except Exception as e: logger.error(f"获取文件夹详情失败: {e!s}\n{traceback.format_exc()}") @@ -444,7 +452,8 @@ class PersonaRoute(Route): else None, }, }, - ).to_json() + ) + .to_json() ) except Exception as e: logger.error(f"创建文件夹失败: {e!s}\n{traceback.format_exc()}") @@ -517,12 +526,14 @@ class PersonaRoute(Route): if not all(k in item for k in ("id", "type", "sort_order")): return ( Response() - .error("每个 item 必须包含 id, type, sort_order 字段").to_json() + .error("每个 item 必须包含 id, type, sort_order 字段") + .to_json() ) if item["type"] not in ("persona", "folder"): return ( Response() - .error("type 字段必须是 'persona' 或 'folder'").to_json() + .error("type 字段必须是 'persona' 或 'folder'") + .to_json() ) await self.persona_mgr.batch_update_sort_order(items) diff --git a/astrbot/dashboard/routes/plugin.py b/astrbot/dashboard/routes/plugin.py index 4efa69802..fe0b3b48d 100644 --- a/astrbot/dashboard/routes/plugin.py +++ b/astrbot/dashboard/routes/plugin.py @@ -114,7 +114,8 @@ class PluginRoute(Route): "message": message, "astrbot_version": version_spec, } - ).to_json() + ) + .to_json() ) except Exception as e: return Response().error(str(e)).to_json() @@ -123,7 +124,8 @@ class PluginRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) try: data = await request.get_json() @@ -149,7 +151,8 @@ class PluginRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) data = await request.get_json() @@ -437,7 +440,8 @@ class PluginRoute(Route): _plugin_resp.append(_t) return ( Response() - .ok(_plugin_resp, message=self.plugin_manager.failed_plugin_info).to_json() + .ok(_plugin_resp, message=self.plugin_manager.failed_plugin_info) + .to_json() ) async def get_failed_plugins(self): @@ -509,7 +513,8 @@ class PluginRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) post_data = await request.get_json() @@ -547,7 +552,8 @@ class PluginRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) try: @@ -587,7 +593,8 @@ class PluginRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) post_data = await request.get_json() @@ -611,7 +618,8 @@ class PluginRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) post_data = await request.get_json() @@ -638,7 +646,8 @@ class PluginRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) post_data = await request.get_json() @@ -659,7 +668,8 @@ class PluginRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) post_data = await request.get_json() @@ -711,7 +721,8 @@ class PluginRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) post_data = await request.get_json() @@ -728,7 +739,8 @@ class PluginRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) post_data = await request.get_json() @@ -790,7 +802,8 @@ class PluginRoute(Route): return ( Response() - .ok({"content": readme_content}, "成功获取README内容").to_json() + .ok({"content": readme_content}, "成功获取README内容") + .to_json() ) except Exception as e: logger.error(f"/api/plugin/readme: {traceback.format_exc()}") @@ -850,7 +863,8 @@ class PluginRoute(Route): changelog_content = await f.read() return ( Response() - .ok({"content": changelog_content}, "成功获取更新日志").to_json() + .ok({"content": changelog_content}, "成功获取更新日志") + .to_json() ) except Exception as e: logger.error(f"/api/plugin/changelog: {traceback.format_exc()}") diff --git a/astrbot/dashboard/routes/session_management.py b/astrbot/dashboard/routes/session_management.py index 2c29d0ca4..9b339ae2b 100644 --- a/astrbot/dashboard/routes/session_management.py +++ b/astrbot/dashboard/routes/session_management.py @@ -241,7 +241,8 @@ class SessionManagementRoute(Route): "available_kbs": available_kbs, "available_rule_keys": AVAILABLE_SESSION_RULE_KEYS, } - ).to_json() + ) + .to_json() ) except Exception as e: logger.error(f"获取规则列表失败: {e!s}") @@ -280,7 +281,8 @@ class SessionManagementRoute(Route): return ( Response() - .ok({"message": f"规则 {rule_key} 已更新", "umo": umo}).to_json() + .ok({"message": f"规则 {rule_key} 已更新", "umo": umo}) + .to_json() ) except Exception as e: logger.error(f"更新会话规则失败: {e!s}") @@ -310,12 +312,15 @@ class SessionManagementRoute(Route): await sp.session_remove(umo, rule_key) return ( Response() - .ok({"message": f"规则 {rule_key} 已删除", "umo": umo}).to_json() + .ok({"message": f"规则 {rule_key} 已删除", "umo": umo}) + .to_json() ) else: # 删除该 umo 的所有规则 await sp.clear_async("umo", umo) - return Response().ok({"message": "所有规则已删除", "umo": umo}).to_json() + return ( + Response().ok({"message": "所有规则已删除", "umo": umo}).to_json() + ) except Exception as e: logger.error(f"删除会话规则失败: {e!s}") return Response().error(f"删除会话规则失败: {e!s}").to_json() @@ -408,7 +413,8 @@ class SessionManagementRoute(Route): "success_count": success_count, "failed_umos": failed_umos, } - ).to_json() + ) + .to_json() ) else: return ( @@ -418,7 +424,8 @@ class SessionManagementRoute(Route): "message": message, "success_count": success_count, } - ).to_json() + ) + .to_json() ) except Exception as e: logger.error(f"批量删除会话规则失败: {e!s}") @@ -593,7 +600,8 @@ class SessionManagementRoute(Route): "available_tts_providers": available_tts_providers, "available_stt_providers": available_stt_providers, } - ).to_json() + ) + .to_json() ) except Exception as e: logger.error(f"获取会话状态列表失败: {e!s}") @@ -710,7 +718,8 @@ class SessionManagementRoute(Route): "failed_count": len(failed_umos), "failed_umos": failed_umos, } - ).to_json() + ) + .to_json() ) except Exception as e: logger.error(f"批量更新服务状态失败: {e!s}") @@ -737,7 +746,8 @@ class SessionManagementRoute(Route): if not provider_type or not provider_id: return ( Response() - .error("缺少必要参数: provider_type, provider_id").to_json() + .error("缺少必要参数: provider_type, provider_id") + .to_json() ) # 转换 provider_type @@ -749,7 +759,8 @@ class SessionManagementRoute(Route): if provider_type not in provider_type_map: return ( Response() - .error(f"不支持的 provider_type: {provider_type}").to_json() + .error(f"不支持的 provider_type: {provider_type}") + .to_json() ) provider_type_enum = provider_type_map[provider_type] @@ -817,7 +828,8 @@ class SessionManagementRoute(Route): "failed_count": len(failed_umos), "failed_umos": failed_umos, } - ).to_json() + ) + .to_json() ) except Exception as e: logger.error(f"批量更新 Provider 失败: {e!s}") @@ -889,7 +901,8 @@ class SessionManagementRoute(Route): "umo_count": len(umos), }, } - ).to_json() + ) + .to_json() ) except Exception as e: logger.error(f"创建分组失败: {e!s}") @@ -945,7 +958,8 @@ class SessionManagementRoute(Route): "umo_count": len(group["umos"]), }, } - ).to_json() + ) + .to_json() ) except Exception as e: logger.error(f"更新分组失败: {e!s}") diff --git a/astrbot/dashboard/routes/skills.py b/astrbot/dashboard/routes/skills.py index be8fb60e0..4497d9aef 100644 --- a/astrbot/dashboard/routes/skills.py +++ b/astrbot/dashboard/routes/skills.py @@ -149,7 +149,8 @@ class SkillsRoute(Route): "runtime": runtime, "sandbox_cache": skill_mgr.get_sandbox_skills_cache_status(), } - ).to_json() + ) + .to_json() ) except Exception as e: logger.error(traceback.format_exc()) @@ -159,7 +160,8 @@ class SkillsRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) temp_path = None @@ -200,7 +202,8 @@ class SkillsRoute(Route): return ( Response() - .ok({"name": skill_name}, "Skill uploaded successfully.").to_json() + .ok({"name": skill_name}, "Skill uploaded successfully.") + .to_json() ) except Exception as e: logger.error(traceback.format_exc()) @@ -217,7 +220,8 @@ class SkillsRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) try: @@ -320,7 +324,8 @@ class SkillsRoute(Route): "skipped": skipped, }, message, - ).to_json() + ) + .to_json() ) if failed_count == 0 and success_count == 0: message = f"All {total} file(s) were skipped." @@ -334,7 +339,8 @@ class SkillsRoute(Route): "skipped": skipped, }, message, - ).to_json() + ) + .to_json() ) if success_count == 0 and skipped_count == 0: message = f"Upload failed for all {total} file(s)." @@ -358,7 +364,8 @@ class SkillsRoute(Route): "skipped": skipped, }, message, - ).to_json() + ) + .to_json() ) except Exception as e: @@ -379,7 +386,8 @@ class SkillsRoute(Route): Response() .error( "Sandbox preset skill cannot be downloaded from local skill files." - ).to_json() + ) + .to_json() ) skill_dir = Path(skill_mgr.skills_root) / name @@ -415,7 +423,8 @@ class SkillsRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) try: data = await request.get_json() @@ -433,7 +442,8 @@ class SkillsRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) try: data = await request.get_json() @@ -511,7 +521,8 @@ class SkillsRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) logger.info("[Neo] POST /skills/neo/evaluate requested.") data = await request.get_json() @@ -540,7 +551,8 @@ class SkillsRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) logger.info("[Neo] POST /skills/neo/promote requested.") data = await request.get_json() @@ -596,7 +608,8 @@ class SkillsRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) logger.info("[Neo] POST /skills/neo/rollback requested.") data = await request.get_json() @@ -615,7 +628,8 @@ class SkillsRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) logger.info("[Neo] POST /skills/neo/sync requested.") data = await request.get_json() @@ -649,7 +663,8 @@ class SkillsRoute(Route): "map_path": result.map_path, "synced_at": result.synced_at, } - ).to_json() + ) + .to_json() ) return await self._with_neo_client(_do) @@ -658,7 +673,8 @@ class SkillsRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) logger.info("[Neo] POST /skills/neo/delete-candidate requested.") data = await request.get_json() @@ -678,7 +694,8 @@ class SkillsRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) logger.info("[Neo] POST /skills/neo/delete-release requested.") data = await request.get_json() diff --git a/astrbot/dashboard/routes/stat.py b/astrbot/dashboard/routes/stat.py index 61152418e..a36acca5e 100644 --- a/astrbot/dashboard/routes/stat.py +++ b/astrbot/dashboard/routes/stat.py @@ -4,8 +4,8 @@ import re import threading import time import traceback -from dataclasses import asdict from collections import defaultdict +from dataclasses import asdict from datetime import datetime, timedelta, timezone from functools import cmp_to_key from pathlib import Path @@ -72,7 +72,8 @@ class StatRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) await self.core_lifecycle.restart() @@ -99,7 +100,8 @@ class StatRoute(Route): "change_pwd_hint": self.is_default_cred(), "need_migration": need_migration, }, - ).to_json() + ) + .to_json() ) async def get_start_time(self): @@ -439,7 +441,9 @@ class StatRoute(Route): } return Response().ok(data=ret).to_json() return ( - Response().error(f"Failed. Status code: {response.status}").to_json() + Response() + .error(f"Failed. Status code: {response.status}") + .to_json() ) except Exception as e: logger.error(traceback.format_exc()) @@ -477,12 +481,14 @@ class StatRoute(Route): if not await anyio.Path(changelog_path).exists(): return ( Response() - .error(f"Changelog for version {version} not found").to_json() + .error(f"Changelog for version {version} not found") + .to_json() ) if not await anyio.Path(changelog_path).is_file(): return ( Response() - .error(f"Changelog for version {version} not found").to_json() + .error(f"Changelog for version {version} not found") + .to_json() ) async with await anyio.open_file(changelog_path, encoding="utf-8") as f: diff --git a/astrbot/dashboard/routes/tools.py b/astrbot/dashboard/routes/tools.py index 45dd6d54c..ee5eca22e 100644 --- a/astrbot/dashboard/routes/tools.py +++ b/astrbot/dashboard/routes/tools.py @@ -148,7 +148,8 @@ class ToolsRoute(Route): if not has_valid_config: return ( Response() - .error("A valid server configuration is required").to_json() + .error("A valid server configuration is required") + .to_json() ) config = self.tool_mgr.load_mcp_config() @@ -186,7 +187,8 @@ class ToolsRoute(Route): return Response().error(err_msg).to_json() return ( Response() - .ok(None, f"Successfully added MCP server {name}").to_json() + .ok(None, f"Successfully added MCP server {name}") + .to_json() ) return Response().error("Failed to save configuration").to_json() except Exception as e: @@ -277,7 +279,8 @@ class ToolsRoute(Route): Response() .error( f"Timed out while disabling MCP server {old_name} before enabling: {e!s}" - ).to_json() + ) + .to_json() ) except Exception as e: logger.error(traceback.format_exc()) @@ -285,7 +288,8 @@ class ToolsRoute(Route): Response() .error( f"Failed to disable MCP server {old_name} before enabling: {e!s}" - ).to_json() + ) + .to_json() ) try: await self.tool_mgr.enable_mcp_server( @@ -296,13 +300,15 @@ class ToolsRoute(Route): except TimeoutError: return ( Response() - .error(f"Timed out while enabling MCP server {name}.").to_json() + .error(f"Timed out while enabling MCP server {name}.") + .to_json() ) except Exception as e: logger.error(traceback.format_exc()) return ( Response() - .error(f"Failed to enable MCP server {name}: {e!s}").to_json() + .error(f"Failed to enable MCP server {name}: {e!s}") + .to_json() ) # 如果要停用服务器 elif old_name in self.tool_mgr.mcp_server_runtime_view: @@ -311,18 +317,21 @@ class ToolsRoute(Route): except TimeoutError: return ( Response() - .error(f"Timed out while disabling MCP server {old_name}.").to_json() + .error(f"Timed out while disabling MCP server {old_name}.") + .to_json() ) except Exception as e: logger.error(traceback.format_exc()) return ( Response() - .error(f"Failed to disable MCP server {old_name}: {e!s}").to_json() + .error(f"Failed to disable MCP server {old_name}: {e!s}") + .to_json() ) return ( Response() - .ok(None, f"Successfully updated MCP server {name}").to_json() + .ok(None, f"Successfully updated MCP server {name}") + .to_json() ) return Response().error("Failed to save configuration").to_json() except Exception as e: @@ -351,17 +360,20 @@ class ToolsRoute(Route): except TimeoutError: return ( Response() - .error(f"Timed out while disabling MCP server {name}.").to_json() + .error(f"Timed out while disabling MCP server {name}.") + .to_json() ) except Exception as e: logger.error(traceback.format_exc()) return ( Response() - .error(f"Failed to disable MCP server {name}: {e!s}").to_json() + .error(f"Failed to disable MCP server {name}: {e!s}") + .to_json() ) return ( Response() - .ok(None, f"Successfully deleted MCP server {name}").to_json() + .ok(None, f"Successfully deleted MCP server {name}") + .to_json() ) return Response().error("Failed to save configuration").to_json() except Exception as e: @@ -384,27 +396,31 @@ class ToolsRoute(Route): Response() .error( "Only one MCP server configuration can be tested at a time" - ).to_json() + ) + .to_json() ) try: config = _extract_mcp_server_config(mcp_servers) except EmptyMcpServersError: return ( Response() - .error("MCP server configuration cannot be empty").to_json() + .error("MCP server configuration cannot be empty") + .to_json() ) except ValueError as e: return Response().error(f"{e!s}").to_json() elif not config: return ( Response() - .error("MCP server configuration cannot be empty").to_json() + .error("MCP server configuration cannot be empty") + .to_json() ) tools_name = await self.tool_mgr.test_mcp_server_connection(config) return ( Response() - .ok(data=tools_name, message="🎉 MCP server is available!").to_json() + .ok(data=tools_name, message="🎉 MCP server is available!") + .to_json() ) except Exception as e: @@ -461,7 +477,8 @@ class ToolsRoute(Route): if not tool_name or action is None: return ( Response() - .error("Missing required parameters: name or activate").to_json() + .error("Missing required parameters: name or activate") + .to_json() ) # Internal tools cannot be toggled by users @@ -481,7 +498,8 @@ class ToolsRoute(Route): return Response().ok(None, "Operation successful.").to_json() return ( Response() - .error(f"Tool {tool_name} does not exist or the operation failed.").to_json() + .error(f"Tool {tool_name} does not exist or the operation failed.") + .to_json() ) except Exception as e: diff --git a/astrbot/dashboard/routes/tui_chat.py b/astrbot/dashboard/routes/tui_chat.py index 931f41aa1..9b0b13632 100644 --- a/astrbot/dashboard/routes/tui_chat.py +++ b/astrbot/dashboard/routes/tui_chat.py @@ -182,7 +182,8 @@ class TUIChatRoute(Route): "filename": filename, "type": attach_type, } - ).to_json() + ) + .to_json() ) async def _build_user_message_parts(self, message: str | list) -> list[dict]: @@ -266,7 +267,8 @@ class TUIChatRoute(Route): if not webchat_message_parts_have_content(message_parts): return ( Response() - .error("Message content is empty (reply only is not allowed)").to_json() + .error("Message content is empty (reply only is not allowed)") + .to_json() ) message_id = str(uuid.uuid4()) @@ -597,7 +599,8 @@ class TUIChatRoute(Route): "failed_count": len(failed_items), "failed_items": failed_items, } - ).to_json() + ) + .to_json() ) def _extract_attachment_ids(self, history_list) -> list[str]: @@ -651,7 +654,8 @@ class TUIChatRoute(Route): "session_id": session.session_id, "platform_id": session.platform_id, } - ).to_json() + ) + .to_json() ) async def get_sessions(self): diff --git a/astrbot/dashboard/routes/update.py b/astrbot/dashboard/routes/update.py index f040c96fe..80246fc5c 100644 --- a/astrbot/dashboard/routes/update.py +++ b/astrbot/dashboard/routes/update.py @@ -59,7 +59,8 @@ class UpdateRoute(Route): if type_ == "dashboard": return ( Response() - .ok({"has_new_version": dv != f"v{VERSION}", "current_version": dv}).to_json() + .ok({"has_new_version": dv != f"v{VERSION}", "current_version": dv}) + .to_json() ) ret = await self.astrbot_updator.check_update(None, None, False) return Response( @@ -121,12 +122,14 @@ class UpdateRoute(Route): await self.core_lifecycle.restart() ret = ( Response() - .ok(None, "更新成功,AstrBot 将在 2 秒内全量重启以应用新的代码。").to_json() + .ok(None, "更新成功,AstrBot 将在 2 秒内全量重启以应用新的代码。") + .to_json() ) return ret, 200, CLEAR_SITE_DATA_HEADERS ret = ( Response() - .ok(None, "更新成功,AstrBot 将在下次启动时应用新的代码。").to_json() + .ok(None, "更新成功,AstrBot 将在下次启动时应用新的代码。") + .to_json() ) return ret, 200, CLEAR_SITE_DATA_HEADERS except Exception as e: @@ -150,7 +153,8 @@ class UpdateRoute(Route): if DEMO_MODE: return ( Response() - .error("You are not permitted to do this operation in demo mode").to_json() + .error("You are not permitted to do this operation in demo mode") + .to_json() ) data = await request.json diff --git a/astrbot/dashboard/server.py b/astrbot/dashboard/server.py index 598717cda..0a8b594d0 100644 --- a/astrbot/dashboard/server.py +++ b/astrbot/dashboard/server.py @@ -591,6 +591,7 @@ class AstrBotDashboard: host = _resolve_dashboard_value(host_value, field_name="host") if not isinstance(host, str) or not host: raise ValueError("Dashboard host must be a non-empty string") + @staticmethod def _resolve_dashboard_ssl_config( ssl_config: dict, @@ -646,7 +647,7 @@ class AstrBotDashboard: return True, resolved_ssl_config - def run(self): + async def run(self): ip_addr = [] dashboard_config = self.core_lifecycle.astrbot_config.get("dashboard", {}) port = ( @@ -674,7 +675,6 @@ class AstrBotDashboard: ssl_config, ) scheme = "https" if ssl_enable else "http" ->>>>>>> origin/master # Port priority: ASTRBOT_PORT env var > cmd_config.json dashboard.port > default 6185 env_port = os.environ.get("ASTRBOT_PORT") @@ -732,7 +732,6 @@ class AstrBotDashboard: config.bind = binds if ssl_enable: -<<<<<<< HEAD cert_file = os.environ.get("ASTRBOT_SSL_CERT") or ssl_config.get( "cert_file", "" ) @@ -762,10 +761,6 @@ class AstrBotDashboard: if not await ca_path.is_file(): raise ValueError(f"SSL CA 证书文件不存在: {ca_path}") config.ca_certs = str(await ca_path.resolve()) - config.certfile = resolved_ssl_config["certfile"] - config.keyfile = resolved_ssl_config["keyfile"] - if "ca_certs" in resolved_ssl_config: - config.ca_certs = resolved_ssl_config["ca_certs"] # 根据配置决定是否禁用访问日志 disable_access_log = dashboard_config.get("disable_access_log", True) diff --git a/dashboard/src/views/SubAgentPage.vue b/dashboard/src/views/SubAgentPage.vue index 63ae4209d..9dd3418d6 100644 --- a/dashboard/src/views/SubAgentPage.vue +++ b/dashboard/src/views/SubAgentPage.vue @@ -1,27 +1,50 @@