diff --git a/astrbot/core/agent/mcp_client.py b/astrbot/core/agent/mcp_client.py index b75999ea65..6cedc2202e 100644 --- a/astrbot/core/agent/mcp_client.py +++ b/astrbot/core/agent/mcp_client.py @@ -347,11 +347,9 @@ def _normalize(node: Any) -> Any: properties = normalized.get("properties") if isinstance(properties, dict): - original_properties = ( - node.get("properties") - if isinstance(node.get("properties"), dict) - else {} - ) + original_properties = node.get("properties") + if not isinstance(original_properties, dict): + original_properties = {} required = normalized.get("required") required_list = required[:] if isinstance(required, list) else [] diff --git a/astrbot/core/agent/runners/tool_loop_agent_runner.py b/astrbot/core/agent/runners/tool_loop_agent_runner.py index 1b9f5a5929..172fd0ab0a 100644 --- a/astrbot/core/agent/runners/tool_loop_agent_runner.py +++ b/astrbot/core/agent/runners/tool_loop_agent_runner.py @@ -5,7 +5,6 @@ import traceback import typing as T import uuid -from collections.abc import AsyncIterator from contextlib import suppress from dataclasses import dataclass, field, replace from pathlib import Path @@ -1396,7 +1395,7 @@ async def _close_executor(self, executor: T.Any) -> None: async def _iter_tool_executor_results( self, - executor: AsyncIterator[ToolExecutorResultT], + executor: T.AsyncGenerator[ToolExecutorResultT, None], ) -> T.AsyncGenerator[ToolExecutorResultT, None]: while True: if self._is_stop_requested(): diff --git a/astrbot/core/computer/booters/cua.py b/astrbot/core/computer/booters/cua.py index 151b4c0e04..db6d1d94e1 100644 --- a/astrbot/core/computer/booters/cua.py +++ b/astrbot/core/computer/booters/cua.py @@ -75,12 +75,14 @@ def _maybe_model_dump(value: Any) -> dict[str, Any]: return value if is_dataclass(value) and not isinstance(value, type): return asdict(value) - if hasattr(value, "model_dump"): - dumped = value.model_dump() + model_dump = getattr(value, "model_dump", None) + if callable(model_dump): + dumped = model_dump() if isinstance(dumped, dict): return dumped - if hasattr(value, "dict"): - dumped = value.dict() + dict_attr = getattr(value, "dict", None) + if callable(dict_attr): + dumped = dict_attr() if isinstance(dumped, dict): return dumped attr_payload = { @@ -134,6 +136,11 @@ def first_text(*keys: str) -> str: exit_code = payload.get("returncode") if exit_code is None: exit_code = payload.get("return_code") + if exit_code is not None: + try: + exit_code = int(exit_code) + except Exception: + exit_code = None if exit_code is None: exit_code = 0 if not stderr else 1 success = bool(payload.get("success", not stderr and exit_code in (0, None))) diff --git a/astrbot/core/db/sqlite.py b/astrbot/core/db/sqlite.py index d79ac9d703..2f837868ba 100644 --- a/astrbot/core/db/sqlite.py +++ b/astrbot/core/db/sqlite.py @@ -558,7 +558,7 @@ async def update_platform_message_history( async with session.begin(): await session.execute( update(PlatformMessageHistory) - .where(PlatformMessageHistory.id == message_id) + .where(col(PlatformMessageHistory.id) == message_id) .values(**values) ) @@ -569,7 +569,7 @@ async def delete_platform_message_history_by_id(self, message_id: int) -> None: async with session.begin(): await session.execute( delete(PlatformMessageHistory).where( - PlatformMessageHistory.id == message_id + col(PlatformMessageHistory.id) == message_id ) ) @@ -676,7 +676,7 @@ async def get_webchat_threads_by_parent_session( ) if creator is not None: query = query.where(WebChatThread.creator == creator) - query = query.order_by(WebChatThread.created_at) + query = query.order_by(col(WebChatThread.created_at)) result = await session.execute(query) return list(result.scalars().all()) @@ -706,7 +706,9 @@ async def delete_webchat_thread(self, thread_id: str) -> None: session: AsyncSession async with session.begin(): await session.execute( - delete(WebChatThread).where(WebChatThread.thread_id == thread_id) + delete(WebChatThread).where( + col(WebChatThread.thread_id) == thread_id + ) ) async def delete_webchat_threads_by_parent_session( diff --git a/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py b/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py index b1a8156a45..7a79a7a411 100644 --- a/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +++ b/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py @@ -774,7 +774,11 @@ def handle_retry(error_msg: str) -> bool: self._shutdown_event = threading.Event() task = loop.create_task(self.client_.start()) # 当 task 完成时唤醒线程(无论是正常退出还是异常退出) - task.add_done_callback(lambda _: self._shutdown_event.set()) + task.add_done_callback( + lambda _: ( + self._shutdown_event.set() if self._shutdown_event else None + ) + ) self._shutdown_event.wait() if task.done(): try: diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py index fa10d28767..6f6068be9e 100644 --- a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +++ b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py @@ -595,7 +595,7 @@ async def post_c2c_message( markdown: message.MarkdownPayload | None = None, keyboard: message.Keyboard | None = None, stream: dict | None = None, - ) -> message.Message: + ) -> message.Message | None: payload = locals() payload.pop("self", None) # QQ API does not accept stream.id=None; remove it when not yet assigned diff --git a/astrbot/core/provider/sources/anthropic_source.py b/astrbot/core/provider/sources/anthropic_source.py index 5caef0b263..1e2dbfe8c7 100644 --- a/astrbot/core/provider/sources/anthropic_source.py +++ b/astrbot/core/provider/sources/anthropic_source.py @@ -226,7 +226,8 @@ def _prepare_payload(self, messages: list[dict]): if isinstance(last_message, dict) else None ) - can_append_to_previous_tool_results = ( + + if ( last_message is not None and last_message.get("role") == "user" and isinstance(last_content, list) @@ -235,9 +236,7 @@ def _prepare_payload(self, messages: list[dict]): isinstance(block, dict) and block.get("type") == "tool_result" for block in last_content ) - ) - - if can_append_to_previous_tool_results: + ): last_content.append(tool_result_block) else: new_messages.append( diff --git a/astrbot/core/star/context.py b/astrbot/core/star/context.py index 593bad9365..62124f7f36 100644 --- a/astrbot/core/star/context.py +++ b/astrbot/core/star/context.py @@ -55,6 +55,7 @@ class PlatformManagerProtocol(Protocol): platform_insts: list[Platform] + get_insts: Callable[[], list[Platform]] class Context: diff --git a/astrbot/core/star/updator.py b/astrbot/core/star/updator.py index c647779069..9eeda1cb83 100644 --- a/astrbot/core/star/updator.py +++ b/astrbot/core/star/updator.py @@ -55,7 +55,7 @@ async def update( f"Downloading plugin update archive for {plugin.name}: {download_url}" ) await self._download_file(download_url, plugin_path + ".zip") - else: + elif repo_url: await self.download_from_repo_url(plugin_path, repo_url, proxy=proxy) try: diff --git a/astrbot/core/tools/computer_tools/cua.py b/astrbot/core/tools/computer_tools/cua.py index 7b37a55086..913bec00b4 100644 --- a/astrbot/core/tools/computer_tools/cua.py +++ b/astrbot/core/tools/computer_tools/cua.py @@ -7,6 +7,7 @@ from typing import Any import mcp +from mcp.types import ContentBlock from astrbot.api import FunctionTool from astrbot.core.agent.run_context import ContextWrapper @@ -88,7 +89,7 @@ async def call( await context.context.event.send(MessageChain().file_image(path)) payload["sent_to_user"] = True image_data = payload.pop("base64", "") - content: list[mcp.types.TextContent | mcp.types.ImageContent] = [ + content: list[ContentBlock] = [ mcp.types.TextContent(type="text", text=_to_json(payload)) ] if return_image_to_llm: diff --git a/astrbot/core/tools/registry.py b/astrbot/core/tools/registry.py index c3b10d2295..c76fb556ef 100644 --- a/astrbot/core/tools/registry.py +++ b/astrbot/core/tools/registry.py @@ -3,7 +3,7 @@ from collections.abc import Callable from dataclasses import dataclass from importlib import import_module -from typing import Any, TypeVar +from typing import Any, TypeVar, overload from astrbot.core.agent.tool import FunctionTool @@ -213,6 +213,22 @@ def _resolve_builtin_tool_name(tool_cls: type[FunctionTool]) -> str: ) +@overload +def builtin_tool( + tool_cls: None = None, + *, + config: dict[str, Any] | None = None, +) -> Callable[[TFunctionTool], TFunctionTool]: ... + + +@overload +def builtin_tool( + tool_cls: TFunctionTool, + *, + config: dict[str, Any] | None = None, +) -> TFunctionTool: ... + + def builtin_tool( tool_cls: TFunctionTool | None = None, *, diff --git a/astrbot/dashboard/routes/plugin.py b/astrbot/dashboard/routes/plugin.py index 10d87eabea..8775dd945e 100644 --- a/astrbot/dashboard/routes/plugin.py +++ b/astrbot/dashboard/routes/plugin.py @@ -1548,7 +1548,7 @@ def _build_command_group_component( self._build_command_group_child(sub_filter) for sub_filter in command_group_filter.sub_command_filters ] - component = { + component: dict[str, object] = { "type": "command", "name": parts[-1], "description": self._get_command_description(