mirror of
https://github.com/SHOGGOTH-SECTOR/sica-fondt.git
synced 2026-08-01 00:23:15 +00:00
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
188 lines
6.8 KiB
Python
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)
|