feat(fase4): add JavaScript and HTTP request nodes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,3 +3,4 @@ from app.nodes.basic import (
|
||||
TriggerExecutor, MessageExecutor, ButtonsExecutor,
|
||||
WaitInputExecutor, SetVariableExecutor, ConditionExecutor
|
||||
)
|
||||
from app.nodes.script import JavaScriptExecutor, HttpRequestExecutor
|
||||
|
||||
100
services/flow-engine/app/nodes/script.py
Normal file
100
services/flow-engine/app/nodes/script.py
Normal file
@@ -0,0 +1,100 @@
|
||||
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"
|
||||
Reference in New Issue
Block a user