griptape-nodes 0.41.0__py3-none-any.whl → 0.43.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. griptape_nodes/__init__.py +0 -0
  2. griptape_nodes/app/.python-version +0 -0
  3. griptape_nodes/app/__init__.py +1 -10
  4. griptape_nodes/app/api.py +199 -0
  5. griptape_nodes/app/app.py +140 -222
  6. griptape_nodes/app/watch.py +4 -2
  7. griptape_nodes/bootstrap/__init__.py +0 -0
  8. griptape_nodes/bootstrap/bootstrap_script.py +0 -0
  9. griptape_nodes/bootstrap/register_libraries_script.py +0 -0
  10. griptape_nodes/bootstrap/structure_config.yaml +0 -0
  11. griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
  12. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +0 -0
  13. griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +0 -0
  14. griptape_nodes/bootstrap/workflow_runners/__init__.py +0 -0
  15. griptape_nodes/bootstrap/workflow_runners/bootstrap_workflow_runner.py +0 -0
  16. griptape_nodes/bootstrap/workflow_runners/local_workflow_runner.py +0 -0
  17. griptape_nodes/bootstrap/workflow_runners/subprocess_workflow_runner.py +6 -2
  18. griptape_nodes/bootstrap/workflow_runners/workflow_runner.py +0 -0
  19. griptape_nodes/drivers/__init__.py +0 -0
  20. griptape_nodes/drivers/storage/__init__.py +0 -0
  21. griptape_nodes/drivers/storage/base_storage_driver.py +0 -0
  22. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +0 -0
  23. griptape_nodes/drivers/storage/local_storage_driver.py +5 -3
  24. griptape_nodes/drivers/storage/storage_backend.py +0 -0
  25. griptape_nodes/exe_types/__init__.py +0 -0
  26. griptape_nodes/exe_types/connections.py +0 -0
  27. griptape_nodes/exe_types/core_types.py +0 -0
  28. griptape_nodes/exe_types/flow.py +68 -368
  29. griptape_nodes/exe_types/node_types.py +17 -1
  30. griptape_nodes/exe_types/type_validator.py +0 -0
  31. griptape_nodes/machines/__init__.py +0 -0
  32. griptape_nodes/machines/control_flow.py +52 -20
  33. griptape_nodes/machines/fsm.py +16 -2
  34. griptape_nodes/machines/node_resolution.py +16 -14
  35. griptape_nodes/mcp_server/__init__.py +1 -0
  36. griptape_nodes/mcp_server/server.py +126 -0
  37. griptape_nodes/mcp_server/ws_request_manager.py +268 -0
  38. griptape_nodes/node_library/__init__.py +0 -0
  39. griptape_nodes/node_library/advanced_node_library.py +0 -0
  40. griptape_nodes/node_library/library_registry.py +0 -0
  41. griptape_nodes/node_library/workflow_registry.py +2 -2
  42. griptape_nodes/py.typed +0 -0
  43. griptape_nodes/retained_mode/__init__.py +0 -0
  44. griptape_nodes/retained_mode/events/__init__.py +0 -0
  45. griptape_nodes/retained_mode/events/agent_events.py +70 -8
  46. griptape_nodes/retained_mode/events/app_events.py +137 -12
  47. griptape_nodes/retained_mode/events/arbitrary_python_events.py +23 -0
  48. griptape_nodes/retained_mode/events/base_events.py +13 -31
  49. griptape_nodes/retained_mode/events/config_events.py +87 -11
  50. griptape_nodes/retained_mode/events/connection_events.py +56 -5
  51. griptape_nodes/retained_mode/events/context_events.py +27 -4
  52. griptape_nodes/retained_mode/events/execution_events.py +99 -14
  53. griptape_nodes/retained_mode/events/flow_events.py +165 -7
  54. griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
  55. griptape_nodes/retained_mode/events/library_events.py +195 -17
  56. griptape_nodes/retained_mode/events/logger_events.py +11 -0
  57. griptape_nodes/retained_mode/events/node_events.py +242 -22
  58. griptape_nodes/retained_mode/events/object_events.py +40 -4
  59. griptape_nodes/retained_mode/events/os_events.py +116 -3
  60. griptape_nodes/retained_mode/events/parameter_events.py +212 -8
  61. griptape_nodes/retained_mode/events/payload_registry.py +0 -0
  62. griptape_nodes/retained_mode/events/secrets_events.py +59 -7
  63. griptape_nodes/retained_mode/events/static_file_events.py +57 -4
  64. griptape_nodes/retained_mode/events/validation_events.py +39 -4
  65. griptape_nodes/retained_mode/events/workflow_events.py +188 -17
  66. griptape_nodes/retained_mode/griptape_nodes.py +89 -363
  67. griptape_nodes/retained_mode/managers/__init__.py +0 -0
  68. griptape_nodes/retained_mode/managers/agent_manager.py +49 -23
  69. griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
  70. griptape_nodes/retained_mode/managers/config_manager.py +0 -0
  71. griptape_nodes/retained_mode/managers/context_manager.py +0 -0
  72. griptape_nodes/retained_mode/managers/engine_identity_manager.py +146 -0
  73. griptape_nodes/retained_mode/managers/event_manager.py +14 -2
  74. griptape_nodes/retained_mode/managers/flow_manager.py +751 -64
  75. griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +45 -0
  76. griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +191 -0
  77. griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +346 -0
  78. griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +439 -0
  79. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +17 -0
  80. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +82 -0
  81. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +116 -0
  82. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +352 -0
  83. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +104 -0
  84. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +155 -0
  85. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +18 -0
  86. griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +12 -0
  87. griptape_nodes/retained_mode/managers/library_manager.py +255 -40
  88. griptape_nodes/retained_mode/managers/node_manager.py +120 -103
  89. griptape_nodes/retained_mode/managers/object_manager.py +11 -3
  90. griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
  91. griptape_nodes/retained_mode/managers/os_manager.py +582 -8
  92. griptape_nodes/retained_mode/managers/secrets_manager.py +4 -0
  93. griptape_nodes/retained_mode/managers/session_manager.py +328 -0
  94. griptape_nodes/retained_mode/managers/settings.py +7 -0
  95. griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
  96. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +2 -2
  97. griptape_nodes/retained_mode/managers/workflow_manager.py +722 -456
  98. griptape_nodes/retained_mode/retained_mode.py +44 -0
  99. griptape_nodes/retained_mode/utils/__init__.py +0 -0
  100. griptape_nodes/retained_mode/utils/engine_identity.py +141 -27
  101. griptape_nodes/retained_mode/utils/name_generator.py +0 -0
  102. griptape_nodes/traits/__init__.py +0 -0
  103. griptape_nodes/traits/add_param_button.py +0 -0
  104. griptape_nodes/traits/button.py +0 -0
  105. griptape_nodes/traits/clamp.py +0 -0
  106. griptape_nodes/traits/compare.py +0 -0
  107. griptape_nodes/traits/compare_images.py +0 -0
  108. griptape_nodes/traits/file_system_picker.py +127 -0
  109. griptape_nodes/traits/minmax.py +0 -0
  110. griptape_nodes/traits/options.py +0 -0
  111. griptape_nodes/traits/slider.py +0 -0
  112. griptape_nodes/traits/trait_registry.py +0 -0
  113. griptape_nodes/traits/traits.json +0 -0
  114. griptape_nodes/updater/__init__.py +2 -2
  115. griptape_nodes/updater/__main__.py +0 -0
  116. griptape_nodes/utils/__init__.py +0 -0
  117. griptape_nodes/utils/dict_utils.py +0 -0
  118. griptape_nodes/utils/image_preview.py +128 -0
  119. griptape_nodes/utils/metaclasses.py +0 -0
  120. griptape_nodes/version_compatibility/__init__.py +0 -0
  121. griptape_nodes/version_compatibility/versions/__init__.py +0 -0
  122. griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
  123. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +5 -5
  124. griptape_nodes-0.43.0.dist-info/METADATA +90 -0
  125. griptape_nodes-0.43.0.dist-info/RECORD +129 -0
  126. griptape_nodes-0.43.0.dist-info/WHEEL +4 -0
  127. {griptape_nodes-0.41.0.dist-info → griptape_nodes-0.43.0.dist-info}/entry_points.txt +1 -0
  128. griptape_nodes/app/app_sessions.py +0 -458
  129. griptape_nodes/retained_mode/utils/session_persistence.py +0 -105
  130. griptape_nodes-0.41.0.dist-info/METADATA +0 -78
  131. griptape_nodes-0.41.0.dist-info/RECORD +0 -112
  132. griptape_nodes-0.41.0.dist-info/WHEEL +0 -4
  133. griptape_nodes-0.41.0.dist-info/licenses/LICENSE +0 -201
