"""Shared Agent Loop helpers for AI providers with tool_use support.""" from __future__ import annotations import uuid from typing import Any from src.plugins.mcp.interfaces import MCPTool, MCPToolProvider, MCPToolResult from src.services.ai_providers.permissions import filter_tools_for_agent from src.services.ai_providers.tool_schema import tool_by_provider_name class AgentToolExecutor: """Execute provider-safe tool names against MCP providers with audit context.""" def __init__( self, *, available_tools: list[MCPTool], providers: dict[str, MCPToolProvider], agent_role: str, incident_id: str | None = None, session_id: str | None = None, flywheel_node: str | None = None, ) -> None: self.available_tools = filter_tools_for_agent(available_tools, agent_role) self.providers = providers self.agent_role = agent_role self.incident_id = incident_id self.session_id = session_id or str(uuid.uuid4()) self.flywheel_node = flywheel_node async def execute(self, provider_tool_name: str, parameters: dict[str, Any]) -> MCPToolResult: """Resolve and execute one tool call.""" tool = tool_by_provider_name(self.available_tools, provider_tool_name) if tool is None: return MCPToolResult( success=False, execution_id=str(uuid.uuid4()), error=f"Tool not allowed or unknown: {provider_tool_name}", ) provider = self.providers.get(tool.server_name) if provider is None: return MCPToolResult( success=False, execution_id=str(uuid.uuid4()), error=f"Provider not available: {tool.server_name}", ) audit_context = { "agent_role": self.agent_role, "session_id": self.session_id, "incident_id": self.incident_id, "flywheel_node": self.flywheel_node, } tool_parameters = dict(parameters) tool_parameters["_mcp_audit"] = { key: value for key, value in audit_context.items() if value } return await provider.execute(tool.name, tool_parameters)