Custom Tools
Extend agent capabilities with your own tools
Custom tools let you extend what your agent can do beyond the built-in Computer and Browser tools. Define functions that the agent can call to perform specific tasks like calculations, API calls, file operations, or any custom logic.
Simple Function Tools
The easiest way to add a tool is to pass a function with a docstring:
from agent import ComputerAgent
def calculate(a: int, b: int) -> int:
"""Calculate the sum of two integers"""
return a + b
def get_weather(city: str) -> str:
"""Get current weather for a city"""
# Your API call here
return f"Weather in {city}: Sunny, 72°F"
agent = ComputerAgent(
model="anthropic/claude-sonnet-4-5-20250929",
tools=[computer, calculate, get_weather]
)
await agent.run("What's 42 + 58? Also check the weather in San Francisco.")The agent can now call calculate and get_weather alongside computer actions. Cua automatically extracts the function signature and docstring to create the tool schema.
Sandboxed Tools
For tools that should run inside the sandbox rather than on your host machine, use the @sandboxed decorator:
from computer.helpers import sandboxed
@sandboxed()
def read_file(path: str) -> str:
"""Read contents of a file in the sandbox"""
with open(path, 'r') as f:
return f.read()
@sandboxed()
def save_note(filename: str, content: str) -> str:
"""Save a note to a file in the sandbox"""
with open(filename, 'w') as f:
f.write(content)
return f"Saved to {filename}"
agent = ComputerAgent(
model="anthropic/claude-sonnet-4-5-20250929",
tools=[computer, read_file, save_note]
)
await agent.run("Read the file at /tmp/data.txt and summarize it")Sandboxed tools execute inside the sandbox's virtual environment, which is safer and can access files on the sandbox's filesystem.
See Sandboxed Python for more details.
Using the BaseTool Class
For more control over tool schema and behavior, extend BaseTool:
from agent.tools import BaseTool, register_tool
from typing import Union
@register_tool("database_query")
class DatabaseQueryTool(BaseTool):
def __init__(self, connection_string: str):
self.connection_string = connection_string
@property
def description(self) -> str:
return "Execute a read-only SQL query against the database"
@property
def parameters(self) -> dict:
return {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "SQL SELECT query to execute"
}
},
"required": ["query"]
}
def call(self, params: Union[str, dict], **kwargs) -> Union[str, dict]:
if isinstance(params, str):
params = {"query": params}
query = params["query"]
# Execute query and return results
return {"rows": [...], "count": 10}
# Use it
db_tool = DatabaseQueryTool("postgresql://...")
agent = ComputerAgent(
model="anthropic/claude-sonnet-4-5-20250929",
tools=[computer, db_tool]
)The @register_tool decorator registers your tool class with a name that the agent uses to invoke it.
Tool Schema
Tools need a schema so the model knows how to call them. For function tools, Cua generates the schema automatically from type hints and docstrings.
For BaseTool classes, you define the schema in the parameters property:
@property
def parameters(self) -> dict:
return {
"type": "object",
"properties": {
"param_name": {
"type": "string",
"description": "What this parameter does"
},
"optional_param": {
"type": "integer",
"description": "An optional number"
}
},
"required": ["param_name"] # Only required params
}This follows the JSON Schema format used by OpenAI and other providers.
Async Tools
Both sync and async functions work as tools:
import asyncio
import httpx
async def fetch_url(url: str) -> str:
"""Fetch content from a URL"""
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.text[:1000] # First 1000 chars
agent = ComputerAgent(
model="anthropic/claude-sonnet-4-5-20250929",
tools=[computer, fetch_url]
)Sync functions are automatically run in a thread pool to avoid blocking.
Combining Tools
Pass multiple tools of different types:
from agent import ComputerAgent
from agent.tools import BrowserTool
from computer.helpers import sandboxed
# Built-in tool
browser = BrowserTool(interface=computer)
# Simple function
def calculate(expression: str) -> float:
"""Evaluate a math expression"""
return eval(expression) # Use with caution
# Sandboxed function
@sandboxed()
def process_data(data: list) -> dict:
"""Process data inside the sandbox"""
import pandas as pd
df = pd.DataFrame(data)
return df.describe().to_dict()
agent = ComputerAgent(
model="anthropic/claude-sonnet-4-5-20250929",
tools=[computer, browser, calculate, process_data]
)The agent decides which tool to use based on the task.
Error Handling
Tools can raise exceptions, which are passed back to the model:
from agent.tools import ToolError
def divide(a: float, b: float) -> float:
"""Divide a by b"""
if b == 0:
raise ToolError("Cannot divide by zero")
return a / bThe model sees the error message and can adjust its approach.
Example: File Management Tools
Here's a complete example with file management tools:
from agent import ComputerAgent
from computer.helpers import sandboxed
@sandboxed()
def list_files(directory: str = ".") -> list:
"""List files in a directory"""
import os
return os.listdir(directory)
@sandboxed()
def read_file(path: str) -> str:
"""Read a file's contents"""
with open(path, 'r') as f:
return f.read()
@sandboxed()
def write_file(path: str, content: str) -> str:
"""Write content to a file"""
with open(path, 'w') as f:
f.write(content)
return f"Written {len(content)} bytes to {path}"
@sandboxed()
def append_file(path: str, content: str) -> str:
"""Append content to a file"""
with open(path, 'a') as f:
f.write(content)
return f"Appended {len(content)} bytes to {path}"
agent = ComputerAgent(
model="anthropic/claude-sonnet-4-5-20250929",
tools=[computer, list_files, read_file, write_file, append_file]
)
await agent.run("Create a file called notes.txt with today's date, then read it back")The agent can now manage files directly without needing to use the UI.
Was this page helpful?