griptape-nodes 0.36.1__py3-none-any.whl → 0.37.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.
@@ -15,7 +15,7 @@ with console.status("Loading Griptape Nodes...") as status:
15
15
  import tarfile
16
16
  import tempfile
17
17
  from pathlib import Path
18
- from typing import Literal
18
+ from typing import Any, Literal
19
19
 
20
20
  import httpx
21
21
  from dotenv import load_dotenv
@@ -75,7 +75,7 @@ def main() -> None:
75
75
  _process_args(args)
76
76
 
77
77
 
78
- def _run_init( # noqa: PLR0913
78
+ def _run_init( # noqa: PLR0913, C901
79
79
  *,
80
80
  interactive: bool = True,
81
81
  workspace_directory: str | None = None,
@@ -83,6 +83,8 @@ def _run_init( # noqa: PLR0913
83
83
  storage_backend: str | None = None,
84
84
  storage_backend_bucket_id: str | None = None,
85
85
  register_advanced_library: bool | None = None,
86
+ config_values: dict[str, Any] | None = None,
87
+ secret_values: dict[str, str] | None = None,
86
88
  ) -> None:
87
89
  """Runs through the engine init steps.
88
90
 
@@ -93,6 +95,8 @@ def _run_init( # noqa: PLR0913
93
95
  storage_backend (str | None): The storage backend to set.
94
96
  storage_backend_bucket_id (str | None): The storage backend bucket ID to set.
95
97
  register_advanced_library (bool | None): Whether to register the advanced library.
98
+ config_values (dict[str, any] | None): Arbitrary config key-value pairs to set.
99
+ secret_values (dict[str, str] | None): Arbitrary secret key-value pairs to set.
96
100
  """
97
101
  __init_system_config()
98
102
 
@@ -132,6 +136,18 @@ def _run_init( # noqa: PLR0913
132
136
  )
133
137
  console.print(f"[bold green]Libraries to register set to: {', '.join(libraries_to_register)}[/bold green]")
134
138
 
139
+ # Set arbitrary config values
140
+ if config_values:
141
+ for key, value in config_values.items():
142
+ config_manager.set_config_value(key, value)
143
+ console.print(f"[bold green]Config '{key}' set to: {value}[/bold green]")
144
+
145
+ # Set arbitrary secret values
146
+ if secret_values:
147
+ for key, value in secret_values.items():
148
+ secrets_manager.set_secret(key, value)
149
+ console.print(f"[bold green]Secret '{key}' set[/bold green]")
150
+
135
151
  _sync_assets()
136
152
  console.print("[bold green]Initialization complete![/bold green]")
137
153
 
@@ -152,6 +168,8 @@ def _start_engine(*, no_update: bool = False) -> None:
152
168
  storage_backend_bucket_id=ENV_STORAGE_BACKEND_BUCKET_ID,
153
169
  register_advanced_library=ENV_REGISTER_ADVANCED_LIBRARY,
154
170
  interactive=True,
171
+ config_values=None,
172
+ secret_values=None,
155
173
  )
156
174
 
157
175
  # Confusing double negation -- If `no_update` is set, we want to skip the update
@@ -214,6 +232,18 @@ def _get_args() -> argparse.Namespace:
214
232
  action="store_true",
215
233
  help="Run init in non-interactive mode (no prompts).",
216
234
  )
235
+ init_parser.add_argument(
236
+ "--config",
237
+ action="append",
238
+ metavar="KEY=VALUE",
239
+ help="Set arbitrary config values as key=value pairs (can be used multiple times). Example: --config log_level=DEBUG --config workspace_directory=/tmp",
240
+ )
241
+ init_parser.add_argument(
242
+ "--secret",
243
+ action="append",
244
+ metavar="KEY=VALUE",
245
+ help="Set arbitrary secret values as key=value pairs (can be used multiple times). Example: --secret MY_API_KEY=abc123 --secret OTHER_KEY=xyz789",
246
+ )
217
247
 
218
248
  # engine
219
249
  subparsers.add_parser("engine", help="Run the Griptape Nodes engine.")
@@ -430,9 +460,7 @@ def __build_libraries_list(*, register_advanced_library: bool) -> list[str]:
430
460
  if default_library not in current_libraries:
431
461
  current_libraries.append(default_library)
432
462
 
433
- advanced_media_library = str(
434
- library_base_dir / "griptape_nodes_advanced_media_library/griptape_nodes_advanced_media_library.json"
435
- )
463
+ advanced_media_library = str(library_base_dir / "griptape_nodes_advanced_media_library/griptape_nodes_library.json")
436
464
  if register_advanced_library:
437
465
  # If the advanced media library is not registered, add it
438
466
  if advanced_media_library not in current_libraries:
@@ -637,8 +665,42 @@ def _uninstall_self() -> None:
637
665
  os_manager.replace_process(["uv", "tool", "uninstall", "griptape-nodes"])
638
666
 
639
667
 
668
+ def _parse_key_value_pairs(pairs: list[str] | None) -> dict[str, str] | None:
669
+ """Parse key=value pairs from a list of strings.
670
+
671
+ Args:
672
+ pairs: List of strings in the format "key=value"
673
+
674
+ Returns:
675
+ Dictionary of key-value pairs, or None if no pairs provided
676
+ """
677
+ if not pairs:
678
+ return None
679
+
680
+ result = {}
681
+ for pair in pairs:
682
+ if "=" not in pair:
683
+ console.print(f"[bold red]Invalid key=value pair: {pair}. Expected format: key=value[/bold red]")
684
+ continue
685
+ # Split only on the first = to handle values that contain =
686
+ key, value = pair.split("=", 1)
687
+ key = key.strip()
688
+ value = value.strip()
689
+
690
+ if not key:
691
+ console.print(f"[bold red]Empty key in pair: {pair}[/bold red]")
692
+ continue
693
+
694
+ result[key] = value
695
+
696
+ return result if result else None
697
+
698
+
640
699
  def _process_args(args: argparse.Namespace) -> None: # noqa: C901, PLR0912
641
700
  if args.command == "init":
701
+ config_values = _parse_key_value_pairs(getattr(args, "config", None))
702
+ secret_values = _parse_key_value_pairs(getattr(args, "secret", None))
703
+
642
704
  _run_init(
643
705
  interactive=not args.no_interactive,
644
706
  workspace_directory=args.workspace_directory,
@@ -646,6 +708,8 @@ def _process_args(args: argparse.Namespace) -> None: # noqa: C901, PLR0912
646
708
  storage_backend=args.storage_backend,
647
709
  storage_backend_bucket_id=args.storage_backend_bucket_id,
648
710
  register_advanced_library=args.register_advanced_library,
711
+ config_values=config_values,
712
+ secret_values=secret_values,
649
713
  )
650
714
  elif args.command == "engine":
651
715
  _start_engine(no_update=args.no_update)
@@ -2,7 +2,7 @@
2
2
 
3
3
  import os
4
4
 
5
- if os.getenv("GTN_USE_WEBSOCKETS", "false").lower() == "true":
5
+ if os.getenv("GTN_USE_WEBSOCKETS", "true").lower() == "true":
6
6
  from griptape_nodes.app.app_websocket import start_app
7
7
  else:
8
8
  from griptape_nodes.app.app import start_app
griptape_nodes/app/app.py CHANGED
@@ -76,12 +76,6 @@ class EventLogHandler(logging.Handler):
76
76
  )
77
77
 
78
78
 
79
- griptape_nodes_logger = logging.getLogger("griptape_nodes")
80
- # When running as an app, we want to forward all log messages to the event queue so they can be sent to the GUI
81
- griptape_nodes_logger.addHandler(EventLogHandler())
82
- griptape_nodes_logger.addHandler(RichHandler(show_time=True, show_path=False, markup=True, rich_tracebacks=True))
83
- griptape_nodes_logger.setLevel(logging.INFO)
84
-
85
79
  # Logger for this module. Important that this is not the same as the griptape_nodes logger or else we'll have infinite log events.
86
80
  logger = logging.getLogger(__name__)
87
81
  console = Console()