@@ -1,19 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- from queue import Queue
5
4
  from typing import TYPE_CHECKING, NamedTuple
6
5
 
7
- from griptape.events import EventBus
8
-
9
- from griptape_nodes.exe_types.connections import Connections
10
- from griptape_nodes.exe_types.core_types import ParameterTypeBuiltin
11
- from griptape_nodes.exe_types.node_types import NodeResolutionState, StartLoopNode, StartNode
12
- from griptape_nodes.machines.control_flow import CompleteState, ControlFlowMachine
13
- from griptape_nodes.retained_mode.events.base_events import ExecutionEvent, ExecutionGriptapeNodeEvent
14
- from griptape_nodes.retained_mode.events.execution_events import ControlFlowCancelledEvent
15
-
16
6
  if TYPE_CHECKING:
7
+ from queue import Queue
8
+
17
9
  from griptape_nodes.exe_types.core_types import Parameter
18
10
  from griptape_nodes.exe_types.node_types import BaseNode
19
11
 
@@ -28,22 +20,16 @@ class CurrentNodes(NamedTuple):
28
20
  current_resolving_node: str | None
29
21
 
30
22
 
31
- # The flow will own all of the nodes and the connections
23
+ # The flow will own all of the nodes
32
24
  class ControlFlow:
33
25
  name: str
