griptape-nodes 0.49.0__tar.gz → 0.50.0__tar.gz

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 (126) hide show
  1. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/PKG-INFO +1 -1
  2. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/pyproject.toml +1 -1
  3. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/exe_types/core_types.py +8 -0
  4. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/exe_types/node_types.py +275 -1
  5. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/node_events.py +53 -0
  6. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/parameter_events.py +2 -0
  7. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/flow_manager.py +26 -1
  8. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/node_manager.py +202 -19
  9. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/README.md +0 -0
  10. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/__init__.py +0 -0
  11. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/app/.python-version +0 -0
  12. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/app/__init__.py +0 -0
  13. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/app/api.py +0 -0
  14. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/app/app.py +0 -0
  15. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/app/watch.py +0 -0
  16. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/bootstrap/__init__.py +0 -0
  17. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
  18. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +0 -0
  19. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +0 -0
  20. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +0 -0
  21. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/drivers/__init__.py +0 -0
  22. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/drivers/storage/__init__.py +0 -0
  23. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/drivers/storage/base_storage_driver.py +0 -0
  24. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +0 -0
  25. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/drivers/storage/local_storage_driver.py +0 -0
  26. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/drivers/storage/storage_backend.py +0 -0
  27. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/exe_types/__init__.py +0 -0
  28. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/exe_types/connections.py +0 -0
  29. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/exe_types/flow.py +0 -0
  30. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/exe_types/type_validator.py +0 -0
  31. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/machines/__init__.py +0 -0
  32. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/machines/control_flow.py +0 -0
  33. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/machines/fsm.py +0 -0
  34. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/machines/node_resolution.py +0 -0
  35. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/mcp_server/__init__.py +0 -0
  36. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/mcp_server/server.py +0 -0
  37. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/mcp_server/ws_request_manager.py +0 -0
  38. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/node_library/__init__.py +0 -0
  39. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/node_library/advanced_node_library.py +0 -0
  40. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/node_library/library_registry.py +0 -0
  41. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/node_library/workflow_registry.py +0 -0
  42. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/py.typed +0 -0
  43. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/__init__.py +0 -0
  44. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/__init__.py +0 -0
  45. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/agent_events.py +0 -0
  46. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/app_events.py +0 -0
  47. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/arbitrary_python_events.py +0 -0
  48. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/base_events.py +0 -0
  49. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/config_events.py +0 -0
  50. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/connection_events.py +0 -0
  51. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/context_events.py +0 -0
  52. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/execution_events.py +0 -0
  53. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/flow_events.py +0 -0
  54. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
  55. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/library_events.py +0 -0
  56. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/logger_events.py +0 -0
  57. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/object_events.py +0 -0
  58. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/os_events.py +0 -0
  59. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/payload_registry.py +0 -0
  60. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/secrets_events.py +0 -0
  61. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/static_file_events.py +0 -0
  62. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/sync_events.py +0 -0
  63. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/validation_events.py +0 -0
  64. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/events/workflow_events.py +0 -0
  65. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/griptape_nodes.py +0 -0
  66. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/__init__.py +0 -0
  67. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/agent_manager.py +0 -0
  68. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
  69. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/config_manager.py +0 -0
  70. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/context_manager.py +0 -0
  71. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/engine_identity_manager.py +0 -0
  72. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/event_manager.py +0 -0
  73. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +0 -0
  74. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +0 -0
  75. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +0 -0
  76. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +0 -0
  77. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +0 -0
  78. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +0 -0
  79. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +0 -0
  80. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +0 -0
  81. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +0 -0
  82. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +0 -0
  83. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +0 -0
  84. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +0 -0
  85. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/library_manager.py +0 -0
  86. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/object_manager.py +0 -0
  87. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
  88. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/os_manager.py +0 -0
  89. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/secrets_manager.py +0 -0
  90. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/session_manager.py +0 -0
  91. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/settings.py +0 -0
  92. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
  93. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/sync_manager.py +0 -0
  94. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/version_compatibility_manager.py +0 -0
  95. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/managers/workflow_manager.py +0 -0
  96. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/retained_mode.py +0 -0
  97. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/utils/__init__.py +0 -0
  98. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/utils/engine_identity.py +0 -0
  99. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/retained_mode/utils/name_generator.py +0 -0
  100. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/traits/__init__.py +0 -0
  101. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/traits/add_param_button.py +0 -0
  102. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/traits/button.py +0 -0
  103. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/traits/clamp.py +0 -0
  104. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/traits/compare.py +0 -0
  105. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/traits/compare_images.py +0 -0
  106. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/traits/file_system_picker.py +0 -0
  107. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/traits/minmax.py +0 -0
  108. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/traits/options.py +0 -0
  109. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/traits/slider.py +0 -0
  110. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/traits/trait_registry.py +0 -0
  111. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/traits/traits.json +0 -0
  112. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/updater/__init__.py +0 -0
  113. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/updater/__main__.py +0 -0
  114. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/utils/__init__.py +0 -0
  115. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/utils/dict_utils.py +0 -0
  116. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/utils/image_preview.py +0 -0
  117. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/utils/metaclasses.py +0 -0
  118. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/utils/uv_utils.py +0 -0
  119. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/utils/version_utils.py +0 -0
  120. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/version_compatibility/__init__.py +0 -0
  121. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/version_compatibility/versions/__init__.py +0 -0
  122. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
  123. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +0 -0
  124. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/version_compatibility/workflow_versions/__init__.py +0 -0
  125. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/version_compatibility/workflow_versions/v0_7_0/__init__.py +0 -0
  126. {griptape_nodes-0.49.0 → griptape_nodes-0.50.0}/src/griptape_nodes/version_compatibility/workflow_versions/v0_7_0/local_executor_argument_addition.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: griptape-nodes
