griptape-nodes 0.41.0__py3-none-any.whl → 0.43.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 (133) hide show
  1. griptape_nodes/__init__.py +0 -0
  2. griptape_nodes/app/.python-version +0 -0
  3. griptape_nodes/app/__init__.py +1 -10
  4. griptape_nodes/app/api.py +199 -0
  5. griptape_nodes/app/app.py +140 -222
  6. griptape_nodes/app/watch.py +4 -2
  7. griptape_nodes/bootstrap/__init__.py +0 -0
  8. griptape_nodes/bootstrap/bootstrap_script.py +0 -0
  9. griptape_nodes/bootstrap/register_libraries_script.py +0 -0
  10. griptape_nodes/bootstrap/structure_config.yaml +0 -0
  11. griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
  12. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +0 -0
  13. griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +0 -0
  14. griptape_nodes/bootstrap/workflow_runners/__init__.py +0 -0
  15. griptape_nodes/bootstrap/workflow_runners/bootstrap_workflow_runner.py +0 -0
  16. griptape_nodes/bootstrap/workflow_runners/local_workflow_runner.py +0 -0
  17. griptape_nodes/bootstrap/workflow_runners/subprocess_workflow_runner.py +6 -2
  18. griptape_nodes/bootstrap/workflow_runners/workflow_runner.py +0 -0
  19. griptape_nodes/drivers/__init__.py +0 -0
  20. griptape_nodes/drivers/storage/__init__.py +0 -0
  21. griptape_nodes/drivers/storage/base_storage_driver.py +0 -0
  22. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +0 -0
  23. griptape_nodes/drivers/storage/local_storage_driver.py +5 -3
  24. griptape_nodes/drivers/storage/storage_backend.py +0 -0
  25. griptape_nodes/exe_types/__init__.py +0 -0
  26. griptape_nodes/exe_types/connections.py +0 -0
  27. griptape_nodes/exe_types/core_types.py +0 -0
  28. griptape_nodes/exe_types/flow.py +68 -368
  29. griptape_nodes/exe_types/node_types.py +17 -1
  30. griptape_nodes/exe_types/type_validator.py +0 -0
  31. griptape_nodes/machines/__init__.py +0 -0
  32. griptape_nodes/machines/control_flow.py +52 -20
  33. griptape_nodes/machines/fsm.py +16 -2
  34. griptape_nodes/machines/node_resolution.py +16 -14
  35. griptape_nodes/mcp_server/__init__.py +1 -0
  36. griptape_nodes/mcp_server/server.py +126 -0
  37. griptape_nodes/mcp_server/ws_request_manager.py +268 -0
  38. griptape_nodes/node_library/__init__.py +0 -0
  39. griptape_nodes/node_library/advanced_node_library.py +0 -0
  40. griptape_nodes/node_library/library_registry.py +0 -0
  41. griptape_nodes/node_library/workflow_registry.py +2 -2
  42. griptape_nodes/py.typed +0 -0
  43. griptape_nodes/retained_mode/__init__.py +0 -0
  44. griptape_nodes/retained_mode/events/__init__.py +0 -0
  45. griptape_nodes/retained_mode/events/agent_events.py +70 -8
  46. griptape_nodes/retained_mode/events/app_events.py +137 -12
  47. griptape_nodes/retained_mode/events/arbitrary_python_events.py +23 -0
  48. griptape_nodes/retained_mode/events/base_events.py +13 -31
  49. griptape_nodes/retained_mode/events/config_events.py +87 -11
  50. griptape_nodes/retained_mode/events/connection_events.py +56 -5
  51. griptape_nodes/retained_mode/events/context_events.py +27 -4
  52. griptape_nodes/retained_mode/events/execution_events.py +99 -14
  53. griptape_nodes/retained_mode/events/flow_events.py +165 -7
  54. griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
  55. griptape_nodes/retained_mode/events/library_events.py +195 -17
  56. griptape_nodes/retained_mode/events/logger_events.py +11 -0
  57. griptape_nodes/retained_mode/events/node_events.py +242 -22
  58. griptape_nodes/retained_mode/events/object_events.py +40 -4
  59. griptape_nodes/retained_mode/events/os_events.py +116 -3
  60. griptape_nodes/retained_mode/events/parameter_events.py +212 -8
  61. griptape_nodes/retained_mode/events/payload_registry.py +0 -0
  62. griptape_nodes/retained_mode/events/secrets_events.py +59 -7
  63. griptape_nodes/retained_mode/events/static_file_events.py +57 -4
  64. griptape_nodes/retained_mode/events/validation_events.py +39 -4
  65. griptape_nodes/retained_mode/events/workflow_events.py +188 -17
  66. griptape_nodes/retained_mode/griptape_nodes.py +89 -363
  67. griptape_nodes/retained_mode/managers/__init__.py +0 -0
  68. griptape_nodes/retained_mode/managers/agent_manager.py +49 -23
  69. griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
  70. griptape_nodes/retained_mode/managers/config_manager.py +0 -0
  71. griptape_nodes/retained_mode/managers/context_manager.py +0 -0
  72. griptape_nodes/retained_mode/managers/engine_identity_manager.py +146 -0
  73. griptape_nodes/retained_mode/managers/event_manager.py +14 -2
  74. griptape_nodes/retained_mode/managers/flow_manager.py +751 -64
  75. griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +45 -0
  76. griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +191 -0
  77. griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +346 -0
  78. griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +439 -0
  79. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +17 -0
  80. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +82 -0
  81. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +116 -0
  82. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +352 -0
  83. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +104 -0
  84. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +155 -0
  85. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +18 -0
  86. griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +12 -0
  87. griptape_nodes/retained_mode/managers/library_manager.py +255 -40
  88. griptape_nodes/retained_mode/managers/node_manager.py +120 -103
  89. griptape_nodes/retained_mode/managers/object_manager.py +11 -3
  90. griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
  91. griptape_nodes/retained_mode/managers/os_manager.py +582 -8
  92. griptape_nodes/retained_mode/managers/secrets_manager.py +4 -0
  93. griptape_nodes/retained_mode/managers/session_manager.py +328 -0
  94. griptape_nodes/retained_mode/managers/settings.py +7 -0
  95. griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
  96. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +2 -2
  97. griptape_nodes/retained_mode/managers/workflow_manager.py +722 -456
  98. griptape_nodes/retained_mode/retained_mode.py +44 -0
  99. griptape_nodes/retained_mode/utils/__init__.py +0 -0
  100. griptape_nodes/retained_mode/utils/engine_identity.py +141 -27
  101. griptape_nodes/retained_mode/utils/name_generator.py +0 -0
  102. griptape_nodes/traits/__init__.py +0 -0
  103. griptape_nodes/traits/add_param_button.py +0 -0
  104. griptape_nodes/traits/button.py +0 -0
  105. griptape_nodes/traits/clamp.py +0 -0
  106. griptape_nodes/traits/compare.py +0 -0
  107. griptape_nodes/traits/compare_images.py +0 -0
  108. griptape_nodes/traits/file_system_picker.py +127 -0
  109. griptape_nodes/traits/minmax.py +0 -0
  110. griptape_nodes/traits/options.py +0 -0
  111. griptape_nodes/traits/slider.py +0 -0
  112. griptape_nodes/traits/trait_registry.py +0 -0
  113. griptape_nodes/traits/traits.json +0 -0
  114. griptape_nodes/updater/__init__.py +2 -2
  115. griptape_nodes/updater/__main__.py +0 -0
  116. griptape_nodes/utils/__init__.py +0 -0
  117. griptape_nodes/utils/dict_utils.py +0 -0
  118. griptape_nodes/utils/image_preview.py +128 -0
  119. griptape_nodes/utils/metaclasses.py +0 -0
  120. griptape_nodes/version_compatibility/__init__.py +0 -0
  121. griptape_nodes/version_compatibility/versions/__init__.py +0 -0
  122. griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
  123. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +5 -5
  124. griptape_nodes-0.43.0.dist-info/METADATA +90 -0
  125. griptape_nodes-0.43.0.dist-info/RECORD +129 -0
  126. griptape_nodes-0.43.0.dist-info/WHEEL +4 -0
  127. {griptape_nodes-0.41.0.dist-info → griptape_nodes-0.43.0.dist-info}/entry_points.txt +1 -0
  128. griptape_nodes/app/app_sessions.py +0 -458
  129. griptape_nodes/retained_mode/utils/session_persistence.py +0 -105
  130. griptape_nodes-0.41.0.dist-info/METADATA +0 -78
  131. griptape_nodes-0.41.0.dist-info/RECORD +0 -112
  132. griptape_nodes-0.41.0.dist-info/WHEEL +0 -4
  133. griptape_nodes-0.41.0.dist-info/licenses/LICENSE +0 -201