34
- connections: Connections
35
26
  nodes: dict[str, BaseNode]
36
- control_flow_machine: ControlFlowMachine
37
- single_node_resolution: bool
38
- flow_queue: Queue[BaseNode]
27
+ metadata: dict
39
28
 
40
- def __init__(self, name: str) -> None:
29
+ def __init__(self, name: str, metadata: dict | None = None) -> None:
41
30
  self.name = name
42
- self.connections = Connections()
43
31
  self.nodes = {}
44
- self.control_flow_machine = ControlFlowMachine(self)
45
- self.single_node_resolution = False
46
- self.flow_queue = Queue()
32
+ self.metadata = metadata or {}
47
33
 
48
34
  def add_node(self, node: BaseNode) -> None:
49
35
  self.nodes[node.name] = node
@@ -59,15 +45,25 @@ class ControlFlow:
59
45
  target_parameter: Parameter,
60
46
  ) -> bool:
61
47
  if source_node.name in self.nodes and target_node.name in self.nodes:
62
- return self.connections.add_connection(source_node, source_parameter, target_node, target_parameter)
48
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
49
+
50
+ return (
51
+ GriptapeNodes.FlowManager()
52
+ .get_connections()
53
+ .add_connection(source_node, source_parameter, target_node, target_parameter)
54
+ )
63
55
  return False
64
56
 
65
57
  def remove_connection(
66
58
  self, source_node: BaseNode, source_parameter: Parameter, target_node: BaseNode, target_parameter: Parameter
67
59
  ) -> bool:
68
60
  if source_node.name in self.nodes and target_node.name in self.nodes:
69
- return self.connections.remove_connection(
70
- source_node.name, source_parameter.name, target_node.name, target_parameter.name
61
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
62
+
63
+ return (
64
+ GriptapeNodes.FlowManager()
65
+ .get_connections()
66
+ .remove_connection(source_node.name, source_parameter.name, target_node.name, target_parameter.name)
71
67
  )
72
68
  return False
73
69
 
@@ -78,343 +74,24 @@ class ControlFlow:
78
74
  target_node: BaseNode,
79
75
  target_parameter: Parameter,
80
76
  ) -> bool:
81
- if source_node.name in self.nodes and target_node.name in self.nodes:
82
- connected_node_tuple = self.get_connected_output_parameters(node=source_node, param=source_parameter)
83
- if connected_node_tuple is not None:
84
- for connected_node_values in connected_node_tuple:
85
- connected_node, connected_param = connected_node_values
86
- if connected_node is target_node and connected_param is target_parameter:
87
- return True
88
- return False
77
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
89
78
 
