griptape-nodes 0.55.1__py3-none-any.whl → 0.56.0__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.
Files changed (35) hide show
  1. griptape_nodes/cli/commands/init.py +88 -0
  2. griptape_nodes/cli/commands/models.py +2 -0
  3. griptape_nodes/cli/shared.py +1 -0
  4. griptape_nodes/exe_types/core_types.py +104 -0
  5. griptape_nodes/exe_types/node_types.py +9 -12
  6. griptape_nodes/machines/control_flow.py +10 -0
  7. griptape_nodes/machines/dag_builder.py +21 -2
  8. griptape_nodes/machines/parallel_resolution.py +25 -10
  9. griptape_nodes/node_library/workflow_registry.py +73 -3
  10. griptape_nodes/retained_mode/events/execution_events.py +12 -2
  11. griptape_nodes/retained_mode/events/flow_events.py +58 -0
  12. griptape_nodes/retained_mode/events/resource_events.py +290 -0
  13. griptape_nodes/retained_mode/events/workflow_events.py +57 -2
  14. griptape_nodes/retained_mode/griptape_nodes.py +9 -1
  15. griptape_nodes/retained_mode/managers/flow_manager.py +678 -12
  16. griptape_nodes/retained_mode/managers/library_manager.py +13 -19
  17. griptape_nodes/retained_mode/managers/model_manager.py +184 -83
  18. griptape_nodes/retained_mode/managers/node_manager.py +3 -3
  19. griptape_nodes/retained_mode/managers/os_manager.py +118 -1
  20. griptape_nodes/retained_mode/managers/resource_components/__init__.py +1 -0
  21. griptape_nodes/retained_mode/managers/resource_components/capability_field.py +41 -0
  22. griptape_nodes/retained_mode/managers/resource_components/comparator.py +18 -0
  23. griptape_nodes/retained_mode/managers/resource_components/resource_instance.py +236 -0
  24. griptape_nodes/retained_mode/managers/resource_components/resource_type.py +79 -0
  25. griptape_nodes/retained_mode/managers/resource_manager.py +306 -0
  26. griptape_nodes/retained_mode/managers/resource_types/__init__.py +1 -0
  27. griptape_nodes/retained_mode/managers/resource_types/cpu_resource.py +108 -0
  28. griptape_nodes/retained_mode/managers/resource_types/os_resource.py +87 -0
  29. griptape_nodes/retained_mode/managers/settings.py +5 -0
  30. griptape_nodes/retained_mode/managers/sync_manager.py +10 -3
  31. griptape_nodes/retained_mode/managers/workflow_manager.py +359 -261
  32. {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.0.dist-info}/METADATA +1 -1
  33. {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.0.dist-info}/RECORD +35 -25
  34. {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.0.dist-info}/WHEEL +1 -1
  35. {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.0.dist-info}/entry_points.txt +0 -0
@@ -3,7 +3,8 @@ from __future__ import annotations
3
3
  import logging
4
4
  from enum import StrEnum
5
5
  from queue import Queue
6
- from typing import TYPE_CHECKING, NamedTuple, cast
6
+ from typing import TYPE_CHECKING, Any, NamedTuple, cast
7
+ from uuid import uuid4
7
8
 
8
9
  from griptape_nodes.exe_types.connections import Connections
