480 lines
21 KiB
Python

"""
Tool registration functions that create ToolMetadata and register all available tools.
"""
import os
from typing import Dict, Any, Callable
from .registry import ToolRegistry, ToolMetadata
from .base_tools import (
file_tools, system_tools, browser_tools, ai_tools,
memory_tools, skill_tools, task_tools
)
from .config_tools import config_tools
def create_tool_registry() -> ToolRegistry:
"""Create and populate a tool registry with all available tools."""
registry = ToolRegistry()
# Register file tools
_register_file_tools(registry, file_tools)
# Register system tools
_register_system_tools(registry, system_tools)
# Register browser tools
_register_browser_tools(registry, browser_tools)
# Register AI tools
_register_ai_tools(registry, ai_tools)
# Register memory tools
_register_memory_tools(registry, memory_tools)
# Register skill tools
_register_skill_tools(registry, skill_tools)
# Register task tools
_register_task_tools(registry, task_tools)
# Register config tools
_register_config_tools(registry, config_tools)
return registry
def _register_file_tools(registry: ToolRegistry, tools: Dict[str, Callable]):
"""Register file operation tools."""
# read_file
registry.register_tool(
'read_file',
tools['read_file'],
ToolMetadata(
name='read_file',
description='Read a text file with line numbers and pagination',
parameters={
'path': {'type': 'string', 'required': True, 'description': 'File path to read'},
'offset': {'type': 'integer', 'required': False, 'default': 1, 'description': 'Line number to start from (1-indexed)'},
'limit': {'type': 'integer', 'required': False, 'default': 500, 'description': 'Maximum number of lines to read'}
},
permissions=['file_read'],
examples=[
{'path': 'config.txt'},
{'path': 'logs/app.log', 'offset': 100, 'limit': 50}
],
security_notes='Cannot read files outside user work directory or system protected directories'
)
)
# write_file
registry.register_tool(
'write_file',
tools['write_file'],
ToolMetadata(
name='write_file',
description='Write content to a file, completely replacing existing content',
parameters={
'path': {'type': 'string', 'required': True, 'description': 'Path to the file to write'},
'content': {'type': 'string', 'required': True, 'description': 'Complete content to write to the file'}
},
permissions=['file_write'],
examples=[
{'path': 'output.txt', 'content': 'Hello World!'}
],
security_notes='Cannot write to system protected directories or outside user work directory'
)
)
# search_files
registry.register_tool(
'search_files',
tools['search_files'],
ToolMetadata(
name='search_files',
description='Search file contents or find files by name using ripgrep',
parameters={
'pattern': {'type': 'string', 'required': True, 'description': 'Regex pattern for content search, or glob pattern for file search'},
'target': {'type': 'string', 'required': False, 'default': 'content', 'description': "'content' searches inside files, 'files' searches for files by name"},
'path': {'type': 'string', 'required': False, 'default': '.', 'description': 'Directory or file to search in'},
'file_glob': {'type': 'string', 'required': False, 'description': 'Filter files by pattern in grep mode (e.g., "*.py")'},
'limit': {'type': 'integer', 'required': False, 'default': 50, 'description': 'Maximum number of results to return'}
},
permissions=['file_read'],
examples=[
{'pattern': 'TODO', 'target': 'content', 'path': './src'},
{'pattern': '*.py', 'target': 'files', 'path': './src'}
],
security_notes='Search is limited to user accessible directories'
)
)
# patch
registry.register_tool(
'patch',
tools['patch'],
ToolMetadata(
name='patch',
description='Targeted find-and-replace edits in files with fuzzy matching',
parameters={
'mode': {'type': 'string', 'required': False, 'default': 'replace', 'description': "Edit mode: 'replace' for targeted find-and-replace, 'patch' for V4A multi-file patches"},
'path': {'type': 'string', 'required': False, 'description': 'File path to edit (required for replace mode)'},
'old_string': {'type': 'string', 'required': False, 'description': 'Text to find in the file (required for replace mode)'},
'new_string': {'type': 'string', 'required': False, 'description': 'Replacement text (required for replace mode)'},
'replace_all': {'type': 'boolean', 'required': False, 'default': False, 'description': 'Replace all occurrences instead of requiring a unique match'}
},
permissions=['file_write'],
examples=[
{'mode': 'replace', 'path': 'config.txt', 'old_string': 'old_value', 'new_string': 'new_value'}
],
security_notes='Edits are limited to user accessible files and require proper permissions'
)
)
def _register_system_tools(registry: ToolRegistry, tools: Dict[str, Callable]):
"""Register system operation tools."""
# terminal
registry.register_tool(
'terminal',
tools['terminal'],
ToolMetadata(
name='terminal',
description='Execute shell commands on a Linux environment',
parameters={
'command': {'type': 'string', 'required': True, 'description': 'The command to execute on the VM'},
'background': {'type': 'boolean', 'required': False, 'default': False, 'description': 'Run the command in the background'},
'timeout': {'type': 'integer', 'required': False, 'default': 180, 'description': 'Max seconds to wait'},
'workdir': {'type': 'string', 'required': False, 'description': 'Working directory for this command'},
'pty': {'type': 'boolean', 'required': False, 'default': False, 'description': 'Run in pseudo-terminal (PTY) mode'},
'notify_on_complete': {'type': 'boolean', 'required': False, 'default': False, 'description': 'Auto-notify when background process completes'}
},
permissions=['system_execute'],
examples=[
{'command': 'ls -la'},
{'command': 'python script.py', 'background': True, 'notify_on_complete': True}
],
security_notes='Commands are executed with user privileges. Dangerous commands may be restricted.'
)
)
# process
registry.register_tool(
'process',
tools['process'],
ToolMetadata(
name='process',
description='Manage background processes started with terminal(background=true)',
parameters={
'action': {'type': 'string', 'required': True, 'description': "Action: 'list', 'poll', 'log', 'wait', 'kill', 'write', 'submit', 'close'"},
'session_id': {'type': 'string', 'required': False, 'description': 'Process session ID (required for all actions except list)'},
'data': {'type': 'string', 'required': False, 'description': 'Text to send to process stdin (for write and submit actions)'},
'timeout': {'type': 'integer', 'required': False, 'description': 'Max seconds to block for wait action'},
'offset': {'type': 'integer', 'required': False, 'description': 'Line offset for log action'},
'limit': {'type': 'integer', 'required': False, 'description': 'Max lines to return for log action'}
},
permissions=['system_manage'],
examples=[
{'action': 'list'},
{'action': 'poll', 'session_id': 'abc123'}
],
security_notes='Can only manage processes started by the current user session'
)
)
# execute_code
registry.register_tool(
'execute_code',
tools['execute_code'],
ToolMetadata(
name='execute_code',
description='Run a Python script that can call Hermes tools programmatically',
parameters={
'code': {'type': 'string', 'required': True, 'description': 'Python code to execute'}
},
permissions=['code_execute'],
examples=[
{'code': 'print("Hello from Python!")'}
],
security_notes='Code execution is sandboxed but still requires caution. Limited to 5-minute timeout.'
)
)
def _register_browser_tools(registry: ToolRegistry, tools: Dict[str, Callable]):
"""Register browser automation tools."""
browser_permissions = ['browser_access']
# Common browser tool metadata
browser_examples = [{'url': 'https://example.com'}]
browser_security_notes = 'Browser access is limited to HTTP/HTTPS URLs. Local file access may be restricted.'
browser_tools_list = [
('browser_navigate', 'Navigate to a URL in the browser'),
('browser_snapshot', 'Get a text-based snapshot of the current page'),
('browser_click', 'Click on an element identified by its ref ID'),
('browser_type', 'Type text into an input field identified by its ref ID'),
('browser_press', 'Press a keyboard key'),
('browser_scroll', 'Scroll the page in a direction'),
('browser_console', 'Get browser console output and JavaScript errors'),
('browser_get_images', 'Get a list of all images on the current page'),
('browser_vision', 'Take a screenshot and analyze it with vision AI'),
('browser_back', 'Navigate back to the previous page')
]
for tool_name, description in browser_tools_list:
registry.register_tool(
tool_name,
tools[tool_name],
ToolMetadata(
name=tool_name,
description=description,
parameters={}, # Parameters vary by tool, simplified for now
permissions=browser_permissions,
examples=browser_examples,
security_notes=browser_security_notes
)
)
def _register_ai_tools(registry: ToolRegistry, tools: Dict[str, Callable]):
"""Register AI-powered tools."""
# vision_analyze
registry.register_tool(
'vision_analyze',
tools['vision_analyze'],
ToolMetadata(
name='vision_analyze',
description='Analyze images using AI vision with comprehensive description and Q&A',
parameters={
'image_url': {'type': 'string', 'required': True, 'description': 'Image URL or local file path to analyze'},
'question': {'type': 'string', 'required': True, 'description': 'Specific question about the image content'}
},
permissions=['ai_vision'],
examples=[
{'image_url': 'https://example.com/image.jpg', 'question': 'What objects are in this image?'}
],
security_notes='Image analysis may have privacy implications. Local files must be in accessible directories.'
)
)
# text_to_speech
registry.register_tool(
'text_to_speech',
tools['text_to_speech'],
ToolMetadata(
name='text_to_speech',
description='Convert text to speech audio with user-configured voice',
parameters={
'text': {'type': 'string', 'required': True, 'description': 'Text to convert to speech (under 4000 characters)'},
'output_path': {'type': 'string', 'required': False, 'description': 'Optional custom file path to save the audio'}
},
permissions=['ai_tts'],
examples=[
{'text': 'Hello, this is a test message.'}
],
security_notes='Audio files are saved to user-accessible directories only'
)
)
def _register_memory_tools(registry: ToolRegistry, tools: Dict[str, Callable]):
"""Register memory management tools."""
# memory
registry.register_tool(
'memory',
tools['memory'],
ToolMetadata(
name='memory',
description='Save durable information to persistent memory across sessions',
parameters={
'action': {'type': 'string', 'required': True, 'description': "Action: 'add', 'replace', or 'remove'"},
'target': {'type': 'string', 'required': True, 'description': "Target: 'memory' for personal notes, 'user' for user profile"},
'content': {'type': 'string', 'required': False, 'description': 'Content to add/replace (required for add/replace)'},
'old_text': {'type': 'string', 'required': False, 'description': 'Text to identify entry for replace/remove'}
},
permissions=['memory_manage'],
examples=[
{'action': 'add', 'target': 'memory', 'content': 'User prefers dark mode'}
],
security_notes='Memory is isolated per user. Sensitive information should be handled carefully.'
)
)
# session_search
registry.register_tool(
'session_search',
tools['session_search'],
ToolMetadata(
name='session_search',
description='Search long-term memory of past conversations or browse recent sessions',
parameters={
'query': {'type': 'string', 'required': False, 'description': 'Search query keywords or phrases'},
'limit': {'type': 'integer', 'required': False, 'default': 3, 'description': 'Max sessions to summarize'}
},
permissions=['memory_read'],
examples=[
{'query': 'database setup'},
{} # Browse recent sessions
],
security_notes='Only searches sessions belonging to the current user'
)
)
def _register_skill_tools(registry: ToolRegistry, tools: Dict[str, Callable]):
"""Register skill management tools."""
# skill_view
registry.register_tool(
'skill_view',
tools['skill_view'],
ToolMetadata(
name='skill_view',
description='Load a skill\'s full content or access its linked files',
parameters={
'name': {'type': 'string', 'required': True, 'description': 'The skill name'},
'file_path': {'type': 'string', 'required': False, 'description': 'Path to a linked file within the skill'}
},
permissions=['skill_read'],
examples=[
{'name': 'module-development-spec'},
{'name': 'bricks-framework', 'file_path': 'templates/base.ui'}
],
security_notes='Skills are loaded from the configured skills_path directory'
)
)
# skills_list
registry.register_tool(
'skills_list',
tools['skills_list'],
ToolMetadata(
name='skills_list',
description='List available skills with name and description',
parameters={
'category': {'type': 'string', 'required': False, 'description': 'Optional category filter'}
},
permissions=['skill_read'],
examples=[
{},
{'category': 'software-development'}
],
security_notes='Lists only skills accessible to the current user'
)
)
# skill_manage
registry.register_tool(
'skill_manage',
tools['skill_manage'],
ToolMetadata(
name='skill_manage',
description='Manage skills (create, update, delete) with procedural memory',
parameters={
'action': {'type': 'string', 'required': True, 'description': "Action: 'create', 'patch', 'edit', 'delete', 'write_file', 'remove_file'"},
'name': {'type': 'string', 'required': True, 'description': 'Skill name'},
'content': {'type': 'string', 'required': False, 'description': 'Full SKILL.md content (for create/edit)'}
},
permissions=['skill_manage'],
examples=[
{'action': 'list'},
{'action': 'create', 'name': 'my-skill', 'content': '# My Skill\n...'}
],
security_notes='Skill management affects the shared skills repository. Use with caution.'
)
)
def _register_task_tools(registry: ToolRegistry, tools: Dict[str, Callable]):
"""Register task management tools."""
# todo
registry.register_tool(
'todo',
tools['todo'],
ToolMetadata(
name='todo',
description='Manage task list for the current session with priority ordering',
parameters={
'todos': {'type': 'array', 'required': False, 'description': 'Task items to write (omit to read current list)'},
'merge': {'type': 'boolean', 'required': False, 'default': False, 'description': 'Update existing items by id, add new ones'}
},
permissions=['task_manage'],
examples=[
{},
{'todos': [{'id': 'task1', 'content': 'Do something', 'status': 'pending'}]}
],
security_notes='Task lists are session-specific and not persisted across sessions'
)
)
# delegate_task
registry.register_tool(
'delegate_task',
tools['delegate_task'],
ToolMetadata(
name='delegate_task',
description='Spawn subagents to work on tasks in isolated contexts',
parameters={
'goal': {'type': 'string', 'required': False, 'description': 'Single task goal'},
'tasks': {'type': 'array', 'required': False, 'description': 'Batch tasks to run in parallel (limit 3)'},
'context': {'type': 'string', 'required': False, 'description': 'Background information for subagent'}
},
permissions=['task_delegate'],
examples=[
{'goal': 'Debug this error', 'context': 'Error message: ...'},
{'tasks': [{'goal': 'Task A'}, {'goal': 'Task B'}]}
],
security_notes='Subagents have no memory of parent conversation and cannot call clarify'
)
)
# clarify
registry.register_tool(
'clarify',
tools['clarify'],
ToolMetadata(
name='clarify',
description='Ask user for clarification, feedback, or decision before proceeding',
parameters={
'question': {'type': 'string', 'required': True, 'description': 'Question to present to the user'},
'choices': {'type': 'array', 'required': False, 'description': 'Up to 4 answer choices for multiple choice'}
},
permissions=['user_interact'],
examples=[
{'question': 'Which approach should I take?', 'choices': ['Option A', 'Option B']}
],
security_notes='Used for interactive decision making with the user'
)
)
# cronjob
registry.register_tool(
'cronjob',
tools['cronjob'],
ToolMetadata(
name='cronjob',
description='Manage scheduled cron jobs with compressed tool interface',
parameters={
'action': {'type': 'string', 'required': True, 'description': "Action: 'create', 'list', 'update', 'pause', 'resume', 'remove', 'run'"},
'prompt': {'type': 'string', 'required': False, 'description': 'Self-contained prompt for create action'},
'schedule': {'type': 'string', 'required': False, 'description': 'Schedule string like \'30m\', \'every 2h\', or cron format'}
},
permissions=['schedule_manage'],
examples=[
{'action': 'list'},
{'action': 'create', 'prompt': 'Check system status', 'schedule': 'every 1h'}
],
security_notes='Cron jobs run autonomously with no user present. Prompts must be self-contained.'
)
)
def _register_config_tools(registry: ToolRegistry, tools: Dict[str, Callable]):
"""Register configuration tools."""
# get_app_config
registry.register_tool(
'get_app_config',
tools['get_app_config'],
ToolMetadata(
name='get_app_config',
description='Read application configuration file and extract skills_path',
parameters={
'config_path': {'type': 'string', 'required': False, 'description': 'Optional custom config file path'}
},
permissions=['config_read'],
examples=[
{},
{'config_path': './custom/config.json'}
],
security_notes='Reads configuration files from accessible directories only'
)
)