griptape-nodes 0.38.1__py3-none-any.whl → 0.41.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 (38) hide show
  1. griptape_nodes/__init__.py +13 -9
  2. griptape_nodes/app/__init__.py +10 -1
  3. griptape_nodes/app/app.py +2 -3
  4. griptape_nodes/app/app_sessions.py +458 -0
  5. griptape_nodes/bootstrap/workflow_executors/__init__.py +1 -0
  6. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +213 -0
  7. griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +13 -0
  8. griptape_nodes/bootstrap/workflow_runners/local_workflow_runner.py +1 -1
  9. griptape_nodes/drivers/storage/__init__.py +4 -0
  10. griptape_nodes/drivers/storage/storage_backend.py +10 -0
  11. griptape_nodes/exe_types/core_types.py +5 -1
  12. griptape_nodes/exe_types/node_types.py +20 -24
  13. griptape_nodes/machines/node_resolution.py +5 -1
  14. griptape_nodes/node_library/advanced_node_library.py +51 -0
  15. griptape_nodes/node_library/library_registry.py +28 -2
  16. griptape_nodes/node_library/workflow_registry.py +1 -1
  17. griptape_nodes/retained_mode/events/agent_events.py +15 -2
  18. griptape_nodes/retained_mode/events/app_events.py +113 -2
  19. griptape_nodes/retained_mode/events/base_events.py +28 -1
  20. griptape_nodes/retained_mode/events/library_events.py +111 -1
  21. griptape_nodes/retained_mode/events/node_events.py +1 -0
  22. griptape_nodes/retained_mode/events/workflow_events.py +1 -0
  23. griptape_nodes/retained_mode/griptape_nodes.py +240 -18
  24. griptape_nodes/retained_mode/managers/agent_manager.py +123 -17
  25. griptape_nodes/retained_mode/managers/flow_manager.py +16 -48
  26. griptape_nodes/retained_mode/managers/library_manager.py +642 -121
  27. griptape_nodes/retained_mode/managers/node_manager.py +2 -2
  28. griptape_nodes/retained_mode/managers/static_files_manager.py +4 -3
  29. griptape_nodes/retained_mode/managers/workflow_manager.py +666 -37
  30. griptape_nodes/retained_mode/utils/__init__.py +1 -0
  31. griptape_nodes/retained_mode/utils/engine_identity.py +131 -0
  32. griptape_nodes/retained_mode/utils/name_generator.py +162 -0
  33. griptape_nodes/retained_mode/utils/session_persistence.py +105 -0
  34. {griptape_nodes-0.38.1.dist-info → griptape_nodes-0.41.0.dist-info}/METADATA +1 -1
  35. {griptape_nodes-0.38.1.dist-info → griptape_nodes-0.41.0.dist-info}/RECORD +38 -28
  36. {griptape_nodes-0.38.1.dist-info → griptape_nodes-0.41.0.dist-info}/WHEEL +0 -0
  37. {griptape_nodes-0.38.1.dist-info → griptape_nodes-0.41.0.dist-info}/entry_points.txt +0 -0
  38. {griptape_nodes-0.38.1.dist-info → griptape_nodes-0.41.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,213 @@
1
+ import logging
2
+ from queue import Queue
3
+ from typing import Any
4
+
5
+ from griptape.events import BaseEvent, EventBus, EventListener
6
+
7
+ from griptape_nodes.bootstrap.workflow_executors.workflow_executor import WorkflowExecutor
8
+ from griptape_nodes.drivers.storage import StorageBackend
9
+ from griptape_nodes.exe_types.node_types import EndNode, StartNode
10
+ from griptape_nodes.retained_mode.events.base_events import (
11
+ AppEvent,
12
+ EventRequest,
13
+ ExecutionGriptapeNodeEvent,
14
+ GriptapeNodeEvent,
15
+ ProgressEvent,
16
+ )
17
+ from griptape_nodes.retained_mode.events.execution_events import SingleExecutionStepRequest, StartFlowRequest
18
+ from griptape_nodes.retained_mode.events.parameter_events import SetParameterValueRequest
19
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class LocalExecutorError(Exception):
25
+ """Exception raised during local workflow execution."""
26
+
27
+
28
+ class LocalWorkflowExecutor(WorkflowExecutor):
29
+ def __init__(self) -> None:
30
+ self.queue = Queue()
31
+ self.output: dict | None = None
32
+
33
+ def _load_flow_for_workflow(self) -> str:
34
+ try:
35
+ context_manager = GriptapeNodes.ContextManager()
36
+ return context_manager.get_current_flow().name
37
+ except Exception as e:
38
+ msg = f"Failed to get current flow from context manager: {e}"
39
+ logger.exception(msg)
40
+ raise LocalExecutorError(msg) from e
41
+
42
+ def _set_storage_backend(self, storage_backend: StorageBackend) -> None:
43
+ from griptape_nodes.retained_mode.managers.config_manager import ConfigManager
44
+
45
+ try:
46
+ config_manager = ConfigManager()
47
+ config_manager.set_config_value(
48
+ key="storage_backend",
49
+ value=storage_backend,
50
+ )
51
+ except Exception as e:
52
+ msg = f"Failed to set storage backend: {e}"
53
+ logger.exception(msg)
54
+ raise LocalExecutorError(msg) from e
55
+
56
+ def _handle_event(self, event: BaseEvent) -> None:
57
+ try:
58
+ match event:
59
+ case GriptapeNodeEvent():
60
+ self.__handle_node_event(event)
61
+ case ExecutionGriptapeNodeEvent():
62
+ self.__handle_execution_node_event(event)
63
+ case ProgressEvent():
64
+ self.__handle_progress_event(event)
65
+ case AppEvent():
66
+ self.__handle_app_event(event)
67
+ case _:
68
+ msg = f"Unknown event type: {type(event)}"
69
+ logger.info(msg)
70
+ self.queue.put(event)
71
+ except Exception as e:
72
+ logger.info(e)
73
+
74
+ def __handle_node_event(self, event: GriptapeNodeEvent) -> None:
75
+ result_event = event.wrapped_event
76
+ event_json = result_event.json()
77
+ event_log = f"GriptapeNodeEvent: {event_json}"
78
+ logger.info(event_log)
79
+
80
+ def __handle_execution_node_event(self, event: ExecutionGriptapeNodeEvent) -> None:
81
+ result_event = event.wrapped_event
82
+ if type(result_event.payload).__name__ == "NodeStartProcessEvent":
83
+ event_log = f"NodeStartProcessEvent: {result_event.payload}"
84
+ logger.info(event_log)
85
+
86
+ elif type(result_event.payload).__name__ == "ResumeNodeProcessingEvent":
87
+ event_log = f"ResumeNodeProcessingEvent: {result_event.payload}"
88
+ logger.info(event_log)
89
+
90
+ # Here we need to handle the resume event since this is the callback mechanism
91
+ # for the flow to be resumed for any Node that yields a generator in its process method.
92
+ node_name = result_event.payload.node_name
93
+ flow_name = GriptapeNodes.NodeManager().get_node_parent_flow_by_name(node_name)
94
+ event_request = EventRequest(request=SingleExecutionStepRequest(flow_name=flow_name))
95
+ GriptapeNodes.handle_request(event_request.request)
96
+
97
+ elif type(result_event.payload).__name__ == "NodeFinishProcessEvent":
98
+ event_log = f"NodeFinishProcessEvent: {result_event.payload}"
99
+ logger.info(event_log)
100
+
101
+ else:
102
+ event_log = f"ExecutionGriptapeNodeEvent: {result_event.payload}"
103
+ logger.info(event_log)
104
+
105
+ self.queue.put(event)
106
+
107
+ def __handle_progress_event(self, gt_event: ProgressEvent) -> None:
108
+ event_log = f"ProgressEvent: {gt_event}"
109
+ logger.info(event_log)
110
+
111
+ def __handle_app_event(self, event: AppEvent) -> None:
112
+ event_log = f"AppEvent: {event.payload}"
113
+ logger.info(event_log)
114
+
115
+ def _submit_output(self, output: dict) -> None:
116
+ self.output = output
117
+
118
+ def _set_input_for_flow(self, flow_name: str, flow_input: dict[str, dict]) -> None:
119
+ control_flow = GriptapeNodes.FlowManager().get_flow_by_name(flow_name)
120
+ nodes = control_flow.nodes
121
+ for node_name, node in nodes.items():
122
+ if isinstance(node, StartNode):
123
+ param_map: dict | None = flow_input.get(node_name)
124
+ if param_map is not None:
125
+ for parameter_name, parameter_value in param_map.items():
126
+ set_parameter_value_request = SetParameterValueRequest(
127
+ parameter_name=parameter_name,
128
+ value=parameter_value,
129
+ node_name=node_name,
130
+ )
131
+ set_parameter_value_result = GriptapeNodes.handle_request(set_parameter_value_request)
132
+
133
+ if set_parameter_value_result.failed():
134
+ msg = f"Failed to set parameter {parameter_name} for node {node_name}."
135
+ raise LocalExecutorError(msg)
136
+
137
+ def _get_output_for_flow(self, flow_name: str) -> dict:
138
+ control_flow = GriptapeNodes.FlowManager().get_flow_by_name(flow_name)
139
+ nodes = control_flow.nodes
140
+ output = {}
141
+ for node_name, node in nodes.items():
142
+ if isinstance(node, EndNode):
143
+ output[node_name] = node.parameter_values
144
+
145
+ return output
146
+
147
+ def run(self, workflow_name: str, flow_input: Any, storage_backend: StorageBackend = StorageBackend.LOCAL) -> None:
148
+ """Executes a local workflow.
149
+
150
+ Executes a workflow by setting up event listeners, registering libraries,
151
+ loading the user-defined workflow, and running the specified workflow.
152
+
153
+ Parameters:
154
+ workflow_name: The name of the workflow to execute.
155
+ flow_input: Input data for the flow, typically a dictionary.
156
+ storage_backend: The storage backend to use for the workflow execution.
157
+
158
+ Returns:
159
+ None
160
+ """
161
+ logger.info("Executing workflow: %s", workflow_name)
162
+
163
+ EventBus.add_event_listener(
164
+ event_listener=EventListener(
165
+ on_event=self._handle_event,
166
+ )
167
+ )
168
+
169
+ # Set the storage backend
170
+ self._set_storage_backend(storage_backend=storage_backend)
171
+ # Load the flow
172
+ flow_name = self._load_flow_for_workflow()
173
+ # Now let's set the input to the flow
174
+ self._set_input_for_flow(flow_name=flow_name, flow_input=flow_input)
175
+
176
+ # Now send the run command to actually execute it
177
+ start_flow_request = StartFlowRequest(flow_name=flow_name)
178
+ start_flow_result = GriptapeNodes.handle_request(start_flow_request)
179
+
180
+ if start_flow_result.failed():
181
+ msg = f"Failed to start flow {flow_name}"
182
+ raise LocalExecutorError(msg)
183
+
184
+ logger.info("Workflow started!")
185
+
186
+ # Wait for the control flow to finish
187
+ is_flow_finished = False
188
+ error: Exception | None = None
189
+ while not is_flow_finished:
190
+ try:
191
+ event = self.queue.get(block=True)
192
+
193
+ if isinstance(event, ExecutionGriptapeNodeEvent):
194
+ result_event = event.wrapped_event
195
+
196
+ if type(result_event.payload).__name__ == "ControlFlowResolvedEvent":
197
+ self._submit_output(self._get_output_for_flow(flow_name=flow_name))
198
+ is_flow_finished = True
199
+ logger.info("Workflow finished!")
200
+ elif type(result_event.payload).__name__ == "ControlFlowCancelledEvent":
201
+ msg = "Control flow cancelled"
202
+ is_flow_finished = True
203
+ logger.error(msg)
204
+ error = LocalExecutorError(msg)
205
+
206
+ self.queue.task_done()
207
+
208
+ except Exception as e:
209
+ msg = f"Error handling queue event: {e}"
210
+ logger.info(msg)
211
+
212
+ if error is not None:
213
+ raise error
@@ -0,0 +1,13 @@
1
+ import logging
2
+ from abc import ABC, abstractmethod
3
+ from typing import Any
4
+
5
+ from griptape_nodes.drivers.storage import StorageBackend
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class WorkflowExecutor(ABC):
11
+ @abstractmethod
12
+ def run(self, workflow_name: str, flow_input: Any, storage_backend: StorageBackend = StorageBackend.LOCAL) -> None:
13
+ pass
@@ -35,7 +35,7 @@ class LocalWorkflowRunner(WorkflowRunner):
35
35
  file_path = Path(path_to_workflow)
36
36
 
37
37
  # Generate a unique module name
38
- module_name = f"dynamic_module_{file_path.name.replace('.', '_')}_{hash(str(file_path))}"
38
+ module_name = f"gtn_dynamic_module_{file_path.name.replace('.', '_')}_{hash(str(file_path))}"
39
39
 
40
40
  # Load the module specification
41
41
  spec = importlib.util.spec_from_file_location(module_name, file_path)
@@ -2,3 +2,7 @@
2
2
 
3
3
  Storage drivers are responsible for managing the storage and retrieval of files.
4
4
  """
5
+
6
+ from .storage_backend import StorageBackend
7
+
8
+ __all__ = ["StorageBackend"]
@@ -0,0 +1,10 @@
1
+ """Storage backend enumeration."""
2
+
3
+ from enum import StrEnum
4
+
5
+
6
+ class StorageBackend(StrEnum):
7
+ """Enumeration of available storage backends."""
8
+
9
+ LOCAL = "local"
10
+ GTC = "gtc"
@@ -216,7 +216,6 @@ class BaseNodeElement:
216
216
  new_value = getattr(self, f"{func.__name__}", None) if hasattr(self, f"{func.__name__}") else None
217
217
  # Track change if different
218
218
  if old_value != new_value:
219
- # it needs to be static so we can call these methods.
220
219
  self._changes[func.__name__] = new_value
221
220
  if self._node_context is not None and self not in self._node_context._tracked_parameters:
222
221
  self._node_context._tracked_parameters.append(self)
@@ -735,6 +734,11 @@ class Parameter(BaseNodeElement):
735
734
  @BaseNodeElement.emits_update_on_write
736
735
  def allowed_modes(self, value: Any) -> None:
737
736
  self._allowed_modes = value
737
+ # Handle mode flag decomposition
738
+ if isinstance(value, set):
739
+ self._changes["mode_allowed_input"] = ParameterMode.INPUT in value
740
+ self._changes["mode_allowed_output"] = ParameterMode.OUTPUT in value
741
+ self._changes["mode_allowed_property"] = ParameterMode.PROPERTY in value
738
742
 
739
743
  @property
740
744
  def ui_options(self) -> dict:
@@ -136,7 +136,6 @@ class BaseNode(ABC):
136
136
  source_node: BaseNode, # noqa: ARG002
137
137
  source_parameter: Parameter, # noqa: ARG002
138
138
  target_parameter: Parameter, # noqa: ARG002
139
- modified_parameters_set: set[str] | None = None, # noqa: ARG002
140
139
  ) -> None:
141
140
  """Callback after a Connection has been established TO this Node."""
142
141
  return
@@ -146,7 +145,6 @@ class BaseNode(ABC):
146
145
  source_parameter: Parameter, # noqa: ARG002
147
146
  target_node: BaseNode, # noqa: ARG002
148
147
  target_parameter: Parameter, # noqa: ARG002
149
- modified_parameters_set: set[str] | None = None, # noqa: ARG002
150
148
  ) -> None:
151
149
  """Callback after a Connection has been established OUT of this Node."""
152
150
  return
@@ -156,7 +154,6 @@ class BaseNode(ABC):
156
154
  source_node: BaseNode, # noqa: ARG002
157
155
  source_parameter: Parameter, # noqa: ARG002
158
156
  target_parameter: Parameter, # noqa: ARG002
159
- modified_parameters_set: set[str] | None = None, # noqa: ARG002
160
157
  ) -> None:
161
158
  """Callback after a Connection TO this Node was REMOVED."""
162
159
  return
@@ -166,7 +163,6 @@ class BaseNode(ABC):
166
163
  source_parameter: Parameter, # noqa: ARG002
167
164
  target_node: BaseNode, # noqa: ARG002
168
165
  target_parameter: Parameter, # noqa: ARG002
169
- modified_parameters_set: set[str] | None = None, # noqa: ARG002
170
166
  ) -> None:
171
167
  """Callback after a Connection OUT of this Node was REMOVED."""
172
168
  return
@@ -175,7 +171,6 @@ class BaseNode(ABC):
175
171
  self,
176
172
  parameter: Parameter, # noqa: ARG002
177
173
  value: Any,
178
- modified_parameters_set: set[str] | None = None, # noqa: ARG002
179
174
  ) -> Any:
180
175
  """Callback when a Parameter's value is ABOUT to be set.
181
176
 
@@ -191,7 +186,6 @@ class BaseNode(ABC):
191
186
  Args:
192
187
  parameter: the Parameter on this node that is about to be changed
193
188
  value: the value intended to be set (this has already gone through any converters and validators on the Parameter)
194
- modified_parameters_set: A set of parameter names within this node that were modified as a result of this call.
195
189
 
196
190
  Returns:
197
191
  The final value to set for the Parameter. This gives the Node logic one last opportunity to mutate the value
@@ -204,7 +198,6 @@ class BaseNode(ABC):
204
198
  self,
205
199
  parameter: Parameter, # noqa: ARG002
206
200
  value: Any, # noqa: ARG002
207
- modified_parameters_set: set[str] | None = None, # noqa: ARG002
208
201
  ) -> None:
209
202
  """Callback AFTER a Parameter's value was set.
210
203
 
@@ -225,8 +218,6 @@ class BaseNode(ABC):
225
218
  Args:
226
219
  parameter: the Parameter on this node that was just changed
227
220
  value: the value that was set (already converted, validated, and possibly mutated by the node code)
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.
230
221
 
231
222
  Returns:
232
223
  Nothing
@@ -480,7 +471,9 @@ class BaseNode(ABC):
480
471
  return element_item
481
472
  return None
482
473
 
483
- def set_parameter_value(self, param_name: str, value: Any) -> None:
474
+ def set_parameter_value(
475
+ self, param_name: str, value: Any, *, initial_setup: bool = False, emit_change: bool = True
476
+ ) -> None:
484
477
  """Attempt to set a Parameter's value.
485
478
 
486
479
  The Node may choose to store a different value (or type) than what was passed in.
@@ -498,6 +491,8 @@ class BaseNode(ABC):
498
491
  Args:
499
492
  param_name: the name of the Parameter on this node that is about to be changed
500
493
  value: the value intended to be set
494
+ emit_change: whether to emit a parameter lifecycle event, defaults to True
495
+ initial_setup: Whether this value is being set as the initial setup on the node, defaults to False. When True, the value is not given to any before/after hooks.
501
496
 
