"""Tool decorator factory with unified result wrapping""" from typing import Callable, Any, Dict, Optional, List from luxx.tools.core import ToolDefinition, ToolResult, registry, CommandPermission import traceback import logging logger = logging.getLogger(__name__) def tool( name: str, description: str, parameters: Dict[str, Any], category: str = "general", required_params: Optional[List[str]] = None, required_permission: CommandPermission = CommandPermission.READ_ONLY ): """ Tool registration decorator with UNIFED result wrapping The decorator automatically wraps all return values into ToolResult format. Tool functions only need to return plain data - no need to use ToolResult manually. Args: name: Tool name description: Tool description parameters: OpenAI format parameters schema category: Tool category required_params: List of required parameter names (auto-validated) Usage: ```python @tool( name="my_tool", description="This is my tool", parameters={ "type": "object", "properties": { "arg1": {"type": "string"} }, "required": ["arg1"] }, required_params=["arg1"] # Auto-validated ) def my_tool(arguments: dict): # Just return plain data - decorator handles wrapping! return {"result": "success", "count": 5} # For errors, return a dict with "error" key: def my_tool2(arguments: dict): return {"error": "Something went wrong"} # Or raise an exception: def my_tool3(arguments: dict): raise ValueError("Invalid input") ``` The decorator will convert: - Plain dict → {"success": true, "data": dict, "error": null} - {"error": "msg"} → {"success": false, "data": null, "error": "msg"} - Exception → {"success": false, "data": null, "error": "..."} """ def decorator(func: Callable) -> Callable: def wrapped_handler(arguments: Dict[str, Any], context=None) -> ToolResult: try: # 1. Validate required params if required_params: for param in required_params: if param not in arguments or arguments[param] is None: return ToolResult.fail(f"Missing required parameter: {param}") # 2. Execute handler - pass context only if function accepts it import inspect sig = inspect.signature(func) if 'context' in sig.parameters: result = func(arguments, context=context) else: result = func(arguments) # 3. Auto-wrap result return _auto_wrap(result) except Exception as e: logger.error(f"[{name}] Unexpected error: {type(e).__name__}: {str(e)}") logger.debug(traceback.format_exc()) return ToolResult.fail(f"Execution failed: {type(e).__name__}: {str(e)}") tool_def = ToolDefinition( name=name, description=description, parameters=parameters, handler=wrapped_handler, category=category, required_permission=required_permission ) registry.register(tool_def) return func return decorator def _auto_wrap(result: Any) -> ToolResult: """ Auto-wrap any return value into ToolResult format Rules: - ToolResult → use directly - dict with "error" key → ToolResult.fail() - dict without "error" → ToolResult.ok() - other values → ToolResult.ok() """ # Already a ToolResult if isinstance(result, ToolResult): return result # Dict with error if isinstance(result, dict) and "error" in result: return ToolResult.fail(str(result["error"])) # Plain dict or other value return ToolResult.ok(result) def tool_function( name: str = None, description: str = None, parameters: Dict[str, Any] = None, category: str = "general", required_params: Optional[List[str]] = None ): """ Alias for tool decorator, providing a more semantic naming All parameters are the same as tool() """ return tool( name=name, description=description, parameters=parameters, category=category, required_params=required_params )