90
- def start_flow(self, start_node: BaseNode | None = None, debug_mode: bool = False) -> None: # noqa: FBT001, FBT002
91
- if self.check_for_existing_running_flow():
92
- # If flow already exists, throw an error
93
- errormsg = "This workflow is already in progress. Please wait for the current process to finish before starting again."
94
- raise RuntimeError(errormsg)
95
-
96
- if start_node is None:
97
- if self.flow_queue.empty():
98
- errormsg = "No Flow exists. You must create at least one control connection."
99
- raise RuntimeError(errormsg)
100
- start_node = self.flow_queue.get()
101
-
102
- try:
103
- self.control_flow_machine.start_flow(start_node, debug_mode)
104
- self.flow_queue.task_done()
105
- except Exception:
106
- if self.check_for_existing_running_flow():
107
- self.cancel_flow_run()
108
- raise
109
-
110
- def check_for_existing_running_flow(self) -> bool:
111
- if self.control_flow_machine._current_state is not CompleteState and self.control_flow_machine._current_state:
112
- # Flow already exists in progress
113
- return True
114
- return bool(
115
- not self.control_flow_machine._context.resolution_machine.is_complete()
116
- and self.control_flow_machine._context.resolution_machine.is_started()
117
- )
118
-
119
- def resolve_singular_node(self, node: BaseNode, debug_mode: bool = False) -> None: # noqa: FBT001, FBT002
120
- # Set that we are only working on one node right now! no other stepping allowed
121
- if self.check_for_existing_running_flow():
122
- # If flow already exists, throw an error
123
- errormsg = f"This workflow is already in progress. Please wait for the current process to finish before starting {node.name} again."
124
- raise RuntimeError(errormsg)
125
- self.single_node_resolution = True
126
- # Get the node resolution machine for the current flow!
127
- self.control_flow_machine._context.current_node = node
128
- resolution_machine = self.control_flow_machine._context.resolution_machine
129
- # Set debug mode
130
- resolution_machine.change_debug_mode(debug_mode)
131
- # Resolve the node.
132
- node.state = NodeResolutionState.UNRESOLVED
133
- resolution_machine.resolve_node(node)
134
- # decide if we can change it back to normal flow mode!
135
- if resolution_machine.is_complete():
136
- self.single_node_resolution = False
137
- self.control_flow_machine._context.current_node = None
138
-
139
- def single_execution_step(self, change_debug_mode: bool) -> None: # noqa: FBT001
140
- # do a granular step
141
- if not self.check_for_existing_running_flow():
142
- if self.flow_queue.empty():
143
- errormsg = "Flow has not yet been started. Cannot step while no flow has begun."
144
- raise RuntimeError(errormsg)
145
- start_node = self.flow_queue.get()
146
- self.control_flow_machine.start_flow(start_node, debug_mode=True)
147
- start_node = self.flow_queue.task_done()
148
- return
149
- self.control_flow_machine.granular_step(change_debug_mode)
150
- resolution_machine = self.control_flow_machine._context.resolution_machine
151
- if self.single_node_resolution:
152
- resolution_machine = self.control_flow_machine._context.resolution_machine
153
- if resolution_machine.is_complete():
154
- self.single_node_resolution = False
155
-
156
- def single_node_step(self) -> None:
157
- # It won't call single_node_step without an existing flow running from US.
158
- if not self.check_for_existing_running_flow():
159
- if self.flow_queue.empty():
160
- errormsg = "Flow has not yet been started. Cannot step while no flow has begun."
161
- raise RuntimeError(errormsg)
162
- start_node = self.flow_queue.get()
163
- self.control_flow_machine.start_flow(start_node, debug_mode=True)
164
- start_node = self.flow_queue.task_done()
165
- return
166
- # Step over a whole node
167
- if self.single_node_resolution:
168
- msg = "Cannot step through the Control Flow in Single Node Execution"
169
- raise RuntimeError(msg)
170
- self.control_flow_machine.node_step()
171
- # Start the next resolution step now please.
172
- if not self.check_for_existing_running_flow() and not self.flow_queue.empty():
173
- start_node = self.flow_queue.get()
174
- self.flow_queue.task_done()
175
- self.control_flow_machine.start_flow(start_node, debug_mode=True)
176
-
177
- def continue_executing(self) -> None:
178
- if not self.check_for_existing_running_flow():
179
- if self.flow_queue.empty():
180
- errormsg = "Flow has not yet been started. Cannot step while no flow has begun."
181
- raise RuntimeError(errormsg)
182
- start_node = self.flow_queue.get()
183
- self.flow_queue.task_done()
184
- self.control_flow_machine.start_flow(start_node, debug_mode=False)
185
- return
186
- # Turn all debugging to false and continue on
187
- self.control_flow_machine.change_debug_mode(False)
188
- if self.single_node_resolution:
189
- if self.control_flow_machine._context.resolution_machine.is_complete():
190
- self.single_node_resolution = False
191
- else:
192
- self.control_flow_machine._context.resolution_machine.update()
193
- else:
194
- self.control_flow_machine.node_step()
195
- # Now it is done executing. make sure it's actually done?
196
- if not self.check_for_existing_running_flow() and not self.flow_queue.empty():
197
- start_node = self.flow_queue.get()
198
- self.flow_queue.task_done()
199
- self.control_flow_machine.start_flow(start_node, debug_mode=False)
200
-
201
- def cancel_flow_run(self) -> None:
202
- if not self.check_for_existing_running_flow():
203
- errormsg = "Flow has not yet been started. Cannot cancel flow that hasn't begun."
204
- raise RuntimeError(errormsg)
205
- self.clear_flow_queue()
206
- self.control_flow_machine.reset_machine()
207
- # Reset control flow machine
208
- self.single_node_resolution = False
209
- logger.debug("Cancelling flow run")
210
-
211
- EventBus.publish_event(
212
- ExecutionGriptapeNodeEvent(wrapped_event=ExecutionEvent(payload=ControlFlowCancelledEvent()))
213
- )
214
-
215
- def unresolve_whole_flow(self) -> None:
216
- for node in self.nodes.values():
217
- node.make_node_unresolved(current_states_to_trigger_change_event=None)
218
-
219
- def flow_state(self) -> CurrentNodes:
220
- if not self.check_for_existing_running_flow():
221
- msg = "Flow hasn't started."
222
- raise RuntimeError(msg)
223
- current_control_node = (
224
- self.control_flow_machine._context.current_node.name
225
- if self.control_flow_machine._context.current_node is not None
226
- else None
227
- )
228
- focus_stack_for_node = self.control_flow_machine._context.resolution_machine._context.focus_stack
229
- current_resolving_node = focus_stack_for_node[-1].node.name if len(focus_stack_for_node) else None
230
- return CurrentNodes(current_control_node, current_resolving_node)
231
-
232
- def clear_flow_queue(self) -> None:
233
- self.flow_queue.queue.clear()
79
+ return GriptapeNodes.FlowManager().has_connection(source_node, source_parameter, target_node, target_parameter)
234
80
 
