griptape-nodes 0.62.3__py3-none-any.whl → 0.63.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 (73) hide show
  1. griptape_nodes/cli/commands/libraries.py +6 -21
  2. griptape_nodes/drivers/thread_storage/__init__.py +15 -0
  3. griptape_nodes/drivers/thread_storage/base_thread_storage_driver.py +106 -0
  4. griptape_nodes/drivers/thread_storage/griptape_cloud_thread_storage_driver.py +213 -0
  5. griptape_nodes/drivers/thread_storage/local_thread_storage_driver.py +137 -0
  6. griptape_nodes/drivers/thread_storage/thread_storage_backend.py +10 -0
  7. griptape_nodes/node_library/library_registry.py +16 -9
  8. griptape_nodes/node_library/workflow_registry.py +1 -1
  9. griptape_nodes/retained_mode/events/agent_events.py +232 -9
  10. griptape_nodes/retained_mode/events/library_events.py +32 -3
  11. griptape_nodes/retained_mode/events/os_events.py +101 -1
  12. griptape_nodes/retained_mode/managers/agent_manager.py +335 -135
  13. griptape_nodes/retained_mode/managers/fitness_problems/__init__.py +1 -0
  14. griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +59 -0
  15. griptape_nodes/retained_mode/managers/fitness_problems/libraries/advanced_library_load_failure_problem.py +33 -0
  16. griptape_nodes/retained_mode/managers/fitness_problems/libraries/after_library_callback_problem.py +32 -0
  17. griptape_nodes/retained_mode/managers/fitness_problems/libraries/before_library_callback_problem.py +32 -0
  18. griptape_nodes/retained_mode/managers/fitness_problems/libraries/create_config_category_problem.py +32 -0
  19. griptape_nodes/retained_mode/managers/fitness_problems/libraries/dependency_installation_failed_problem.py +32 -0
  20. griptape_nodes/retained_mode/managers/fitness_problems/libraries/deprecated_node_warning_problem.py +83 -0
  21. griptape_nodes/retained_mode/managers/fitness_problems/libraries/duplicate_library_problem.py +28 -0
  22. griptape_nodes/retained_mode/managers/fitness_problems/libraries/duplicate_node_registration_problem.py +44 -0
  23. griptape_nodes/retained_mode/managers/fitness_problems/libraries/engine_version_error_problem.py +28 -0
  24. griptape_nodes/retained_mode/managers/fitness_problems/libraries/insufficient_disk_space_problem.py +33 -0
  25. griptape_nodes/retained_mode/managers/fitness_problems/libraries/invalid_version_string_problem.py +32 -0
  26. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_json_decode_problem.py +28 -0
  27. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_load_exception_problem.py +32 -0
  28. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_not_found_problem.py +30 -0
  29. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_problem.py +20 -0
  30. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_schema_exception_problem.py +32 -0
  31. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_schema_validation_problem.py +38 -0
  32. griptape_nodes/retained_mode/managers/fitness_problems/libraries/modified_parameters_set_deprecation_warning_problem.py +44 -0
  33. griptape_nodes/retained_mode/managers/fitness_problems/libraries/modified_parameters_set_removed_problem.py +44 -0
  34. griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_class_not_base_node_problem.py +40 -0
  35. griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_class_not_found_problem.py +38 -0
  36. griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_module_import_problem.py +53 -0
  37. griptape_nodes/retained_mode/managers/fitness_problems/libraries/sandbox_directory_missing_problem.py +28 -0
  38. griptape_nodes/retained_mode/managers/fitness_problems/libraries/ui_options_field_modified_incompatible_problem.py +44 -0
  39. griptape_nodes/retained_mode/managers/fitness_problems/libraries/ui_options_field_modified_warning_problem.py +35 -0
  40. griptape_nodes/retained_mode/managers/fitness_problems/libraries/update_config_category_problem.py +32 -0
  41. griptape_nodes/retained_mode/managers/fitness_problems/libraries/venv_creation_failed_problem.py +32 -0
  42. griptape_nodes/retained_mode/managers/fitness_problems/workflows/__init__.py +75 -0
  43. griptape_nodes/retained_mode/managers/fitness_problems/workflows/deprecated_node_in_workflow_problem.py +83 -0
  44. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_dependency_version_string_problem.py +38 -0
  45. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_library_version_string_problem.py +38 -0
  46. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_metadata_schema_problem.py +31 -0
  47. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_metadata_section_count_problem.py +31 -0
  48. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_toml_format_problem.py +30 -0
  49. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_not_registered_problem.py +35 -0
  50. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_below_required_problem.py +41 -0
  51. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_large_difference_problem.py +41 -0
  52. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_major_mismatch_problem.py +41 -0
  53. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_minor_difference_problem.py +41 -0
  54. griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_creation_date_problem.py +30 -0
  55. griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_last_modified_date_problem.py +30 -0
  56. griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_toml_section_problem.py +30 -0
  57. griptape_nodes/retained_mode/managers/fitness_problems/workflows/node_type_not_found_problem.py +51 -0
  58. griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_not_found_problem.py +27 -0
  59. griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_problem.py +20 -0
  60. griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_schema_version_problem.py +39 -0
  61. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +17 -3
  62. griptape_nodes/retained_mode/managers/library_manager.py +159 -75
  63. griptape_nodes/retained_mode/managers/os_manager.py +172 -1
  64. griptape_nodes/retained_mode/managers/settings.py +5 -0
  65. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +76 -51
  66. griptape_nodes/retained_mode/managers/workflow_manager.py +154 -137
  67. griptape_nodes/servers/static.py +18 -19
  68. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +16 -12
  69. griptape_nodes/version_compatibility/workflow_versions/v0_7_0/local_executor_argument_addition.py +6 -3
  70. {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.0.dist-info}/METADATA +2 -1
  71. {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.0.dist-info}/RECORD +73 -20
  72. {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.0.dist-info}/WHEEL +0 -0
  73. {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.0.dist-info}/entry_points.txt +0 -0