@@ -94,6 +88,12 @@ def start_app() -> None:
94
88
  """
95
89
  global socket_manager # noqa: PLW0603 # Need to initialize the socket lazily here to avoid auth-ing too early
96
90
 
91
+ griptape_nodes_logger = logging.getLogger("griptape_nodes")
92
+ # When running as an app, we want to forward all log messages to the event queue so they can be sent to the GUI
93
+ griptape_nodes_logger.addHandler(EventLogHandler())
94
+ griptape_nodes_logger.addHandler(RichHandler(show_time=True, show_path=False, markup=True, rich_tracebacks=True))
95
+ griptape_nodes_logger.setLevel(logging.INFO)
96
+
97
97
  # Listen for SSE events from the Nodes API in a separate thread
98
98
  socket_manager = NodesApiSocketManager()
99
99
 
@@ -79,12 +79,6 @@ class EventLogHandler(logging.Handler):
79
79
  )
80
80
 
81
81
 
82
- griptape_nodes_logger = logging.getLogger("griptape_nodes")
83
- # When running as an app, we want to forward all log messages to the event queue so they can be sent to the GUI
84
- griptape_nodes_logger.addHandler(EventLogHandler())
85
- griptape_nodes_logger.addHandler(RichHandler(show_time=True, show_path=False, markup=True, rich_tracebacks=True))
86
- griptape_nodes_logger.setLevel(logging.INFO)
87
-
88
82
  # Logger for this module. Important that this is not the same as the griptape_nodes logger or else we'll have infinite log events.
89
83
  logger = logging.getLogger("griptape_nodes_app")
90
84
  console = Console()
@@ -97,6 +91,12 @@ def start_app() -> None:
97
91
  """
98
92
  _init_event_listeners()
99
93
 
94
+ griptape_nodes_logger = logging.getLogger("griptape_nodes")
95
+ # When running as an app, we want to forward all log messages to the event queue so they can be sent to the GUI
96
+ griptape_nodes_logger.addHandler(EventLogHandler())
97
+ griptape_nodes_logger.addHandler(RichHandler(show_time=True, show_path=False, markup=True, rich_tracebacks=True))
98
+ griptape_nodes_logger.setLevel(logging.INFO)
99
+
100
100
  # Listen for any signals to exit the app
101
101
  for sig in (signal.SIGINT, signal.SIGTERM):
102
102
  signal.signal(sig, lambda *_: sys.exit(0))
@@ -2,7 +2,7 @@ import logging
2
2
  from dataclasses import dataclass
3
3
 
4
4
  from griptape_nodes.exe_types.core_types import Parameter, ParameterMode, ParameterTypeBuiltin
5
- from griptape_nodes.exe_types.node_types import BaseNode, Connection, NodeResolutionState
5
+ from griptape_nodes.exe_types.node_types import BaseNode, Connection, EndLoopNode, NodeResolutionState, StartLoopNode
6
6
 
7
7
  logger = logging.getLogger("griptape_nodes")
8
8
 
@@ -99,8 +99,39 @@ class Connections:
99
99
  )
100
100
  return connection is None
101
101
 
102
+ def _get_connected_node_for_end_loop_control(
103
+ self, end_loop_node: EndLoopNode, control_parameter: Parameter
104
+ ) -> tuple[BaseNode, Parameter] | None:
105
+ """For an EndLoopNode and its control parameter, finds the connected node and parameter.
106
+
107
+ It checks both outgoing connections (where EndLoopNode's parameter is a source)
108
+ and incoming connections (where EndLoopNode's parameter is a target).
109
+ """
110
+ # Check if the EndLoopNode's control parameter is a source for an outgoing connection
111
+ if ParameterMode.OUTPUT in control_parameter.allowed_modes:
112
+ outgoing_connections_for_node = self.outgoing_index.get(end_loop_node.name, {})
113
+ connection_ids_as_source = outgoing_connections_for_node.get(control_parameter.name, [])
114
+ if connection_ids_as_source:
115
+ connection_id = connection_ids_as_source[0]
116
+ connection = self.connections.get(connection_id)
117
+ if connection:
118
+ return connection.target_node, connection.target_parameter
119
+ elif ParameterMode.INPUT in control_parameter.allowed_modes:
120
+ # Check if the EndLoopNode's control parameter is a target for an incoming connection
121
+ incoming_connections_for_node = self.incoming_index.get(end_loop_node.name, {})
122
+ connection_ids_as_target = incoming_connections_for_node.get(control_parameter.name, [])
123
+ if connection_ids_as_target:
124
+ for connection_id in connection_ids_as_target:
125
+ connection = self.connections.get(connection_id)
126
+ if connection and isinstance(connection.source_node, StartLoopNode):
127
+ return connection.source_node, connection.source_parameter
128
+ return None # No connection found for this control parameter
129
+
102
130
  def get_connected_node(self, node: BaseNode, parameter: Parameter) -> tuple[BaseNode, Parameter] | None:
103
131
  # Check to see if we should be getting the next connection or the previous connection based on the parameter.
132
+ # Override this method for EndLoopNodes - these might have to go backwards or forwards.
133
+ if isinstance(node, EndLoopNode) and ParameterTypeBuiltin.CONTROL_TYPE.value == parameter.output_type:
134
+ return self._get_connected_node_for_end_loop_control(node, parameter)
104
135
  if ParameterTypeBuiltin.CONTROL_TYPE.value == parameter.output_type:
105
136
  connections = self.outgoing_index
106
137
  else:
@@ -163,6 +163,7 @@ class BaseNodeElement:
163
163
  element_id: str = field(default_factory=lambda: str(uuid.uuid4().hex))
164
164
  element_type: str = field(default_factory=lambda: BaseNodeElement.__name__)
165
165
  name: str = field(default_factory=lambda: str(f"{BaseNodeElement.__name__}_{uuid.uuid4().hex}"))
166
+ parent_group_name: str | None = None
166
167
 
167
168
  _children: list[BaseNodeElement] = field(default_factory=list)
168
169
  _stack: ClassVar[list[BaseNodeElement]] = []
@@ -219,6 +220,7 @@ class BaseNodeElement:
219
220
  return {
220
221
  "element_id": self.element_id,
221
222
  "element_type": self.__class__.__name__,
223
+ "parent_group_name": self.parent_group_name,
222
224
  "children": [child.to_dict() for child in self._children],
223
225
  }
224
226
 
@@ -409,6 +411,20 @@ class ParameterGroup(BaseNodeElement):
409
411
  differences[key] = other_value
410
412
  return differences
411
413
 
414
+ def add_child(self, child: BaseNodeElement) -> None:
415
+ child.parent_group_name = self.name
416
+ return super().add_child(child)
417
+
418
+ def remove_child(self, child: BaseNodeElement | str) -> None:
419
+ if isinstance(child, str):
420
+ child_from_str = self.find_element_by_name(child)
421
+ if child_from_str is not None and isinstance(child_from_str, BaseNodeElement):
422
+ child_from_str.parent_group_name = None
423
+ return super().remove_child(child_from_str)
424
+ else:
425
+ child.parent_group_name = None
426
+ return super().remove_child(child)
427
+
412
428
 
413
429
  # TODO: https://github.com/griptape-ai/griptape-nodes/issues/856
414
430
  class ParameterBase(BaseNodeElement, ABC):
@@ -8,7 +8,7 @@ from griptape.events import EventBus
8
8
 
9
9
  from griptape_nodes.exe_types.connections import Connections
10
10
  from griptape_nodes.exe_types.core_types import ParameterTypeBuiltin
11
- from griptape_nodes.exe_types.node_types import NodeResolutionState, StartNode
11
+ from griptape_nodes.exe_types.node_types import NodeResolutionState, StartLoopNode, StartNode
12
12
  from griptape_nodes.machines.control_flow import CompleteState, ControlFlowMachine
13
13
  from griptape_nodes.retained_mode.events.base_events import ExecutionEvent, ExecutionGriptapeNodeEvent
14
14
  from griptape_nodes.retained_mode.events.execution_events import ControlFlowCancelledEvent
@@ -313,7 +313,7 @@ class ControlFlow:
313
313
  has_control_connection = True
314
314
  break
315
315
  # if there is a connection coming in, isn't a start.
316
- if has_control_connection:
316
+ if has_control_connection and not isinstance(node, StartLoopNode):
317
317
  continue
318
318
  # Does it have an outgoing connection?
319
319
  if node.name in cn_mgr.outgoing_index:
@@ -581,6 +581,201 @@ class BaseNode(ABC):
581
581
  msg = f"Parameter '{parameter_name} doesn't exist on {self.name}'"
582
582
  raise RuntimeError(msg)
583
583
 
584
+ def reorder_elements(self, element_order: list[str | int]) -> None:
585
+ """Reorder the elements of this node.
586
+
587
+ Args:
588
+ element_order: A list of element names or indices in the desired order.
589
+ Can mix names and indices. Names take precedence over indices.
590
+
591
+ Example:
592
+ # Reorder by names
593
+ node.reorder_elements(["element1", "element2", "element3"])
594
+
595
+ # Reorder by indices
596
+ node.reorder_elements([0, 2, 1])
597
+
598
+ # Mix names and indices
599
+ node.reorder_elements(["element1", 2, "element3"])
600
+ """
601
+ # Get current elements
602
+ current_elements = self.root_ui_element._children
603
+
604
+ # Create a new ordered list of elements
605
+ ordered_elements = []
606
+ for item in element_order:
607
+ if isinstance(item, str):
608
+ # Find element by name
609
+ element = self.root_ui_element.find_element_by_name(item)
610
+ if element is None:
611
+ msg = f"Element '{item}' not found"
612
+ raise ValueError(msg)
613
+ ordered_elements.append(element)
614
+ elif isinstance(item, int):
615
+ # Get element by index
616
+ if item < 0 or item >= len(current_elements):
617
+ msg = f"Element index {item} out of range"
618
+ raise ValueError(msg)
619
+ ordered_elements.append(current_elements[item])
620
+ else:
621
+ msg = "Element order must contain strings (names) or integers (indices)"
622
+ raise TypeError(msg)
623
+
624
+ # Verify we have all elements
625
+ if len(ordered_elements) != len(current_elements):
626
+ msg = "Element order must include all elements exactly once"
627
+ raise ValueError(msg)
628
+
629
+ # Remove all elements from root_ui_element
630
+ for element in current_elements:
631
+ self.root_ui_element.remove_child(element)
632
+
633
+ # Add elements back in the new order
634
+ for element in ordered_elements:
635
+ self.root_ui_element.add_child(element)
636
+
637
+ def move_element_to_position(self, element: str | int, position: str | int) -> None:
638
+ """Move a single element to a specific position in the element list.
639
+
640
+ Args:
641
+ element: The element to move, specified by name or index
642
+ position: The target position, which can be:
643
+ - "first" to move to the beginning
644
+ - "last" to move to the end
645
+ - An integer index (0-based) for a specific position
646
+
647
+ Example:
648
+ # Move element to first position by name
649
+ node.move_element_to_position("element1", "first")
650
+
651
+ # Move element to last position by index
652
+ node.move_element_to_position(0, "last")
653
+
654
+ # Move element to specific position
655
+ node.move_element_to_position("element1", 2)
656
+ """
657
+ # Get list of all element names
658
+ element_names = [child.name for child in self.root_ui_element._children]
659
+
660
+ # Convert element index to name if needed
661
+ element = self._get_element_name(element, element_names)
662
+
663
+ # Create new order with moved element
664
+ new_order = element_names.copy()
665
+ idx = new_order.index(element)
666
+
667
+ # Handle special position values
668
+ if position == "first":
669
+ target_pos = 0
670
+ elif position == "last":
671
+ target_pos = len(new_order) - 1
672
+ elif isinstance(position, int):
673
+ if position < 0 or position >= len(new_order):
674
+ msg = f"Target position {position} out of range"
675
+ raise ValueError(msg)
676
+ target_pos = position
677
+ else:
678
+ msg = "Position must be 'first', 'last', or an integer index"
679
+ raise TypeError(msg)
680
+
681
+ # Remove element from current position and insert at target position
682
+ new_order.pop(idx)
683
+ new_order.insert(target_pos, element)
684
+
685
+ # Use reorder_elements to apply the move
686
+ self.reorder_elements(list(new_order))
687
+
688
+ def _get_element_name(self, element: str | int, element_names: list[str]) -> str:
689
+ """Convert an element identifier (name or index) to its name.
690
+
691
+ Args:
692
+ element: Element identifier, either a name (str) or index (int)
693
+ element_names: List of all element names
694
+
695
+ Returns:
696
+ The element name
697
+
698
+ Raises:
699
+ ValueError: If index is out of range
700
+ """
701
+ if isinstance(element, int):
702
+ if element < 0 or element >= len(element_names):
703
+ msg = f"Element index {element} out of range"
704
+ raise ValueError(msg)
705
+ return element_names[element]
706
+ return element
707
+
708
+ def swap_elements(self, elem1: str | int, elem2: str | int) -> None:
709
+ """Swap the positions of two elements.
710
+
711
+ Args:
712
+ elem1: First element to swap, specified by name or index
713
+ elem2: Second element to swap, specified by name or index
714
+
715
+ Example:
716
+ # Swap by names
717
+ node.swap_elements("element1", "element2")
718
+
719
+ # Swap by indices
720
+ node.swap_elements(0, 2)
721
+
722
+ # Mix names and indices
723
+ node.swap_elements("element1", 2)
724
+ """
725
+ # Get list of all element names
726
+ element_names = [child.name for child in self.root_ui_element._children]
727
+
728
+ # Convert indices to names if needed
729
+ elem1 = self._get_element_name(elem1, element_names)
730
+ elem2 = self._get_element_name(elem2, element_names)
731
+
732
+ # Create new order with swapped elements
733
+ new_order = element_names.copy()
734
+ idx1 = new_order.index(elem1)
735
+ idx2 = new_order.index(elem2)
736
+ new_order[idx1], new_order[idx2] = new_order[idx2], new_order[idx1]
737
+
738
+ # Use reorder_elements to apply the swap
739
+ self.reorder_elements(list(new_order))
740
+
741
+ def move_element_up_down(self, element: str | int, *, up: bool = True) -> None:
742
+ """Move an element up or down one position in the element list.
743
+
744
+ Args:
745
+ element: The element to move, specified by name or index
746
+ up: If True, move element up one position. If False, move down one position.
747
+
748
+ Example:
749
+ # Move element up by name
750
+ node.move_element_up_down("element1", up=True)
751
+
752
+ # Move element down by index
753
+ node.move_element_up_down(0, up=False)
754
+ """
755
+ # Get list of all element names
756
+ element_names = [child.name for child in self.root_ui_element._children]
757
+
758
+ # Convert index to name if needed
759
+ element = self._get_element_name(element, element_names)
760
+
761
+ # Create new order with moved element
762
+ new_order = element_names.copy()
763
+ idx = new_order.index(element)
764
+
765
+ if up:
766
+ if idx == 0:
767
+ msg = "Element is already at the top"
768
+ raise ValueError(msg)
769
+ new_order[idx], new_order[idx - 1] = new_order[idx - 1], new_order[idx]
770
+ else:
771
+ if idx == len(new_order) - 1:
772
+ msg = "Element is already at the bottom"
773
+ raise ValueError(msg)
774
+ new_order[idx], new_order[idx + 1] = new_order[idx + 1], new_order[idx]
775
+
776
+ # Use reorder_elements to apply the move
777
+ self.reorder_elements(list(new_order))
778
+
584
779
 
