e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
48 lines
1.7 KiB
Python
48 lines
1.7 KiB
Python
"""ToolRegistry.execute: tool-name arg must not collide with a tool's own params.
|
|
|
|
Regression for the ``read_skill(name=...)`` dispatch bug: the registry takes
|
|
the tool *name* as its first parameter, which collided with any tool whose
|
|
schema declares a ``name`` argument (read_skill, and potentially MCP tools).
|
|
The fix makes the tool-name parameter positional-only.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from deeptutor.core.tool_protocol import BaseTool, ToolDefinition, ToolParameter, ToolResult
|
|
from deeptutor.runtime.registry.tool_registry import ToolRegistry
|
|
|
|
|
|
class _NameParamTool(BaseTool):
|
|
"""A tool whose own argument is literally called ``name``."""
|
|
|
|
def get_definition(self) -> ToolDefinition:
|
|
return ToolDefinition(
|
|
name="thing_reader",
|
|
description="reads a thing by name",
|
|
parameters=[ToolParameter(name="name", type="string")],
|
|
)
|
|
|
|
async def execute(self, **kwargs: object) -> ToolResult:
|
|
return ToolResult(content=f"read:{kwargs.get('name')}")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_passes_name_argument_without_collision() -> None:
|
|
reg = ToolRegistry()
|
|
reg.register(_NameParamTool())
|
|
# Tool name positional, tool's own ``name`` arg as keyword — must not
|
|
# raise "got multiple values for argument 'name'".
|
|
result = await reg.execute("thing_reader", name="widget")
|
|
assert result.content == "read:widget"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_forwards_event_sink_alongside_name() -> None:
|
|
reg = ToolRegistry()
|
|
reg.register(_NameParamTool())
|
|
# Mirrors the dispatcher, which always passes event_sink plus tool args.
|
|
result = await reg.execute("thing_reader", event_sink=None, name="gadget")
|
|
assert result.content == "read:gadget"
|