# Copyright (c) 2025 Patrick Motsch # I/O node executor - delegates to ActionExecutor. import logging from typing import Dict, Any logger = logging.getLogger(__name__) class IOExecutor: """Execute I/O nodes by calling ActionExecutor.executeAction(method, action, params).""" def __init__(self, services: Any): self.services = services async def execute( self, node: Dict[str, Any], context: Dict[str, Any], ) -> Any: from modules.workflows.processing.core.actionExecutor import ActionExecutor nodeType = node.get("type", "") nodeId = node.get("id", "") logger.info("IOExecutor node %s type=%s", nodeId, nodeType) if not nodeType.startswith("io."): logger.debug("IOExecutor node %s not io.* -> None", nodeId) return None parts = nodeType.split(".", 2) if len(parts) < 3: logger.debug("IOExecutor node %s invalid type parts -> None", nodeId) return None _, methodName, actionName = parts logger.info("IOExecutor node %s method=%s action=%s", nodeId, methodName, actionName) nodeOutputs = context.get("nodeOutputs", {}) params = dict(node.get("parameters") or {}) from modules.workflows.automation2.graphUtils import resolveParameterReferences resolvedParams = resolveParameterReferences(params, nodeOutputs) logger.info("IOExecutor node %s resolvedParams keys=%s", nodeId, list(resolvedParams.keys())) inputSources = context.get("inputSources", {}).get(nodeId, {}) if 0 in inputSources: srcId, _ = inputSources[0] inp = nodeOutputs.get(srcId) from modules.workflows.automation2.executors.actionNodeExecutor import _extract_wired_document_list wired = _extract_wired_document_list(inp) docs = (wired or {}).get("documents") if isinstance(wired, dict) else None if docs: resolvedParams.setdefault("documentList", wired) elif inp is not None: resolvedParams.setdefault("input", inp) executor = ActionExecutor(self.services) logger.info("IOExecutor node %s calling executeAction(%s, %s)", nodeId, methodName, actionName) result = await executor.executeAction(methodName, actionName, resolvedParams) docs_list = [d.model_dump() if hasattr(d, "model_dump") else d for d in (result.documents or [])] out = { "success": result.success, "error": result.error, "documents": docs_list, "documentList": docs_list, "data": result.model_dump() if hasattr(result, "model_dump") else {"success": result.success, "error": result.error}, } logger.info( "IOExecutor node %s result: success=%s error=%s doc_count=%d", nodeId, result.success, result.error, len(out.get("documents", [])), ) return out