3
- Version: 0.49.0
3
+ Version: 0.50.0
4
4
  Summary: Add your description here
5
5
  Requires-Dist: griptape>=1.8.0
6
6
  Requires-Dist: pydantic>=2.10.6
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "griptape-nodes"
3
- version = "0.49.0"
3
+ version = "0.50.0"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12.0, <3.13"
@@ -714,6 +714,10 @@ class Parameter(BaseNodeElement, UIOptionsMixin):
714
714
  # During save/load, this value IS still serialized to save its proper state.
715
715
  settable: bool = True
716
716
 
717
+ # "serializable" controls whether parameter values should be serialized during save/load operations.
718
+ # Set to False for parameters containing non-serializable types (ImageDrivers, PromptDrivers, file handles, etc.)
719
+ serializable: bool = True
720
+
717
721
  user_defined: bool = False
718
722
  _allowed_modes: set = field(
719
723
  default_factory=lambda: {
@@ -747,6 +751,7 @@ class Parameter(BaseNodeElement, UIOptionsMixin):
747
751
  ui_options: dict | None = None,
748
752
  *,
749
753
  settable: bool = True,
754
+ serializable: bool = True,
750
755
  user_defined: bool = False,
751
756
  element_id: str | None = None,
752
757
  element_type: str | None = None,
@@ -764,6 +769,7 @@ class Parameter(BaseNodeElement, UIOptionsMixin):
764
769
  self.tooltip_as_property = tooltip_as_property
765
770
  self.tooltip_as_output = tooltip_as_output
766
771
  self.settable = settable
772
+ self.serializable = serializable
767
773
  self.user_defined = user_defined
768
774
  if allowed_modes is None:
769
775
  self._allowed_modes = {ParameterMode.INPUT, ParameterMode.OUTPUT, ParameterMode.PROPERTY}
@@ -813,6 +819,8 @@ class Parameter(BaseNodeElement, UIOptionsMixin):
813
819
  our_dict["tooltip_as_property"] = self.tooltip_as_property
814
820
 
815
821
  our_dict["is_user_defined"] = self.user_defined
822
+ our_dict["settable"] = self.settable
823
+ our_dict["serializable"] = self.serializable
816
824
  our_dict["ui_options"] = self.ui_options
817
825
 
818
826
  # Let's bundle up the mode details.
@@ -4,7 +4,7 @@ import logging
4
4
  from abc import ABC, abstractmethod
5
5
  from collections.abc import Callable, Generator, Iterable
6
6
  from enum import StrEnum, auto
7
- from typing import Any, TypeVar
7
+ from typing import Any, NamedTuple, TypeVar
8
8
 
9
9
  from griptape.events import BaseEvent, EventBus
10
10
 
@@ -26,12 +26,14 @@ from griptape_nodes.retained_mode.events.base_events import (
26
26
  ExecutionEvent,
27
27
  ExecutionGriptapeNodeEvent,
28
28
  ProgressEvent,
29
+ RequestPayload,
29
30
  )
30
31
  from griptape_nodes.retained_mode.events.execution_events import (
31
32
  NodeUnresolvedEvent,
32
33
  ParameterValueUpdateEvent,
33
34
  )
34
35
  from griptape_nodes.retained_mode.events.parameter_events import (
36
+ AddParameterToNodeRequest,
35
37
  RemoveElementEvent,
36
38
  RemoveParameterFromNodeRequest,
37
39
  )
@@ -52,6 +54,23 @@ class NodeResolutionState(StrEnum):
52
54
  RESOLVED = auto()
53
55
 
54
56
 
57
+ class NodeMessageResult(NamedTuple):
58
+ """Result from a node message callback.
59
+
60
+ Attributes:
61
+ success: True if the message was handled successfully, False otherwise
62
+ details: Human-readable description of what happened
63
+ response: Optional response data to return to the sender
64
+ altered_workflow_state: True if the message handling altered workflow state.
65
+ Clients can use this to determine if the workflow needs to be re-saved.
66
+ """
67
+
68
+ success: bool
69
+ details: str
70
+ response: Any = None
71
+ altered_workflow_state: bool = True
72
+
73
+
55
74
  class BaseNode(ABC):
56
75
  # Owned by a flow
57
76
  name: str
@@ -148,6 +167,15 @@ class BaseNode(ABC):
148
167
  """Callback to confirm allowing a Connection going OUT of this Node."""
149
168
  return True
150
169
 
170
+ def before_incoming_connection(
171
+ self,
172
+ source_node: BaseNode, # noqa: ARG002
173
+ source_parameter_name: str, # noqa: ARG002
174
+ target_parameter_name: str, # noqa: ARG002
175
+ ) -> None:
176
+ """Callback before validating a Connection coming TO this Node."""
177
+ return
178
+
151
179
  def after_incoming_connection(
152
180
  self,
153
181
  source_node: BaseNode, # noqa: ARG002
@@ -157,6 +185,15 @@ class BaseNode(ABC):
157
185
  """Callback after a Connection has been established TO this Node."""
158
186
  return
159
187
 
188
+ def before_outgoing_connection(
189
+ self,
190
+ source_parameter_name: str, # noqa: ARG002
191
+ target_node: BaseNode, # noqa: ARG002
192
+ target_parameter_name: str, # noqa: ARG002
193
+ ) -> None:
194
+ """Callback before validating a Connection going OUT of this Node."""
195
+ return
196
+
160
197
  def after_outgoing_connection(
161
198
  self,
162
199
  source_parameter: Parameter, # noqa: ARG002
@@ -251,6 +288,31 @@ class BaseNode(ABC):
251
288
  """Callback for when a Griptape Event comes destined for this Node."""
252
289
  return
253
290
 
291
+ def on_node_message_received(
292
+ self,
293
+ optional_element_name: str | None, # noqa: ARG002
294
+ message_type: str,
295
+ message: Any, # noqa: ARG002
296
+ ) -> NodeMessageResult:
297
+ """Callback for when a message is sent directly to this node.
298
+
299
+ Custom nodes may elect to override this method to handle specific message types
300
+ and implement custom communication patterns with external systems.
301
+
302
+ Args:
303
+ optional_element_name: Optional element name this message relates to
304
+ message_type: String indicating the message type for parsing
305
+ message: Message payload of any type
306
+
307
+ Returns:
308
+ NodeMessageResult: Result containing success status, details, and optional response
309
+ """
310
+ return NodeMessageResult(
311
+ success=False,
312
+ details=f"Node '{self.name}' was sent a message of type '{message_type}'. Failed because no message handler was specified for this node. Implement the on_node_message_received method in this node class in order for it to receive messages.",
313
+ response=None,
314
+ )
315
+
254
316
  def does_name_exist(self, param_name: str) -> bool:
255
317
  for parameter in self.parameters:
256
318
  if parameter.name == param_name:
@@ -1070,6 +1132,218 @@ class EndLoopNode(BaseNode):
1070
1132
  """Creating class for Start Loop Node in order to implement loop functionality in execution."""
1071
1133
 
1072
1134
 
1135
+ class ErrorProxyNode(BaseNode):
1136
+ """A proxy node that substitutes for nodes that failed to create due to missing dependencies or errors.
1137
+
1138
+ This node maintains the original node type information and allows workflows to continue loading
1139
+ even when some node types are unavailable. It generates parameters dynamically as connections
1140
+ and values are assigned to maintain workflow structure.
1141
+ """
1142
+
1143
+ def __init__(
1144
+ self,
1145
+ name: str,
1146
+ original_node_type: str,
1147
+ original_library_name: str,
1148
+ failure_reason: str,
1149
+ metadata: dict[Any, Any] | None = None,
1150
+ ) -> None:
1151
+ super().__init__(name, metadata)
1152
+
1153
+ self.original_node_type = original_node_type
1154
+ self.original_library_name = original_library_name
1155
+ self.failure_reason = failure_reason
1156
+ # Record ALL initial_setup=True requests in order for 1:1 replay
1157
+ self._recorded_initialization_requests: list[RequestPayload] = []
1158
+
1159
+ # Track if user has made connection modifications after initial setup
1160
+ self._has_connection_modifications: bool = False
1161
+
1162
+ # Add error message parameter explaining the failure
1163
+ self._error_message = ParameterMessage(
1164
+ name="error_proxy_message",
1165
+ variant="error",
1166
+ value="", # Will be set by _update_error_message
1167
+ )
1168
+ self.add_node_element(self._error_message)
1169
+ self._update_error_message()
1170
+
1171
+ def _get_base_error_message(self) -> str:
1172
+ """Generate the base error message for this ErrorProxyNode."""
1173
+ return (
1174
+ f"This is a placeholder for a node of type '{self.original_node_type}'"
1175
+ f"\nfrom the '{self.original_library_name}' library."
1176
+ f"\nIt encountered a problem when loading."
1177
+ f"\nThe technical issue:\n{self.failure_reason}\n\n"
1178
+ f"Your original node will be restored once the issue above is fixed"
1179
+ f"(which may require registering the appropriate library, or getting"
1180
+ f"a code fix from the node author)."
1181
+ )
1182
+
1183
+ def on_attempt_set_parameter_value(self, param_name: str) -> None:
1184
+ """Public method to attempt setting a parameter value during initial setup.
1185
+
1186
+ Creates a PROPERTY mode parameter if it doesn't exist to support value setting.
1187
+
1188
+ Args:
1189
+ param_name: Name of the parameter to prepare for value setting
1190
+ """
1191
+ self._ensure_parameter_exists(param_name)
1192
+
1193
+ def _ensure_parameter_exists(self, param_name: str) -> None:
1194
+ """Ensures a parameter exists on this node.
1195
+
1196
+ Creates a universal parameter with all modes enabled for maximum flexibility.
1197
+ Auto-generated parameters are marked as non-user-defined so they don't get serialized.
1198
+
1199
+ Args:
1200
+ param_name: Name of the parameter to ensure exists
1201
+ """
1202
+ existing_param = super().get_parameter_by_name(param_name)
1203
+
1204
+ if existing_param is None:
1205
+ # Create new universal parameter with all modes enabled
1206
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
1207
+
1208
+ request = AddParameterToNodeRequest(
1209
+ node_name=self.name,
1210
+ parameter_name=param_name,
1211
+ type=ParameterTypeBuiltin.ANY.value, # ANY = parameter's main type for maximum flexibility
1212
+ input_types=[ParameterTypeBuiltin.ANY.value], # ANY = accepts any single input type
1213
+ output_type=ParameterTypeBuiltin.ALL.value, # ALL = can output any type (passthrough)
1214
+ tooltip="Parameter created for placeholder node to preserve workflow connections",
1215
+ mode_allowed_input=True, # Enable all modes upfront
1216
+ mode_allowed_output=True,
1217
+ mode_allowed_property=True,
1218
+ is_user_defined=False, # Don't serialize this parameter
1219
+ initial_setup=True, # Allows setting non-settable parameters and prevents resolution cascades during workflow loading
1220
+ )
1221
+ result = GriptapeNodes.handle_request(request)
1222
+
1223
+ # Check if parameter creation was successful
1224
+ from griptape_nodes.retained_mode.events.parameter_events import AddParameterToNodeResultSuccess
1225
+
1226
+ if not isinstance(result, AddParameterToNodeResultSuccess):
1227
+ failure_message = f"Failed to create parameter '{param_name}': {result.result_details}"
1228
+ raise RuntimeError(failure_message)
1229
+ # If parameter already exists, nothing to do - it already has all modes
1230
+
1231
+ def allow_incoming_connection(
1232
+ self,
1233
+ source_node: BaseNode, # noqa: ARG002
1234
+ source_parameter: Parameter, # noqa: ARG002
1235
+ target_parameter: Parameter, # noqa: ARG002
1236
+ ) -> bool:
1237
+ """ErrorProxyNode allows connections - it's a shell for maintaining connections."""
1238
+ return True
1239
+
1240
+ def allow_outgoing_connection(
1241
+ self,
1242
+ source_parameter: Parameter, # noqa: ARG002
1243
+ target_node: BaseNode, # noqa: ARG002
1244
+ target_parameter: Parameter, # noqa: ARG002
1245
+ ) -> bool:
1246
+ """ErrorProxyNode allows connections - it's a shell for maintaining connections."""
1247
+ return True
1248
+
1249
+ def before_incoming_connection(
1250
+ self,
1251
+ source_node: BaseNode, # noqa: ARG002
1252
+ source_parameter_name: str, # noqa: ARG002
1253
+ target_parameter_name: str,
1254
+ ) -> None:
1255
+ """Create target parameter before connection validation."""
1256
+ self._ensure_parameter_exists(target_parameter_name)
1257
+
1258
+ def before_outgoing_connection(
1259
+ self,
1260
+ source_parameter_name: str,
1261
+ target_node: BaseNode, # noqa: ARG002
1262
+ target_parameter_name: str, # noqa: ARG002
1263
+ ) -> None:
1264
+ """Create source parameter before connection validation."""
1265
+ self._ensure_parameter_exists(source_parameter_name)
1266
+
1267
+ def set_post_init_connections_modified(self) -> None:
1268
+ """Mark that user-initiated connections have been modified and update the warning message."""
1269
+ if not self._has_connection_modifications:
1270
+ self._has_connection_modifications = True
1271
+ self._update_error_message()
1272
+
1273
+ def _update_error_message(self) -> None:
1274
+ """Update the ParameterMessage to include connection modification warning."""
1275
+ # Build the updated message with connection warning
1276
+ base_message = self._get_base_error_message()
1277
+
1278
+ # Add connection modification warning if applicable
1279
+ if self._has_connection_modifications:
1280
+ connection_warning = (
1281
+ "\n\nWARNING: You have modified connections to this placeholder node."
1282
+ "\nThis may require manual fixes when the original node is restored."
1283
+ )
1284
+ final_message = base_message + connection_warning
1285
+ else:
1286
+ # Add the general note only if no modifications have been made
1287
+ general_warning = (
1288
+ "\n\nNote: Making changes to this node may require manual fixes when restored,"
1289
+ "\nas we can't predict how all node authors craft their custom nodes."
1290
+ )
1291
+ final_message = base_message + general_warning
1292
+
1293
+ # Update the error message value
1294
+ self._error_message.value = final_message
1295
+
1296
+ def validate_before_node_run(self) -> list[Exception] | None:
1297
+ """Prevent ErrorProxy nodes from running - validate at node level only."""
1298
+ error_msg = (
1299
+ f"Cannot run node '{self.name}': This is a placeholder node put in place to preserve your workflow until the breaking issue is fixed.\n\n"
1300
+ f"The original '{self.original_node_type}' from library '{self.original_library_name}' failed to load due to this technical issue:\n\n"
1301
+ f"{self.failure_reason}\n\n"
1302
+ f"Once you resolve the issue above, reload this workflow and the placeholder will be automatically replaced with the original node."
1303
+ )
1304
+ return [RuntimeError(error_msg)]
1305
+
1306
+ def record_initialization_request(self, request: RequestPayload) -> None:
1307
+ """Record an initialization request for replay during serialization.
1308
+
1309
+ This method captures requests that modify ErrorProxyNode structure during workflow loading,
1310
+ preserving information needed for restoration when the original node becomes available.
1311
+
1312
+ WHAT WE RECORD:
1313
+ - AlterParameterDetailsRequest: Parameter modifications from original node definition
1314
+ - Any request with initial_setup=True that changes node structure in ways that cannot
1315
+ be reconstructed from final state alone
1316
+
1317
+ WHAT WE DO NOT RECORD (and why):
1318
+ - SetParameterValueRequest: Final parameter values are serialized normally via parameter_values
1319
+ - AddParameterToNodeRequest: User-defined parameters are serialized via is_user_defined=True flag
1320
+ - CreateConnectionRequest: Connections are serialized separately and recreated during loading
1321
+ - RenameParameterRequest: Final parameter names are preserved in serialized state
1322
+ - SetNodeMetadataRequest: Final metadata state is preserved in node.metadata
1323
+ - SetLockNodeStateRequest: Final lock state is preserved in node.lock
1324
+ """
1325
+ self._recorded_initialization_requests.append(request)
1326
+
1327
+ def get_recorded_initialization_requests(self, request_type: type | None = None) -> list[RequestPayload]:
1328
+ """Get recorded initialization requests for 1:1 serialization replay.
1329
+
1330
+ Args:
1331
+ request_type: Optional class to filter by. If provided, only returns requests
1332
+ of that type. If None, returns all recorded requests.
1333
+
1334
+ Returns:
1335
+ List of recorded requests in the order they were received.
1336
+ """
1337
+ if request_type is None:
1338
+ return self._recorded_initialization_requests
1339
+
1340
+ return [req for req in self._recorded_initialization_requests if isinstance(req, request_type)]
1341
+
1342
+ def process(self) -> Any:
1343
+ """No-op process method. Error Proxy nodes do nothing during execution."""
1344
+ return None
1345
+
1346
+
1073
1347
  class Connection:
1074
1348
  source_node: BaseNode
1075
1349
  target_node: BaseNode
@@ -45,6 +45,7 @@ class CreateNodeRequest(RequestPayload):
45
45
  resolution: Initial resolution state (defaults to UNRESOLVED)
46
46
  initial_setup: Skip setup work when loading from file (defaults to False)
47
47
  set_as_new_context: Set this node as current context after creation (defaults to False)
48
+ create_error_proxy_on_failure: Create Error Proxy node if creation fails (defaults to True)
48
49
 
49
50
  Results: CreateNodeResultSuccess (with assigned name) | CreateNodeResultFailure (invalid type, missing library, flow not found)
50
51
  """
@@ -60,6 +61,8 @@ class CreateNodeRequest(RequestPayload):
60
61
  initial_setup: bool = False
61
62
  # When True, this Node will be pushed as the current Node within the Current Context.
62
63
  set_as_new_context: bool = False
64
+ # When True, create an Error Proxy node if the requested node type fails to create
65
+ create_error_proxy_on_failure: bool = True
63
66
 
64
67
 
65
68
  @dataclass
@@ -677,3 +680,53 @@ class DuplicateSelectedNodesResultFailure(WorkflowNotAlteredMixin, ResultPayload
677
680
  Common causes: nodes not found, constraints/conflicts,
678
681
  insufficient resources, connection duplication failures.
679
682
  """
683
+
684
+
685
+ @dataclass
686
+ @PayloadRegistry.register
687
+ class SendNodeMessageRequest(RequestPayload):
688
+ """Send a message to a specific node.
689
+
690
+ Use when: External systems need to signal or send data directly to individual nodes,
691
+ implementing custom communication patterns, triggering node-specific behaviors.
692
+
693
+ Args:
694
+ node_name: Name of the target node (None for current context node)
695
+ optional_element_name: Optional element name this message relates to
696
+ message_type: String indicating message type for receiver parsing
697
+ message: Message payload of any type
698
+
699
+ Results: SendNodeMessageResultSuccess (with response) | SendNodeMessageResultFailure (node not found, handler error)
700
+ """
701
+
702
+ message_type: str
703
+ message: Any
704
+ node_name: str | None = None
705
+ optional_element_name: str | None = None
706
+
707
+
708
+ @dataclass
709
+ @PayloadRegistry.register
710
+ class SendNodeMessageResultSuccess(ResultPayloadSuccess):
711
+ """Node message sent and processed successfully.
712
+
713
+ Args:
714
+ response: Optional response data from the node's message handler
715
+ """
716
+
717
+ response: Any = None
718
+
719
+
720
+ @dataclass
721
+ @PayloadRegistry.register
722
+ class SendNodeMessageResultFailure(ResultPayloadFailure):
723
+ """Node message sending failed.
724
+
725
+ Common causes: node not found, no current context, message handler error,
726
+ unsupported message type.
727
+
728
+ Args:
729
+ response: Optional response data from the node's message handler (even on failure)
730
+ """
731
+
732
+ response: Any = None
@@ -40,6 +40,7 @@ class AddParameterToNodeRequest(RequestPayload):
40
40
  mode_allowed_input: Whether parameter can be used as input
41
41
  mode_allowed_property: Whether parameter can be used as property
42
42
  mode_allowed_output: Whether parameter can be used as output
43
+ is_user_defined: Whether this is a user-defined parameter (affects serialization)
43
44
  parent_container_name: Name of parent container if nested
44
45
  initial_setup: Skip setup work when loading from file
45
46
 
@@ -61,6 +62,7 @@ class AddParameterToNodeRequest(RequestPayload):
61
62
  mode_allowed_input: bool = Field(default=True)
62
63
  mode_allowed_property: bool = Field(default=True)
63
64
  mode_allowed_output: bool = Field(default=True)
65
+ is_user_defined: bool = Field(default=True)
64
66
  parent_container_name: str | None = None
65
67
  # initial_setup prevents unnecessary work when we are loading a workflow from a file.
66
68
  initial_setup: bool = False
@@ -14,7 +14,7 @@ from griptape_nodes.exe_types.core_types import (
14
14
  ParameterTypeBuiltin,
15
15
  )
16
16
  from griptape_nodes.exe_types.flow import ControlFlow
17
- from griptape_nodes.exe_types.node_types import BaseNode, NodeResolutionState, StartLoopNode, StartNode
17
+ from griptape_nodes.exe_types.node_types import BaseNode, ErrorProxyNode, NodeResolutionState, StartLoopNode, StartNode
18
18
  from griptape_nodes.machines.control_flow import CompleteState, ControlFlowMachine
19
19
  from griptape_nodes.retained_mode.events.base_events import (
20
20
  ExecutionEvent,
@@ -681,6 +681,18 @@ class FlowManager:
681
681
 
682
682
  # Cross-flow connections are now supported via global connection storage
683
683
 
684
+ # Call before_connection callbacks to allow nodes to prepare parameters
685
+ source_node.before_outgoing_connection(
686
+ source_parameter_name=request.source_parameter_name,
687
+ target_node=target_node,
688
+ target_parameter_name=request.target_parameter_name,
689
+ )
690
+ target_node.before_incoming_connection(
691
+ source_node=source_node,
692
+ source_parameter_name=request.source_parameter_name,
693
+ target_parameter_name=request.target_parameter_name,
694
+ )
695
+
684
696
  # Now validate the parameters.
685
697
  source_param = source_node.get_parameter_by_name(request.source_parameter_name)
686
698
  if source_param is None:
@@ -852,6 +864,13 @@ class FlowManager:
852
864
  )
853
865
  )
854
866
 
867
+ # Check if either node is ErrorProxyNode and mark connection modification if not initial_setup
868
+ if not request.initial_setup:
869
+ if isinstance(source_node, ErrorProxyNode):
870
+ source_node.set_post_init_connections_modified()
871
+ if isinstance(target_node, ErrorProxyNode):
872
+ target_node.set_post_init_connections_modified()
873
+
855
874
  result = CreateConnectionResultSuccess()
856
875
 
857
876
  return result
@@ -995,6 +1014,12 @@ class FlowManager:
995
1014
  details = f'Connection "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}" deleted.'
996
1015
  logger.debug(details)
997
1016
 
1017
+ # Check if either node is ErrorProxyNode and mark connection modification (deletes are always user-initiated)
1018
+ if isinstance(source_node, ErrorProxyNode):
1019
+ source_node.set_post_init_connections_modified()
1020
+ if isinstance(target_node, ErrorProxyNode):
1021
+ target_node.set_post_init_connections_modified()
1022
+
998
1023
  result = DeleteConnectionResultSuccess()
999
1024
  return result
1000
1025