@@ -5,6 +5,7 @@ import asyncio
5
5
  import logging
6
6
  import pickle
7
7
  import re
8
+ from collections import defaultdict
8
9
  from dataclasses import dataclass, field, fields, is_dataclass
9
10
  from datetime import UTC, datetime
10
11
  from enum import StrEnum
@@ -54,6 +55,8 @@ from griptape_nodes.retained_mode.events.flow_events import (
54
55
  from griptape_nodes.retained_mode.events.library_events import (
55
56
  GetLibraryMetadataRequest,
56
57
  GetLibraryMetadataResultSuccess,
58
+ ListRegisteredLibrariesRequest,
59
+ ListRegisteredLibrariesResultSuccess,
57
60
  )
58
61
  from griptape_nodes.retained_mode.events.object_events import ClearAllObjectStateRequest
59
62
  from griptape_nodes.retained_mode.events.workflow_events import (
@@ -118,6 +121,22 @@ from griptape_nodes.retained_mode.events.workflow_events import (
118
121
  from griptape_nodes.retained_mode.griptape_nodes import (
119
122
  GriptapeNodes,
120
123
  )
124
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows import (
125
+ InvalidDependencyVersionStringProblem,
126
+ InvalidLibraryVersionStringProblem,
127
+ InvalidMetadataSchemaProblem,
128
+ InvalidMetadataSectionCountProblem,
129
+ InvalidTomlFormatProblem,
130
+ LibraryNotRegisteredProblem,
131
+ LibraryVersionBelowRequiredProblem,
132
+ LibraryVersionLargeDifferenceProblem,
133
+ LibraryVersionMajorMismatchProblem,
134
+ LibraryVersionMinorDifferenceProblem,
135
+ MissingCreationDateProblem,
136
+ MissingLastModifiedDateProblem,
137
+ MissingTomlSectionProblem,
138
+ WorkflowNotFoundProblem,
139
+ )
121
140
  from griptape_nodes.retained_mode.managers.os_manager import OSManager
122
141
 
123
142
  if TYPE_CHECKING:
@@ -128,6 +147,7 @@ if TYPE_CHECKING:
128
147
  from griptape_nodes.retained_mode.events.base_events import ResultPayload
129
148
  from griptape_nodes.retained_mode.events.node_events import SerializedNodeCommands, SetLockNodeStateRequest
130
149
  from griptape_nodes.retained_mode.managers.event_manager import EventManager
150
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.workflow_problem import WorkflowProblem
131
151
 
132
152
 
133
153
  T = TypeVar("T")
@@ -189,7 +209,7 @@ class WorkflowManager:
189
209
  workflow_path: str
190
210
  workflow_name: str | None = None
191
211
  workflow_dependencies: list[WorkflowManager.WorkflowDependencyInfo] = field(default_factory=list)
192
- problems: list[str] = field(default_factory=list)
212
+ problems: list[WorkflowProblem] = field(default_factory=list)
193
213
 
194
214
  _workflow_file_path_to_info: dict[str, WorkflowInfo]
195
215
 
@@ -419,30 +439,48 @@ class WorkflowManager:
419
439
 
420
440
  return matches
421
441
 
422
- def print_workflow_load_status(self) -> None:
442
+ def print_workflow_load_status(self, min_status: WorkflowStatus = WorkflowStatus.FLAWED) -> None: # noqa: PLR0915
423
443
  workflow_file_paths = self.get_workflows_attempted_to_load()
424
444
  workflow_infos = []
425
445
  for workflow_file_path in workflow_file_paths:
426
446
  workflow_info = self.get_workflow_info_for_attempted_load(workflow_file_path)
427
447
  workflow_infos.append(workflow_info)
428
448
 
449
+ # Filter workflows to only show those at or worse than min_status
450
+ all_statuses = list(self.WorkflowStatus)
451
+ min_status_index = all_statuses.index(min_status)
452
+ filtered_workflow_infos = [
453
+ wf_info for wf_info in workflow_infos if all_statuses.index(wf_info.status) >= min_status_index
454
+ ]
455
+
456
+ # Sort workflows by severity (worst to best)
457
+ filtered_workflow_infos.sort(key=lambda wf: all_statuses.index(wf.status), reverse=True)
458
+
429
459
  console = Console()
430
460
 
431
461
  # Check if the list is empty
432
- if not workflow_infos:
433
- # Display a message indicating no workflows are available
462
+ if not filtered_workflow_infos:
434
463
  empty_message = Text("No workflow information available", style="italic")
435
464
  panel = Panel(empty_message, title="Workflow Information", border_style="blue")
436
465
  console.print(panel)
437
466
  return
438
467
 
439
- # Create a table with five columns and row dividers
468
+ # Add filter message if not showing all workflows
469
+ if min_status != self.WorkflowStatus.GOOD:
470
+ statuses_shown = all_statuses[min_status_index:]
471
+ status_names = ", ".join(s.value for s in statuses_shown)
472
+ filter_message = Text(
473
+ f"Only displaying workflows with a fitness of {status_names}",
474
+ style="italic yellow",
475
+ )
476
+ console.print(filter_message)
477
+ console.print()
478
+
479
+ # Create a table with three columns and row dividers
440
480
  table = Table(show_header=True, box=HEAVY_EDGE, show_lines=True, expand=True)
441
- table.add_column("Workflow Name", style="green")
442
- table.add_column("Status", style="green")
443
- table.add_column("File Path", style="cyan")
444
- table.add_column("Problems", style="yellow")
445
- table.add_column("Dependencies", style="magenta")
481
+ table.add_column("Workflow", style="green", ratio=2)
482
+ table.add_column("Problems", style="yellow", ratio=3)
483
+ table.add_column("Dependencies", style="magenta", ratio=2)
446
484
 
447
485
  # Status emojis mapping
448
486
  status_emoji = {
@@ -452,6 +490,14 @@ class WorkflowManager:
452
490
  self.WorkflowStatus.MISSING: "[red]?[/red]",
453
491
  }
454
492
 
493
+ # Status text mapping (colored)
494
+ status_text = {
495
+ self.WorkflowStatus.GOOD: "[green](GOOD)[/green]",
496
+ self.WorkflowStatus.FLAWED: "[yellow](FLAWED)[/yellow]",
497
+ self.WorkflowStatus.UNUSABLE: "[red](UNUSABLE)[/red]",
498
+ self.WorkflowStatus.MISSING: "[red](MISSING)[/red]",
499
+ }
500
+
455
501
  dependency_status_emoji = {
456
502
  self.WorkflowDependencyStatus.PERFECT: "[green]OK[/green]",
457
503
  self.WorkflowDependencyStatus.GOOD: "[green]GOOD[/green]",
@@ -462,19 +508,37 @@ class WorkflowManager:
462
508
  }
463
509
 
464
510
  # Add rows for each workflow info
465
- for wf_info in workflow_infos:
466
- # File path column
467
- file_path = wf_info.workflow_path
468
- file_path_text = Text(file_path, style="cyan")
469
- file_path_text.overflow = "fold" # Force wrapping
470
-
471
- # Workflow name column with emoji based on status
511
+ for wf_info in filtered_workflow_infos:
512
+ # Workflow name column with emoji, name, colored status, and file path underneath
472
513
  emoji = status_emoji.get(wf_info.status, "ERR: Unknown/Unexpected Workflow Status")
514
+ colored_status = status_text.get(wf_info.status, "(UNKNOWN)")
473
515
  name = wf_info.workflow_name if wf_info.workflow_name else "*UNKNOWN*"
474
- workflow_name = f"{emoji} - {name}"
516
+ file_path = wf_info.workflow_path
517
+ workflow_name_with_path = Text.from_markup(
518
+ f"{emoji} - {name} {colored_status}\n[cyan dim]{file_path}[/cyan dim]"
519
+ )
520
+ workflow_name_with_path.overflow = "fold"
475
521
 
476
- # Problems column - format with numbers if there's more than one
477
- problems = "\n".join(wf_info.problems) if wf_info.problems else "No problems detected."
522
+ # Problems column - collate by type
523
+ if not wf_info.problems:
524
+ problems = "No problems detected."
525
+ else:
526
+ # Group problems by type
527
+ problems_by_type = defaultdict(list)
528
+ for problem in wf_info.problems:
529
+ problems_by_type[type(problem)].append(problem)
530
+
531
+ # Collate each group
532
+ collated_strings = []
533
+ for problem_class, instances in problems_by_type.items():
534
+ collated_display = problem_class.collate_problems_for_display(instances)
535
+ collated_strings.append(collated_display)
536
+
537
+ # Format for display
538
+ if len(collated_strings) == 1:
539
+ problems = collated_strings[0]
540
+ else:
541
+ problems = "\n".join([f"{j + 1}. {problem}" for j, problem in enumerate(collated_strings)])
478
542
 
479
543
  # Dependencies column
480
544
  if wf_info.status == self.WorkflowStatus.MISSING or (
@@ -492,9 +556,7 @@ class WorkflowManager:
492
556
  )
493
557
 
494
558
  table.add_row(
495
- workflow_name,
496
- wf_info.status.value,
497
- file_path_text,
559
+ workflow_name_with_path,
498
560
  problems,
499
561
  dependencies,
500
562
  )
@@ -873,9 +935,7 @@ class WorkflowManager:
873
935
  workflow_path=str_path,
874
936
  workflow_name=None,
875
937
  workflow_dependencies=[],
876
- problems=[
877
- "Workflow could not be found at the file path specified. It will be removed from the configuration."
878
- ],
938
+ problems=[WorkflowNotFoundProblem()],
879
939
  )
880
940
  details = f"Attempted to load workflow metadata for a file at '{complete_file_path}. Failed because no file could be found at that path."
881
941
  return LoadWorkflowMetadataResultFailure(result_details=details)
@@ -889,9 +949,7 @@ class WorkflowManager:
889
949
  workflow_path=str_path,
890
950
  workflow_name=None,
891
951
  workflow_dependencies=[],
892
- problems=[
893
- f"Failed as it had {len(matches)} sections titled '{block_name}', and we expect exactly 1 such section."
894
- ],
952
+ problems=[InvalidMetadataSectionCountProblem(section_name=block_name, count=len(matches))],
895
953
  )
896
954
  details = f"Attempted to load workflow metadata for a file at '{complete_file_path}'. Failed as it had {len(matches)} sections titled '{block_name}', and we expect exactly 1 such section."
897
955
  return LoadWorkflowMetadataResultFailure(result_details=details)
@@ -910,7 +968,7 @@ class WorkflowManager:
910
968
  workflow_path=str_path,
911
969
  workflow_name=None,
912
970
  workflow_dependencies=[],
913
- problems=[f"Failed because the metadata was not valid TOML: {err}"],
971
+ problems=[InvalidTomlFormatProblem(error_message=str(err))],
914
972
  )