502
497
  Returns:
503
498
  A set of parameter names within this node that were modified as a result
@@ -525,22 +520,18 @@ class BaseNode(ABC):
525
520
 
526
521
  # Allow custom node logic to prepare and possibly mutate the value before it is actually set.
527
522
  # Record any parameters modified for cascading.
528
- try:
523
+ if not initial_setup:
529
524
  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
- )
534
- # ACTUALLY SET THE NEW VALUE
535
- self.parameter_values[param_name] = final_value
525
+ # ACTUALLY SET THE NEW VALUE
526
+ self.parameter_values[param_name] = final_value
536
527
 
537
- # If a parameter value has been set at the top level of a container, wipe all children.
538
- # Allow custom node logic to respond after it's been set. Record any modified parameters for cascading.
539
- try:
528
+ # If a parameter value has been set at the top level of a container, wipe all children.
529
+ # Allow custom node logic to respond after it's been set. Record any modified parameters for cascading.
540
530
  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)
531
+ if emit_change:
532
+ self._emit_parameter_lifecycle_event(parameter)
533
+ else:
534
+ self.parameter_values[param_name] = candidate_value
544
535
  # handle with container parameters
545
536
  if parameter.parent_container_name is not None:
546
537
  # Does it have a parent container
@@ -551,7 +542,12 @@ class BaseNode(ABC):
551
542
  new_parent_value = handle_container_parameter(self, parent_parameter)
