fix(tool): implement tool_type filter in ToolRegistry.list_tools() #3308

Merged
freemo merged 1 commits from fix/tool-registry-list-tools-type-filter into master 2026-04-05 21:07:34 +00:00
6 changed files with 114 additions and 8 deletions
+43 -2
View File
@@ -966,9 +966,50 @@ Feature: Consolidated Tool
# ---- List Tools With tool_type Filter ----
Scenario: List tools with tool_type filter
Scenario: List tools with tool_type filter returns matching tools
Given a tool registry
And a registered tool spec named "test/filtered"
And a registered tool spec named "test/filtered" with tool_type "tool"
When I list tools with tool_type "tool"
Then the tool list should contain 1 tools
Scenario: List tools with tool_type validation returns only validations
Given a tool registry
And a registered tool spec named "test/my-tool" with tool_type "tool"
And a registered tool spec named "test/my-validation" with tool_type "validation"
When I list tools with tool_type "validation"
Then the tool list should contain 1 tools
Scenario: List tools with tool_type tool excludes validations
Given a tool registry
And a registered tool spec named "test/plain-tool" with tool_type "tool"
And a registered tool spec named "test/plain-validation" with tool_type "validation"
When I list tools with tool_type "tool"
Then the tool list should contain 1 tools
Scenario: List tools without tool_type filter returns all tools
Given a tool registry
And a registered tool spec named "test/all-tool" with tool_type "tool"
And a registered tool spec named "test/all-validation" with tool_type "validation"
When I list all tools
Then the tool list should contain 2 tools
Scenario: List tools with tool_type None returns all tools
Given a tool registry
And a registered tool spec named "test/none-tool" with tool_type "tool"
And a registered tool spec named "test/none-validation" with tool_type "validation"
When I list all tools
Then the tool list should contain 2 tools
Scenario: ToolSpec default tool_type is tool
Given a tool registry
And a registered tool spec named "test/default-type"
When I list tools with tool_type "tool"
Then the tool list should contain 1 tools
Scenario: ToolSpec with validation tool_type is excluded from tool filter
Given a tool registry
And a registered tool spec named "test/excl-validation" with tool_type "validation"
When I list tools with tool_type "tool"
Then the tool list should contain 0 tools
+11
View File
@@ -68,6 +68,17 @@ def step_given_registered_tool_spec(context: Any, name: str) -> None:
context.registry.register(spec)
@given('a registered tool spec named "{name}" with tool_type "{tt}"')
def step_given_registered_tool_spec_with_type(context: Any, name: str, tt: str) -> None:
spec = ToolSpec(
name=name,
description=f"Test tool {name}",
handler=_echo_handler,
tool_type=tt,
)
context.registry.register(spec)
@given('a registered tool spec named "{name}" with an adder handler')
def step_given_registered_adder(context: Any, name: str) -> None:
spec = ToolSpec(
+34
View File
@@ -193,9 +193,43 @@ def validation_detach() -> None:
print("validation-cli-detach-ok")
def tool_list_type_tool() -> None:
"""Verify tool list --type tool returns only tools."""
svc = MagicMock()
svc.list_tools.return_value = [_mock_tool("local/smoke-tool", "tool")]
with patch(
"cleveragents.cli.commands.tool._get_tool_registry_service",
return_value=svc,
):
result = runner.invoke(tool_app, ["list", "--type", "tool"])
assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}"
svc.list_tools.assert_called_once_with(
namespace=None, tool_type="tool", source=None
)
print("tool-cli-list-type-tool-ok")
def tool_list_type_validation() -> None:
"""Verify tool list --type validation returns only validations."""
svc = MagicMock()
svc.list_tools.return_value = [_mock_tool("local/smoke-val", "validation")]
with patch(
"cleveragents.cli.commands.tool._get_tool_registry_service",
return_value=svc,
):
result = runner.invoke(tool_app, ["list", "--type", "validation"])
assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}"
svc.list_tools.assert_called_once_with(
namespace=None, tool_type="validation", source=None
)
print("tool-cli-list-type-validation-ok")
_COMMANDS = {
"tool-add-config": tool_add_config,
"tool-list": tool_list_all,
"tool-list-type-tool": tool_list_type_tool,
"tool-list-type-validation": tool_list_type_validation,
"tool-show": tool_show_name,
"tool-remove": tool_remove_name,
"validation-add-config": validation_add_config,
+16
View File
@@ -55,3 +55,19 @@ Validation Detach Removes Attachment
${result}= Run Process ${PYTHON} ${HELPER} validation-detach cwd=${WORKSPACE}
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} validation-cli-detach-ok
Tool List Type Tool Filter Works
[Documentation] Verify that ``tool list --type tool`` passes tool_type to service
${result}= Run Process ${PYTHON} ${HELPER} tool-list-type-tool cwd=${WORKSPACE}
Log ${result.stdout}
Log ${result.stderr}
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} tool-cli-list-type-tool-ok
Tool List Type Validation Filter Works
[Documentation] Verify that ``tool list --type validation`` passes tool_type to service
${result}= Run Process ${PYTHON} ${HELPER} tool-list-type-validation cwd=${WORKSPACE}
Log ${result.stdout}
Log ${result.stderr}
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} tool-cli-list-type-validation-ok
+5 -5
View File
2
@@ -75,8 +75,9 @@ class ToolRegistry:
If provided, only return tools whose name starts with
``namespace/``.
tool_type:
Reserved for future use (e.g. filtering by ``tool`` vs
``validation``). Currently unused.
If provided, only return tools whose ``tool_type`` matches
(``"tool"`` or ``"validation"``). When ``None``, all tools
are returned regardless of type.
source:
If provided, only return tools whose ``source`` field matches.
"""
@@ -90,9 +91,8 @@ class ToolRegistry:
if source is not None:
specs = [s for s in specs if s.source == source]
# tool_type filtering is a no-op for now but the parameter is
# part of the public API to avoid a breaking change later.
_ = tool_type
if tool_type is not None:
specs = [s for s in specs if s.tool_type == tool_type]
return specs
+5 -1
View File
@@ -29,7 +29,7 @@ Provides the execution-layer data structures that sit on top of the domain
from __future__ import annotations
from collections.abc import Callable
from typing import Any
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field, model_validator
@@ -82,6 +82,10 @@ class ToolSpec(BaseModel):
...,
description="Callable that executes the tool logic",
)
tool_type: Literal["tool", "validation"] = Field(
default="tool",
description="Discriminator: tool or validation",
)
source: str = Field(
default="builtin",
description=("Origin of the tool: builtin, agent_skills, mcp, or custom"),