Spaces:
Sleeping
Sleeping
| """Consumer-side validation for backend-exposed tool availability. | |
| This module keeps Person 2's prompt and policy layers fail-closed. The mock and | |
| real backends may evolve independently during integration, so we validate the | |
| adapter contract before building prompts or selecting actions. | |
| """ | |
| from __future__ import annotations | |
| from .tool_catalog import TOOL_SPEC_BY_NAME | |
| class ToolAvailabilityError(ValueError): | |
| """Raised when the backend omits or corrupts the available-tools contract.""" | |
| def validate_tool_availability(available_tools: list[str] | None) -> list[str]: | |
| """Validate adapter-exposed tools and return a normalized list. | |
| The consumer stack must fail closed here: a missing or empty tool list is a | |
| contract violation during integration, not a signal to expose the full | |
| catalog speculatively. | |
| """ | |
| if available_tools is None: | |
| raise ToolAvailabilityError( | |
| "available_tools is missing from the backend contract; refusing to build prompts or select actions." | |
| ) | |
| if not isinstance(available_tools, list): | |
| raise ToolAvailabilityError( | |
| f"available_tools must be a list of tool names, received {type(available_tools).__name__}." | |
| ) | |
| normalized = [tool_name for tool_name in available_tools if isinstance(tool_name, str) and tool_name.strip()] | |
| if not normalized: | |
| raise ToolAvailabilityError( | |
| "available_tools is empty; the backend must expose at least one supported tool." | |
| ) | |
| unknown_tools = sorted(set(normalized) - set(TOOL_SPEC_BY_NAME)) | |
| if unknown_tools: | |
| raise ToolAvailabilityError( | |
| f"available_tools contains unknown tool names: {', '.join(unknown_tools)}." | |
| ) | |
| return normalized | |