import json from typing import Optional, Any import httpx from app.nodes.base import NodeExecutor from app.context import FlowContext class JavaScriptExecutor(NodeExecutor): """Execute Python expressions with restricted globals""" ALLOWED_BUILTINS = { 'abs': abs, 'all': all, 'any': any, 'bool': bool, 'dict': dict, 'float': float, 'int': int, 'len': len, 'list': list, 'max': max, 'min': min, 'round': round, 'str': str, 'sum': sum, 'sorted': sorted, } async def execute(self, config: dict, context: FlowContext, session: Any) -> Optional[str]: code = config.get("code", "") output_variable = config.get("output_variable", "_result") if not code: return "default" exec_globals = { '__builtins__': self.ALLOWED_BUILTINS, 'context': { 'contact': context.contact, 'conversation': context.conversation, 'message': context.message, 'variables': context.variables.copy(), }, 'variables': context.variables.copy(), } try: result = eval(code, exec_globals, {}) if result is not None: context.set(output_variable, result) return "success" except Exception as e: context.set("_script_error", str(e)) return "error" class HttpRequestExecutor(NodeExecutor): """Make HTTP requests to external APIs""" async def execute(self, config: dict, context: FlowContext, session: Any) -> Optional[str]: url = context.interpolate(config.get("url", "")) method = config.get("method", "GET").upper() headers = config.get("headers", {}) body = config.get("body") output_variable = config.get("output_variable", "_http_response") timeout = config.get("timeout", 10) if not url: return "error" headers = {k: context.interpolate(str(v)) for k, v in headers.items()} if body and isinstance(body, str): body = context.interpolate(body) try: async with httpx.AsyncClient() as client: json_body = None if body: try: json_body = json.loads(body) if isinstance(body, str) else body except json.JSONDecodeError: json_body = None response = await client.request( method=method, url=url, headers=headers, json=json_body, timeout=timeout ) response_json = None if response.headers.get("content-type", "").startswith("application/json"): try: response_json = response.json() except json.JSONDecodeError: pass context.set(output_variable, { "status": response.status_code, "body": response.text, "json": response_json }) if 200 <= response.status_code < 300: return "success" return "error" except Exception as e: context.set("_http_error", str(e)) return "error"