915
973
  details = f"Attempted to load workflow metadata for a file at '{complete_file_path}'. Failed because the metadata was not valid TOML: {err}"
916
974
  return LoadWorkflowMetadataResultFailure(result_details=details)
@@ -925,7 +983,7 @@ class WorkflowManager:
925
983
  workflow_path=str_path,
926
984
  workflow_name=None,
927
985
  workflow_dependencies=[],
928
- problems=[f"Failed because the '[{tool_header}.{griptape_nodes_header}]' section could not be found."],
986
+ problems=[MissingTomlSectionProblem(section_path=f"[{tool_header}.{griptape_nodes_header}]")],
929
987
  )
930
988
  details = f"Attempted to load workflow metadata for a file at '{complete_file_path}'. Failed because the '[{tool_header}.{griptape_nodes_header}]' section could not be found: {err}"
931
989
  return LoadWorkflowMetadataResultFailure(result_details=details)
@@ -941,7 +999,9 @@ class WorkflowManager:
941
999
  workflow_name=None,
942
1000
  workflow_dependencies=[],
943
1001
  problems=[
944
- f"Failed because the metadata in the '[{tool_header}.{griptape_nodes_header}]' section did not match the requisite schema with error: {err}"
1002
+ InvalidMetadataSchemaProblem(
1003
+ section_path=f"[{tool_header}.{griptape_nodes_header}]", error_message=str(err)
1004
+ )
945
1005
  ],
