feat: update permission handling to delegate event context in guarded tools (#9001)

This commit is contained in:
Weilong Liao
2026-06-25 16:18:04 +08:00
committed by GitHub
parent 9daf8f0a84
commit 473377f340
2 changed files with 16 additions and 9 deletions

View File

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

View File

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