585
780
  class ControlNode(BaseNode):
586
781
  # Control Nodes may have one Control Input Port and at least one Control Output Port
@@ -620,6 +815,18 @@ class EndNode(BaseNode):
620
815
  self.add_parameter(ControlParameterInput())
621
816
 
622
817
 
818
+ class StartLoopNode(BaseNode):
819
+ finished: bool
820
+ current_index: int
821
+ end_node: EndLoopNode | None = None
822
+ """Creating class for Start Loop Node in order to implement loop functionality in execution."""
823
+
824
+
825
+ class EndLoopNode(BaseNode):
826
+ start_node: StartLoopNode | None = None
827
+ """Creating class for Start Loop Node in order to implement loop functionality in execution."""
828
+
829
+
623
830
  class Connection:
624
831
  source_node: BaseNode
625
832
  target_node: BaseNode
@@ -0,0 +1,83 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ from griptape.memory.structure import Run
4
+
5
+ from griptape_nodes.retained_mode.events.base_events import (
6
+ RequestPayload,
7
+ ResultPayloadFailure,
8
+ ResultPayloadSuccess,
9
+ WorkflowNotAlteredMixin,
10
+ )
11
+ from griptape_nodes.retained_mode.events.payload_registry import PayloadRegistry
12
+
13
+
14
+ @dataclass
15
+ @PayloadRegistry.register
16
+ class RunAgentRequest(RequestPayload):
17
+ input: str
18
+
19
+
20
+ @dataclass
21
+ @PayloadRegistry.register
22
+ class RunAgentResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
23
+ output: str
24
+
25
+
26
+ @dataclass
27
+ @PayloadRegistry.register
28
+ class RunAgentResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
29
+ error: str
30
+
31
+
32
+ @dataclass
33
+ @PayloadRegistry.register
34
+ class GetConversationMemoryRequest(RequestPayload):
35
+ pass
36
+
37
+
38
+ @dataclass
39
+ @PayloadRegistry.register
40
+ class GetConversationMemoryResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
41
+ runs: list[Run]
42
+
43
+
44
+ @dataclass
45
+ @PayloadRegistry.register
46
+ class GetConversationMemoryResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
47
+ pass
48
+
49
+
50
+ @dataclass
51
+ @PayloadRegistry.register
52
+ class ConfigureAgentRequest(RequestPayload):
53
+ prompt_driver: dict = field(default_factory=dict)
54
+
55
+
56
+ @dataclass
57
+ @PayloadRegistry.register
58
+ class ConfigureAgentResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
59
+ pass
60
+
61
+
62
+ @dataclass
63
+ @PayloadRegistry.register
64
+ class ConfigureAgentResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
65
+ pass
66
+
67
+
68
+ @dataclass
69
+ @PayloadRegistry.register
70
+ class ResetAgentConversationMemoryRequest(RequestPayload):
71
+ pass
72
+
73
+
74
+ @dataclass
75
+ @PayloadRegistry.register
76
+ class ResetAgentConversationMemoryResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
77
+ pass
78
+
79
+
80
+ @dataclass
81
+ @PayloadRegistry.register
82
+ class ResetAgentConversationMemoryResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
83
+ pass
@@ -39,6 +39,7 @@ from griptape_nodes.utils.metaclasses import SingletonMeta
39
39
 
