griptape-nodes 0.62.3__py3-none-any.whl → 0.63.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.
Files changed (76) hide show
  1. griptape_nodes/cli/commands/libraries.py +6 -21
  2. griptape_nodes/cli/commands/models.py +21 -4
  3. griptape_nodes/drivers/thread_storage/__init__.py +15 -0
  4. griptape_nodes/drivers/thread_storage/base_thread_storage_driver.py +106 -0
  5. griptape_nodes/drivers/thread_storage/griptape_cloud_thread_storage_driver.py +213 -0
  6. griptape_nodes/drivers/thread_storage/local_thread_storage_driver.py +137 -0
  7. griptape_nodes/drivers/thread_storage/thread_storage_backend.py +10 -0
  8. griptape_nodes/node_library/library_registry.py +16 -9
  9. griptape_nodes/node_library/workflow_registry.py +1 -1
  10. griptape_nodes/retained_mode/events/agent_events.py +232 -9
  11. griptape_nodes/retained_mode/events/library_events.py +32 -3
  12. griptape_nodes/retained_mode/events/model_events.py +4 -4
  13. griptape_nodes/retained_mode/events/os_events.py +101 -1
  14. griptape_nodes/retained_mode/managers/agent_manager.py +335 -135
  15. griptape_nodes/retained_mode/managers/fitness_problems/__init__.py +1 -0
  16. griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +59 -0
  17. griptape_nodes/retained_mode/managers/fitness_problems/libraries/advanced_library_load_failure_problem.py +33 -0
  18. griptape_nodes/retained_mode/managers/fitness_problems/libraries/after_library_callback_problem.py +32 -0
  19. griptape_nodes/retained_mode/managers/fitness_problems/libraries/before_library_callback_problem.py +32 -0
  20. griptape_nodes/retained_mode/managers/fitness_problems/libraries/create_config_category_problem.py +32 -0
  21. griptape_nodes/retained_mode/managers/fitness_problems/libraries/dependency_installation_failed_problem.py +32 -0
  22. griptape_nodes/retained_mode/managers/fitness_problems/libraries/deprecated_node_warning_problem.py +83 -0
  23. griptape_nodes/retained_mode/managers/fitness_problems/libraries/duplicate_library_problem.py +28 -0
  24. griptape_nodes/retained_mode/managers/fitness_problems/libraries/duplicate_node_registration_problem.py +44 -0
  25. griptape_nodes/retained_mode/managers/fitness_problems/libraries/engine_version_error_problem.py +28 -0
  26. griptape_nodes/retained_mode/managers/fitness_problems/libraries/insufficient_disk_space_problem.py +33 -0
  27. griptape_nodes/retained_mode/managers/fitness_problems/libraries/invalid_version_string_problem.py +32 -0
  28. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_json_decode_problem.py +28 -0
  29. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_load_exception_problem.py +32 -0
  30. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_not_found_problem.py +30 -0
  31. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_problem.py +20 -0
  32. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_schema_exception_problem.py +32 -0
  33. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_schema_validation_problem.py +38 -0
  34. griptape_nodes/retained_mode/managers/fitness_problems/libraries/modified_parameters_set_deprecation_warning_problem.py +44 -0
  35. griptape_nodes/retained_mode/managers/fitness_problems/libraries/modified_parameters_set_removed_problem.py +44 -0
  36. griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_class_not_base_node_problem.py +40 -0
  37. griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_class_not_found_problem.py +38 -0
  38. griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_module_import_problem.py +53 -0
  39. griptape_nodes/retained_mode/managers/fitness_problems/libraries/sandbox_directory_missing_problem.py +28 -0
  40. griptape_nodes/retained_mode/managers/fitness_problems/libraries/ui_options_field_modified_incompatible_problem.py +44 -0
  41. griptape_nodes/retained_mode/managers/fitness_problems/libraries/ui_options_field_modified_warning_problem.py +35 -0
  42. griptape_nodes/retained_mode/managers/fitness_problems/libraries/update_config_category_problem.py +32 -0
  43. griptape_nodes/retained_mode/managers/fitness_problems/libraries/venv_creation_failed_problem.py +32 -0
  44. griptape_nodes/retained_mode/managers/fitness_problems/workflows/__init__.py +75 -0
  45. griptape_nodes/retained_mode/managers/fitness_problems/workflows/deprecated_node_in_workflow_problem.py +83 -0
  46. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_dependency_version_string_problem.py +38 -0
  47. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_library_version_string_problem.py +38 -0
  48. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_metadata_schema_problem.py +31 -0
  49. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_metadata_section_count_problem.py +31 -0
  50. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_toml_format_problem.py +30 -0
  51. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_not_registered_problem.py +35 -0
  52. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_below_required_problem.py +41 -0
  53. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_large_difference_problem.py +41 -0
  54. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_major_mismatch_problem.py +41 -0
  55. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_minor_difference_problem.py +41 -0
  56. griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_creation_date_problem.py +30 -0
  57. griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_last_modified_date_problem.py +30 -0
  58. griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_toml_section_problem.py +30 -0
  59. griptape_nodes/retained_mode/managers/fitness_problems/workflows/node_type_not_found_problem.py +51 -0
  60. griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_not_found_problem.py +27 -0
  61. griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_problem.py +20 -0
  62. griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_schema_version_problem.py +39 -0
  63. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +17 -3
  64. griptape_nodes/retained_mode/managers/library_manager.py +159 -75
  65. griptape_nodes/retained_mode/managers/model_manager.py +182 -205
  66. griptape_nodes/retained_mode/managers/os_manager.py +172 -1
  67. griptape_nodes/retained_mode/managers/settings.py +5 -0
  68. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +76 -51
  69. griptape_nodes/retained_mode/managers/workflow_manager.py +154 -137
  70. griptape_nodes/servers/static.py +18 -19
  71. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +16 -12
  72. griptape_nodes/version_compatibility/workflow_versions/v0_7_0/local_executor_argument_addition.py +6 -3
  73. {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.1.dist-info}/METADATA +3 -2
  74. {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.1.dist-info}/RECORD +76 -23
  75. {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.1.dist-info}/WHEEL +0 -0
  76. {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.1.dist-info}/entry_points.txt +0 -0