235
- def get_connected_output_parameters(self, node: BaseNode, param: Parameter) -> list[tuple[BaseNode, Parameter]]:
236
- connections = []
237
- if node.name in self.connections.outgoing_index:
238
- outgoing_params = self.connections.outgoing_index[node.name]
239
- if param.name in outgoing_params:
240
- for connection_id in outgoing_params[param.name]:
241
- connection = self.connections.connections[connection_id]
242
- connections.append((connection.target_node, connection.target_parameter))
243
- return connections
81
+ def clear_execution_queue(self) -> None:
82
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
244
83
 
245
- def get_connected_input_parameters(self, node: BaseNode, param: Parameter) -> list[tuple[BaseNode, Parameter]]:
246
- connections = []
247
- if node.name in self.connections.incoming_index:
248
- incoming_params = self.connections.incoming_index[node.name]
249
- if param.name in incoming_params:
250
- for connection_id in incoming_params[param.name]:
251
- connection = self.connections.connections[connection_id]
252
- connections.append((connection.source_node, connection.source_parameter))
253
- return connections
254
-
255
- def get_connected_output_from_node(self, node: BaseNode) -> list[tuple[BaseNode, Parameter]]:
256
- connections = []
257
- if node.name in self.connections.outgoing_index:
258
- connection_ids = [
259
- item for value_list in self.connections.outgoing_index[node.name].values() for item in value_list
260
- ]
261
- for connection_id in connection_ids:
262
- connection = self.connections.connections[connection_id]
263
- connections.append((connection.target_node, connection.target_parameter))
264
- return connections
265
-
266
- def get_connected_input_from_node(self, node: BaseNode) -> list[tuple[BaseNode, Parameter]]:
267
- connections = []
268
- if node.name in self.connections.incoming_index:
269
- connection_ids = [
270
- item for value_list in self.connections.incoming_index[node.name].values() for item in value_list
271
- ]
272
- for connection_id in connection_ids:
273
- connection = self.connections.connections[connection_id]
274
- connections.append((connection.source_node, connection.source_parameter))
275
- return connections
276
-
277
- def get_start_node_queue(self) -> Queue | None: # noqa: C901, PLR0912
278
- # check all nodes in flow
279
- # add them all to a stack. We're calling this only if no flow was specified, so we're running them all.
280
- self.flow_queue = Queue()
281
- # if no nodes, no flow.
282
- if not len(self.nodes):
283
- return None
284
- data_nodes = []
285
- valid_data_nodes = []
286
- start_nodes = []
287
- control_nodes = []
288
- for node in self.nodes.values():
289
- # if it's a start node, start here! Return the first one!
290
- if isinstance(node, StartNode):
291
- start_nodes.append(node)
292
- continue
293
- # no start nodes. let's find the first control node.
294
- # if it's a control node, there could be a flow.
295
- control_param = False
296
- for parameter in node.parameters:
297
- if ParameterTypeBuiltin.CONTROL_TYPE.value == parameter.output_type:
298
- control_param = True
299
- break
300
- if not control_param:
301
- # saving this for later
302
- data_nodes.append(node)
303
- # If this node doesn't have a control connection..
304
- continue
305
- cn_mgr = self.connections
306
- # check if it has an incoming connection. If it does, it's not a start node
307
- has_control_connection = False
308
- if node.name in cn_mgr.incoming_index:
309
- for param_name in cn_mgr.incoming_index[node.name]:
310
- param = node.get_parameter_by_name(param_name)
311
- if param and ParameterTypeBuiltin.CONTROL_TYPE.value == param.output_type:
312
- # there is a control connection coming in
313
- has_control_connection = True
314
- break
315
- # if there is a connection coming in, isn't a start.
316
- if has_control_connection and not isinstance(node, StartLoopNode):
317
- continue
318
- # Does it have an outgoing connection?
319
- if node.name in cn_mgr.outgoing_index:
320
- # If one of the outgoing connections is control, add it. otherwise don't.
321
- for param_name in cn_mgr.outgoing_index[node.name]:
322
- param = node.get_parameter_by_name(param_name)
323
- if param and ParameterTypeBuiltin.CONTROL_TYPE.value == param.output_type:
324
- control_nodes.append(node)
325
- break
326
- else:
327
- control_nodes.append(node)
328
-
329
- # If we've gotten to this point, there are no control parameters
330
- # Let's return a data node that has no OUTGOING data connections!
331
- for node in data_nodes:
332
- cn_mgr = self.connections
333
- # check if it has an outgoing connection. We don't want it to (that means we get the most resolution)
334
- if node.name not in cn_mgr.outgoing_index:
335
- valid_data_nodes.append(node)
336
- # ok now
337
- for node in start_nodes:
338
- self.flow_queue.put(node)
339
- for node in control_nodes:
340
- self.flow_queue.put(node)
341
- for node in valid_data_nodes:
342
- self.flow_queue.put(node)
343
-
344
- return self.flow_queue
345
-
346
- def get_start_node_from_node(self, node: BaseNode) -> BaseNode | None:
347
- # backwards chain in control outputs.
348
- if node not in self.nodes.values():
349
- return None
350
- # Go back through incoming control connections to get the start node
351
- curr_node = node
352
- prev_node = self.get_prev_node(curr_node)
353
- # Fencepost loop - get the first previous node name and then we go
354
- while prev_node:
355
- curr_node = prev_node
356
- prev_node = self.get_prev_node(prev_node)
357
- return curr_node
358
-
359
- def get_prev_node(self, node: BaseNode) -> BaseNode | None:
360
- if node.name in self.connections.incoming_index:
361
- parameters = self.connections.incoming_index[node.name]
362
- for parameter_name in parameters:
363
- parameter = node.get_parameter_by_name(parameter_name)
364
- if parameter and ParameterTypeBuiltin.CONTROL_TYPE.value == parameter.output_type:
365
- # this is a control connection
366
- connection_ids = self.connections.incoming_index[node.name][parameter_name]
367
- for connection_id in connection_ids:
368
- connection = self.connections.connections[connection_id]
369
- return connection.get_source_node()
370
- return None
371
-
372
- def stop_flow_breakpoint(self, node: BaseNode) -> None:
373
- # This will prevent the flow from continuing on.
374
- node.stop_flow = True
84
+ GriptapeNodes.FlowManager().clear_execution_queue()
375
85
 
