Claude 24f816b6a3
Consolidate 22 sibling repos into layered organism structure
Place useful parts of the surrounding repos into sica-fondt by layer, per the
body model (Ada = membrane; brain/endocrine/capabilities/knowledge non-Ada):

- brain/        LLM reasoning + providers (dapr, hermes, MoMoA)
- capabilities/ REPRAG sidecars: hermes tools/skills, dapr tools, parallel
                dispatch, A51 channels, and the OSINT cluster
- knowledge/    LORAG corpus: 754 cyber-skills, agency personas, secure-coding,
                MITRE ATT&CK data
- reference/    defensive threat-reference (C3, shhbruh doc) + AdaYaml parser

License handling: AGPL sources (worldosint, advanced_evolution, mercury,
Reticulum) and GPL DeTTECT are SPEC-only clean-room/port descriptions — no
copyleft code copied. MIT/Apache/data parts copied as working trees.

Safety: shhbruh escape/persistence material and C3 covert-C2 kept as reference
only, not wired into the running organism. See CONSOLIDATION.md.

https://claude.ai/code/session_01UehUqEXXJJCsHoA4voCU5c
2026-06-10 06:53:01 +00:00

188 lines
6.8 KiB
Python

#
# Copyright 2026 The Dapr Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
from typing import Any, Dict, List, Optional, Union, Callable
from pydantic import BaseModel, Field, PrivateAttr
from rich.table import Table
from rich.console import Console
from dapr_agents.tool.base import AgentTool
from dapr_agents.types import AgentToolExecutorError, ToolError, ToolResult, TextContent
logger = logging.getLogger(__name__)
# The existing AgentBase class allows tools to be both Callable and AgentTool instances.
# Therefore, the AgentToolExecutor must support both types of tools as well.
class AgentToolExecutor(BaseModel):
"""
Manages the registration and execution of tools, providing both sync and async interfaces.
Attributes:
tools (List[Union[AgentTool, Callable]]): List of tools to register and manage.
"""
tools: List[Union[AgentTool, Callable]] = Field(
default_factory=list, description="List of tools to register and manage."
)
_tools_map: Dict[str, AgentTool] = PrivateAttr(default_factory=dict)
def model_post_init(self, __context: Any) -> None:
"""Initializes the internal tools map after model creation."""
for tool in self.tools:
self.register_tool(tool)
logger.info(f"Tool Executor initialized with {len(self._tools_map)} tool(s).")
super().model_post_init(__context)
@staticmethod
def _normalize(name: str) -> str:
"""Normalize a tool name: lowercase, strip spaces and underscores."""
return name.lower().replace(" ", "").replace("_", "")
def register_tool(self, tool: Union[AgentTool, Callable]) -> None:
"""
Registers a tool instance, ensuring no duplicate names.
Args:
tool (Union[AgentTool, Callable]): The tool to register.
Raises:
AgentToolExecutorError: If the tool name is already registered.
"""
# Convert callable to AgentTool if needed since we support both Callable and AgentTool instances.
if callable(tool) and not isinstance(tool, AgentTool):
try:
tool = AgentTool.from_func(tool)
logger.info(f"Converted callable to AgentTool: {tool.name}")
except Exception as e:
logger.error(f"Failed to convert callable to AgentTool: {e}")
raise AgentToolExecutorError(
f"Failed to convert callable to AgentTool: {e}"
) from e
if isinstance(tool, AgentTool):
key = self._normalize(tool.name)
if key in self._tools_map:
logger.error(f"Attempted to register duplicate tool: {tool.name}")
raise AgentToolExecutorError(
f"Tool '{tool.name}' is already registered."
)
self._tools_map[key] = tool
logger.info(f"Tool registered: {tool.name}")
else:
raise TypeError(f"Unsupported tool type: {type(tool).__name__}")
def get_tool(self, tool_name: str) -> Optional[AgentTool]:
"""
Retrieves a tool by name (case-insensitive, spaces/underscores ignored).
Args:
tool_name (str): Name of the tool to retrieve.
Returns:
AgentTool or None if not found.
"""
return self._tools_map.get(self._normalize(tool_name))
def list_tools(self) -> List[AgentTool]:
"""
Returns all registered tools in registration order.
Returns:
List[AgentTool]: All registered tools.
"""
return list(self._tools_map.values())
def get_tool_names(self) -> List[str]:
"""
Lists all registered tool display names.
Returns:
List[str]: Names of all registered tools.
"""
return [tool.name for tool in self._tools_map.values()]
def get_tool_signatures(self) -> str:
"""
Retrieves the signatures of all registered tools.
Returns:
str: Tool signatures, each on a new line.
"""
return "\n".join(tool.signature for tool in self._tools_map.values())
def get_tool_details(self) -> str:
"""
Retrieves names, descriptions, and argument schemas of all tools.
Returns:
str: Detailed tool information, each on a new line.
"""
return "\n".join(
f"{tool.name}: {tool.description}. Args schema: {tool.args_schema}"
for tool in self._tools_map.values()
)
async def run_tool(self, tool_name: str, *args, **kwargs) -> Any:
"""
Executes a tool by name, automatically handling both sync and async tools.
Args:
tool_name (str): Tool name to execute.
*args: Positional arguments.
**kwargs: Keyword arguments.
Returns:
Any: Result of tool execution.
Raises:
AgentToolExecutorError: If the tool is not found or execution fails.
"""
tool = self.get_tool(tool_name)
if not tool:
logger.error(f"Tool not found: {tool_name}")
raise AgentToolExecutorError(f"Tool '{tool_name}' not found.")
try:
logger.info(f"Running tool (auto): {tool_name}")
if tool._is_async:
return await tool.arun(*args, **kwargs)
return tool(*args, **kwargs)
except ToolError as e:
logger.error(f"Tool execution error in '{tool_name}': {e}")
return ToolResult(
content=[
TextContent(text=f"Error executing tool '{tool_name}': {str(e)}")
],
isError=True,
)
except Exception as e:
logger.error(f"Unexpected error in '{tool_name}': {e}")
raise AgentToolExecutorError(
f"Unexpected error in tool '{tool_name}': {e}"
) from e
@property
def help(self) -> None:
"""Displays a rich-formatted table of registered tools."""
table = Table(title="Available Tools")
table.add_column("Name", style="bold cyan")
table.add_column("Description")
table.add_column("Signature")
for name, tool in self._tools_map.items():
table.add_row(name, tool.description, tool.signature)
console = Console()
console.print(table)