griptape-nodes 0.67.0__py3-none-any.whl → 0.68.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.
@@ -2,16 +2,24 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import logging
5
+ import os
5
6
  import sys
6
7
  from typing import TYPE_CHECKING, Any
7
8
 
8
9
  if TYPE_CHECKING:
9
10
  from pathlib import Path
10
- from subprocess import _ENV
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13
 
14
14
 
15
+ def _create_subprocess_env(extra_env: dict[str, str] | None = None) -> dict[str, str]:
16
+ """Create environment for subprocess, inheriting parent env with optional overrides."""
17
+ env = os.environ.copy()
18
+ if extra_env:
19
+ env.update(extra_env)
20
+ return env
21
+
22
+
15
23
  class PythonSubprocessExecutorError(Exception):
16
24
  """Exception raised during Python subprocess execution."""
17
25
 
@@ -22,7 +30,11 @@ class PythonSubprocessExecutor:
22
30
  self._is_running = False
23
31
 
24
32
  async def execute_python_script(
25
- self, script_path: Path, args: list[str] | None = None, cwd: Path | None = None, env: _ENV | None = None
33
+ self,
34
+ script_path: Path,
35
+ args: list[str] | None = None,
36
+ cwd: Path | None = None,
37
+ env: dict[str, str] | None = None,
26
38
  ) -> None:
27
39
  """Execute a Python script in a subprocess and wait for completion.
28
40
 
@@ -30,7 +42,7 @@ class PythonSubprocessExecutor:
30
42
  script_path: Path to the Python script to execute
31
43
  args: Additional command line arguments
32
44
  cwd: Working directory for the subprocess
33
- env: Environment variables for the subprocess
45
+ env: Extra environment variables to add or override in the subprocess
34
46
  """
35
47
  if self.is_running():
36
48
  logger.warning("Another subprocess is already running. Terminating it first.")
@@ -38,6 +50,7 @@ class PythonSubprocessExecutor:
38
50
 
39
51
  args = args or []
40
52
  command = [sys.executable, str(script_path), *args]
53
+ subprocess_env = _create_subprocess_env(env)
41
54
 
42
55
  try:
43
56
  logger.info("Starting subprocess: %s", " ".join(command))
@@ -46,7 +59,7 @@ class PythonSubprocessExecutor:
46
59
  self._process = await asyncio.create_subprocess_exec(
47
60
  *command,
48
61
  cwd=cwd,
49
- env=env,
62
+ env=subprocess_env,
50
63
  stdout=asyncio.subprocess.PIPE,
51
64
  stderr=asyncio.subprocess.PIPE,
52
65
  )
@@ -5,6 +5,7 @@ import asyncio
5
5
  import logging
6
6
  import pickle
7
7
  from dataclasses import dataclass
8
+ from enum import StrEnum
8
9
  from pathlib import Path
9
10
  from typing import TYPE_CHECKING, Any, NamedTuple
10
11
 
@@ -16,7 +17,7 @@ from griptape_nodes.exe_types.base_iterative_nodes import (
16
17
  BaseIterativeStartNode,
17
18
  )
18
19
  from griptape_nodes.exe_types.core_types import ParameterTypeBuiltin
