feat(sdk): transport unchanged V2 node values

This commit is contained in:
benjcooley
2026-08-27 02:05:23 -07:00
parent 4416b35be2
commit b4b7b14b25
2 changed files with 19 additions and 11 deletions
+9 -3
View File
@@ -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
+10 -8
View File
@@ -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)