93 lines
2.2 KiB
Python
93 lines
2.2 KiB
Python
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
|
|
} |
