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.
- griptape_nodes/cli/commands/libraries.py +6 -21
- griptape_nodes/drivers/thread_storage/__init__.py +15 -0
- griptape_nodes/drivers/thread_storage/base_thread_storage_driver.py +106 -0
- griptape_nodes/drivers/thread_storage/griptape_cloud_thread_storage_driver.py +213 -0
- griptape_nodes/drivers/thread_storage/local_thread_storage_driver.py +137 -0
- griptape_nodes/drivers/thread_storage/thread_storage_backend.py +10 -0
- griptape_nodes/node_library/library_registry.py +16 -9
- griptape_nodes/node_library/workflow_registry.py +1 -1
- griptape_nodes/retained_mode/events/agent_events.py +232 -9
- griptape_nodes/retained_mode/events/library_events.py +32 -3
- griptape_nodes/retained_mode/events/os_events.py +101 -1
- griptape_nodes/retained_mode/managers/agent_manager.py +335 -135
- griptape_nodes/retained_mode/managers/fitness_problems/__init__.py +1 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +59 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/advanced_library_load_failure_problem.py +33 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/after_library_callback_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/before_library_callback_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/create_config_category_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/dependency_installation_failed_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/deprecated_node_warning_problem.py +83 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/duplicate_library_problem.py +28 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/duplicate_node_registration_problem.py +44 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/engine_version_error_problem.py +28 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/insufficient_disk_space_problem.py +33 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/invalid_version_string_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_json_decode_problem.py +28 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_load_exception_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_not_found_problem.py +30 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_problem.py +20 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_schema_exception_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_schema_validation_problem.py +38 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/modified_parameters_set_deprecation_warning_problem.py +44 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/modified_parameters_set_removed_problem.py +44 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_class_not_base_node_problem.py +40 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_class_not_found_problem.py +38 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_module_import_problem.py +53 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/sandbox_directory_missing_problem.py +28 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/ui_options_field_modified_incompatible_problem.py +44 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/ui_options_field_modified_warning_problem.py +35 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/update_config_category_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/venv_creation_failed_problem.py +32 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/__init__.py +75 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/deprecated_node_in_workflow_problem.py +83 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_dependency_version_string_problem.py +38 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_library_version_string_problem.py +38 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_metadata_schema_problem.py +31 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_metadata_section_count_problem.py +31 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_toml_format_problem.py +30 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_not_registered_problem.py +35 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_below_required_problem.py +41 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_large_difference_problem.py +41 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_major_mismatch_problem.py +41 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_minor_difference_problem.py +41 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_creation_date_problem.py +30 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_last_modified_date_problem.py +30 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_toml_section_problem.py +30 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/node_type_not_found_problem.py +51 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_not_found_problem.py +27 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_problem.py +20 -0
- griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_schema_version_problem.py +39 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +17 -3
- griptape_nodes/retained_mode/managers/library_manager.py +159 -75
- griptape_nodes/retained_mode/managers/os_manager.py +172 -1
- griptape_nodes/retained_mode/managers/settings.py +5 -0
- griptape_nodes/retained_mode/managers/version_compatibility_manager.py +76 -51
- griptape_nodes/retained_mode/managers/workflow_manager.py +154 -137
- griptape_nodes/servers/static.py +18 -19
- griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +16 -12
- griptape_nodes/version_compatibility/workflow_versions/v0_7_0/local_executor_argument_addition.py +6 -3
- {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.0.dist-info}/METADATA +2 -1
- {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.0.dist-info}/RECORD +73 -20
- {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.0.dist-info}/WHEEL +0 -0
- {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.0.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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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(
|
|
248
|
-
#
|
|
249
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
)
|