diff --git a/astrbot/core/provider/func_tool_manager.py b/astrbot/core/provider/func_tool_manager.py index 7afcbbd09..8a2565c41 100644 --- a/astrbot/core/provider/func_tool_manager.py +++ b/astrbot/core/provider/func_tool_manager.py @@ -249,9 +249,10 @@ class _PermissionGuardedTool(FunctionTool): if error is not None: return error - # Delegate to handler first (plugin tools). + # @filter.llm_tool decorated tools have a handler attribute, which is the actual callable. if self._wrapped.handler is not None: - result = self._wrapped.handler(context, **kwargs) + event = context.context.event + result = self._wrapped.handler(event, **kwargs) if _inspect.isasyncgen(result): last: Any = None async for item in result: @@ -261,11 +262,12 @@ class _PermissionGuardedTool(FunctionTool): return await result return result - # Fall back to overridden call() on subclasses (e.g. MCPTool). + # If the tool has a "call" method that is not the default FunctionTool.call, invoke it. call_override = getattr(type(self._wrapped), "call", None) if call_override is not None and call_override is not FunctionTool.call: return await self._wrapped.call(context, **kwargs) + # Compatibility fallback: if the tool has a "run" method, invoke it. This is for legacy tools that don't use the new handler/call interface. run = getattr(self._wrapped, "run", None) if run is not None: event = context.context.event diff --git a/tests/unit/test_tool_permission.py b/tests/unit/test_tool_permission.py index 40a09cbb8..bb7626aaa 100644 --- a/tests/unit/test_tool_permission.py +++ b/tests/unit/test_tool_permission.py @@ -174,16 +174,19 @@ async def test_check_permission_passes_for_member_when_configured_member(): @pytest.mark.asyncio -async def test_guarded_tool_delegates_when_permission_passes(): +async def test_guarded_tool_delegates_handler_with_event_when_permission_passes(): _clear_tool_permissions() mgr = FunctionToolManager() called = False + received_event = None - async def handler(ctx, **kw): + async def handler(event, **kw): nonlocal called + nonlocal received_event called = True - return "ok" + received_event = event + return f"ok:{event.get_sender_id()}:{kw['value']}" wrapped = FunctionTool( name="delegated", @@ -194,9 +197,10 @@ async def test_guarded_tool_delegates_when_permission_passes(): guarded = _PermissionGuardedTool(wrapped, mgr) context = _make_context(role="member") - result = await guarded.call(context) + result = await guarded.call(context, value="sentinel") assert called - assert result == "ok" + assert received_event is context.context.event + assert result == "ok:user_123:sentinel" @pytest.mark.asyncio @@ -280,7 +284,8 @@ async def test_guarded_tool_handles_async_generator_handler(): _clear_tool_permissions() mgr = FunctionToolManager() - async def gen_handler(ctx, **kw): # type: ignore[misc] + async def gen_handler(event, **kw): # type: ignore[misc] + assert event is context.context.event yield "A" yield "B" yield "C"