griptape-nodes 0.65.1__py3-none-any.whl → 0.65.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,10 +6,10 @@ from dataclasses import dataclass
6
6
  from typing import TYPE_CHECKING
7
7
 
8
8
  from griptape_nodes.exe_types.base_iterative_nodes import BaseIterativeStartNode
9
+ from griptape_nodes.exe_types.node_groups import SubflowNodeGroup
9
10
  from griptape_nodes.exe_types.node_types import (
10
11
  LOCAL_EXECUTION,
11
12
  BaseNode,
12
- NodeGroupNode,
13
13
  NodeResolutionState,
14
14
  )
15
15
  from griptape_nodes.machines.fsm import FSM, State
@@ -209,7 +209,7 @@ def _resolve_target_node_for_control_flow(next_node_info: NextNodeInfo) -> tuple
209
209
  entry_parameter = next_node_info.entry_parameter
210
210
 
211
211
  # Check if node has a parent and if parent is not local execution
212
- if target_node.parent_group is not None and isinstance(target_node.parent_group, NodeGroupNode):
212
+ if target_node.parent_group is not None and isinstance(target_node.parent_group, SubflowNodeGroup):
213
213
  parent_group = target_node.parent_group
214
214
  execution_env = parent_group.get_parameter_value(parent_group.execution_environment.name)
215
215
  if execution_env != LOCAL_EXECUTION:
@@ -345,7 +345,7 @@ class ControlFlowMachine(FSM[ControlFlowContext]):
345
345
  current_nodes = await self._process_nodes_for_dag(start_node)
346
346
  else:
347
347
  current_nodes = [start_node]
348
- if isinstance(start_node.parent_group, NodeGroupNode):
348
+ if isinstance(start_node.parent_group, SubflowNodeGroup):
349
349
  # In sequential mode, we aren't going to run this. Just continue.
350
350
  node = GriptapeNodes.FlowManager().get_next_node_from_execution_queue()
351
351
  if node is not None:
@@ -473,8 +473,8 @@ class ControlFlowMachine(FSM[ControlFlowContext]):
473
473
  node.state = NodeResolutionState.UNRESOLVED
474
474
  # Use proxy node if this node is part of a group, otherwise use original node
475
475
  node_to_add = node
476
- disconnected = True
477
476
  # Only add if not already added (proxy might already be in DAG)
477
+ disconnected = True
478
478
  if node_to_add.name not in dag_builder.node_to_reference:
479
479
  # Now, we need to create the DAG, but it can't be queued or used until it's dependencies have been resolved.
480
480
  # Figure out which graph the data node belongs to, if it belongs to a graph.
@@ -72,6 +72,7 @@ class NodeMetadata(BaseModel):
72
72
  color: str | None = None
73
73
  group: str | None = None
74
74
  deprecation: NodeDeprecationMetadata | None = None
75
+ is_node_group: bool | None = None
75
76
 
76
77
 
77
78
  class CategoryDefinition(BaseModel):
@@ -45,7 +45,7 @@ class WorkflowShape(BaseModel):
45
45
 
46
46
 
47
47
  class WorkflowMetadata(BaseModel):
48
- LATEST_SCHEMA_VERSION: ClassVar[str] = "0.13.0"
48
+ LATEST_SCHEMA_VERSION: ClassVar[str] = "0.14.0"
49
49
 
50
50
  name: str
51
51
  schema_version: str
@@ -456,7 +456,7 @@ class PackageNodesAsSerializedFlowRequest(RequestPayload):
456
456
  entry_control_node_name: str | None = None
457
457
  entry_control_parameter_name: str | None = None
458
458
  output_parameter_prefix: str = "packaged_node_"
459
- node_group_name: str | None = None # Name of the NodeGroupNode if packaging a group
459
+ node_group_name: str | None = None # Name of the SubflowNodeGroup if packaging a group
460
460
 
461
461
 
462
462
  @dataclass
@@ -55,6 +55,7 @@ class CreateNodeRequest(RequestPayload):
55
55
  initial_setup: Skip setup work when loading from file (defaults to False)
56
56
  set_as_new_context: Set this node as current context after creation (defaults to False)
57
57
  create_error_proxy_on_failure: Create Error Proxy node if creation fails (defaults to True)
58
+ node_names_to_add: List of existing node names to add to this node after creation (used by SubflowNodeGroup, defaults to None)
58
59
 
59
60
  Results: CreateNodeResultSuccess (with assigned name) | CreateNodeResultFailure (invalid type, missing library, flow not found)