946
1006
  )
947
1007
  details = f"Attempted to load workflow metadata for a file at '{complete_file_path}'. Failed because the metadata in the '[{tool_header}.{griptape_nodes_header}]' section did not match the requisite schema with error: {err}"
@@ -956,15 +1016,23 @@ class WorkflowManager:
956
1016
  if workflow_metadata.creation_date is None:
957
1017
  # Assign it to the epoch start and flag it as a warning.
958
1018
  workflow_metadata.creation_date = WorkflowManager.EPOCH_START
959
- problems.append(
960
- f"Workflow metadata was missing a creation date. Defaulting to {WorkflowManager.EPOCH_START}. This value will be replaced with the current date the first time it is saved."
961
- )
1019
+ problems.append(MissingCreationDateProblem(default_date=str(WorkflowManager.EPOCH_START)))
962
1020
  if workflow_metadata.last_modified_date is None:
963
1021
  # Assign it to the epoch start and flag it as a warning.
964
1022
  workflow_metadata.last_modified_date = WorkflowManager.EPOCH_START
965
- problems.append(
966
- f"Workflow metadata was missing a last modified date. Defaulting to {WorkflowManager.EPOCH_START}. This value will be replaced with the current date the first time it is saved."
967
- )
1023
+ problems.append(MissingLastModifiedDateProblem(default_date=str(WorkflowManager.EPOCH_START)))
1024
+
1025
+ # Get list of registered libraries once (silent check - no error logging)
1026
+ list_libraries_request = ListRegisteredLibrariesRequest()
1027
+ list_libraries_result = GriptapeNodes.LibraryManager().on_list_registered_libraries_request(
1028
+ list_libraries_request
1029
+ )
1030
+
1031
+ if not isinstance(list_libraries_result, ListRegisteredLibrariesResultSuccess):
1032
+ # Should not happen, but handle gracefully - treat as no libraries registered
1033
+ registered_libraries = []
1034
+ else:
1035
+ registered_libraries = list_libraries_result.libraries
968
1036
 
969
1037
  dependency_infos = []
970
1038
  for node_library_referenced in workflow_metadata.node_libraries_referenced:
@@ -975,7 +1043,7 @@ class WorkflowManager:
975
1043
  except Exception:
976
1044
  had_critical_error = True
977
1045
  problems.append(
978
- f"Workflow cited an invalid version string '{desired_version_str}' for library '{library_name}'. Must be specified in major.minor.patch format."
1046
+ InvalidDependencyVersionStringProblem(library_name=library_name, version_string=desired_version_str)
979
1047
  )