@@ -3,12 +3,14 @@ import logging
3
3
  import mimetypes
4
4
  import os
5
5
  import shutil
6
+ import stat
6
7
  import subprocess
7
8
  import sys
8
9
  from dataclasses import dataclass
9
10
  from pathlib import Path
10
11
  from typing import Any, NamedTuple
11
12
 
13
+ import aioshutil
12
14
  from binaryornot.check import is_binary
13
15
  from rich.console import Console
14
16
 
@@ -24,9 +26,15 @@ from griptape_nodes.retained_mode.events.os_events import (
24
26
  CreateFileRequest,
25
27
  CreateFileResultFailure,
26
28
  CreateFileResultSuccess,
29
+ DeleteFileRequest,
30
+ DeleteFileResultFailure,
31
+ DeleteFileResultSuccess,
27
32
  ExistingFilePolicy,
28
33
  FileIOFailureReason,
29
34
  FileSystemEntry,
35
+ GetFileInfoRequest,
36
+ GetFileInfoResultFailure,
37
+ GetFileInfoResultSuccess,
30
38
  ListDirectoryRequest,
31
39
  ListDirectoryResultFailure,
32
40
  ListDirectoryResultSuccess,
@@ -137,6 +145,14 @@ class OSManager:
137
145
  request_type=CopyFileRequest, callback=self.on_copy_file_request
138
146
  )
139
147
 
148
+ event_manager.assign_manager_to_request_type(
149
+ request_type=DeleteFileRequest, callback=self.on_delete_file_request
150
+ )
151
+
152
+ event_manager.assign_manager_to_request_type(
153
+ request_type=GetFileInfoRequest, callback=self.on_get_file_info_request
154
+ )
155
+
140
156
  # Register for app initialization event to setup system resources
141
157
  event_manager.add_listener_to_app_event(AppInitializationComplete, self.on_app_initialization_complete)
142
158
 
@@ -518,10 +534,16 @@ class OSManager:
518
534
  if not request.show_hidden and entry.name.startswith("."):
519
535
  continue
520
536
 
