from __future__ import annotations from dataclasses import dataclass from typing import Any, Dict, Iterable, Optional, Tuple @dataclass class SafeActionCheck: safe: bool reason: Any def element_text(element: Optional[Dict[str, Any]]) -> str: if not element: return "" fields = [ element.get("element_id"), element.get("stable_uid"), element.get("role"), element.get("name"), element.get("text"), element.get("href"), element.get("tag"), element.get("type"), ] return " ".join(str(x or "") for x in fields).lower() def find_element(elements: Iterable[Dict[str, Any]], element_id: Optional[str]) -> Optional[Dict[str, Any]]: if not element_id: return None for e in elements: if e.get("element_id") == element_id or e.get("stable_uid") == element_id: return e return None def check_safe_action( *, action: Dict[str, Any], elements: Iterable[Dict[str, Any]], safe_target_contains: Dict[str, list[str]], allow_done: bool = True, allow_wait: bool = True, ) -> SafeActionCheck: act = action.get("action") target = action.get("target_element_id") if act == "done": return SafeActionCheck(bool(allow_done), "done_allowed" if allow_done else "done_blocked") if act == "wait": return SafeActionCheck(bool(allow_wait), "wait_allowed" if allow_wait else "wait_blocked") rules = safe_target_contains.get(act) if not rules: return SafeActionCheck(False, {"reason": "no_safe_rule_for_action", "action": act}) e = find_element(elements, target) txt = element_text(e) if any(str(rule).lower() in txt for rule in rules): return SafeActionCheck(True, {"reason": "target_matches_safe_rule", "target_text": txt}) return SafeActionCheck(False, { "reason": "target_mismatch", "action": act, "target": target, "target_text": txt, "required_any": rules, }) def strict_url_success(url: str, required_substrings: list[str]) -> bool: u = (url or "").lower() return any(x.lower() in u for x in required_substrings)