griptape-nodes 0.57.1__py3-none-any.whl → 0.58.1__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 (51) hide show
  1. griptape_nodes/api_client/__init__.py +9 -0
  2. griptape_nodes/api_client/client.py +279 -0
  3. griptape_nodes/api_client/request_client.py +273 -0
  4. griptape_nodes/app/app.py +57 -150
  5. griptape_nodes/bootstrap/utils/python_subprocess_executor.py +1 -1
  6. griptape_nodes/bootstrap/workflow_executors/local_session_workflow_executor.py +22 -50
  7. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +6 -1
  8. griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +27 -46
  9. griptape_nodes/bootstrap/workflow_executors/utils/subprocess_script.py +7 -0
  10. griptape_nodes/bootstrap/workflow_publishers/local_workflow_publisher.py +3 -1
  11. griptape_nodes/bootstrap/workflow_publishers/subprocess_workflow_publisher.py +3 -1
  12. griptape_nodes/bootstrap/workflow_publishers/utils/subprocess_script.py +16 -1
  13. griptape_nodes/common/node_executor.py +466 -0
  14. griptape_nodes/drivers/storage/base_storage_driver.py +0 -11
  15. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +7 -25
  16. griptape_nodes/drivers/storage/local_storage_driver.py +2 -2
  17. griptape_nodes/exe_types/connections.py +37 -9
  18. griptape_nodes/exe_types/core_types.py +1 -1
  19. griptape_nodes/exe_types/node_types.py +115 -22
  20. griptape_nodes/machines/control_flow.py +48 -7
  21. griptape_nodes/machines/parallel_resolution.py +98 -29
  22. griptape_nodes/machines/sequential_resolution.py +61 -22
  23. griptape_nodes/node_library/library_registry.py +24 -1
  24. griptape_nodes/node_library/workflow_registry.py +38 -2
  25. griptape_nodes/retained_mode/events/execution_events.py +8 -1
  26. griptape_nodes/retained_mode/events/flow_events.py +90 -3
  27. griptape_nodes/retained_mode/events/node_events.py +17 -10
  28. griptape_nodes/retained_mode/events/workflow_events.py +5 -0
  29. griptape_nodes/retained_mode/griptape_nodes.py +16 -219
  30. griptape_nodes/retained_mode/managers/config_manager.py +0 -46
  31. griptape_nodes/retained_mode/managers/engine_identity_manager.py +225 -74
  32. griptape_nodes/retained_mode/managers/flow_manager.py +1276 -230
  33. griptape_nodes/retained_mode/managers/library_manager.py +7 -8
  34. griptape_nodes/retained_mode/managers/node_manager.py +197 -9
  35. griptape_nodes/retained_mode/managers/secrets_manager.py +26 -0
  36. griptape_nodes/retained_mode/managers/session_manager.py +264 -227
  37. griptape_nodes/retained_mode/managers/settings.py +4 -38
  38. griptape_nodes/retained_mode/managers/static_files_manager.py +3 -3
  39. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +135 -6
  40. griptape_nodes/retained_mode/managers/workflow_manager.py +206 -78
  41. griptape_nodes/servers/mcp.py +23 -15
  42. griptape_nodes/utils/async_utils.py +36 -0
  43. griptape_nodes/utils/dict_utils.py +8 -2
  44. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +11 -6
  45. griptape_nodes/version_compatibility/workflow_versions/v0_7_0/local_executor_argument_addition.py +12 -5
  46. {griptape_nodes-0.57.1.dist-info → griptape_nodes-0.58.1.dist-info}/METADATA +4 -3
  47. {griptape_nodes-0.57.1.dist-info → griptape_nodes-0.58.1.dist-info}/RECORD +49 -47
  48. {griptape_nodes-0.57.1.dist-info → griptape_nodes-0.58.1.dist-info}/WHEEL +1 -1
  49. griptape_nodes/retained_mode/utils/engine_identity.py +0 -245
  50. griptape_nodes/servers/ws_request_manager.py +0 -268
  51. {griptape_nodes-0.57.1.dist-info → griptape_nodes-0.58.1.dist-info}/entry_points.txt +0 -0
@@ -100,6 +100,7 @@ from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance.
100
100
  from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
101
101
  from griptape_nodes.retained_mode.managers.os_manager import OSManager
102
102
  from griptape_nodes.utils.async_utils import subprocess_run
