griptape-nodes 0.37.0__py3-none-any.whl → 0.38.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 (38) hide show
  1. griptape_nodes/__init__.py +292 -132
  2. griptape_nodes/app/__init__.py +1 -6
  3. griptape_nodes/app/app.py +108 -76
  4. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +80 -5
  5. griptape_nodes/drivers/storage/local_storage_driver.py +5 -1
  6. griptape_nodes/exe_types/core_types.py +84 -3
  7. griptape_nodes/exe_types/node_types.py +260 -50
  8. griptape_nodes/machines/node_resolution.py +2 -14
  9. griptape_nodes/retained_mode/events/agent_events.py +7 -0
  10. griptape_nodes/retained_mode/events/base_events.py +16 -0
  11. griptape_nodes/retained_mode/events/library_events.py +26 -0
  12. griptape_nodes/retained_mode/events/parameter_events.py +31 -0
  13. griptape_nodes/retained_mode/griptape_nodes.py +32 -0
  14. griptape_nodes/retained_mode/managers/agent_manager.py +25 -12
  15. griptape_nodes/retained_mode/managers/config_manager.py +37 -4
  16. griptape_nodes/retained_mode/managers/event_manager.py +15 -0
  17. griptape_nodes/retained_mode/managers/flow_manager.py +64 -61
  18. griptape_nodes/retained_mode/managers/library_manager.py +215 -45
  19. griptape_nodes/retained_mode/managers/node_manager.py +344 -147
  20. griptape_nodes/retained_mode/managers/operation_manager.py +6 -0
  21. griptape_nodes/retained_mode/managers/os_manager.py +6 -1
  22. griptape_nodes/retained_mode/managers/secrets_manager.py +7 -2
  23. griptape_nodes/retained_mode/managers/settings.py +2 -11
  24. griptape_nodes/retained_mode/managers/static_files_manager.py +12 -3
  25. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +105 -0
  26. griptape_nodes/retained_mode/managers/workflow_manager.py +4 -4
  27. griptape_nodes/updater/__init__.py +14 -8
  28. griptape_nodes/version_compatibility/__init__.py +1 -0
  29. griptape_nodes/version_compatibility/versions/__init__.py +1 -0
  30. griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +1 -0
  31. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +77 -0
  32. {griptape_nodes-0.37.0.dist-info → griptape_nodes-0.38.0.dist-info}/METADATA +4 -1
  33. {griptape_nodes-0.37.0.dist-info → griptape_nodes-0.38.0.dist-info}/RECORD +36 -33
  34. griptape_nodes/app/app_websocket.py +0 -481
  35. griptape_nodes/app/nodes_api_socket_manager.py +0 -117
  36. {griptape_nodes-0.37.0.dist-info → griptape_nodes-0.38.0.dist-info}/WHEEL +0 -0
  37. {griptape_nodes-0.37.0.dist-info → griptape_nodes-0.38.0.dist-info}/entry_points.txt +0 -0
  38. {griptape_nodes-0.37.0.dist-info → griptape_nodes-0.38.0.dist-info}/licenses/LICENSE +0 -0
@@ -4,12 +4,11 @@ import pickle
4
4
  from typing import Any, NamedTuple, cast
5
5
  from uuid import uuid4
6
6
 
7
- from griptape.events import EventBus
8
-
9
7
  from griptape_nodes.exe_types.core_types import (
10
8
  BaseNodeElement,
11
9
  Parameter,
12
10
  ParameterContainer,
11
+ ParameterGroup,
13
12
  ParameterMode,
14
13
  ParameterTypeBuiltin,
15
14
  )
@@ -18,8 +17,6 @@ from griptape_nodes.exe_types.node_types import BaseNode, EndLoopNode, NodeResol
18
17
  from griptape_nodes.exe_types.type_validator import TypeValidator
19
18
  from griptape_nodes.node_library.library_registry import LibraryNameAndVersion, LibraryRegistry
20
19
  from griptape_nodes.retained_mode.events.base_events import (
21
- ExecutionEvent,
22
- ExecutionGriptapeNodeEvent,
23
20
  ResultPayload,
24
21
  ResultPayloadFailure,
25
22
  )
