griptape-nodes 0.64.10__py3-none-any.whl → 0.65.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 (55) hide show
  1. griptape_nodes/app/app.py +25 -5
  2. griptape_nodes/cli/commands/init.py +65 -54
  3. griptape_nodes/cli/commands/libraries.py +92 -85
  4. griptape_nodes/cli/commands/self.py +121 -0
  5. griptape_nodes/common/node_executor.py +2142 -101
  6. griptape_nodes/exe_types/base_iterative_nodes.py +1004 -0
  7. griptape_nodes/exe_types/connections.py +114 -19
  8. griptape_nodes/exe_types/core_types.py +225 -7
  9. griptape_nodes/exe_types/flow.py +3 -3
  10. griptape_nodes/exe_types/node_types.py +681 -225
  11. griptape_nodes/exe_types/param_components/README.md +414 -0
  12. griptape_nodes/exe_types/param_components/api_key_provider_parameter.py +200 -0
  13. griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py +2 -0
  14. griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py +79 -5
  15. griptape_nodes/exe_types/param_types/parameter_button.py +443 -0
  16. griptape_nodes/machines/control_flow.py +77 -38
  17. griptape_nodes/machines/dag_builder.py +148 -70
  18. griptape_nodes/machines/parallel_resolution.py +61 -35
  19. griptape_nodes/machines/sequential_resolution.py +11 -113
  20. griptape_nodes/retained_mode/events/app_events.py +1 -0
  21. griptape_nodes/retained_mode/events/base_events.py +16 -13
  22. griptape_nodes/retained_mode/events/connection_events.py +3 -0
  23. griptape_nodes/retained_mode/events/execution_events.py +35 -0
  24. griptape_nodes/retained_mode/events/flow_events.py +15 -2
  25. griptape_nodes/retained_mode/events/library_events.py +347 -0
  26. griptape_nodes/retained_mode/events/node_events.py +48 -0
  27. griptape_nodes/retained_mode/events/os_events.py +86 -3
  28. griptape_nodes/retained_mode/events/project_events.py +15 -1
  29. griptape_nodes/retained_mode/events/workflow_events.py +48 -1
  30. griptape_nodes/retained_mode/griptape_nodes.py +6 -2
  31. griptape_nodes/retained_mode/managers/config_manager.py +10 -8
  32. griptape_nodes/retained_mode/managers/event_manager.py +168 -0
  33. griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +2 -0
  34. griptape_nodes/retained_mode/managers/fitness_problems/libraries/old_xdg_location_warning_problem.py +43 -0
  35. griptape_nodes/retained_mode/managers/flow_manager.py +664 -123
  36. griptape_nodes/retained_mode/managers/library_manager.py +1143 -139
  37. griptape_nodes/retained_mode/managers/model_manager.py +2 -3
  38. griptape_nodes/retained_mode/managers/node_manager.py +148 -25
  39. griptape_nodes/retained_mode/managers/object_manager.py +3 -1
  40. griptape_nodes/retained_mode/managers/operation_manager.py +3 -1
  41. griptape_nodes/retained_mode/managers/os_manager.py +1158 -122
  42. griptape_nodes/retained_mode/managers/secrets_manager.py +2 -3
  43. griptape_nodes/retained_mode/managers/settings.py +21 -1
  44. griptape_nodes/retained_mode/managers/sync_manager.py +2 -3
  45. griptape_nodes/retained_mode/managers/workflow_manager.py +358 -104
  46. griptape_nodes/retained_mode/retained_mode.py +3 -3
  47. griptape_nodes/traits/button.py +44 -2
  48. griptape_nodes/traits/file_system_picker.py +2 -2
  49. griptape_nodes/utils/file_utils.py +101 -0
  50. griptape_nodes/utils/git_utils.py +1226 -0
  51. griptape_nodes/utils/library_utils.py +122 -0
  52. {griptape_nodes-0.64.10.dist-info → griptape_nodes-0.65.0.dist-info}/METADATA +2 -1
  53. {griptape_nodes-0.64.10.dist-info → griptape_nodes-0.65.0.dist-info}/RECORD +55 -47
  54. {griptape_nodes-0.64.10.dist-info → griptape_nodes-0.65.0.dist-info}/WHEEL +1 -1
  55. {griptape_nodes-0.64.10.dist-info → griptape_nodes-0.65.0.dist-info}/entry_points.txt +0 -0