9
10
  from griptape_nodes.exe_types.core_types import (
@@ -19,6 +20,7 @@ from griptape_nodes.machines.control_flow import CompleteState, ControlFlowMachi
19
20
  from griptape_nodes.machines.dag_builder import DagBuilder
20
21
  from griptape_nodes.machines.parallel_resolution import ParallelResolutionMachine
21
22
  from griptape_nodes.machines.sequential_resolution import SequentialResolutionMachine
23
+ from griptape_nodes.node_library.library_registry import LibraryNameAndVersion, LibraryRegistry
22
24
  from griptape_nodes.retained_mode.events.base_events import (
23
25
  ExecutionEvent,
24
26
  ExecutionGriptapeNodeEvent,
@@ -33,6 +35,10 @@ from griptape_nodes.retained_mode.events.connection_events import (
33
35
  DeleteConnectionRequest,
34
36
  DeleteConnectionResultFailure,
35
37
  DeleteConnectionResultSuccess,
38
+ IncomingConnection,
39
+ ListConnectionsForNodeRequest,
40
+ ListConnectionsForNodeResultSuccess,
41
+ OutgoingConnection,
36
42
  )
37
43
  from griptape_nodes.retained_mode.events.execution_events import (
38
44
  CancelFlowRequest,
@@ -48,6 +54,7 @@ from griptape_nodes.retained_mode.events.execution_events import (
48
54
  GetIsFlowRunningRequest,
49
55
  GetIsFlowRunningResultFailure,
50
56
  GetIsFlowRunningResultSuccess,
57
+ InvolvedNodesEvent,
51
58
  SingleExecutionStepRequest,
52
59
  SingleExecutionStepResultFailure,
53
60
  SingleExecutionStepResultSuccess,
@@ -88,6 +95,9 @@ from griptape_nodes.retained_mode.events.flow_events import (
88
95
  ListNodesInFlowRequest,
89
96
  ListNodesInFlowResultFailure,
90
97
  ListNodesInFlowResultSuccess,
98
+ PackageNodeAsSerializedFlowRequest,
99
+ PackageNodeAsSerializedFlowResultFailure,
100
+ PackageNodeAsSerializedFlowResultSuccess,
91
101
  SerializedFlowCommands,
92
102
  SerializeFlowToCommandsRequest,
93
103
  SerializeFlowToCommandsResultFailure,
@@ -97,14 +107,18 @@ from griptape_nodes.retained_mode.events.flow_events import (
97
107
  SetFlowMetadataResultSuccess,
98
108
  )
99
109
  from griptape_nodes.retained_mode.events.node_events import (
110
+ CreateNodeRequest,
100
111
  DeleteNodeRequest,
101
112
  DeleteNodeResultFailure,
102
113
  DeserializeNodeFromCommandsRequest,
114
+ SerializedNodeCommands,
103
115
  SerializedParameterValueTracker,
104
116
  SerializeNodeToCommandsRequest,
105
117
  SerializeNodeToCommandsResultSuccess,
106
118
  )
107
119
  from griptape_nodes.retained_mode.events.parameter_events import (
120
+ AddParameterToNodeRequest,
121
+ AlterParameterDetailsRequest,
108
122
  SetParameterValueRequest,
109
123
  )
110
124
  from griptape_nodes.retained_mode.events.validation_events import (
@@ -121,6 +135,7 @@ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
121
135
  if TYPE_CHECKING:
122
136
  from griptape_nodes.retained_mode.events.base_events import ResultPayload
123
137
  from griptape_nodes.retained_mode.managers.event_manager import EventManager
138
+ from griptape_nodes.retained_mode.managers.workflow_manager import WorkflowShapeNodes
124
139
 
125
140
  logger = logging.getLogger("griptape_nodes")
126
141
 
@@ -138,6 +153,39 @@ class QueueItem(NamedTuple):
138
153
  dag_execution_type: DagExecutionType
139
154
 
140
155
 
156
+ class ConnectionAnalysis(NamedTuple):
157
+ """Analysis of connections separated by type (data vs control) when packaging nodes."""
158
+
159
+ incoming_data_connections: list[IncomingConnection]
160
+ incoming_control_connections: list[IncomingConnection]
161
+ outgoing_data_connections: list[OutgoingConnection]
162
+ outgoing_control_connections: list[OutgoingConnection]
163
+
164
+
165
+ class PackageNodeInfo(NamedTuple):
166
+ """Information about the node being packaged."""
167
+
168
+ package_node: BaseNode
169
+ package_flow_name: str
170
+
171
+
172
+ class PackagingStartNodeResult(NamedTuple):
173
+ """Result of creating start node commands and data connections for flow packaging."""
174
+
175
+ start_node_commands: SerializedNodeCommands
176
+ start_to_package_data_connections: list[SerializedFlowCommands.IndirectConnectionSerialization]
177
+ input_shape_data: WorkflowShapeNodes
178
+ start_node_parameter_value_commands: list[SerializedNodeCommands.IndirectSetParameterValueCommand]
179
+
180
+
181
+ class PackagingEndNodeResult(NamedTuple):
182
+ """Result of creating end node commands and data connections for flow packaging."""
183
+
184
+ end_node_commands: SerializedNodeCommands
185
+ package_to_end_data_connections: list[SerializedFlowCommands.IndirectConnectionSerialization]
186
+ output_shape_data: WorkflowShapeNodes
187
+
188
+
141
189
  class FlowManager:
142
190
  _name_to_parent_name: dict[str, str | None]
143
191
  _flow_to_referenced_workflow_name: dict[ControlFlow, str]
@@ -181,6 +229,9 @@ class FlowManager:
181
229
  event_manager.assign_manager_to_request_type(
182
230
  DeserializeFlowFromCommandsRequest, self.on_deserialize_flow_from_commands
183
231
  )
232
+ event_manager.assign_manager_to_request_type(
233
+ PackageNodeAsSerializedFlowRequest, self.on_package_node_as_serialized_flow_request
234
+ )
184
235
  event_manager.assign_manager_to_request_type(FlushParameterChangesRequest, self.on_flush_request)
185
236
 
186
237
  self._name_to_parent_name = {}
@@ -1012,6 +1063,593 @@ class FlowManager:
1012
1063
  result = DeleteConnectionResultSuccess(result_details=details)
1013
1064
  return result
1014
1065
 
1066
+ def on_package_node_as_serialized_flow_request(self, request: PackageNodeAsSerializedFlowRequest) -> ResultPayload: # noqa: PLR0911
1067
+ """Handle request to package a node as a serialized flow.
1068
+
1069
+ Creates a self-contained flow with Start node -> Package node -> End node structure,
1070
+ where artificial start/end nodes match the package node's connections.
1071
+ """
1072
+ # Step 1: Validate package node and flow
1073
+ package_node_info = self._validate_package_node_and_flow(request=request)
1074
+ if isinstance(package_node_info, PackageNodeAsSerializedFlowResultFailure):
1075
+ return package_node_info
1076
+
1077
+ # Step 2: Validate library and get version
1078
+ library_version = self._validate_and_get_library_info(request=request)
1079
+ if isinstance(library_version, PackageNodeAsSerializedFlowResultFailure):
1080
+ return library_version
1081
+
1082
+ # Step 3: Analyze package node connections
1083
+ connection_analysis = self._analyze_package_node_connections(
1084
+ package_node=package_node_info.package_node,
1085
+ node_name=package_node_info.package_node.name,
1086
+ )
1087
+ if isinstance(connection_analysis, PackageNodeAsSerializedFlowResultFailure):
1088
+ return connection_analysis
1089
+
1090
+ # Step 4: Serialize the package node
1091
+ unique_parameter_uuid_to_values = {}
1092
+ serialized_parameter_value_tracker = SerializedParameterValueTracker()
1093
+ serialized_package_result = self._serialize_package_node(
1094
+ node_name=package_node_info.package_node.name,
1095
+ package_node=package_node_info.package_node,
1096
+ unique_parameter_uuid_to_values=unique_parameter_uuid_to_values,
1097
+ serialized_parameter_value_tracker=serialized_parameter_value_tracker,
1098
+ )
1099
+ if isinstance(serialized_package_result, PackageNodeAsSerializedFlowResultFailure):
1100
+ return serialized_package_result
1101
+
1102
+ # Step 5: Create start node commands and data connections
1103
+ start_node_result = self._create_start_node_commands(
1104
+ request=request,
1105
+ incoming_data_connections=connection_analysis.incoming_data_connections,
1106
+ package_node=package_node_info.package_node,
1107
+ package_node_uuid=serialized_package_result.serialized_node_commands.node_uuid,
1108
+ library_version=library_version,
1109
+ unique_parameter_uuid_to_values=unique_parameter_uuid_to_values,
1110
+ serialized_parameter_value_tracker=serialized_parameter_value_tracker,
1111
+ )
1112
+ if isinstance(start_node_result, PackageNodeAsSerializedFlowResultFailure):
1113
+ return start_node_result
1114
+
1115
+ # Step 6: Create end node commands and data connections
1116
+ end_node_result = self._create_end_node_commands(
1117
+ request=request,
1118
+ package_node=package_node_info.package_node,
1119
+ package_node_uuid=serialized_package_result.serialized_node_commands.node_uuid,
1120
+ library_version=library_version,
1121
+ )
1122
+ if isinstance(end_node_result, PackageNodeAsSerializedFlowResultFailure):
1123
+ return end_node_result
1124
+
1125
+ # Step 7a: Create start node control flow connection
1126
+ start_control_connection_result = self._create_start_node_control_connection(
1127
+ entry_control_parameter_name=request.entry_control_parameter_name,
1128
+ start_node_uuid=start_node_result.start_node_commands.node_uuid,
1129
+ package_node_uuid=serialized_package_result.serialized_node_commands.node_uuid,
1130
+ package_node=package_node_info.package_node,
1131
+ )
1132
+ if isinstance(start_control_connection_result, PackageNodeAsSerializedFlowResultFailure):
1133
+ return start_control_connection_result
1134
+
1135
+ start_control_connections = [start_control_connection_result]
1136
+
1137
+ # Use only start control connections for now (end node control connections not implemented yet)
1138
+ control_flow_connections = start_control_connections
1139
+
1140
+ # Step 8: Assemble the complete serialized flow
1141
+ packaged_flow = self._assemble_serialized_flow(
1142
+ serialized_package_result=serialized_package_result,
1143
+ start_node_result=start_node_result,
1144
+ end_node_result=end_node_result,
1145
+ control_flow_connections=control_flow_connections,
1146
+ unique_parameter_uuid_to_values=unique_parameter_uuid_to_values,
1147
+ library_version=library_version,
1148
+ request=request,
1149
+ )
1150
+
1151
+ # Step 9: Build WorkflowShape from collected parameter shape data
1152
+ workflow_shape = GriptapeNodes.WorkflowManager().build_workflow_shape_from_parameter_info(
1153
+ input_node_params=start_node_result.input_shape_data, output_node_params=end_node_result.output_shape_data
1154
+ )
1155
+
1156
+ # Return success result
1157
+ return PackageNodeAsSerializedFlowResultSuccess(
1158
+ result_details=f'Successfully packaged node "{package_node_info.package_node.name}" from flow "{package_node_info.package_flow_name}" as serialized flow with start node type "{request.start_node_type}" and end node type "{request.end_node_type}" from library "{request.start_end_specific_library_name}".',
1159
+ serialized_flow_commands=packaged_flow,
1160
+ workflow_shape=workflow_shape,
1161
+ )
1162
+
1163
+ def _validate_package_node_and_flow( # noqa: PLR0911
1164
+ self, request: PackageNodeAsSerializedFlowRequest
1165
+ ) -> PackageNodeInfo | PackageNodeAsSerializedFlowResultFailure:
1166
+ """Validate and retrieve the package node and its parent flow."""
1167
+ node_name = request.node_name
1168
+ package_node = None
1169
+
1170
+ if node_name is None:
1171
+ # First check if we have a current node
1172
+ if not GriptapeNodes.ContextManager().has_current_node():
1173
+ details = (
1174
+ "Attempted to package node from Current Context. Failed because the Current Context was empty."
1175
+ )
1176
+ return PackageNodeAsSerializedFlowResultFailure(result_details=details)
1177
+
1178
+ # Get the current node from context
1179
+ package_node = GriptapeNodes.ContextManager().get_current_node()
1180
+ node_name = package_node.name
1181
+
1182
+ if package_node is None:
1183
+ try:
1184
+ package_node = GriptapeNodes.NodeManager().get_node_by_name(node_name)
1185
+ except ValueError as err:
1186
+ details = f"Attempted to package node '{node_name}'. Failed because node does not exist. Error: {err}."
1187
+ return PackageNodeAsSerializedFlowResultFailure(result_details=details)
1188
+
1189
+ # Get the flow containing this node using the same pattern
1190
+ package_flow_name = GriptapeNodes.NodeManager().get_node_parent_flow_by_name(node_name)
1191
+ if package_flow_name is None:
1192
+ details = f"Attempted to package node '{node_name}'. Failed because node is not assigned to any flow."
1193
+ return PackageNodeAsSerializedFlowResultFailure(result_details=details)
1194
+
1195
+ try:
1196
+ self.get_flow_by_name(flow_name=package_flow_name)
1197
+ except KeyError as err:
1198
+ details = f"Attempted to package node '{node_name}' from flow '{package_flow_name}'. Failed because flow does not exist. Error: {err}."
1199
+ return PackageNodeAsSerializedFlowResultFailure(result_details=details)
1200
+
1201
+ # Validate entry control parameter if specified
1202
+ if request.entry_control_parameter_name is not None:
1203
+ entry_param = package_node.get_parameter_by_name(request.entry_control_parameter_name)
1204
+ if entry_param is None:
1205
+ details = f"Attempted to package node '{node_name}' with entry control parameter '{request.entry_control_parameter_name}'. Failed because the parameter does not exist on the node."
1206
+ return PackageNodeAsSerializedFlowResultFailure(result_details=details)
1207
+
1208
+ # Verify it's actually a control parameter
1209
+ if ParameterTypeBuiltin.CONTROL_TYPE.value not in entry_param.input_types:
1210
+ details = f"Attempted to package node '{node_name}' with entry control parameter '{request.entry_control_parameter_name}'. Failed because the parameter is not a control type parameter."
1211
+ return PackageNodeAsSerializedFlowResultFailure(result_details=details)
1212
+
1213
+ return PackageNodeInfo(package_node=package_node, package_flow_name=package_flow_name)
1214
+
1215
+ def _validate_and_get_library_info(
1216
+ self, request: PackageNodeAsSerializedFlowRequest
1217
+ ) -> str | PackageNodeAsSerializedFlowResultFailure:
1218
+ """Validate start/end node types exist in library and return library version."""
1219
+ # Early validation - ensure both start and end node types exist in the specified library
1220
+ try:
1221
+ start_end_library = LibraryRegistry.get_library_for_node_type(
1222
+ node_type=request.start_node_type, specific_library_name=request.start_end_specific_library_name
1223
+ )
1224
+ except KeyError as err:
1225
+ details = f"Attempted to package node with start node type '{request.start_node_type}' from library '{request.start_end_specific_library_name}'. Failed because start node type was not found in library. Error: {err}."
1226
+ return PackageNodeAsSerializedFlowResultFailure(result_details=details)
1227
+
1228
+ try:
1229
+ LibraryRegistry.get_library_for_node_type(
1230
+ node_type=request.end_node_type, specific_library_name=request.start_end_specific_library_name
1231
+ )
1232
+ except KeyError as err:
1233
+ details = f"Attempted to package node with end node type '{request.end_node_type}' from library '{request.start_end_specific_library_name}'. Failed because end node type was not found in library. Error: {err}."
1234
+ return PackageNodeAsSerializedFlowResultFailure(result_details=details)
1235
+
1236
+ # Get the actual library version
1237
+ start_end_library_metadata = start_end_library.get_metadata()
1238
+ return start_end_library_metadata.library_version
1239
+
1240
+ def _analyze_package_node_connections(
1241
+ self, package_node: BaseNode, node_name: str
1242
+ ) -> ConnectionAnalysis | PackageNodeAsSerializedFlowResultFailure:
1243
+ """Analyze package node connections and separate control from data connections."""
1244
+ # Get connection details using the efficient approach
1245
+ list_connections_request = ListConnectionsForNodeRequest(node_name=node_name)
1246
+ list_connections_result = GriptapeNodes.NodeManager().on_list_connections_for_node_request(
1247
+ list_connections_request
1248
+ )
1249
+
1250
+ if not isinstance(list_connections_result, ListConnectionsForNodeResultSuccess):
1251
+ details = f"Attempted to analyze connections for package node '{node_name}'. Failed because connection listing failed."
1252
+ return PackageNodeAsSerializedFlowResultFailure(result_details=details)
1253
+
1254
+ # Separate control connections from data connections based on package node's parameter types
1255
+ incoming_data_connections = []
1256
+ incoming_control_connections = []
1257
+ for incoming_conn in list_connections_result.incoming_connections:
1258
+ # Get the package node's parameter to check if it's a control type
1259
+ package_param = package_node.get_parameter_by_name(incoming_conn.target_parameter_name)
1260
+ if package_param and ParameterTypeBuiltin.CONTROL_TYPE.value in package_param.input_types:
1261
+ incoming_control_connections.append(incoming_conn)
1262
+ else:
1263
+ incoming_data_connections.append(incoming_conn)
1264
+
1265
+ outgoing_data_connections = []
1266
+ outgoing_control_connections = []
1267
+ for outgoing_conn in list_connections_result.outgoing_connections:
1268
+ # Get the package node's parameter to check if it's a control type
1269
+ package_param = package_node.get_parameter_by_name(outgoing_conn.source_parameter_name)
1270
+ if package_param and ParameterTypeBuiltin.CONTROL_TYPE.value == package_param.output_type:
1271
+ outgoing_control_connections.append(outgoing_conn)
1272
+ else:
1273
+ outgoing_data_connections.append(outgoing_conn)
1274
+
1275
+ return ConnectionAnalysis(
1276
+ incoming_data_connections=incoming_data_connections,
1277
+ incoming_control_connections=incoming_control_connections,
1278
+ outgoing_data_connections=outgoing_data_connections,
1279
+ outgoing_control_connections=outgoing_control_connections,
1280
+ )
1281
+
1282
+ def _serialize_package_node(
1283
+ self,
1284
+ node_name: str,
1285
+ package_node: BaseNode,
1286
+ unique_parameter_uuid_to_values: dict[SerializedNodeCommands.UniqueParameterValueUUID, Any],
1287
+ serialized_parameter_value_tracker: SerializedParameterValueTracker,
1288
+ ) -> SerializeNodeToCommandsResultSuccess | PackageNodeAsSerializedFlowResultFailure:
1289
+ """Serialize the package node to commands, adding OUTPUT mode to PROPERTY-only parameters."""
1290
+ # Use the provided parameter tracking structures
1291
+
1292
+ # Create serialization request for the package node
1293
+ serialize_node_request = SerializeNodeToCommandsRequest(
1294
+ node_name=node_name,
1295
+ unique_parameter_uuid_to_values=unique_parameter_uuid_to_values,
1296
+ serialized_parameter_value_tracker=serialized_parameter_value_tracker,
1297
+ )
1298
+
1299
+ # Execute the serialization
1300
+ serialize_node_result = GriptapeNodes.handle_request(serialize_node_request)
1301
+ if not isinstance(serialize_node_result, SerializeNodeToCommandsResultSuccess):
1302
+ details = f"Attempted to serialize package node '{node_name}'. Failed because node serialization failed."
1303
+ return PackageNodeAsSerializedFlowResultFailure(result_details=details)
1304
+
1305
+ # Add ALTER parameter commands for PROPERTY-only parameters to enable OUTPUT mode
1306
+ # We need these to emit their values back so that the orchestrator/caller
1307
+ # can reconcile the packaged node's values after it is executed.
1308
+ package_alter_parameter_commands = []
1309
+ for package_param in package_node.parameters:
1310
+ has_output_mode = ParameterMode.OUTPUT in package_param.allowed_modes
1311
+ has_property_mode = ParameterMode.PROPERTY in package_param.allowed_modes
1312
+
1313
+ # If has PROPERTY but not OUTPUT, add ALTER command to enable OUTPUT
1314
+ if has_property_mode and not has_output_mode:
1315
+ alter_param_request = AlterParameterDetailsRequest(
1316
+ parameter_name=package_param.name,
1317
+ node_name=package_node.name,
1318
+ mode_allowed_output=True,
1319
+ )
1320
+ package_alter_parameter_commands.append(alter_param_request)
1321
+
1322
+ # If we have alter parameter commands, append them to the existing element_modification_commands
1323
+ if package_alter_parameter_commands:
1324
+ serialize_node_result.serialized_node_commands.element_modification_commands.extend(
1325
+ package_alter_parameter_commands
1326
+ )
1327
+
1328
+ return serialize_node_result
1329
+
1330
+ def _create_start_node_commands( # noqa: PLR0913
1331
+ self,
1332
+ request: PackageNodeAsSerializedFlowRequest,
1333
+ incoming_data_connections: list[IncomingConnection],
1334
+ package_node: BaseNode,
1335
+ package_node_uuid: SerializedNodeCommands.NodeUUID,
1336
+ library_version: str,
1337
+ unique_parameter_uuid_to_values: dict[SerializedNodeCommands.UniqueParameterValueUUID, Any],
1338
+ serialized_parameter_value_tracker: SerializedParameterValueTracker,
1339
+ ) -> PackagingStartNodeResult | PackageNodeAsSerializedFlowResultFailure:
1340
+ """Create start node commands and connections for incoming data connections."""
1341
+ # Generate UUID and name for start node
1342
+ start_node_uuid = SerializedNodeCommands.NodeUUID(str(uuid4()))
1343
+ start_node_name = f"Start_Package_{package_node.name}"
1344
+
1345
+ # Build start node CreateNodeRequest
1346
+ start_create_node_command = CreateNodeRequest(
1347
+ node_type=request.start_node_type,
1348
+ specific_library_name=request.start_end_specific_library_name,
1349
+ node_name=start_node_name,
1350
+ metadata={},
1351
+ initial_setup=True,
1352
+ create_error_proxy_on_failure=False,
1353
+ )
1354
+
1355
+ # Create library details
1356
+ start_node_library_details = LibraryNameAndVersion(
1357
+ library_name=request.start_end_specific_library_name,
1358
+ library_version=library_version,
1359
+ )
1360
+
1361
+ # Create parameter modification commands and connection mappings for the start node based on incoming DATA connections
1362
+ start_node_parameter_commands = []
1363
+ start_to_package_data_connections = []
1364
+ start_node_parameter_value_commands = []
1365
+ input_shape_data: WorkflowShapeNodes = {}
1366
+
1367
+ for incoming_conn in incoming_data_connections:
1368
+ # Parameter name: use the package node's parameter name
1369
+ param_name = incoming_conn.target_parameter_name
1370
+
1371
+ # Get the source node to determine parameter type
1372
+ try:
1373
+ source_node = GriptapeNodes.NodeManager().get_node_by_name(incoming_conn.source_node_name)
1374
+ except ValueError as err:
1375
+ details = f"Attempted to package node '{package_node.name}'. Failed because source node '{incoming_conn.source_node_name}' from incoming connection could not be found. Error: {err}."
1376
+ return PackageNodeAsSerializedFlowResultFailure(result_details=details)
1377
+
1378
+ # Get the source parameter
1379
+ source_param = source_node.get_parameter_by_name(incoming_conn.source_parameter_name)
1380
+ if not source_param:
1381
+ details = f"Attempted to package node '{package_node.name}'. Failed because source parameter '{incoming_conn.source_parameter_name}' on node '{incoming_conn.source_node_name}' from incoming connection could not be found."
1382
+ return PackageNodeAsSerializedFlowResultFailure(result_details=details)
1383
+
1384
+ # Extract parameter shape info for workflow shape (inputs from external sources)
1385
+ param_shape_info = GriptapeNodes.WorkflowManager().extract_parameter_shape_info(
1386
+ source_param, include_control_params=True
1387
+ )
1388
+ if param_shape_info is not None:
1389
+ if start_node_name not in input_shape_data:
1390
+ input_shape_data[start_node_name] = {}
1391
+ input_shape_data[start_node_name][param_name] = param_shape_info
1392
+
1393
+ # Extract parameter value from source node to set on start node
1394
+ param_value_commands = GriptapeNodes.NodeManager().handle_parameter_value_saving(
1395
+ parameter=source_param,
1396
+ node=source_node,
1397
+ unique_parameter_uuid_to_values=unique_parameter_uuid_to_values,
1398
+ serialized_parameter_value_tracker=serialized_parameter_value_tracker,
1399
+ create_node_request=start_create_node_command,
1400
+ )
1401
+ if param_value_commands is not None:
1402
+ # Modify each command to target the start node parameter instead
1403
+ for param_value_command in param_value_commands:
1404
+ param_value_command.set_parameter_value_command.node_name = start_node_name
1405
+ param_value_command.set_parameter_value_command.parameter_name = param_name
1406
+ start_node_parameter_value_commands.append(param_value_command)
1407
+
1408
+ # Create parameter command for start node
1409
+ add_param_request = AddParameterToNodeRequest(
1410
+ node_name=start_node_name,
1411
+ parameter_name=param_name,
1412
+ # Use the source parameter's output_type as the type for our start node parameter
1413
+ # since we want to match what the original connection was providing
1414
+ type=source_param.output_type,
1415
+ default_value=None,
1416
+ tooltip=f"Parameter {param_name} from packaged flow",
1417
+ initial_setup=True,
1418
+ )
1419
+ start_node_parameter_commands.append(add_param_request)
1420
+
1421
+ # Create connection from start node to package node
1422
+ start_to_package_connection = SerializedFlowCommands.IndirectConnectionSerialization(
1423
+ source_node_uuid=start_node_uuid,
1424
+ source_parameter_name=param_name,
1425
+ target_node_uuid=package_node_uuid,
1426
+ target_parameter_name=param_name,
1427
+ )
1428
+ start_to_package_data_connections.append(start_to_package_connection)
1429
+
1430
+ # Build complete SerializedNodeCommands for start node
1431
+ start_node_commands = SerializedNodeCommands(
1432
+ create_node_command=start_create_node_command,
1433
+ element_modification_commands=start_node_parameter_commands,
1434
+ node_library_details=start_node_library_details,
1435
+ node_uuid=start_node_uuid,
1436
+ )
1437
+
1438
+ return PackagingStartNodeResult(
1439
+ start_node_commands=start_node_commands,
1440
+ start_to_package_data_connections=start_to_package_data_connections,
1441
+ input_shape_data=input_shape_data,
1442
+ start_node_parameter_value_commands=start_node_parameter_value_commands,
1443
+ )
1444
+
1445
+ def _create_end_node_commands(
1446
+ self,
1447
+ request: PackageNodeAsSerializedFlowRequest,
1448
+ package_node: BaseNode,
1449
+ package_node_uuid: SerializedNodeCommands.NodeUUID,
1450
+ library_version: str,
1451
+ ) -> PackagingEndNodeResult | PackageNodeAsSerializedFlowResultFailure:
1452
+ """Create end node commands and connections for ALL package parameters that meet criteria."""
1453
+ # Generate UUID and name for end node
1454
+ end_node_uuid = SerializedNodeCommands.NodeUUID(str(uuid4()))
1455
+ end_node_name = f"End_Package_{package_node.name}"
1456
+
1457
+ # Build end node CreateNodeRequest
1458
+ end_create_node_command = CreateNodeRequest(
1459
+ node_type=request.end_node_type,
1460
+ specific_library_name=request.start_end_specific_library_name,
1461
+ node_name=end_node_name,
1462
+ metadata={},
1463
+ initial_setup=True,
1464
+ create_error_proxy_on_failure=False,
1465
+ )
1466
+
1467
+ # Create library details
1468
+ end_node_library_details = LibraryNameAndVersion(
1469
+ library_name=request.start_end_specific_library_name,
1470
+ library_version=library_version,
1471
+ )
1472
+
1473
+ # Process ALL package node parameters to create end node parameters and connections
1474
+ # Note: PROPERTY-only parameters are guaranteed to have OUTPUT mode after serialization
1475
+ end_node_parameter_commands = []
1476
+ package_to_end_data_connections = []
1477
+ output_shape_data: WorkflowShapeNodes = {}
1478
+
1479
+ for package_param in package_node.parameters:
1480
+ # Only ignore parameters that have ONLY INPUT mode (no OUTPUT or PROPERTY)
1481
+ has_output_mode = ParameterMode.OUTPUT in package_param.allowed_modes
1482
+ has_property_mode = ParameterMode.PROPERTY in package_param.allowed_modes
1483
+
1484
+ # Skip parameters that only have INPUT mode
1485
+ if not has_output_mode and not has_property_mode:
1486
+ continue
1487
+
1488
+ # Create prefixed parameter name for end node to avoid collisions
1489
+ end_param_name = f"{request.output_parameter_prefix}{package_param.name}"
1490
+
1491
+ # Extract parameter shape info for workflow shape (outputs to external consumers)
1492
+ param_shape_info = GriptapeNodes.WorkflowManager().extract_parameter_shape_info(
1493
+ package_param, include_control_params=True
1494
+ )
1495
+ if param_shape_info is not None:
1496
+ if end_node_name not in output_shape_data:
1497
+ output_shape_data[end_node_name] = {}
1498
+ output_shape_data[end_node_name][end_param_name] = param_shape_info
1499
+
1500
+ # Create parameter command for end node
1501
+ add_param_request = AddParameterToNodeRequest(
1502
+ node_name=end_node_name,
1503
+ parameter_name=end_param_name,
1504
+ # Use the package node's output_type as the type for our end node parameter
1505
+ type=package_param.output_type,
1506
+ default_value=None,
1507
+ tooltip=f"Output parameter {package_param.name} from packaged node {package_node.name}",
1508
+ initial_setup=True,
1509
+ )
1510
+ end_node_parameter_commands.append(add_param_request)
1511
+
1512
+ # Create connection from package node to end node
1513
+ package_to_end_connection = SerializedFlowCommands.IndirectConnectionSerialization(
1514
+ source_node_uuid=package_node_uuid,
1515
+ source_parameter_name=package_param.name,
1516
+ target_node_uuid=end_node_uuid,
1517
+ target_parameter_name=end_param_name,
1518
+ )
1519
+ package_to_end_data_connections.append(package_to_end_connection)
1520
+
1521
+ # Build complete SerializedNodeCommands for end node
1522
+ end_node_commands = SerializedNodeCommands(
1523
+ create_node_command=end_create_node_command,
1524
+ element_modification_commands=end_node_parameter_commands,
1525
+ node_library_details=end_node_library_details,
1526
+ node_uuid=end_node_uuid,
1527
+ )
1528
+
1529
+ return PackagingEndNodeResult(
1530
+ end_node_commands=end_node_commands,
1531
+ package_to_end_data_connections=package_to_end_data_connections,
1532
+ output_shape_data=output_shape_data,
1533
+ )
1534
+
1535
+ def _create_start_node_control_connection(
1536
+ self,
1537
+ entry_control_parameter_name: str | None,
1538
+ start_node_uuid: SerializedNodeCommands.NodeUUID,
1539
+ package_node_uuid: SerializedNodeCommands.NodeUUID,
1540
+ package_node: BaseNode,
1541
+ ) -> SerializedFlowCommands.IndirectConnectionSerialization | PackageNodeAsSerializedFlowResultFailure:
1542
+ """Create control flow connection from start node to package node.
1543
+
1544
+ Connects the start node's first control output to the specified or first available package node control input.
1545
+ """
1546
+ if entry_control_parameter_name is not None:
1547
+ # Case 1: Specific entry parameter name provided
1548
+ package_control_input_name = entry_control_parameter_name
1549
+ else:
1550
+ # Case 2: Find the first available control input parameter
1551
+ package_control_input_name = None
1552
+ for param in package_node.parameters:
1553
+ if ParameterTypeBuiltin.CONTROL_TYPE.value in param.input_types:
1554
+ package_control_input_name = param.name
1555
+ logger.warning(
1556
+ "No entry_control_parameter_name specified for packaging node '%s'. "
1557
+ "Using first available control input parameter: '%s'",
1558
+ package_node.name,
1559
+ package_control_input_name,
1560
+ )
1561
+ break
1562
+
1563
+ if package_control_input_name is None:
1564
+ details = f"Attempted to package node '{package_node.name}'. Failed because no control input parameters found on the node, so cannot create control flow connection."
1565
+ return PackageNodeAsSerializedFlowResultFailure(result_details=details)
1566
+
1567
+ # StartNode always has a control output parameter with name "exec_out"
1568
+ source_control_parameter_name = "exec_out"
1569
+
1570
+ # Create the connection
1571
+ control_connection = SerializedFlowCommands.IndirectConnectionSerialization(
1572
+ source_node_uuid=start_node_uuid,
1573
+ source_parameter_name=source_control_parameter_name,
1574
+ target_node_uuid=package_node_uuid,
1575
+ target_parameter_name=package_control_input_name,
1576
+ )
1577
+ return control_connection
1578
+
1579
+ def _assemble_serialized_flow( # noqa: PLR0913
1580
+ self,
1581
+ serialized_package_result: SerializeNodeToCommandsResultSuccess,
1582
+ start_node_result: PackagingStartNodeResult,
1583
+ end_node_result: PackagingEndNodeResult,
1584
+ control_flow_connections: list[SerializedFlowCommands.IndirectConnectionSerialization],
1585
+ unique_parameter_uuid_to_values: dict[SerializedNodeCommands.UniqueParameterValueUUID, Any],
1586
+ library_version: str,
1587
+ request: PackageNodeAsSerializedFlowRequest,
1588
+ ) -> SerializedFlowCommands:
1589
+ """Assemble the complete SerializedFlowCommands from all components."""
1590
+ # Combine all connections: Start->Package + Package->End + Control Flow
1591
+ all_connections = (
1592
+ start_node_result.start_to_package_data_connections
1593
+ + end_node_result.package_to_end_data_connections
1594
+ + control_flow_connections
1595
+ )
1596
+
1597
+ # Set up lock commands if needed
1598
+ set_lock_commands_per_node = {}
1599
+ if serialized_package_result.serialized_node_commands.lock_node_command:
1600
+ set_lock_commands_per_node[serialized_package_result.serialized_node_commands.node_uuid] = (
1601
+ serialized_package_result.serialized_node_commands.lock_node_command
1602
+ )
1603
+
1604
+ # Collect all libraries used
1605
+ end_node_library_details = LibraryNameAndVersion(
1606
+ library_name=request.start_end_specific_library_name,
1607
+ library_version=library_version,
1608
+ )
1609
+
1610
+ node_libraries_used = {
1611
+ serialized_package_result.serialized_node_commands.node_library_details,
1612
+ start_node_result.start_node_commands.node_library_details,
1613
+ end_node_library_details,
1614
+ }
1615
+
1616
+ # Include all three nodes in the flow
1617
+ all_serialized_nodes = [
1618
+ start_node_result.start_node_commands,
1619
+ serialized_package_result.serialized_node_commands,
1620
+ end_node_result.end_node_commands,
1621
+ ]
1622
+
1623
+ # Create a CreateFlowRequest for the packaged flow so that it can
1624
+ # run as a standalone workflow.
1625
+ package_flow_name = (
1626
+ f"Packaged_{serialized_package_result.serialized_node_commands.create_node_command.node_name}"
1627
+ )
1628
+ packaged_flow_metadata = {} # Keep it simple until we have reason to populate it
1629
+
1630
+ create_packaged_flow_request = CreateFlowRequest(
1631
+ parent_flow_name=None, # Standalone flow
1632
+ flow_name=package_flow_name,
1633
+ set_as_new_context=False, # Let deserializer decide
1634
+ metadata=packaged_flow_metadata,
1635
+ )
1636
+
1637
+ # Build the complete SerializedFlowCommands
1638
+ return SerializedFlowCommands(
1639
+ node_libraries_used=node_libraries_used,
1640
+ flow_initialization_command=create_packaged_flow_request,
1641
+ serialized_node_commands=all_serialized_nodes,
1642
+ serialized_connections=all_connections,
1643
+ unique_parameter_uuid_to_values=unique_parameter_uuid_to_values,
1644
+ set_parameter_value_commands={
1645
+ serialized_package_result.serialized_node_commands.node_uuid: serialized_package_result.set_parameter_value_commands,
1646
+ start_node_result.start_node_commands.node_uuid: start_node_result.start_node_parameter_value_commands,
1647
+ },
1648
+ set_lock_commands_per_node=set_lock_commands_per_node,
1649
+ sub_flows_commands=[],
1650
+ referenced_workflows=set(),
1651
+ )
1652
+
1015
1653
  async def on_start_flow_request(self, request: StartFlowRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912
1016
1654
  # which flow
1017
1655
  flow_name = request.flow_name
@@ -1090,7 +1728,7 @@ class FlowManager:
1090
1728
  details = f"Could not get flow state. Error: {err}"
1091
1729
  return GetFlowStateResultFailure(result_details=details)
1092
1730
  try:
1093
- control_nodes, resolving_nodes, involved_nodes = self.flow_state(flow)
1731
+ control_nodes, resolving_nodes = self.flow_state(flow)
1094
1732
  except Exception as e:
1095
1733
  details = f"Failed to get flow state of flow with name {flow_name}. Exception occurred: {e} "
1096
1734
  logger.exception(details)
@@ -1100,7 +1738,6 @@ class FlowManager:
1100
1738
  control_nodes=control_nodes,
1101
1739
  resolving_node=resolving_nodes,
1102
1740
  result_details=details,
1103
- involved_nodes=involved_nodes,
1104
1741
  )
1105
1742
 
1106
1743
  def on_cancel_flow_request(self, request: CancelFlowRequest) -> ResultPayload:
@@ -1608,12 +2245,16 @@ class FlowManager:
1608
2245
  # Initialize global control flow machine and DAG builder
1609
2246
 
1610
2247
  self._global_control_flow_machine = ControlFlowMachine(flow.name)
2248
+ # Set off the request here.
1611
2249
  try:
1612
2250
  await self._global_control_flow_machine.start_flow(start_node, debug_mode)
1613
2251
  except Exception:
1614
2252
  if self.check_for_existing_running_flow():
1615
2253
  self.cancel_flow_run()
1616
2254
  raise
2255
+ GriptapeNodes.EventManager().put_event(
2256
+ ExecutionGriptapeNodeEvent(wrapped_event=ExecutionEvent(payload=InvolvedNodesEvent(involved_nodes=[])))
2257
+ )
1617
2258
 
1618
2259
  def check_for_existing_running_flow(self) -> bool:
1619
2260
  if self._global_control_flow_machine is None:
@@ -1720,6 +2361,13 @@ class FlowManager:
1720
2361
  if self.check_for_existing_running_flow():
1721
2362
  # Now we know something is running, it's ParallelResolutionMachine, and that we are in single_node_resolution.
1722
2363
  self._global_dag_builder.add_node_with_dependencies(node, node.name)
2364
+ # Emit involved nodes update after adding node to DAG
2365
+ involved_nodes = list(self._global_dag_builder.node_to_reference.keys())
2366
+ GriptapeNodes.EventManager().put_event(
2367
+ ExecutionGriptapeNodeEvent(
2368
+ wrapped_event=ExecutionEvent(payload=InvolvedNodesEvent(involved_nodes=involved_nodes))
2369
+ )
2370
+ )
1723
2371
  else:
1724
2372
  # Set that we are only working on one node right now!
1725
2373
  self._global_single_node_resolution = True
@@ -1733,10 +2381,30 @@ class FlowManager:
1733
2381
  if isinstance(resolution_machine, ParallelResolutionMachine):
1734
2382
  self._global_dag_builder.add_node_with_dependencies(node)
1735
2383
  resolution_machine.context.dag_builder = self._global_dag_builder
1736
- await resolution_machine.resolve_node(node)
2384
+ involved_nodes = list(self._global_dag_builder.node_to_reference.keys())
2385
+ else:
2386
+ involved_nodes = list(flow.nodes.keys())
2387
+ # Send a InvolvedNodesRequest
2388
+
2389
+ GriptapeNodes.EventManager().put_event(
2390
+ ExecutionGriptapeNodeEvent(
2391
+ wrapped_event=ExecutionEvent(payload=InvolvedNodesEvent(involved_nodes=involved_nodes))
2392
+ )
2393
+ )
2394
+ try:
2395
+ await resolution_machine.resolve_node(node)
2396
+ except Exception as e:
2397
+ if self.check_for_existing_running_flow():
2398
+ self.cancel_flow_run()
2399
+ raise RuntimeError(e) from e
1737
2400
  if resolution_machine.is_complete():
1738
2401
  self._global_single_node_resolution = False
1739
2402
  self._global_control_flow_machine.context.current_nodes = []
2403
+ GriptapeNodes.EventManager().put_event(
2404
+ ExecutionGriptapeNodeEvent(
2405
+ wrapped_event=ExecutionEvent(payload=InvolvedNodesEvent(involved_nodes=[]))
2406
+ )
2407
+ )
1740
2408
 
1741
2409
  async def single_execution_step(self, flow: ControlFlow, change_debug_mode: bool) -> None: # noqa: FBT001
1742
2410
  # do a granular step
@@ -1794,12 +2462,11 @@ class FlowManager:
1794
2462
  # Clear entry control parameter for new execution
1795
2463
  node.set_entry_control_parameter(None)
1796
2464
 
1797
- def flow_state(self, flow: ControlFlow) -> tuple[list[str] | None, list[str] | None, list[str] | None]: # noqa: ARG002
2465
+ def flow_state(self, flow: ControlFlow) -> tuple[list[str] | None, list[str] | None]: # noqa: ARG002
1798
2466
  if not self.check_for_existing_running_flow():
1799
- msg = "Flow hasn't started."
1800
- raise RuntimeError(msg)
2467
+ return None, None
1801
2468
  if self._global_control_flow_machine is None:
1802
- return None, None, None
2469
+ return None, None
1803
2470
  control_flow_context = self._global_control_flow_machine.context
1804
2471
  current_control_nodes = (
1805
2472
  [control_flow_node.name for control_flow_node in control_flow_context.current_nodes]
@@ -1812,13 +2479,12 @@ class FlowManager:
1812
2479
  node.node_reference.name
1813
2480
  for node in control_flow_context.resolution_machine.context.task_to_node.values()
1814
2481
  ]
1815
- involved_nodes = list(self._global_dag_builder.node_to_reference.keys())
1816
- return current_control_nodes, current_resolving_nodes, involved_nodes if len(involved_nodes) != 0 else None
2482
+ return current_control_nodes, current_resolving_nodes
1817
2483
  if isinstance(control_flow_context.resolution_machine, SequentialResolutionMachine):
1818
2484
  focus_stack_for_node = control_flow_context.resolution_machine.context.focus_stack
1819
2485
  current_resolving_node = focus_stack_for_node[-1].node.name if len(focus_stack_for_node) else None
1820
- return current_control_nodes, [current_resolving_node] if current_resolving_node else None, None
1821
- return current_control_nodes, None, None
2486
+ return current_control_nodes, [current_resolving_node] if current_resolving_node else None
2487
+ return current_control_nodes, None
1822
2488
 
1823
2489
  def get_start_node_from_node(self, flow: ControlFlow, node: BaseNode) -> BaseNode | None:
1824
2490
  # backwards chain in control outputs.