@@ -92,7 +89,6 @@ from griptape_nodes.retained_mode.events.parameter_events import (
92
89
  AddParameterToNodeRequest,
93
90
  AddParameterToNodeResultFailure,
94
91
  AddParameterToNodeResultSuccess,
95
- AlterElementEvent,
96
92
  AlterParameterDetailsRequest,
97
93
  AlterParameterDetailsResultFailure,
98
94
  AlterParameterDetailsResultSuccess,
@@ -112,6 +108,9 @@ from griptape_nodes.retained_mode.events.parameter_events import (
112
108
  RemoveParameterFromNodeRequest,
113
109
  RemoveParameterFromNodeResultFailure,
114
110
  RemoveParameterFromNodeResultSuccess,
111
+ RenameParameterRequest,
112
+ RenameParameterResultFailure,
113
+ RenameParameterResultSuccess,
115
114
  SetParameterValueRequest,
116
115
  SetParameterValueResultFailure,
117
116
  SetParameterValueResultSuccess,
@@ -156,6 +155,7 @@ class NodeManager:
156
155
  )
157
156
  event_manager.assign_manager_to_request_type(GetParameterValueRequest, self.on_get_parameter_value_request)
158
157
  event_manager.assign_manager_to_request_type(SetParameterValueRequest, self.on_set_parameter_value_request)
158
+ event_manager.assign_manager_to_request_type(RenameParameterRequest, self.on_rename_parameter_request)
159
159
  event_manager.assign_manager_to_request_type(ResolveNodeRequest, self.on_resolve_from_node_request)
160
160
  event_manager.assign_manager_to_request_type(GetAllNodeInfoRequest, self.on_get_all_node_info_request)
161
161
  event_manager.assign_manager_to_request_type(
@@ -217,7 +217,7 @@ class NodeManager:
217
217
  if parent_flow_name == old_name:
218
218
  self._name_to_parent_flow_name[node_name] = new_name
219
219
 
220
- def on_create_node_request(self, request: CreateNodeRequest) -> ResultPayload: # noqa: C901, PLR0912, PLR0915
220
+ def on_create_node_request(self, request: CreateNodeRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912, PLR0915
221
221
  # Validate as much as possible before we actually create one.
222
222
  parent_flow_name = request.override_parent_flow_name
223
223
  parent_flow = None
@@ -321,30 +321,53 @@ class NodeManager:
321
321
 
322
322
  if isinstance(node, StartLoopNode) and not request.initial_setup:
323
323
  # If it's StartLoop, create an EndLoop and connect it to the StartLoop.
324
+ # Get the class name of the node
325
+ node_class_name = node.__class__.__name__
326
+
327
+ # Get the opposing EndNode
328
+ # TODO: (griptape) Get paired classes implemented so we dont need to do name stuff. https://github.com/griptape-ai/griptape-nodes/issues/1549
329
+ end_class_name = node_class_name.replace("Start", "End")
330
+
331
+ # Check and see if the class exists
332
+ libraries_with_node_type = LibraryRegistry.get_libraries_with_node_type(end_class_name)
333
+ if not libraries_with_node_type:
334
+ msg = f"End class '{end_class_name}' does not exist for start class '{node_class_name}'"
335
+ logger.error(msg)
336
+ return CreateNodeResultFailure()
337
+
338
+ # Create the EndNode
324
339
  end_loop = GriptapeNodes.handle_request(
325
340
  CreateNodeRequest(
326
- node_type="ForEachEndNode",
341
+ node_type=end_class_name,
327
342
  metadata={
328
343
  "position": {"x": node.metadata["position"]["x"] + 650, "y": node.metadata["position"]["y"]}
329
344
  },
330
345
  override_parent_flow_name=parent_flow_name,
331
346
  )
332
347
  )
333
- if isinstance(end_loop, CreateNodeResultSuccess):
334
- # Create Loop between output and input to the start node.
335
- GriptapeNodes.handle_request(
336
- CreateConnectionRequest(
337
- source_node_name=node.name,
338
- source_parameter_name="loop",
339
- target_node_name=end_loop.node_name,
340
- target_parameter_name="from_start",
341
- )
348
+ if not isinstance(end_loop, CreateNodeResultSuccess):
349
+ msg = f"Failed to create EndLoop node for StartLoop node '{node.name}'"
350
+ logger.error(msg)
351
+ return CreateNodeResultFailure()
352
+
353
+ # Create Loop between output and input to the start node.
354
+ GriptapeNodes.handle_request(
355
+ CreateConnectionRequest(
356
+ source_node_name=node.name,
357
+ source_parameter_name="loop",
358
+ target_node_name=end_loop.node_name,
359
+ target_parameter_name="from_start",
342
360
  )
343
- end_node = self.get_node_by_name(end_loop.node_name)
344
- if isinstance(end_node, EndLoopNode):
345
- # create the connection bt them
346
- node.end_node = end_node
347
- end_node.start_node = node
361
+ )
362
+ end_node = self.get_node_by_name(end_loop.node_name)
363
+ if not isinstance(end_node, EndLoopNode):
364
+ msg = f"End node '{end_loop.node_name}' is not a valid EndLoopNode"
365
+ logger.error(msg)
366
+ return CreateNodeResultFailure()
367
+
368
+ # create the connection
369
+ node.end_node = end_node
370
+ end_node.start_node = node
348
371
 
349
372
  return CreateNodeResultSuccess(
350
373
  node_name=node.name, node_type=node.__class__.__name__, specific_library_name=request.specific_library_name
@@ -700,6 +723,24 @@ class NodeManager:
700
723
  )
701
724
  return result
702
725
 
726
+ def generate_unique_parameter_name(self, node: BaseNode, base_name: str) -> str:
727
+ """Generate a unique parameter name for a node by appending a number if needed.
728
+
729
+ Args:
730
+ node: The node to check for existing parameter names
731
+ base_name: The desired base name for the parameter
732
+
733
+ Returns:
734
+ A unique parameter name that doesn't conflict with existing parameters
735
+ """
736
+ if node.get_parameter_by_name(base_name) is None:
737
+ return base_name
738
+
739
+ counter = 1
740
+ while node.get_parameter_by_name(f"{base_name}_{counter}") is not None:
741
+ counter += 1
742
+ return f"{base_name}_{counter}"
743
+
703
744
  def on_add_parameter_to_node_request(self, request: AddParameterToNodeRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912, PLR0915
704
745
  node_name = request.node_name
705
746
  node = None
@@ -744,6 +785,7 @@ class NodeManager:
744
785
  logger.exception(details)
745
786
  result = AddParameterToNodeResultFailure()
746
787
  return result
788
+
747
789
  return AddParameterToNodeResultSuccess(
748
790
  parameter_name=new_param.name, type=new_param.type, node_name=node_name
749
791
  )
@@ -752,13 +794,14 @@ class NodeManager:
752
794
  logger.error(details)
753
795
  result = AddParameterToNodeResultFailure()
754
796
  return result
755
- # Does the Node already have a parameter by this name?
756
- if node.get_parameter_by_name(request.parameter_name) is not None:
757
- details = f"Attempted to add Parameter '{request.parameter_name}' to Node '{node_name}'. Failed because it already had a Parameter with that name on it. Parameter names must be unique within the Node."
758
- logger.error(details)
759
797
 
760
- result = AddParameterToNodeResultFailure()
761
- return result
798
+ # Generate a unique parameter name if needed
799
+ requested_parameter_name = request.parameter_name
800
+ if requested_parameter_name is None:
801
+ # Not allowed to have a parameter with no name, so we'll give it a default name
802
+ requested_parameter_name = "parameter"
803
+
804
+ final_param_name = self.generate_unique_parameter_name(node, requested_parameter_name)
762
805
 
763
806
  # Let's see if the Parameter is properly formed.
764
807
  # If a Parameter is intended for Control, it needs to have that be the exclusive type.
@@ -799,7 +842,7 @@ class NodeManager:
799
842
 
800
843
  # Let's roll, I guess.
801
844
  new_param = Parameter(
802
- name=request.parameter_name,
845
+ name=final_param_name,
803
846
  type=request.type,
804
847
  input_types=request.input_types,
805
848
  output_type=request.output_type,
@@ -822,12 +865,17 @@ class NodeManager:
822
865
  logger.info(new_param.name)
823
866
  node.add_parameter(new_param)
824
867
  except Exception as e:
825
- details = f"Couldn't add parameter with name {request.parameter_name} to node. Error: {e}"
868
+ details = f"Couldn't add parameter with name {request.parameter_name} to Node '{node_name}'. Error: {e}"
826
869
  logger.error(details)
827
870
  return AddParameterToNodeResultFailure()
828
871
 
829
- details = f"Successfully added Parameter '{request.parameter_name}' to Node '{node_name}'."
830
- logger.debug(details)
872
+ details = f"Successfully added Parameter '{final_param_name}' to Node '{node_name}'."
873
+ log_level = logging.DEBUG
874
+ if final_param_name != requested_parameter_name:
875
+ log_level = logging.WARNING
876
+ details = f"{details} WARNING: Had to rename from original parameter name '{requested_parameter_name}' as a parameter with this name already existed in node '{node_name}'."
877
+
878
+ logger.log(level=log_level, msg=details)
831
879
 
832
880
  result = AddParameterToNodeResultSuccess(
833
881
  parameter_name=new_param.name, type=new_param.type, node_name=node_name
@@ -861,83 +909,87 @@ class NodeManager:
861
909
  result = RemoveParameterFromNodeResultFailure()
862
910
  return result
863
911
 
864
- # Does the Parameter actually exist on the Node?
865
- parameter = node.get_parameter_by_name(request.parameter_name)
866
- parameter_group = node.get_group_by_name_or_element_id(request.parameter_name)
867
- if parameter is None and parameter_group is None:
868
- details = f"Attempted to remove Parameter '{request.parameter_name}' from Node '{node_name}'. Failed because it didn't have a Parameter with that name on it."
912
+ # Does the Element actually exist on the Node?
913
+ element = node.get_element_by_name_and_type(request.parameter_name)
914
+ if element is None:
915
+ details = f"Attempted to remove Element '{request.parameter_name}' from Node '{node_name}'. Failed because it didn't have an Element with that name on it."
869
916
  logger.error(details)
870
917
 
871
918
  result = RemoveParameterFromNodeResultFailure()
872
919
  return result
873
- if parameter_group is not None:
874
- for child in parameter_group.find_elements_by_type(Parameter):
920
+
921
+ # If it's a ParameterGroup, we need to remove all the Parameters inside it.
922
+ if isinstance(element, ParameterGroup):
923
+ for child in element.find_elements_by_type(Parameter):
875
924
  GriptapeNodes.handle_request(RemoveParameterFromNodeRequest(child.name, node_name))
876
925
  node.remove_parameter_element_by_name(request.parameter_name)
926
+
877
927
  return RemoveParameterFromNodeResultSuccess()
878
928
 
879
929
  # No tricky stuff, users!
880
- if parameter is not None and parameter.user_defined is False:
881
- details = f"Attempted to remove Parameter '{request.parameter_name}' from Node '{node_name}'. Failed because the Parameter was not user-defined (i.e., critical to the Node implementation). Only user-defined Parameters can be removed from a Node."
930
+ # if user_defined doesn't exist, or is false, then it's not user-defined
931
+ if not getattr(element, "user_defined", False):
932
+ details = f"Attempted to remove Element '{request.parameter_name}' from Node '{node_name}'. Failed because the Element was not user-defined (i.e., critical to the Node implementation). Only user-defined Elements can be removed from a Node."
882
933
  logger.error(details)
883
934
 
884
935
  result = RemoveParameterFromNodeResultFailure()
885
936
  return result
886
937
 
887
938
  # Get all the connections to/from this Parameter.
888
- list_node_connections_request = ListConnectionsForNodeRequest(node_name=node_name)
889
- list_connections_result = GriptapeNodes.handle_request(request=list_node_connections_request)
890
- if not isinstance(list_connections_result, ListConnectionsForNodeResultSuccess):
891
- details = f"Attempted to remove Parameter '{request.parameter_name}' from Node '{node_name}'. Failed because we were unable to get a list of Connections for the Parameter's Node."
892
- logger.error(details)
939
+ if isinstance(element, Parameter):
940
+ list_node_connections_request = ListConnectionsForNodeRequest(node_name=node_name)
941
+ list_connections_result = GriptapeNodes.handle_request(request=list_node_connections_request)
942
+ if not isinstance(list_connections_result, ListConnectionsForNodeResultSuccess):
943
+ details = f"Attempted to remove Parameter '{request.parameter_name}' from Node '{node_name}'. Failed because we were unable to get a list of Connections for the Parameter's Node."
944
+ logger.error(details)
893
945
 
894
- result = RemoveParameterFromNodeResultFailure()
895
- return result
946
+ result = RemoveParameterFromNodeResultFailure()
947
+ return result
896
948
 
897
- # We have a list of all connections to the NODE. Sift down to just those that are about this PARAMETER.
949
+ # We have a list of all connections to the NODE. Sift down to just those that are about this PARAMETER.
898
950
 
899
- # Destroy all the incoming Connections to this PARAMETER
900
- for incoming_connection in list_connections_result.incoming_connections:
901
- if incoming_connection.target_parameter_name == request.parameter_name:
902
- delete_request = DeleteConnectionRequest(
903
- source_node_name=incoming_connection.source_node_name,
904
- source_parameter_name=incoming_connection.source_parameter_name,
905
- target_node_name=node_name,
906
- target_parameter_name=incoming_connection.target_parameter_name,
907
- )
908
- delete_result = GriptapeNodes.handle_request(delete_request)
909
- if isinstance(delete_result, DeleteConnectionResultFailure):
910
- details = f"Attempted to remove Parameter '{request.parameter_name}' from Node '{node_name}'. Failed because we were unable to delete a Connection for that Parameter."
911
- logger.error(details)
951
+ # Destroy all the incoming Connections to this PARAMETER
952
+ for incoming_connection in list_connections_result.incoming_connections:
953
+ if incoming_connection.target_parameter_name == request.parameter_name:
954
+ delete_request = DeleteConnectionRequest(
955
+ source_node_name=incoming_connection.source_node_name,
956
+ source_parameter_name=incoming_connection.source_parameter_name,
957
+ target_node_name=node_name,
958
+ target_parameter_name=incoming_connection.target_parameter_name,
959
+ )
960
+ delete_result = GriptapeNodes.handle_request(delete_request)
961
+ if isinstance(delete_result, DeleteConnectionResultFailure):
962
+ details = f"Attempted to remove Parameter '{request.parameter_name}' from Node '{node_name}'. Failed because we were unable to delete a Connection for that Parameter."
963
+ logger.error(details)
912
964
 
913
- result = RemoveParameterFromNodeResultFailure()
965
+ result = RemoveParameterFromNodeResultFailure()
914
966
 
915
- # Destroy all the outgoing Connections from this PARAMETER
916
- for outgoing_connection in list_connections_result.outgoing_connections:
917
- if outgoing_connection.source_parameter_name == request.parameter_name:
918
- delete_request = DeleteConnectionRequest(
919
- source_node_name=node_name,
920
- source_parameter_name=outgoing_connection.source_parameter_name,
921
- target_node_name=outgoing_connection.target_node_name,
922
- target_parameter_name=outgoing_connection.target_parameter_name,
923
- )
924
- delete_result = GriptapeNodes.handle_request(delete_request)
925
- if isinstance(delete_result, DeleteConnectionResultFailure):
926
- details = f"Attempted to remove Parameter '{request.parameter_name}' from Node '{node_name}'. Failed because we were unable to delete a Connection for that Parameter."
927
- logger.error(details)
967
+ # Destroy all the outgoing Connections from this PARAMETER
968
+ for outgoing_connection in list_connections_result.outgoing_connections:
969
+ if outgoing_connection.source_parameter_name == request.parameter_name:
970
+ delete_request = DeleteConnectionRequest(
971
+ source_node_name=node_name,
972
+ source_parameter_name=outgoing_connection.source_parameter_name,
973
+ target_node_name=outgoing_connection.target_node_name,
974
+ target_parameter_name=outgoing_connection.target_parameter_name,
975
+ )
976
+ delete_result = GriptapeNodes.handle_request(delete_request)
977
+ if isinstance(delete_result, DeleteConnectionResultFailure):
978
+ details = f"Attempted to remove Parameter '{request.parameter_name}' from Node '{node_name}'. Failed because we were unable to delete a Connection for that Parameter."
979
+ logger.error(details)
928
980
 
929
- result = RemoveParameterFromNodeResultFailure()
981
+ result = RemoveParameterFromNodeResultFailure()
930
982
 
931
- # Delete the Parameter itself.
932
- if parameter is not None:
933
- node.remove_parameter_element(parameter)
983
+ # Delete the Element itself.
984
+ if element is not None:
985
+ node.remove_parameter_element(element)
934
986
  else:
935
- details = f"Attempted to remove Parameter '{request.parameter_name}' from Node '{node_name}'. Failed because parameter didn't exist."
987
+ details = f"Attempted to remove Element '{request.parameter_name}' from Node '{node_name}'. Failed because element didn't exist."
936
988
  logger.error(details)
937
989
 
938
990
  result = RemoveParameterFromNodeResultFailure()
939
991
 
940
- details = f"Successfully removed Parameter '{request.parameter_name}' from Node '{node_name}'."
992
+ details = f"Successfully removed Element '{request.parameter_name}' from Node '{node_name}'."
941
993
  logger.debug(details)
942
994
 
943
995
  result = RemoveParameterFromNodeResultSuccess()
@@ -968,39 +1020,43 @@ class NodeManager:
968
1020
  result = GetParameterDetailsResultFailure()
969
1021
  return result
970
1022
 
971
- # Does the Parameter actually exist on the Node?
972
- parameter = node.get_parameter_by_name(request.parameter_name)
973
- if parameter is None:
974
- details = f"Attempted to get details for Parameter '{request.parameter_name}' from Node '{node_name}'. Failed because it didn't have a Parameter with that name on it."
975
- logger.error(details)
1023
+ # Does the Element actually exist on the Node?
1024
+ element = node.get_element_by_name_and_type(request.parameter_name)
976
1025
 
977
- result = GetParameterDetailsResultFailure()
978
- return result
1026
+ if element is None:
1027
+ details = f"Attempted to get details for Element '{request.parameter_name}' from Node '{node_name}'. Failed because it didn't have an Element with that name on it."
1028
+ logger.error(details)
1029
+ return GetParameterDetailsResultFailure()
979
1030
 
980
1031
  # Let's bundle up the details.
981
- modes_allowed = parameter.allowed_modes
982
- allows_input = ParameterMode.INPUT in modes_allowed
983
- allows_property = ParameterMode.PROPERTY in modes_allowed
984
- allows_output = ParameterMode.OUTPUT in modes_allowed
1032
+ allows_input = False
1033
+ allows_property = False
1034
+ allows_output = False
985
1035
 
986
- details = f"Successfully got details for Parameter '{request.parameter_name}' from Node '{node_name}'."
987
- logger.debug(details)
1036
+ if isinstance(element, Parameter):
1037
+ modes_allowed = element.allowed_modes
1038
+ allows_input = ParameterMode.INPUT in modes_allowed
1039
+ allows_property = ParameterMode.PROPERTY in modes_allowed
1040
+ allows_output = ParameterMode.OUTPUT in modes_allowed
1041
+
1042
+ details = f"Successfully got details for Element '{request.parameter_name}' from Node '{node_name}'."
1043
+ logger.debug(details)
988
1044
 
989
1045
  result = GetParameterDetailsResultSuccess(
990
- element_id=parameter.element_id,
991
- type=parameter.type,
992
- input_types=parameter.input_types,
993
- output_type=parameter.output_type,
994
- default_value=parameter.default_value,
995
- tooltip=parameter.tooltip,
996
- tooltip_as_input=parameter.tooltip_as_input,
997
- tooltip_as_property=parameter.tooltip_as_property,
998
- tooltip_as_output=parameter.tooltip_as_output,
1046
+ element_id=element.element_id,
1047
+ type=getattr(element, "type", ""),
1048
+ input_types=getattr(element, "input_types", []),
1049
+ output_type=getattr(element, "output_type", ""),
1050
+ default_value=getattr(element, "default_value", None),
1051
+ tooltip=getattr(element, "tooltip", ""),
1052
+ tooltip_as_input=getattr(element, "tooltip_as_input", None),
1053
+ tooltip_as_property=getattr(element, "tooltip_as_property", None),
1054
+ tooltip_as_output=getattr(element, "tooltip_as_output", None),
999
1055
  mode_allowed_input=allows_input,
1000
1056
  mode_allowed_property=allows_property,
1001
1057
  mode_allowed_output=allows_output,
1002
- is_user_defined=parameter.user_defined,
1003
- ui_options=parameter.ui_options,
1058
+ is_user_defined=getattr(element, "user_defined", False),
1059
+ ui_options=getattr(element, "ui_options", None),
1004
1060
  )
1005
1061
  return result
1006
1062
 
@@ -1040,7 +1096,7 @@ class NodeManager:
1040
1096
  return GetNodeElementDetailsResultFailure()
1041
1097
 
1042
1098
  element_details = element.to_dict()
1043
- # We need to get parameter values from here
1099
+ # We need to get element values from here
1044
1100
  param_to_value = {}
1045
1101
  self._set_param_to_value(node, element, param_to_value)
1046
1102
  if param_to_value:
@@ -1076,17 +1132,18 @@ class NodeManager:
1076
1132
  # Otherwise, just set it here. It'll be handled in .json() when we send it over.
1077
1133
  param_to_value[element_id] = value
1078
1134
 
1079
- def modify_alterable_fields(self, request: AlterParameterDetailsRequest, parameter: Parameter) -> None:
1080
- if request.tooltip is not None:
1081
- parameter.tooltip = request.tooltip
1082
- if request.tooltip_as_input is not None:
1083
- parameter.tooltip_as_input = request.tooltip_as_input
1084
- if request.tooltip_as_property is not None:
1085
- parameter.tooltip_as_property = request.tooltip_as_property
1086
- if request.tooltip_as_output is not None:
1087
- parameter.tooltip_as_output = request.tooltip_as_output
1088
- if request.ui_options is not None:
1089
- parameter.ui_options = request.ui_options
1135
+ def modify_alterable_fields(self, request: AlterParameterDetailsRequest, parameter: BaseNodeElement) -> None:
1136
+ if isinstance(parameter, Parameter):
1137
+ if request.tooltip:
1138
+ parameter.tooltip = request.tooltip
1139
+ if request.tooltip_as_input is not None:
1140
+ parameter.tooltip_as_input = request.tooltip_as_input
1141
+ if request.tooltip_as_property is not None:
1142
+ parameter.tooltip_as_property = request.tooltip_as_property
1143
+ if request.tooltip_as_output is not None:
1144
+ parameter.tooltip_as_output = request.tooltip_as_output
1145
+ if request.ui_options is not None and hasattr(parameter, "ui_options"):
1146
+ parameter.ui_options = request.ui_options # type: ignore[attr-defined]
1090
1147
 
1091
1148
  def modify_key_parameter_fields(self, request: AlterParameterDetailsRequest, parameter: Parameter) -> None:
1092
1149
  if request.type is not None:
@@ -1114,7 +1171,67 @@ class NodeManager:
1114
1171
  else:
1115
1172
  parameter.allowed_modes.discard(ParameterMode.OUTPUT)
1116
1173
 
1117
- def on_alter_parameter_details_request(self, request: AlterParameterDetailsRequest) -> ResultPayload:
1174
+ def _validate_and_break_invalid_connections(
1175
+ self, node_name: str, parameter: Parameter, request: AlterParameterDetailsRequest
1176
+ ) -> ResultPayload | None:
1177
+ """Validate and break any connections that are no longer valid after a parameter type change.
1178
+
1179
+ This method checks both incoming and outgoing connections for a parameter and removes
1180
+ any that are no longer type-compatible after the parameter's type has been changed.
1181
+
1182
+ Returns:
1183
+ ResultPayload | None: Returns AlterParameterDetailsResultFailure if any connection deletion fails,
1184
+ None otherwise.
1185
+ """
1186
+ # Get all connections for this node
1187
+ list_connections_request = ListConnectionsForNodeRequest(node_name=node_name)
1188
+ list_connections_result = self.on_list_connections_for_node_request(list_connections_request)
1189
+
1190
+ if not isinstance(list_connections_result, ListConnectionsForNodeResultSuccess):
1191
+ # No connections exist for this node, which is not a failure - just nothing to validate
1192
+ return None
1193
+
1194
+ # Check and break invalid incoming connections
1195
+ for conn in list_connections_result.incoming_connections:
1196
+ if conn.target_parameter_name == request.parameter_name:
1197
+ source_node = self.get_node_by_name(conn.source_node_name)
1198
+ source_param = source_node.get_parameter_by_name(conn.source_parameter_name)
1199
+ if source_param and not parameter.is_incoming_type_allowed(source_param.output_type):
1200
+ delete_result = GriptapeNodes.FlowManager().on_delete_connection_request(
1201
+ DeleteConnectionRequest(
1202
+ source_node_name=conn.source_node_name,
1203
+ source_parameter_name=conn.source_parameter_name,
1204
+ target_node_name=node_name,
1205
+ target_parameter_name=request.parameter_name,
1206
+ )
1207
+ )
1208
+ if isinstance(delete_result, ResultPayloadFailure):
1209
+ details = f"Failed to delete incompatible incoming connection from {conn.source_node_name}.{conn.source_parameter_name} to {node_name}.{request.parameter_name}: {delete_result}"
1210
+ logger.error(details)
1211
+ return AlterParameterDetailsResultFailure()
1212
+
1213
+ # Check and break invalid outgoing connections
1214
+ for conn in list_connections_result.outgoing_connections:
1215
+ if conn.source_parameter_name == request.parameter_name:
1216
+ target_node = self.get_node_by_name(conn.target_node_name)
1217
+ target_param = target_node.get_parameter_by_name(conn.target_parameter_name)
1218
+ if target_param and not target_param.is_incoming_type_allowed(parameter.output_type):
1219
+ delete_result = GriptapeNodes.FlowManager().on_delete_connection_request(
1220
+ DeleteConnectionRequest(
1221
+ source_node_name=node_name,
1222
+ source_parameter_name=request.parameter_name,
1223
+ target_node_name=conn.target_node_name,
1224
+ target_parameter_name=conn.target_parameter_name,
1225
+ )
1226
+ )
1227
+ if isinstance(delete_result, ResultPayloadFailure):
1228
+ details = f"Failed to delete incompatible outgoing connection from {node_name}.{request.parameter_name} to {conn.target_node_name}.{conn.target_parameter_name}: {delete_result}"
1229
+ logger.error(details)
1230
+ return AlterParameterDetailsResultFailure()
1231
+
1232
+ return None
1233
+
1234
+ def on_alter_parameter_details_request(self, request: AlterParameterDetailsRequest) -> ResultPayload: # noqa: C901
1118
1235
  node_name = request.node_name
1119
1236
  node = None
1120
1237
 
@@ -1137,36 +1254,42 @@ class NodeManager:
1137
1254
 
1138
1255
  return AlterParameterDetailsResultFailure()
1139
1256
 
1140
- # Does the Parameter actually exist on the Node?
1141
- parameter = node.get_parameter_by_name(request.parameter_name)
1142
- parameter_group = node.get_group_by_name_or_element_id(request.parameter_name)
1143
- if parameter is None:
1144
- parameter_group = node.get_group_by_name_or_element_id(request.parameter_name)
1145
- if parameter_group is None:
1146
- details = f"Attempted to alter details for Parameter '{request.parameter_name}' from Node '{node_name}'. Failed because it didn't have a Parameter with that name on it."
1147
- logger.error(details)
1148
-
1149
- return AlterParameterDetailsResultFailure()
1150
- if request.ui_options is not None:
1151
- parameter_group.ui_options = request.ui_options
1152
- return AlterParameterDetailsResultSuccess()
1257
+ # Does the Element actually exist on the Node?
1258
+ element = node.get_element_by_name_and_type(request.parameter_name)
1259
+ if element is None:
1260
+ details = f"Attempted to alter details for Element '{request.parameter_name}' from Node '{node_name}'. Failed because it didn't have an Element with that name on it."
1261
+ logger.error(details)
1262
+ return AlterParameterDetailsResultFailure()
1263
+ if request.ui_options is not None:
1264
+ element.ui_options = request.ui_options # type: ignore[attr-defined]
1265
+
1266
+ # Check and handle connections if type was changed
1267
+ if isinstance(element, Parameter) and (
1268
+ request.type is not None or request.input_types is not None or request.output_type is not None
1269
+ ):
1270
+ result = self._validate_and_break_invalid_connections(node_name, element, request)
1271
+ if isinstance(result, AlterParameterDetailsResultFailure):
1272
+ return result
1153
1273
 
1154
1274
  # TODO: https://github.com/griptape-ai/griptape-nodes/issues/827
1155
- # Now change all the values on the Parameter.
1156
- self.modify_alterable_fields(request, parameter)
1275
+ # Now change all the values on the Element.
1276
+ self.modify_alterable_fields(request, element)
1277
+
1157
1278
  # The rest of these are not alterable
1158
- if parameter.user_defined is False and request.request_id:
1159
- # TODO: https://github.com/griptape-ai/griptape-nodes/issues/826
1160
- details = f"Attempted to alter details for Parameter '{request.parameter_name}' from Node '{node_name}'. Could only alter some values because the Parameter was not user-defined (i.e., critical to the Node implementation). Only user-defined Parameters can be totally modified from a Node."
1161
- logger.warning(details)
1162
- return AlterParameterDetailsResultSuccess()
1163
- self.modify_key_parameter_fields(request, parameter)
1279
+ if isinstance(element, Parameter):
1280
+ if hasattr(element, "user_defined") and element.user_defined is False and request.request_id: # type: ignore[attr-defined]
1281
+ # TODO: https://github.com/griptape-ai/griptape-nodes/issues/826
1282
+ details = f"Attempted to alter details for Element '{request.parameter_name}' from Node '{node_name}'. Could only alter some values because the Element was not user-defined (i.e., critical to the Node implementation). Only user-defined Elements can be totally modified from a Node."
1283
+ logger.warning(details)
1284
+ return AlterParameterDetailsResultSuccess()
1285
+ self.modify_key_parameter_fields(request, element)
1286
+
1164
1287
  # This field requires the node as well
1165
1288
  if request.default_value is not None:
1166
1289
  # TODO: https://github.com/griptape-ai/griptape-nodes/issues/825
1167
1290
  node.parameter_values[request.parameter_name] = request.default_value
1168
1291
 
1169
- details = f"Successfully altered details for Parameter '{request.parameter_name}' from Node '{node_name}'."
1292
+ details = f"Successfully altered details for Element '{request.parameter_name}' from Node '{node_name}'."
1170
1293
  logger.debug(details)
1171
1294
 
1172
1295
  result = AlterParameterDetailsResultSuccess()
@@ -1355,18 +1478,12 @@ class NodeManager:
1355
1478
  return NodeManager.ModifiedReturnValue(object_created, modified)
1356
1479
  # Otherwise use set_parameter_value. This calls our converters and validators.
1357
1480
  old_value = node.get_parameter_value(request.parameter_name)
1358
- modified_parameters = node.set_parameter_value(request.parameter_name, object_created)
1481
+ node.set_parameter_value(request.parameter_name, object_created)
1359
1482
  # Get the "converted" value here.
1360
1483
  finalized_value = node.get_parameter_value(request.parameter_name)
1361
1484
  if old_value != finalized_value:
1362
1485
  modified = True
1363
1486
  # If any parameters were dependent on that value, we're calling this details request to emit the result to the editor.
1364
- if modified_parameters:
1365
- for modified_parameter_name in modified_parameters:
1366
- modified_parameter = node.root_ui_element.find_element_by_name(modified_parameter_name)
1367
- if modified_parameter is not None:
1368
- modified_request = AlterElementEvent(element_details=modified_parameter.to_event(node))
1369
- EventBus.publish_event(ExecutionGriptapeNodeEvent(ExecutionEvent(payload=modified_request)))
1370
1487
  return NodeManager.ModifiedReturnValue(finalized_value, modified)
1371
1488
 
1372
1489
  # For C901 (too complex): Need to give customers explicit reasons for failure on each case.
@@ -2255,3 +2372,83 @@ class NodeManager:
2255
2372
  else:
2256
2373
  commands.append(output_command)
2257
2374
  return commands if commands else None
2375
+
2376
+ def on_rename_parameter_request(self, request: RenameParameterRequest) -> ResultPayload: # noqa: C901, PLR0912
2377
+ """Handle renaming a parameter on a node.
2378
+
2379
+ Args:
2380
+ request: The rename parameter request containing the old and new parameter names
2381
+
2382
+ Returns:
2383
+ ResultPayload: Success or failure result
2384
+ """
2385
+ # Get the node
2386
+ node_name = request.node_name
2387
+ if node_name is None:
2388
+ if not GriptapeNodes.ContextManager().has_current_node():
2389
+ details = "Attempted to rename Parameter in the Current Context. Failed because the Current Context was empty."
2390
+ logger.error(details)
2391
+ return RenameParameterResultFailure()
2392
+ node = GriptapeNodes.ContextManager().get_current_node()
2393
+ node_name = node.name
2394
+ else:
2395
+ try:
2396
+ node = self.get_node_by_name(node_name)
2397
+ except KeyError as err:
2398
+ details = f"Attempted to rename Parameter '{request.parameter_name}' on Node '{node_name}'. Failed because the Node could not be found. Error: {err}"
2399
+ logger.error(details)
2400
+ return RenameParameterResultFailure()
2401
+
2402
+ # Get the parameter
2403
+ parameter = node.get_parameter_by_name(request.parameter_name)
2404
+ if parameter is None:
2405
+ details = f"Attempted to rename Parameter '{request.parameter_name}' on Node '{node_name}'. Failed because the Parameter could not be found."
2406
+ logger.error(details)
2407
+ return RenameParameterResultFailure()
2408
+
2409
+ # Validate the new parameter name
2410
+ if any(char.isspace() for char in request.new_parameter_name):
2411
+ details = f"Failed to rename Parameter '{request.parameter_name}' to '{request.new_parameter_name}'. Parameter names cannot contain any whitespace characters."
2412
+ logger.error(details)
2413
+ return RenameParameterResultFailure()
2414
+
2415
+ # Check for duplicate names
2416
+ if node.does_name_exist(request.new_parameter_name):
2417
+ details = f"Failed to rename Parameter '{request.parameter_name}' to '{request.new_parameter_name}'. A Parameter with that name already exists."
2418
+ logger.error(details)
2419
+ return RenameParameterResultFailure()
2420
+
2421
+ # Get all connections for this node
2422
+ flow_name = self.get_node_parent_flow_by_name(node_name)
2423
+ flow = GriptapeNodes.FlowManager().get_flow_by_name(flow_name)
2424
+
2425
+ # Update connections that reference this parameter
2426
+ if node_name in flow.connections.incoming_index:
2427
+ incoming_connections = flow.connections.incoming_index[node_name]
2428
+ for connection_ids in incoming_connections.values():
2429
+ for connection_id in connection_ids:
2430
+ connection = flow.connections.connections[connection_id]
2431
+ if connection.target_parameter.name == request.parameter_name:
2432
+ connection.target_parameter.name = request.new_parameter_name
2433
+
2434
+ if node_name in flow.connections.outgoing_index:
2435
+ outgoing_connections = flow.connections.outgoing_index[node_name]
2436
+ for connection_ids in outgoing_connections.values():
2437
+ for connection_id in connection_ids:
2438
+ connection = flow.connections.connections[connection_id]
2439
+ if connection.source_parameter.name == request.parameter_name:
2440
+ connection.source_parameter.name = request.new_parameter_name
2441
+
2442
+ # Update parameter name
2443
+ old_name = parameter.name
2444
+ parameter.name = request.new_parameter_name
2445
+
2446
+ # Update parameter values if they exist
2447
+ if old_name in node.parameter_values:
2448
+ node.parameter_values[request.new_parameter_name] = node.parameter_values.pop(old_name)
2449
+ if old_name in node.parameter_output_values:
2450
+ node.parameter_output_values[request.new_parameter_name] = node.parameter_output_values.pop(old_name)
2451
+
2452
+ return RenameParameterResultSuccess(
2453
+ old_parameter_name=old_name, new_parameter_name=request.new_parameter_name, node_name=node_name
2454
+ )