60
61
  """
@@ -72,6 +73,8 @@ class CreateNodeRequest(RequestPayload):
72
73
  set_as_new_context: bool = False
73
74
  # When True, create an Error Proxy node if the requested node type fails to create
74
75
  create_error_proxy_on_failure: bool = True
76
+ # List of node names to add to this node after creation (used by SubflowNodeGroup)
77
+ node_names_to_add: list[str] | None = None
75
78
 
76
79
 
77
80
  @dataclass
@@ -102,6 +105,12 @@ class CreateNodeResultFailure(ResultPayloadFailure):
102
105
  """
103
106
 
104
107
 
108
+ # Backwards compatibility for workflows that use the deprecated CreateNodeGroupRequest
109
+ @dataclass
110
+ class CreateNodeGroupRequest:
111
+ pass
112
+
113
+
105
114
  @dataclass
106
115
  @PayloadRegistry.register
107
116
  class DeleteNodeRequest(RequestPayload):
@@ -443,10 +452,11 @@ class SerializedNodeCommands:
443
452
  set_parameter_value_command: SetParameterValueRequest
444
453
  unique_value_uuid: SerializedNodeCommands.UniqueParameterValueUUID
445
454
 
446
- create_node_command: CreateNodeRequest | CreateNodeGroupRequest
455
+ create_node_command: CreateNodeRequest
447
456
  element_modification_commands: list[RequestPayload]
448
457
  node_dependencies: NodeDependencies
449
458
  lock_node_command: SetLockNodeStateRequest | None = None
459
+ is_node_group: bool = False
450
460
  node_uuid: NodeUUID = field(default_factory=lambda: SerializedNodeCommands.NodeUUID(str(uuid4())))
451
461
 
452
462
 
