""" ActionExecutorAgent - Prepares actionable buttons for user Determines what actions are available based on analysis context """ from typing import Dict from .base import Agent, AgentConfig, AgentResult import time class ActionExecutorAgent(Agent): """ Prepares action buttons based on context. Does NOT execute — just plans available actions. Actual execution happens in /api/copilot/action endpoint. """ AVAILABLE_ACTIONS = { "CREATE_CASE": { "label": "Create Investigation Case", "icon": "📁", "category": "DIRECT_ACTION", "requires_confirmation": False, "priority": 1, }, "GENERATE_STR": { "label": "Generate STR Report (PDF)", "icon": "📄", "category": "DATA_ACTION", "requires_confirmation": False, "priority": 2, }, "OPEN_STR_BUILDER": { "label": "Open STR Builder", "icon": "🔨", "category": "NAVIGATION", "requires_confirmation": False, "priority": 3, }, "VIEW_GRAPH": { "label": "Open Network Graph", "icon": "🕸", "category": "NAVIGATION", "requires_confirmation": False, "priority": 4, }, "ADD_NOTES": { "label": "Add Investigation Notes", "icon": "📝", "category": "DIRECT_ACTION", "requires_confirmation": False, "priority": 7, }, } def __init__(self, api_pool): config = AgentConfig( name="ActionExecutorAgent", model="llama-3.1-8b-instant", temperature=0.0, max_tokens=500, timeout_ms=5000, ) super().__init__(config, api_pool) def _build_prompt(self, **inputs) -> str: return "" def _parse_response(self, response_text: str) -> Dict: return {} async def invoke( self, account_id: str = None, intent: str = "GENERAL", risk_assessment: Dict = None, alert_data: Dict = None, **kwargs, ) -> AgentResult: """Prepare list of actions to show user based on context.""" start_time = time.time() try: actions = [] risk_score = 0 risk_tier = "LOW" if risk_assessment: recommendation = risk_assessment.get("recommendation", {}) risk_tier = recommendation.get("priority", "MEDIUM") if alert_data: risk_score = alert_data.get("risk_score", 0) risk_tier = alert_data.get("risk_tier", risk_tier) is_high_risk = risk_tier == "CRITICAL" is_medium_risk = risk_tier in ["HIGH", "MEDIUM"] # Create case for medium/high risk accounts if account_id and (is_high_risk or is_medium_risk): actions.append(self._build_action("CREATE_CASE", { "account_id": account_id, "alert_id": alert_data.get("alert_id") if alert_data else None, "description": f"Investigation for {account_id}", "priority": "CRITICAL" if is_high_risk else "HIGH", })) # STR/SAR generation for NARRATIVE intent or high-risk if account_id and (intent == "NARRATIVE" or is_high_risk): actions.append(self._build_action("GENERATE_STR", { "account_id": account_id, "alert_id": alert_data.get("alert_id") if alert_data else None, })) actions.append(self._build_action("OPEN_STR_BUILDER", { "account_id": account_id, "url": f"#str-builder?account={account_id}", })) # Navigation — always available when account_id is known if account_id: actions.append(self._build_action("VIEW_GRAPH", { "account_id": account_id, "url": f"#investigation?account={account_id}", "hops": 2, "max_nodes": 50, })) # Add notes — only if a case exists (medium/high risk context) if account_id and (is_high_risk or is_medium_risk): actions.append(self._build_action("ADD_NOTES", { "account_id": account_id, "url": f"#cases?account={account_id}&action=new_note", })) actions.sort(key=lambda x: x.get("priority", 99)) result_data = { "actions": actions, "actions_count": len(actions), "context_analyzed": { "account_id": account_id, "intent": intent, "risk_score": risk_score, "risk_tier": risk_tier, }, } self.logger.info(f"[OK] {self.config.name}: Prepared {len(actions)} actions") return await self._create_result( success=True, data=result_data, tokens_input=0, tokens_output=0, start_time=start_time, ) except Exception as e: self.logger.error(f"[FAIL] {self.config.name}: {e}") return await self._create_result( success=False, data={"actions": []}, error=str(e), start_time=start_time, ) def _build_action(self, action_type: str, payload: Dict) -> Dict: """Build an action object with full metadata.""" if action_type not in self.AVAILABLE_ACTIONS: return {} cfg = self.AVAILABLE_ACTIONS[action_type] return { "id": f"action_{action_type.lower()}_{int(time.time() * 1000)}", "type": action_type, "label": cfg["label"], "icon": cfg.get("icon", ""), "category": cfg["category"], "priority": cfg["priority"], "requires_confirmation": cfg.get("requires_confirmation", False), "confirmation_message": cfg.get("confirmation_message", ""), "enabled": True, "payload": payload, }