376
86
  def get_connections_on_node(self, node: BaseNode) -> list[BaseNode] | None:
377
- # get all of the connection ids
378
- connected_nodes = []
379
- # Handle outgoing connections
380
- if node.name in self.connections.outgoing_index:
381
- outgoing_params = self.connections.outgoing_index[node.name]
382
- outgoing_connection_ids = []
383
- for connection_ids in outgoing_params.values():
384
- outgoing_connection_ids = outgoing_connection_ids + connection_ids
385
- for connection_id in outgoing_connection_ids:
386
- connection = self.connections.connections[connection_id]
387
- if connection.source_node not in connected_nodes:
388
- connected_nodes.append(connection.target_node)
389
- # Handle incoming connections
390
- if node.name in self.connections.incoming_index:
391
- incoming_params = self.connections.incoming_index[node.name]
392
- incoming_connection_ids = []
393
- for connection_ids in incoming_params.values():
394
- incoming_connection_ids = incoming_connection_ids + connection_ids
395
- for connection_id in incoming_connection_ids:
396
- connection = self.connections.connections[connection_id]
397
- if connection.source_node not in connected_nodes:
398
- connected_nodes.append(connection.source_node)
399
- # Return all connected nodes. No duplicates
400
- return connected_nodes
87
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
88
+
89
+ return GriptapeNodes.FlowManager().get_connections_on_node(self, node)
401
90
 
402
91
  def get_all_connected_nodes(self, node: BaseNode) -> list[BaseNode]:
403
- discovered = {}
404
- processed = {}
405
- queue = Queue()
406
- queue.put(node)
407
- discovered[node] = True
408
- while not queue.empty():
409
- curr_node = queue.get()
410
- processed[curr_node] = True
411
- next_nodes = self.get_connections_on_node(curr_node)
412
- if next_nodes:
413
- for next_node in next_nodes:
414
- if next_node not in discovered:
415
- discovered[next_node] = True
416
- queue.put(next_node)
417
- return list(processed.keys())
92
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
93
+
94
+ return GriptapeNodes.FlowManager().get_all_connected_nodes(self, node)
418
95
 
419
96
  def get_node_dependencies(self, node: BaseNode) -> list[BaseNode]:
420
97
  """Get all upstream nodes that the given node depends on.
@@ -428,18 +105,41 @@ class ControlFlow:
428
105
  Returns:
429
106
  list[BaseNode]: A list of all nodes that the given node depends on, including the node itself (as the first element)
430
107
  """
