183 lines
6.5 KiB
Python
183 lines
6.5 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
"""
|
|
Toolbox Registry for the Agent service.
|
|
Manages thematic tool groupings (toolboxes) and the `requestToolbox` meta-tool.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Dict, List, Any, Optional, Set
|
|
from pydantic import BaseModel, Field
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ToolboxDefinition(BaseModel):
|
|
"""Definition of a thematic toolbox."""
|
|
id: str = Field(description="Unique toolbox identifier (e.g. 'core', 'email', 'workflow')")
|
|
label: str = Field(description="Human-readable label")
|
|
description: str = Field(default="", description="What this toolbox provides")
|
|
featureCode: Optional[str] = Field(default=None, description="Feature code if toolbox is feature-specific")
|
|
tools: List[str] = Field(default_factory=list, description="Tool names belonging to this toolbox")
|
|
isDefault: bool = Field(default=False, description="If true, toolbox is active by default")
|
|
requiresConnection: Optional[str] = Field(
|
|
default=None,
|
|
description="Connection authority required (e.g. 'microsoft', 'google'). None = always available."
|
|
)
|
|
|
|
|
|
class ToolboxRegistry:
|
|
"""Registry for toolbox definitions. Manages activation and tool lookup."""
|
|
|
|
def __init__(self):
|
|
self._toolboxes: Dict[str, ToolboxDefinition] = {}
|
|
|
|
def registerToolbox(self, toolbox: ToolboxDefinition) -> None:
|
|
"""Register a toolbox definition."""
|
|
if toolbox.id in self._toolboxes:
|
|
logger.debug("Toolbox '%s' already registered, updating", toolbox.id)
|
|
self._toolboxes[toolbox.id] = toolbox
|
|
logger.debug("Registered toolbox: %s (%d tools, default=%s)", toolbox.id, len(toolbox.tools), toolbox.isDefault)
|
|
|
|
def getToolbox(self, toolboxId: str) -> Optional[ToolboxDefinition]:
|
|
"""Get a toolbox by ID."""
|
|
return self._toolboxes.get(toolboxId)
|
|
|
|
def getAllToolboxes(self) -> List[ToolboxDefinition]:
|
|
"""Get all registered toolboxes."""
|
|
return list(self._toolboxes.values())
|
|
|
|
def getDefaultToolboxes(self) -> List[ToolboxDefinition]:
|
|
"""Get all default toolboxes (active at agent start)."""
|
|
return [tb for tb in self._toolboxes.values() if tb.isDefault]
|
|
|
|
def getActiveToolboxes(self, userConnections: List[str] = None) -> List[ToolboxDefinition]:
|
|
"""
|
|
Get toolboxes available to the user based on their connections.
|
|
Toolboxes without requiresConnection are always available.
|
|
Toolboxes with requiresConnection are available only if the user has that connection.
|
|
"""
|
|
available = []
|
|
connectionAuthorities: Set[str] = set(userConnections or [])
|
|
for tb in self._toolboxes.values():
|
|
if tb.requiresConnection is None:
|
|
available.append(tb)
|
|
elif tb.requiresConnection in connectionAuthorities:
|
|
available.append(tb)
|
|
return available
|
|
|
|
def getToolsForToolboxes(self, toolboxIds: List[str]) -> List[str]:
|
|
"""Get the union of all tool names for the given toolbox IDs."""
|
|
tools: Set[str] = set()
|
|
for tbId in toolboxIds:
|
|
tb = self._toolboxes.get(tbId)
|
|
if tb:
|
|
tools.update(tb.tools)
|
|
return sorted(tools)
|
|
|
|
def getToolboxForTool(self, toolName: str) -> Optional[str]:
|
|
"""Find which toolbox a tool belongs to."""
|
|
for tb in self._toolboxes.values():
|
|
if toolName in tb.tools:
|
|
return tb.id
|
|
return None
|
|
|
|
def toApiResponse(self, userConnections: List[str] = None) -> List[Dict[str, Any]]:
|
|
"""Serialize available toolboxes for API response."""
|
|
available = self.getActiveToolboxes(userConnections)
|
|
return [
|
|
{
|
|
"id": tb.id,
|
|
"label": tb.label,
|
|
"description": tb.description,
|
|
"toolCount": len(tb.tools),
|
|
"isDefault": tb.isDefault,
|
|
"requiresConnection": tb.requiresConnection,
|
|
}
|
|
for tb in available
|
|
]
|
|
|
|
|
|
# Module-level singleton
|
|
_toolboxRegistry = ToolboxRegistry()
|
|
|
|
|
|
def getToolboxRegistry() -> ToolboxRegistry:
|
|
"""Get the global toolbox registry singleton."""
|
|
return _toolboxRegistry
|
|
|
|
|
|
def _registerDefaultToolboxes() -> None:
|
|
"""Register the default set of toolboxes."""
|
|
defaults = [
|
|
ToolboxDefinition(
|
|
id="core",
|
|
label="Core Tools",
|
|
description="Basic agent tools: search, read, write, web",
|
|
isDefault=True,
|
|
tools=[],
|
|
),
|
|
ToolboxDefinition(
|
|
id="ai",
|
|
label="AI Tools",
|
|
description="AI-powered analysis and generation",
|
|
isDefault=True,
|
|
tools=[],
|
|
),
|
|
ToolboxDefinition(
|
|
id="datasources",
|
|
label="Data Sources",
|
|
description="Access external data sources and databases",
|
|
isDefault=False,
|
|
tools=[],
|
|
),
|
|
ToolboxDefinition(
|
|
id="email",
|
|
label="Email",
|
|
description="Read and send emails via Outlook/Gmail",
|
|
requiresConnection="microsoft",
|
|
isDefault=False,
|
|
tools=[],
|
|
),
|
|
ToolboxDefinition(
|
|
id="sharepoint",
|
|
label="SharePoint",
|
|
description="Access SharePoint sites, lists, and files",
|
|
requiresConnection="microsoft",
|
|
isDefault=False,
|
|
tools=[],
|
|
),
|
|
ToolboxDefinition(
|
|
id="clickup",
|
|
label="ClickUp",
|
|
description="Manage ClickUp tasks and projects",
|
|
requiresConnection="clickup",
|
|
isDefault=False,
|
|
tools=[],
|
|
),
|
|
ToolboxDefinition(
|
|
id="jira",
|
|
label="Jira",
|
|
description="Manage Jira issues and projects",
|
|
requiresConnection="jira",
|
|
isDefault=False,
|
|
tools=[],
|
|
),
|
|
ToolboxDefinition(
|
|
id="workflow",
|
|
label="Workflow",
|
|
description="Graph manipulation tools for the visual editor",
|
|
featureCode="graphicalEditor",
|
|
isDefault=False,
|
|
tools=[
|
|
"readWorkflowGraph", "addNode", "removeNode", "connectNodes",
|
|
"setNodeParameter", "listAvailableNodeTypes", "validateGraph",
|
|
"listWorkflowHistory", "readWorkflowMessages",
|
|
],
|
|
),
|
|
]
|
|
for tb in defaults:
|
|
_toolboxRegistry.registerToolbox(tb)
|
|
|
|
|
|
_registerDefaultToolboxes()
|