103
+ from griptape_nodes.utils.dict_utils import merge_dicts
103
104
  from griptape_nodes.utils.uv_utils import find_uv_bin
104
105
  from griptape_nodes.utils.version_utils import get_complete_version_string
105
106
 
@@ -881,10 +882,9 @@ class LibraryManager:
881
882
  continue # SKIP IT
882
883
  else:
883
884
  # We had an existing category. Union our changes into it (not replacing anything that matched).
884
- existing_category_contents = get_category_result.contents
885
- existing_category_contents |= {
886
- k: v for k, v in library_data_setting.contents.items() if k not in existing_category_contents
887
- }
885
+ existing_category_contents = merge_dicts(
886
+ library_data_setting.contents, get_category_result.contents, add_keys=True, merge_lists=True
887
+ )
888
888
  set_category_request = SetConfigCategoryRequest(
889
889
  category=library_data_setting.category, contents=existing_category_contents
890
890
  )
@@ -1128,7 +1128,6 @@ class LibraryManager:
1128
1128
  lib_info = self.get_library_info_by_library_name(request.library_name)
1129
1129
  if lib_info:
1130
1130
  del self._library_file_path_to_info[lib_info.library_path]
1131
-
1132
1131
  details = f"Successfully unloaded (and unregistered) library '{request.library_name}'."
1133
1132
  return UnloadLibraryFromRegistryResultSuccess(result_details=details)
1134
1133
 
@@ -1545,12 +1544,12 @@ class LibraryManager:
1545
1544
  self._remove_missing_libraries_from_config(config_category=user_libraries_section)
1546
1545
 
1547
1546
  async def on_app_initialization_complete(self, _payload: AppInitializationComplete) -> None:
1548
- GriptapeNodes.EngineIdentityManager().initialize_engine_id()
1549
- GriptapeNodes.SessionManager().get_saved_session_id()
1550
-
1551
1547
  # App just got init'd. See if there are library JSONs to load!
1552
1548
  await self.load_all_libraries_from_config()
1553
1549
 
1550
+ # Register all secrets now that libraries are loaded and settings are merged
1551
+ GriptapeNodes.SecretsManager().register_all_secrets()
1552
+
1554
1553
  # We have to load all libraries before we attempt to load workflows.
1555
1554
 
1556
1555
  # Load workflows specified by libraries.
