| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- from __future__ import annotations
- import json
- import pytest
- from agent.tools.models import ToolCapability
- from agent.tools.registry import ToolRegistry, get_tool_registry, tool
- def _schema(name: str) -> dict:
- return {
- "type": "function",
- "function": {
- "name": name,
- "description": name,
- "parameters": {"type": "object", "properties": {}},
- },
- }
- @pytest.mark.asyncio
- async def test_registering_the_same_tool_twice_is_idempotent() -> None:
- registry = ToolRegistry()
- async def sample() -> str:
- return "ok"
- registry.register(
- sample,
- schema=_schema("sample"),
- groups=["test"],
- capabilities=[ToolCapability.READ],
- )
- assert await registry.execute("sample", {}) == "ok"
- registry.register(
- sample,
- schema=_schema("sample"),
- groups=["test"],
- capabilities=[ToolCapability.READ],
- )
- assert registry.get_tool_names() == ["sample"]
- assert registry.get_stats("sample")["sample"]["call_count"] == 1
- @pytest.mark.asyncio
- async def test_same_factory_definition_rebinds_a_fresh_host_adapter() -> None:
- registry = ToolRegistry()
- class Gateway:
- def __init__(self, value: str) -> None:
- self.value = value
- def make_tool(gateway: Gateway):
- async def host_tool() -> str:
- return gateway.value
- return host_tool
- registry.register(
- make_tool(Gateway("first")),
- schema=_schema("host_tool"),
- capabilities=[ToolCapability.READ],
- )
- assert await registry.execute("host_tool", {}) == "first"
- registry.register(
- make_tool(Gateway("second")),
- schema=_schema("host_tool"),
- capabilities=[ToolCapability.READ],
- )
- assert await registry.execute("host_tool", {}) == "second"
- assert registry.get_stats("host_tool")["host_tool"]["call_count"] == 2
- def test_same_name_from_a_different_callable_is_rejected() -> None:
- registry = ToolRegistry()
- async def original() -> str:
- return "original"
- async def replacement() -> str:
- return "replacement"
- replacement.__name__ = original.__name__
- registry.register(original, schema=_schema("original"))
- with pytest.raises(
- ValueError,
- match=r"Duplicate tool registration for 'original'.*different callable",
- ) as exc_info:
- registry.register(replacement, schema=_schema("original"))
- message = str(exc_info.value)
- assert "original" in message
- assert "replacement" in message
- assert registry._tools["original"]["func"] is original
- def test_reregistering_a_tool_with_different_metadata_is_rejected() -> None:
- registry = ToolRegistry()
- async def sample() -> str:
- return "ok"
- registry.register(sample, schema=_schema("sample"), groups=["read"])
- with pytest.raises(
- ValueError,
- match=r"Duplicate tool registration for 'sample'.*metadata differs: groups",
- ):
- registry.register(sample, schema=_schema("sample"), groups=["write"])
- @pytest.mark.asyncio
- async def test_legacy_execute_inject_values_are_defaults_not_overrides() -> None:
- registry = ToolRegistry()
- async def sample(value: str) -> str:
- return value
- registry.register(sample, schema=_schema("sample"))
- assert await registry.execute(
- "sample",
- {},
- inject_values={"value": "injected", "unknown": "ignored"},
- ) == "injected"
- assert await registry.execute(
- "sample",
- {"value": "explicit"},
- inject_values={"value": "injected"},
- ) == "explicit"
- @pytest.mark.asyncio
- async def test_host_can_replace_a_capability_matched_submission_protocol() -> None:
- registry = ToolRegistry()
- async def submit_attempt() -> str:
- return "framework"
- submit_attempt.__module__ = "agent.tools.builtin.example"
- registry.register(
- submit_attempt,
- schema=_schema("submit_attempt"),
- groups=["orchestration_worker"],
- capabilities=[ToolCapability.ATTEMPT_SUBMIT],
- )
- assert await registry.execute("submit_attempt", {}) == "framework"
- async def submit_attempt() -> str: # noqa: F811
- return "host"
- submit_attempt.__module__ = "host.tools"
- registry.register(
- submit_attempt,
- schema={
- **_schema("submit_attempt"),
- "x-host-contract": True,
- },
- groups=["host"],
- capabilities=[ToolCapability.ATTEMPT_SUBMIT],
- )
- assert await registry.execute("submit_attempt", {}) == "host"
- assert registry.get_stats("submit_attempt")["submit_attempt"]["call_count"] == 2
- async def competing_host_tool() -> str:
- return "competing host"
- competing_host_tool.__name__ = "submit_attempt"
- competing_host_tool.__module__ = "another_host.tools"
- with pytest.raises(ValueError, match="different callable"):
- registry.register(
- competing_host_tool,
- schema=_schema("submit_attempt"),
- groups=["another_host"],
- capabilities=[ToolCapability.ATTEMPT_SUBMIT],
- )
- def test_host_cannot_replace_a_builtin_with_different_capabilities() -> None:
- registry = ToolRegistry()
- async def read_contract() -> str:
- return "framework"
- read_contract.__module__ = "agent.tools.builtin.example"
- registry.register(
- read_contract,
- schema=_schema("read_contract"),
- capabilities=[ToolCapability.READ],
- )
- async def read_contract() -> str: # noqa: F811
- return "host"
- read_contract.__module__ = "host.tools"
- with pytest.raises(ValueError, match="different callable"):
- registry.register(
- read_contract,
- schema=_schema("read_contract"),
- capabilities=[ToolCapability.WRITE],
- )
- def test_builtin_tool_registration_contract() -> None:
- """Guard the security-relevant metadata without snapshotting optional tools."""
- registry = get_tool_registry()
- schemas = registry.get_schemas()
- schema_names = [schema["function"]["name"] for schema in schemas]
- assert len(schema_names) == len(set(schema_names))
- assert set(schema_names) == set(registry.get_tool_names())
- expected = {
- "read_file": (
- "agent.tools.builtin.file.read",
- {"core"},
- {ToolCapability.READ},
- ),
- "write_file": (
- "agent.tools.builtin.file.write",
- {"core"},
- {ToolCapability.WRITE},
- ),
- "glob_files": (
- "agent.tools.builtin.file.glob",
- {"core"},
- {ToolCapability.READ},
- ),
- "bash_command": (
- "agent.tools.builtin.bash",
- {"system"},
- {ToolCapability.WRITE},
- ),
- "agent": (
- "agent.tools.builtin.subagent",
- {"core"},
- {ToolCapability.AGENT_SPAWN},
- ),
- "task_plan": (
- "agent.tools.builtin.orchestration",
- {"orchestration_planner"},
- {ToolCapability.TASK_CONTROL},
- ),
- "submit_attempt": (
- "agent.tools.builtin.orchestration",
- {"orchestration_worker"},
- {ToolCapability.ATTEMPT_SUBMIT},
- ),
- "submit_validation": (
- "agent.tools.builtin.orchestration",
- {"orchestration_validator"},
- {ToolCapability.VALIDATION_SUBMIT},
- ),
- }
- actual = {
- name: (
- registry._tools[name]["func"].__module__,
- set(registry._tools[name]["groups"]),
- set(registry.get_capabilities(name)),
- )
- for name in expected
- }
- assert actual == expected, json.dumps(
- {
- name: {
- "module": module,
- "groups": sorted(groups),
- "capabilities": sorted(map(str, capabilities)),
- }
- for name, (module, groups, capabilities) in actual.items()
- },
- ensure_ascii=False,
- indent=2,
- )
- def test_tool_decorator_applies_explicit_schema_descriptions() -> None:
- registry = get_tool_registry()
- try:
- @tool(
- description="Explicit tool description",
- param_descriptions={"value": "Explicit value description"},
- )
- async def governance_description_probe(value: str) -> str:
- """Docstring description.
- Args:
- value: Docstring value description.
- """
- return value
- schema = registry.get_schemas(["governance_description_probe"])[0]["function"]
- assert schema["description"] == "Explicit tool description"
- assert (
- schema["parameters"]["properties"]["value"]["description"]
- == "Explicit value description"
- )
- finally:
- registry._tools.pop("governance_description_probe", None)
- registry._stats.pop("governance_description_probe", None)
|