@@ -986,82 +996,6 @@ class RemoveNodeFromNodeGroupResultFailure(ResultPayloadFailure):
986
996
  """
987
997
 
988
998
 
989
- @dataclass
990
- @PayloadRegistry.register
991
- class CreateNodeGroupRequest(RequestPayload):
992
- """Create a new NodeGroup node.
993
-
994
- Use when: Need to create a new NodeGroup for organizing nodes, building workflows
995
- with grouped nodes programmatically.
996
-
997
- Args:
998
- node_group_name: Desired name for the NodeGroup node (None for auto-generated)
999
- flow_name: Optional flow name to create the NodeGroup in (None for current context flow)
1000
- metadata: Initial metadata for the NodeGroup (position, display properties)
1001
-
1002
- Results: CreateNodeGroupResultSuccess (with assigned name) | CreateNodeGroupResultFailure (creation failed)
1003
- """
1004
-
1005
- node_group_name: str | None = None
1006
- flow_name: str | None = None
1007
- metadata: dict | None = None
1008
- node_names_to_add: list[str] = field(default_factory=list)
1009
-
1010
-
1011
- @dataclass
1012
- @PayloadRegistry.register
1013
- class CreateNodeGroupResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
1014
- """NodeGroup created successfully. NodeGroup is now available for adding nodes.
1015
-
1016
- Args:
1017
- node_group_name: Final assigned name (may differ from requested)
1018
- """
1019
-
1020
- node_group_name: str
1021
-
1022
-
1023
- @dataclass
1024
- @PayloadRegistry.register
1025
- class CreateNodeGroupResultFailure(ResultPayloadFailure):
1026
- """NodeGroup creation failed.
1027
-
1028
- Common causes: flow not found, no current context, instantiation errors,
1029
- NodeGroup node type not available.
1030
- """
1031
-
1032
-
1033
- @dataclass
1034
- @PayloadRegistry.register
1035
- class DeleteNodeGroupRequest(RequestPayload):
1036
- """Delete a NodeGroup node.
1037
-
1038
- Use when: Removing obsolete NodeGroups, cleaning up workflow structure,
1039
- implementing undo. Handles cascading cleanup of the NodeGroup node.
1040
-
1041
- Args:
1042
- node_group_name: Name of the NodeGroup node to delete
1043
-
1044
- Results: DeleteNodeGroupResultSuccess | DeleteNodeGroupResultFailure (NodeGroup not found, deletion failed)
1045
- """
1046
-
1047
- node_group_name: str
1048
-
1049
-
1050
- @dataclass
1051
- @PayloadRegistry.register
1052
- class DeleteNodeGroupResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
1053
- """NodeGroup deleted successfully. NodeGroup node removed from workflow."""
1054
-
1055
-
1056
- @dataclass
1057
- @PayloadRegistry.register
1058
- class DeleteNodeGroupResultFailure(ResultPayloadFailure):
1059
- """NodeGroup deletion failed.
1060
-
1061
- Common causes: NodeGroup not found, deletion cleanup failed, node is not a NodeGroup.
1062
- """
1063
-
1064
-
1065
999
  @dataclass
1066
1000
  @PayloadRegistry.register
1067
1001
  class MoveNodeToNewFlowRequest(RequestPayload):
@@ -17,11 +17,11 @@ from griptape_nodes.exe_types.core_types import (
17
17
  ParameterTypeBuiltin,
18
18
  )
19
19
  from griptape_nodes.exe_types.flow import ControlFlow
20
+ from griptape_nodes.exe_types.node_groups import SubflowNodeGroup
20
21
  from griptape_nodes.exe_types.node_types import (
21
22
  BaseNode,
22
23
  ErrorProxyNode,
23
24
  NodeDependencies,
24
- NodeGroupNode,
25
25
  NodeResolutionState,
26
26
  StartNode,
27
27
  )
@@ -123,7 +123,6 @@ from griptape_nodes.retained_mode.events.flow_events import (
123
123
  SetFlowMetadataResultSuccess,
124
124
  )
125
125
  from griptape_nodes.retained_mode.events.node_events import (
126
- CreateNodeGroupRequest,
127
126
  CreateNodeRequest,
128
127
  DeleteNodeRequest,
129
128
  DeleteNodeResultFailure,
@@ -594,22 +593,6 @@ class FlowManager:
594
593
 
595
594
  # Let this Flow assume the Current Context while we delete everything within it.
596
595
  with GriptapeNodes.ContextManager().flow(flow=flow):
597
- # Delete all child nodes in this Flow.
598
- list_nodes_request = ListNodesInFlowRequest()
599
- list_nodes_result = GriptapeNodes.handle_request(list_nodes_request)
600
- if not isinstance(list_nodes_result, ListNodesInFlowResultSuccess):
601
- details = f"Attempted to delete Flow '{flow.name}', but failed while attempting to get the list of Nodes owned by this Flow."
602
- result = DeleteFlowResultFailure(result_details=details)
603
- return result
604
- node_names = list_nodes_result.node_names
605
- for node_name in node_names:
606
- delete_node_request = DeleteNodeRequest(node_name=node_name)
607
- delete_node_result = GriptapeNodes.handle_request(delete_node_request)
608
- if isinstance(delete_node_result, DeleteNodeResultFailure):
609
- details = f"Attempted to delete Flow '{flow.name}', but failed while attempting to delete child Node '{node_name}'."
610
- result = DeleteFlowResultFailure(result_details=details)
611
- return result
612
-
613
596
  # Delete all child Flows of this Flow.
614
597
  # Note: We use ListFlowsInCurrentContextRequest here instead of ListFlowsInFlowRequest(parent_flow_name=None)
615
598
  # because None in ListFlowsInFlowRequest means "get canvas/top-level flows". We want the flows in the
@@ -632,12 +615,27 @@ class FlowManager:
632
615
  return result
633
616
  with GriptapeNodes.ContextManager().flow(flow=child_flow):
634
617
  # Delete them.
635
- delete_flow_request = DeleteFlowRequest()
618
+ delete_flow_request = DeleteFlowRequest(flow_name=child_flow_name)
636
619
  delete_flow_result = GriptapeNodes.handle_request(delete_flow_request)
637
620
  if isinstance(delete_flow_result, DeleteFlowResultFailure):
638
621
  details = f"Attempted to delete Flow '{flow.name}', but failed while attempting to delete child Flow '{child_flow.name}'."
639
622
  result = DeleteFlowResultFailure(result_details=details)
640
623
  return result
624
+ # Delete all child nodes in this Flow.
625
+ list_nodes_request = ListNodesInFlowRequest()
626
+ list_nodes_result = GriptapeNodes.handle_request(list_nodes_request)
627
+ if not isinstance(list_nodes_result, ListNodesInFlowResultSuccess):
628
+ details = f"Attempted to delete Flow '{flow.name}', but failed while attempting to get the list of Nodes owned by this Flow."
629
+ result = DeleteFlowResultFailure(result_details=details)
630
+ return result
631
+ node_names = list_nodes_result.node_names
632
+ for node_name in node_names:
633
+ delete_node_request = DeleteNodeRequest(node_name=node_name)
634
+ delete_node_result = GriptapeNodes.handle_request(delete_node_request)
635
+ if isinstance(delete_node_result, DeleteNodeResultFailure):
636
+ details = f"Attempted to delete Flow '{flow.name}', but failed while attempting to delete child Node '{node_name}'."
637
+ result = DeleteFlowResultFailure(result_details=details)
638
+ return result
641
639
 
642
640
  # If we've made it this far, we have deleted all the children Flows and their nodes.
643
641
  # Remove the flow from our map.
@@ -917,8 +915,8 @@ class FlowManager:
917
915
  details = f"Deleted the previous connection from '{old_source_node_name}.{old_source_param_name}' to '{old_target_node_name}.{old_target_param_name}' to make room for the new connection."
918
916
  try:
919
917
  # Actually create the Connection.
920
- if (isinstance(source_node, NodeGroupNode) and target_node.parent_group == source_node) or (
921
- isinstance(target_node, NodeGroupNode) and source_node.parent_group == target_node
918
+ if (isinstance(source_node, SubflowNodeGroup) and target_node.parent_group == source_node) or (
919
+ isinstance(target_node, SubflowNodeGroup) and source_node.parent_group == target_node
922
920
  ):
923
921
  # Here we're checking if it's an internal connection. (from the NodeGroup to a node within it.)
924
922
  # If that's true, we set that automatically.
@@ -976,7 +974,7 @@ class FlowManager:
976
974
  # If source is in a group, this is an outgoing external connection
977
975
  if (
978
976
  source_parent is not None
979
- and isinstance(source_parent, NodeGroupNode)
977
+ and isinstance(source_parent, SubflowNodeGroup)
980
978
  and source_parent not in (target_parent, target_node)
981
979
  ):
982
980
  success = source_parent.map_external_connection(
@@ -992,7 +990,7 @@ class FlowManager:
992
990
  # If target is in a group, this is an incoming external connection
993
991
  if (
994
992
  target_parent is not None
995
- and isinstance(target_parent, NodeGroupNode)
993
+ and isinstance(target_parent, SubflowNodeGroup)
996
994
  and target_parent not in (source_parent, source_node)
997
995
  ):
998
996
  success = target_parent.map_external_connection(
@@ -1291,15 +1289,15 @@ class FlowManager:
1291
1289
  if isinstance(node_connections_dict, PackageNodesAsSerializedFlowResultFailure):
1292
1290
  return node_connections_dict
1293
1291
 
1294
- # Step 8: Retrieve NodeGroupNode if node_group_name was provided
1295
- node_group_node: NodeGroupNode | None = None
1292
+ # Step 8: Retrieve SubflowNodeGroup if node_group_name was provided
1293
+ node_group_node: SubflowNodeGroup | None = None
1296
1294
  if request.node_group_name:
1297
1295
  try:
1298
1296
  node = GriptapeNodes.NodeManager().get_node_by_name(request.node_group_name)
1299
- if isinstance(node, NodeGroupNode):
1297
+ if isinstance(node, SubflowNodeGroup):
1300
1298
  node_group_node = node
1301
1299
  except Exception as e:
1302
- logger.debug("Failed to retrieve NodeGroupNode '%s': %s", request.node_group_name, e)
1300
+ logger.debug("Failed to retrieve SubflowNodeGroup '%s': %s", request.node_group_name, e)
1303
1301
 
1304
1302
  # Step 9: Create start node with parameters for external incoming connections
1305
1303
  start_node_result = self._create_multi_node_start_node_with_connections(
@@ -1509,7 +1507,7 @@ class FlowManager:
1509
1507
  """