431
- node_list = [node]
432
- node_queue = Queue()
433
- node_queue.put(node)
434
- while not node_queue.empty():
435
- curr_node = node_queue.get()
436
- input_connections = self.get_connected_input_from_node(curr_node)
437
- if input_connections:
438
- for input_node, input_parameter in input_connections:
439
- if (
440
- ParameterTypeBuiltin.CONTROL_TYPE.value != input_parameter.output_type
441
- and input_node not in node_list
442
- ):
443
- node_list.append(input_node)
444
- node_queue.put(input_node)
445
- return node_list
108
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
109
+
110
+ return GriptapeNodes.FlowManager().get_node_dependencies(self, node)
111
+
112
+ def get_connected_output_parameters(self, node: BaseNode, param: Parameter) -> list[tuple[BaseNode, Parameter]]:
113
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
114
+
115
+ return GriptapeNodes.FlowManager().get_connected_output_parameters(node, param)
116
+
117
+ def get_connected_input_parameters(self, node: BaseNode, param: Parameter) -> list[tuple[BaseNode, Parameter]]:
118
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
119
+
120
+ return GriptapeNodes.FlowManager().get_connected_input_parameters(self, node, param)
121
+
122
+ def get_connected_output_from_node(self, node: BaseNode) -> list[tuple[BaseNode, Parameter]]:
123
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
124
+
125
+ return GriptapeNodes.FlowManager().get_connected_output_from_node(self, node)
126
+
127
+ def get_connected_input_from_node(self, node: BaseNode) -> list[tuple[BaseNode, Parameter]]:
128
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
129
+
130
+ return GriptapeNodes.FlowManager().get_connected_input_from_node(self, node)
131
+
132
+ def get_start_node_queue(self) -> Queue | None:
133
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
134
+
135
+ return GriptapeNodes.FlowManager().get_start_node_queue()
136
+
137
+ def get_start_node_from_node(self, node: BaseNode) -> BaseNode | None:
138
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
139
+
140
+ return GriptapeNodes.FlowManager().get_start_node_from_node(self, node)
141
+
142
+ def get_prev_node(self, node: BaseNode) -> BaseNode | None:
143
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
144
+
145
+ return GriptapeNodes.FlowManager().get_prev_node(self, node)
@@ -65,6 +65,9 @@ class BaseNode(ABC):
65
65
  stop_flow: bool = False
66
66
  root_ui_element: BaseNodeElement
67
67
  _tracked_parameters: list[BaseNodeElement]
68
+ _entry_control_parameter: Parameter | None = (
69
+ None # The control input parameter used to enter this node during execution
70
+ )
68
71
 
69
72
  @property
70
73
  def parameters(self) -> list[Parameter]:
@@ -92,6 +95,7 @@ class BaseNode(ABC):
92
95
  self.root_ui_element._node_context = self
93
96
  self.process_generator = None
94
97
  self._tracked_parameters = []
98
+ self.set_entry_control_parameter(None)
95
99
 
96
100
  # 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!
97
101
  # https://github.com/griptape-ai/griptape-nodes/issues/994
@@ -106,6 +110,18 @@ class BaseNode(ABC):
106
110
  )
107
111
  )
108
112
  self.state = NodeResolutionState.UNRESOLVED
113
+ # NOTE: _entry_control_parameter is NOT cleared here as it represents execution context
114
+ # that should persist through the resolve/unresolve cycle during a single execution
115
+
116
+ def set_entry_control_parameter(self, parameter: Parameter | None) -> None:
117
+ """Set the control parameter that was used to enter this node.
118
+
119
+ This should only be called by the ControlFlowContext during execution.
120
+
121
+ Args:
122
+ parameter: The control input parameter that triggered this node's execution, or None to clear
123
+ """
124
+ self._entry_control_parameter = parameter
109
125
 
110
126
  def emit_parameter_changes(self) -> None:
111
127
  if self._tracked_parameters:
@@ -577,7 +593,7 @@ class BaseNode(ABC):
577
593
 
578
594
  def _flatten(items: Iterable[Any]) -> Generator[Any, None, None]:
579
595
  for item in items:
580
- if isinstance(item, Iterable) and not isinstance(item, (str, bytes)):
596
+ if isinstance(item, Iterable) and not isinstance(item, (str, bytes, dict)):
581
597
  yield from _flatten(item)
582
598
  elif item:
583
599
  yield item
File without changes
File without changes
@@ -2,10 +2,12 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import logging
5
+ from dataclasses import dataclass
5
6
  from typing import TYPE_CHECKING
6
7
 
7
8
  from griptape.events import EventBus
8
9
 
10
+ from griptape_nodes.exe_types.core_types import Parameter
9
11
  from griptape_nodes.exe_types.node_types import BaseNode, NodeResolutionState
10
12
  from griptape_nodes.exe_types.type_validator import TypeValidator
11
13
  from griptape_nodes.machines.fsm import FSM, State
@@ -17,6 +19,15 @@ from griptape_nodes.retained_mode.events.execution_events import (
17
19
  SelectedControlOutputEvent,
18
20
  )
19
21
 
22
+
23
+ @dataclass
24
+ class NextNodeInfo:
25
+ """Information about the next node to execute and how to reach it."""
26
+
27
+ node: BaseNode
28
+ entry_parameter: Parameter | None
29
+
30
+
20
31
  if TYPE_CHECKING:
21
32
  from griptape_nodes.exe_types.core_types import Parameter
22
33
  from griptape_nodes.exe_types.flow import ControlFlow
@@ -32,21 +43,30 @@ class ControlFlowContext:
32
43
  selected_output: Parameter | None
33
44
  paused: bool = False
34
45
 
35
- def __init__(self, flow: ControlFlow) -> None:
36
- self.resolution_machine = NodeResolutionMachine(flow)
37
- self.flow = flow
46
+ def __init__(self) -> None:
47
+ self.resolution_machine = NodeResolutionMachine()
38
48
  self.current_node = None
39
49
 
40
- def get_next_node(self, output_parameter: Parameter) -> BaseNode | None:
50
+ def get_next_node(self, output_parameter: Parameter) -> NextNodeInfo | None:
51
+ """Get the next node and the target parameter that will receive the control flow.
52
+
53
+ Returns:
54
+ NextNodeInfo | None: Information about the next node or None if no connection
55
+ """
41
56
  if self.current_node is not None:
42
- node = self.flow.connections.get_connected_node(self.current_node, output_parameter)
57
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
58
+
59
+ node_connection = (
60
+ GriptapeNodes.FlowManager().get_connections().get_connected_node(self.current_node, output_parameter)
61
+ )
62
+ if node_connection is not None:
63
+ node, entry_parameter = node_connection
64
+ return NextNodeInfo(node=node, entry_parameter=entry_parameter)
65
+ # Continue Execution to the next node that needs to be executed using global execution queue
66
+ # Get the next node in the execution queue, or None if queue is empty
67
+ node = GriptapeNodes.FlowManager().get_next_node_from_execution_queue()
43
68
  if node is not None:
44
- node, _ = node
45
- # Continue Execution to the next node that needs to be executed.
46
- elif not self.flow.flow_queue.empty():
47
- node = self.flow.flow_queue.get()
48
- self.flow.flow_queue.task_done()
49
- return node
69
+ return NextNodeInfo(node=node, entry_parameter=None)
50
70
  return None
51
71
 
52
72
  def reset(self) -> None:
@@ -110,10 +130,11 @@ class NextNodeState(State):
110
130
  context.current_node.stop_flow = False
111
131
  return CompleteState
112
132
  next_output = context.current_node.get_next_control_output()
113
- next_node = None
133
+ next_node_info = None
134
+
114
135
  if next_output is not None:
115
136
  context.selected_output = next_output
116
- next_node = context.get_next_node(context.selected_output)
137
+ next_node_info = context.get_next_node(context.selected_output)
117
138
  EventBus.publish_event(
118
139
  ExecutionGriptapeNodeEvent(
119
140
  wrapped_event=ExecutionEvent(
@@ -124,14 +145,23 @@ class NextNodeState(State):
124
145
  )
125
146
  )
126
147
  )
127
- elif not context.flow.flow_queue.empty():
128
- next_node = context.flow.flow_queue.get()
129
- context.flow.flow_queue.task_done()
148
+ else:
149
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
150
+
151
+ # Get the next node in the execution queue, or None if queue is empty
152
+ next_node = GriptapeNodes.FlowManager().get_next_node_from_execution_queue()
153
+ if next_node is not None:
154
+ next_node_info = NextNodeInfo(node=next_node, entry_parameter=None)
155
+
130
156
  # The parameter that will be evaluated next
131
- if next_node is None:
157
+ if next_node_info is None:
132
158
  # If no node attached
133
159
  return CompleteState
134
- context.current_node = next_node
160
+
161
+ # Always set the entry control parameter (None for execution queue nodes)
162
+ next_node_info.node.set_entry_control_parameter(next_node_info.entry_parameter)
163
+
164
+ context.current_node = next_node_info.node
135
165
  context.selected_output = None
136
166
  if not context.paused:
137
167
  return ResolveNodeState
@@ -168,12 +198,14 @@ class CompleteState(State):
168
198
 
169
199
  # MACHINE TIME!!!
170
200
  class ControlFlowMachine(FSM[ControlFlowContext]):
171
- def __init__(self, flow: ControlFlow) -> None:
172
- context = ControlFlowContext(flow)
201
+ def __init__(self) -> None:
202
+ context = ControlFlowContext()
173
203
  super().__init__(context)
174
204
 
175
205
  def start_flow(self, start_node: BaseNode, debug_mode: bool = False) -> None: # noqa: FBT001, FBT002
176
206
  self._context.current_node = start_node
207
+ # Set entry control parameter for initial node (None for workflow start)
208
+ start_node.set_entry_control_parameter(None)
177
209
  # Set up to debug
178
210
  self._context.paused = debug_mode
179
211
  self.start(ResolveNodeState) # Begins the flow