From 22ba831a3165462f75ef4762abb17cf497dcbac6 Mon Sep 17 00:00:00 2001 From: NayukiMeko Date: Wed, 13 May 2026 11:50:06 +0800 Subject: [PATCH] fix(message_tools): throw exception and block message sending when path does not exist (#8149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(message_tools): 路径不存在时抛出异常并阻止消息发送 - _resolve_path_from_sandbox 在所有解析路径均失败时改为抛出 FileNotFoundError,而非静默返回原始路径,避免将无效路径传递给下游组件 - 新增 component_type 关键字参数,使错误信息能明确指出是 image/record/video/file 哪类资源路径缺失 - 在 call 方法中捕获 FileNotFoundError 并提前返回错误字符串,确保路径无效时不会继续构建或发送任何消息组件 - 补充单元测试,验证缺失图片路径场景下 send_message 不会被调用 * Update tests/unit/test_message_tools.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * fix(tools): propagate sandbox error instead of masking as FileNotFoundError --------- Co-authored-by: Ruochen Pan Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: RC-CHN <1051989940@qq.com> --- astrbot/core/tools/message_tools.py | 20 ++++++++++++++------ tests/unit/test_message_tools.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/astrbot/core/tools/message_tools.py b/astrbot/core/tools/message_tools.py index c3f33a7e8..c57d6b73d 100644 --- a/astrbot/core/tools/message_tools.py +++ b/astrbot/core/tools/message_tools.py @@ -79,8 +79,13 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]): ) async def _resolve_path_from_sandbox( - self, context: ContextWrapper[AstrAgentContext], path: str + self, + context: ContextWrapper[AstrAgentContext], + path: str, + *, + component_type: str = "file", ) -> tuple[str, bool]: + path = str(path) # if the path is relative, check if the file exists in user's local workspace if not os.path.isabs(path): unified_msg_origin = context.context.event.unified_msg_origin @@ -115,8 +120,9 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]): return local_path, True except Exception as exc: logger.warning(f"Failed to check/download file from sandbox: {exc}") + raise - return path, False + raise FileNotFoundError(f"{component_type} path does not exist: {path}") async def call( self, context: ContextWrapper[AstrAgentContext], **kwargs @@ -155,7 +161,7 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]): url = msg.get("url") if path: local_path, _ = await self._resolve_path_from_sandbox( - context, path + context, path, component_type="image" ) components.append(Comp.Image.fromFileSystem(path=local_path)) elif url: @@ -167,7 +173,7 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]): url = msg.get("url") if path: local_path, _ = await self._resolve_path_from_sandbox( - context, path + context, path, component_type="record" ) components.append(Comp.Record.fromFileSystem(path=local_path)) elif url: @@ -179,7 +185,7 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]): url = msg.get("url") if path: local_path, _ = await self._resolve_path_from_sandbox( - context, path + context, path, component_type="video" ) components.append(Comp.Video.fromFileSystem(path=local_path)) elif url: @@ -197,7 +203,7 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]): ) if path: local_path, _ = await self._resolve_path_from_sandbox( - context, path + context, path, component_type="file" ) components.append(Comp.File(name=name, file=local_path)) elif url: @@ -213,6 +219,8 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]): return ( f"error: unsupported message type '{msg_type}' at index {idx}." ) + except FileNotFoundError as exc: + return f"error: {exc}" except Exception as exc: return f"error: failed to build messages[{idx}] component: {exc}" diff --git a/tests/unit/test_message_tools.py b/tests/unit/test_message_tools.py index eedee80ab..28896b6d2 100644 --- a/tests/unit/test_message_tools.py +++ b/tests/unit/test_message_tools.py @@ -133,3 +133,31 @@ async def test_send_message_empty_messages_returns_error(): result = await tool.call(ctx, messages=[], session="oc_xxx") assert "error:" in result assert "messages" in result.lower() + + +@pytest.mark.asyncio +async def test_send_message_missing_image_path_stops_before_send(tmp_path, monkeypatch): + """Missing image paths fail before sending any message components.""" + tool = SendMessageToUserTool() + ctx = _make_context() + missing_image_path = tmp_path / "missing.png" + + async def mock_get_booter(*args, **kwargs): + del args, kwargs + raise RuntimeError("sandbox unavailable") + + monkeypatch.setattr( + "astrbot.core.tools.message_tools.get_booter", + mock_get_booter, + ) + + result = await tool.call( + ctx, + messages=[ + {"type": "plain", "text": "before image"}, + {"type": "image", "path": str(missing_image_path)}, + ], + ) + + assert "error: failed to build messages[1] component: sandbox unavailable" in result + ctx.context.context.send_message.assert_not_called()