1510
1508
  # Serialize each node using shared unique_parameter_uuid_to_values dictionary for deduplication
1511
1509
  serialized_node_commands = []
1512
- serialized_node_group_commands = [] # NodeGroupNodes must be added LAST
1510
+ serialized_node_group_commands = [] # SubflowNodeGroups must be added LAST
1513
1511
 
1514
1512
  for node in nodes_to_package:
1515
1513
  # Serialize this node using shared dictionaries for value deduplication
@@ -1527,16 +1525,14 @@ class FlowManager:
1527
1525
 
1528
1526
  # Populate the shared node_name_to_uuid mapping
1529
1527
  create_cmd = serialize_result.serialized_node_commands.create_node_command
1530
- # Get the node name from the CreateNodeGroupRequest command if necessary.
1531
- node_name = (
1532
- create_cmd.node_group_name if isinstance(create_cmd, CreateNodeGroupRequest) else create_cmd.node_name
1533
- )
1528
+ # Get the node name from the CreateNodeRequest command.
1529
+ node_name = create_cmd.node_name
1534
1530
  if node_name is not None:
1535
1531
  node_name_to_uuid[node_name] = serialize_result.serialized_node_commands.node_uuid
1536
1532
 
1537
- # NodeGroupNodes must be serialized LAST because CreateNodeGroupRequest references child node names
1533
+ # SubflowNodeGroups must be serialized LAST because they reference child node names via node_names_to_add
1538
1534
  # If we deserialize a NodeGroup before its children, the child nodes won't exist yet
