griptape-nodes 0.51.2__py3-none-any.whl → 0.52.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- griptape_nodes/__init__.py +5 -4
- griptape_nodes/app/api.py +22 -30
- griptape_nodes/app/app.py +374 -289
- griptape_nodes/app/watch.py +17 -2
- griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +66 -103
- griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +16 -4
- griptape_nodes/exe_types/core_types.py +16 -4
- griptape_nodes/exe_types/node_types.py +74 -16
- griptape_nodes/machines/control_flow.py +21 -26
- griptape_nodes/machines/fsm.py +16 -16
- griptape_nodes/machines/node_resolution.py +30 -119
- griptape_nodes/mcp_server/server.py +14 -10
- griptape_nodes/mcp_server/ws_request_manager.py +2 -2
- griptape_nodes/node_library/workflow_registry.py +5 -0
- griptape_nodes/retained_mode/events/base_events.py +12 -7
- griptape_nodes/retained_mode/events/execution_events.py +0 -6
- griptape_nodes/retained_mode/events/node_events.py +38 -0
- griptape_nodes/retained_mode/events/parameter_events.py +11 -0
- griptape_nodes/retained_mode/events/variable_events.py +361 -0
- griptape_nodes/retained_mode/events/workflow_events.py +35 -0
- griptape_nodes/retained_mode/griptape_nodes.py +61 -26
- griptape_nodes/retained_mode/managers/agent_manager.py +8 -9
- griptape_nodes/retained_mode/managers/event_manager.py +215 -74
- griptape_nodes/retained_mode/managers/flow_manager.py +39 -33
- griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +14 -14
- griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +20 -20
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +1 -1
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +1 -1
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +4 -3
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +1 -1
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +1 -1
- griptape_nodes/retained_mode/managers/library_manager.py +20 -19
- griptape_nodes/retained_mode/managers/node_manager.py +83 -8
- griptape_nodes/retained_mode/managers/object_manager.py +4 -0
- griptape_nodes/retained_mode/managers/settings.py +1 -0
- griptape_nodes/retained_mode/managers/sync_manager.py +3 -9
- griptape_nodes/retained_mode/managers/variable_manager.py +529 -0
- griptape_nodes/retained_mode/managers/workflow_manager.py +156 -50
- griptape_nodes/retained_mode/variable_types.py +18 -0
- griptape_nodes/utils/__init__.py +4 -0
- griptape_nodes/utils/async_utils.py +89 -0
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/METADATA +2 -3
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/RECORD +45 -42
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/WHEEL +1 -1
- griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +0 -90
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import NamedTuple
|
|
3
|
+
|
|
4
|
+
from griptape_nodes.retained_mode.events.base_events import ResultPayload
|
|
5
|
+
from griptape_nodes.retained_mode.events.variable_events import (
|
|
6
|
+
CreateVariableRequest,
|
|
7
|
+
CreateVariableResultFailure,
|
|
8
|
+
CreateVariableResultSuccess,
|
|
9
|
+
DeleteVariableRequest,
|
|
10
|
+
DeleteVariableResultFailure,
|
|
11
|
+
DeleteVariableResultSuccess,
|
|
12
|
+
GetVariableDetailsRequest,
|
|
13
|
+
GetVariableDetailsResultFailure,
|
|
14
|
+
GetVariableDetailsResultSuccess,
|
|
15
|
+
GetVariableRequest,
|
|
16
|
+
GetVariableResultFailure,
|
|
17
|
+
GetVariableResultSuccess,
|
|
18
|
+
GetVariableTypeRequest,
|
|
19
|
+
GetVariableTypeResultFailure,
|
|
20
|
+
GetVariableTypeResultSuccess,
|
|
21
|
+
GetVariableValueRequest,
|
|
22
|
+
GetVariableValueResultFailure,
|
|
23
|
+
GetVariableValueResultSuccess,
|
|
24
|
+
HasVariableRequest,
|
|
25
|
+
HasVariableResultFailure,
|
|
26
|
+
HasVariableResultSuccess,
|
|
27
|
+
ListVariablesRequest,
|
|
28
|
+
ListVariablesResultFailure,
|
|
29
|
+
ListVariablesResultSuccess,
|
|
30
|
+
RenameVariableRequest,
|
|
31
|
+
RenameVariableResultFailure,
|
|
32
|
+
RenameVariableResultSuccess,
|
|
33
|
+
SetVariableTypeRequest,
|
|
34
|
+
SetVariableTypeResultFailure,
|
|
35
|
+
SetVariableTypeResultSuccess,
|
|
36
|
+
SetVariableValueRequest,
|
|
37
|
+
SetVariableValueResultFailure,
|
|
38
|
+
SetVariableValueResultSuccess,
|
|
39
|
+
VariableDetails,
|
|
40
|
+
)
|
|
41
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
42
|
+
from griptape_nodes.retained_mode.managers.event_manager import EventManager
|
|
43
|
+
from griptape_nodes.retained_mode.variable_types import FlowVariable, VariableScope
|
|
44
|
+
|
|
45
|
+
logger = logging.getLogger("griptape_nodes")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class VariableLookupResult(NamedTuple):
|
|
49
|
+
"""Result of hierarchical variable lookup."""
|
|
50
|
+
|
|
51
|
+
variable: FlowVariable | None
|
|
52
|
+
found_scope: VariableScope | None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class VariablesManager:
|
|
56
|
+
"""Manager for variables with scoped access control."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, event_manager: EventManager | None = None) -> None:
|
|
59
|
+
# Storage for flow-scoped variables: {flow_name: {variable_name: FlowVariable}}
|
|
60
|
+
self._flow_variables: dict[str, dict[str, FlowVariable]] = {}
|
|
61
|
+
# Storage for global variables: {variable_name: FlowVariable}
|
|
62
|
+
self._global_variables: dict[str, FlowVariable] = {}
|
|
63
|
+
if event_manager is not None:
|
|
64
|
+
event_manager.assign_manager_to_request_type(CreateVariableRequest, self.on_create_variable_request)
|
|
65
|
+
event_manager.assign_manager_to_request_type(GetVariableRequest, self.on_get_variable_request)
|
|
66
|
+
event_manager.assign_manager_to_request_type(GetVariableValueRequest, self.on_get_variable_value_request)
|
|
67
|
+
event_manager.assign_manager_to_request_type(SetVariableValueRequest, self.on_set_variable_value_request)
|
|
68
|
+
event_manager.assign_manager_to_request_type(GetVariableTypeRequest, self.on_get_variable_type_request)
|
|
69
|
+
event_manager.assign_manager_to_request_type(SetVariableTypeRequest, self.on_set_variable_type_request)
|
|
70
|
+
event_manager.assign_manager_to_request_type(DeleteVariableRequest, self.on_delete_variable_request)
|
|
71
|
+
event_manager.assign_manager_to_request_type(RenameVariableRequest, self.on_rename_variable_request)
|
|
72
|
+
event_manager.assign_manager_to_request_type(HasVariableRequest, self.on_has_variable_request)
|
|
73
|
+
event_manager.assign_manager_to_request_type(ListVariablesRequest, self.on_list_variables_request)
|
|
74
|
+
event_manager.assign_manager_to_request_type(
|
|
75
|
+
GetVariableDetailsRequest, self.on_get_variable_details_request
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def on_clear_object_state(self) -> None:
|
|
79
|
+
"""Clear all variables."""
|
|
80
|
+
self._flow_variables.clear()
|
|
81
|
+
self._global_variables.clear()
|
|
82
|
+
|
|
83
|
+
def _get_starting_flow(self, starting_flow: str | None) -> str:
|
|
84
|
+
"""Get the starting flow name, using Context Manager if None."""
|
|
85
|
+
if starting_flow is not None:
|
|
86
|
+
# Validate that the specified flow exists
|
|
87
|
+
flow_manager = GriptapeNodes.FlowManager()
|
|
88
|
+
try:
|
|
89
|
+
flow_manager.get_parent_flow(starting_flow) # This will raise if flow doesn't exist
|
|
90
|
+
except Exception as e:
|
|
91
|
+
msg = f"Specified starting flow '{starting_flow}' does not exist: {e}"
|
|
92
|
+
raise ValueError(msg) from e
|
|
93
|
+
return starting_flow
|
|
94
|
+
|
|
95
|
+
# Get current flow from Context Manager
|
|
96
|
+
context_manager = GriptapeNodes.ContextManager()
|
|
97
|
+
|
|
98
|
+
if not context_manager.has_current_flow():
|
|
99
|
+
msg = "No starting flow specified and no current flow in Context Manager"
|
|
100
|
+
raise ValueError(msg)
|
|
101
|
+
|
|
102
|
+
return context_manager.get_current_flow().name
|
|
103
|
+
|
|
104
|
+
def _get_flow_hierarchy(self, starting_flow: str) -> list[str]:
|
|
105
|
+
"""Get the flow hierarchy from starting flow up to root."""
|
|
106
|
+
flow_manager = GriptapeNodes.FlowManager()
|
|
107
|
+
|
|
108
|
+
hierarchy = []
|
|
109
|
+
current_flow = starting_flow
|
|
110
|
+
|
|
111
|
+
while current_flow:
|
|
112
|
+
hierarchy.append(current_flow)
|
|
113
|
+
try:
|
|
114
|
+
parent = flow_manager.get_parent_flow(current_flow)
|
|
115
|
+
current_flow = parent
|
|
116
|
+
except Exception:
|
|
117
|
+
# No parent flow found, we've reached the root
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
return hierarchy
|
|
121
|
+
|
|
122
|
+
def _find_variable_in_flow(self, flow_name: str, variable_name: str) -> FlowVariable | None:
|
|
123
|
+
"""Find a variable in a specific flow."""
|
|
124
|
+
flow_vars = self._flow_variables.get(flow_name, {})
|
|
125
|
+
return flow_vars.get(variable_name)
|
|
126
|
+
|
|
127
|
+
def _find_variable_hierarchical(
|
|
128
|
+
self, starting_flow: str, variable_name: str, lookup_scope: VariableScope
|
|
129
|
+
) -> VariableLookupResult:
|
|
130
|
+
"""Find a variable using hierarchical lookup strategy."""
|
|
131
|
+
match lookup_scope:
|
|
132
|
+
case VariableScope.CURRENT_FLOW_ONLY:
|
|
133
|
+
variable = self._find_variable_in_flow(starting_flow, variable_name)
|
|
134
|
+
found_scope = VariableScope.CURRENT_FLOW_ONLY if variable else None
|
|
135
|
+
return VariableLookupResult(variable=variable, found_scope=found_scope)
|
|
136
|
+
|
|
137
|
+
case VariableScope.GLOBAL_ONLY:
|
|
138
|
+
variable = self._global_variables.get(variable_name)
|
|
139
|
+
found_scope = VariableScope.GLOBAL_ONLY if variable else None
|
|
140
|
+
return VariableLookupResult(variable=variable, found_scope=found_scope)
|
|
141
|
+
|
|
142
|
+
case VariableScope.HIERARCHICAL:
|
|
143
|
+
# Search through flow hierarchy
|
|
144
|
+
hierarchy = self._get_flow_hierarchy(starting_flow)
|
|
145
|
+
for flow_name in hierarchy:
|
|
146
|
+
variable = self._find_variable_in_flow(flow_name, variable_name)
|
|
147
|
+
if variable:
|
|
148
|
+
found_scope = (
|
|
149
|
+
VariableScope.CURRENT_FLOW_ONLY
|
|
150
|
+
if flow_name == starting_flow
|
|
151
|
+
else VariableScope.HIERARCHICAL
|
|
152
|
+
)
|
|
153
|
+
return VariableLookupResult(variable=variable, found_scope=found_scope)
|
|
154
|
+
|
|
155
|
+
# Check global variables as fallback
|
|
156
|
+
variable = self._global_variables.get(variable_name)
|
|
157
|
+
found_scope = VariableScope.GLOBAL_ONLY if variable else None
|
|
158
|
+
return VariableLookupResult(variable=variable, found_scope=found_scope)
|
|
159
|
+
|
|
160
|
+
case VariableScope.ALL:
|
|
161
|
+
# This is primarily for ListVariables - just search current flow for now
|
|
162
|
+
variable = self._find_variable_in_flow(starting_flow, variable_name)
|
|
163
|
+
found_scope = VariableScope.CURRENT_FLOW_ONLY if variable else None
|
|
164
|
+
return VariableLookupResult(variable=variable, found_scope=found_scope)
|
|
165
|
+
|
|
166
|
+
case _:
|
|
167
|
+
msg = f"Attempted to find variable '{variable_name}' from starting flow '{starting_flow}', but encountered an unknown/unexpected variable scope '{lookup_scope.value}'"
|
|
168
|
+
raise ValueError(msg)
|
|
169
|
+
|
|
170
|
+
def on_create_variable_request(self, request: CreateVariableRequest) -> ResultPayload:
|
|
171
|
+
"""Create a new variable."""
|
|
172
|
+
if request.is_global:
|
|
173
|
+
# Check for name collision in global variables
|
|
174
|
+
if request.name in self._global_variables:
|
|
175
|
+
return CreateVariableResultFailure(
|
|
176
|
+
result_details=f"Attempted to create a global variable named '{request.name}'. Failed because a variable with that name already exists."
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Create global variable
|
|
180
|
+
variable = FlowVariable(
|
|
181
|
+
name=request.name,
|
|
182
|
+
owning_flow_name=None,
|
|
183
|
+
type=request.type,
|
|
184
|
+
value=request.value,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
self._global_variables[request.name] = variable
|
|
188
|
+
return CreateVariableResultSuccess(result_details=f"Successfully created global variable '{request.name}'.")
|
|
189
|
+
|
|
190
|
+
# Get the target flow
|
|
191
|
+
try:
|
|
192
|
+
target_flow = self._get_starting_flow(request.owning_flow)
|
|
193
|
+
except ValueError as e:
|
|
194
|
+
return CreateVariableResultFailure(
|
|
195
|
+
result_details=f"Attempted to create variable '{request.name}'. Failed to determine target flow: {e}"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Initialize flow storage if needed
|
|
199
|
+
if target_flow not in self._flow_variables:
|
|
200
|
+
self._flow_variables[target_flow] = {}
|
|
201
|
+
|
|
202
|
+
# Check for name collision in target flow
|
|
203
|
+
if request.name in self._flow_variables[target_flow]:
|
|
204
|
+
return CreateVariableResultFailure(
|
|
205
|
+
result_details=f"Attempted to create a variable named '{request.name}' in flow '{target_flow}'. Failed because a variable with that name already exists."
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Create flow-scoped variable
|
|
209
|
+
variable = FlowVariable(
|
|
210
|
+
name=request.name,
|
|
211
|
+
owning_flow_name=target_flow,
|
|
212
|
+
type=request.type,
|
|
213
|
+
value=request.value,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
self._flow_variables[target_flow][request.name] = variable
|
|
217
|
+
return CreateVariableResultSuccess(
|
|
218
|
+
result_details=f"Successfully created variable '{request.name}' in flow '{target_flow}'."
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def on_get_variable_request(self, request: GetVariableRequest) -> ResultPayload:
|
|
222
|
+
"""Get a full variable by name."""
|
|
223
|
+
try:
|
|
224
|
+
starting_flow = self._get_starting_flow(request.starting_flow)
|
|
225
|
+
except ValueError as e:
|
|
226
|
+
return GetVariableResultFailure(
|
|
227
|
+
result_details=f"Attempted to get variable '{request.name}'. Failed to determine starting flow: {e}"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
result = self._find_variable_hierarchical(starting_flow, request.name, request.lookup_scope)
|
|
231
|
+
|
|
232
|
+
if not result.variable:
|
|
233
|
+
return GetVariableResultFailure(
|
|
234
|
+
result_details=f"Attempted to get variable '{request.name}'. Failed because no such variable could be found."
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return GetVariableResultSuccess(
|
|
238
|
+
variable=result.variable, result_details=f"Successfully retrieved variable '{request.name}'."
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
def on_get_variable_value_request(self, request: GetVariableValueRequest) -> ResultPayload:
|
|
242
|
+
"""Get the value of a variable."""
|
|
243
|
+
try:
|
|
244
|
+
starting_flow = self._get_starting_flow(request.starting_flow)
|
|
245
|
+
except ValueError as e:
|
|
246
|
+
return GetVariableValueResultFailure(
|
|
247
|
+
result_details=f"Attempted to get value for variable '{request.name}'. Failed to determine starting flow: {e}"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
result = self._find_variable_hierarchical(starting_flow, request.name, request.lookup_scope)
|
|
251
|
+
|
|
252
|
+
if not result.variable:
|
|
253
|
+
return GetVariableValueResultFailure(
|
|
254
|
+
result_details=f"Attempted to get value for variable '{request.name}'. Failed because no such variable could be found."
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
return GetVariableValueResultSuccess(
|
|
258
|
+
value=result.variable.value, result_details=f"Successfully retrieved value for variable '{request.name}'."
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def on_set_variable_value_request(self, request: SetVariableValueRequest) -> ResultPayload:
|
|
262
|
+
"""Set the value of an existing variable."""
|
|
263
|
+
try:
|
|
264
|
+
starting_flow = self._get_starting_flow(request.starting_flow)
|
|
265
|
+
except ValueError as e:
|
|
266
|
+
return SetVariableValueResultFailure(
|
|
267
|
+
result_details=f"Attempted to set value for variable '{request.name}'. Failed to determine starting flow: {e}"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
result = self._find_variable_hierarchical(starting_flow, request.name, request.lookup_scope)
|
|
271
|
+
|
|
272
|
+
if not result.variable:
|
|
273
|
+
return SetVariableValueResultFailure(
|
|
274
|
+
result_details=f"Attempted to set value for variable '{request.name}'. Failed because no such variable could be found."
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
result.variable.value = request.value
|
|
278
|
+
return SetVariableValueResultSuccess(result_details=f"Successfully set value for variable '{request.name}'.")
|
|
279
|
+
|
|
280
|
+
def on_get_variable_type_request(self, request: GetVariableTypeRequest) -> ResultPayload:
|
|
281
|
+
"""Get the type of a variable."""
|
|
282
|
+
try:
|
|
283
|
+
starting_flow = self._get_starting_flow(request.starting_flow)
|
|
284
|
+
except ValueError as e:
|
|
285
|
+
return GetVariableTypeResultFailure(
|
|
286
|
+
result_details=f"Attempted to get type for variable '{request.name}'. Failed to determine starting flow: {e}"
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
result = self._find_variable_hierarchical(starting_flow, request.name, request.lookup_scope)
|
|
290
|
+
|
|
291
|
+
if not result.variable:
|
|
292
|
+
return GetVariableTypeResultFailure(
|
|
293
|
+
result_details=f"Attempted to get type for variable '{request.name}'. Failed because no such variable could be found."
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
return GetVariableTypeResultSuccess(
|
|
297
|
+
type=result.variable.type, result_details=f"Successfully retrieved type for variable '{request.name}'."
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
def on_set_variable_type_request(self, request: SetVariableTypeRequest) -> ResultPayload:
|
|
301
|
+
"""Set the type of an existing variable."""
|
|
302
|
+
try:
|
|
303
|
+
starting_flow = self._get_starting_flow(request.starting_flow)
|
|
304
|
+
except ValueError as e:
|
|
305
|
+
return SetVariableTypeResultFailure(
|
|
306
|
+
result_details=f"Attempted to set type for variable '{request.name}'. Failed to determine starting flow: {e}"
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
result = self._find_variable_hierarchical(starting_flow, request.name, request.lookup_scope)
|
|
310
|
+
|
|
311
|
+
if not result.variable:
|
|
312
|
+
return SetVariableTypeResultFailure(
|
|
313
|
+
result_details=f"Attempted to set type for variable '{request.name}'. Failed because no such variable could be found."
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
result.variable.type = request.type
|
|
317
|
+
return SetVariableTypeResultSuccess(
|
|
318
|
+
result_details=f"Successfully set type for variable '{request.name}' to '{request.type}'."
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
def on_delete_variable_request(self, request: DeleteVariableRequest) -> ResultPayload:
|
|
322
|
+
"""Delete a variable."""
|
|
323
|
+
try:
|
|
324
|
+
starting_flow = self._get_starting_flow(request.starting_flow)
|
|
325
|
+
except ValueError as e:
|
|
326
|
+
return DeleteVariableResultFailure(
|
|
327
|
+
result_details=f"Attempted to delete variable '{request.name}'. Failed to determine starting flow: {e}"
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
result = self._find_variable_hierarchical(starting_flow, request.name, request.lookup_scope)
|
|
331
|
+
|
|
332
|
+
if not result.variable:
|
|
333
|
+
return DeleteVariableResultFailure(
|
|
334
|
+
result_details=f"Attempted to delete variable '{request.name}'. Failed because no such variable could be found."
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
variable = result.variable
|
|
338
|
+
|
|
339
|
+
# Remove from appropriate storage based on owning flow
|
|
340
|
+
if variable.owning_flow_name is None:
|
|
341
|
+
# Global variable
|
|
342
|
+
del self._global_variables[variable.name]
|
|
343
|
+
else:
|
|
344
|
+
# Flow-scoped variable
|
|
345
|
+
flow_vars = self._flow_variables.get(variable.owning_flow_name, {})
|
|
346
|
+
if variable.name in flow_vars:
|
|
347
|
+
del flow_vars[variable.name]
|
|
348
|
+
|
|
349
|
+
return DeleteVariableResultSuccess(result_details=f"Successfully deleted variable '{request.name}'.")
|
|
350
|
+
|
|
351
|
+
def on_rename_variable_request(self, request: RenameVariableRequest) -> ResultPayload:
|
|
352
|
+
"""Rename a variable."""
|
|
353
|
+
try:
|
|
354
|
+
starting_flow = self._get_starting_flow(request.starting_flow)
|
|
355
|
+
except ValueError as e:
|
|
356
|
+
return RenameVariableResultFailure(
|
|
357
|
+
result_details=f"Attempted to rename variable '{request.name}'. Failed to determine starting flow: {e}"
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
result = self._find_variable_hierarchical(starting_flow, request.name, request.lookup_scope)
|
|
361
|
+
|
|
362
|
+
if not result.variable:
|
|
363
|
+
return RenameVariableResultFailure(
|
|
364
|
+
result_details=f"Attempted to rename variable '{request.name}'. Failed because no such variable could be found."
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
variable = result.variable
|
|
368
|
+
|
|
369
|
+
# Check for name collision with new name in the same scope
|
|
370
|
+
new_name_result = self._find_variable_hierarchical(starting_flow, request.new_name, request.lookup_scope)
|
|
371
|
+
if new_name_result.variable and new_name_result.variable.name != variable.name:
|
|
372
|
+
return RenameVariableResultFailure(
|
|
373
|
+
result_details=f"Attempted to rename variable '{request.name}' to '{request.new_name}'. Failed because a variable with that name already exists."
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Update the variable name and storage key
|
|
377
|
+
old_name = variable.name
|
|
378
|
+
variable.name = request.new_name
|
|
379
|
+
|
|
380
|
+
# Update in appropriate storage based on owning flow
|
|
381
|
+
if variable.owning_flow_name is None:
|
|
382
|
+
# Global variable
|
|
383
|
+
del self._global_variables[old_name]
|
|
384
|
+
self._global_variables[request.new_name] = variable
|
|
385
|
+
else:
|
|
386
|
+
# Flow-scoped variable
|
|
387
|
+
flow_vars = self._flow_variables.get(variable.owning_flow_name, {})
|
|
388
|
+
if old_name in flow_vars:
|
|
389
|
+
del flow_vars[old_name]
|
|
390
|
+
flow_vars[request.new_name] = variable
|
|
391
|
+
|
|
392
|
+
return RenameVariableResultSuccess(
|
|
393
|
+
result_details=f"Successfully renamed variable '{old_name}' to '{request.new_name}'."
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
def on_has_variable_request(self, request: HasVariableRequest) -> ResultPayload:
|
|
397
|
+
"""Check if a variable exists."""
|
|
398
|
+
try:
|
|
399
|
+
starting_flow = self._get_starting_flow(request.starting_flow)
|
|
400
|
+
except ValueError as e:
|
|
401
|
+
return HasVariableResultFailure(
|
|
402
|
+
result_details=f"Attempted to check existence of variable '{request.name}'. Failed to determine starting flow: {e}"
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
result = self._find_variable_hierarchical(starting_flow, request.name, request.lookup_scope)
|
|
406
|
+
exists = result.variable is not None
|
|
407
|
+
|
|
408
|
+
return HasVariableResultSuccess(
|
|
409
|
+
exists=exists,
|
|
410
|
+
found_scope=result.found_scope,
|
|
411
|
+
result_details=f"Successfully checked existence of variable '{request.name}': {'exists' if exists else 'not found'}.",
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
def _get_variables_by_scope(self, starting_flow: str, lookup_scope: VariableScope) -> list[FlowVariable]:
|
|
415
|
+
"""Get variables for the specified scope."""
|
|
416
|
+
match lookup_scope:
|
|
417
|
+
case VariableScope.CURRENT_FLOW_ONLY:
|
|
418
|
+
if starting_flow in self._flow_variables:
|
|
419
|
+
return list(self._flow_variables[starting_flow].values())
|
|
420
|
+
return []
|
|
421
|
+
|
|
422
|
+
case VariableScope.GLOBAL_ONLY:
|
|
423
|
+
return list(self._global_variables.values())
|
|
424
|
+
|
|
425
|
+
case VariableScope.HIERARCHICAL:
|
|
426
|
+
return self._get_hierarchical_variables(starting_flow)
|
|
427
|
+
|
|
428
|
+
case VariableScope.ALL:
|
|
429
|
+
return self._get_all_variables()
|
|
430
|
+
|
|
431
|
+
case _:
|
|
432
|
+
msg = f"Attempted to get variables from starting flow '{starting_flow}', but encountered an unknown/unexpected variable scope '{lookup_scope.value}'"
|
|
433
|
+
raise ValueError(msg)
|
|
434
|
+
|
|
435
|
+
def _get_hierarchical_variables(self, starting_flow: str) -> list[FlowVariable]:
|
|
436
|
+
"""Get variables using hierarchical lookup with shadowing.
|
|
437
|
+
|
|
438
|
+
Variable shadowing behavior:
|
|
439
|
+
- Child flow variables shadow (hide) parent flow variables with same name
|
|
440
|
+
- Flow variables shadow global variables with same name
|
|
441
|
+
|
|
442
|
+
Example:
|
|
443
|
+
- Global: user_id = "global_user"
|
|
444
|
+
- Parent flow: user_id = "parent_user"
|
|
445
|
+
- Child flow: user_id = "child_user"
|
|
446
|
+
Result from child flow: only user_id = "child_user" (others are shadowed)
|
|
447
|
+
"""
|
|
448
|
+
hierarchy = self._get_flow_hierarchy(starting_flow)
|
|
449
|
+
seen_names = set()
|
|
450
|
+
variables = []
|
|
451
|
+
|
|
452
|
+
# Add variables from flows (child to parent to implement shadowing)
|
|
453
|
+
for flow_name in hierarchy:
|
|
454
|
+
flow_vars = self._flow_variables.get(flow_name, {})
|
|
455
|
+
for var in flow_vars.values():
|
|
456
|
+
if var.name not in seen_names:
|
|
457
|
+
variables.append(var)
|
|
458
|
+
seen_names.add(var.name)
|
|
459
|
+
|
|
460
|
+
# Add global variables (lowest priority, can be shadowed by flow variables)
|
|
461
|
+
variables.extend(var for var in self._global_variables.values() if var.name not in seen_names)
|
|
462
|
+
|
|
463
|
+
return variables
|
|
464
|
+
|
|
465
|
+
def _get_all_variables(self) -> list[FlowVariable]:
|
|
466
|
+
"""Get all variables from all flows for GUI enumeration.
|
|
467
|
+
|
|
468
|
+
Note: This returns ALL variables without shadowing - variables with the same name
|
|
469
|
+
from different flows/scopes will all be included in the result.
|
|
470
|
+
"""
|
|
471
|
+
variables = []
|
|
472
|
+
|
|
473
|
+
# Add all flow variables (no shadowing - include all)
|
|
474
|
+
for flow_vars in self._flow_variables.values():
|
|
475
|
+
variables.extend(flow_vars.values())
|
|
476
|
+
|
|
477
|
+
# Add all global variables
|
|
478
|
+
variables.extend(self._global_variables.values())
|
|
479
|
+
|
|
480
|
+
return variables
|
|
481
|
+
|
|
482
|
+
def on_list_variables_request(self, request: ListVariablesRequest) -> ResultPayload:
|
|
483
|
+
"""List all variables in the specified scope."""
|
|
484
|
+
try:
|
|
485
|
+
starting_flow = self._get_starting_flow(request.starting_flow)
|
|
486
|
+
except ValueError as e:
|
|
487
|
+
return ListVariablesResultFailure(
|
|
488
|
+
result_details=f"Attempted to list variables. Failed to determine starting flow: {e}"
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
variables = self._get_variables_by_scope(starting_flow, request.lookup_scope)
|
|
492
|
+
|
|
493
|
+
# Sort by name for consistent output
|
|
494
|
+
variables = sorted(variables, key=lambda v: v.name)
|
|
495
|
+
return ListVariablesResultSuccess(
|
|
496
|
+
variables=variables, result_details=f"Successfully listed {len(variables)} variables."
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
def on_get_variable_details_request(self, request: GetVariableDetailsRequest) -> ResultPayload:
|
|
500
|
+
"""Get variable details (metadata only, no heavy values)."""
|
|
501
|
+
try:
|
|
502
|
+
starting_flow = self._get_starting_flow(request.starting_flow)
|
|
503
|
+
except ValueError as e:
|
|
504
|
+
return GetVariableDetailsResultFailure(
|
|
505
|
+
result_details=f"Attempted to get details for variable '{request.name}'. Failed to determine starting flow: {e}"
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
result = self._find_variable_hierarchical(starting_flow, request.name, request.lookup_scope)
|
|
509
|
+
|
|
510
|
+
if not result.variable:
|
|
511
|
+
return GetVariableDetailsResultFailure(
|
|
512
|
+
result_details=f"Attempted to get details for variable '{request.name}'. Failed because no such variable could be found."
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
variable = result.variable
|
|
516
|
+
details = VariableDetails(name=variable.name, owning_flow_name=variable.owning_flow_name, type=variable.type)
|
|
517
|
+
return GetVariableDetailsResultSuccess(
|
|
518
|
+
details=details, result_details=f"Successfully retrieved details for variable '{request.name}'."
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
def _find_variable_by_name(self, name: str) -> FlowVariable | None:
|
|
522
|
+
"""Find a variable by name in current flow context (legacy compatibility)."""
|
|
523
|
+
try:
|
|
524
|
+
starting_flow = self._get_starting_flow(None)
|
|
525
|
+
except ValueError:
|
|
526
|
+
return None
|
|
527
|
+
|
|
528
|
+
result = self._find_variable_hierarchical(starting_flow, name, VariableScope.HIERARCHICAL)
|
|
529
|
+
return result.variable
|