@@ -0,0 +1,352 @@
1
+ """Local file library provenance implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ import os
8
+ import subprocess
9
+ import sys
10
+ from dataclasses import dataclass
11
+ from pathlib import Path
12
+ from typing import TYPE_CHECKING
13
+
14
+ from pydantic import BaseModel, Field, ValidationError
15
+
16
+ from griptape_nodes.node_library.library_registry import LibraryRegistry, LibrarySchema
17
+ from griptape_nodes.retained_mode.events.config_events import (
18
+ GetConfigCategoryRequest,
19
+ GetConfigCategoryResultSuccess,
20
+ SetConfigCategoryRequest,
21
+ SetConfigCategoryResultSuccess,
22
+ )
23
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
24
+ from griptape_nodes.retained_mode.managers.library_lifecycle.data_models import (
25
+ EvaluationResult,
26
+ InspectionResult,
27
+ InstallationResult,
28
+ LibraryLoadedResult,
29
+ LifecycleIssue,
30
+ )
31
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance.base import LibraryProvenance
32
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
33
+ from griptape_nodes.retained_mode.managers.os_manager import OSManager
34
+
35
+ if TYPE_CHECKING:
36
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_fsm import LibraryLifecycleContext
37
+
38
+ logger = logging.getLogger("griptape_nodes")
39
+
40
+
41
+ class LibraryPreferenceLocalFile(BaseModel):
42
+ """Serializable preference for a local file library."""
43
+
44
+ file_path: str = Field(description="Path to the library file")
45
+ active: bool = Field(default=True, description="Whether this local file library is active")
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class LibraryProvenanceLocalFile(LibraryProvenance):
50
+ """Reference to a local file library."""
51
+
52
+ file_path: str
53
+
54
+ def get_display_name(self) -> str:
55
+ """Get a human-readable name for this provenance."""
56
+ return f"Local file: {self.file_path}"
57
+
58
+ def inspect(self) -> InspectionResult:
59
+ """Inspect this local file to extract schema and identify issues."""
60
+ issues = []
61
+
62
+ # File system validation
63
+ if not self._validate_file_exists():
64
+ issues.append(
65
+ LifecycleIssue(
66
+ message=f"Library file does not exist or is not readable: {self.file_path}",
67
+ severity=LibraryStatus.UNUSABLE,
68
+ )
69
+ )
70
+ return InspectionResult(schema=None, issues=issues)
71
+
72
+ # Schema validation
73
+ try:
74
+ with Path(self.file_path).open(encoding="utf-8") as f:
75
+ raw_data = json.load(f)
76
+ except json.JSONDecodeError as e:
77
+ issues.append(LifecycleIssue(message=f"Invalid JSON in library file: {e}", severity=LibraryStatus.UNUSABLE))
78
+ return InspectionResult(schema=None, issues=issues)
79
+ except Exception as e:
80
+ issues.append(LifecycleIssue(message=f"Failed to read library file: {e}", severity=LibraryStatus.UNUSABLE))
81
+ return InspectionResult(schema=None, issues=issues)
82
+
83
+ # Validate library schema structure
84
+ try:
85
+ schema = LibrarySchema.model_validate(raw_data)
86
+ except ValidationError as e:
87
+ for error in e.errors():
88
+ loc = " -> ".join(map(str, error["loc"]))
89
+ msg = error["msg"]
90
+ error_type = error["type"]
91
+ problem = f"Error in section '{loc}': {error_type}, {msg}"
92
+ issues.append(LifecycleIssue(message=problem, severity=LibraryStatus.UNUSABLE))
93
+ return InspectionResult(schema=None, issues=issues)
94
+
95
+ return InspectionResult(schema=schema, issues=issues)
96
+
97
+ def evaluate(self, context: LibraryLifecycleContext) -> EvaluationResult:
98
+ """Evaluate this local file for conflicts/issues."""
99
+ issues = []
100
+
101
+ # Get schema from context (guaranteed to be valid at this point)
102
+ assert context.inspection_result is not None # noqa: S101
103
+ schema = context.inspection_result.schema
104
+ assert schema is not None # noqa: S101
105
+
106
+ # Version compatibility validation
107
+ version_issues = GriptapeNodes.VersionCompatibilityManager().check_library_version_compatibility(schema)
108
+ for issue in version_issues:
109
+ lifecycle_severity = LibraryStatus(issue.severity.value)
110
+ issues.append(LifecycleIssue(message=issue.message, severity=lifecycle_severity))
111
+
112
+ # NOTE: Library name conflicts are checked at the manager level
113
+ # across all evaluated libraries, not here
114
+
115
+ return EvaluationResult(issues=issues)
116
+
117
+ def install(self, context: LibraryLifecycleContext) -> InstallationResult:
118
+ """Install this local file library."""
119
+ problems = []
120
+ venv_path = ""
121
+
122
+ # Get the LibraryManager instance to use its methods
123
+ library_manager = GriptapeNodes.LibraryManager()
124
+
125
+ # Get library schema from context (guaranteed to be valid at this point)
126
+ assert context.inspection_result is not None # noqa: S101
127
+ library_data = context.inspection_result.schema
128
+ assert library_data is not None # noqa: S101
129
+
130
+ # If no dependencies are specified, early out
131
+ if not (
132
+ library_data.metadata
133
+ and library_data.metadata.dependencies
134
+ and library_data.metadata.dependencies.pip_dependencies
135
+ ):
136
+ return InstallationResult(
137
+ installation_path=self.file_path,
138
+ venv_path="",
139
+ issues=problems,
140
+ )
141
+
142
+ pip_install_flags = library_data.metadata.dependencies.pip_install_flags
143
+ if pip_install_flags is None:
144
+ pip_install_flags = []
145
+ pip_dependencies = library_data.metadata.dependencies.pip_dependencies
146
+
147
+ # Determine venv path for dependency installation
148
+ venv_path = library_manager._get_library_venv_path(library_data.name, self.file_path)
149
+
150
+ # Only install dependencies if conditions are met
151
+ library_venv_python_path = None
152
+ try:
153
+ library_venv_python_path = library_manager._init_library_venv(venv_path)
154
+ except RuntimeError as e:
155
+ problems.append(
156
+ LifecycleIssue(
157
+ message=str(e),
158
+ severity=LibraryStatus.UNUSABLE,
159
+ )
160
+ )
161
+ # Return early for blocking issues
162
+ return InstallationResult(
163
+ installation_path=self.file_path,
164
+ venv_path=str(venv_path) if venv_path else "",
165
+ issues=problems,
166
+ )
167
+
168
+ if library_venv_python_path and library_manager._can_write_to_venv_location(library_venv_python_path):
169
+ # Check disk space before installing dependencies
170
+ config_manager = GriptapeNodes.ConfigManager()
171
+ min_space_gb = config_manager.get_config_value("minimum_disk_space_gb_libraries")
172
+ if not OSManager.check_available_disk_space(Path(venv_path), min_space_gb):
173
+ error_msg = OSManager.format_disk_space_error(Path(venv_path))
174
+ problems.append(
175
+ LifecycleIssue(
176
+ message=f"Insufficient disk space for dependencies (requires {min_space_gb} GB): {error_msg}",
177
+ severity=LibraryStatus.UNUSABLE,
178
+ )
179
+ )
180
+ # Return early for blocking issues
181
+ return InstallationResult(
182
+ installation_path=self.file_path,
183
+ venv_path=str(venv_path) if venv_path else "",
184
+ issues=problems,
185
+ )
186
+
187
+ # Grab the python executable from the virtual environment so that we can pip install there
188
+ logger.info("Installing dependencies for library '%s' with pip in venv at %s", library_data.name, venv_path)
189
+ try:
190
+ subprocess.run( # noqa: S603
191
+ [
192
+ sys.executable,
193
+ "-m",
194
+ "uv",
195
+ "pip",
196
+ "install",
197
+ *pip_dependencies,
198
+ *pip_install_flags,
199
+ "--python",
200
+ str(library_venv_python_path),
201
+ ],
202
+ check=True,
203
+ capture_output=True,
204
+ text=True,
205
+ )
206
+ except subprocess.CalledProcessError as e:
207
+ error_details = f"return code={e.returncode}, stdout={e.stdout}, stderr={e.stderr}"
208
+ problems.append(
209
+ LifecycleIssue(
210
+ message=f"Dependency installation failed: {error_details}",
211
+ severity=LibraryStatus.FLAWED,
212
+ )
213
+ )
214
+ elif library_venv_python_path:
215
+ logger.debug(
216
+ "Skipping dependency installation for library '%s' - venv location at %s is not writable",
217
+ library_data.name,
218
+ venv_path,
219
+ )
220
+
221
+ return InstallationResult(
222
+ installation_path=self.file_path,
223
+ venv_path=str(venv_path) if venv_path else "",
224
+ issues=problems,
225
+ )
226
+
227
+ def load_library(self, context: LibraryLifecycleContext) -> LibraryLoadedResult:
228
+ """Load this local file library into the registry."""
229
+ issues = []
230
+
231
+ # Get the LibraryManager instance to use its methods
232
+ library_manager = GriptapeNodes.LibraryManager()
233
+
234
+ # Get library schema from context (guaranteed to be valid at this point)
235
+ assert context.inspection_result is not None # noqa: S101
236
+ library_data = context.inspection_result.schema
237
+ assert library_data is not None # noqa: S101
238
+
239
+ # Use the file path from this provenance
240
+ file_path = self.file_path
241
+ base_dir = Path(file_path).parent
242
+
243
+ # Load advanced library module if specified
244
+ advanced_library_instance = None
245
+ if library_data.advanced_library_path:
246
+ try:
247
+ advanced_library_instance = library_manager._load_advanced_library_module(
248
+ library_data=library_data,
249
+ base_dir=base_dir,
250
+ )
251
+ except Exception as err:
252
+ issues.append(
253
+ LifecycleIssue(
254
+ message=f"Failed to load Advanced Library module from '{library_data.advanced_library_path}': {err}",
255
+ severity=LibraryStatus.UNUSABLE,
256
+ )
257
+ )
258
+ return LibraryLoadedResult(issues=issues)
259
+
260
+ # Create or get the library
261
+ library = None
262
+ try:
263
+ # Try to create a new library
264
+ library = LibraryRegistry.generate_new_library(
265
+ library_data=library_data,
266
+ mark_as_default_library=False, # TODO(#1234): determine if this should be configurable
267
+ advanced_library=advanced_library_instance,
268
+ )
269
+ except KeyError:
270
+ # Library already exists
271
+ issues.append(
272
+ LifecycleIssue(
273
+ message="Failed because a library with this name was already registered. Check the Settings to ensure duplicate libraries are not being loaded.",
274
+ severity=LibraryStatus.UNUSABLE,
275
+ )
276
+ )
277
+ return LibraryLoadedResult(issues=issues)
278
+
279
+ # Handle library settings
280
+ if library_data.settings is not None:
281
+ # Assign them into the config space
282
+ for library_data_setting in library_data.settings:
283
+ # Does the category exist?
284
+ get_category_request = GetConfigCategoryRequest(category=library_data_setting.category)
285
+ get_category_result = GriptapeNodes.handle_request(get_category_request)
286
+ if not isinstance(get_category_result, GetConfigCategoryResultSuccess):
287
+ # That's OK, we'll invent it. Or at least we'll try.
288
+ create_new_category_request = SetConfigCategoryRequest(
289
+ category=library_data_setting.category, contents=library_data_setting.contents
290
+ )
291
+ create_new_category_result = GriptapeNodes.handle_request(create_new_category_request)
292
+ if not isinstance(create_new_category_result, SetConfigCategoryResultSuccess):
293
+ issues.append(
294
+ LifecycleIssue(
295
+ message=f"Failed to create new config category '{library_data_setting.category}'.",
296
+ severity=LibraryStatus.FLAWED,
297
+ )
298
+ )
299
+ continue # SKIP IT
300
+ else:
301
+ # We had an existing category. Union our changes into it (not replacing anything that matched).
302
+ existing_category_contents = get_category_result.contents
303
+ existing_category_contents.update(library_data_setting.contents)
304
+ set_category_request = SetConfigCategoryRequest(
305
+ category=library_data_setting.category, contents=existing_category_contents
306
+ )
307
+ set_category_result = GriptapeNodes.handle_request(set_category_request)
308
+ if not isinstance(set_category_result, SetConfigCategoryResultSuccess):
309
+ issues.append(
310
+ LifecycleIssue(
311
+ message=f"Failed to update config category '{library_data_setting.category}'.",
312
+ severity=LibraryStatus.FLAWED,
313
+ )
314
+ )
315
+ continue # SKIP IT
316
+
317
+ # Get library version from schema metadata
318
+ library_version = library_data.metadata.library_version
319
+
320
+ # Add the directory to the Python path to allow for relative imports
321
+ sys.path.insert(0, str(base_dir))
322
+
323
+ # Attempt to load nodes from the library
324
+ library_load_results = library_manager._attempt_load_nodes_from_library(
325
+ library_data=library_data,
326
+ library=library,
327
+ base_dir=base_dir,
328
+ library_file_path=file_path,
329
+ library_version=library_version,
330
+ problems=[], # We'll handle problems through issues instead
331
+ )
332
+
333
+ # Convert any problems from library_load_results to issues
334
+ if library_load_results.problems:
335
+ collated_problems = "\n".join(library_load_results.problems)
336
+ issues.append(
337
+ LifecycleIssue(
338
+ message=collated_problems,
339
+ severity=library_load_results.status,
340
+ )
341
+ )
342
+
343
+ return LibraryLoadedResult(issues=issues)
344
+
345
+ def _validate_file_exists(self) -> bool:
346
+ """Validate that the library file exists and is readable."""
347
+ try:
348
+ path = Path(self.file_path)
349
+ return path.exists() and path.is_file() and os.access(path, os.R_OK)
350
+ except Exception as e:
351
+ logger.error("Failed to validate file %s: %s", self.file_path, e)
352
+ return False
@@ -0,0 +1,104 @@
1
+ """Package library provenance implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING
8
+
9
+ from xdg_base_dirs import xdg_data_home
10
+
11
+ from griptape_nodes.retained_mode.managers.library_lifecycle.data_models import (
12
+ EvaluationResult,
13
+ InspectionResult,
14
+ InstallationResult,
15
+ LibraryLoadedResult,
16
+ LifecycleIssue,
17
+ )
18
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance.base import LibraryProvenance
19
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
20
+
21
+ if TYPE_CHECKING:
22
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_fsm import LibraryLifecycleContext
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class LibraryProvenancePackage(LibraryProvenance):
27
+ """Reference to a package library."""
28
+
29
+ requirement_specifier: str
30
+
31
+ def get_display_name(self) -> str:
32
+ """Get a human-readable name for this provenance."""
33
+ return f"Package: {self.requirement_specifier}"
34
+
35
+ def inspect(self) -> InspectionResult:
36
+ """Inspect this package to extract schema and identify issues."""
37
+ # TODO: Implement package inspection (https://github.com/griptape-ai/griptape-nodes/issues/1234)
38
+ # This should:
39
+ # 1. Check if package is available in PyPI or other repositories
40
+ # 2. Download and inspect package metadata
41
+ # 3. Extract library schema from package
42
+
43
+ return InspectionResult(
44
+ schema=None,
45
+ issues=[
46
+ LifecycleIssue(
47
+ message=f"Package inspection not yet implemented for {self.requirement_specifier}",
48
+ severity=LibraryStatus.UNUSABLE,
49
+ )
50
+ ],
51
+ )
52
+
53
+ def evaluate(self, context: LibraryLifecycleContext) -> EvaluationResult: # noqa: ARG002
54
+ """Evaluate this package for conflicts/issues."""
55
+ issues = []
56
+ issues.append(
57
+ LifecycleIssue(
58
+ message="Package evaluation not yet implemented",
59
+ severity=LibraryStatus.UNUSABLE,
60
+ )
61
+ )
62
+ return EvaluationResult(issues=issues)
63
+
64
+ def install(self, context: LibraryLifecycleContext) -> InstallationResult: # noqa: ARG002
65
+ """Install this package library."""
66
+ issues = []
67
+ issues.append(
68
+ LifecycleIssue(
69
+ message="Package installation not yet implemented",
70
+ severity=LibraryStatus.UNUSABLE,
71
+ )
72
+ )
73
+
74
+ # TODO: Implement package installation (https://github.com/griptape-ai/griptape-nodes/issues/1234)
75
+ # This should:
76
+ # 1. Create virtual environment
77
+ # 2. Install package using pip
78
+ # 3. Extract library files from installed package
79
+
80
+ return InstallationResult(
81
+ installation_path="",
82
+ venv_path="",
83
+ issues=issues,
84
+ )
85
+
86
+ def load_library(self, context: LibraryLifecycleContext) -> LibraryLoadedResult: # noqa: ARG002
87
+ """Load this package library into the registry."""
88
+ issues = []
89
+ issues.append(
90
+ LifecycleIssue(
91
+ message="Package loading not yet implemented",
92
+ severity=LibraryStatus.UNUSABLE,
93
+ )
94
+ )
95
+
96
+ return LibraryLoadedResult(issues=issues)
97
+
98
+ def _get_base_venv_directory(self) -> str:
99
+ """Get the base directory for virtual environments."""
100
+ return str(xdg_data_home() / "griptape_nodes" / "library_venvs")
101
+
102
+ def _ensure_venv_directory_exists(self, venv_dir: str) -> None:
103
+ """Ensure the virtual environment directory exists."""
104
+ Path(venv_dir).mkdir(parents=True, exist_ok=True)
@@ -0,0 +1,155 @@
1
+ """Sandbox library provenance implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import os
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING
10
+
11
+ from pydantic import BaseModel, Field
12
+
13
+ from griptape_nodes.node_library.library_registry import LibraryMetadata, LibrarySchema
14
+ from griptape_nodes.retained_mode.managers.library_lifecycle.data_models import (
15
+ EvaluationResult,
16
+ InspectionResult,
17
+ InstallationResult,
18
+ LibraryLoadedResult,
19
+ LifecycleIssue,
20
+ )
21
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance.base import LibraryProvenance
22
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
23
+
24
+ if TYPE_CHECKING:
25
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_fsm import LibraryLifecycleContext
26
+
27
+ logger = logging.getLogger("griptape_nodes")
28
+
29
+
30
+ class LibraryPreferenceSandbox(BaseModel):
31
+ """Serializable preference for a sandbox library directory."""
32
+
33
+ directory_path: str = Field(description="Path to the sandbox library directory")
34
+ active: bool = Field(default=True, description="Whether this sandbox library is active")
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class LibraryProvenanceSandbox(LibraryProvenance):
39
+ """Reference to a sandbox library (dynamically assembled from node files)."""
40
+
41
+ sandbox_path: str
42
+
43
+ def get_display_name(self) -> str:
44
+ """Get a human-readable name for this provenance."""
45
+ return f"Sandbox: {self.sandbox_path}"
46
+
47
+ def inspect(self) -> InspectionResult:
48
+ """Inspect this sandbox to dynamically create schema from node files."""
49
+ if not self._validate_sandbox_path():
50
+ return InspectionResult(
51
+ schema=None,
52
+ issues=[
53
+ LifecycleIssue(
54
+ message=f"Sandbox directory does not exist or is not readable: {self.sandbox_path}",
55
+ severity=LibraryStatus.UNUSABLE,
56
+ )
57
+ ],
58
+ )
59
+
60
+ # TODO: Implement dynamic library schema creation from node files (https://github.com/griptape-ai/griptape-nodes/issues/1234)
61
+ # This should:
62
+ # 1. Scan the sandbox directory for Python files
63
+ # 2. Extract node class definitions and metadata
64
+ # 3. Dynamically create LibrarySchema with discovered nodes
65
+ # 4. Generate appropriate categories and metadata
66
+
67
+ # For now, return a basic schema structure
68
+ # This is a placeholder that should be replaced with actual node discovery
69
+ sandbox_name = Path(self.sandbox_path).name
70
+
71
+ # Create minimal metadata for sandbox
72
+ metadata = LibraryMetadata(
73
+ author="Sandbox Developer",
74
+ description=f"Dynamically discovered sandbox library from {sandbox_name}",
75
+ library_version="dev",
76
+ engine_version="1.0.0",
77
+ tags=["sandbox", "development"],
78
+ )
79
+
80
+ # Create basic schema - this should be replaced with actual node discovery
81
+ schema = LibrarySchema(
82
+ name=sandbox_name,
83
+ library_schema_version="1.0.0",
84
+ metadata=metadata,
85
+ categories=[], # Should be populated from discovered nodes
86
+ nodes=[], # Should be populated from discovered nodes
87
+ )
88
+
89
+ return InspectionResult(schema=schema, issues=[])
90
+
91
+ def evaluate(self, context: LibraryLifecycleContext) -> EvaluationResult: # noqa: ARG002
92
+ """Evaluate this sandbox for conflicts/issues."""
93
+ issues = []
94
+
95
+ # Check if sandbox is still accessible
96
+ if not self._validate_sandbox_path():
97
+ issues.append(
98
+ LifecycleIssue(
99
+ message=f"Sandbox directory is no longer accessible: {self.sandbox_path}",
100
+ severity=LibraryStatus.UNUSABLE,
101
+ )
102
+ )
103
+ return EvaluationResult(issues=issues)
104
+
105
+ # TODO: Add sandbox-specific evaluation logic (https://github.com/griptape-ai/griptape-nodes/issues/1234)
106
+ # This could include:
107
+ # - Checking for naming conflicts with existing libraries
108
+ # - Validating node implementations
109
+ # - Checking for missing dependencies
110
+
111
+ return EvaluationResult(issues=issues)
112
+
113
+ def install(self, context: LibraryLifecycleContext) -> InstallationResult: # noqa: ARG002
114
+ """Install this sandbox library."""
115
+ issues = []
116
+
117
+ # Sandbox libraries don't need complex installation
118
+ # They're loaded directly from the sandbox directory
119
+ return InstallationResult(
120
+ installation_path=self.sandbox_path,
121
+ venv_path="",
122
+ issues=issues,
123
+ )
124
+
125
+ def load_library(self, context: LibraryLifecycleContext) -> LibraryLoadedResult:
126
+ """Load this sandbox library into the registry."""
127
+ issues = []
128
+
129
+ # Get library schema from context
130
+ library_schema = context.inspection_result.schema if context.inspection_result else None
131
+
132
+ if not library_schema or not library_schema.metadata:
133
+ issues.append(
134
+ LifecycleIssue(
135
+ message="No metadata available for loading",
136
+ severity=LibraryStatus.FLAWED,
137
+ )
138
+ )
139
+
140
+ # TODO: Actually register the sandbox library with the LibraryRegistry (https://github.com/griptape-ai/griptape-nodes/issues/1234)
141
+ # This would involve:
142
+ # 1. Creating a Library instance from the dynamically discovered nodes
143
+ # 2. Adding it to the LibraryRegistry
144
+ # 3. Handling any registration conflicts or errors
145
+
146
+ return LibraryLoadedResult(issues=issues)
147
+
148
+ def _validate_sandbox_path(self) -> bool:
149
+ """Validate that the sandbox path exists and is readable."""
150
+ try:
151
+ path = Path(self.sandbox_path)
152
+ return path.exists() and path.is_dir() and os.access(path, os.R_OK)
153
+ except Exception as e:
154
+ logger.error("Failed to validate sandbox path %s: %s", self.sandbox_path, e)
155
+ return False
@@ -0,0 +1,18 @@
1
+ """Library provenance classes for tracking library sources."""
2
+
3
+ # Re-export all provenance classes from their new location
4
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance.base import LibraryProvenance
5
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance.github import LibraryProvenanceGitHub
6
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance.local_file import (
7
+ LibraryProvenanceLocalFile,
8
+ )
9
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance.package import LibraryProvenancePackage
10
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance.sandbox import LibraryProvenanceSandbox
11
+
12
+ __all__ = [
13
+ "LibraryProvenance",
14
+ "LibraryProvenanceGitHub",
15
+ "LibraryProvenanceLocalFile",
16
+ "LibraryProvenancePackage",
17
+ "LibraryProvenanceSandbox",
18
+ ]
@@ -0,0 +1,12 @@
1
+ """Library status enumeration."""
2
+
3
+ from enum import StrEnum
4
+
5
+
6
+ class LibraryStatus(StrEnum):
7
+ """Status of the library that was attempted to be loaded."""
8
+
9
+ GOOD = "GOOD" # No errors detected during loading. Registered.
10
+ FLAWED = "FLAWED" # Some errors detected, but recoverable. Registered.
11
+ UNUSABLE = "UNUSABLE" # Errors detected and not recoverable. Not registered.
12
+ MISSING = "MISSING" # File not found. Not registered.