19
- from griptape_nodes.exe_types.node_groups import BaseIterativeNodeGroup, SubflowNodeGroup
20
+ from griptape_nodes.exe_types.node_groups import BaseIterativeNodeGroup, IterationControlParam, SubflowNodeGroup
20
21
  from griptape_nodes.exe_types.node_types import (
21
22
  CONTROL_INPUT_PARAMETER,
22
23
  LOCAL_EXECUTION,
@@ -106,6 +107,15 @@ if TYPE_CHECKING:
106
107
 
107
108
  logger = logging.getLogger("griptape_nodes")
108
109
 
110
+
111
+ class IterationControlAction(StrEnum):
112
+ """Enum for iterative group control actions."""
113
+
114
+ ADD = "add" # Normal path - add result to list
115
+ SKIP = "skip" # Skip this iteration, don't add result
116
+ BREAK = "break" # Break out of loop immediately
117
+
118
+
109
119
  LOOP_EVENTS_TO_SUPPRESS = {
110
120
  CreateFlowResultSuccess,
111
121
  CreateFlowResultFailure,
@@ -816,13 +826,153 @@ class NodeExecutor:
816
826
  # Check if it's the break signal
817
827
  return next_control_output == deserialized_end_node.break_loop_signal_output
818
828
 
819
- async def _execute_loop_iterations_sequentially( # noqa: PLR0915
829
+ def _get_iteration_control_action_for_group(
830
+ self,
831
+ end_loop_node: BaseIterativeNodeGroup,
832
+ node_name_mappings: dict[str, str],
833
+ ) -> IterationControlAction:
834
+ """Determine which control action was taken during iteration for an iterative group.
835
+
836
+ Checks if any internal nodes have executed their control outputs that connect to the
837
+ group's control parameters (loop_complete, skip_iteration, break_loop). This is done by
838
+ checking the source node's parameter_output_values for CONTROL_INPUT_PARAMETER.
839
+
840
+ Args:
841
+ end_loop_node: The BaseIterativeNodeGroup being executed
842
+ node_name_mappings: Mapping from original to deserialized node names
843
+
844
+ Returns:
845
+ IterationControlAction indicating which control path was taken
846
+ """
847
+ # Get incoming connections to the end_loop_node (the iterative group)
848
+ list_connections_request = ListConnectionsForNodeRequest(node_name=end_loop_node.name)
849
+ list_connections_result = GriptapeNodes.handle_request(list_connections_request)
850
+ if not isinstance(list_connections_result, ListConnectionsForNodeResultSuccess):
851
+ logger.warning("Failed to list connections for node %s", end_loop_node.name)
852
+ return IterationControlAction.ADD
853
+
854
+ incoming_connections = list_connections_result.incoming_connections
855
+
856
+ # Check each control parameter to see if its source node has fired
857
+ # Priority: BREAK > SKIP > LOOP_COMPLETE > default ADD
858
+ break_source = self._find_source_for_control_param(incoming_connections, IterationControlParam.BREAK_LOOP)
859
+ skip_source = self._find_source_for_control_param(incoming_connections, IterationControlParam.SKIP_ITERATION)
860
+ loop_complete_source = self._find_source_for_control_param(
861
+ incoming_connections, IterationControlParam.LOOP_COMPLETE
862
+ )
863
+
864
+ # Check if break was triggered
865
+ if self._check_control_source_fired(break_source, node_name_mappings):
866
+ return IterationControlAction.BREAK
867
+
868
+ # Check if skip was triggered
869
+ if self._check_control_source_fired(skip_source, node_name_mappings):
870
+ return IterationControlAction.SKIP
871
+
872
+ # Check if loop_complete was triggered
873
+ if self._check_control_source_fired(loop_complete_source, node_name_mappings):
874
+ return IterationControlAction.ADD
875
+
876
+ # If none of the control parameters were triggered, default to ADD
877
+ # This preserves backward compatibility for workflows without explicit loop_complete connections
878
+ return IterationControlAction.ADD
879
+
880
+ def _check_control_source_fired(
881
+ self,
882
+ source: tuple[str, str] | None,
883
+ node_name_mappings: dict[str, str],
884
+ ) -> bool:
885
+ """Check if a control source node has fired its control output.
886
+
887
+ Args:
888
+ source: Tuple of (source_node_name, source_parameter_name) or None
889
+ node_name_mappings: Mapping from original to deserialized node names
890
+
891
+ Returns:
892
+ True if the source node's next control output matches the specified parameter
893
+ """
894
+ if source is None:
895
+ return False
896
+
897
+ source_node_name, source_param_name = source
898
+ deserialized_source_name = node_name_mappings.get(source_node_name)
899
+ if deserialized_source_name is None:
900
+ return False
901
+
902
+ node_manager = GriptapeNodes.NodeManager()
903
+ try:
904
+ deserialized_source_node = node_manager.get_node_by_name(deserialized_source_name)
905
+ except ValueError:
906
+ return False
907
+
908
+ if deserialized_source_node is None:
909
+ return False
910
+
911
+ # Check if the node's next control output matches the source parameter
912
+ next_control_output = deserialized_source_node.get_next_control_output()
913
+ if next_control_output is None:
914
+ return False
915
+
916
+ # Get the parameter object to compare
917
+ source_param = deserialized_source_node.get_parameter_by_name(source_param_name)
918
+ return next_control_output == source_param
919
+
920
+ def _find_source_for_control_param(
921
+ self,
922
+ incoming_connections: list,
923
+ control_param_name: str,
924
+ ) -> tuple[str, str] | None:
925
+ """Find the source node and parameter that connects to a control parameter on the iterative group.
926
+
927
+ Args:
928
+ incoming_connections: List of incoming connections to the iterative group
929
+ control_param_name: Name of the control parameter to find (e.g., "break_loop")
930
+
931
+ Returns:
932
+ Tuple of (source_node_name, source_parameter_name), or None if not found
933
+ """
934
+ flow_manager = GriptapeNodes.FlowManager()
935
+ connections = flow_manager.get_connections()
936
+
937
+ for conn in incoming_connections:
938
+ if conn.target_parameter_name != control_param_name:
939
+ continue
940
+
941
+ source_node_name = conn.source_node_name
942
+ source_param_name = conn.source_parameter_name
943
+
944
+ # If source is a SubflowNodeGroup, follow the internal connection to get the actual source
945
+ node_manager = GriptapeNodes.NodeManager()
946
+ try:
947
+ source_node = node_manager.get_node_by_name(source_node_name)
948
+ except ValueError:
949
+ continue
950
+
951
+ if isinstance(source_node, SubflowNodeGroup):
952
+ # Get connections to this proxy parameter to find the actual internal source
953
+ proxy_param = source_node.get_parameter_by_name(source_param_name)
954
+ if proxy_param:
955
+ internal_connections = connections.get_all_incoming_connections(source_node)
956
+ for internal_conn in internal_connections:
957
+ if (
958
+ internal_conn.target_parameter.name == source_param_name
959
+ and internal_conn.is_node_group_internal
960
+ ):
961
+ source_node_name = internal_conn.source_node.name
962
+ source_param_name = internal_conn.source_parameter.name
963
+ break
964
+
965
+ return (source_node_name, source_param_name)
966
+
967
+ return None
968
+
969
+ async def _execute_loop_iterations_sequentially( # noqa: PLR0915, C901, PLR0912
820
970
  self,
821
971
  package_result: PackageNodesAsSerializedFlowResultSuccess,
822
972
  total_iterations: int,
823
973
  parameter_values_per_iteration: dict[int, dict[str, Any]],
824
974
  end_loop_node: BaseIterativeEndNode | BaseIterativeNodeGroup,
825
- ) -> tuple[dict[int, Any], list[int], dict[str, Any]]:
975
+ ) -> tuple[dict[int, Any], list[int], dict[str, Any], int, bool]:
826
976
  """Execute loop iterations sequentially by running one flow instance N times.
827
977
 
828
978
  Args:
@@ -834,8 +984,10 @@ class NodeExecutor:
834
984
  Returns:
835
985
  Tuple of:
836
986
  - iteration_results: Dict mapping iteration_index -> result value
837
- - successful_iterations: List of iteration indices that succeeded
987
+ - successful_iterations: List of iteration indices that executed without error
838
988
  - last_iteration_values: Dict mapping parameter names -> values from last iteration
989
+ - skipped_count: Number of iterations that were skipped via skip control signal
990
+ - break_occurred: True if the loop exited early due to a break signal
839
991
  """
840
992
  # Deserialize flow once
841
993
  context_manager = GriptapeNodes.ContextManager()
@@ -868,6 +1020,8 @@ class NodeExecutor:
868
1020
 
869
1021
  iteration_results: dict[int, Any] = {}
870
1022
  successful_iterations: list[int] = []
1023
+ skipped_count = 0
1024
+ break_occurred = False
871
1025
 
872
1026
  # Build reverse mapping: packaged_name → original_name for event translation
873
1027
  reverse_node_mapping = {
@@ -927,6 +1081,39 @@ class NodeExecutor:
927
1081
 
928
1082
  successful_iterations.append(iteration_index)
929
1083
 
1084
+ # For BaseIterativeNodeGroup, check control action to handle skip/break
1085
+ if isinstance(end_loop_node, BaseIterativeNodeGroup):
1086
+ control_action = self._get_iteration_control_action_for_group(end_loop_node, node_name_mappings)
1087
+
1088
+ if control_action == IterationControlAction.SKIP:
1089
+ logger.info(
1090
+ "Skip detected at iteration %d/%d - skipping result collection",
1091
+ iteration_index + 1,
1092
+ total_iterations,
1093
+ )
1094
+ skipped_count += 1
1095
+ continue
1096
+
1097
+ if control_action == IterationControlAction.BREAK:
1098
+ logger.info(
1099
+ "Break detected at iteration %d/%d - collecting result then stopping",
1100
+ iteration_index + 1,
1101
+ total_iterations,
1102
+ )
1103
+ # Extract result from this iteration before breaking
1104
+ # (the work was done, so we should collect the result)
1105
+ deserialized_flows = [(iteration_index, flow_name, node_name_mappings)]
1106
+ single_iteration_results = self.get_parameter_values_from_iterations(
1107
+ end_loop_node=end_loop_node,
1108
+ deserialized_flows=deserialized_flows,
1109
+ package_flow_result_success=package_result,
1110
+ )
1111
+ iteration_results.update(single_iteration_results)
1112
+ break_occurred = True
1113
+ break
1114
+
1115
+ # control_action == ADD: fall through to extract result
1116
+
930
1117
  # Extract result from this iteration
931
1118
  deserialized_flows = [(iteration_index, flow_name, node_name_mappings)]
932
1119
  single_iteration_results = self.get_parameter_values_from_iterations(
@@ -938,13 +1125,14 @@ class NodeExecutor:
938
1125
 
939
1126
  logger.info("Completed sequential iteration %d/%d", iteration_index + 1, total_iterations)
940
1127
 
941
- # Check if the end node signaled a break
1128
+ # Check if the end node signaled a break (for BaseIterativeEndNode)
942
1129
  if self._should_break_loop(node_name_mappings, package_result):
943
1130
  logger.info(
944
1131
  "Loop break detected at iteration %d/%d - stopping execution early",
945
1132
  iteration_index + 1,
946
1133
  total_iterations,
947
1134
  )
1135
+ break_occurred = True
948
1136
  break
949
1137
 
950
1138
  # Extract last iteration values from the last successful iteration
@@ -956,7 +1144,7 @@ class NodeExecutor:
956
1144
  total_iterations=len(successful_iterations),
957
1145
  )
958
1146
 
959
- return iteration_results, successful_iterations, last_iteration_values
1147
+ return iteration_results, successful_iterations, last_iteration_values, skipped_count, break_occurred
960
1148
 
961
1149
  finally:
962
1150
  # Cleanup - delete the flow
@@ -1018,6 +1206,8 @@ class NodeExecutor:
1018
1206
  iteration_results,
1019
1207
  successful_iterations,
1020
1208
  last_iteration_values,
1209
+ _skipped_count,
1210
+ _break_occurred,
1021
1211
  ) = await self._execute_loop_iterations_sequentially(
1022
1212
  package_result=package_result,
1023
1213
  total_iterations=total_iterations,
@@ -1383,12 +1573,16 @@ class NodeExecutor:
1383
1573
  parameter_values_per_iteration = self._get_merged_parameter_values_for_iterative_group(node, package_result)
1384
1574
 
1385
1575
  # Execute iterations sequentially based on execution environment
1576
+ skipped_count = 0
1577
+ break_occurred = False
1386
1578
  match execution_type:
1387
1579
  case node_types.LOCAL_EXECUTION:
1388
1580
  (
1389
1581
  iteration_results,
1390
1582
  successful_iterations,
1391
1583
  last_iteration_values,
1584
+ skipped_count,
1585
+ break_occurred,
1392
1586
  ) = await self._execute_loop_iterations_sequentially(
1393
1587
  package_result=package_result,
1394
1588
  total_iterations=total_iterations,
@@ -1420,19 +1614,25 @@ class NodeExecutor:
1420
1614
  execution_type=execution_type,
1421
1615
  )
1422
1616
 
1423
- # Check if execution stopped early due to break (not failure)
1617
+ # Check if execution stopped early
1424
1618
  if len(successful_iterations) < total_iterations:
1425
- expected_count = len(successful_iterations)
1426
- actual_count = len(iteration_results)
1427
- if expected_count != actual_count:
1428
- failed_count = expected_count - actual_count
1429
- msg = f"Iterative group execution failed: {failed_count} of {expected_count} iterations failed"
1430
- raise RuntimeError(msg)
1431
- logger.info(
1432
- "Iterative group execution stopped early at %d of %d iterations (break signal)",
1433
- len(successful_iterations),
1434
- total_iterations,
1435
- )
1619
+ if break_occurred:
1620
+ # Early exit due to break signal - this is expected behavior
1621
+ logger.info(
1622
+ "Iterative group execution stopped early at %d of %d iterations (break signal)",
1623
+ len(successful_iterations),
1624
+ total_iterations,
1625
+ )
1626
+ else:
1627
+ # Early exit not due to break - check for failures
1628
+ # successful_iterations includes skipped iterations, but iteration_results does not
1629
+ # So we need to account for skipped iterations when checking for failures
1630
+ expected_result_count = len(successful_iterations) - skipped_count
1631
+ actual_result_count = len(iteration_results)
1632
+ if expected_result_count != actual_result_count:
1633
+ failed_count = expected_result_count - actual_result_count
1634
+ msg = f"Iterative group execution failed: {failed_count} of {len(successful_iterations)} iterations failed"
1635
+ raise RuntimeError(msg)
1436
1636
 
1437
1637
  # Build results list in iteration order
1438
1638
  node._results_list = []
@@ -1,7 +1,7 @@
1
1
  """Node group implementations for managing collections of nodes."""
2
2
 
3
- from .base_iterative_node_group import BaseIterativeNodeGroup
3
+ from .base_iterative_node_group import BaseIterativeNodeGroup, IterationControlParam
4
4
  from .base_node_group import BaseNodeGroup
5
5
  from .subflow_node_group import SubflowNodeGroup
6
6
 
7
- __all__ = ["BaseIterativeNodeGroup", "BaseNodeGroup", "SubflowNodeGroup"]
7
+ __all__ = ["BaseIterativeNodeGroup", "BaseNodeGroup", "IterationControlParam", "SubflowNodeGroup"]
@@ -3,9 +3,11 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from abc import abstractmethod
6
+ from enum import StrEnum
6
7
  from typing import Any
7
8
 
8
9
  from griptape_nodes.exe_types.core_types import (
10
+ ControlParameterInput,
9
11
  Parameter,
10
12
  ParameterMode,
11
13
  ParameterTypeBuiltin,
@@ -23,6 +25,14 @@ EXECUTION_MODE_VALUE_LOOKUP = {
23
25
  }
24
26
 
25
27
 
28
+ class IterationControlParam(StrEnum):
29
+ """Parameter names for iteration control on iterative node groups."""
30
+
31
+ LOOP_COMPLETE = "loop_complete"
32
+ SKIP_ITERATION = "skip_iteration"
33
+ BREAK_LOOP = "break_loop"
34
+
35
+
26
36
  class BaseIterativeNodeGroup(SubflowNodeGroup):
27
37
  """Base class for iterative node groups (ForEach, ForLoop, etc.).
28
38
 
@@ -100,7 +110,15 @@ class BaseIterativeNodeGroup(SubflowNodeGroup):
100
110
  self.metadata["left_parameters"] = []
101
111
  self.metadata["left_parameters"].append("index")
102
112
 
103
- # Results collection parameters (right side - collects from group)
113
+ # Control input for loop completion (right side - primary loop completion path)
114
+ self.loop_complete = ControlParameterInput(
115
+ tooltip="Signal that this iteration is complete and continue to next iteration",
116
+ name=IterationControlParam.LOOP_COMPLETE.value,
117
+ )
118
+ self.loop_complete.ui_options = {"display_name": "Loop Complete"}
119
+ self.add_parameter(self.loop_complete)
120
+
121
+ # Data parameter for the item to add (right side - collects from group)
104
122
  self.new_item_to_add = Parameter(
105
123
  name="new_item_to_add",
106
124
  tooltip="Item to add to results list for each iteration",
@@ -109,6 +127,21 @@ class BaseIterativeNodeGroup(SubflowNodeGroup):
109
127
  )
110
128
  self.add_parameter(self.new_item_to_add)
111
129
 
130
+ # Skip and Break control inputs (right side - for loop control)
131
+ self.skip_iteration = ControlParameterInput(
132
+ tooltip="Skip current item and continue to next iteration",
133
+ name=IterationControlParam.SKIP_ITERATION.value,
134
+ )
135
+ self.skip_iteration.ui_options = {"display_name": "Skip to Next Iteration"}
136
+ self.add_parameter(self.skip_iteration)
137
+
138
+ self.break_loop = ControlParameterInput(
139
+ tooltip="Break out of loop immediately",
140
+ name=IterationControlParam.BREAK_LOOP.value,
141
+ )
142
+ self.break_loop.ui_options = {"display_name": "Break Out of Loop"}
143
+ self.add_parameter(self.break_loop)
144
+
112
145
  self.results = Parameter(
113
146
  name="results",
114
147
  tooltip="Collected results from all iterations",
@@ -120,7 +153,15 @@ class BaseIterativeNodeGroup(SubflowNodeGroup):
120
153
  # Track right parameters for UI layout
121
154
  if "right_parameters" not in self.metadata:
122
155
  self.metadata["right_parameters"] = []
123
- self.metadata["right_parameters"].extend(["new_item_to_add", "results"])
156
+ self.metadata["right_parameters"].extend(
157
+ [
158
+ IterationControlParam.LOOP_COMPLETE.value,
159
+ "new_item_to_add",
160
+ IterationControlParam.SKIP_ITERATION.value,
161
+ IterationControlParam.BREAK_LOOP.value,
162
+ "results",
163
+ ]
164
+ )
124
165
 
125
166
  def after_value_set(self, parameter: Parameter, value: Any) -> None:
126
167
  """Handle parameter value changes."""
@@ -130,6 +171,17 @@ class BaseIterativeNodeGroup(SubflowNodeGroup):
130
171
  run_in_order = EXECUTION_MODE_VALUE_LOOKUP.get(value, True)
131
172
  self.set_parameter_value("run_in_order", run_in_order)
132
173
 
174
+ # Hide or show skip/break controls based on execution mode
175
+ # Skip and Break are only supported in sequential mode (run_in_order=True)
176
+ if run_in_order:
177
+ # Show controls when running sequentially
178
+ self.show_parameter_by_name(self.skip_iteration.name)
179
+ self.show_parameter_by_name(self.break_loop.name)
180
+ else:
181
+ # Hide controls when running in parallel (not supported)
182
+ self.hide_parameter_by_name(self.skip_iteration.name)
183
+ self.hide_parameter_by_name(self.break_loop.name)
184
+
133
185
  @abstractmethod
134
186
  def _get_iteration_items(self) -> list[Any]:
135
187
  """Get the list of items to iterate over.
@@ -0,0 +1,152 @@
1
+ """HuggingFace parameter class that supports repo + variant/subfolder selection."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+
6
+ from huggingface_hub.constants import HF_HUB_CACHE
7
+
8
+ from griptape_nodes.exe_types.node_types import BaseNode
9
+ from griptape_nodes.exe_types.param_components.huggingface.huggingface_model_parameter import HuggingFaceModelParameter
10
+
11
+ logger = logging.getLogger("griptape_nodes")
12
+
13
+
14
+ def _get_repo_cache_path(repo_id: str) -> Path | None:
15
+ """Get the cache path for a repo if it exists."""
16
+ cache_path = Path(HF_HUB_CACHE)
17
+ if not cache_path.exists():
18
+ return None
19
+
20
+ # Convert repo_id to cache folder format: owner/repo -> models--owner--repo
21
+ folder_name = f"models--{repo_id.replace('/', '--')}"
22
+ repo_path = cache_path / folder_name
23
+
24
+ if repo_path.exists():
25
+ return repo_path
26
+ return None
27
+
28
+
29
+ def _get_snapshot_path(repo_path: Path) -> Path | None:
30
+ """Get the latest snapshot path for a repo."""
31
+ snapshots_dir = repo_path / "snapshots"
32
+ if not snapshots_dir.exists():
33
+ return None
34
+
35
+ snapshots = [p for p in snapshots_dir.iterdir() if p.is_dir()]
36
+ if not snapshots:
37
+ return None
38
+
39
+ # Return the most recent snapshot
40
+ return max(snapshots, key=lambda p: p.stat().st_mtime)
41
+
42
+
43
+ def _list_variants_in_cache(repo_id: str, variants: list[str]) -> list[tuple[str, str, str]]:
44
+ """List (repo_id, variant, revision) tuples for variants that exist in cache.
45
+
46
+ Args:
47
+ repo_id: The HuggingFace repo ID (e.g., "Lightricks/LTX-2")
48
+ variants: List of variant/subfolder names to check for (e.g., ["ltx-2-19b-dev", "ltx-2-19b-dev-fp8"])
49
+
50
+ Returns:
51
+ List of (repo_id, variant, revision) tuples for variants found in cache
52
+ """
53
+ repo_path = _get_repo_cache_path(repo_id)
54
+ if repo_path is None:
55
+ return []
56
+
57
+ snapshot_path = _get_snapshot_path(repo_path)
58
+ if snapshot_path is None:
59
+ return []
60
+
61
+ revision = snapshot_path.name
62
+ results = []
63
+
64
+ for variant in variants:
65
+ # Check for variant as a directory (subfolder model structure)
66
+ variant_dir_path = snapshot_path / variant
67
+ if variant_dir_path.exists() and variant_dir_path.is_dir():
68
+ results.append((repo_id, variant, revision))
69
+ continue
70
+
71
+ # Check for variant as a .safetensors file (single-file model structure)
72
+ variant_file_path = snapshot_path / f"{variant}.safetensors"
73
+ if variant_file_path.exists() and variant_file_path.is_file():
74
+ results.append((repo_id, variant, revision))
75
+
76
+ return results
77
+
78
+
79
+ class HuggingFaceRepoVariantParameter(HuggingFaceModelParameter):
80
+ """Parameter class for selecting a variant/subfolder within a HuggingFace repo.
81
+
82
+ Use this when a single repo contains multiple model variants as subfolders.
83
+ For example, Lightricks/LTX-2 contains:
84
+ - ltx-2-19b-dev
85
+ - ltx-2-19b-dev-fp8
86
+ - ltx-2-19b-dev-fp4
87
+ """
88
+
89
+ def __init__(
90
+ self,
91
+ node: BaseNode,
92
+ repo_id: str,
93
+ variants: list[str],
94
+ parameter_name: str = "model",
95
+ ):
96
+ """Initialize the parameter.
97
+
98
+ Args:
99
+ node: The node this parameter belongs to
100
+ repo_id: The HuggingFace repo ID (e.g., "Lightricks/LTX-2")
101
+ variants: List of variant/subfolder names (e.g., ["ltx-2-19b-dev", "ltx-2-19b-dev-fp8"])
102
+ parameter_name: Name of the parameter (default: "model")
103
+ """
104
+ super().__init__(node, parameter_name)
105
+ self._repo_id = repo_id
106
+ self._variants = variants
107
+ self.refresh_parameters()
108
+
109
+ @classmethod
110
+ def _repo_variant_to_key(cls, repo_id: str, variant: str) -> str:
111
+ """Convert repo_id and variant to a display key."""
112
+ return f"{repo_id}/{variant}"
113
+
114
+ @classmethod
115
+ def _key_to_repo_variant(cls, key: str) -> tuple[str, str]:
116
+ """Parse a display key back to repo_id and variant.
117
+
118
+ Key format: "owner/repo/variant"
119
+ """
120
+ parts = key.rsplit("/", 1)
121
+ if len(parts) == 2: # noqa: PLR2004
122
+ return parts[0], parts[1]
123
+ # Fallback: treat entire key as repo_id with empty variant
124
+ return key, ""
125
+
126
+ def fetch_repo_revisions(self) -> list[tuple[str, str]]:
127
+ """Fetch available variants from cache.
128
+
129
+ Returns list of (display_key, revision) tuples where display_key is "repo_id/variant".
130
+ """
131
+ variant_revisions = _list_variants_in_cache(self._repo_id, self._variants)
132
+ return [
133
+ (self._repo_variant_to_key(repo_id, variant), revision) for repo_id, variant, revision in variant_revisions
134
+ ]
135
+
136
+ def get_download_commands(self) -> list[str]:
137
+ """Return download commands for the repo."""
138
+ return [f'huggingface-cli download "{self._repo_id}"']
139
+
140
+ def get_download_models(self) -> list[str]:
141
+ """Return list of model names for download."""
142
+ return [self._repo_id]
143
+
144
+ def get_repo_variant_revision(self) -> tuple[str, str, str]:
145
+ """Get the selected repo_id, variant, and revision.
146
+
147
+ Returns:
148
+ Tuple of (repo_id, variant, revision)
149
+ """
150
+ repo_key, revision = self.get_repo_revision()
151
+ repo_id, variant = self._key_to_repo_variant(repo_key)
152
+ return repo_id, variant, revision
@@ -33,6 +33,7 @@ class MCPServerConfig(TypedDict, total=False):
33
33
  terminate_on_close: bool # Session termination behavior for streamable HTTP transport
34
34
  description: str | None
35
35
  capabilities: list[str] | None
36
+ rules: str | None # Optional rules for this MCP server as a single string
36
37
 
37
38
 
38
39
  class MCPServerCapability(TypedDict):
@@ -162,6 +163,7 @@ class CreateMCPServerRequest(RequestPayload):
162
163
  description: Optional description of the server
163
164
  capabilities: List of server capabilities
164
165
  enabled: Whether the server is enabled by default
166
+ rules: Optional rules for this MCP server as a single string
165
167
  """
166
168
 
167
169
  name: str
@@ -186,6 +188,7 @@ class CreateMCPServerRequest(RequestPayload):
186
188
  # Common fields
187
189
  description: str | None = None
188
190
  capabilities: list[str] | None = None
191
+ rules: str | None = None
189
192
 
190
193
 
191
194
  @dataclass
@@ -224,6 +227,7 @@ class UpdateMCPServerRequest(RequestPayload):
224
227
  terminate_on_close: Updated session termination behavior for streamable HTTP transport
225
228
  description: Updated description of the server
226
229
  capabilities: Updated list of server capabilities
230
+ rules: Updated rules for this MCP server as a single string
227
231
  """
228
232
 
229
233
  name: str
@@ -249,6 +253,7 @@ class UpdateMCPServerRequest(RequestPayload):
249
253
  # Common fields
250
254
  description: str | None = None
251
255
  capabilities: list[str] | None = None
256
+ rules: str | None = None
252
257
 
253
258
 
254
259
  @dataclass
@@ -389,49 +389,107 @@ class AgentManager:
389
389
  driver = self.thread_storage_driver.get_conversation_memory_driver(thread_id)
390
390
  conversation_memory = ConversationMemory(conversation_memory_driver=driver)
391
391
 
392
+ # Collect MCP server rulesets
393
+ mcp_rulesets = self._collect_mcp_server_rulesets(additional_mcp_servers)
394
+
395
+ # Get default rulesets
396
+ default_rulesets = self._get_default_rulesets()
397
+
392
398
  return Agent(
393
399
  prompt_driver=self.prompt_driver,
394
400
  conversation_memory=conversation_memory,
395
401
  tools=tools,
396
402
  output_schema=output_schema,
397
- rulesets=[
398
- Ruleset(
399
- name="generated_image_urls",
400
- rules=[
401
- Rule("Do not hallucinate generated_image_urls."),
402
- Rule("Only set generated_image_urls with images generated with your tools."),
403
- ],
404
- ),
405
- # Note: Griptape's MCPTool automatically wraps arguments in a 'values' key, but our MCP server
406
- # expects arguments directly. This ruleset instructs the agent to provide arguments without
407
- # the 'values' wrapper to avoid validation errors. If MCPTool behavior changes in the future,
408
- # this ruleset may need to be updated or removed.
409
- Ruleset(
410
- name="mcp_tool_usage",
411
- rules=[
412
- Rule(
413
- "When calling MCP tools (mcpGriptapeNodes), provide arguments directly without wrapping them in a 'values' key. "
414
- "For example, use {'node_type': 'FluxImageGeneration', 'node_name': 'MyNode'} not {'values': {'node_type': 'FluxImageGeneration'}}."
415
- ),
416
- ],
417
- ),
418
- Ruleset(
419
- name="node_rulesets",
420
- rules=[
421
- Rule(
422
- "When asked to create a node, use ListNodeTypesInLibraryRequest or GetAllInfoForAllLibrariesRequest to check available node types and find the appropriate node."
423
- ),
424
- Rule(
425
- "When matching user requests to node types, account for variations: users may include spaces (e.g., 'Image Generation' vs 'ImageGeneration') or reorder words (e.g., 'Generate Image' vs 'Image Generation'). Match based on the words present, not exact spelling."
426
- ),
427
- Rule(
428
- "If you cannot determine the correct node type or node creation fails, ask the user for clarification."
429
- ),
430
- ],
431
- ),
432
- ],
403
+ rulesets=[*default_rulesets, *mcp_rulesets],
433
404
  )
434
405
 
406
+ @staticmethod
407
+ def _create_ruleset_from_rules_string(rules_string: str | None, server_name: str) -> Ruleset | None:
408
+ """Create a Ruleset from a rules string for an MCP server.
409
+
410
+ Args:
411
+ rules_string: Optional rules string for the MCP server
412
+ server_name: Name of the MCP server
413
+
414
+ Returns:
415
+ Ruleset with a single Rule containing the rules string, or None if rules_string is None/empty
416
+ """
417
+ if not rules_string or not rules_string.strip():
418
+ return None
419
+
420
+ rules_text = rules_string.strip()
421
+ ruleset_name = f"mcp_{server_name}_rules"
422
+
423
+ return Ruleset(name=ruleset_name, rules=[Rule(rules_text)])
424
+
425
+ def _collect_mcp_server_rulesets(self, additional_mcp_servers: list[str] | None) -> list[Ruleset]:
426
+ """Collect rulesets from MCP server configurations."""
427
+ mcp_rulesets = []
428
+
429
+ # Collect server names to get rules for
430
+ server_names_to_check = []
431
+ if additional_mcp_servers:
432
+ server_names_to_check.extend(additional_mcp_servers)
433
+
434
+ # Get rules for all MCP servers
435
+ if not server_names_to_check:
436
+ return mcp_rulesets
437
+
438
+ enabled_result = GriptapeNodes.handle_request(GetEnabledMCPServersRequest())
439
+ if not isinstance(enabled_result, GetEnabledMCPServersResultSuccess):
440
+ return mcp_rulesets
441
+
442
+ for server_name in server_names_to_check:
443
+ if server_name not in enabled_result.servers:
444
+ continue
445
+
446
+ server_config = enabled_result.servers[server_name]
447
+ rules_string = server_config.get("rules")
448
+ ruleset = AgentManager._create_ruleset_from_rules_string(rules_string, server_name)
449
+ if ruleset is not None:
450
+ mcp_rulesets.append(ruleset)
451
+
452
+ return mcp_rulesets
453
+
454
+ def _get_default_rulesets(self) -> list[Ruleset]:
455
+ """Get the default rulesets for agents."""
456
+ return [
457
+ Ruleset(
458
+ name="generated_image_urls",
459
+ rules=[
460
+ Rule("Do not hallucinate generated_image_urls."),
461
+ Rule("Only set generated_image_urls with images generated with your tools."),
462
+ ],
463
+ ),
464
+ # Note: Griptape's MCPTool automatically wraps arguments in a 'values' key, but our MCP server
465
+ # expects arguments directly. This ruleset instructs the agent to provide arguments without
466
+ # the 'values' wrapper to avoid validation errors. If MCPTool behavior changes in the future,
467
+ # this ruleset may need to be updated or removed.
468
+ Ruleset(
469
+ name="mcp_tool_usage",
470
+ rules=[
471
+ Rule(
472
+ "When calling MCP tools (mcpGriptapeNodes), provide arguments directly without wrapping them in a 'values' key. "
473
+ "For example, use {'node_type': 'FluxImageGeneration', 'node_name': 'MyNode'} not {'values': {'node_type': 'FluxImageGeneration'}}."
474
+ ),
475
+ ],
476
+ ),
477
+ Ruleset(
478
+ name="node_rulesets",
479
+ rules=[
480
+ Rule(
481
+ "When asked to create a node, use ListNodeTypesInLibraryRequest or GetAllInfoForAllLibrariesRequest to check available node types and find the appropriate node."
482
+ ),
483
+ Rule(
484
+ "When matching user requests to node types, account for variations: users may include spaces (e.g., 'Image Generation' vs 'ImageGeneration') or reorder words (e.g., 'Generate Image' vs 'Image Generation'). Match based on the words present, not exact spelling."
485
+ ),
486
+ Rule(
487
+ "If you cannot determine the correct node type or node creation fails, ask the user for clarification."
488
+ ),
489
+ ],
490
+ ),
491
+ ]
492
+
435
493
  def _validate_thread_for_run(self, thread_id: str | None) -> str:
436
494
  """Validate and return thread_id for agent run, or raise ValueError."""
437
495
  if thread_id is None:
@@ -510,10 +568,7 @@ class AgentManager:
510
568
  additional_tools = []
511
569
 
512
570
  try:
513
- app = GriptapeNodes()
514
-
515
- enabled_request = GetEnabledMCPServersRequest()
516
- enabled_result = app.handle_request(enabled_request)
571
+ enabled_result = GriptapeNodes.handle_request(GetEnabledMCPServersRequest())
517
572
 
518
573
  if not isinstance(enabled_result, GetEnabledMCPServersResultSuccess):
519
574
  msg = f"Failed to get enabled MCP servers for additional tools: {enabled_result}. Agent will continue with default MCP tool only."
@@ -2319,11 +2319,12 @@ class FlowManager:
2319
2319
  start_node_parameter_value_commands.append(param_value_command)
2320
2320
 
2321
2321
  # Create parameter command for start node (following single-node pattern exactly)
2322
+ # Use source parameter's default value to ensure type-safe propagation during connection creation
2322
2323
  add_param_request = AddParameterToNodeRequest(
2323
2324
  node_name=start_node_name,
2324
2325
  parameter_name=param_name,
2325
2326
  type=source_param.output_type,
2326
- default_value=None,
2327
+ default_value=source_param.default_value,
2327
2328
  tooltip=f"Parameter {target_parameter_name} from node {target_node_name} in packaged flow",
2328
2329
  initial_setup=True,
2329
2330
  )
@@ -127,6 +127,7 @@ class MCPManager:
127
127
  "terminate_on_close": "terminate_on_close",
128
128
  "description": "description",
129
129
  "capabilities": "capabilities",
130
+ "rules": "rules",
130
131
  }
131
132
 
132
133
  # Update fields that are not None
@@ -213,6 +214,7 @@ class MCPManager:
213
214
  # Common fields
214
215
  description=request.description,
215
216
  capabilities=request.capabilities or [],
217
+ rules=request.rules,
216
218
  )
217
219
 
218
220
  servers.append(server_config)
@@ -97,6 +97,7 @@ class MCPServerConfig(BaseModel):
97
97
  # Common fields
98
98
  description: str | None = Field(default=None, description="Optional description of what this MCP server provides")
99
99
  capabilities: list[str] = Field(default_factory=list, description="List of capabilities this MCP server provides")
100
+ rules: str | None = Field(default=None, description="Optional rules for this MCP server as a single string.")
100
101
 
101
102
  def __str__(self) -> str:
102
103
  return f"{self.name} ({'enabled' if self.enabled else 'disabled'})"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: griptape-nodes
3
- Version: 0.67.0
3
+ Version: 0.68.1
4
4
  Summary: Add your description here
5
5
  Requires-Dist: griptape>=1.8.12
6
6
  Requires-Dist: pydantic>=2.10.6
@@ -9,7 +9,7 @@ griptape_nodes/app/app.py,sha256=48q09c2xDjvbf4Qa1_buQhnPjR2jqkXo428GeBRkw8g,160
9
9
  griptape_nodes/app/watch.py,sha256=hKVP_SuV9C17bH1h9o4uIVTKH-IL_-0iyHaNYmILTWU,1594
10
10
  griptape_nodes/bootstrap/__init__.py,sha256=ENv3SIzQ9TtlRrg1y4e4CnoBpJaFpFSkNpTFBV8X5Ls,25
11
11
  griptape_nodes/bootstrap/utils/__init__.py,sha256=tlNEApJLZazcBNhxkTdup4URwznnz4nZxjSaRfFrTBM,31
12
- griptape_nodes/bootstrap/utils/python_subprocess_executor.py,sha256=JUCWV1G8oz1LtBhiLHoTtaYPG14zsYoj3bDvz3cWBQk,4466
12
+ griptape_nodes/bootstrap/utils/python_subprocess_executor.py,sha256=_XUQxrxt8n3vPv3_UHFlaAFRSODLhzKmAK5iNcf92lg,4847
13
13
  griptape_nodes/bootstrap/workflow_executors/__init__.py,sha256=pyjN81-eLtjyECFYLXOtMCixiiI9qBi5yald86iM7Ek,34
14
14
  griptape_nodes/bootstrap/workflow_executors/local_session_workflow_executor.py,sha256=Zqb9yQe-3PxcaCEpLVcq6CQPn5iwjl5TJjQehm1fvZ0,16652
15
15
  griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py,sha256=jp81z4QFSuQsXigKN0h2Ywo0Tr_whbxFG-TJ7MCrS2Q,10263
@@ -42,7 +42,7 @@ griptape_nodes/common/macro_parser/matching.py,sha256=hnlPIihz8W1TGnfq4anbBGciwg
42
42
  griptape_nodes/common/macro_parser/parsing.py,sha256=_37L3lCQFeetamWIiHT0C7eW1E3Wyx5V8y8OWzIUw_g,7428
43
43
  griptape_nodes/common/macro_parser/resolution.py,sha256=30_bAIRMKWw-kM3E1ne-my16xT1Wd734JMmZSOZtNPA,6937
44
44
  griptape_nodes/common/macro_parser/segments.py,sha256=r-WbZas1nSc-PlEdAZ7DycNoD6IUf3R_nhGo3T9vIa8,1010
45
- griptape_nodes/common/node_executor.py,sha256=BOAeOPHul0qsbyT1HCnw2eotRZELdyH7PelwisBL3wY,137796
45
+ griptape_nodes/common/node_executor.py,sha256=zaVCw4BxurjwhVhCA9ujGXw2xzC4-Tkvjo3SFtY0S7Y,147191
46
46
  griptape_nodes/common/project_templates/__init__.py,sha256=5DP6Sa6ClsdDcjD30pkK0iTTysTgGdRKEkeY7ztHZyc,1541
47
47
  griptape_nodes/common/project_templates/default_project_template.py,sha256=PJFPfziH0EOGVxU5L0wZnc21bVVweW4S7wcnGPF45Co,3120
48
48
  griptape_nodes/common/project_templates/defaults/README.md,sha256=1ou6_OFi7-t86nj-7tulW1vxNMtsvkJmNxr6SseG8hA,1080
@@ -67,8 +67,8 @@ griptape_nodes/exe_types/base_iterative_nodes.py,sha256=z2u4Pb9iqy2Zl3gz_LB7k6hm
67
67
  griptape_nodes/exe_types/connections.py,sha256=HcM_fwWivMsXk5q6K7ZBSJpmK9THQwX_xLS_daF0PDQ,21737
68
68
  griptape_nodes/exe_types/core_types.py,sha256=XrMT0Rf0bPZBcx06f1GUTi5G_IwLgdyzGjBKkUwRh3M,101505
69
69
  griptape_nodes/exe_types/flow.py,sha256=2iAh3vN5wnMVxTc5jcPBg9TSiASq1DGIm5jgpO9Bdq4,5732
70
- griptape_nodes/exe_types/node_groups/__init__.py,sha256=N-x5jU8XthYShF9TYnP90pd9DfUE5toiXb5lEoGFL-A,298
71
- griptape_nodes/exe_types/node_groups/base_iterative_node_group.py,sha256=1q8-3Rwlx6bXNDte3K0WMRrrddGfq4nD57W_eKmBE94,7411
70
+ griptape_nodes/exe_types/node_groups/__init__.py,sha256=u91XCSR4OAAr6x5kYq8i14mtHEWGQF_fSDUsWHmiOdY,346
71
+ griptape_nodes/exe_types/node_groups/base_iterative_node_group.py,sha256=sPPy3Psl-Ku_R46upWm_8hIGFLNPeLpUBN_Sm4RzXzg,9655
72
72
  griptape_nodes/exe_types/node_groups/base_node_group.py,sha256=dJofcmERzYQSQ2KB7dC0rOKjbZKW8Ko8eD6r0smBu48,3209
73
73
  griptape_nodes/exe_types/node_groups/subflow_node_group.py,sha256=UlZS95TKUCWiHgg6z4Wm1t9xXfUjWhk0L8pB5cXMcV0,48361
74
74
  griptape_nodes/exe_types/node_types.py,sha256=pPljG84zJ6Wd6yzcRjnK0n1805b-Jyzxx7-NFox66QE,84227
@@ -82,6 +82,7 @@ griptape_nodes/exe_types/param_components/huggingface/__init__.py,sha256=YeVFck-
82
82
  griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py,sha256=rEGN4kvblSAlRh7pD73U__wzAtVzDYWYlDC-k1nKtyM,6395
83
83
  griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py,sha256=c61de3mbkc5FvVkf5vubtD0XfThDInTfibAtYhZKcKA,4265
84
84
  griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_parameter.py,sha256=zcBRQP0eZJloSdPQwJFw89B6QDHECnrosRZUy8mE2fM,1401
85
+ griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_variant_parameter.py,sha256=FaaDZ4dvZJCnpqSOKfJ5nR1klgl07JZto2A7gxhUi3o,5290
85
86
  griptape_nodes/exe_types/param_components/huggingface/huggingface_utils.py,sha256=cE0Ht81kDcZjlN_2VoRirCA4zMlrG9GFlcptn-gC2tU,4696
86
87
  griptape_nodes/exe_types/param_components/log_parameter.py,sha256=K6XRWVIpL9MtQ6IZG3hYYwIv3sNqFxONxbVnKcA_Oes,4160
87
88
  griptape_nodes/exe_types/param_components/progress_bar_component.py,sha256=GrAFTOrLNAx6q9zfOqiioPTG_NLHKhTe9NiZOo6zGSc,1949
@@ -127,7 +128,7 @@ griptape_nodes/retained_mode/events/flow_events.py,sha256=UcRHfaubb1GpDFSPg-5HRW
127
128
  griptape_nodes/retained_mode/events/generate_request_payload_schemas.py,sha256=PQ0QRZAUBljSIGjB8QhYUeRmSrfmLsJ6XVcygJKi13I,1102
128
129
  griptape_nodes/retained_mode/events/library_events.py,sha256=4NUc3uoLgofo1X9nrCVl2zPX3hsnk06Oo4Op0AQKA5o,36229
129
130
  griptape_nodes/retained_mode/events/logger_events.py,sha256=jYlxzPomgCsJuPtJ0znWBhD8QJfC8qC4xfChDiuVuyg,705
130
- griptape_nodes/retained_mode/events/mcp_events.py,sha256=51OlMZVcabdcIxBb_ojvrHtq2jAzZA31pooPBKaApyg,10734
131
+ griptape_nodes/retained_mode/events/mcp_events.py,sha256=fs83jAQamhwNC9Zt8UNkBKd6iTids8531V_RMnGj__o,11008
131
132
  griptape_nodes/retained_mode/events/model_events.py,sha256=jXm-v-_Uxysn4MJ7a80_uIphrxTHgua3BGgHGyy3T_Y,9232
132
133
  griptape_nodes/retained_mode/events/node_events.py,sha256=vpILjBkgrPzF_KSow2EJCwcU-tDD78WEWM2DAAzhhiE,37720
133
134
  griptape_nodes/retained_mode/events/object_events.py,sha256=cJaqEU73Lzf1RRxJrFqEpl8eTr-gDhKpXKywJ-vVCJQ,2631
@@ -144,7 +145,7 @@ griptape_nodes/retained_mode/events/variable_events.py,sha256=fnl_sY8GWI_R4UJQTE
144
145
  griptape_nodes/retained_mode/events/workflow_events.py,sha256=TFJBYAERYZ6gLGGkLFUNxEkPP0uub7RXubPjvgy0ark,24915
145
146
  griptape_nodes/retained_mode/griptape_nodes.py,sha256=0pv6TmHqcaNPb1Rd4XJzhYMLtxrl7eLQLpZsDSvrhz0,20178
146
147
  griptape_nodes/retained_mode/managers/__init__.py,sha256=OTXysKusqYCQeAYwnVj4PbE3MxvAUTq9xOZT6vUE3JA,24
147
- griptape_nodes/retained_mode/managers/agent_manager.py,sha256=B7O9lczy83AJjRwdHUFgs6I0Pd3ANNbzPYhf70bsg7c,26311
148
+ griptape_nodes/retained_mode/managers/agent_manager.py,sha256=FOKrcHEBw6M-RmfWTWEQmV-9iy9cD1zHiiDUDMqJHE4,28307
148
149
  griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py,sha256=LyWzHQKmyDh-2zh2PIahdYyAthf_rMYs4zvhtVo-LsU,3423
149
150
  griptape_nodes/retained_mode/managers/config_manager.py,sha256=74AMeaCS0eOeFbzMk5I54MtB7-SNQbwlx_-O6zBBQRo,26739
150
151
  griptape_nodes/retained_mode/managers/context_manager.py,sha256=eb44_CAZhCg2iYIoodlAPpYc67tG3sHyq9dPNoiq_1s,23031
@@ -200,9 +201,9 @@ griptape_nodes/retained_mode/managers/fitness_problems/workflows/node_type_not_f
200
201
  griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_not_found_problem.py,sha256=kPhaIeDxp0duxJBJLKFiYp269kEDsCx3uVLnkKbF2gk,895
201
202
  griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_problem.py,sha256=NLAoF_x7g5tL70qZtPVFI8xeM1jPZNsm7FKt9nkuGBc,542
202
203
  griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_schema_version_problem.py,sha256=rUnbBBnGwgq5WTVPt2G4pGP3gVPoX5xvCdTkRPBmP04,1328
203
- griptape_nodes/retained_mode/managers/flow_manager.py,sha256=0Z8hNjfwlnJgQGOsQf3MEvQnkPLeQBXe2ZuixUov2QY,220137
204
+ griptape_nodes/retained_mode/managers/flow_manager.py,sha256=fY0xCXHiualDwVQJnNlPpMUnLz5bYWvr5D1_UvOyNVM,220269
204
205
  griptape_nodes/retained_mode/managers/library_manager.py,sha256=paogGavZj2MDOYvfUklhjORQg16YdpVGBxBncVSxtR0,192265
205
- griptape_nodes/retained_mode/managers/mcp_manager.py,sha256=Ezmn5_48r4JWYe-tFGqmw9dXLvvTiO1rw9b4MNCkw0U,15955
206
+ griptape_nodes/retained_mode/managers/mcp_manager.py,sha256=AEHG1SqFc3g5hOImOHaH7ZjOkmgNAavmQgG9osXVGkE,16018
206
207
  griptape_nodes/retained_mode/managers/model_manager.py,sha256=3lj2X8vIvDSERPtR2VEXNFEWy_D8H6muxRvD-PEx8U8,44845
207
208
  griptape_nodes/retained_mode/managers/node_manager.py,sha256=KbThJ5fvBTuF9EEIqBT37Rop41_7o-irb_-KY8hkXVY,225038
208
209
  griptape_nodes/retained_mode/managers/object_manager.py,sha256=xABsjpE1_HLEIFwYj60zDOLi18EqrYUBuNaj37lUcH4,12921
@@ -221,7 +222,7 @@ griptape_nodes/retained_mode/managers/resource_types/cpu_resource.py,sha256=YHjU
221
222
  griptape_nodes/retained_mode/managers/resource_types/os_resource.py,sha256=HHJ-kgZB2BsnvU0rp-CQlM1xTJm3OZ3Dh-NpB01gGG8,3439
222
223
  griptape_nodes/retained_mode/managers/secrets_manager.py,sha256=knoHlbR-VrkeNULYjq72cHnhbCeupkKjtUT0EfBH8LQ,7743
223
224
  griptape_nodes/retained_mode/managers/session_manager.py,sha256=u3OEir9rjwG7GFM4MA5crKwcwq6F-lCUKlFzxlFU4co,14085
224
- griptape_nodes/retained_mode/managers/settings.py,sha256=BnIOYxsaE834_oIg6C_oRqFz0MDv8qSqLtwEGpiJZvA,12054
225
+ griptape_nodes/retained_mode/managers/settings.py,sha256=UxYA5GzHqBPFMNwKqq2Np4nT8ZciBahpDIFrhQKNMm0,12168
225
226
  griptape_nodes/retained_mode/managers/static_files_manager.py,sha256=rWeI5mO8CCdEHx1odo4z4pxfTy33ggzCcUwkRj67aNM,12408
226
227
  griptape_nodes/retained_mode/managers/sync_manager.py,sha256=dF6nAAy1DLDQydefx7v3EtPCBiCrk4vURMum57WfmK8,21448
227
228
  griptape_nodes/retained_mode/managers/user_manager.py,sha256=JAOOKDhUfIbj0CJ5EHRBcplmxheQTzeogbvGO23VqXQ,4508
@@ -275,7 +276,7 @@ griptape_nodes/version_compatibility/versions/v0_65_5/__init__.py,sha256=4UyspOV
275
276
  griptape_nodes/version_compatibility/versions/v0_65_5/flux_2_removed_parameters.py,sha256=jOlmY5kKHXh8HPzAUtvwMJqzD4bP7pkE--yHUb7jTRA,3305
276
277
  griptape_nodes/version_compatibility/versions/v0_7_0/__init__.py,sha256=IzPPmGK86h2swfGGTOHyVcBIlOng6SjgWQzlbf3ngmo,51
277
278
  griptape_nodes/version_compatibility/versions/v0_7_0/local_executor_argument_addition.py,sha256=Thx8acnbw5OychhwEEj9aFxvbPe7Wgn4V9ZmZ7KRZqc,2082
278
- griptape_nodes-0.67.0.dist-info/WHEEL,sha256=KSLUh82mDPEPk0Bx0ScXlWL64bc8KmzIPNcpQZFV-6E,79
279
- griptape_nodes-0.67.0.dist-info/entry_points.txt,sha256=qvevqd3BVbAV5TcantnAm0ouqaqYKhsRO3pkFymWLWM,82
280
- griptape_nodes-0.67.0.dist-info/METADATA,sha256=1YMtiTN8fY4LDSKw5JlmUaQpIK6ehIW-vHAZtLGdM3k,5374
281
- griptape_nodes-0.67.0.dist-info/RECORD,,
279
+ griptape_nodes-0.68.1.dist-info/WHEEL,sha256=eycQt0QpYmJMLKpE3X9iDk8R04v2ZF0x82ogq-zP6bQ,79
280
+ griptape_nodes-0.68.1.dist-info/entry_points.txt,sha256=qvevqd3BVbAV5TcantnAm0ouqaqYKhsRO3pkFymWLWM,82
281
+ griptape_nodes-0.68.1.dist-info/METADATA,sha256=c23iianw-Es7pl0SlHWlsx04xm_T9PMujNlLdfT2jDk,5374
282
+ griptape_nodes-0.68.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.22
2
+ Generator: uv 0.9.24
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any