From b4b7b14b2591300ef08b014dab19beb69abc78ef Mon Sep 17 00:00:00 2001 From: benjcooley Date: Thu, 27 Aug 2026 02:05:23 -0700 Subject: [PATCH] feat(sdk): transport unchanged V2 node values --- comfy_api/latest/_sdk.py | 12 +++++++++--- execution.py | 18 ++++++++++-------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/comfy_api/latest/_sdk.py b/comfy_api/latest/_sdk.py index 9cf735056..97cffd8f3 100644 --- a/comfy_api/latest/_sdk.py +++ b/comfy_api/latest/_sdk.py @@ -760,17 +760,23 @@ class ExecutionPlan: node_type: str tier: str = "default" # overlay reads manifest tier; OSS is always "default" permissions: tuple[str, ...] = () - # Work-unit payload for out-of-process backends: import spec of the node's - # defining module and the (ref-wrapped) inputs. Populated by the execution - # seam for SDK_REFS nodes; in-process dispatch ignores them. + # Work-unit payload for out-of-process backends. ``refs`` means the node + # explicitly consumes SDK handles; ``values`` means the backend may wrap + # for transport and the guest must materialize those handles before + # invoking the unchanged V2 body. In-process dispatch ignores the payload. node_module: str = "" inputs: Optional[dict] = None + input_mode: str = "refs" prompt: Any = None extra_pnginfo: Any = None method: str = "execute" def __post_init__(self) -> None: self.method = _normalize_v2_node_method(self.method) + if self.input_mode not in {"refs", "values"}: + raise ValueError( + f"V2 node input mode {self.input_mode!r} is not allowed; " + f"expected 'refs' or 'values'") @runtime_checkable diff --git a/execution.py b/execution.py index bd24a7a0d..1e57096b0 100644 --- a/execution.py +++ b/execution.py @@ -299,11 +299,14 @@ async def _async_map_node_over_list(prompt_id, unique_id, obj, input_data_all, f # registry manifest can narrow the set further. Nodes that # declare nothing (the overwhelming majority) get nothing. _sdk_perms = getattr(type_obj, "SDK_PERMISSIONS", ()) or () + _sdk_refs_mode = bool(getattr(type_obj, "SDK_REFS", False)) _sdk_plan = _comfy_sdk.ExecutionPlan( prompt_id=str(prompt_id), node_id=str(unique_id), node_type=getattr(type_obj, "__name__", "node"), node_module=getattr(type_obj, "__module__", "") or "", + inputs=inputs, + input_mode="refs" if _sdk_refs_mode else "values", method=func, permissions=tuple(_sdk_perms), prompt=getattr(class_clone.hidden, "prompt", None), @@ -317,7 +320,6 @@ async def _async_map_node_over_list(prompt_id, unique_id, obj, input_data_all, f _comfy_sdk.providers.ops_provider, ) # SDK nodes see assets (refs), not buffers: wrap heavy inputs. - _sdk_refs_mode = getattr(type_obj, "SDK_REFS", False) if _sdk_refs_mode: inputs = await _comfy_sdk.wrap_inputs(_sdk_refs, inputs) # Ship the work unit on the plan so an out-of-process @@ -351,11 +353,12 @@ async def _async_map_node_over_list(prompt_id, unique_id, obj, input_data_all, f result = await _comfy_sdk.providers.execution_backend.dispatch( _sdk_plan, local_call, _sdk_runtime.runtime ) - # Resolve output refs back to real objects for downstream nodes. - if _sdk_refs_mode: - if isinstance(result, asyncio.Task): - result = await result - result = await _comfy_sdk.unwrap_outputs(_sdk_refs, result) + # A sandbox backend returns refs in both modes: explicit-ref + # nodes create them directly, while compatibility-mode V2 + # nodes have their ordinary outputs wrapped by the guest. + if isinstance(result, asyncio.Task): + result = await result + result = await _comfy_sdk.unwrap_outputs(_sdk_refs, result) finally: # The table holds this node's inputs and every intermediate # it created — for image or latent work, the largest @@ -363,8 +366,7 @@ async def _async_map_node_over_list(prompt_id, unique_id, obj, input_data_all, f # to collection, and in `finally` because the path that most # needs it is the one where the node raised or its guest # died: that is exactly when nothing else is going to run. - if _sdk_refs_mode: - _sdk_refs.clear() + _sdk_refs.clear() else: result = await local_call() results.append(result)