feat(fase4): add JavaScript and HTTP request nodes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude AI
2026-01-29 11:13:03 +00:00
parent b8d97e2557
commit af061b1a07
2 changed files with 101 additions and 0 deletions

View File

@@ -3,3 +3,4 @@ from app.nodes.basic import (
TriggerExecutor, MessageExecutor, ButtonsExecutor, TriggerExecutor, MessageExecutor, ButtonsExecutor,
WaitInputExecutor, SetVariableExecutor, ConditionExecutor WaitInputExecutor, SetVariableExecutor, ConditionExecutor
) )
from app.nodes.script import JavaScriptExecutor, HttpRequestExecutor

View 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"