File size: 6,408 Bytes
f70ac6a
 
f9459c0
f70ac6a
 
 
 
 
 
 
 
 
f9459c0
 
 
f70ac6a
 
 
 
 
f9459c0
f70ac6a
 
 
 
f9459c0
 
 
 
 
f70ac6a
 
f9459c0
 
 
 
 
f70ac6a
 
f9459c0
 
 
 
f70ac6a
 
 
 
 
f9459c0
f70ac6a
 
 
 
 
 
 
 
 
f9459c0
f70ac6a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f9459c0
f70ac6a
 
 
 
 
 
 
 
 
 
 
 
 
 
37f9507
 
f70ac6a
f9459c0
f70ac6a
f9459c0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f70ac6a
f9459c0
f70ac6a
f9459c0
 
 
 
 
 
f70ac6a
f9459c0
 
 
 
 
 
f70ac6a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f9459c0
f70ac6a
 
 
 
 
 
 
 
 
 
 
 
 
 
f9459c0
f70ac6a
 
f9459c0
f70ac6a
 
 
f9459c0
 
 
 
 
 
f70ac6a
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
"""
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,
        }