fix(message_tools): throw exception and block message sending when path does not exist (#8149)

* 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 <badbatch0x01@gmail.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: RC-CHN <1051989940@qq.com>
This commit is contained in:
NayukiMeko
2026-05-13 11:50:06 +08:00
committed by GitHub
parent 4672a04eb7
commit 22ba831a31
2 changed files with 42 additions and 6 deletions

View File

@@ -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}"

View File

@@ -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()