1539
- if isinstance(node, NodeGroupNode):
1535
+ if isinstance(node, SubflowNodeGroup):
1540
1536
  serialized_node_group_commands.append(serialize_result.serialized_node_commands)
1541
1537
  else:
1542
1538
  serialized_node_commands.append(serialize_result.serialized_node_commands)
@@ -1547,13 +1543,13 @@ class FlowManager:
1547
1543
  serialize_result.set_parameter_value_commands
1548
1544
  )
1549
1545
 
1550
- # Update NodeGroupNode commands to use UUIDs instead of names in node_names_to_add
1546
+ # Update SubflowNodeGroup commands to use UUIDs instead of names in node_names_to_add
1551
1547
  # This allows workflow generation to directly look up variable names from UUIDs
1552
1548
 
1553
1549
  for node_group_command in serialized_node_group_commands:
1554
1550
  create_cmd = node_group_command.create_node_command
1555
1551
 
1556
- if isinstance(create_cmd, CreateNodeGroupRequest) and create_cmd.node_names_to_add:
1552
+ if create_cmd.node_names_to_add:
1557
1553
  node_uuids = []
1558
1554
  for child_node_name in create_cmd.node_names_to_add:
1559
1555
  if child_node_name in node_name_to_uuid:
@@ -1648,12 +1644,8 @@ class FlowManager:
1648
1644
  for serialized_node_command in serialized_package_nodes:
1649
1645
  # We need to get the create commoand.
1650
1646
  create_cmd = serialized_node_command.create_node_command
1651
- # In a CreateNodeGroupRequest, the node_name is the node_group_name, so we need to add both.
1652
- cmd_node_name = (
1653
- create_cmd.node_group_name
1654
- if isinstance(create_cmd, CreateNodeGroupRequest)
1655
- else create_cmd.node_name
1656
- )
1647
+ # Get the node name from CreateNodeRequest
1648
+ cmd_node_name = create_cmd.node_name
1657
1649
  if cmd_node_name == package_node.name:
1658
1650
  serialized_node = serialized_node_command
1659
1651
  break
@@ -2048,7 +2040,7 @@ class FlowManager:
2048
2040
  external_connections_dict: dict[
2049
2041
  str, ConnectionAnalysis
2050
2042
  ], # Contains EXTERNAL connections only - used to determine which parameters need start node inputs
2051
- node_group_node: NodeGroupNode | None = None,
2043
+ node_group_node: SubflowNodeGroup | None = None,
2052
2044
  ) -> PackagingStartNodeResult | PackageNodesAsSerializedFlowResultFailure:
2053
2045
  """Create start node commands and connections for external incoming connections."""
2054
2046
  # Generate UUID and name for start node
@@ -2123,7 +2115,7 @@ class FlowManager:
2123
2115
  # Add control connections to the same list as data connections
2124
2116
  start_to_package_connections.extend(control_connections)
2125
2117
 
2126
- # Set parameter values from NodeGroupNode if provided
2118
+ # Set parameter values from SubflowNodeGroup if provided
2127
2119
  if node_group_node is not None:
2128
2120
  self._apply_node_group_parameters_to_start_node(
2129
2121
  node_group_node=node_group_node,
@@ -2156,21 +2148,21 @@ class FlowManager:
2156
2148
 
2157
2149
  def _apply_node_group_parameters_to_start_node( # noqa: PLR0913
2158
2150
  self,
2159
- node_group_node: NodeGroupNode,
2151
+ node_group_node: SubflowNodeGroup,
2160
2152
  start_node_library_name: str,
2161
2153
  start_node_type: str,
2162
2154
  start_node_parameter_value_commands: list[SerializedNodeCommands.IndirectSetParameterValueCommand],
2163
2155
  unique_parameter_uuid_to_values: dict[SerializedNodeCommands.UniqueParameterValueUUID, Any],
2164
2156
  serialized_parameter_value_tracker: SerializedParameterValueTracker,
2165
2157
  ) -> None:
2166
- """Apply parameter values from NodeGroupNode to the StartFlow node.
2158
+ """Apply parameter values from SubflowNodeGroup to the StartFlow node.
2167
2159
 
2168
- This method reads the execution environment metadata from the NodeGroupNode,
2160
+ This method reads the execution environment metadata from the SubflowNodeGroup,
2169
2161
  extracts parameter values for the specified StartFlow node type, and creates
2170
2162
  set parameter value commands for those parameters.
2171
2163
 
2172
2164
  Args:
2173
- node_group_node: The NodeGroupNode containing parameter values
2165
+ node_group_node: The SubflowNodeGroup containing parameter values
2174
2166
  start_node_library_name: Name of the library containing the StartFlow node
2175
2167
  start_node_type: Type of the StartFlow node
2176
2168
  start_node_parameter_value_commands: List to append parameter value commands to
@@ -2178,28 +2170,28 @@ class FlowManager:
2178
2170
  serialized_parameter_value_tracker: Tracker for serialized parameter values
2179
2171
 
2180
2172
  Raises:
2181
- ValueError: If required metadata is missing from NodeGroupNode
2173
+ ValueError: If required metadata is missing from SubflowNodeGroup
2182
2174
  """
2183
- # Get execution environment metadata from NodeGroupNode
2175
+ # Get execution environment metadata from SubflowNodeGroup
2184
2176
  if not node_group_node.metadata:
2185
- msg = f"NodeGroupNode '{node_group_node.name}' is missing metadata. Cannot apply parameters to StartFlow node."
2177
+ msg = f"SubflowNodeGroup '{node_group_node.name}' is missing metadata. Cannot apply parameters to StartFlow node."
2186
2178
  raise ValueError(msg)
2187
2179
 
2188
2180
  execution_env_metadata = node_group_node.metadata.get("execution_environment")
2189
2181
  if not execution_env_metadata:
2190
- msg = f"NodeGroupNode '{node_group_node.name}' metadata is missing 'execution_environment'. Cannot apply parameters to StartFlow node."
2182
+ msg = f"SubflowNodeGroup '{node_group_node.name}' metadata is missing 'execution_environment'. Cannot apply parameters to StartFlow node."
2191
2183
  raise ValueError(msg)
2192
2184
 
2193
2185
  # Find the metadata for the current library
2194
2186
  library_metadata = execution_env_metadata.get(start_node_library_name)
2195
2187
  if library_metadata is None:
2196
- msg = f"NodeGroupNode '{node_group_node.name}' metadata does not contain library '{start_node_library_name}'. Available libraries: {list(execution_env_metadata.keys())}"
2188
+ msg = f"SubflowNodeGroup '{node_group_node.name}' metadata does not contain library '{start_node_library_name}'. Available libraries: {list(execution_env_metadata.keys())}"
2197
2189
  raise ValueError(msg)
2198
2190
 
2199
2191
  # Verify this is the correct StartFlow node type
2200
2192
  registered_start_flow_node = library_metadata.get("start_flow_node")
2201
2193
  if registered_start_flow_node != start_node_type:
2202
- msg = f"NodeGroupNode '{node_group_node.name}' has mismatched StartFlow node type. Expected '{start_node_type}', but metadata has '{registered_start_flow_node}'"
2194
+ msg = f"SubflowNodeGroup '{node_group_node.name}' has mismatched StartFlow node type. Expected '{start_node_type}', but metadata has '{registered_start_flow_node}'"
2203
2195
  raise ValueError(msg)
2204
2196
 
2205
2197
  # Get the list of parameter names that belong to this StartFlow node
@@ -2207,15 +2199,15 @@ class FlowManager:
2207
2199
  if not parameter_names:
2208
2200
  # This is not an error - it's valid for a StartFlow node to have no parameters
2209
2201
  logger.debug(
2210
- "NodeGroupNode '%s' has no parameters registered for StartFlow node '%s'",
2202
+ "SubflowNodeGroup '%s' has no parameters registered for StartFlow node '%s'",
2211
2203
  node_group_node.name,
2212
2204
  start_node_type,
2213
2205
  )
2214
2206
  return
2215
2207
 
2216
- # For each parameter, get its value from the NodeGroupNode and create a set value command
2208
+ # For each parameter, get its value from the SubflowNodeGroup and create a set value command
2217
2209
  for prefixed_param_name in parameter_names:
2218
- # Get the value from the NodeGroupNode parameter
2210
+ # Get the value from the SubflowNodeGroup parameter
2219
2211
  param_value = node_group_node.get_parameter_value(param_name=prefixed_param_name)
2220
2212
 
2221
2213
  # Skip if no value is set
@@ -2621,7 +2613,7 @@ class FlowManager:
2621
2613
  2. A control node with no incoming control connections, OR
2622
2614
  3. A data node with no outgoing connections
2623
2615
 
2624
- Nodes that are children of NodeGroupNodes are excluded.
2616
+ Nodes that are children of SubflowNodeGroups are excluded.
2625
2617
 
2626
2618
  Args:
2627
2619
  flow: The flow to search for start nodes
@@ -2988,9 +2980,6 @@ class FlowManager:
2988
2980
  # Collect node types from all nodes in this flow
2989
2981
  for node_cmd in serialized_node_commands:
2990
2982
  create_cmd = node_cmd.create_node_command
2991
- # Skip NodeGroupNode as it doesn't have node_type/specific_library_name
2992
- if isinstance(create_cmd, CreateNodeGroupRequest):
2993
- continue
2994
2983
  node_type = create_cmd.node_type
2995
2984
  library_name = create_cmd.specific_library_name
2996
2985
  if library_name is None:
@@ -3127,7 +3116,7 @@ class FlowManager:
3127
3116
  create_flow_request = None
3128
3117
 
3129
3118
  serialized_node_commands = []
3130
- serialized_node_group_commands = [] # NodeGroupNodes must be added LAST
3119
+ serialized_node_group_commands = [] # SubflowNodeGroups must be added LAST
3131
3120
  set_parameter_value_commands_per_node = {} # Maps a node UUID to a list of set parameter value commands
3132
3121
  set_lock_commands_per_node = {} # Maps a node UUID to a set Lock command, if it exists.
3133
3122
 
@@ -3164,9 +3153,9 @@ class FlowManager:
3164
3153
  # Store the serialized node's UUID for correlation to connections and setting parameter values later.
3165
3154
  node_name_to_uuid[node_name] = serialized_node.node_uuid
3166
3155
 
3167
- # NodeGroupNodes must be serialized LAST because CreateNodeGroupRequest references child node names
3156
+ # SubflowNodeGroups must be serialized LAST because CreateNodeGroupRequest references child node names
3168
3157
  # If we deserialize a NodeGroup before its children, the child nodes won't exist yet
3169
- if isinstance(node, NodeGroupNode):
3158
+ if isinstance(node, SubflowNodeGroup):
3170
3159
  serialized_node_group_commands.append(serialized_node)
3171
3160
  else:
3172
3161
  serialized_node_commands.append(serialized_node)
@@ -3239,7 +3228,7 @@ class FlowManager:
3239
3228
  # This ensures child nodes exist before their parent NodeGroups are created during deserialization
3240
3229
  serialized_node_commands.extend(serialized_node_group_commands)
3241
3230
 
3242
- # Update NodeGroupNode commands to use UUIDs instead of names in node_names_to_add
3231
+ # Update SubflowNodeGroup commands to use UUIDs instead of names in node_names_to_add
3243
3232
  # This allows workflow generation to directly look up variable names from UUIDs
3244
3233
  # Build a complete node name to UUID map including nodes from all subflows
3245
3234
  complete_node_name_to_uuid = dict(node_name_to_uuid) # Start with current flow's nodes
@@ -3250,10 +3239,8 @@ class FlowManager:
3250
3239
  for node_cmd in subflow_cmd.serialized_node_commands:
3251
3240
  # Extract node name from the create command
3252
3241
  create_cmd = node_cmd.create_node_command
3253
- if isinstance(create_cmd, CreateNodeRequest) and create_cmd.node_name:
3242
+ if create_cmd.node_name:
3254
3243
  complete_node_name_to_uuid[create_cmd.node_name] = node_cmd.node_uuid
3255
- elif isinstance(create_cmd, CreateNodeGroupRequest) and create_cmd.node_group_name:
3256
- complete_node_name_to_uuid[create_cmd.node_group_name] = node_cmd.node_uuid
3257
3244
  # Recursively process nested subflows
3258
3245
  if subflow_cmd.sub_flows_commands:
3259
3246
  collect_subflow_node_uuids(subflow_cmd.sub_flows_commands)
@@ -3263,7 +3250,7 @@ class FlowManager:
3263
3250
  for node_group_command in serialized_node_group_commands:
3264
3251
  create_cmd = node_group_command.create_node_command
3265
3252
 
3266
- if isinstance(create_cmd, CreateNodeGroupRequest) and create_cmd.node_names_to_add:
3253
+ if create_cmd.node_names_to_add:
3267
3254
  # Convert node names to UUIDs using the complete map (including subflows)
3268
3255
  node_uuids = []
3269
3256
  for child_node_name in create_cmd.node_names_to_add:
@@ -3382,16 +3369,14 @@ class FlowManager:
3382
3369
  node_uuid_to_deserialized_node_result = {}
3383
3370
  node_name_mappings = {}
3384
3371
  for serialized_node in request.serialized_flow_commands.serialized_node_commands:
3385
- # Get the node name from the CreateNodeGroupRequest command if necessary
3372
+ # Get the node name from the CreateNodeRequest command
3386
3373
  create_cmd = serialized_node.create_node_command
3387
- original_node_name = (
3388
- create_cmd.node_group_name if isinstance(create_cmd, CreateNodeGroupRequest) else create_cmd.node_name
3389
- )
3374
+ original_node_name = create_cmd.node_name
3390
3375
 
3391
- # For NodeGroupNodes, remap node_names_to_add from UUIDs to actual node names
3376
+ # For SubflowNodeGroups, remap node_names_to_add from UUIDs to actual node names
3392
3377
  # Create a copy to avoid mutating the original serialized data
3393
3378
  serialized_node_for_deserialization = serialized_node
3394
- if isinstance(create_cmd, CreateNodeGroupRequest) and create_cmd.node_names_to_add:
3379
+ if create_cmd.node_names_to_add:
3395
3380
  # Use list comprehension to remap UUIDs to deserialized node names
3396
3381
  remapped_names = [
3397
3382
  node_uuid_to_deserialized_node_result[node_uuid].node_name
@@ -3399,10 +3384,17 @@ class FlowManager:
3399
3384
  if node_uuid in node_uuid_to_deserialized_node_result
3400
3385
  ]
3401
3386
  # Create a copy of the command with remapped names instead of mutating original
3402
- create_cmd_copy = CreateNodeGroupRequest(
3403
- node_group_name=create_cmd.node_group_name,
3387
+ create_cmd_copy = CreateNodeRequest(
3388
+ node_type=create_cmd.node_type,
3389
+ specific_library_name=create_cmd.specific_library_name,
3390
+ node_name=create_cmd.node_name,
3404
3391
  node_names_to_add=remapped_names,
3392
+ override_parent_flow_name=create_cmd.override_parent_flow_name,
3405
3393
  metadata=create_cmd.metadata,
3394
+ resolution=create_cmd.resolution,
3395
+ initial_setup=create_cmd.initial_setup,
3396
+ set_as_new_context=create_cmd.set_as_new_context,
3397
+ create_error_proxy_on_failure=create_cmd.create_error_proxy_on_failure,
3406
3398
  )
3407
3399
  # Create a copy of serialized_node with the new command
3408
3400
  serialized_node_for_deserialization = SerializedNodeCommands(
@@ -3411,6 +3403,7 @@ class FlowManager:
3411
3403
  element_modification_commands=serialized_node.element_modification_commands,
3412
3404
  node_dependencies=serialized_node.node_dependencies,
3413
3405
  lock_node_command=serialized_node.lock_node_command,
3406
+ is_node_group=serialized_node.is_node_group,
3414
3407
  )
3415
3408
 
3416
3409
  deserialize_node_request = DeserializeNodeFromCommandsRequest(
@@ -3837,8 +3830,8 @@ class FlowManager:
3837
3830
  control_nodes = []
3838
3831
  cn_mgr = self.get_connections()
3839
3832
  for node in all_nodes:
3840
- # Skip nodes that are children of a NodeGroupNode - they should not be start nodes
3841
- if node.parent_group is not None and isinstance(node.parent_group, NodeGroupNode):
3833
+ # Skip nodes that are children of a SubflowNodeGroup - they should not be start nodes
3834
+ if node.parent_group is not None and isinstance(node.parent_group, SubflowNodeGroup):
3842
3835
  continue
3843
3836
 
3844
3837
  # if it's a start node, start here! Return the first one!