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.
- griptape_nodes/__init__.py +292 -132
- griptape_nodes/app/__init__.py +1 -6
- griptape_nodes/app/app.py +108 -76
- griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +80 -5
- griptape_nodes/drivers/storage/local_storage_driver.py +5 -1
- griptape_nodes/exe_types/core_types.py +84 -3
- griptape_nodes/exe_types/node_types.py +260 -50
- griptape_nodes/machines/node_resolution.py +2 -14
- griptape_nodes/retained_mode/events/agent_events.py +7 -0
- griptape_nodes/retained_mode/events/base_events.py +16 -0
- griptape_nodes/retained_mode/events/library_events.py +26 -0
- griptape_nodes/retained_mode/events/parameter_events.py +31 -0
- griptape_nodes/retained_mode/griptape_nodes.py +32 -0
- griptape_nodes/retained_mode/managers/agent_manager.py +25 -12
- griptape_nodes/retained_mode/managers/config_manager.py +37 -4
- griptape_nodes/retained_mode/managers/event_manager.py +15 -0
- griptape_nodes/retained_mode/managers/flow_manager.py +64 -61
- griptape_nodes/retained_mode/managers/library_manager.py +215 -45
- griptape_nodes/retained_mode/managers/node_manager.py +344 -147
- griptape_nodes/retained_mode/managers/operation_manager.py +6 -0
- griptape_nodes/retained_mode/managers/os_manager.py +6 -1
- griptape_nodes/retained_mode/managers/secrets_manager.py +7 -2
- griptape_nodes/retained_mode/managers/settings.py +2 -11
- griptape_nodes/retained_mode/managers/static_files_manager.py +12 -3
- griptape_nodes/retained_mode/managers/version_compatibility_manager.py +105 -0
- griptape_nodes/retained_mode/managers/workflow_manager.py +4 -4
- griptape_nodes/updater/__init__.py +14 -8
- griptape_nodes/version_compatibility/__init__.py +1 -0
- griptape_nodes/version_compatibility/versions/__init__.py +1 -0
- griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +1 -0
- griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +77 -0
- {griptape_nodes-0.37.0.dist-info → griptape_nodes-0.38.0.dist-info}/METADATA +4 -1
- {griptape_nodes-0.37.0.dist-info → griptape_nodes-0.38.0.dist-info}/RECORD +36 -33
- griptape_nodes/app/app_websocket.py +0 -481
- griptape_nodes/app/nodes_api_socket_manager.py +0 -117
- {griptape_nodes-0.37.0.dist-info → griptape_nodes-0.38.0.dist-info}/WHEEL +0 -0
- {griptape_nodes-0.37.0.dist-info → griptape_nodes-0.38.0.dist-info}/entry_points.txt +0 -0
- {griptape_nodes-0.37.0.dist-info → griptape_nodes-0.38.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -32,8 +32,10 @@ from griptape_nodes.retained_mode.events.execution_events import (
|
|
|
32
32
|
ParameterValueUpdateEvent,
|
|
33
33
|
)
|
|
34
34
|
from griptape_nodes.retained_mode.events.parameter_events import (
|
|
35
|
+
RemoveElementEvent,
|
|
35
36
|
RemoveParameterFromNodeRequest,
|
|
36
37
|
)
|
|
38
|
+
from griptape_nodes.traits.options import Options
|
|
37
39
|
|
|
38
40
|
logger = logging.getLogger("griptape_nodes")
|
|
39
41
|
|
|
@@ -62,6 +64,7 @@ class BaseNode(ABC):
|
|
|
62
64
|
parameter_output_values: dict[str, Any]
|
|
63
65
|
stop_flow: bool = False
|
|
64
66
|
root_ui_element: BaseNodeElement
|
|
67
|
+
_tracked_parameters: list[BaseNodeElement]
|
|
65
68
|
|
|
66
69
|
@property
|
|
67
70
|
def parameters(self) -> list[Parameter]:
|
|
@@ -83,9 +86,12 @@ class BaseNode(ABC):
|
|
|
83
86
|
else:
|
|
84
87
|
self.metadata = metadata
|
|
85
88
|
self.parameter_values = {}
|
|
86
|
-
self.parameter_output_values =
|
|
89
|
+
self.parameter_output_values = TrackedParameterOutputValues(self)
|
|
87
90
|
self.root_ui_element = BaseNodeElement()
|
|
91
|
+
# Set the node context for the root element
|
|
92
|
+
self.root_ui_element._node_context = self
|
|
88
93
|
self.process_generator = None
|
|
94
|
+
self._tracked_parameters = []
|
|
89
95
|
|
|
90
96
|
# This is gross and we need to have a universal pass on resolution state changes and emission of events. That's what this ticket does!
|
|
91
97
|
# https://github.com/griptape-ai/griptape-nodes/issues/994
|
|
@@ -101,6 +107,12 @@ class BaseNode(ABC):
|
|
|
101
107
|
)
|
|
102
108
|
self.state = NodeResolutionState.UNRESOLVED
|
|
103
109
|
|
|
110
|
+
def emit_parameter_changes(self) -> None:
|
|
111
|
+
if self._tracked_parameters:
|
|
112
|
+
for parameter in self._tracked_parameters:
|
|
113
|
+
parameter._emit_alter_element_event_if_possible()
|
|
114
|
+
self._tracked_parameters.clear()
|
|
115
|
+
|
|
104
116
|
def allow_incoming_connection(
|
|
105
117
|
self,
|
|
106
118
|
source_node: BaseNode, # noqa: ARG002
|
|
@@ -123,8 +135,8 @@ class BaseNode(ABC):
|
|
|
123
135
|
self,
|
|
124
136
|
source_node: BaseNode, # noqa: ARG002
|
|
125
137
|
source_parameter: Parameter, # noqa: ARG002
|
|
126
|
-
target_parameter: Parameter, # noqa: ARG002
|
|
127
|
-
modified_parameters_set: set[str], # noqa: ARG002
|
|
138
|
+
target_parameter: Parameter, # noqa: ARG002
|
|
139
|
+
modified_parameters_set: set[str] | None = None, # noqa: ARG002
|
|
128
140
|
) -> None:
|
|
129
141
|
"""Callback after a Connection has been established TO this Node."""
|
|
130
142
|
return
|
|
@@ -134,7 +146,7 @@ class BaseNode(ABC):
|
|
|
134
146
|
source_parameter: Parameter, # noqa: ARG002
|
|
135
147
|
target_node: BaseNode, # noqa: ARG002
|
|
136
148
|
target_parameter: Parameter, # noqa: ARG002
|
|
137
|
-
modified_parameters_set: set[str], # noqa: ARG002
|
|
149
|
+
modified_parameters_set: set[str] | None = None, # noqa: ARG002
|
|
138
150
|
) -> None:
|
|
139
151
|
"""Callback after a Connection has been established OUT of this Node."""
|
|
140
152
|
return
|
|
@@ -144,7 +156,7 @@ class BaseNode(ABC):
|
|
|
144
156
|
source_node: BaseNode, # noqa: ARG002
|
|
145
157
|
source_parameter: Parameter, # noqa: ARG002
|
|
146
158
|
target_parameter: Parameter, # noqa: ARG002
|
|
147
|
-
modified_parameters_set: set[str], # noqa: ARG002
|
|
159
|
+
modified_parameters_set: set[str] | None = None, # noqa: ARG002
|
|
148
160
|
) -> None:
|
|
149
161
|
"""Callback after a Connection TO this Node was REMOVED."""
|
|
150
162
|
return
|
|
@@ -154,12 +166,17 @@ class BaseNode(ABC):
|
|
|
154
166
|
source_parameter: Parameter, # noqa: ARG002
|
|
155
167
|
target_node: BaseNode, # noqa: ARG002
|
|
156
168
|
target_parameter: Parameter, # noqa: ARG002
|
|
157
|
-
modified_parameters_set: set[str], # noqa: ARG002
|
|
169
|
+
modified_parameters_set: set[str] | None = None, # noqa: ARG002
|
|
158
170
|
) -> None:
|
|
159
171
|
"""Callback after a Connection OUT of this Node was REMOVED."""
|
|
160
172
|
return
|
|
161
173
|
|
|
162
|
-
def before_value_set(
|
|
174
|
+
def before_value_set(
|
|
175
|
+
self,
|
|
176
|
+
parameter: Parameter, # noqa: ARG002
|
|
177
|
+
value: Any,
|
|
178
|
+
modified_parameters_set: set[str] | None = None, # noqa: ARG002
|
|
179
|
+
) -> Any:
|
|
163
180
|
"""Callback when a Parameter's value is ABOUT to be set.
|
|
164
181
|
|
|
165
182
|
Custom nodes may elect to override the default behavior by implementing this function in their node code.
|
|
@@ -174,8 +191,7 @@ class BaseNode(ABC):
|
|
|
174
191
|
Args:
|
|
175
192
|
parameter: the Parameter on this node that is about to be changed
|
|
176
193
|
value: the value intended to be set (this has already gone through any converters and validators on the Parameter)
|
|
177
|
-
modified_parameters_set: A set of parameter names within this node that were modified as a result
|
|
178
|
-
of this call. The Parameter this was called on does NOT need to be part of the return.
|
|
194
|
+
modified_parameters_set: A set of parameter names within this node that were modified as a result of this call.
|
|
179
195
|
|
|
180
196
|
Returns:
|
|
181
197
|
The final value to set for the Parameter. This gives the Node logic one last opportunity to mutate the value
|
|
@@ -184,7 +200,12 @@ class BaseNode(ABC):
|
|
|
184
200
|
# Default behavior is to do nothing to the supplied value, and indicate no other modified Parameters.
|
|
185
201
|
return value
|
|
186
202
|
|
|
187
|
-
def after_value_set(
|
|
203
|
+
def after_value_set(
|
|
204
|
+
self,
|
|
205
|
+
parameter: Parameter, # noqa: ARG002
|
|
206
|
+
value: Any, # noqa: ARG002
|
|
207
|
+
modified_parameters_set: set[str] | None = None, # noqa: ARG002
|
|
208
|
+
) -> None:
|
|
188
209
|
"""Callback AFTER a Parameter's value was set.
|
|
189
210
|
|
|
190
211
|
Custom nodes may elect to override the default behavior by implementing this function in their node code.
|
|
@@ -193,11 +214,19 @@ class BaseNode(ABC):
|
|
|
193
214
|
changing other Parameters on the node. If other Parameters are changed, the engine needs a list of which
|
|
194
215
|
ones have changed to cascade unresolved state.
|
|
195
216
|
|
|
217
|
+
NOTE: Subclasses can override this method with either signature:
|
|
218
|
+
- def after_value_set(self, parameter, value) -> None: (most common)
|
|
219
|
+
- def after_value_set(self, parameter, value, **kwargs) -> None: (advanced)
|
|
220
|
+
The base implementation uses **kwargs for compatibility with both patterns.
|
|
221
|
+
The engine will try calling with 2 arguments first, then fall back to 3 if needed.
|
|
222
|
+
Pyright may show false positive "incompatible override" warnings for the 2-argument
|
|
223
|
+
version - this is expected and the code will work correctly at runtime.
|
|
224
|
+
|
|
196
225
|
Args:
|
|
197
226
|
parameter: the Parameter on this node that was just changed
|
|
198
227
|
value: the value that was set (already converted, validated, and possibly mutated by the node code)
|
|
199
|
-
modified_parameters_set:
|
|
200
|
-
of this call. The Parameter this was called on does NOT need to be part of the return.
|
|
228
|
+
modified_parameters_set: Optional set of parameter names within this node
|
|
229
|
+
that were modified as a result of this call. The Parameter this was called on does NOT need to be part of the return.
|
|
201
230
|
|
|
202
231
|
Returns:
|
|
203
232
|
Nothing
|
|
@@ -205,7 +234,7 @@ class BaseNode(ABC):
|
|
|
205
234
|
# Default behavior is to do nothing, and indicate no other modified Parameters.
|
|
206
235
|
return None # noqa: RET501
|
|
207
236
|
|
|
208
|
-
def after_settings_changed(self,
|
|
237
|
+
def after_settings_changed(self, **kwargs: Any) -> None: # noqa: ARG002
|
|
209
238
|
"""Callback for when the settings of this Node are changed."""
|
|
210
239
|
# Waiting for https://github.com/griptape-ai/griptape-nodes/issues/1309
|
|
211
240
|
return
|
|
@@ -229,6 +258,7 @@ class BaseNode(ABC):
|
|
|
229
258
|
msg = "Cannot have duplicate names on parameters."
|
|
230
259
|
raise ValueError(msg)
|
|
231
260
|
self.add_node_element(param)
|
|
261
|
+
self._emit_parameter_lifecycle_event(param)
|
|
232
262
|
|
|
233
263
|
def remove_parameter_element_by_name(self, element_name: str) -> None:
|
|
234
264
|
element = self.root_ui_element.find_element_by_name(element_name)
|
|
@@ -236,6 +266,9 @@ class BaseNode(ABC):
|
|
|
236
266
|
self.remove_parameter_element(element)
|
|
237
267
|
|
|
238
268
|
def remove_parameter_element(self, param: BaseNodeElement) -> None:
|
|
269
|
+
# Emit event before removal if it's a Parameter
|
|
270
|
+
if isinstance(param, Parameter):
|
|
271
|
+
self._emit_parameter_lifecycle_event(param)
|
|
239
272
|
for child in param.find_elements_by_type(BaseNodeElement):
|
|
240
273
|
self.remove_node_element(child)
|
|
241
274
|
self.remove_node_element(param)
|
|
@@ -248,6 +281,8 @@ class BaseNode(ABC):
|
|
|
248
281
|
return None
|
|
249
282
|
|
|
250
283
|
def add_node_element(self, ui_element: BaseNodeElement) -> None:
|
|
284
|
+
# Set the node context before adding to ensure proper propagation
|
|
285
|
+
ui_element._node_context = self
|
|
251
286
|
self.root_ui_element.add_child(ui_element)
|
|
252
287
|
|
|
253
288
|
def remove_node_element(self, ui_element: BaseNodeElement) -> None:
|
|
@@ -269,7 +304,9 @@ class BaseNode(ABC):
|
|
|
269
304
|
for name in names:
|
|
270
305
|
parameter = self.get_parameter_by_name(name)
|
|
271
306
|
if parameter is not None:
|
|
272
|
-
|
|
307
|
+
ui_options = parameter.ui_options
|
|
308
|
+
ui_options["hide"] = not visible
|
|
309
|
+
parameter.ui_options = ui_options
|
|
273
310
|
|
|
274
311
|
def get_message_by_name_or_element_id(self, element: str) -> ParameterMessage | None:
|
|
275
312
|
element_items = self.root_ui_element.find_elements_by_type(ParameterMessage)
|
|
@@ -291,7 +328,9 @@ class BaseNode(ABC):
|
|
|
291
328
|
for name in names:
|
|
292
329
|
message = self.get_message_by_name_or_element_id(name)
|
|
293
330
|
if message is not None:
|
|
294
|
-
|
|
331
|
+
ui_options = message.ui_options
|
|
332
|
+
ui_options["hide"] = not visible
|
|
333
|
+
message.ui_options = ui_options
|
|
295
334
|
|
|
296
335
|
def hide_message_by_name(self, names: str | list[str]) -> None:
|
|
297
336
|
self._set_message_visibility(names, visible=False)
|
|
@@ -307,8 +346,91 @@ class BaseNode(ABC):
|
|
|
307
346
|
"""Shows one or more parameters by name."""
|
|
308
347
|
self._set_parameter_visibility(names, visible=True)
|
|
309
348
|
|
|
349
|
+
def _update_option_choices(self, param: str, choices: list[str], default: str) -> None:
|
|
350
|
+
"""Updates the model selection parameter with a new set of choices.
|
|
351
|
+
|
|
352
|
+
This method is intended to be called by subclasses to set the available
|
|
353
|
+
models for the driver. It modifies the 'model' parameter's `Options` trait
|
|
354
|
+
to reflect the provided choices.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
param: The name of the parameter representing the model selection or the Parameter object itself.
|
|
358
|
+
choices: A list of model names to be set as choices.
|
|
359
|
+
default: The default model name to be set. It must be one of the provided choices.
|
|
360
|
+
"""
|
|
361
|
+
parameter = self.get_parameter_by_name(param)
|
|
362
|
+
if parameter is not None:
|
|
363
|
+
trait = parameter.find_element_by_id("Options")
|
|
364
|
+
if trait and isinstance(trait, Options):
|
|
365
|
+
trait.choices = choices
|
|
366
|
+
|
|
367
|
+
if default in choices:
|
|
368
|
+
parameter.default_value = default
|
|
369
|
+
self.set_parameter_value(param, default)
|
|
370
|
+
else:
|
|
371
|
+
msg = f"Default model '{default}' is not in the provided choices."
|
|
372
|
+
raise ValueError(msg)
|
|
373
|
+
else:
|
|
374
|
+
msg = f"Parameter '{param}' not found for updating model choices."
|
|
375
|
+
raise ValueError(msg)
|
|
376
|
+
|
|
377
|
+
def _remove_options_trait(self, param: str) -> None:
|
|
378
|
+
"""Removes the options trait from the specified parameter.
|
|
379
|
+
|
|
380
|
+
This method is intended to be called by subclasses to remove the
|
|
381
|
+
`Options` trait from a parameter, if it exists.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
param: The name of the parameter from which to remove the `Options` trait.
|
|
385
|
+
"""
|
|
386
|
+
parameter = self.get_parameter_by_name(param)
|
|
387
|
+
if parameter is not None:
|
|
388
|
+
trait = parameter.find_element_by_id("Options")
|
|
389
|
+
if trait and isinstance(trait, Options):
|
|
390
|
+
parameter.remove_trait(trait)
|
|
391
|
+
else:
|
|
392
|
+
msg = f"Parameter '{param}' not found for removing options trait."
|
|
393
|
+
raise ValueError(msg)
|
|
394
|
+
|
|
395
|
+
def _replace_param_by_name( # noqa: PLR0913
|
|
396
|
+
self,
|
|
397
|
+
param_name: str,
|
|
398
|
+
new_param_name: str,
|
|
399
|
+
new_output_type: str | None = None,
|
|
400
|
+
tooltip: str | list[dict] | None = None,
|
|
401
|
+
default_value: Any = None,
|
|
402
|
+
ui_options: dict | None = None,
|
|
403
|
+
) -> None:
|
|
404
|
+
"""Replaces a parameter in the node configuration.
|
|
405
|
+
|
|
406
|
+
This method is used to replace a parameter with a new name and
|
|
407
|
+
optionally update its tooltip and default value.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
param_name (str): The name of the parameter to replace.
|
|
411
|
+
new_param_name (str): The new name for the parameter.
|
|
412
|
+
new_output_type (str, optional): The new output type for the parameter.
|
|
413
|
+
tooltip (str, list[dict], optional): The new tooltip for the parameter.
|
|
414
|
+
default_value (Any, optional): The new default value for the parameter.
|
|
415
|
+
ui_options (dict, optional): UI options for the parameter.
|
|
416
|
+
"""
|
|
417
|
+
param = self.get_parameter_by_name(param_name)
|
|
418
|
+
if param is not None:
|
|
419
|
+
param.name = new_param_name
|
|
420
|
+
if tooltip is not None:
|
|
421
|
+
param.tooltip = tooltip
|
|
422
|
+
if default_value is not None:
|
|
423
|
+
param.default_value = default_value
|
|
424
|
+
if new_output_type is not None:
|
|
425
|
+
param.output_type = new_output_type
|
|
426
|
+
if ui_options is not None:
|
|
427
|
+
param.ui_options = ui_options
|
|
428
|
+
else:
|
|
429
|
+
msg = f"Parameter '{param_name}' not found in node configuration."
|
|
430
|
+
raise ValueError(msg)
|
|
431
|
+
|
|
310
432
|
def initialize_spotlight(self) -> None:
|
|
311
|
-
#
|
|
433
|
+
# Create a linked list of parameters for spotlight navigation.
|
|
312
434
|
curr_param = None
|
|
313
435
|
prev_param = None
|
|
314
436
|
for parameter in self.parameters:
|
|
@@ -317,14 +439,13 @@ class BaseNode(ABC):
|
|
|
317
439
|
and ParameterTypeBuiltin.CONTROL_TYPE.value not in parameter.input_types
|
|
318
440
|
):
|
|
319
441
|
if not self.current_spotlight_parameter or prev_param is None:
|
|
320
|
-
#
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
prev_param = param_copy
|
|
442
|
+
# Use the original parameter and assign it to current spotlight
|
|
443
|
+
self.current_spotlight_parameter = parameter
|
|
444
|
+
prev_param = parameter
|
|
324
445
|
# go on to the next one because prev and next don't need to be set yet.
|
|
325
446
|
continue
|
|
326
447
|
# prev_param will have been initialized at this point
|
|
327
|
-
curr_param = parameter
|
|
448
|
+
curr_param = parameter
|
|
328
449
|
prev_param.next = curr_param
|
|
329
450
|
curr_param.prev = prev_param
|
|
330
451
|
prev_param = curr_param
|
|
@@ -349,7 +470,17 @@ class BaseNode(ABC):
|
|
|
349
470
|
return parameter
|
|
350
471
|
return None
|
|
351
472
|
|
|
352
|
-
def
|
|
473
|
+
def get_element_by_name_and_type(
|
|
474
|
+
self, elem_name: str, element_type: type[BaseNodeElement] | None = None
|
|
475
|
+
) -> BaseNodeElement | None:
|
|
476
|
+
find_type = element_type if element_type is not None else BaseNodeElement
|
|
477
|
+
element_items = self.root_ui_element.find_elements_by_type(find_type)
|
|
478
|
+
for element_item in element_items:
|
|
479
|
+
if elem_name == element_item.name:
|
|
480
|
+
return element_item
|
|
481
|
+
return None
|
|
482
|
+
|
|
483
|
+
def set_parameter_value(self, param_name: str, value: Any) -> None:
|
|
353
484
|
"""Attempt to set a Parameter's value.
|
|
354
485
|
|
|
355
486
|
The Node may choose to store a different value (or type) than what was passed in.
|
|
@@ -392,25 +523,24 @@ class BaseNode(ABC):
|
|
|
392
523
|
for validator in parameter.validators:
|
|
393
524
|
validator(parameter, candidate_value)
|
|
394
525
|
|
|
395
|
-
# Keep track of which other parameters got modified as a result of any node-specific logic.
|
|
396
|
-
modified_parameters: set[str] = set()
|
|
397
|
-
|
|
398
526
|
# Allow custom node logic to prepare and possibly mutate the value before it is actually set.
|
|
399
527
|
# Record any parameters modified for cascading.
|
|
400
|
-
|
|
401
|
-
parameter=parameter,
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
528
|
+
try:
|
|
529
|
+
final_value = self.before_value_set(parameter=parameter, value=candidate_value)
|
|
530
|
+
except TypeError:
|
|
531
|
+
final_value = self.before_value_set(
|
|
532
|
+
parameter=parameter, value=candidate_value, modified_parameters_set=set()
|
|
533
|
+
)
|
|
405
534
|
# ACTUALLY SET THE NEW VALUE
|
|
406
535
|
self.parameter_values[param_name] = final_value
|
|
536
|
+
|
|
407
537
|
# If a parameter value has been set at the top level of a container, wipe all children.
|
|
408
538
|
# Allow custom node logic to respond after it's been set. Record any modified parameters for cascading.
|
|
409
|
-
|
|
410
|
-
parameter=parameter,
|
|
411
|
-
|
|
412
|
-
modified_parameters_set=
|
|
413
|
-
)
|
|
539
|
+
try:
|
|
540
|
+
self.after_value_set(parameter=parameter, value=final_value)
|
|
541
|
+
except TypeError:
|
|
542
|
+
self.after_value_set(parameter=parameter, value=final_value, modified_parameters_set=set())
|
|
543
|
+
self._emit_parameter_lifecycle_event(parameter)
|
|
414
544
|
# handle with container parameters
|
|
415
545
|
if parameter.parent_container_name is not None:
|
|
416
546
|
# Does it have a parent container
|
|
@@ -421,13 +551,7 @@ class BaseNode(ABC):
|
|
|
421
551
|
new_parent_value = handle_container_parameter(self, parent_parameter)
|
|
422
552
|
if new_parent_value is not None:
|
|
423
553
|
# set that new value if it exists.
|
|
424
|
-
|
|
425
|
-
parameter.parent_container_name, new_parent_value
|
|
426
|
-
)
|
|
427
|
-
# Return the complete set of modified parameters.
|
|
428
|
-
if modified_parameters_from_container:
|
|
429
|
-
modified_parameters = modified_parameters | modified_parameters_from_container
|
|
430
|
-
return modified_parameters
|
|
554
|
+
self.set_parameter_value(parameter.parent_container_name, new_parent_value)
|
|
431
555
|
|
|
432
556
|
def kill_parameter_children(self, parameter: Parameter) -> None:
|
|
433
557
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
@@ -436,13 +560,13 @@ class BaseNode(ABC):
|
|
|
436
560
|
GriptapeNodes.handle_request(RemoveParameterFromNodeRequest(parameter_name=child.name, node_name=self.name))
|
|
437
561
|
|
|
438
562
|
def get_parameter_value(self, param_name: str) -> Any:
|
|
439
|
-
if param_name in self.parameter_values:
|
|
440
|
-
return self.parameter_values[param_name]
|
|
441
563
|
param = self.get_parameter_by_name(param_name)
|
|
442
|
-
if param:
|
|
564
|
+
if param and isinstance(param, ParameterContainer):
|
|
443
565
|
value = handle_container_parameter(self, param)
|
|
444
566
|
if value:
|
|
445
567
|
return value
|
|
568
|
+
if param_name in self.parameter_values:
|
|
569
|
+
return self.parameter_values[param_name]
|
|
446
570
|
return param.default_value if param else None
|
|
447
571
|
|
|
448
572
|
def get_parameter_list_value(self, param: str) -> list:
|
|
@@ -543,11 +667,16 @@ class BaseNode(ABC):
|
|
|
543
667
|
self.state = NodeResolutionState.UNRESOLVED
|
|
544
668
|
# delete all output values potentially generated
|
|
545
669
|
self.parameter_output_values.clear()
|
|
546
|
-
#
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
670
|
+
# Clear the spotlight linked list
|
|
671
|
+
# First, clear all next/prev pointers to break the linked list
|
|
672
|
+
current = self.current_spotlight_parameter
|
|
673
|
+
while current is not None:
|
|
674
|
+
next_param = current.next
|
|
675
|
+
current.next = None
|
|
676
|
+
current.prev = None
|
|
677
|
+
current = next_param
|
|
678
|
+
# Then clear the reference to the first spotlight parameter
|
|
679
|
+
self.current_spotlight_parameter = None
|
|
551
680
|
|
|
552
681
|
def append_value_to_parameter(self, parameter_name: str, value: Any) -> None:
|
|
553
682
|
# Add the value to the node
|
|
@@ -685,6 +814,25 @@ class BaseNode(ABC):
|
|
|
685
814
|
# Use reorder_elements to apply the move
|
|
686
815
|
self.reorder_elements(list(new_order))
|
|
687
816
|
|
|
817
|
+
def _emit_parameter_lifecycle_event(self, parameter: BaseNodeElement, *, remove: bool = False) -> None:
|
|
818
|
+
"""Emit an AlterElementEvent for parameter add/remove operations."""
|
|
819
|
+
from griptape_nodes.retained_mode.events.base_events import ExecutionEvent, ExecutionGriptapeNodeEvent
|
|
820
|
+
from griptape_nodes.retained_mode.events.parameter_events import AlterElementEvent
|
|
821
|
+
|
|
822
|
+
# Create event data using the parameter's to_event method
|
|
823
|
+
if remove:
|
|
824
|
+
event = ExecutionGriptapeNodeEvent(
|
|
825
|
+
wrapped_event=ExecutionEvent(payload=RemoveElementEvent(element_id=parameter.element_id))
|
|
826
|
+
)
|
|
827
|
+
else:
|
|
828
|
+
event_data = parameter.to_event(self)
|
|
829
|
+
|
|
830
|
+
# Publish the event
|
|
831
|
+
event = ExecutionGriptapeNodeEvent(
|
|
832
|
+
wrapped_event=ExecutionEvent(payload=AlterElementEvent(element_details=event_data))
|
|
833
|
+
)
|
|
834
|
+
EventBus.publish_event(event)
|
|
835
|
+
|
|
688
836
|
def _get_element_name(self, element: str | int, element_names: list[str]) -> str:
|
|
689
837
|
"""Convert an element identifier (name or index) to its name.
|
|
690
838
|
|
|
@@ -777,6 +925,68 @@ class BaseNode(ABC):
|
|
|
777
925
|
self.reorder_elements(list(new_order))
|
|
778
926
|
|
|
779
927
|
|
|
928
|
+
class TrackedParameterOutputValues(dict[str, Any]):
|
|
929
|
+
"""A dictionary that tracks modifications and emits AlterElementEvent when parameter output values change."""
|
|
930
|
+
|
|
931
|
+
def __init__(self, node: BaseNode) -> None:
|
|
932
|
+
super().__init__()
|
|
933
|
+
self._node = node
|
|
934
|
+
|
|
935
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
936
|
+
old_value = self.get(key)
|
|
937
|
+
super().__setitem__(key, value)
|
|
938
|
+
|
|
939
|
+
# Only emit event if value actually changed
|
|
940
|
+
if old_value != value:
|
|
941
|
+
self._emit_parameter_change_event(key, value)
|
|
942
|
+
|
|
943
|
+
def __delitem__(self, key: str) -> None:
|
|
944
|
+
if key in self:
|
|
945
|
+
super().__delitem__(key)
|
|
946
|
+
self._emit_parameter_change_event(key, None, deleted=True)
|
|
947
|
+
|
|
948
|
+
def clear(self) -> None:
|
|
949
|
+
if self: # Only emit events if there were values to clear
|
|
950
|
+
keys_to_clear = list(self.keys())
|
|
951
|
+
super().clear()
|
|
952
|
+
for key in keys_to_clear:
|
|
953
|
+
self._emit_parameter_change_event(key, None, deleted=True)
|
|
954
|
+
|
|
955
|
+
def update(self, *args, **kwargs) -> None:
|
|
956
|
+
# Handle both dict.update(other) and dict.update(**kwargs) patterns
|
|
957
|
+
if args:
|
|
958
|
+
other = args[0]
|
|
959
|
+
if hasattr(other, "items"):
|
|
960
|
+
for key, value in other.items():
|
|
961
|
+
self[key] = value # Use __setitem__ to trigger events
|
|
962
|
+
else:
|
|
963
|
+
for key, value in other:
|
|
964
|
+
self[key] = value
|
|
965
|
+
|
|
966
|
+
for key, value in kwargs.items():
|
|
967
|
+
self[key] = value
|
|
968
|
+
|
|
969
|
+
def _emit_parameter_change_event(self, parameter_name: str, value: Any, *, deleted: bool = False) -> None:
|
|
970
|
+
"""Emit an AlterElementEvent for parameter output value changes."""
|
|
971
|
+
parameter = self._node.get_parameter_by_name(parameter_name)
|
|
972
|
+
if parameter is not None:
|
|
973
|
+
from griptape_nodes.retained_mode.events.base_events import ExecutionEvent, ExecutionGriptapeNodeEvent
|
|
974
|
+
from griptape_nodes.retained_mode.events.parameter_events import AlterElementEvent
|
|
975
|
+
|
|
976
|
+
# Create event data using the parameter's to_event method
|
|
977
|
+
event_data = parameter.to_event(self._node)
|
|
978
|
+
event_data["value"] = value
|
|
979
|
+
|
|
980
|
+
# Add modification metadata
|
|
981
|
+
event_data["modification_type"] = "deleted" if deleted else "set"
|
|
982
|
+
|
|
983
|
+
# Publish the event
|
|
984
|
+
event = ExecutionGriptapeNodeEvent(
|
|
985
|
+
wrapped_event=ExecutionEvent(payload=AlterElementEvent(element_details=event_data))
|
|
986
|
+
)
|
|
987
|
+
EventBus.publish_event(event)
|
|
988
|
+
|
|
989
|
+
|
|
780
990
|
class ControlNode(BaseNode):
|
|
781
991
|
# Control Nodes may have one Control Input Port and at least one Control Output Port
|
|
782
992
|
def __init__(self, name: str, metadata: dict[Any, Any] | None = None) -> None:
|
|
@@ -28,7 +28,6 @@ from griptape_nodes.retained_mode.events.execution_events import (
|
|
|
28
28
|
ResumeNodeProcessingEvent,
|
|
29
29
|
)
|
|
30
30
|
from griptape_nodes.retained_mode.events.parameter_events import (
|
|
31
|
-
AlterElementEvent,
|
|
32
31
|
SetParameterValueRequest,
|
|
33
32
|
)
|
|
34
33
|
|
|
@@ -199,7 +198,7 @@ class ExecuteNodeState(State):
|
|
|
199
198
|
current_node.parameter_output_values.clear()
|
|
200
199
|
|
|
201
200
|
@staticmethod
|
|
202
|
-
def on_enter(context: ResolutionContext) -> type[State] | None:
|
|
201
|
+
def on_enter(context: ResolutionContext) -> type[State] | None:
|
|
203
202
|
current_node = context.focus_stack[-1].node
|
|
204
203
|
# Clear all of the current output values
|
|
205
204
|
ExecuteNodeState.clear_parameter_output_values(context)
|
|
@@ -210,18 +209,7 @@ class ExecuteNodeState(State):
|
|
|
210
209
|
# If a parameter value is not already set
|
|
211
210
|
value = current_node.get_parameter_value(parameter.name)
|
|
212
211
|
if value is not None:
|
|
213
|
-
|
|
214
|
-
if modified_parameters:
|
|
215
|
-
for modified_parameter_name in modified_parameters:
|
|
216
|
-
# TODO: https://github.com/griptape-ai/griptape-nodes/issues/865
|
|
217
|
-
modified_parameter = current_node.get_parameter_by_name(modified_parameter_name)
|
|
218
|
-
if modified_parameter is not None:
|
|
219
|
-
modified_request = AlterElementEvent(
|
|
220
|
-
element_details=modified_parameter.to_event(current_node)
|
|
221
|
-
)
|
|
222
|
-
EventBus.publish_event(
|
|
223
|
-
ExecutionGriptapeNodeEvent(ExecutionEvent(payload=modified_request))
|
|
224
|
-
)
|
|
212
|
+
current_node.set_parameter_value(parameter.name, value)
|
|
225
213
|
|
|
226
214
|
if parameter.name in current_node.parameter_values:
|
|
227
215
|
parameter_value = current_node.get_parameter_value(parameter.name)
|
|
@@ -3,6 +3,7 @@ from dataclasses import dataclass, field
|
|
|
3
3
|
from griptape.memory.structure import Run
|
|
4
4
|
|
|
5
5
|
from griptape_nodes.retained_mode.events.base_events import (
|
|
6
|
+
ExecutionPayload,
|
|
6
7
|
RequestPayload,
|
|
7
8
|
ResultPayloadFailure,
|
|
8
9
|
ResultPayloadSuccess,
|
|
@@ -81,3 +82,9 @@ class ResetAgentConversationMemoryResultSuccess(WorkflowNotAlteredMixin, ResultP
|
|
|
81
82
|
@PayloadRegistry.register
|
|
82
83
|
class ResetAgentConversationMemoryResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
83
84
|
pass
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
@PayloadRegistry.register
|
|
89
|
+
class AgentStreamEvent(ExecutionPayload):
|
|
90
|
+
token: str
|
|
@@ -557,3 +557,19 @@ class ProgressEvent(GtBaseEvent):
|
|
|
557
557
|
value: Any = field()
|
|
558
558
|
node_name: str = field()
|
|
559
559
|
parameter_name: str = field()
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
# Special internal request for flushing parameter changes
|
|
563
|
+
@dataclass(kw_only=True)
|
|
564
|
+
class FlushParameterChangesRequest(RequestPayload, WorkflowNotAlteredMixin):
|
|
565
|
+
pass
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
@dataclass
|
|
569
|
+
class FlushParameterChangesResultSuccess(ResultPayloadSuccess):
|
|
570
|
+
pass
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
@dataclass
|
|
574
|
+
class FlushParameterChangesResultFailure(ResultPayloadFailure):
|
|
575
|
+
pass
|
|
@@ -196,3 +196,29 @@ class UnloadLibraryFromRegistryResultSuccess(WorkflowAlteredMixin, ResultPayload
|
|
|
196
196
|
@PayloadRegistry.register
|
|
197
197
|
class UnloadLibraryFromRegistryResultFailure(ResultPayloadFailure):
|
|
198
198
|
pass
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@dataclass
|
|
202
|
+
@PayloadRegistry.register
|
|
203
|
+
class ReloadAllLibrariesRequest(RequestPayload):
|
|
204
|
+
"""WARNING: This request will CLEAR ALL CURRENT WORKFLOW STATE!
|
|
205
|
+
|
|
206
|
+
Reloading all libraries requires clearing all existing workflows, nodes, and execution state
|
|
207
|
+
because there is no way to comprehensively erase references to old Python modules.
|
|
208
|
+
All current work will be lost and must be recreated after the reload operation completes.
|
|
209
|
+
|
|
210
|
+
Use this operation only when you need to pick up changes to library code during development
|
|
211
|
+
or when library corruption requires a complete reset.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@dataclass
|
|
216
|
+
@PayloadRegistry.register
|
|
217
|
+
class ReloadAllLibrariesResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
|
|
218
|
+
pass
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@dataclass
|
|
222
|
+
@PayloadRegistry.register
|
|
223
|
+
class ReloadAllLibrariesResultFailure(ResultPayloadFailure):
|
|
224
|
+
pass
|
|
@@ -298,3 +298,34 @@ class GetNodeElementDetailsResultFailure(WorkflowNotAlteredMixin, ResultPayloadF
|
|
|
298
298
|
@PayloadRegistry.register
|
|
299
299
|
class AlterElementEvent(ExecutionPayload):
|
|
300
300
|
element_details: dict[str, Any]
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@dataclass
|
|
304
|
+
@PayloadRegistry.register
|
|
305
|
+
class RenameParameterRequest(RequestPayload):
|
|
306
|
+
parameter_name: str
|
|
307
|
+
new_parameter_name: str
|
|
308
|
+
# If node name is None, use the Current Context
|
|
309
|
+
node_name: str | None = None
|
|
310
|
+
# initial_setup prevents unnecessary work when we are loading a workflow from a file.
|
|
311
|
+
initial_setup: bool = False
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@dataclass
|
|
315
|
+
@PayloadRegistry.register
|
|
316
|
+
class RenameParameterResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
|
|
317
|
+
old_parameter_name: str
|
|
318
|
+
new_parameter_name: str
|
|
319
|
+
node_name: str
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@dataclass
|
|
323
|
+
@PayloadRegistry.register
|
|
324
|
+
class RenameParameterResultFailure(ResultPayloadFailure):
|
|
325
|
+
pass
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
@dataclass
|
|
329
|
+
@PayloadRegistry.register
|
|
330
|
+
class RemoveElementEvent(ExecutionPayload):
|
|
331
|
+
element_id: str
|