980
1048
  dependency_infos.append(
981
1049
  WorkflowManager.WorkflowDependencyInfo(
@@ -988,20 +1056,32 @@ class WorkflowManager:
988
1056
  # SKIP IT.
989
1057
  continue
990
1058
  # See how our desired version compares against the actual library we (may) have.
991
- # See if the library exists.
1059
+ # Check if library is registered (silent check - no error logging)
1060
+ if library_name not in registered_libraries:
1061
+ # Library not registered
1062
+ had_critical_error = True
1063
+ problems.append(LibraryNotRegisteredProblem(library_name=library_name))
1064
+ dependency_infos.append(
1065
+ WorkflowManager.WorkflowDependencyInfo(
1066
+ library_name=library_name,
1067
+ version_requested=desired_version_str,
1068
+ version_present=None,
1069
+ status=WorkflowManager.WorkflowDependencyStatus.MISSING,
1070
+ )
1071
+ )
1072
+ # SKIP IT.
1073
+ continue
1074
+
1075
+ # Get library metadata (we know library is registered, so no error logging)
992
1076
  library_metadata_request = GetLibraryMetadataRequest(library=library_name)
993
- # NOTE: Per https://github.com/griptape-ai/griptape-vsl-gui/issues/1123, we
994
- # generate a FLOOD of error messages here that can swamp the GUI. We'll call
995
- # directly instead of the usual handle_request() path so we don't generate those.
996
1077
  library_metadata_result = GriptapeNodes.LibraryManager().get_library_metadata_request(
997
1078
  library_metadata_request
998
1079
  )
1080
+
999
1081
  if not isinstance(library_metadata_result, GetLibraryMetadataResultSuccess):
1000
- # Metadata failed to be found.
1082
+ # Should not happen since we verified library is registered, but handle gracefully
1001
1083
  had_critical_error = True
1002
- problems.append(
1003
- f"Library '{library_name}' was not successfully registered. It may have other problems that prevented it from loading."
1004
- )
1084
+ problems.append(LibraryNotRegisteredProblem(library_name=library_name))
1005
1085
  dependency_infos.append(
1006
1086
  WorkflowManager.WorkflowDependencyInfo(
1007
1087
  library_name=library_name,
@@ -1021,7 +1101,7 @@ class WorkflowManager:
1021
1101
  except Exception:
1022
1102
  had_critical_error = True
1023
1103
  problems.append(
1024
- f"Library an invalid version string '{library_version_str}' for library '{library_name}'. Must be specified in major.minor.patch format."
1104
+ InvalidLibraryVersionStringProblem(library_name=library_name, version_string=library_version_str)
1025
1105
  )
1026
1106
  dependency_infos.append(
1027
1107
  WorkflowManager.WorkflowDependencyInfo(
@@ -1046,24 +1126,40 @@ class WorkflowManager:
1046
1126
  delta = library_version.minor - desired_version.minor
1047
1127
  if delta < 0:
1048
1128
  problems.append(
1049
- f"Library '{library_name}' is at version '{library_version}', which is below the desired version."
1129
+ LibraryVersionBelowRequiredProblem(
1130
+ library_name=library_name,
1131
+ current_version=str(library_version),
1132
+ required_version=str(desired_version),
1133
+ )
1050
1134
  )
1051
1135
  status = WorkflowManager.WorkflowDependencyStatus.BAD
1052
1136
  had_critical_error = True
1053
1137
  elif delta > WorkflowManager.MAX_MINOR_VERSION_DEVIATION:
1054
1138
  problems.append(
1055
- f"This workflow was built with library '{library_name}' v{desired_version}, but you have v{library_version}. This large version difference may cause compatibility issues. You can update the library to a compatible version or save this workflow to update it to your current library versions."
1139
+ LibraryVersionLargeDifferenceProblem(
1140
+ library_name=library_name,
1141
+ workflow_version=str(desired_version),
1142
+ current_version=str(library_version),
1143
+ )
1056
1144
  )
1057
1145
  status = WorkflowManager.WorkflowDependencyStatus.BAD
1058
1146
  had_critical_error = True
1059
1147
  else:
1060
1148
  problems.append(
1061
- f"This workflow was built with library '{library_name}' v{desired_version}, but you have v{library_version}. Minor differences are usually compatible. If you experience issues, you can update the library or save this workflow to update it to your current library versions."
1149
+ LibraryVersionMinorDifferenceProblem(
1150
+ library_name=library_name,
1151
+ workflow_version=str(desired_version),
1152
+ current_version=str(library_version),
1153
+ )
1062
1154
  )
1063
1155
  status = WorkflowManager.WorkflowDependencyStatus.CAUTION
1064
1156
  else:
1065
1157
  problems.append(
1066
- f"This workflow requires library '{library_name}' v{desired_version}, but you have v{library_version}. Major version changes may include breaking changes. Consider updating the library to match, or save this workflow to update it to your current library versions."
1158
+ LibraryVersionMajorMismatchProblem(
1159
+ library_name=library_name,
1160
+ workflow_version=str(desired_version),
1161
+ current_version=str(library_version),
1162
+ )
1067
1163
  )
1068
1164
  status = WorkflowManager.WorkflowDependencyStatus.BAD
1069
1165
  had_critical_error = True
@@ -1083,7 +1179,7 @@ class WorkflowManager:
1083
1179
  workflow_metadata
1084
1180
  )
1085
1181
  for issue in workflow_version_issues:
1086
- problems.append(issue.message)
1182
+ problems.append(issue.problem)
1087
1183
  if issue.severity == WorkflowManager.WorkflowStatus.UNUSABLE:
1088
1184
  had_critical_error = True
1089
1185
 
@@ -2386,82 +2482,11 @@ class WorkflowManager:
2386
2482
  workflow_name: str,
2387
2483
  import_recorder: ImportRecorder,
2388
2484
  ) -> list[ast.AST]:
2389
- import_recorder.add_from_import(
2390
- "griptape_nodes.retained_mode.events.library_events", "GetAllInfoForAllLibrariesRequest"
2391
- )
2392
- import_recorder.add_from_import(
2393
- "griptape_nodes.retained_mode.events.library_events", "GetAllInfoForAllLibrariesResultSuccess"
2394
- )
2395
- import_recorder.add_from_import(
2396
- "griptape_nodes.retained_mode.events.library_events", "ReloadAllLibrariesRequest"
2397
- )
2485
+ import_recorder.add_from_import("griptape_nodes.retained_mode.events.library_events", "LoadLibrariesRequest")
2398
2486
 
2399
2487
  code_blocks: list[ast.AST] = []
2400
2488
 
2401
- response_assign = ast.Assign(
2402
- targets=[ast.Name(id="response", ctx=ast.Store())],
2403
- value=ast.Call(
2404
- func=ast.Attribute(
2405
- value=ast.Name(id="GriptapeNodes", ctx=ast.Load()),
2406
- attr="handle_request",
2407
- ctx=ast.Load(),
2408
- ),
2409
- args=[
2410
- ast.Call(
2411
- func=ast.Name(id="GetAllInfoForAllLibrariesRequest", ctx=ast.Load()),
2412
- args=[],
2413
- keywords=[],
2414
- )
2415
- ],
2416
- keywords=[],
2417
- ),
2418
- )
2419
- ast.fix_missing_locations(response_assign)
2420
- code_blocks.append(response_assign)
2421
-
2422
- isinstance_test = ast.Call(
2423
- func=ast.Name(id="isinstance", ctx=ast.Load()),
2424
- args=[
2425
- ast.Name(id="response", ctx=ast.Load()),
2426
- ast.Name(id="GetAllInfoForAllLibrariesResultSuccess", ctx=ast.Load()),
2427
- ],
2428
- keywords=[],
2429
- )
2430
- ast.fix_missing_locations(isinstance_test)
2431
-
2432
- len_call = ast.Call(
2433
- func=ast.Name(id="len", ctx=ast.Load()),
2434
- args=[
2435
- ast.Call(
2436
- func=ast.Attribute(
2437
- value=ast.Attribute(
2438
- value=ast.Name(id="response", ctx=ast.Load()),
2439
- attr="library_name_to_library_info",
2440
- ctx=ast.Load(),
2441
- ),
2442
- attr="keys",
2443
- ctx=ast.Load(),
2444
- ),
2445
- args=[],
2446
- keywords=[],
2447
- )
2448
- ],
2449
- keywords=[],
2450
- )
2451
- compare_len = ast.Compare(
2452
- left=len_call,
2453
- ops=[ast.Lt()],
2454
- comparators=[ast.Constant(value=1)],
2455
- )
2456
- ast.fix_missing_locations(compare_len)
2457
-
2458
- test = ast.BoolOp(
2459
- op=ast.And(),
2460
- values=[isinstance_test, compare_len],
2461
- )
2462
- ast.fix_missing_locations(test)
2463
-
2464
- # 3) the body: GriptapeNodes.handle_request(ReloadAllLibrariesRequest())
2489
+ # Generate load libraries request call
2465
2490
  # TODO (https://github.com/griptape-ai/griptape-nodes/issues/1615): Generate requests to load ONLY the libraries used in this workflow
2466
2491
  load_call = ast.Expr(
2467
2492
  value=ast.Call(
@@ -2472,7 +2497,7 @@ class WorkflowManager:
2472
2497
  ),
2473
2498
  args=[
2474
2499
  ast.Call(
2475
- func=ast.Name(id="ReloadAllLibrariesRequest", ctx=ast.Load()),
2500
+ func=ast.Name(id="LoadLibrariesRequest", ctx=ast.Load()),
2476
2501
  args=[],
2477
2502
  keywords=[],
2478
2503
  )
@@ -2481,17 +2506,9 @@ class WorkflowManager:
2481
2506
  )
2482
2507
  )
2483
2508
  ast.fix_missing_locations(load_call)
2509
+ code_blocks.append(load_call)
2484
2510
 
2485
- # 4) assemble the `if` statement
2486
- if_node = ast.If(
2487
- test=test,
2488
- body=[load_call],
2489
- orelse=[],
2490
- )
2491
- ast.fix_missing_locations(if_node)
2492
- code_blocks.append(if_node)
2493
-
2494
- # 5) context_manager = GriptapeNodes.ContextManager()
2511
+ # Generate context manager assignment
2495
2512
  assign_context_manager = ast.Assign(
2496
2513
  targets=[ast.Name(id="context_manager", ctx=ast.Store())],
2497
2514
  value=ast.Call(
@@ -29,11 +29,6 @@ logger = logging.getLogger("griptape_nodes_api")
29
29
  logging.getLogger("uvicorn").addHandler(RichHandler(show_time=True, show_path=False, markup=True, rich_tracebacks=True))
30
30
 
31
31
 
32
- """Create and configure the FastAPI application."""
33
- app = FastAPI()
34
-
35
-
36
- @app.post("/static-upload-urls")
37
32
  async def _create_static_file_upload_url(request: Request) -> dict:
38
33
  """Create a URL for uploading a static file.
39
34
 
@@ -47,7 +42,6 @@ async def _create_static_file_upload_url(request: Request) -> dict:
47
42
  return {"url": url}
48
43
 
49
44
 
50
- @app.put("/static-uploads/{file_path:path}")
51
45
  async def _create_static_file(request: Request, file_path: str) -> dict:
52
46
  """Upload a static file to the static server."""
53
47
  if not STATIC_SERVER_ENABLED:
@@ -76,8 +70,6 @@ async def _create_static_file(request: Request, file_path: str) -> dict:
76
70
  return {"url": static_url}
77
71
 
78
72
 
79
- @app.get("/static-uploads/{file_path_prefix:path}")
80
- @app.get("/static-uploads/")
81
73
  async def _list_static_files(file_path_prefix: str = "") -> dict:
82
74
  """List static files in the static server under the specified path prefix."""
83
75
  if not STATIC_SERVER_ENABLED:
@@ -107,7 +99,6 @@ async def _list_static_files(file_path_prefix: str = "") -> dict:
107
99
  return {"files": file_names}
108
100
 
109
101
 
110
- @app.delete("/static-files/{file_path:path}")
111
102
  async def _delete_static_file(file_path: str) -> dict:
112
103
  """Delete a static file from the static server."""
113
104
  if not STATIC_SERVER_ENABLED:
@@ -139,11 +130,21 @@ async def _delete_static_file(file_path: str) -> dict:
139
130
  return {"message": f"File {file_path} deleted successfully"}
140
131
 
141
132
 
142
- def _setup_app() -> None:
143
- """Setup FastAPI app with middleware and static files."""
144
- workspace_directory = Path(GriptapeNodes.ConfigManager().get_config_value("workspace_directory"))
145
- static_files_directory = Path(GriptapeNodes.ConfigManager().get_config_value("static_files_directory"))
133
+ def start_static_server() -> None:
134
+ """Run uvicorn server synchronously using uvicorn.run."""
135
+ logger.debug("Starting static server...")
136
+
137
+ # Create FastAPI app
138
+ app = FastAPI()
139
+
140
+ # Register routes
141
+ app.add_api_route("/static-upload-urls", _create_static_file_upload_url, methods=["POST"])
142
+ app.add_api_route("/static-uploads/{file_path:path}", _create_static_file, methods=["PUT"])
143
+ app.add_api_route("/static-uploads/{file_path_prefix:path}", _list_static_files, methods=["GET"])
144
+ app.add_api_route("/static-uploads/", _list_static_files, methods=["GET"])
145
+ app.add_api_route("/static-files/{file_path:path}", _delete_static_file, methods=["DELETE"])
146
146
 
147
+ # Add CORS middleware
147
148
  app.add_middleware(
148
149
  CORSMiddleware,
149
150
  allow_origins=[
@@ -156,6 +157,10 @@ def _setup_app() -> None:
156
157
  allow_headers=["*"],
157
158
  )
158
159
 
160
+ # Mount static files
161
+ workspace_directory = Path(GriptapeNodes.ConfigManager().get_config_value("workspace_directory"))
162
+ static_files_directory = Path(GriptapeNodes.ConfigManager().get_config_value("static_files_directory"))
163
+
159
164
  app.mount(
160
165
  STATIC_SERVER_URL,
161
166
  StaticFiles(directory=workspace_directory),
@@ -170,12 +175,6 @@ def _setup_app() -> None:
170
175
  name="static",
171
176
  )
172
177
 
173
-
174
- def start_static_server() -> None:
175
- """Run uvicorn server synchronously using uvicorn.run."""
176
- # Setup the FastAPI app
177
- _setup_app()
178
-
179
178
  try:
180
179
  # Run server using uvicorn.run
181
180
  uvicorn.run(
@@ -9,6 +9,18 @@ from griptape_nodes.retained_mode.events.app_events import (
9
9
  GetEngineVersionResultSuccess,
10
10
  )
11
11
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
12
+ from griptape_nodes.retained_mode.managers.fitness_problems.libraries.modified_parameters_set_deprecation_warning_problem import (
13
+ ModifiedParametersSetDeprecationWarningProblem,
14
+ )
15
+ from griptape_nodes.retained_mode.managers.fitness_problems.libraries.modified_parameters_set_removed_problem import (
16
+ ModifiedParametersSetRemovedProblem,
17
+ )
18
+ from griptape_nodes.retained_mode.managers.fitness_problems.libraries.ui_options_field_modified_incompatible_problem import (
19
+ UiOptionsFieldModifiedIncompatibleProblem,
20
+ )
21
+ from griptape_nodes.retained_mode.managers.fitness_problems.libraries.ui_options_field_modified_warning_problem import (
22
+ UiOptionsFieldModifiedWarningProblem,
23
+ )
12
24
  from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
13
25
  from griptape_nodes.retained_mode.managers.version_compatibility_manager import (
14
26
  LibraryVersionCompatibilityCheck,
@@ -49,15 +61,11 @@ class ModifiedParametersSetRemovalCheck(LibraryVersionCompatibilityCheck):
49
61
  # 0.39+ Release: Parameter removed, reject incompatible libraries
50
62
  return [
51
63
  LibraryVersionCompatibilityIssue(
52
- message=f"This library (built for engine version {library_version_str}) is incompatible with Griptape Nodes 0.39+. "
53
- "The 'modified_parameters_set' parameter has been removed from BaseNode methods: 'after_incoming_connection', 'after_outgoing_connection', 'after_incoming_connection_removed', 'after_outgoing_connection_removed', 'before_value_set', and 'after_value_set'. "
54
- "If this library overrides any of these methods, it will not load or function properly. Please update to a newer version of this library or contact the library author immediately.",
64
+ problem=ModifiedParametersSetRemovedProblem(library_engine_version=library_version_str),
55
65
  severity=LibraryStatus.UNUSABLE,
56
66
  ),
57
67
  LibraryVersionCompatibilityIssue(
58
- message=f"This library (built for engine version {library_version_str}) is incompatible with Griptape Nodes 0.39+."
59
- "The 'ui_options' field has been modified on all Elements. In order to function properly, all nodes must update ui_options by setting its value to a new dictionary. Updating ui_options by accessing the private field _ui_options will no longer create UI updates in the editor."
60
- "If this library accesses the private _ui_options field, it will not update the editor properly. Please update to a newer version of this library or contact the library author immediately.",
68
+ problem=UiOptionsFieldModifiedIncompatibleProblem(library_engine_version=library_version_str),
61
69
  severity=LibraryStatus.UNUSABLE,
62
70
  ),
63
71
  ]
@@ -65,15 +73,11 @@ class ModifiedParametersSetRemovalCheck(LibraryVersionCompatibilityCheck):
65
73
  # 0.38 Release: Warning about upcoming removal in 0.39
66
74
  return [
67
75
  LibraryVersionCompatibilityIssue(
68
- message=f"WARNING: The 'modified_parameters_set' parameter will be removed in Griptape Nodes 0.39 from BaseNode methods: 'after_incoming_connection', 'after_outgoing_connection', 'after_incoming_connection_removed', 'after_outgoing_connection_removed', 'before_value_set', and 'after_value_set'. "
69
- f"This library (built for engine version {library_version_str}) must be updated before the 0.39 release. "
70
- "If this library overrides any of these methods, it will fail to load in 0.39. If not, no action is necessary. Please contact the library author to confirm whether this library is impacted.",
76
+ problem=ModifiedParametersSetDeprecationWarningProblem(library_engine_version=library_version_str),
71
77
  severity=LibraryStatus.FLAWED,
72
78
  ),
73
79
  LibraryVersionCompatibilityIssue(
74
- message="WARNING: The 'ui_options' field has been modified in Griptape Nodes 0.38 on all BaseNodeElements."
75
- "In order to function properly, all nodes must update ui_options by setting its value to a new dictionary. Updating ui_options by accessing the private field _ui_options will no longer create UI updates in the editor."
76
- "If this library accesses the private _ui_options field, it will not update the editor properly. Please update to a newer version of this library or contact the library author immediately.",
80
+ problem=UiOptionsFieldModifiedWarningProblem(),
77
81
  severity=LibraryStatus.FLAWED,
78
82
  ),
79
83
  ]
@@ -6,6 +6,9 @@ from typing import TYPE_CHECKING
6
6
 
7
7
  import semver
8
8
 
9
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.workflow_schema_version_problem import (
10
+ WorkflowSchemaVersionProblem,
11
+ )
9
12
  from griptape_nodes.retained_mode.managers.version_compatibility_manager import (
10
13
  WorkflowVersionCompatibilityCheck,
11
14
  WorkflowVersionCompatibilityIssue,
@@ -39,9 +42,9 @@ class LocalExecutorArgumentAddition(WorkflowVersionCompatibilityCheck):
39
42
  if workflow_schema_version < semver.VersionInfo(0, 7, 0):
40
43
  issues.append(
41
44
  WorkflowVersionCompatibilityIssue(
42
- message=f"Workflow schema version {workflow_metadata.schema_version} is older than version 0.7.0. "
43
- "The generated LocalExecutor code for the workflow file is missing the `--json-input` argument, which may cause issues for Publish Workflow requests when using certain Library targets. "
44
- "Consider updating the workflow by saving it again.",
45
+ problem=WorkflowSchemaVersionProblem(
46
+ description=f"Schema version {workflow_metadata.schema_version} older than 0.7.0. This workflow may not publish or execute properly. Re-save workflow to update."
47
+ ),
45
48
  severity=WorkflowManager.WorkflowStatus.FLAWED,
46
49
  )
47
50
  )