@@ -9,6 +9,7 @@ from griptape_nodes.exe_types.core_types import (
9
9
  Parameter,
10
10
  ParameterContainer,
11
11
  ParameterGroup,
12
+ ParameterMessage,
12
13
  ParameterMode,
13
14
  ParameterType,
14
15
  ParameterTypeBuiltin,
@@ -148,6 +149,18 @@ from griptape_nodes.retained_mode.managers.event_manager import EventManager
148
149
  logger = logging.getLogger("griptape_nodes")
149
150
 
150
151
 
152
+ class SerializedParameterValues(NamedTuple):
153
+ """Result of serializing parameter output values.
154
+
155
+ Attributes:
156
+ parameter_output_values: Either raw values or UUID references if pickling was used
157
+ unique_parameter_uuid_to_values: Dictionary of pickled values (None if no pickling needed)
158
+ """
159
+
160
+ parameter_output_values: dict[str, Any]
161
+ unique_parameter_uuid_to_values: dict[Any, Any] | None
162
+
163
+
151
164
  class NodeManager:
152
165
  _name_to_parent_flow_name: dict[str, str]
153
166
 
@@ -888,7 +901,7 @@ class NodeManager:
888
901
  has_non_control_types = True
889
902
  if request.input_types is not None:
890
903
  for test_type in request.input_types:
891
- if test_type.lower == ParameterTypeBuiltin.CONTROL_TYPE.value.lower():
904
+ if test_type.lower() == ParameterTypeBuiltin.CONTROL_TYPE.value.lower():
892
905
  has_control_type = True
893
906
  else:
894
907
  has_non_control_types = True
@@ -995,12 +1008,19 @@ class NodeManager:
995
1008
  if isinstance(element, ParameterGroup):
996
1009
  for child in element.find_elements_by_type(Parameter):
997
1010
  GriptapeNodes.handle_request(RemoveParameterFromNodeRequest(child.name, node_name))
998
- node.remove_parameter_element_by_name(request.parameter_name)
1011
+ node.remove_node_element(element)
999
1012
 
1000
1013
  return RemoveParameterFromNodeResultSuccess(
1001
1014
  result_details=f"Successfully removed parameter group '{request.parameter_name}' and all its children from node '{node_name}'."
1002
1015
  )
1003
1016
 
1017
+ if isinstance(element, ParameterMessage):
1018
+ node.remove_node_element(element)
1019
+
1020
+ return RemoveParameterFromNodeResultSuccess(
1021
+ result_details=f"Successfully removed parameter message '{request.parameter_name}' from node '{node_name}'."
1022
+ )
1023
+
1004
1024
  # No tricky stuff, users!
1005
1025
  # if user_defined doesn't exist, or is false, then it's not user-defined
1006
1026
  if not getattr(element, "user_defined", False):
@@ -1528,11 +1548,11 @@ class NodeManager:
1528
1548
  details = f"Attempted to set parameter value for '{node_name}.{request.parameter_name}'. Failed because that Parameter was flagged as not settable."
1529
1549
  result = SetParameterValueResultFailure(result_details=details)
1530
1550
  return result
1531
-
1532
- # Well this seems kind of stupid
1533
1551
  object_type = request.data_type if request.data_type else parameter.type
1534
- # Is this value kosher for the types allowed?
1535
- if not parameter.is_incoming_type_allowed(object_type):
1552
+ # If the parameter is control type, we shouldn't check the value being set, since it's just a marker for which path to take, not a real value, and will likely be a string, which doesn't match ControlType.
1553
+ if parameter.type != ParameterTypeBuiltin.CONTROL_TYPE.value and not parameter.is_incoming_type_allowed(
1554
+ object_type
1555
+ ):
1536
1556
  details = f"Attempted to set parameter value for '{node_name}.{request.parameter_name}'. Failed because the value's type of '{object_type}' was not in the Parameter's list of allowed types: {parameter.input_types}."
1537
1557
 
1538
1558
  result = SetParameterValueResultFailure(result_details=details)
@@ -2308,9 +2328,10 @@ class NodeManager:
2308
2328
  set_parameter_value_commands=parameter_commands,
2309
2329
  set_lock_commands_per_node=lock_commands,
2310
2330
  )