552
543
  if new_parent_value is not None:
553
544
  # set that new value if it exists.
554
- self.set_parameter_value(parameter.parent_container_name, new_parent_value)
545
+ self.set_parameter_value(
546
+ parameter.parent_container_name,
547
+ new_parent_value,
548
+ initial_setup=initial_setup,
549
+ emit_change=False,
550
+ )
555
551
 
556
552
  def kill_parameter_children(self, parameter: Parameter) -> None:
557
553
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
@@ -230,7 +230,11 @@ class ExecuteNodeState(State):
230
230
  )
231
231
  )
232
232
 
233
- current_node.validate_before_node_run()
233
+ exceptions = current_node.validate_before_node_run()
234
+ if exceptions:
235
+ msg = f"Canceling flow run. Node '{current_node.name}' encountered problems: {exceptions}"
236
+ # Mark the node as unresolved, broadcasting to everyone.
237
+ raise RuntimeError(msg)
234
238
  if not context.paused:
235
239
  return ExecuteNodeState
236
240
  return None
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from griptape_nodes.node_library.library_registry import Library, LibrarySchema
7
+
8
+
9
+ class AdvancedNodeLibrary:
10
+ """Base class for advanced node libraries with callback support.
11
+
12
+ Library modules can inherit from this class to provide custom initialization
13
+ and cleanup logic that runs before and after node loading.
14
+
15
+ Example usage:
16
+ ```python
17
+ # In your library's advanced library module file:
18
+ from griptape_nodes.node_library.advanced_node_library import AdvancedNodeLibrary
19
+
20
+ class MyLibrary(AdvancedNodeLibrary):
21
+ def before_library_nodes_loaded(self, library_data, library):
22
+ # Set up any prerequisites before nodes are loaded
23
+ print(f"About to load nodes for {library_data.name}")
24
+
25
+ def after_library_nodes_loaded(self, library_data, library):
26
+ # Perform any cleanup or additional setup after nodes are loaded
27
+ print(f"Finished loading {len(library.get_registered_nodes())} nodes")
28
+ ```
29
+ """
30
+
31
+ def before_library_nodes_loaded(self, library_data: LibrarySchema, library: Library) -> None:
32
+ """Called before any nodes are loaded from the library.
33
+
34
+ This method is called after the library instance is created but before
35
+ any individual node classes are dynamically loaded and registered.
36
+
37
+ Args:
38
+ library_data: The library schema containing metadata and node definitions
39
+ library: The library instance that will contain the loaded nodes
40
+ """
41
+
42
+ def after_library_nodes_loaded(self, library_data: LibrarySchema, library: Library) -> None:
43
+ """Called after all nodes have been loaded from the library.
44
+
45
+ This method is called after all node classes have been successfully
46
+ loaded and registered with the library.
47
+
48
+ Args:
49
+ library_data: The library schema containing metadata and node definitions
50
+ library: The library instance containing the loaded nodes
51
+ """
@@ -9,6 +9,7 @@ from griptape_nodes.utils.metaclasses import SingletonMeta
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  from griptape_nodes.exe_types.node_types import BaseNode
12
+ from griptape_nodes.node_library.advanced_node_library import AdvancedNodeLibrary
12
13
 
