chore: import upstream snapshot with attribution
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
"""
|
||||
Base tool interface and registration system for agent tools.
|
||||
Provides a protocol for implementing tools that can be registered and discovered.
|
||||
"""
|
||||
|
||||
import json
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
# Global tool registry
|
||||
TOOL_REGISTRY: Dict[str, type] = {}
|
||||
|
||||
|
||||
def register_tool(name: str, allow_overwrite: bool = False):
|
||||
"""
|
||||
Decorator to register a tool class with a given name.
|
||||
|
||||
Args:
|
||||
name: The name to register the tool under
|
||||
allow_overwrite: Whether to allow overwriting an existing tool with the same name
|
||||
|
||||
Returns:
|
||||
Decorator function that registers the class
|
||||
|
||||
Example:
|
||||
@register_tool("my_tool")
|
||||
class MyTool(BaseTool):
|
||||
...
|
||||
"""
|
||||
|
||||
def decorator(cls):
|
||||
if name in TOOL_REGISTRY:
|
||||
if allow_overwrite:
|
||||
print(f"Warning: Tool `{name}` already exists! Overwriting with class {cls}.")
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Tool `{name}` already exists! Please ensure that the tool name is unique."
|
||||
)
|
||||
if hasattr(cls, "name") and cls.name and (cls.name != name):
|
||||
raise ValueError(
|
||||
f'{cls.__name__}.name="{cls.name}" conflicts with @register_tool(name="{name}").'
|
||||
)
|
||||
cls.name = name
|
||||
TOOL_REGISTRY[name] = cls
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def is_tool_schema(obj: dict) -> bool:
|
||||
"""
|
||||
Check if obj is a valid JSON schema describing a tool compatible with OpenAI's tool calling.
|
||||
|
||||
Example valid schema:
|
||||
{
|
||||
"name": "get_current_weather",
|
||||
"description": "Get the current weather in a given location",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "The city and state, e.g. San Francisco, CA"
|
||||
},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"enum": ["celsius", "fahrenheit"]
|
||||
}
|
||||
},
|
||||
"required": ["location"]
|
||||
}
|
||||
}
|
||||
"""
|
||||
try:
|
||||
# Basic structure validation
|
||||
assert set(obj.keys()) == {"name", "description", "parameters"}
|
||||
assert isinstance(obj["name"], str)
|
||||
assert obj["name"].strip()
|
||||
assert isinstance(obj["description"], str)
|
||||
assert isinstance(obj["parameters"], dict)
|
||||
|
||||
# Parameters structure validation
|
||||
assert "type" in obj["parameters"]
|
||||
assert obj["parameters"]["type"] == "object"
|
||||
assert "properties" in obj["parameters"]
|
||||
assert isinstance(obj["parameters"]["properties"], dict)
|
||||
|
||||
if "required" in obj["parameters"]:
|
||||
assert isinstance(obj["parameters"]["required"], list)
|
||||
assert set(obj["parameters"]["required"]).issubset(
|
||||
set(obj["parameters"]["properties"].keys())
|
||||
)
|
||||
|
||||
return True
|
||||
except (AssertionError, KeyError, TypeError):
|
||||
return False
|
||||
|
||||
|
||||
class BaseTool(ABC):
|
||||
"""
|
||||
Base class for all agent tools.
|
||||
|
||||
Tools must implement:
|
||||
- name: str - The tool name (set by @register_tool decorator)
|
||||
- description: property that returns str - Tool description
|
||||
- parameters: property that returns dict - JSON schema for tool parameters
|
||||
- call: method - Execute the tool with given parameters
|
||||
"""
|
||||
|
||||
name: str = ""
|
||||
|
||||
def __init__(self, cfg: Optional[dict] = None):
|
||||
"""
|
||||
Initialize the tool.
|
||||
|
||||
Args:
|
||||
cfg: Optional configuration dictionary
|
||||
"""
|
||||
self.cfg = cfg or {}
|
||||
|
||||
if not self.name:
|
||||
raise ValueError(
|
||||
f"You must set {self.__class__.__name__}.name, either by "
|
||||
f"@register_tool(name=...) or explicitly setting "
|
||||
f"{self.__class__.__name__}.name"
|
||||
)
|
||||
|
||||
# Validate schema if parameters is a dict
|
||||
if isinstance(self.parameters, dict):
|
||||
if not is_tool_schema(
|
||||
{"name": self.name, "description": self.description, "parameters": self.parameters}
|
||||
):
|
||||
raise ValueError(
|
||||
"The parameters, when provided as a dict, must conform to a "
|
||||
"valid openai-compatible JSON schema."
|
||||
)
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def description(self) -> str:
|
||||
"""Return the tool description."""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def parameters(self) -> dict:
|
||||
"""Return the JSON schema for tool parameters."""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def call(self, params: Union[str, dict], **kwargs) -> Union[str, list, dict]:
|
||||
"""
|
||||
Execute the tool with the given parameters.
|
||||
|
||||
Args:
|
||||
params: The parameters for the tool call (JSON string or dict)
|
||||
**kwargs: Additional keyword arguments
|
||||
|
||||
Returns:
|
||||
The result of the tool execution
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _verify_json_format_args(self, params: Union[str, dict]) -> dict:
|
||||
"""
|
||||
Verify and parse the parameters as JSON.
|
||||
|
||||
Args:
|
||||
params: Parameters as string or dict
|
||||
|
||||
Returns:
|
||||
Parsed parameters as dict
|
||||
|
||||
Raises:
|
||||
ValueError: If parameters are not valid JSON or don't match schema
|
||||
"""
|
||||
if isinstance(params, str):
|
||||
try:
|
||||
params_json: dict = json.loads(params)
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"Parameters must be formatted as valid JSON: {e}")
|
||||
else:
|
||||
params_json: dict = params
|
||||
|
||||
# Validate against schema if using dict parameters
|
||||
if isinstance(self.parameters, dict):
|
||||
try:
|
||||
# Basic validation of required fields
|
||||
if "required" in self.parameters:
|
||||
for field in self.parameters["required"]:
|
||||
if field not in params_json:
|
||||
raise ValueError(f'Required parameter "{field}" is missing')
|
||||
except (KeyError, TypeError) as e:
|
||||
raise ValueError(f"Invalid parameters: {e}")
|
||||
|
||||
return params_json
|
||||
|
||||
@property
|
||||
def function(self) -> dict:
|
||||
"""
|
||||
Return the function information for this tool.
|
||||
|
||||
Returns:
|
||||
Dict with tool metadata
|
||||
"""
|
||||
return {
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"parameters": self.parameters,
|
||||
}
|
||||
|
||||
|
||||
def get_registered_tools() -> Dict[str, type]:
|
||||
"""
|
||||
Get all registered tools.
|
||||
|
||||
Returns:
|
||||
Dictionary mapping tool names to tool classes
|
||||
"""
|
||||
return TOOL_REGISTRY.copy()
|
||||
|
||||
|
||||
def get_tool(name: str) -> Optional[type]:
|
||||
"""
|
||||
Get a registered tool by name.
|
||||
|
||||
Args:
|
||||
name: The tool name
|
||||
|
||||
Returns:
|
||||
The tool class, or None if not found
|
||||
"""
|
||||
return TOOL_REGISTRY.get(name)
|
||||
|
||||
|
||||
class BaseComputerTool(BaseTool):
|
||||
"""
|
||||
Base class for computer tools that can provide screenshots.
|
||||
|
||||
Computer tools must implement:
|
||||
- All BaseTool requirements (name, description, parameters, call)
|
||||
- screenshot() method that returns screenshot as base64 string
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def screenshot(self) -> str:
|
||||
"""
|
||||
Take a screenshot of the computer/browser.
|
||||
|
||||
Returns:
|
||||
Screenshot image data as base64-encoded string
|
||||
"""
|
||||
raise NotImplementedError
|
||||
Reference in New Issue
Block a user