Initial commit
This commit is contained in:
93
core/tools/base.py
Normal file
93
core/tools/base.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Protocol
|
||||
|
||||
from core.events import bus
|
||||
|
||||
|
||||
# =========================
|
||||
# TOOL CONTEXT
|
||||
# =========================
|
||||
|
||||
@dataclass
|
||||
class ToolContext:
|
||||
user: str | None = None
|
||||
dry_run: bool = False
|
||||
|
||||
# safe mutable metadata container
|
||||
meta: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
# =========================
|
||||
# TOOL CONTRACT
|
||||
# =========================
|
||||
|
||||
class Tool(Protocol):
|
||||
name: str
|
||||
|
||||
def execute(self, payload: dict[str, Any], ctx: ToolContext) -> Any:
|
||||
...
|
||||
|
||||
|
||||
# =========================
|
||||
# BASE TOOL
|
||||
# =========================
|
||||
|
||||
class BaseTool:
|
||||
name: str = "base"
|
||||
|
||||
# -------------------------
|
||||
# LIFECYCLE WRAPPER
|
||||
# -------------------------
|
||||
|
||||
def run(self, payload: dict[str, Any], ctx: ToolContext):
|
||||
bus.log(self.name, "tool_start", "INFO", {"payload": payload})
|
||||
|
||||
try:
|
||||
# attach execution metadata (useful for event graph later)
|
||||
ctx.meta.setdefault("tool", self.name)
|
||||
ctx.meta.setdefault("dry_run", ctx.dry_run)
|
||||
|
||||
if ctx.dry_run:
|
||||
result = self.preview(payload, ctx)
|
||||
else:
|
||||
result = self.execute(payload, ctx)
|
||||
|
||||
bus.log(
|
||||
self.name,
|
||||
"tool_success",
|
||||
"SUCCESS",
|
||||
{
|
||||
"result_type": type(result).__name__,
|
||||
"result": repr(result)[:500] # prevent log explosion
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
bus.log(
|
||||
self.name,
|
||||
"tool_error",
|
||||
"ERROR",
|
||||
{
|
||||
"error_type": type(e).__name__,
|
||||
"error": str(e)
|
||||
}
|
||||
)
|
||||
raise
|
||||
|
||||
# -------------------------
|
||||
# OVERRIDABLE
|
||||
# -------------------------
|
||||
|
||||
def execute(self, payload: dict[str, Any], ctx: ToolContext):
|
||||
raise NotImplementedError("Tool must implement execute()")
|
||||
|
||||
def preview(self, payload: dict[str, Any], ctx: ToolContext):
|
||||
return {
|
||||
"dry_run": True,
|
||||
"tool": self.name,
|
||||
"payload": payload
|
||||
}
|
||||
124
core/tools/registry.py
Normal file
124
core/tools/registry.py
Normal file
@@ -0,0 +1,124 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from core.tools.base import BaseTool, ToolContext
|
||||
from core.events import bus
|
||||
|
||||
|
||||
# =========================
|
||||
# TOOL REGISTRY
|
||||
# =========================
|
||||
|
||||
class ToolRegistry:
|
||||
def __init__(self):
|
||||
self._tools: dict[str, BaseTool] = {}
|
||||
|
||||
# -------------------------
|
||||
# REGISTER
|
||||
# -------------------------
|
||||
|
||||
def register(self, tool: BaseTool):
|
||||
self._tools[tool.name] = tool
|
||||
|
||||
bus.log(
|
||||
"REGISTRY",
|
||||
"tool_registered",
|
||||
"INFO",
|
||||
{
|
||||
"tool": tool.name
|
||||
}
|
||||
)
|
||||
|
||||
# -------------------------
|
||||
# RESOLVE
|
||||
# -------------------------
|
||||
|
||||
def get(self, name: str) -> BaseTool:
|
||||
tool = self._tools.get(name)
|
||||
|
||||
if tool is None:
|
||||
bus.log(
|
||||
"REGISTRY",
|
||||
"tool_not_found",
|
||||
"ERROR",
|
||||
{"tool": name}
|
||||
)
|
||||
|
||||
raise ValueError(f"Tool not found: {name}")
|
||||
|
||||
return tool
|
||||
|
||||
# -------------------------
|
||||
# EXECUTE
|
||||
# -------------------------
|
||||
|
||||
def run(
|
||||
self,
|
||||
name: str,
|
||||
payload: dict[str, Any],
|
||||
ctx: ToolContext
|
||||
):
|
||||
tool = self.get(name)
|
||||
|
||||
# registry metadata
|
||||
ctx.meta.setdefault("registry", True)
|
||||
ctx.meta.setdefault("tool_name", name)
|
||||
|
||||
bus.log(
|
||||
"REGISTRY",
|
||||
"tool_dispatch",
|
||||
"INFO",
|
||||
{
|
||||
"tool": name,
|
||||
"dry_run": ctx.dry_run,
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
result = tool.run(payload, ctx)
|
||||
|
||||
bus.log(
|
||||
"REGISTRY",
|
||||
"tool_completed",
|
||||
"SUCCESS",
|
||||
{
|
||||
"tool": name,
|
||||
"result_type": type(result).__name__
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
bus.log(
|
||||
"REGISTRY",
|
||||
"tool_failed",
|
||||
"ERROR",
|
||||
{
|
||||
"tool": name,
|
||||
"error": str(e),
|
||||
"error_type": type(e).__name__
|
||||
}
|
||||
)
|
||||
raise
|
||||
|
||||
# -------------------------
|
||||
# DISCOVERY (NEW)
|
||||
# -------------------------
|
||||
|
||||
def all_tools(self) -> list[BaseTool]:
|
||||
return list(self._tools.values())
|
||||
|
||||
def names(self) -> list[str]:
|
||||
return list(self._tools.keys())
|
||||
|
||||
def exists(self, name: str) -> bool:
|
||||
return name in self._tools
|
||||
|
||||
|
||||
# =========================
|
||||
# SINGLETON
|
||||
# =========================
|
||||
|
||||
registry = ToolRegistry()
|
||||
Reference in New Issue
Block a user