13
14
  logger = logging.getLogger("griptape_nodes")
14
15
 
@@ -42,6 +43,13 @@ class LibraryMetadata(BaseModel):
42
43
  is_griptape_nodes_searchable: bool = True
43
44
 
44
45
 
46
+ class IconVariant(BaseModel):
47
+ """Icon variant for light and dark themes."""
48
+
49
+ light: str
50
+ dark: str
51
+
52
+
45
53
  class NodeMetadata(BaseModel):
46
54
  """Metadata about each node within the library, which informs where in the hierarchy it sits, details on usage, and tags to assist search."""
47
55
 
@@ -49,6 +57,9 @@ class NodeMetadata(BaseModel):
49
57
  description: str
50
58
  display_name: str
51
59
  tags: list[str] | None = None
60
+ icon: str | IconVariant | None = None
61
+ color: str | None = None
62
+ group: str | None = None
52
63
 
53
64
 
54
65
  class CategoryDefinition(BaseModel):
@@ -84,7 +95,7 @@ class LibrarySchema(BaseModel):
84
95
  library itself.
85
96
  """
86
97
 
87
- LATEST_SCHEMA_VERSION: ClassVar[str] = "0.1.0"
98
+ LATEST_SCHEMA_VERSION: ClassVar[str] = "0.2.0"
88
99
 
89
100
  name: str
90
101
  library_schema_version: str
@@ -95,6 +106,7 @@ class LibrarySchema(BaseModel):
95
106
  scripts: list[str] | None = None
96
107
  settings: list[Setting] | None = None
97
108
  is_default_library: bool | None = None
109
+ advanced_library_path: str | None = None
98
110
 
99
111
 
100
112
  class LibraryRegistry(metaclass=SingletonMeta):
@@ -110,13 +122,16 @@ class LibraryRegistry(metaclass=SingletonMeta):
110
122
  library_data: LibrarySchema,
111
123
  *,
112
124
  mark_as_default_library: bool = False,
125
+ advanced_library: AdvancedNodeLibrary | None = None,
113
126
  ) -> Library:
114
127
  instance = cls()
115
128
 
116
129
  if library_data.name in instance._libraries:
117
130
  msg = f"Library '{library_data.name}' already registered."
118
131
  raise KeyError(msg)
119
- library = Library(library_data=library_data, is_default_library=mark_as_default_library)
132
+ library = Library(
133
+ library_data=library_data, is_default_library=mark_as_default_library, advanced_library=advanced_library
134
+ )
120
135
  instance._libraries[library_data.name] = library
121
136
  return library
122
137
 
@@ -227,12 +242,14 @@ class Library:
227
242
  # Maintain fast lookups for node class name to class and to its metadata.
228
243
  _node_types: dict[str, type[BaseNode]]
229
244
  _node_metadata: dict[str, NodeMetadata]
245
+ _advanced_library: AdvancedNodeLibrary | None
230
246
 
231
247
  def __init__(
232
248
  self,
233
249
  library_data: LibrarySchema,
234
250
  *,
235
251
  is_default_library: bool = False,
252
+ advanced_library: AdvancedNodeLibrary | None = None,
236
253
  ) -> None:
237
254
  self._library_data = library_data
238
255
 
@@ -244,6 +261,7 @@ class Library:
244
261
 
245
262
  self._node_types = {}
246
263
  self._node_metadata = {}
264
+ self._advanced_library = advanced_library
247
265
 
248
266
  def register_new_node_type(self, node_class: type[BaseNode], metadata: NodeMetadata) -> str | None:
249
267
  """Register a new node type in this library. Returns an error string for forensics, or None if all clear."""
@@ -303,3 +321,11 @@ class Library:
303
321
 
304
322
  def get_metadata(self) -> LibraryMetadata:
305
323
  return self._library_data.metadata
324
+
325
+ def get_advanced_library(self) -> AdvancedNodeLibrary | None:
326
+ """Get the advanced library instance for this library.
327
+
328
+ Returns:
329
+ The AdvancedNodeLibrary instance, or None if not set
330
+ """
331
+ return self._advanced_library
@@ -13,7 +13,7 @@ from griptape_nodes.utils.metaclasses import SingletonMeta
13
13
 
14
14
 
15
15
  class WorkflowMetadata(BaseModel):
16
- LATEST_SCHEMA_VERSION: ClassVar[str] = "0.3.0"
16
+ LATEST_SCHEMA_VERSION: ClassVar[str] = "0.4.0"
17
17
 
18
18
  name: str
19
19
  schema_version: str
@@ -12,22 +12,35 @@ from griptape_nodes.retained_mode.events.base_events import (
12
12
  from griptape_nodes.retained_mode.events.payload_registry import PayloadRegistry
13
13
 
14
14
 
15
+ @dataclass
16
+ class RunAgentRequestArtifact(dict):
17
+ type: str
18
+ value: str
19
+
20
+
15
21
  @dataclass
16
22
  @PayloadRegistry.register
17
23
  class RunAgentRequest(RequestPayload):
18
24
  input: str
25
+ url_artifacts: list[RunAgentRequestArtifact]
26
+
27
+
28
+ @dataclass
29
+ @PayloadRegistry.register
30
+ class RunAgentResultStarted(WorkflowNotAlteredMixin, ResultPayloadSuccess):
31
+ pass
19
32
 
20
33
 
21
34
  @dataclass
22
35
  @PayloadRegistry.register
23
36
  class RunAgentResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
24
- output: str
37
+ output: dict
25
38
 
26
39
 
27
40
  @dataclass
28
41
  @PayloadRegistry.register
29
42
  class RunAgentResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
30
- error: str
43
+ error: dict
31
44
 
32
45
 
33
46
  @dataclass