griptape-nodes 0.52.1__py3-none-any.whl → 0.54.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.
- griptape_nodes/__init__.py +8 -942
- griptape_nodes/__main__.py +6 -0
- griptape_nodes/app/app.py +48 -86
- griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +35 -5
- griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +15 -1
- griptape_nodes/cli/__init__.py +1 -0
- griptape_nodes/cli/commands/__init__.py +1 -0
- griptape_nodes/cli/commands/config.py +74 -0
- griptape_nodes/cli/commands/engine.py +80 -0
- griptape_nodes/cli/commands/init.py +550 -0
- griptape_nodes/cli/commands/libraries.py +96 -0
- griptape_nodes/cli/commands/models.py +504 -0
- griptape_nodes/cli/commands/self.py +120 -0
- griptape_nodes/cli/main.py +56 -0
- griptape_nodes/cli/shared.py +75 -0
- griptape_nodes/common/__init__.py +1 -0
- griptape_nodes/common/directed_graph.py +71 -0
- griptape_nodes/drivers/storage/base_storage_driver.py +40 -20
- griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +24 -29
- griptape_nodes/drivers/storage/local_storage_driver.py +23 -14
- griptape_nodes/exe_types/core_types.py +60 -2
- griptape_nodes/exe_types/node_types.py +257 -38
- griptape_nodes/exe_types/param_components/__init__.py +1 -0
- griptape_nodes/exe_types/param_components/execution_status_component.py +138 -0
- griptape_nodes/machines/control_flow.py +195 -94
- griptape_nodes/machines/dag_builder.py +207 -0
- griptape_nodes/machines/fsm.py +10 -1
- griptape_nodes/machines/parallel_resolution.py +558 -0
- griptape_nodes/machines/{node_resolution.py → sequential_resolution.py} +30 -57
- griptape_nodes/node_library/library_registry.py +34 -1
- griptape_nodes/retained_mode/events/app_events.py +5 -1
- griptape_nodes/retained_mode/events/base_events.py +9 -9
- griptape_nodes/retained_mode/events/config_events.py +30 -0
- griptape_nodes/retained_mode/events/execution_events.py +2 -2
- griptape_nodes/retained_mode/events/model_events.py +296 -0
- griptape_nodes/retained_mode/events/node_events.py +4 -3
- griptape_nodes/retained_mode/griptape_nodes.py +34 -12
- griptape_nodes/retained_mode/managers/agent_manager.py +23 -5
- griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +3 -1
- griptape_nodes/retained_mode/managers/config_manager.py +44 -3
- griptape_nodes/retained_mode/managers/context_manager.py +6 -5
- griptape_nodes/retained_mode/managers/event_manager.py +8 -2
- griptape_nodes/retained_mode/managers/flow_manager.py +150 -206
- griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +1 -1
- griptape_nodes/retained_mode/managers/library_manager.py +35 -25
- griptape_nodes/retained_mode/managers/model_manager.py +1107 -0
- griptape_nodes/retained_mode/managers/node_manager.py +102 -220
- griptape_nodes/retained_mode/managers/object_manager.py +11 -5
- griptape_nodes/retained_mode/managers/os_manager.py +28 -13
- griptape_nodes/retained_mode/managers/secrets_manager.py +8 -4
- griptape_nodes/retained_mode/managers/settings.py +116 -7
- griptape_nodes/retained_mode/managers/static_files_manager.py +85 -12
- griptape_nodes/retained_mode/managers/sync_manager.py +17 -9
- griptape_nodes/retained_mode/managers/workflow_manager.py +186 -192
- griptape_nodes/retained_mode/retained_mode.py +19 -0
- griptape_nodes/servers/__init__.py +1 -0
- griptape_nodes/{mcp_server/server.py → servers/mcp.py} +1 -1
- griptape_nodes/{app/api.py → servers/static.py} +43 -40
- griptape_nodes/traits/add_param_button.py +1 -1
- griptape_nodes/traits/button.py +334 -6
- griptape_nodes/traits/color_picker.py +66 -0
- griptape_nodes/traits/multi_options.py +188 -0
- griptape_nodes/traits/numbers_selector.py +77 -0
- griptape_nodes/traits/options.py +93 -2
- griptape_nodes/traits/traits.json +4 -0
- griptape_nodes/utils/async_utils.py +31 -0
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/METADATA +4 -1
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/RECORD +71 -48
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/WHEEL +1 -1
- /griptape_nodes/{mcp_server → servers}/ws_request_manager.py +0 -0
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/entry_points.txt +0 -0
|
@@ -6,12 +6,13 @@ from abc import ABC, abstractmethod
|
|
|
6
6
|
from collections.abc import Callable, Generator, Iterable
|
|
7
7
|
from concurrent.futures import ThreadPoolExecutor
|
|
8
8
|
from enum import StrEnum, auto
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
10
10
|
|
|
11
11
|
from griptape_nodes.exe_types.core_types import (
|
|
12
12
|
BaseNodeElement,
|
|
13
13
|
ControlParameterInput,
|
|
14
14
|
ControlParameterOutput,
|
|
15
|
+
NodeMessageResult,
|
|
15
16
|
Parameter,
|
|
16
17
|
ParameterContainer,
|
|
17
18
|
ParameterDictionary,
|
|
@@ -21,6 +22,7 @@ from griptape_nodes.exe_types.core_types import (
|
|
|
21
22
|
ParameterMode,
|
|
22
23
|
ParameterTypeBuiltin,
|
|
23
24
|
)
|
|
25
|
+
from griptape_nodes.exe_types.param_components.execution_status_component import ExecutionStatusComponent
|
|
24
26
|
from griptape_nodes.exe_types.type_validator import TypeValidator
|
|
25
27
|
from griptape_nodes.retained_mode.events.base_events import (
|
|
26
28
|
ExecutionEvent,
|
|
@@ -39,6 +41,9 @@ from griptape_nodes.retained_mode.events.parameter_events import (
|
|
|
39
41
|
)
|
|
40
42
|
from griptape_nodes.traits.options import Options
|
|
41
43
|
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from griptape_nodes.exe_types.core_types import NodeMessagePayload
|
|
46
|
+
|
|
42
47
|
logger = logging.getLogger("griptape_nodes")
|
|
43
48
|
|
|
44
49
|
T = TypeVar("T")
|
|
@@ -54,23 +59,6 @@ class NodeResolutionState(StrEnum):
|
|
|
54
59
|
RESOLVED = auto()
|
|
55
60
|
|
|
56
61
|
|
|
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
|
-
|
|
74
62
|
class BaseNode(ABC):
|
|
75
63
|
# Owned by a flow
|
|
76
64
|
name: str
|
|
@@ -80,7 +68,7 @@ class BaseNode(ABC):
|
|
|
80
68
|
state: NodeResolutionState
|
|
81
69
|
current_spotlight_parameter: Parameter | None = None
|
|
82
70
|
parameter_values: dict[str, Any]
|
|
83
|
-
parameter_output_values:
|
|
71
|
+
parameter_output_values: TrackedParameterOutputValues
|
|
84
72
|
stop_flow: bool = False
|
|
85
73
|
root_ui_element: BaseNodeElement
|
|
86
74
|
_tracked_parameters: list[BaseNodeElement]
|
|
@@ -289,15 +277,18 @@ class BaseNode(ABC):
|
|
|
289
277
|
|
|
290
278
|
def on_node_message_received(
|
|
291
279
|
self,
|
|
292
|
-
optional_element_name: str | None,
|
|
280
|
+
optional_element_name: str | None,
|
|
293
281
|
message_type: str,
|
|
294
|
-
message:
|
|
282
|
+
message: NodeMessagePayload | None,
|
|
295
283
|
) -> NodeMessageResult:
|
|
296
284
|
"""Callback for when a message is sent directly to this node.
|
|
297
285
|
|
|
298
286
|
Custom nodes may elect to override this method to handle specific message types
|
|
299
287
|
and implement custom communication patterns with external systems.
|
|
300
288
|
|
|
289
|
+
If optional_element_name is provided, this method will attempt to find the
|
|
290
|
+
element and delegate the message handling to that element's on_message_received method.
|
|
291
|
+
|
|
301
292
|
Args:
|
|
302
293
|
optional_element_name: Optional element name this message relates to
|
|
303
294
|
message_type: String indicating the message type for parsing
|
|
@@ -306,6 +297,26 @@ class BaseNode(ABC):
|
|
|
306
297
|
Returns:
|
|
307
298
|
NodeMessageResult: Result containing success status, details, and optional response
|
|
308
299
|
"""
|
|
300
|
+
# If optional_element_name is provided, delegate to the specific element
|
|
301
|
+
if optional_element_name is not None:
|
|
302
|
+
element = self.root_ui_element.find_element_by_name(optional_element_name)
|
|
303
|
+
if element is None:
|
|
304
|
+
return NodeMessageResult(
|
|
305
|
+
success=False,
|
|
306
|
+
details=f"Node '{self.name}' received message for element '{optional_element_name}' but no element with that name was found",
|
|
307
|
+
response=None,
|
|
308
|
+
)
|
|
309
|
+
# Delegate to the element's message handler
|
|
310
|
+
result = element.on_message_received(message_type, message)
|
|
311
|
+
if result is None:
|
|
312
|
+
return NodeMessageResult(
|
|
313
|
+
success=False,
|
|
314
|
+
details=f"Element '{optional_element_name}' received message type '{message_type}' but no handler was available",
|
|
315
|
+
response=None,
|
|
316
|
+
)
|
|
317
|
+
return result
|
|
318
|
+
|
|
319
|
+
# If no element name specified, fall back to node-level handling
|
|
309
320
|
return NodeMessageResult(
|
|
310
321
|
success=False,
|
|
311
322
|
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.",
|
|
@@ -620,10 +631,10 @@ class BaseNode(ABC):
|
|
|
620
631
|
# Allow custom node logic to prepare and possibly mutate the value before it is actually set.
|
|
621
632
|
# Record any parameters modified for cascading.
|
|
622
633
|
if not initial_setup:
|
|
623
|
-
if
|
|
624
|
-
final_value = self.before_value_set(parameter=parameter, value=candidate_value)
|
|
625
|
-
else:
|
|
634
|
+
if skip_before_value_set:
|
|
626
635
|
final_value = candidate_value
|
|
636
|
+
else:
|
|
637
|
+
final_value = self.before_value_set(parameter=parameter, value=candidate_value)
|
|
627
638
|
# ACTUALLY SET THE NEW VALUE
|
|
628
639
|
self.parameter_values[param_name] = final_value
|
|
629
640
|
|
|
@@ -705,6 +716,9 @@ class BaseNode(ABC):
|
|
|
705
716
|
raise KeyError(err)
|
|
706
717
|
|
|
707
718
|
def get_next_control_output(self) -> Parameter | None:
|
|
719
|
+
# The default behavior for nodes is to find the first control output found.
|
|
720
|
+
# Advanced nodes can override this behavior (e.g., nodes that have multiple possible
|
|
721
|
+
# control paths).
|
|
708
722
|
for param in self.parameters:
|
|
709
723
|
if (
|
|
710
724
|
ParameterTypeBuiltin.CONTROL_TYPE.value == param.output_type
|
|
@@ -1097,6 +1111,10 @@ class TrackedParameterOutputValues(dict[str, Any]):
|
|
|
1097
1111
|
for key in keys_to_clear:
|
|
1098
1112
|
self._emit_parameter_change_event(key, None, deleted=True)
|
|
1099
1113
|
|
|
1114
|
+
def silent_clear(self) -> None:
|
|
1115
|
+
"""Clear all values without emitting parameter change events."""
|
|
1116
|
+
super().clear()
|
|
1117
|
+
|
|
1100
1118
|
def update(self, *args, **kwargs) -> None:
|
|
1101
1119
|
# Handle both dict.update(other) and dict.update(**kwargs) patterns
|
|
1102
1120
|
if args:
|
|
@@ -1136,28 +1154,185 @@ class TrackedParameterOutputValues(dict[str, Any]):
|
|
|
1136
1154
|
|
|
1137
1155
|
class ControlNode(BaseNode):
|
|
1138
1156
|
# Control Nodes may have one Control Input Port and at least one Control Output Port
|
|
1157
|
+
def __init__(
|
|
1158
|
+
self,
|
|
1159
|
+
name: str,
|
|
1160
|
+
metadata: dict[Any, Any] | None = None,
|
|
1161
|
+
input_control_name: str | None = None,
|
|
1162
|
+
output_control_name: str | None = None,
|
|
1163
|
+
) -> None:
|
|
1164
|
+
super().__init__(name, metadata=metadata)
|
|
1165
|
+
self.control_parameter_in = ControlParameterInput(
|
|
1166
|
+
display_name=input_control_name if input_control_name is not None else "Flow In"
|
|
1167
|
+
)
|
|
1168
|
+
self.control_parameter_out = ControlParameterOutput(
|
|
1169
|
+
display_name=output_control_name if output_control_name is not None else "Flow Out"
|
|
1170
|
+
)
|
|
1171
|
+
|
|
1172
|
+
self.add_parameter(self.control_parameter_in)
|
|
1173
|
+
self.add_parameter(self.control_parameter_out)
|
|
1174
|
+
|
|
1175
|
+
|
|
1176
|
+
class DataNode(BaseNode):
|
|
1139
1177
|
def __init__(self, name: str, metadata: dict[Any, Any] | None = None) -> None:
|
|
1140
1178
|
super().__init__(name, metadata=metadata)
|
|
1141
|
-
control_parameter_in = ControlParameterInput()
|
|
1142
|
-
control_parameter_out = ControlParameterOutput()
|
|
1143
1179
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1180
|
+
# Create control parameters like ControlNode, but initialize them as hidden
|
|
1181
|
+
# This allows the user to turn a DataNode "into" a Control Node; useful when
|
|
1182
|
+
# in situations like within a For Loop.
|
|
1183
|
+
self.control_parameter_in = ControlParameterInput()
|
|
1184
|
+
self.control_parameter_out = ControlParameterOutput()
|
|
1146
1185
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
ParameterTypeBuiltin.CONTROL_TYPE.value == param.output_type
|
|
1151
|
-
and ParameterMode.OUTPUT in param.allowed_modes
|
|
1152
|
-
):
|
|
1153
|
-
return param
|
|
1154
|
-
return None
|
|
1186
|
+
# Hide the control parameters by default
|
|
1187
|
+
self.control_parameter_in.ui_options["hide"] = True
|
|
1188
|
+
self.control_parameter_out.ui_options["hide"] = True
|
|
1155
1189
|
|
|
1190
|
+
self.add_parameter(self.control_parameter_in)
|
|
1191
|
+
self.add_parameter(self.control_parameter_out)
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
class SuccessFailureNode(BaseNode):
|
|
1195
|
+
"""Base class for nodes that have success/failure branching with control outputs.
|
|
1196
|
+
|
|
1197
|
+
This class provides:
|
|
1198
|
+
- Control input parameter
|
|
1199
|
+
- Two control outputs: success ("exec_out") and failure ("failure")
|
|
1200
|
+
- Execution state tracking for control flow routing
|
|
1201
|
+
- Helper method to check outgoing connections
|
|
1202
|
+
- Helper method to create standard status output parameters
|
|
1203
|
+
"""
|
|
1156
1204
|
|
|
1157
|
-
class DataNode(BaseNode):
|
|
1158
1205
|
def __init__(self, name: str, metadata: dict[Any, Any] | None = None) -> None:
|
|
1159
1206
|
super().__init__(name, metadata=metadata)
|
|
1160
1207
|
|
|
1208
|
+
# Track execution state for control flow routing
|
|
1209
|
+
self._execution_succeeded: bool | None = None
|
|
1210
|
+
|
|
1211
|
+
# Add control input parameter
|
|
1212
|
+
self.control_parameter_in = ControlParameterInput()
|
|
1213
|
+
self.add_parameter(self.control_parameter_in)
|
|
1214
|
+
|
|
1215
|
+
# Add success control output (uses default "exec_out" name)
|
|
1216
|
+
self.control_parameter_out = ControlParameterOutput(
|
|
1217
|
+
display_name="Succeeded", tooltip="Control path when the operation succeeds"
|
|
1218
|
+
)
|
|
1219
|
+
self.add_parameter(self.control_parameter_out)
|
|
1220
|
+
|
|
1221
|
+
# Add failure control output
|
|
1222
|
+
self.failure_output = ControlParameterOutput(
|
|
1223
|
+
name="failure",
|
|
1224
|
+
display_name="Failed",
|
|
1225
|
+
tooltip="Control path when the operation fails",
|
|
1226
|
+
)
|
|
1227
|
+
self.add_parameter(self.failure_output)
|
|
1228
|
+
|
|
1229
|
+
def get_next_control_output(self) -> Parameter | None:
|
|
1230
|
+
"""Determine which control output to follow based on execution result."""
|
|
1231
|
+
if self._execution_succeeded is None:
|
|
1232
|
+
# Execution hasn't completed yet
|
|
1233
|
+
self.stop_flow = True
|
|
1234
|
+
return None
|
|
1235
|
+
|
|
1236
|
+
if self._execution_succeeded:
|
|
1237
|
+
return self.control_parameter_out
|
|
1238
|
+
return self.failure_output
|
|
1239
|
+
|
|
1240
|
+
def _has_outgoing_connections(self, parameter: Parameter) -> bool:
|
|
1241
|
+
"""Check if a specific parameter has outgoing connections."""
|
|
1242
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
1243
|
+
|
|
1244
|
+
connections = GriptapeNodes.FlowManager().get_connections()
|
|
1245
|
+
|
|
1246
|
+
# Check if node has any outgoing connections
|
|
1247
|
+
node_connections = connections.outgoing_index.get(self.name)
|
|
1248
|
+
if node_connections is None:
|
|
1249
|
+
return False
|
|
1250
|
+
|
|
1251
|
+
# Check if this specific parameter has any outgoing connections
|
|
1252
|
+
param_connections = node_connections.get(parameter.name, [])
|
|
1253
|
+
return len(param_connections) > 0
|
|
1254
|
+
|
|
1255
|
+
def _create_status_parameters(
|
|
1256
|
+
self,
|
|
1257
|
+
result_details_tooltip: str = "Details about the operation result",
|
|
1258
|
+
result_details_placeholder: str = "Details on the operation will be presented here.",
|
|
1259
|
+
) -> None:
|
|
1260
|
+
"""Create and add standard status output parameters in a collapsible group.
|
|
1261
|
+
|
|
1262
|
+
This method creates a "Status" ParameterGroup and immediately adds it to the node.
|
|
1263
|
+
Nodes that use this are responsible for calling this at their desired location
|
|
1264
|
+
in their class constructor.
|
|
1265
|
+
|
|
1266
|
+
Creates and adds:
|
|
1267
|
+
- was_successful: Boolean parameter indicating success/failure
|
|
1268
|
+
- result_details: String parameter with operation details
|
|
1269
|
+
|
|
1270
|
+
Args:
|
|
1271
|
+
result_details_tooltip: Custom tooltip for result_details parameter
|
|
1272
|
+
result_details_placeholder: Custom placeholder text for result_details parameter
|
|
1273
|
+
"""
|
|
1274
|
+
# Create status component with OUTPUT modes for SuccessFailureNode
|
|
1275
|
+
self.status_component = ExecutionStatusComponent(
|
|
1276
|
+
self,
|
|
1277
|
+
was_successful_modes={ParameterMode.OUTPUT},
|
|
1278
|
+
result_details_modes={ParameterMode.OUTPUT},
|
|
1279
|
+
parameter_group_initially_collapsed=True,
|
|
1280
|
+
result_details_tooltip=result_details_tooltip,
|
|
1281
|
+
result_details_placeholder=result_details_placeholder,
|
|
1282
|
+
)
|
|
1283
|
+
|
|
1284
|
+
def _clear_execution_status(self) -> None:
|
|
1285
|
+
"""Clear execution status and reset status parameters.
|
|
1286
|
+
|
|
1287
|
+
This method should be called at the start of process() to reset the node state.
|
|
1288
|
+
"""
|
|
1289
|
+
self._execution_succeeded = None
|
|
1290
|
+
self.status_component.clear_execution_status("Beginning execution...")
|
|
1291
|
+
|
|
1292
|
+
def _set_status_results(self, *, was_successful: bool, result_details: str) -> None:
|
|
1293
|
+
"""Set status results and update execution state.
|
|
1294
|
+
|
|
1295
|
+
This method should be called from the process() method to communicate success or failure.
|
|
1296
|
+
It sets the execution state for control flow routing and updates the status output parameters.
|
|
1297
|
+
|
|
1298
|
+
Args:
|
|
1299
|
+
was_successful: Whether the operation succeeded
|
|
1300
|
+
result_details: Details about the operation result
|
|
1301
|
+
"""
|
|
1302
|
+
self._execution_succeeded = was_successful
|
|
1303
|
+
self.status_component.set_execution_result(was_successful=was_successful, result_details=result_details)
|
|
1304
|
+
|
|
1305
|
+
def _handle_failure_exception(self, exception: Exception) -> None:
|
|
1306
|
+
"""Handle failure exceptions based on whether failure output is connected.
|
|
1307
|
+
|
|
1308
|
+
If the failure output has outgoing connections, logs the error and continues execution
|
|
1309
|
+
to allow graceful failure handling. If no connections exist, raises the exception
|
|
1310
|
+
to crash the flow and provide immediate feedback.
|
|
1311
|
+
|
|
1312
|
+
Args:
|
|
1313
|
+
exception: The exception that caused the failure
|
|
1314
|
+
"""
|
|
1315
|
+
if self._has_outgoing_connections(self.failure_output):
|
|
1316
|
+
# User has connected something to Failed output, they want to handle errors gracefully
|
|
1317
|
+
logger.error(
|
|
1318
|
+
"Error in node '%s': %s. Continuing execution since failure output is connected for graceful handling.",
|
|
1319
|
+
self.name,
|
|
1320
|
+
exception,
|
|
1321
|
+
)
|
|
1322
|
+
else:
|
|
1323
|
+
# No graceful handling, raise the exception to crash the flow
|
|
1324
|
+
raise exception
|
|
1325
|
+
|
|
1326
|
+
def validate_before_workflow_run(self) -> list[Exception] | None:
|
|
1327
|
+
"""Clear result details before workflow runs to avoid confusion from previous sessions."""
|
|
1328
|
+
self._set_status_results(was_successful=False, result_details="<Results will appear when the node executes>")
|
|
1329
|
+
return super().validate_before_workflow_run()
|
|
1330
|
+
|
|
1331
|
+
def validate_before_node_run(self) -> list[Exception] | None:
|
|
1332
|
+
"""Clear result details before node runs to avoid confusion from previous sessions."""
|
|
1333
|
+
self._set_status_results(was_successful=False, result_details="<Results will appear when the node executes>")
|
|
1334
|
+
return super().validate_before_node_run()
|
|
1335
|
+
|
|
1161
1336
|
|
|
1162
1337
|
class StartNode(BaseNode):
|
|
1163
1338
|
def __init__(self, name: str, metadata: dict[Any, Any] | None = None) -> None:
|
|
@@ -1169,7 +1344,51 @@ class EndNode(BaseNode):
|
|
|
1169
1344
|
# TODO: https://github.com/griptape-ai/griptape-nodes/issues/854
|
|
1170
1345
|
def __init__(self, name: str, metadata: dict[Any, Any] | None = None) -> None:
|
|
1171
1346
|
super().__init__(name, metadata)
|
|
1172
|
-
|
|
1347
|
+
|
|
1348
|
+
# Add dual control inputs
|
|
1349
|
+
self.succeeded_control = ControlParameterInput(
|
|
1350
|
+
display_name="Succeeded", tooltip="Control path when the flow completed successfully"
|
|
1351
|
+
)
|
|
1352
|
+
self.failed_control = ControlParameterInput(
|
|
1353
|
+
name="failed", display_name="Failed", tooltip="Control path when the flow failed"
|
|
1354
|
+
)
|
|
1355
|
+
|
|
1356
|
+
self.add_parameter(self.succeeded_control)
|
|
1357
|
+
self.add_parameter(self.failed_control)
|
|
1358
|
+
|
|
1359
|
+
# Create status component with INPUT and PROPERTY modes
|
|
1360
|
+
self.status_component = ExecutionStatusComponent(
|
|
1361
|
+
self,
|
|
1362
|
+
was_successful_modes={ParameterMode.PROPERTY},
|
|
1363
|
+
result_details_modes={ParameterMode.INPUT},
|
|
1364
|
+
parameter_group_initially_collapsed=False,
|
|
1365
|
+
result_details_placeholder="Details about the completion or failure will be shown here.",
|
|
1366
|
+
)
|
|
1367
|
+
|
|
1368
|
+
def process(self) -> None:
|
|
1369
|
+
# Detect which control input was used to enter this node and determine success status
|
|
1370
|
+
match self._entry_control_parameter:
|
|
1371
|
+
case self.succeeded_control:
|
|
1372
|
+
was_successful = True
|
|
1373
|
+
status_prefix = "[SUCCEEDED]"
|
|
1374
|
+
case self.failed_control:
|
|
1375
|
+
was_successful = False
|
|
1376
|
+
status_prefix = "[FAILED]"
|
|
1377
|
+
case _:
|
|
1378
|
+
# No specific success/failure connection provided, assume success
|
|
1379
|
+
was_successful = True
|
|
1380
|
+
status_prefix = "[SUCCEEDED] No connection provided for success or failure, assuming successful"
|
|
1381
|
+
|
|
1382
|
+
# Get result details and format the final message
|
|
1383
|
+
result_details_value = self.get_parameter_value("result_details")
|
|
1384
|
+
if result_details_value and self._entry_control_parameter in (self.succeeded_control, self.failed_control):
|
|
1385
|
+
details = f"{status_prefix}\n{result_details_value}"
|
|
1386
|
+
elif self._entry_control_parameter in (self.succeeded_control, self.failed_control):
|
|
1387
|
+
details = f"{status_prefix}\nNo details supplied by flow"
|
|
1388
|
+
else:
|
|
1389
|
+
details = status_prefix
|
|
1390
|
+
|
|
1391
|
+
self.status_component.set_execution_result(was_successful=was_successful, result_details=details)
|
|
1173
1392
|
|
|
1174
1393
|
|
|
1175
1394
|
class StartLoopNode(BaseNode):
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Parameter components for reusable node functionality."""
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from griptape_nodes.exe_types.core_types import (
|
|
4
|
+
Parameter,
|
|
5
|
+
ParameterGroup,
|
|
6
|
+
ParameterMode,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ExecutionStatusComponent:
|
|
11
|
+
"""A reusable component for managing execution status parameters.
|
|
12
|
+
|
|
13
|
+
This component creates and manages a "Status" ParameterGroup containing:
|
|
14
|
+
- was_successful: Boolean parameter indicating success/failure
|
|
15
|
+
- result_details: String parameter with operation details
|
|
16
|
+
|
|
17
|
+
The component can be customized for different parameter modes to support
|
|
18
|
+
various node types (EndNode uses INPUT/PROPERTY, SuccessFailureNode uses OUTPUT).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__( # noqa: PLR0913
|
|
22
|
+
self,
|
|
23
|
+
node: Any, # BaseNode type, but avoiding circular import
|
|
24
|
+
*,
|
|
25
|
+
was_successful_modes: set[ParameterMode],
|
|
26
|
+
result_details_modes: set[ParameterMode],
|
|
27
|
+
parameter_group_initially_collapsed: bool = True,
|
|
28
|
+
result_details_tooltip: str = "Details about the operation result",
|
|
29
|
+
result_details_placeholder: str = "Details on the operation will be presented here.",
|
|
30
|
+
) -> None:
|
|
31
|
+
"""Initialize the ExecutionStatusComponent and create the parameters immediately.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
node: The node instance that will own these parameters
|
|
35
|
+
was_successful_modes: Set of ParameterModes for was_successful parameter
|
|
36
|
+
result_details_modes: Set of ParameterModes for result_details parameter
|
|
37
|
+
parameter_group_initially_collapsed: Whether the Status group should start collapsed
|
|
38
|
+
result_details_tooltip: Custom tooltip for result_details parameter
|
|
39
|
+
result_details_placeholder: Custom placeholder text for result_details parameter
|
|
40
|
+
"""
|
|
41
|
+
self._node = node
|
|
42
|
+
|
|
43
|
+
# Create the Status ParameterGroup
|
|
44
|
+
self._status_group = ParameterGroup(name="Status")
|
|
45
|
+
self._status_group.ui_options = {"collapsed": parameter_group_initially_collapsed}
|
|
46
|
+
|
|
47
|
+
# Boolean parameter to indicate success/failure
|
|
48
|
+
self._was_successful = Parameter(
|
|
49
|
+
name="was_successful",
|
|
50
|
+
tooltip="Indicates whether it completed without errors.",
|
|
51
|
+
type="bool",
|
|
52
|
+
default_value=False,
|
|
53
|
+
settable=False,
|
|
54
|
+
allowed_modes=was_successful_modes,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Result details parameter with multiline option
|
|
58
|
+
self._result_details = Parameter(
|
|
59
|
+
name="result_details",
|
|
60
|
+
tooltip=result_details_tooltip,
|
|
61
|
+
type="str",
|
|
62
|
+
default_value=None,
|
|
63
|
+
allowed_modes=result_details_modes,
|
|
64
|
+
settable=False,
|
|
65
|
+
ui_options={
|
|
66
|
+
"multiline": True,
|
|
67
|
+
"placeholder_text": result_details_placeholder,
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Add parameters to the group
|
|
72
|
+
self._status_group.add_child(self._was_successful)
|
|
73
|
+
self._status_group.add_child(self._result_details)
|
|
74
|
+
|
|
75
|
+
# Add the group to the node
|
|
76
|
+
self._node.add_node_element(self._status_group)
|
|
77
|
+
|
|
78
|
+
def get_parameter_group(self) -> ParameterGroup:
|
|
79
|
+
"""Get the Status ParameterGroup.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
ParameterGroup: The Status group containing was_successful and result_details
|
|
83
|
+
"""
|
|
84
|
+
return self._status_group
|
|
85
|
+
|
|
86
|
+
def set_execution_result(self, *, was_successful: bool, result_details: str) -> None:
|
|
87
|
+
"""Set the execution result values.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
was_successful: Whether the operation succeeded
|
|
91
|
+
result_details: Details about the operation result
|
|
92
|
+
"""
|
|
93
|
+
self._update_parameter_value(self._was_successful, was_successful)
|
|
94
|
+
self._update_parameter_value(self._result_details, result_details)
|
|
95
|
+
|
|
96
|
+
def clear_execution_status(self, initial_message: str | None = None) -> None:
|
|
97
|
+
"""Clear execution status and reset parameters.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
initial_message: Initial message to set in result_details. If None, clears result_details entirely.
|
|
101
|
+
"""
|
|
102
|
+
if initial_message is None:
|
|
103
|
+
initial_message = ""
|
|
104
|
+
self.set_execution_result(was_successful=False, result_details=initial_message)
|
|
105
|
+
|
|
106
|
+
def append_to_result_details(self, additional_text: str, separator: str = "\n") -> None:
|
|
107
|
+
"""Append text to the existing result_details.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
additional_text: Text to append to the current result_details
|
|
111
|
+
separator: Separator to use between existing and new text (default: newline)
|
|
112
|
+
"""
|
|
113
|
+
# Get current result_details value
|
|
114
|
+
current_details = self._node.get_parameter_value(self._result_details.name)
|
|
115
|
+
|
|
116
|
+
# Append the new text
|
|
117
|
+
if current_details:
|
|
118
|
+
updated_details = f"{current_details}{separator}{additional_text}"
|
|
119
|
+
else:
|
|
120
|
+
updated_details = additional_text
|
|
121
|
+
|
|
122
|
+
# Use consolidated update method
|
|
123
|
+
self._update_parameter_value(self._result_details, updated_details)
|
|
124
|
+
|
|
125
|
+
def _update_parameter_value(self, parameter: Parameter, value: Any) -> None:
|
|
126
|
+
"""Update a parameter value with all necessary operations.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
parameter: The parameter to update
|
|
130
|
+
value: The new value to set
|
|
131
|
+
"""
|
|
132
|
+
# ALWAYS set parameter value and publish update
|
|
133
|
+
self._node.set_parameter_value(parameter.name, value)
|
|
134
|
+
self._node.publish_update_to_parameter(parameter.name, value)
|
|
135
|
+
|
|
136
|
+
# ONLY set output values if the parameter mode is OUTPUT
|
|
137
|
+
if ParameterMode.OUTPUT in parameter.get_mode():
|
|
138
|
+
self._node.parameter_output_values[parameter.name] = value
|