537
+ # Apply pattern filter if specified
538
+ if request.pattern is not None and not entry.match(request.pattern):
539
+ continue
540
+
521
541
  try:
522
542
  stat = entry.stat()
523
543
  # Get path relative to workspace if within workspace
524
- _is_entry_in_workspace, entry_path = self._validate_workspace_path(entry)
544
+ _, entry_path = self._validate_workspace_path(entry)
545
+ # Also get absolute resolved path
546
+ absolute_resolved_path = str(entry.resolve())
525
547
  mime_type = self._detect_mime_type(entry)
526
548
  entries.append(
527
549
  FileSystemEntry(
@@ -531,6 +553,7 @@ class OSManager:
531
553
  size=stat.st_size,
532
554
  modified_time=stat.st_mtime,
533
555
  mime_type=mime_type,
556
+ absolute_path=absolute_resolved_path,
534
557
  )
535
558
  )
536
559
  except (OSError, PermissionError) as e:
@@ -1320,6 +1343,154 @@ class OSManager:
1320
1343
  result_details=f"File copied successfully: {source_path} -> {destination_path}",
1321
1344
  )
1322
1345
 
1346
+ @staticmethod
1347
+ def remove_readonly(func, path, excinfo) -> None: # noqa: ANN001, ARG004
1348
+ """Handles read-only files and long paths on Windows during shutil.rmtree.
1349
+
1350
+ https://stackoverflow.com/a/50924863
1351
+ """
1352
+ if not GriptapeNodes.OSManager().is_windows():
1353
+ return
1354
+
1355
+ long_path = Path(GriptapeNodes.OSManager().normalize_path_for_platform(Path(path)))
1356
+
1357
+ try:
1358
+ Path.chmod(long_path, stat.S_IWRITE)
1359
+ func(long_path)
1360
+ except Exception as e:
1361
+ console.print(f"[red]Error removing read-only file: {path}[/red]")
1362
+ console.print(f"[red]Details: {e}[/red]")
1363
+ raise
1364
+
1365
+ async def on_delete_file_request(self, request: DeleteFileRequest) -> ResultPayload: # noqa: PLR0911, PLR0912, C901
1366
+ """Handle a request to delete a file or directory."""
1367
+ # FAILURE CASES FIRST (per CLAUDE.md)
1368
+
1369
+ # Validate exactly one of path or file_entry provided and determine path to delete
1370
+ if request.path is not None and request.file_entry is not None:
1371
+ msg = "Attempted to delete file with both path and file_entry. Failed due to invalid parameters"
1372
+ return DeleteFileResultFailure(failure_reason=FileIOFailureReason.INVALID_PATH, result_details=msg)
1373
+
1374
+ if request.path is not None:
1375
+ path_to_delete = request.path
1376
+ elif request.file_entry is not None:
1377
+ path_to_delete = request.file_entry.path
1378
+ else:
1379
+ msg = "Attempted to delete file with neither path nor file_entry. Failed due to invalid parameters"
1380
+ return DeleteFileResultFailure(failure_reason=FileIOFailureReason.INVALID_PATH, result_details=msg)
1381
+
1382
+ # Resolve and validate path
1383
+ try:
1384
+ resolved_path = self._resolve_file_path(path_to_delete, workspace_only=request.workspace_only is True)
1385
+ except (ValueError, RuntimeError) as e:
1386
+ msg = f"Attempted to delete file at path {path_to_delete}. Failed due to invalid path: {e}"
1387
+ return DeleteFileResultFailure(failure_reason=FileIOFailureReason.INVALID_PATH, result_details=msg)
1388
+
1389
+ # Check if path exists
1390
+ if not resolved_path.exists():
1391
+ msg = f"Attempted to delete file at path {path_to_delete}. Failed due to path not found"
1392
+ return DeleteFileResultFailure(failure_reason=FileIOFailureReason.FILE_NOT_FOUND, result_details=msg)
1393
+
1394
+ # Determine if this is a directory
1395
+ is_directory = resolved_path.is_dir()
1396
+
1397
+ # Collect all paths that will be deleted (for reporting)
1398
+ if is_directory:
1399
+ # Collect all file and directory paths before deletion
1400
+ deleted_paths = [str(item) for item in resolved_path.rglob("*")]
1401
+ deleted_paths.append(str(resolved_path))
1402
+ else:
1403
+ deleted_paths = [str(resolved_path)]
1404
+
1405
+ # Perform deletion
1406
+ try:
1407
+ if is_directory:
1408
+ await aioshutil.rmtree(resolved_path, onexc=OSManager.remove_readonly)
1409
+ else:
1410
+ resolved_path.unlink()
1411
+ except PermissionError as e:
1412
+ msg = f"Attempted to delete {'directory' if is_directory else 'file'} at path {path_to_delete}. Failed due to permission denied: {e}"
1413
+ return DeleteFileResultFailure(failure_reason=FileIOFailureReason.PERMISSION_DENIED, result_details=msg)
1414
+ except OSError as e:
1415
+ msg = f"Attempted to delete {'directory' if is_directory else 'file'} at path {path_to_delete}. Failed due to I/O error: {e}"
1416
+ return DeleteFileResultFailure(failure_reason=FileIOFailureReason.IO_ERROR, result_details=msg)
1417
+ except Exception as e:
1418
+ msg = f"Attempted to delete {'directory' if is_directory else 'file'} at path {path_to_delete}. Failed due to unexpected error: {type(e).__name__}: {e}"
1419
+ return DeleteFileResultFailure(failure_reason=FileIOFailureReason.UNKNOWN, result_details=msg)
1420
+
1421
+ # SUCCESS PATH AT END
1422
+ return DeleteFileResultSuccess(
1423
+ deleted_path=str(resolved_path),
1424
+ was_directory=is_directory,
1425
+ deleted_paths=deleted_paths,
1426
+ result_details=f"Successfully deleted {'directory' if is_directory else 'file'} at path {path_to_delete}",
1427
+ )
1428
+
1429
+ def on_get_file_info_request( # noqa: PLR0911
1430
+ self, request: GetFileInfoRequest
1431
+ ) -> GetFileInfoResultSuccess | GetFileInfoResultFailure:
1432
+ """Handle a request to get file/directory information."""
1433
+ # FAILURE CASES FIRST (per CLAUDE.md)
1434
+
1435
+ # Validate path provided
1436
+ if not request.path:
1437
+ msg = "Attempted to get file info with empty path. Failed due to invalid parameters"
1438
+ return GetFileInfoResultFailure(failure_reason=FileIOFailureReason.INVALID_PATH, result_details=msg)
1439
+
1440
+ # Resolve and validate path
1441
+ try:
1442
+ resolved_path = self._resolve_file_path(request.path, workspace_only=request.workspace_only is True)
1443
+ except (ValueError, RuntimeError) as e:
1444
+ msg = f"Attempted to get file info at path {request.path}. Failed due to invalid path: {e}"
1445
+ return GetFileInfoResultFailure(failure_reason=FileIOFailureReason.INVALID_PATH, result_details=msg)
1446
+
1447
+ # Check if path exists - if not, return success with None (file doesn't exist)
1448
+ if not resolved_path.exists():
1449
+ msg = f"File info retrieved for path {request.path}: file does not exist"
1450
+ return GetFileInfoResultSuccess(file_entry=None, result_details=msg)
1451
+
1452
+ # Get file information
1453
+ try:
1454
+ is_dir = resolved_path.is_dir()
1455
+ size = 0 if is_dir else resolved_path.stat().st_size
1456
+ modified_time = resolved_path.stat().st_mtime
1457
+
1458
+ # Get MIME type for files only
1459
+ mime_type = None
1460
+ if not is_dir:
1461
+ mime_type = self._detect_mime_type(resolved_path)
1462
+
1463
+ # Get path relative to workspace if within workspace
1464
+ _, file_path = self._validate_workspace_path(resolved_path)
1465
+
1466
+ # Also get absolute resolved path
1467
+ absolute_resolved_path = str(resolved_path.resolve())
1468
+
1469
+ file_entry = FileSystemEntry(
1470
+ name=resolved_path.name,
1471
+ path=str(file_path),
1472
+ is_dir=is_dir,
1473
+ size=size,
1474
+ modified_time=modified_time,
1475
+ mime_type=mime_type,
1476
+ absolute_path=absolute_resolved_path,
1477
+ )
1478
+ except PermissionError as e:
1479
+ msg = f"Attempted to get file info at path {request.path}. Failed due to permission denied: {e}"
1480
+ return GetFileInfoResultFailure(failure_reason=FileIOFailureReason.PERMISSION_DENIED, result_details=msg)
1481
+ except OSError as e:
1482
+ msg = f"Attempted to get file info at path {request.path}. Failed due to I/O error: {e}"
1483
+ return GetFileInfoResultFailure(failure_reason=FileIOFailureReason.IO_ERROR, result_details=msg)
1484
+ except Exception as e:
1485
+ msg = f"Attempted to get file info at path {request.path}. Failed due to unexpected error: {type(e).__name__}: {e}"
1486
+ return GetFileInfoResultFailure(failure_reason=FileIOFailureReason.UNKNOWN, result_details=msg)
1487
+
1488
+ # SUCCESS PATH AT END
1489
+ return GetFileInfoResultSuccess(
1490
+ file_entry=file_entry,
1491
+ result_details=f"Successfully retrieved file info for path {request.path}",
1492
+ )
1493
+
1323
1494
  def _validate_copy_tree_paths(
1324
1495
  self, source_str: str, dest_str: str, *, dirs_exist_ok: bool
1325
1496
  ) -> CopyTreeValidationResult | CopyTreeResultFailure:
@@ -217,6 +217,11 @@ class Settings(BaseModel):
217
217
  default="synced_workflows",
218
218
  description="Path to the synced workflows directory, relative to the workspace directory.",
219
219
  )
220
+ thread_storage_backend: Literal["local", "gtc"] = Field(
221
+ category=STORAGE,
222
+ default="local",
223
+ description="Storage backend for conversation threads: 'local' for filesystem or 'gtc' for Griptape Cloud",
224
+ )
220
225
  enable_workspace_file_watching: bool = Field(
221
226
  category=FILE_SYSTEM,
222
227
  default=True,
@@ -17,14 +17,29 @@ from griptape_nodes.retained_mode.events.library_events import (
17
17
  GetLibraryMetadataResultSuccess,
18
18
  GetNodeMetadataFromLibraryRequest,
19
19
  GetNodeMetadataFromLibraryResultSuccess,
20
+ ListNodeTypesInLibraryRequest,
21
+ ListNodeTypesInLibraryResultSuccess,
22
+ ListRegisteredLibrariesRequest,
23
+ ListRegisteredLibrariesResultSuccess,
20
24
  )
21
25
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
26
+ from griptape_nodes.retained_mode.managers.fitness_problems.libraries.deprecated_node_warning_problem import (
27
+ DeprecatedNodeWarningProblem,
28
+ )
29
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.deprecated_node_in_workflow_problem import (
30
+ DeprecatedNodeInWorkflowProblem,
31
+ )
32
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.node_type_not_found_problem import (
33
+ NodeTypeNotFoundProblem,
34
+ )
22
35
  from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
23
36
 
24
37
  if TYPE_CHECKING:
25
38
  from griptape_nodes.node_library.library_registry import LibrarySchema
26
39
  from griptape_nodes.node_library.workflow_registry import WorkflowMetadata
27
40
  from griptape_nodes.retained_mode.managers.event_manager import EventManager
41
+ from griptape_nodes.retained_mode.managers.fitness_problems.libraries.library_problem import LibraryProblem
42
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.workflow_problem import WorkflowProblem
28
43
  from griptape_nodes.retained_mode.managers.workflow_manager import WorkflowManager
29
44
 
30
45
  logger = logging.getLogger("griptape_nodes")
@@ -33,7 +48,7 @@ logger = logging.getLogger("griptape_nodes")
33
48
  class LibraryVersionCompatibilityIssue(NamedTuple):
34
49
  """Represents a library version compatibility issue found in a library."""
35
50
 
36
- message: str
51
+ problem: LibraryProblem
37
52
  severity: LibraryStatus
38
53
 
39
54
 
@@ -52,7 +67,7 @@ class LibraryVersionCompatibilityCheck(ABC):
52
67
  class WorkflowVersionCompatibilityIssue(NamedTuple):
53
68
  """Represents a workflow version compatibility issue found in a workflow."""
54
69
 
55
- message: str
70
+ problem: WorkflowProblem
56
71
  severity: WorkflowManager.WorkflowStatus
57
72
 
58
73
 
@@ -165,7 +180,12 @@ class VersionCompatibilityManager:
165
180
  """Check a library for deprecated nodes."""
166
181
  return [
167
182
  LibraryVersionCompatibilityIssue(
168
- message=f"Node '{node.metadata.display_name}' (class: {node.class_name}) is deprecated and {'will be removed in version ' + node.metadata.deprecation.removal_version if node.metadata.deprecation.removal_version else 'may be removed in future versions'}. {node.metadata.deprecation.deprecation_message or ''}".strip(),
183
+ problem=DeprecatedNodeWarningProblem(
184
+ display_name=node.metadata.display_name,
185
+ class_name=node.class_name,
186
+ removal_version=node.metadata.deprecation.removal_version,
187
+ deprecation_message=node.metadata.deprecation.deprecation_message,
188
+ ),
169
189
  severity=LibraryStatus.FLAWED,
170
190
  )
171
191
  for node in library_data.nodes
@@ -205,7 +225,7 @@ class VersionCompatibilityManager:
205
225
 
206
226
  return version_issues
207
227
 
208
- def _check_workflow_for_deprecated_nodes( # noqa: C901, PLR0912
228
+ def _check_workflow_for_deprecated_nodes( # noqa: C901
209
229
  self, workflow_metadata: WorkflowMetadata
210
230
  ) -> list[WorkflowVersionCompatibilityIssue]:
211
231
  """Check a workflow for deprecated nodes.
@@ -215,18 +235,33 @@ class VersionCompatibilityManager:
215
235
  """
216
236
  issues: list[WorkflowVersionCompatibilityIssue] = []
217
237
 
238
+ # Get list of registered libraries once (silent check - no error logging)
239
+ list_request = ListRegisteredLibrariesRequest()
240
+ list_result = GriptapeNodes.LibraryManager().on_list_registered_libraries_request(list_request)
241
+
242
+ if not isinstance(list_result, ListRegisteredLibrariesResultSuccess):
243
+ # Should not happen, but handle gracefully - return empty issues
244
+ return issues
245
+
246
+ registered_libraries = list_result.libraries
247
+
218
248
  for library_name_and_node_type in workflow_metadata.node_types_used:
219
249
  library_name = library_name_and_node_type.library_name
220
250
  node_type = library_name_and_node_type.node_type
221
251
 
222
- # Get library metadata to check if library exists and get version
252
+ # Check if library is registered
253
+ if library_name not in registered_libraries:
254
+ # Library not registered - skip this node silently, other checks handle missing libraries
255
+ continue
256
+
257
+ # Get library metadata to get version
223
258
  library_metadata_request = GetLibraryMetadataRequest(library=library_name)
224
259
  library_metadata_result = GriptapeNodes.LibraryManager().get_library_metadata_request(
225
260
  library_metadata_request
226
261
  )
227
262
 
228
263
  if not isinstance(library_metadata_result, GetLibraryMetadataResultSuccess):
229
- # Library not found - skip this node, other checks handle missing libraries
264
+ # Should not happen since we verified library exists, but handle gracefully
230
265
  continue
231
266
 
232
267
  current_library_version = library_metadata_result.metadata.library_version
@@ -238,29 +273,41 @@ class VersionCompatibilityManager:
238
273
  workflow_library_version = lib_ref.library_version
239
274
  break
240
275
 
241
- # Get node metadata from library
242
- node_metadata_request = GetNodeMetadataFromLibraryRequest(library=library_name, node_type=node_type)
243
- node_metadata_result = GriptapeNodes.LibraryManager().get_node_metadata_from_library_request(
244
- node_metadata_request
276
+ # Check if node type exists in library (silent check - no error logging)
277
+ list_node_types_request = ListNodeTypesInLibraryRequest(library=library_name)
278
+ list_node_types_result = GriptapeNodes.LibraryManager().on_list_node_types_in_library_request(
279
+ list_node_types_request
245
280
  )
246
281
 
247
- if not isinstance(node_metadata_result, GetNodeMetadataFromLibraryResultSuccess):
248
- # Node type doesn't exist in current library version
249
- message = f"This workflow uses node type '{node_type}' from library '{library_name}', but this node type is not found in the current library version {current_library_version}"
250
-
251
- if workflow_library_version:
252
- message += f". The workflow was saved with library version {workflow_library_version}"
253
-
254
- message += ". This node may have been removed or renamed. Contact the library author for more details."
282
+ if not isinstance(list_node_types_result, ListNodeTypesInLibraryResultSuccess):
283
+ # Should not happen since we verified library exists, but handle gracefully
284
+ continue
255
285
 
286
+ if node_type not in list_node_types_result.node_types:
287
+ # Node type doesn't exist in current library version
256
288
  issues.append(
257
289
  WorkflowVersionCompatibilityIssue(
258
- message=message,
290
+ problem=NodeTypeNotFoundProblem(
291
+ node_type=node_type,
292
+ library_name=library_name,
293
+ current_library_version=current_library_version,
294
+ workflow_library_version=workflow_library_version,
295
+ ),
259
296
  severity=GriptapeNodes.WorkflowManager().WorkflowStatus.FLAWED,
260
297
  )
261
298
  )
262
299
  continue
263
300
 
301
+ # Get node metadata from library (we know the node exists now)
302
+ node_metadata_request = GetNodeMetadataFromLibraryRequest(library=library_name, node_type=node_type)
303
+ node_metadata_result = GriptapeNodes.LibraryManager().get_node_metadata_from_library_request(
304
+ node_metadata_request
305
+ )
306
+
307
+ if not isinstance(node_metadata_result, GetNodeMetadataFromLibraryResultSuccess):
308
+ # Should not happen since we verified node exists, but handle gracefully
309
+ continue
310
+
264
311
  node_metadata = node_metadata_result.metadata
265
312
 
266
313
  if node_metadata.deprecation is None:
@@ -268,40 +315,18 @@ class VersionCompatibilityManager:
268
315
 
269
316
  deprecation = node_metadata.deprecation
270
317
 
271
- removal_version_reached = False
272
- if deprecation.removal_version:
273
- try:
274
- current_version = semver.VersionInfo.parse(current_library_version)
275
- removal_version = semver.VersionInfo.parse(deprecation.removal_version)
276
- removal_version_reached = current_version >= removal_version
277
- except Exception:
278
- # Errored out trying to parse the version strings; assume not reached.
279
- removal_version_reached = False
280
-
281
- # Build the complete message
282
- message = f"This workflow uses node '{node_metadata.display_name}' (class: {node_type}) from library '{library_name}', which is deprecated"
283
-
284
- if deprecation.removal_version:
285
- if removal_version_reached:
286
- message += f" and was removed in version {deprecation.removal_version}"
287
- else:
288
- message += f" and will be removed in version {deprecation.removal_version}"
289
- else:
290
- message += " and may be removed in future versions"
291
-
292
- message += f". You are currently using library version: {current_library_version}"
293
-
294
- if workflow_library_version:
295
- message += f", and the workflow was saved with library version: {workflow_library_version}"
296
-
297
- if deprecation.deprecation_message:
298
- message += f". The library author provided the following message for this deprecation: {deprecation.deprecation_message}"
299
- else:
300
- message += ". The library author did not provide a message explaining the deprecation. Contact the library author for details on how to remedy this."
301
-
318
+ # Create deprecated node problem
302
319
  issues.append(
303
320
  WorkflowVersionCompatibilityIssue(
304
- message=message,
321
+ problem=DeprecatedNodeInWorkflowProblem(
322
+ node_display_name=node_metadata.display_name,
323
+ node_type=node_type,
324
+ library_name=library_name,
325
+ current_library_version=current_library_version,
326
+ workflow_library_version=workflow_library_version,
327
+ removal_version=deprecation.removal_version,
328
+ deprecation_message=deprecation.deprecation_message,
329
+ ),
305
330
  severity=GriptapeNodes.WorkflowManager().WorkflowStatus.FLAWED,
306
331
  )
307
332
  )