diff --git a/component_planning.py b/component_planning.py index 7ac0911..e7da7f0 100644 --- a/component_planning.py +++ b/component_planning.py @@ -178,10 +178,16 @@ def _build_component_plan( if source in boundary_outputs_by_source: continue output_name = output_names[output_index] + effective_io_type = _remote_output_io_type( + prompt=prompt, + node_id=node_id, + output_index=output_index, + nodes_module=nodes_module, + ) or str(io_type) boundary_outputs_by_source[source] = BoundaryOutputSpec( proxy_output_name=f"{node_id}_{output_name}", source=source, - io_type=str(io_type), + io_type=effective_io_type, is_list=bool(output_is_list[output_index]), preview_target_node_ids=_preview_target_node_ids( prompt=prompt, diff --git a/remote_graph_analysis.py b/remote_graph_analysis.py index 4ef847c..e870d1b 100644 --- a/remote_graph_analysis.py +++ b/remote_graph_analysis.py @@ -85,6 +85,7 @@ _LOCAL_ONLY_REMOTE_CLASS_TYPES = frozenset( _TRANSPORTABLE_OUTPUT_TYPES = frozenset( { "*", + "COMFY_MATCHTYPE_V3", "AUDIO", "BOOLEAN", "FLOAT", @@ -578,7 +579,24 @@ def _remote_output_io_type( output_types, _, _ = _normalize_output_metadata(node_class) if output_index < 0 or output_index >= len(output_types): return None - return str(output_types[output_index]) + io_type = str(output_types[output_index]) + if io_type not in {"*", "COMFY_MATCHTYPE_V3"}: + return io_type + + # V3 dynamic nodes expose a generic output in their class metadata. Resolve + # the concrete type from the linked input before validating a component edge. + for input_value in (prompt_node.get("inputs") or {}).values(): + if not _is_link(input_value): + continue + resolved_type = _remote_output_io_type( + prompt=prompt, + node_id=str(input_value[0]), + output_index=int(input_value[1]), + nodes_module=nodes_module, + ) + if resolved_type is not None and resolved_type != "COMFY_MATCHTYPE_V3": + return resolved_type + return io_type def _remote_output_is_list( diff --git a/tests/test_remote_graph_analysis.py b/tests/test_remote_graph_analysis.py index c2a08f3..4912e2c 100644 --- a/tests/test_remote_graph_analysis.py +++ b/tests/test_remote_graph_analysis.py @@ -4,6 +4,38 @@ from __future__ import annotations from api_intercept_test_support import * # noqa: F401,F403 + +def test_dynamic_match_type_output_resolves_from_linked_image( + remote_graph_analysis_module: Any, +) -> None: + """V3 dynamic resize outputs should cross boundaries as their concrete image type.""" + class ImageSource: + RETURN_TYPES = ("IMAGE",) + + class DynamicResize: + RETURN_TYPES = ("COMFY_MATCHTYPE_V3",) + + prompt = { + "1": {"class_type": "ImageSource", "inputs": {}}, + "2": {"class_type": "ResizeImageMaskNode", "inputs": {"input": ["1", 0]}}, + } + nodes_module = SimpleNamespace( + NODE_CLASS_MAPPINGS={ + "ImageSource": ImageSource, + "ResizeImageMaskNode": DynamicResize, + } + ) + + assert remote_graph_analysis_module._remote_output_io_type( + prompt=prompt, + node_id="2", + output_index=0, + nodes_module=nodes_module, + ) == "IMAGE" + assert remote_graph_analysis_module._is_transportable_output_type( + "COMFY_MATCHTYPE_V3" + ) + def test_remote_partition_preserves_dag_around_ssh_only_llm( api_intercept_module: Any, remote_graph_analysis_module: Any,