2311
- # Set everything in the clipboard!
2312
- GriptapeNodes.ContextManager()._clipboard.node_commands = final_result
2313
- GriptapeNodes.ContextManager()._clipboard.parameter_uuid_to_values = unique_uuid_to_values
2331
+ # Set everything in the clipboard if requested
2332
+ if request.copy_to_clipboard:
2333
+ GriptapeNodes.ContextManager()._clipboard.node_commands = final_result
2334
+ GriptapeNodes.ContextManager()._clipboard.parameter_uuid_to_values = unique_uuid_to_values
2314
2335
  return SerializeSelectedNodesToCommandsResultSuccess(
2315
2336
  final_result,
2316
2337
  result_details=f"Successfully serialized {len(request.nodes_to_serialize)} selected nodes to commands.",
@@ -2573,6 +2594,173 @@ class NodeManager:
2573
2594
  commands.append(output_command)
2574
2595
  return commands if commands else None
2575
2596
 
2597
+ @staticmethod
2598
+ def serialize_parameter_output_values(node: BaseNode, *, use_pickling: bool = False) -> SerializedParameterValues:
2599
+ """Serialize parameter output values with optional pickling for complex objects.
2600
+
2601
+ Args:
2602
+ node: The node whose parameter output values should be serialized
2603
+ use_pickling: If True, use pickle-based serialization; if False, use TypeValidator.safe_serialize
2604
+
2605
+ Returns:
2606
+ SerializedParameterValues containing:
2607
+ - parameter_output_values: Either raw values or UUID references if pickling was used
2608
+ - unique_parameter_uuid_to_values: Dictionary of pickled values (None if no pickling needed)
2609
+ """
2610
+ if not node.parameters:
2611
+ return SerializedParameterValues({}, None)
2612
+
2613
+ if not use_pickling:
2614
+ return NodeManager._serialize_without_pickling(node)
2615
+
2616
+ return NodeManager._serialize_with_pickling(node)
2617
+
2618
+ @staticmethod
2619
+ def _serialize_without_pickling(node: BaseNode) -> SerializedParameterValues:
2620
+ """Serialize parameter values using simple TypeValidator serialization.
2621
+
2622
+ Args:
2623
+ node: The node whose parameter values should be serialized
2624
+
2625
+ Returns:
2626
+ SerializedParameterValues with no pickling
2627
+ """
2628
+ param_values = {}
2629
+ for param in node.parameters:
2630
+ if param.name in node.parameter_output_values:
2631
+ param_values[param.name] = node.parameter_output_values[param.name]
2632
+ else:
2633
+ param_values[param.name] = node.get_parameter_value(param.name)
2634
+ simple_values = TypeValidator.safe_serialize(param_values)
2635
+ return SerializedParameterValues(simple_values, None)
2636
+
2637
+ @staticmethod
2638
+ def _serialize_with_pickling(
2639
+ node: BaseNode,
2640
+ ) -> SerializedParameterValues:
2641
+ """Serialize parameter values using pickle-based serialization with UUID references.
2642
+
2643
+ Args:
2644
+ node: The node whose parameter values should be serialized
2645
+
2646
+ Returns:
2647
+ SerializedParameterValues with pickled values
2648
+ """
2649
+ unique_parameter_uuid_to_values = {}
2650
+ serialized_parameter_value_tracker = SerializedParameterValueTracker()
2651
+ uuid_referenced_values = {}
2652
+
2653
+ for parameter in node.parameters:
2654
+ param_name = parameter.name
2655
+ param_value = NodeManager._get_parameter_value_for_serialization(node, param_name)
2656
+
2657
+ unique_uuid = NodeManager._process_parameter_for_pickling(
2658
+ param_value,
2659
+ param_name,
2660
+ serialized_parameter_value_tracker,
2661
+ unique_parameter_uuid_to_values,
2662
+ uuid_referenced_values,
2663
+ )
2664
+
2665
+ uuid_referenced_values[param_name] = unique_uuid
2666
+
2667
+ return SerializedParameterValues(
2668
+ uuid_referenced_values, unique_parameter_uuid_to_values if unique_parameter_uuid_to_values else None
2669
+ )
2670
+
2671
+ @staticmethod
2672
+ def _get_parameter_value_for_serialization(node: BaseNode, param_name: str) -> Any:
2673
+ """Get parameter value for serialization, checking output values first.
2674
+
2675
+ Args:
2676
+ node: The node to get the parameter value from
2677
+ param_name: The parameter name
2678
+
2679
+ Returns:
2680
+ The parameter value
2681
+ """
2682
+ if param_name in node.parameter_output_values:
2683
+ return node.parameter_output_values[param_name]
2684
+ return node.get_parameter_value(param_name)
2685
+
2686
+ @staticmethod
2687
+ def _process_parameter_for_pickling(
2688
+ param_value: Any,
2689
+ param_name: str,
2690
+ tracker: SerializedParameterValueTracker,
2691
+ unique_parameter_uuid_to_values: dict,
2692
+ uuid_referenced_values: dict,
2693
+ ) -> SerializedNodeCommands.UniqueParameterValueUUID | None:
2694
+ """Process a parameter value for pickle-based serialization.
2695
+
2696
+ Args:
2697
+ param_value: The value to serialize
2698
+ param_name: Parameter name for tracking
2699
+ tracker: Tracker for managing serialization state
2700
+ unique_parameter_uuid_to_values: Dictionary to store pickled values
2701
+ uuid_referenced_values: Dictionary to store UUID references
2702
+
2703
+ Returns:
2704
+ UUID reference for the value, or None if not serializable
2705
+ """
2706
+ try:
2707
+ hash(param_value)
2708
+ value_id = param_value
2709
+ except TypeError:
2710
+ value_id = id(param_value)
2711
+
2712
+ tracker_status = tracker.get_tracker_state(value_id)
2713
+
2714
+ match tracker_status:
2715
+ case SerializedParameterValueTracker.TrackerState.SERIALIZABLE:
2716
+ return tracker.get_uuid_for_value_hash(value_id)
2717
+ case SerializedParameterValueTracker.TrackerState.NOT_SERIALIZABLE:
2718
+ uuid_referenced_values[param_name] = None
2719
+ return None
2720
+ case SerializedParameterValueTracker.TrackerState.NOT_IN_TRACKER:
2721
+ return NodeManager._handle_new_value_for_pickling(
2722
+ param_value, param_name, tracker, unique_parameter_uuid_to_values, uuid_referenced_values
2723
+ )
2724
+
2725
+ @staticmethod
2726
+ def _handle_new_value_for_pickling(
2727
+ param_value: Any,
2728
+ param_name: str,
2729
+ tracker: SerializedParameterValueTracker,
2730
+ unique_parameter_uuid_to_values: dict,
2731
+ uuid_referenced_values: dict,
2732
+ ) -> SerializedNodeCommands.UniqueParameterValueUUID | None:
2733
+ """Handle a new value that hasn't been seen before in pickling serialization.
2734
+
2735
+ Args:
2736
+ param_value: The value to pickle
2737
+ param_name: Parameter name for tracking
2738
+ tracker: Tracker for managing serialization state
2739
+ unique_parameter_uuid_to_values: Dictionary to store pickled values
2740
+ uuid_referenced_values: Dictionary to store UUID references
2741
+
2742
+ Returns:
2743
+ UUID reference for the value, or None if not serializable
2744
+ """
2745
+ try:
2746
+ hash(param_value)
2747
+ value_id = param_value
2748
+ except TypeError:
2749
+ value_id = id(param_value)
2750
+
2751
+ try:
2752
+ workflow_manager = GriptapeNodes.WorkflowManager()
2753
+ pickled_bytes = workflow_manager._patch_and_pickle_object(param_value)
2754
+ except Exception:
2755
+ tracker.add_as_not_serializable(value_id)
2756
+ uuid_referenced_values[param_name] = None
2757
+ return None
2758
+
2759
+ unique_uuid = SerializedNodeCommands.UniqueParameterValueUUID(str(uuid4()))
2760
+ unique_parameter_uuid_to_values[unique_uuid] = pickled_bytes
2761
+ tracker.add_as_serializable(value_id, unique_uuid)
2762
+ return unique_uuid
2763
+
2576
2764
  def on_rename_parameter_request(self, request: RenameParameterRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912
2577
2765
  """Handle renaming a parameter on a node.
2578
2766
 
@@ -2,6 +2,7 @@ import logging
2
2
  import re
3
3
  from os import getenv
4
4
  from pathlib import Path
5
+ from typing import Literal, overload
5
6
 
6
7
  from dotenv import dotenv_values, get_key, load_dotenv, set_key, unset_key
7
8
  from dotenv.main import DotEnv
@@ -51,6 +52,25 @@ class SecretsManager:
51
52
  def workspace_env_path(self) -> Path:
52
53
  return self.config_manager.workspace_path / ".env"
53
54
 
55
+ def register_all_secrets(self) -> None:
56
+ """Register all secrets from config and library settings.
57
+
58
+ This should be called after libraries are loaded and their settings
59
+ are merged into the config.
60
+ """
61
+ secret_names = set()
62
+
63
+ secrets_to_register = self.config_manager.get_config_value(
64
+ "app_events.on_app_initialization_complete.secrets_to_register", default=[]
65
+ )
66
+
67
+ secret_names.update(secrets_to_register)
68
+
69
+ # Register each secret (create blank entry if doesn't exist)
70
+ for secret_name in secret_names:
71
+ if self.get_secret(secret_name, should_error_on_not_found=False) is None:
72
+ self.set_secret(secret_name, "")
73
+
54
74
  def on_handle_get_secret_request(self, request: GetSecretValueRequest) -> ResultPayload:
55
75
  secret_key = SecretsManager._apply_secret_name_compliance(request.key)
56
76
  secret_value = self.get_secret(secret_key, should_error_on_not_found=request.should_error_on_not_found)
@@ -107,6 +127,12 @@ class SecretsManager:
107
127
 
108
128
  return DeleteSecretValueResultSuccess(result_details=f"Successfully deleted secret: {secret_name}")
109
129
 
130
+ @overload
131
+ def get_secret(self, secret_name: str, *, should_error_on_not_found: Literal[True] = True) -> str: ...
132
+
133
+ @overload
134
+ def get_secret(self, secret_name: str, *, should_error_on_not_found: Literal[False]) -> str | None: ...
135
+
110
136
  def get_secret(self, secret_name: str, *, should_error_on_not_found: bool = True) -> str | None:
111
137
  """Return the secret value with the following search precedence (highest to lowest priority).
112
138