@@ -181,10 +181,14 @@ class LoadLibraryMetadataFromFileResultSuccess(WorkflowNotAlteredMixin, ResultPa
181
181
  library_schema: The validated LibrarySchema object containing all metadata
182
182
  about the library including nodes, categories, and settings.
183
183
  file_path: The file path from which the library metadata was loaded.
184
+ git_remote: The git remote URL if the library is in a git repository, None otherwise.
185
+ git_ref: The current git reference (branch, tag, or commit) if the library is in a git repository, None otherwise.
184
186
  """
185
187
 
186
188
  library_schema: LibrarySchema
187
189
  file_path: str
190
+ git_remote: str | None
191
+ git_ref: str | None
188
192
 
189
193
 
190
194
  @dataclass
@@ -539,3 +543,346 @@ class LoadLibrariesResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
539
543
  @PayloadRegistry.register
540
544
  class LoadLibrariesResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
541
545
  """Library loading failed. Common causes: library loading errors, configuration issues, initialization failures."""
546
+
547
+
548
+ @dataclass
549
+ @PayloadRegistry.register
550
+ class CheckLibraryUpdateRequest(RequestPayload):
551
+ """Check if a library has updates available via git.
552
+
553
+ Use when: Checking for library updates, displaying update status,
554
+ validating library versions, implementing update notifications.
555
+
556
+ Args:
557
+ library_name: Name of the library to check for updates
558
+
559
+ Results: CheckLibraryUpdateResultSuccess (with update info) | CheckLibraryUpdateResultFailure (library not found, not a git repo, check error)
560
+ """
561
+
562
+ library_name: str
563
+
564
+
565
+ @dataclass
566
+ @PayloadRegistry.register
567
+ class CheckLibraryUpdateResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
568
+ """Library update check completed successfully.
569
+
570
+ Updates are detected based on either version changes or commit differences:
571
+ - If remote version > local version: update available (semantic versioning)
572
+ - If remote version < local version: no update (prevent regression)
573
+ - If versions equal: compare commits; if different, update available
574
+
575
+ Args:
576
+ has_update: True if an update is available, False otherwise
577
+ current_version: The current library version
578
+ latest_version: The latest library version from remote
579
+ git_remote: The git remote URL
580
+ git_ref: The current git reference (branch, tag, or commit)
581
+ local_commit: The local HEAD commit SHA (None if not a git repository)
582
+ remote_commit: The remote HEAD commit SHA (None if not available)
583
+ """
584
+
585
+ has_update: bool
586
+ current_version: str | None
587
+ latest_version: str | None
588
+ git_remote: str | None
589
+ git_ref: str | None
590
+ local_commit: str | None
591
+ remote_commit: str | None
592
+
593
+
594
+ @dataclass
595
+ @PayloadRegistry.register
596
+ class CheckLibraryUpdateResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
597
+ """Library update check failed. Common causes: library not found, not a git repository, git remote error, network error."""
598
+
599
+
600
+ @dataclass
601
+ @PayloadRegistry.register
602
+ class UpdateLibraryRequest(RequestPayload):
603
+ """Update a library to the latest version using the appropriate git strategy.
604
+
605
+ Automatically detects whether the library uses branch-based or tag-based workflow:
606
+ - Branch-based: Uses git fetch + git reset --hard (forces local to match remote)
607
+ - Tag-based: Uses git fetch --tags --force + git checkout (for moving tags like 'latest')
608
+
609
+ Use when: Applying library updates, synchronizing with remote changes,
610
+ updating library versions, implementing auto-update features.
611
+
612
+ Args:
613
+ library_name: Name of the library to update
614
+ overwrite_existing: If True, discard any uncommitted local changes. If False, fail if uncommitted changes exist (default: False)
615
+
616
+ Results: UpdateLibraryResultSuccess (with version info) | UpdateLibraryResultFailure (library not found, git error, update failure)
617
+ """
618
+
619
+ library_name: str
620
+ overwrite_existing: bool = False
621
+
622
+
623
+ @dataclass
624
+ @PayloadRegistry.register
625
+ class UpdateLibraryResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
626
+ """Library updated successfully.
627
+
628
+ Args:
629
+ old_version: The previous library version
630
+ new_version: The new library version after update
631
+ """
632
+
633
+ old_version: str
634
+ new_version: str
635
+
636
+
637
+ @dataclass
638
+ @PayloadRegistry.register
639
+ class UpdateLibraryResultFailure(ResultPayloadFailure):
640
+ """Library update failed. Common causes: library not found, not a git repository, git pull error, uncommitted changes.
641
+
642
+ Args:
643
+ retryable: If True, the operation can be retried with overwrite_existing=True
644
+ """
645
+
646
+ retryable: bool = False
647
+
648
+
649
+ @dataclass
650
+ @PayloadRegistry.register
651
+ class SwitchLibraryRefRequest(RequestPayload):
652
+ """Switch a library to a different git branch or tag.
653
+
654
+ Supports switching to both branches and tags (e.g., 'main', 'develop', 'latest', 'v1.0.0').
655
+
656
+ Use when: Switching between branches for development, testing different versions,
657
+ reverting to stable branches, checking out feature branches, or switching to specific tags.
658
+
659
+ Args:
660
+ library_name: Name of the library to switch
661
+ ref_name: Name of the branch or tag to switch to
662
+
663
+ Results: SwitchLibraryRefResultSuccess (with ref/version info) | SwitchLibraryRefResultFailure (library not found, git error, ref not found)
664
+ """
665
+
666
+ library_name: str
667
+ ref_name: str
668
+
669
+
670
+ @dataclass
671
+ @PayloadRegistry.register
672
+ class SwitchLibraryRefResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
673
+ """Library branch or tag switched successfully.
674
+
675
+ Args:
676
+ old_ref: The previous branch or tag name
677
+ new_ref: The new branch or tag name after switch
678
+ old_version: The previous library version
679
+ new_version: The new library version after switch
680
+ """
681
+
682
+ old_ref: str
683
+ new_ref: str
684
+ old_version: str
685
+ new_version: str
686
+
687
+
688
+ @dataclass
689
+ @PayloadRegistry.register
690
+ class SwitchLibraryRefResultFailure(ResultPayloadFailure):
691
+ """Library ref switch failed. Common causes: library not found, not a git repository, ref not found, git checkout error."""
692
+
693
+
694
+ @dataclass
695
+ @PayloadRegistry.register
696
+ class DownloadLibraryRequest(RequestPayload):
697
+ """Download a library from a git repository.
698
+
699
+ Use when: Installing new libraries from git repositories, downloading third-party libraries,
700
+ setting up development libraries, adding community libraries.
701
+
702
+ Args:
703
+ git_url: The git repository URL to clone
704
+ branch_tag_commit: Optional branch, tag, or commit to checkout (defaults to default branch)
705
+ target_directory_name: Optional name for the target directory (defaults to repository name)
706
+ download_directory: Optional parent directory path for download (defaults to workspace/libraries)
707
+ overwrite_existing: If True, delete existing directory before cloning (default: False)
708
+ auto_register: If True, automatically register library after download (default: True)
709
+ fail_on_exists: If True, fail with retryable error when directory exists and overwrite_existing=False.
710
+ If False, skip clone and register existing library (idempotent). (default: True)
711
+
712
+ Results: DownloadLibraryResultSuccess (with library info) | DownloadLibraryResultFailure (clone error, directory exists)
713
+ """
714
+
715
+ git_url: str
716
+ branch_tag_commit: str | None = None
717
+ target_directory_name: str | None = None
718
+ download_directory: str | None = None
719
+ overwrite_existing: bool = False
720
+ auto_register: bool = True
721
+ fail_on_exists: bool = True
722
+
723
+
724
+ @dataclass
725
+ @PayloadRegistry.register
726
+ class DownloadLibraryResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
727
+ """Library downloaded successfully.
728
+
729
+ Args:
730
+ library_name: Name of the library extracted from griptape_nodes_library.json
731
+ library_path: Full path where the library was downloaded
732
+ """
733
+
734
+ library_name: str
735
+ library_path: str
736
+
737
+
738
+ @dataclass
739
+ @PayloadRegistry.register
740
+ class DownloadLibraryResultFailure(ResultPayloadFailure):
741
+ """Library download failed. Common causes: invalid git URL, network error, target directory already exists, no griptape_nodes_library.json found.
742
+
743
+ Args:
744
+ retryable: If True, the operation can be retried with overwrite_existing=True
745
+ """
746
+
747
+ retryable: bool = False
748
+
749
+
750
+ @dataclass
751
+ @PayloadRegistry.register
752
+ class InstallLibraryDependenciesRequest(RequestPayload):
753
+ """Install dependencies for a library.
754
+
755
+ Use when: Installing or reinstalling dependencies for a library,
756
+ setting up a library's environment, updating dependencies after changes.
757
+
758
+ This operation:
759
+ 1. Loads library metadata from the file
760
+ 2. Gets library dependencies from metadata
761
+ 3. Initializes the library's virtual environment
762
+ 4. Installs pip dependencies specified in the library metadata
763
+ 5. Always installs dependencies without version checks
764
+
765
+ Args:
766
+ library_file_path: Path to the library JSON file
767
+
768
+ Results: InstallLibraryDependenciesResultSuccess | InstallLibraryDependenciesResultFailure
769
+ """
770
+
771
+ library_file_path: str
772
+
773
+
774
+ @dataclass
775
+ @PayloadRegistry.register
776
+ class InstallLibraryDependenciesResultSuccess(ResultPayloadSuccess):
777
+ """Library dependencies installed successfully.
778
+
779
+ Args:
780
+ library_name: Name of the library whose dependencies were installed
781
+ dependencies_installed: Number of dependencies that were installed
782
+ """
783
+
784
+ library_name: str
785
+ dependencies_installed: int
786
+
787
+
788
+ @dataclass
789
+ @PayloadRegistry.register
790
+ class InstallLibraryDependenciesResultFailure(ResultPayloadFailure):
791
+ """Library dependency installation failed. Common causes: library not found, no dependencies defined, venv initialization failed, pip install error."""
792
+
793
+
794
+ @dataclass
795
+ @PayloadRegistry.register
796
+ class SyncLibrariesRequest(RequestPayload):
797
+ """Sync all libraries to latest versions and ensure dependencies are installed.
798
+
799
+ Similar to `uv sync` - ensures workspace is in a consistent, up-to-date state.
800
+ This operation:
801
+ 1. Downloads missing libraries from git URLs specified in config
802
+ 2. Gets all registered libraries (including newly downloaded)
803
+ 3. Checks each library for available updates
804
+ 4. Updates libraries that have updates available
805
+ 5. Installs/updates dependencies for all libraries
806
+ 6. Returns comprehensive summary of changes
807
+
808
+ Use when: Updating workspace to latest versions, ensuring all libraries are
809
+ up-to-date, setting up development environment, periodic maintenance.
810
+
811
+ Args:
812
+ overwrite_existing: If True, discard any uncommitted local changes when updating libraries. If False, fail if uncommitted changes exist (default: False)
813
+
814
+ Results: SyncLibrariesResultSuccess (with summary) | SyncLibrariesResultFailure (sync errors)
815
+ """
816
+
817
+ overwrite_existing: bool = False
818
+
819
+
820
+ @dataclass
821
+ @PayloadRegistry.register
822
+ class SyncLibrariesResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
823
+ """Libraries synced successfully.
824
+
825
+ Args:
826
+ libraries_downloaded: Number of libraries that were downloaded from git URLs
827
+ libraries_checked: Number of libraries checked for updates
828
+ libraries_updated: Number of libraries that were updated
829
+ update_summary: Dict mapping library names to their update info (old_version -> new_version, or status for downloads)
830
+ """
831
+
832
+ libraries_downloaded: int
833
+ libraries_checked: int
834
+ libraries_updated: int
835
+ update_summary: dict[str, dict[str, str]]
836
+
837
+
838
+ @dataclass
839
+ @PayloadRegistry.register
840
+ class SyncLibrariesResultFailure(ResultPayloadFailure):
841
+ """Library sync failed. Common causes: git errors, network errors, dependency installation failures."""
842
+
843
+
844
+ @dataclass
845
+ @PayloadRegistry.register
846
+ class InspectLibraryRepoRequest(RequestPayload):
847
+ """Inspect a library's metadata from a git repository without downloading the full repository.
848
+
849
+ Performs a sparse checkout to fetch only the library JSON file, which is efficient for
850
+ previewing library information, checking compatibility, or validating git URLs before
851
+ full download.
852
+
853
+ Use when: Previewing library details, displaying library information in UI,
854
+ validating library compatibility, checking library versions remotely.
855
+
856
+ Args:
857
+ git_url: Git repository URL (supports GitHub shorthand like "user/repo")
858
+ ref: Branch, tag, or commit to inspect (defaults to "HEAD")
859
+
860
+ Results: InspectLibraryRepoResultSuccess (with library metadata) | InspectLibraryRepoResultFailure (invalid URL, network error, no library JSON found)
861
+ """
862
+
863
+ git_url: str
864
+ ref: str = "HEAD"
865
+
866
+
867
+ @dataclass
868
+ @PayloadRegistry.register
869
+ class InspectLibraryRepoResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
870
+ """Library repository inspection completed successfully.
871
+
872
+ Args:
873
+ library_schema: Complete library schema with all metadata (name, version, nodes, categories, dependencies, settings, etc.)
874
+ commit_sha: Git commit SHA that was inspected
875
+ git_url: Git URL that was inspected (normalized)
876
+ ref: Git reference that was inspected
877
+ """
878
+
879
+ library_schema: LibrarySchema
880
+ commit_sha: str
881
+ git_url: str
882
+ ref: str
883
+
884
+
885
+ @dataclass
886
+ @PayloadRegistry.register
887
+ class InspectLibraryRepoResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
888
+ """Library repository inspection failed. Common causes: invalid git URL, network error, no library JSON found, invalid JSON format."""
@@ -1060,3 +1060,51 @@ class DeleteNodeGroupResultFailure(ResultPayloadFailure):
1060
1060
 
1061
1061
  Common causes: NodeGroup not found, deletion cleanup failed, node is not a NodeGroup.
1062
1062
  """
1063
+
1064
+
1065
+ @dataclass
1066
+ @PayloadRegistry.register
1067
+ class MoveNodeToNewFlowRequest(RequestPayload):
1068
+ """Move a node from one flow to another flow.
1069
+
1070
+ Use when: Reorganizing nodes between flows, adding nodes to NodeGroup subflows,
1071
+ removing nodes from NodeGroup subflows. Node connections are preserved since
1072
+ connections are global and work across flows.
1073
+
1074
+ Args:
1075
+ node_name: Name of the node to move (None for current context)
1076
+ target_flow_name: Name of the destination flow
1077
+ source_flow_name: Name of the source flow (None to use node's current flow)
1078
+
1079
+ Results: MoveNodeToNewFlowResultSuccess | MoveNodeToNewFlowResultFailure (node not found, flow not found, node not in source flow)
1080
+ """
1081
+
1082
+ target_flow_name: str
1083
+ node_name: str | None = None
1084
+ source_flow_name: str | None = None
1085
+
1086
+
1087
+ @dataclass
1088
+ @PayloadRegistry.register
1089
+ class MoveNodeToNewFlowResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
1090
+ """Node moved successfully between flows. Connections preserved.
1091
+
1092
+ Args:
1093
+ node_name: Name of the node that was moved
1094
+ source_flow_name: Name of the flow the node was moved from
1095
+ target_flow_name: Name of the flow the node was moved to
1096
+ """
1097
+
1098
+ node_name: str
1099
+ source_flow_name: str
1100
+ target_flow_name: str
1101
+
1102
+
1103
+ @dataclass
1104
+ @PayloadRegistry.register
1105
+ class MoveNodeToNewFlowResultFailure(ResultPayloadFailure):
1106
+ """Node move failed.
1107
+
1108
+ Common causes: node not found, source flow not found, target flow not found,
1109
+ node not in source flow, node is a NodeGroup with subflow conflicts.
1110
+ """
@@ -1,5 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses import dataclass
2
4
  from enum import StrEnum
5
+ from typing import TYPE_CHECKING
3
6
 
4
7
  from griptape_nodes.retained_mode.events.base_events import (
5
8
  RequestPayload,
@@ -9,6 +12,9 @@ from griptape_nodes.retained_mode.events.base_events import (
9
12
  )
10
13
  from griptape_nodes.retained_mode.events.payload_registry import PayloadRegistry
11
14
 
15
+ if TYPE_CHECKING:
16
+ from griptape_nodes.retained_mode.events.project_events import MacroPath
17
+
12
18
 
13
19
  class ExistingFilePolicy(StrEnum):
14
20
  """Policy for handling existing files during write operations."""
@@ -31,6 +37,7 @@ class FileIOFailureReason(StrEnum):
31
37
  # Permission/access errors
32
38
  PERMISSION_DENIED = "permission_denied" # No read/write permission
33
39
  FILE_NOT_FOUND = "file_not_found" # File doesn't exist (read operations)
40
+ FILE_LOCKED = "file_locked" # File is locked by another process
34
41
 
35
42
  # Resource errors
36
43
  DISK_FULL = "disk_full" # Insufficient disk space
@@ -38,6 +45,7 @@ class FileIOFailureReason(StrEnum):
38
45
  # Path errors
39
46
  INVALID_PATH = "invalid_path" # Malformed or invalid path
40
47
  IS_DIRECTORY = "is_directory" # Path is a directory, not a file
48
+ MISSING_MACRO_VARIABLES = "missing_macro_variables" # MacroPath has unresolved required variables
41
49
 
42
50
  # Content errors
43
51
  ENCODING_ERROR = "encoding_error" # Text encoding/decoding failed
@@ -162,6 +170,8 @@ class ReadFileRequest(RequestPayload):
162
170
  workspace_only: If True, constrain to workspace directory. If False, allow system-wide access.
163
171
  If None, workspace constraints don't apply (e.g., cloud environments).
164
172
  TODO: Remove workspace_only parameter - see https://github.com/griptape-ai/griptape-nodes/issues/2753
173
+ should_transform_image_content_to_thumbnail: If True, convert image files to thumbnail data URLs.
174
+ If False, return raw image bytes. Default True for backwards compatibility.
165
175
 
166
176
  Results: ReadFileResultSuccess (with content) | ReadFileResultFailure (file not found, permission denied)
167
177
  """
@@ -170,6 +180,7 @@ class ReadFileRequest(RequestPayload):
170
180
  file_entry: FileSystemEntry | None = None
171
181
  encoding: str = "utf-8"
172
182
  workspace_only: bool | None = True # TODO: Remove - see https://github.com/griptape-ai/griptape-nodes/issues/2753
183
+ should_transform_image_content_to_thumbnail: bool = True
173
184
 
174
185
 
175
186
  @dataclass
@@ -310,6 +321,76 @@ class RenameFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
310
321
  failure_reason: FileIOFailureReason
311
322
 
312
323
 
324
+ @dataclass
325
+ @PayloadRegistry.register
326
+ class GetNextUnusedFilenameRequest(RequestPayload):
327
+ """Find the next available filename with auto-incrementing index (preview only - no file creation).
328
+
329
+ Use when: Finding available filenames without file collision before actual write operations.
330
+
331
+ This request scans the filesystem and returns the next available filename.
332
+ This is a preview operation that DOES NOT create any files or acquire any locks.
333
+
334
+ Args:
335
+ file_path: Path to the file (str for direct path, MacroPath for macro resolution)
336
+
337
+ Results: GetNextUnusedFilenameResultSuccess | GetNextUnusedFilenameResultFailure
338
+
339
+ Examples:
340
+ # Simple string path - cleanest for most use cases
341
+ file_path = "/outputs/render.png"
342
+ # Returns: "/outputs/render.png" if available
343
+ # "/outputs/render_1.png" if render.png exists
344
+ # "/outputs/render_2.png" if render_1.png exists, etc.
345
+
346
+ # MacroPath with required {_index} and padding
347
+ file_path = MacroPath(
348
+ parsed_macro=ParsedMacro("{outputs}/frame_{_index:05}.png"),
349
+ variables={"outputs": "/abs/path"}
350
+ )
351
+ # Returns: "/abs/path/frame_00001.png", "/abs/path/frame_00002.png", etc.
352
+ # Note: Always includes index, cannot return "frame.png"
353
+
354
+ # MacroPath with optional {_index} - limited by separator position
355
+ file_path = MacroPath(
356
+ parsed_macro=ParsedMacro("{outputs}/frame{_index?:_}.png"),
357
+ variables={"outputs": "/abs/path"}
358
+ )
359
+ # Returns: "/abs/path/frame.png" if {_index} omitted
360
+ # "/abs/path/frame1_.png" if {_index}=1 (separator goes after value)
361
+ # Note: Cannot achieve "frame.png" → "frame_1.png" with optional variable
362
+ """
363
+
364
+ file_path: str | MacroPath
365
+
366
+
367
+ @dataclass
368
+ @PayloadRegistry.register
369
+ class GetNextUnusedFilenameResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
370
+ """Next unused filename found (preview only - no file created).
371
+
372
+ Attributes:
373
+ available_filename: Absolute path to the available filename
374
+ index_used: The index number that was used (e.g., 1, 2, 3...), or None if base filename is available
375
+ """
376
+
377
+ available_filename: str
378
+ index_used: int | None
379
+
380
+
381
+ @dataclass
382
+ @PayloadRegistry.register
383
+ class GetNextUnusedFilenameResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
384
+ """Failed to find available filename.
385
+
386
+ Attributes:
387
+ failure_reason: Classification of why the operation failed
388
+ result_details: Human-readable error message (inherited from ResultPayloadFailure)
389
+ """
390
+
391
+ failure_reason: FileIOFailureReason
392
+
393
+
313
394
  @dataclass
314
395
  @PayloadRegistry.register
315
396
  class WriteFileRequest(RequestPayload):
@@ -321,14 +402,14 @@ class WriteFileRequest(RequestPayload):
321
402
  creating configuration files, writing binary data.
322
403
 
323
404
  Args:
324
- file_path: Path to the file to write
405
+ file_path: Path to the file to write (str for direct path, MacroPath for macro resolution)
325
406
  content: Content to write (str for text files, bytes for binary files)
326
407
  encoding: Text encoding for str content (default: 'utf-8', ignored for bytes)
327
408
  append: If True, append to existing file; if False, use existing_file_policy (default: False)
328
409
  existing_file_policy: How to handle existing files when append=False:
329
410
  - "overwrite": Replace file content (default)
330
411
  - "fail": Return failure if file exists
331
- - "create_new": Create new file with modified name (NOT YET IMPLEMENTED)
412
+ - "create_new": Create new file with auto-incrementing index (e.g., file_1.txt, file_2.txt)
332
413
  create_parents: If True, create parent directories if missing (default: True)
333
414
 
334
415
  Results: WriteFileResultSuccess | WriteFileResultFailure
@@ -336,7 +417,7 @@ class WriteFileRequest(RequestPayload):
336
417
  Note: existing_file_policy is ignored when append=True (append always allows existing files)
337
418
  """
338
419
 
339
- file_path: str
420
+ file_path: str | MacroPath
340
421
  content: str | bytes
341
422
  encoding: str = "utf-8" # Ignored for bytes
342
423
  append: bool = False
@@ -366,10 +447,12 @@ class WriteFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
366
447
 
367
448
  Attributes:
368
449
  failure_reason: Classification of why the write failed
450
+ missing_variables: Set of missing variable names (for MISSING_MACRO_VARIABLES failures)
369
451
  result_details: Human-readable error message (inherited from ResultPayloadFailure)
370
452
  """
371
453
 
372
454
  failure_reason: FileIOFailureReason
455
+ missing_variables: set[str] | None = None
373
456
 
374
457
 
375
458
  @dataclass
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from dataclasses import dataclass
6
6
  from enum import StrEnum
7
- from typing import TYPE_CHECKING, Any
7
+ from typing import TYPE_CHECKING, Any, NamedTuple
8
8
 
9
9
  from griptape_nodes.retained_mode.events.base_events import (
10
10
  RequestPayload,
@@ -25,6 +25,20 @@ if TYPE_CHECKING:
25
25
  MacroVariables = dict[str, str | int]
26
26
 
27
27
 
28
+ class MacroPath(NamedTuple):
29
+ """A macro path with its parsed template and variable values.
30
+
31
+ Used when file paths need macro resolution before filesystem operations.
32
+
33
+ Attributes:
34
+ parsed_macro: The parsed macro template
35
+ variables: Variable values for macro substitution
36
+ """
37
+
38
+ parsed_macro: ParsedMacro
39
+ variables: MacroVariables
40
+
41
+
28
42
  class PathResolutionFailureReason(StrEnum):
29
43
  """Reason why path resolution from macro failed."""
30
44
 
@@ -632,6 +632,49 @@ class MoveWorkflowResultFailure(ResultPayloadFailure):
632
632
  """Workflow move failed. Common causes: workflow not found, invalid target directory, file system error."""
633
633
 
634
634
 
635
+ @dataclass
636
+ @PayloadRegistry.register
637
+ class GetWorkflowMetadataRequest(RequestPayload):
638
+ """Get selected metadata for a workflow by name from the registry."""
639
+
640
+ workflow_name: str
641
+
642
+
643
+ @dataclass
644
+ @PayloadRegistry.register
645
+ class GetWorkflowMetadataResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
646
+ """Workflow metadata retrieved successfully."""
647
+
648
+ workflow_metadata: WorkflowMetadata
649
+
650
+
651
+ @dataclass
652
+ @PayloadRegistry.register
653
+ class GetWorkflowMetadataResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
654
+ """Workflow metadata retrieval failed. Common causes: workflow not found, registry error, file load error."""
655
+
656
+
657
+ @dataclass
658
+ @PayloadRegistry.register
659
+ class SetWorkflowMetadataRequest(RequestPayload):
660
+ """Replace the workflow's metadata entirely and persist to file."""
661
+
662
+ workflow_name: str
663
+ workflow_metadata: WorkflowMetadata
664
+
665
+
666
+ @dataclass
667
+ @PayloadRegistry.register
668
+ class SetWorkflowMetadataResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
669
+ """Workflow metadata updated successfully."""
670
+
671
+
672
+ @dataclass
673
+ @PayloadRegistry.register
674
+ class SetWorkflowMetadataResultFailure(ResultPayloadFailure):
675
+ """Workflow metadata update failed. Common causes: workflow not found, invalid keys/types, file system error."""
676
+
677
+
635
678
  @dataclass
636
679
  @PayloadRegistry.register
637
680
  class RegisterWorkflowsFromConfigRequest(RequestPayload):
@@ -681,7 +724,9 @@ class SaveWorkflowFileFromSerializedFlowRequest(RequestPayload):
681
724
  serialized_flow_commands: The serialized commands representing the workflow structure
682
725
  file_name: Name for the workflow file (without .py extension)
683
726
  creation_date: Optional creation date for the workflow metadata (defaults to current time if not provided)
684
- image_path: Optional path to workflow image/thumbnail
727
+ image_path: Optional path to workflow image/thumbnail. If None, callers may preserve existing image.
728
+ description: Optional workflow description text. If None, callers may preserve existing description.
729
+ is_template: Optional template status flag. If None, callers may preserve existing template status.
685
730
  execution_flow_name: Optional flow name to use for execution code (defaults to file_name if not provided)
686
731
  branched_from: Optional branched from information to preserve workflow lineage
687
732
  workflow_shape: Optional workflow shape defining inputs and outputs for external callers
@@ -696,6 +741,8 @@ class SaveWorkflowFileFromSerializedFlowRequest(RequestPayload):
696
741
  file_path: str | None = None
697
742
  creation_date: datetime | None = None
698
743
  image_path: str | None = None
744
+ description: str | None = None
745
+ is_template: bool | None = None
699
746
  execution_flow_name: str | None = None
700
747
  branched_from: str | None = None
701
748
  workflow_shape: WorkflowShape | None = None