40
40
  if TYPE_CHECKING:
41
41
  from griptape_nodes.exe_types.node_types import BaseNode
42
+ from griptape_nodes.retained_mode.managers.agent_manager import AgentManager
42
43
  from griptape_nodes.retained_mode.managers.arbitrary_code_exec_manager import (
43
44
  ArbitraryCodeExecManager,
44
45
  )
@@ -98,8 +99,10 @@ class GriptapeNodes(metaclass=SingletonMeta):
98
99
  _arbitrary_code_exec_manager: ArbitraryCodeExecManager
99
100
  _operation_depth_manager: OperationDepthManager
100
101
  _static_files_manager: StaticFilesManager
102
+ _agent_manager: AgentManager
101
103
 
102
104
  def __init__(self) -> None:
105
+ from griptape_nodes.retained_mode.managers.agent_manager import AgentManager
103
106
  from griptape_nodes.retained_mode.managers.arbitrary_code_exec_manager import (
104
107
  ArbitraryCodeExecManager,
105
108
  )
@@ -139,6 +142,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
139
142
  self._static_files_manager = StaticFilesManager(
140
143
  self._config_manager, self._secrets_manager, self._event_manager
141
144
  )
145
+ self._agent_manager = AgentManager(self._event_manager)
142
146
 
143
147
  # Assign handlers now that these are created.
144
148
  self._event_manager.assign_manager_to_request_type(
@@ -222,6 +226,10 @@ class GriptapeNodes(metaclass=SingletonMeta):
222
226
  def StaticFilesManager(cls) -> StaticFilesManager:
223
227
  return GriptapeNodes.get_instance()._static_files_manager
224
228
 
229
+ @classmethod
230
+ def AgentManager(cls) -> AgentManager:
231
+ return GriptapeNodes.get_instance()._agent_manager
232
+
225
233
  @classmethod
226
234
  def clear_data(cls) -> None:
227
235
  # Get canvas