Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,7 @@ async def process_tools(self, msg: str):
self,
response=response,
tool_name=tool_name,
tool_args=tool_args or {},
)

await tool.after_execution(response)
Expand Down Expand Up @@ -954,8 +955,8 @@ async def validate_tool_request(self, tool_request: Any):
raise ValueError("Tool request must be a dictionary")
if not tool_request.get("tool_name") or not isinstance(tool_request.get("tool_name"), str):
raise ValueError("Tool request must have a tool_name (type string) field")
if not tool_request.get("tool_args") or not isinstance(tool_request.get("tool_args"), dict):
raise ValueError("Tool request must have a tool_args (type dictionary) field")
# if not tool_request.get("tool_args") or not isinstance(tool_request.get("tool_args"), dict):
# raise ValueError("Tool request must have a tool_args (type dictionary) field")



Expand Down
67 changes: 67 additions & 0 deletions extensions/python/webui_ws_event/_20_mcp_elicitation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from helpers.extension import Extension
from helpers.mcp_elicitation import ElicitationManager
from helpers.print_style import PrintStyle


class McpElicitationWsHandler(Extension):
"""Handle elicitation response events from the frontend."""

async def execute(
self,
instance=None,
sid: str = "",
event_type: str = "",
data: dict | None = None,
response_data: dict | None = None,
**kwargs,
):
if instance is None or data is None:
return

if event_type == "mcp_elicitation_response":
await self._handle_elicitation_response(data, response_data)
elif event_type == "mcp_elicitation_list_pending":
await self._handle_list_pending(response_data)

async def _handle_elicitation_response(
self,
data: dict,
response_data: dict | None,
):
request_id = data.get("request_id", "")
action = data.get("action", "")
content = data.get("content", None)

if not request_id:
PrintStyle(font_color="orange", padding=True).print(
"MCP Elicitation WS: Received response with no request_id"
)
if response_data is not None:
response_data["ok"] = False
response_data["error"] = "Missing request_id"
return

if not action:
PrintStyle(font_color="orange", padding=True).print(
f"MCP Elicitation WS: Received response with no action for request '{request_id}'"
)
if response_data is not None:
response_data["ok"] = False
response_data["error"] = "Missing action"
return

manager = ElicitationManager.get_instance()
resolved = manager.resolve(request_id, action, content)

if response_data is not None:
response_data["ok"] = resolved
if not resolved:
response_data["error"] = f"Request '{request_id}' not found or already resolved"

async def _handle_list_pending(self, response_data: dict | None):
manager = ElicitationManager.get_instance()
pending = manager.get_all_pending()

if response_data is not None:
response_data["ok"] = True
response_data["pending"] = pending
66 changes: 66 additions & 0 deletions extensions/python/webui_ws_event/_21_mcp_sampling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from helpers.extension import Extension
from helpers.mcp_sampling import SamplingManager
from helpers.print_style import PrintStyle


class McpSamplingWsHandler(Extension):
"""Handle sampling response events from the frontend."""

async def execute(
self,
instance=None,
sid: str = "",
event_type: str = "",
data: dict | None = None,
response_data: dict | None = None,
**kwargs,
):
if instance is None or data is None:
return

if event_type == "mcp_sampling_response":
await self._handle_sampling_response(data, response_data)
elif event_type == "mcp_sampling_list_pending":
await self._handle_list_pending(response_data)

async def _handle_sampling_response(
self,
data: dict,
response_data: dict | None,
):
request_id = data.get("request_id", "")
action = data.get("action", "")

if not request_id:
PrintStyle(font_color="orange", padding=True).print(
"MCP Sampling WS: Received response with no request_id"
)
if response_data is not None:
response_data["ok"] = False
response_data["error"] = "Missing request_id"
return

if not action:
PrintStyle(font_color="orange", padding=True).print(
f"MCP Sampling WS: Received response with no action for request '{request_id}'"
)
if response_data is not None:
response_data["ok"] = False
response_data["error"] = "Missing action"
return

manager = SamplingManager.get_instance()
resolved = manager.resolve(request_id, action)

if response_data is not None:
response_data["ok"] = resolved
if not resolved:
response_data["error"] = f"Request '{request_id}' not found or already resolved"

async def _handle_list_pending(self, response_data: dict | None):
manager = SamplingManager.get_instance()
pending = manager.get_all_pending()

if response_data is not None:
response_data["ok"] = True
response_data["pending"] = pending
22 changes: 4 additions & 18 deletions helpers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
ThreadLockType = Union[threading.Lock, threading.RLock]

CACHE_AREA = "api_handlers(api)"
cache.toggle_area(CACHE_AREA, False) # cache off for now
# cache.toggle_area(CACHE_AREA, False) # cache off for now

Input = dict
Output = Union[Dict[str, Any], Response]
Expand Down Expand Up @@ -236,10 +236,13 @@ async def call_handler() -> BaseResponse:

def register_watchdogs():
from helpers import watchdog
from helpers.ws import CACHE_AREA as WS_CACHE_AREA


def on_api_change(items: list[watchdog.WatchItem]):
PrintStyle.debug("API endpoint watchdog triggered:", items)
cache.clear(CACHE_AREA)
cache.clear(WS_CACHE_AREA)

watchdog.add_watchdog(
"api_handlers",
Expand All @@ -250,20 +253,3 @@ def on_api_change(items: list[watchdog.WatchItem]):
patterns=["*.py"],
handler=on_api_change,
)

# WS handler cache shares the same watched directories (api/, usr/api/)
from helpers.ws import CACHE_AREA as WS_CACHE_AREA

def on_ws_change(items: list[watchdog.WatchItem]):
PrintStyle.debug("WS handler watchdog triggered:", items)
cache.clear(WS_CACHE_AREA)

watchdog.add_watchdog(
"ws_handlers",
roots=[
files.get_abs_path(files.API_DIR),
files.get_abs_path(files.USER_DIR, files.API_DIR),
],
patterns=["ws_*.py"],
handler=on_ws_change,
)
Loading