griptape-nodes 0.59.2__py3-none-any.whl → 0.60.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/common/macro_parser/__init__.py +28 -0
- griptape_nodes/common/macro_parser/core.py +230 -0
- griptape_nodes/common/macro_parser/exceptions.py +23 -0
- griptape_nodes/common/macro_parser/formats.py +170 -0
- griptape_nodes/common/macro_parser/matching.py +134 -0
- griptape_nodes/common/macro_parser/parsing.py +172 -0
- griptape_nodes/common/macro_parser/resolution.py +168 -0
- griptape_nodes/common/macro_parser/segments.py +42 -0
- griptape_nodes/exe_types/core_types.py +241 -4
- griptape_nodes/exe_types/node_types.py +7 -1
- griptape_nodes/exe_types/param_components/huggingface/__init__.py +1 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py +168 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py +38 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_parameter.py +33 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_utils.py +136 -0
- griptape_nodes/exe_types/param_components/log_parameter.py +136 -0
- griptape_nodes/exe_types/param_components/seed_parameter.py +59 -0
- griptape_nodes/exe_types/param_types/__init__.py +1 -0
- griptape_nodes/exe_types/param_types/parameter_bool.py +221 -0
- griptape_nodes/exe_types/param_types/parameter_float.py +179 -0
- griptape_nodes/exe_types/param_types/parameter_int.py +183 -0
- griptape_nodes/exe_types/param_types/parameter_number.py +380 -0
- griptape_nodes/exe_types/param_types/parameter_string.py +232 -0
- griptape_nodes/node_library/library_registry.py +2 -1
- griptape_nodes/retained_mode/events/app_events.py +21 -0
- griptape_nodes/retained_mode/events/os_events.py +142 -6
- griptape_nodes/retained_mode/events/parameter_events.py +2 -0
- griptape_nodes/retained_mode/griptape_nodes.py +14 -0
- griptape_nodes/retained_mode/managers/agent_manager.py +5 -3
- griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +19 -1
- griptape_nodes/retained_mode/managers/library_manager.py +27 -32
- griptape_nodes/retained_mode/managers/node_manager.py +14 -1
- griptape_nodes/retained_mode/managers/os_manager.py +403 -124
- griptape_nodes/retained_mode/managers/user_manager.py +120 -0
- griptape_nodes/retained_mode/managers/workflow_manager.py +44 -34
- griptape_nodes/traits/multi_options.py +26 -2
- griptape_nodes/utils/huggingface_utils.py +136 -0
- {griptape_nodes-0.59.2.dist-info → griptape_nodes-0.60.0.dist-info}/METADATA +1 -1
- {griptape_nodes-0.59.2.dist-info → griptape_nodes-0.60.0.dist-info}/RECORD +41 -18
- {griptape_nodes-0.59.2.dist-info → griptape_nodes-0.60.0.dist-info}/WHEEL +1 -1
- {griptape_nodes-0.59.2.dist-info → griptape_nodes-0.60.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
+
from enum import StrEnum
|
|
2
3
|
|
|
3
4
|
from griptape_nodes.retained_mode.events.base_events import (
|
|
4
5
|
RequestPayload,
|
|
@@ -9,6 +10,43 @@ from griptape_nodes.retained_mode.events.base_events import (
|
|
|
9
10
|
from griptape_nodes.retained_mode.events.payload_registry import PayloadRegistry
|
|
10
11
|
|
|
11
12
|
|
|
13
|
+
class ExistingFilePolicy(StrEnum):
|
|
14
|
+
"""Policy for handling existing files during write operations."""
|
|
15
|
+
|
|
16
|
+
OVERWRITE = "overwrite" # Replace existing file content
|
|
17
|
+
FAIL = "fail" # Fail if file exists
|
|
18
|
+
CREATE_NEW = "create_new" # Create new file with modified name (e.g., file_1.txt)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FileIOFailureReason(StrEnum):
|
|
22
|
+
"""Classification of file I/O failure reasons.
|
|
23
|
+
|
|
24
|
+
Used by read and write operations to provide structured error information.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# Policy violations
|
|
28
|
+
POLICY_NO_OVERWRITE = "policy_no_overwrite" # File exists and policy prohibits overwrite
|
|
29
|
+
POLICY_NO_CREATE_PARENT_DIRS = "policy_no_create_parent_dirs" # Parent dir missing and policy prohibits creation
|
|
30
|
+
|
|
31
|
+
# Permission/access errors
|
|
32
|
+
PERMISSION_DENIED = "permission_denied" # No read/write permission
|
|
33
|
+
FILE_NOT_FOUND = "file_not_found" # File doesn't exist (read operations)
|
|
34
|
+
|
|
35
|
+
# Resource errors
|
|
36
|
+
DISK_FULL = "disk_full" # Insufficient disk space
|
|
37
|
+
|
|
38
|
+
# Path errors
|
|
39
|
+
INVALID_PATH = "invalid_path" # Malformed or invalid path
|
|
40
|
+
IS_DIRECTORY = "is_directory" # Path is a directory, not a file
|
|
41
|
+
|
|
42
|
+
# Content errors
|
|
43
|
+
ENCODING_ERROR = "encoding_error" # Text encoding/decoding failed
|
|
44
|
+
|
|
45
|
+
# Generic errors
|
|
46
|
+
IO_ERROR = "io_error" # Generic I/O error
|
|
47
|
+
UNKNOWN = "unknown" # Unexpected error
|
|
48
|
+
|
|
49
|
+
|
|
12
50
|
@dataclass
|
|
13
51
|
class FileSystemEntry:
|
|
14
52
|
"""Represents a file or directory in the file system."""
|
|
@@ -50,7 +88,14 @@ class OpenAssociatedFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSucc
|
|
|
50
88
|
@dataclass
|
|
51
89
|
@PayloadRegistry.register
|
|
52
90
|
class OpenAssociatedFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
53
|
-
"""File or directory opening failed.
|
|
91
|
+
"""File or directory opening failed.
|
|
92
|
+
|
|
93
|
+
Attributes:
|
|
94
|
+
failure_reason: Classification of why the open failed
|
|
95
|
+
result_details: Human-readable error message (inherited from ResultPayloadFailure)
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
failure_reason: FileIOFailureReason
|
|
54
99
|
|
|
55
100
|
|
|
56
101
|
@dataclass
|
|
@@ -88,7 +133,14 @@ class ListDirectoryResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
|
|
|
88
133
|
@dataclass
|
|
89
134
|
@PayloadRegistry.register
|
|
90
135
|
class ListDirectoryResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
91
|
-
"""Directory listing failed.
|
|
136
|
+
"""Directory listing failed.
|
|
137
|
+
|
|
138
|
+
Attributes:
|
|
139
|
+
failure_reason: Classification of why the listing failed
|
|
140
|
+
result_details: Human-readable error message (inherited from ResultPayloadFailure)
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
failure_reason: FileIOFailureReason
|
|
92
144
|
|
|
93
145
|
|
|
94
146
|
@dataclass
|
|
@@ -105,6 +157,7 @@ class ReadFileRequest(RequestPayload):
|
|
|
105
157
|
encoding: Text encoding to use if file is detected as text (default: 'utf-8')
|
|
106
158
|
workspace_only: If True, constrain to workspace directory. If False, allow system-wide access.
|
|
107
159
|
If None, workspace constraints don't apply (e.g., cloud environments).
|
|
160
|
+
TODO: Remove workspace_only parameter - see https://github.com/griptape-ai/griptape-nodes/issues/2753
|
|
108
161
|
|
|
109
162
|
Results: ReadFileResultSuccess (with content) | ReadFileResultFailure (file not found, permission denied)
|
|
110
163
|
"""
|
|
@@ -112,7 +165,7 @@ class ReadFileRequest(RequestPayload):
|
|
|
112
165
|
file_path: str | None = None
|
|
113
166
|
file_entry: FileSystemEntry | None = None
|
|
114
167
|
encoding: str = "utf-8"
|
|
115
|
-
workspace_only: bool | None = True
|
|
168
|
+
workspace_only: bool | None = True # TODO: Remove - see https://github.com/griptape-ai/griptape-nodes/issues/2753
|
|
116
169
|
|
|
117
170
|
|
|
118
171
|
@dataclass
|
|
@@ -139,7 +192,14 @@ class ReadFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
|
|
|
139
192
|
@dataclass
|
|
140
193
|
@PayloadRegistry.register
|
|
141
194
|
class ReadFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
142
|
-
"""File reading failed.
|
|
195
|
+
"""File reading failed.
|
|
196
|
+
|
|
197
|
+
Attributes:
|
|
198
|
+
failure_reason: Classification of why the read failed
|
|
199
|
+
result_details: Human-readable error message (inherited from ResultPayloadFailure)
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
failure_reason: FileIOFailureReason
|
|
143
203
|
|
|
144
204
|
|
|
145
205
|
@dataclass
|
|
@@ -193,7 +253,14 @@ class CreateFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
|
|
|
193
253
|
@dataclass
|
|
194
254
|
@PayloadRegistry.register
|
|
195
255
|
class CreateFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
196
|
-
"""File/directory creation failed.
|
|
256
|
+
"""File/directory creation failed.
|
|
257
|
+
|
|
258
|
+
Attributes:
|
|
259
|
+
failure_reason: Classification of why the creation failed
|
|
260
|
+
result_details: Human-readable error message (inherited from ResultPayloadFailure)
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
failure_reason: FileIOFailureReason
|
|
197
264
|
|
|
198
265
|
|
|
199
266
|
@dataclass
|
|
@@ -229,4 +296,73 @@ class RenameFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
|
|
|
229
296
|
@dataclass
|
|
230
297
|
@PayloadRegistry.register
|
|
231
298
|
class RenameFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
232
|
-
"""File/directory rename failed.
|
|
299
|
+
"""File/directory rename failed.
|
|
300
|
+
|
|
301
|
+
Attributes:
|
|
302
|
+
failure_reason: Classification of why the rename failed
|
|
303
|
+
result_details: Human-readable error message (inherited from ResultPayloadFailure)
|
|
304
|
+
"""
|
|
305
|
+
|
|
306
|
+
failure_reason: FileIOFailureReason
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@dataclass
|
|
310
|
+
@PayloadRegistry.register
|
|
311
|
+
class WriteFileRequest(RequestPayload):
|
|
312
|
+
"""Write content to a file.
|
|
313
|
+
|
|
314
|
+
Automatically detects text vs binary mode based on content type.
|
|
315
|
+
|
|
316
|
+
Use when: Saving generated content, writing output files,
|
|
317
|
+
creating configuration files, writing binary data.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
file_path: Path to the file to write
|
|
321
|
+
content: Content to write (str for text files, bytes for binary files)
|
|
322
|
+
encoding: Text encoding for str content (default: 'utf-8', ignored for bytes)
|
|
323
|
+
append: If True, append to existing file; if False, use existing_file_policy (default: False)
|
|
324
|
+
existing_file_policy: How to handle existing files when append=False:
|
|
325
|
+
- "overwrite": Replace file content (default)
|
|
326
|
+
- "fail": Return failure if file exists
|
|
327
|
+
- "create_new": Create new file with modified name (NOT YET IMPLEMENTED)
|
|
328
|
+
create_parents: If True, create parent directories if missing (default: True)
|
|
329
|
+
|
|
330
|
+
Results: WriteFileResultSuccess | WriteFileResultFailure
|
|
331
|
+
|
|
332
|
+
Note: existing_file_policy is ignored when append=True (append always allows existing files)
|
|
333
|
+
"""
|
|
334
|
+
|
|
335
|
+
file_path: str
|
|
336
|
+
content: str | bytes
|
|
337
|
+
encoding: str = "utf-8" # Ignored for bytes
|
|
338
|
+
append: bool = False
|
|
339
|
+
existing_file_policy: ExistingFilePolicy = ExistingFilePolicy.OVERWRITE
|
|
340
|
+
create_parents: bool = True
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@dataclass
|
|
344
|
+
@PayloadRegistry.register
|
|
345
|
+
class WriteFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
|
|
346
|
+
"""File written successfully.
|
|
347
|
+
|
|
348
|
+
Attributes:
|
|
349
|
+
final_file_path: The actual path where file was written
|
|
350
|
+
(may differ from requested path if create_new policy used)
|
|
351
|
+
bytes_written: Number of bytes written to the file
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
final_file_path: str
|
|
355
|
+
bytes_written: int
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
@dataclass
|
|
359
|
+
@PayloadRegistry.register
|
|
360
|
+
class WriteFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
361
|
+
"""File write failed.
|
|
362
|
+
|
|
363
|
+
Attributes:
|
|
364
|
+
failure_reason: Classification of why the write failed
|
|
365
|
+
result_details: Human-readable error message (inherited from ResultPayloadFailure)
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
failure_reason: FileIOFailureReason
|
|
@@ -47,6 +47,7 @@ class AddParameterToNodeRequest(RequestPayload):
|
|
|
47
47
|
mode_allowed_output: Whether parameter can be used as output
|
|
48
48
|
is_user_defined: Whether this is a user-defined parameter (affects serialization)
|
|
49
49
|
parent_container_name: Name of parent container if nested
|
|
50
|
+
parent_element_name: Name of parent element if nested
|
|
50
51
|
initial_setup: Skip setup work when loading from file
|
|
51
52
|
settable: Whether parameter can be set directly by the user or not
|
|
52
53
|
|
|
@@ -71,6 +72,7 @@ class AddParameterToNodeRequest(RequestPayload):
|
|
|
71
72
|
is_user_defined: bool = Field(default=True)
|
|
72
73
|
settable: bool = Field(default=True)
|
|
73
74
|
parent_container_name: str | None = None
|
|
75
|
+
parent_element_name: str | None = None
|
|
74
76
|
# initial_setup prevents unnecessary work when we are loading a workflow from a file.
|
|
75
77
|
initial_setup: bool = False
|
|
76
78
|
|
|
@@ -58,6 +58,7 @@ if TYPE_CHECKING:
|
|
|
58
58
|
StaticFilesManager,
|
|
59
59
|
)
|
|
60
60
|
from griptape_nodes.retained_mode.managers.sync_manager import SyncManager
|
|
61
|
+
from griptape_nodes.retained_mode.managers.user_manager import UserManager
|
|
61
62
|
from griptape_nodes.retained_mode.managers.variable_manager import (
|
|
62
63
|
VariablesManager,
|
|
63
64
|
)
|
|
@@ -93,6 +94,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
93
94
|
_mcp_manager: MCPManager
|
|
94
95
|
_resource_manager: ResourceManager
|
|
95
96
|
_sync_manager: SyncManager
|
|
97
|
+
_user_manager: UserManager
|
|
96
98
|
|
|
97
99
|
def __init__(self) -> None:
|
|
98
100
|
from griptape_nodes.retained_mode.managers.agent_manager import AgentManager
|
|
@@ -120,6 +122,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
120
122
|
StaticFilesManager,
|
|
121
123
|
)
|
|
122
124
|
from griptape_nodes.retained_mode.managers.sync_manager import SyncManager
|
|
125
|
+
from griptape_nodes.retained_mode.managers.user_manager import UserManager
|
|
123
126
|
from griptape_nodes.retained_mode.managers.variable_manager import (
|
|
124
127
|
VariablesManager,
|
|
125
128
|
)
|
|
@@ -156,6 +159,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
156
159
|
self._session_manager = SessionManager(self._engine_identity_manager, self._event_manager)
|
|
157
160
|
self._mcp_manager = MCPManager(self._event_manager, self._config_manager)
|
|
158
161
|
self._sync_manager = SyncManager(self._event_manager, self._config_manager)
|
|
162
|
+
self._user_manager = UserManager(self._secrets_manager)
|
|
159
163
|
|
|
160
164
|
# Assign handlers now that these are created.
|
|
161
165
|
self._event_manager.assign_manager_to_request_type(
|
|
@@ -322,6 +326,10 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
322
326
|
def VariablesManager(cls) -> VariablesManager:
|
|
323
327
|
return GriptapeNodes.get_instance()._workflow_variables_manager
|
|
324
328
|
|
|
329
|
+
@classmethod
|
|
330
|
+
def UserManager(cls) -> UserManager:
|
|
331
|
+
return GriptapeNodes.get_instance()._user_manager
|
|
332
|
+
|
|
325
333
|
@classmethod
|
|
326
334
|
def clear_data(cls) -> None:
|
|
327
335
|
# Get canvas
|
|
@@ -374,6 +382,10 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
374
382
|
# Get engine name
|
|
375
383
|
engine_name = GriptapeNodes.EngineIdentityManager().engine_name
|
|
376
384
|
|
|
385
|
+
# Get user and organization
|
|
386
|
+
user = GriptapeNodes.UserManager().user
|
|
387
|
+
user_organization = GriptapeNodes.UserManager().user_organization
|
|
388
|
+
|
|
377
389
|
return EngineHeartbeatResultSuccess(
|
|
378
390
|
heartbeat_id=request.heartbeat_id,
|
|
379
391
|
engine_version=engine_version,
|
|
@@ -381,6 +393,8 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
381
393
|
engine_id=GriptapeNodes.EngineIdentityManager().active_engine_id,
|
|
382
394
|
session_id=GriptapeNodes.SessionManager().active_session_id,
|
|
383
395
|
timestamp=datetime.now(tz=UTC).isoformat(),
|
|
396
|
+
user=user,
|
|
397
|
+
user_organization=user_organization,
|
|
384
398
|
result_details="Engine heartbeat successful",
|
|
385
399
|
**instance_info,
|
|
386
400
|
**workflow_info,
|
|
@@ -208,8 +208,8 @@ class AgentManager:
|
|
|
208
208
|
def _create_agent(self, additional_mcp_servers: list[str] | None = None) -> Agent:
|
|
209
209
|
output_schema = Schema(
|
|
210
210
|
{
|
|
211
|
-
"generated_image_urls": [str],
|
|
212
211
|
"conversation_output": str,
|
|
212
|
+
"generated_image_urls": [str],
|
|
213
213
|
}
|
|
214
214
|
)
|
|
215
215
|
|
|
@@ -249,10 +249,11 @@ class AgentManager:
|
|
|
249
249
|
if url_artifact["type"] == "ImageUrlArtifact"
|
|
250
250
|
]
|
|
251
251
|
agent = self._create_agent(additional_mcp_servers=request.additional_mcp_servers)
|
|
252
|
-
|
|
252
|
+
event_stream = agent.run_stream([request.input, *artifacts])
|
|
253
253
|
full_result = ""
|
|
254
254
|
last_conversation_output = ""
|
|
255
|
-
|
|
255
|
+
last_event = None
|
|
256
|
+
for event in event_stream:
|
|
256
257
|
if isinstance(event, TextChunkEvent):
|
|
257
258
|
full_result += event.token
|
|
258
259
|
try:
|
|
@@ -273,6 +274,7 @@ class AgentManager:
|
|
|
273
274
|
last_conversation_output = new_conversation_output
|
|
274
275
|
except json.JSONDecodeError:
|
|
275
276
|
pass # Ignore incomplete JSON
|
|
277
|
+
last_event = event
|
|
276
278
|
if isinstance(last_event, FinishTaskEvent):
|
|
277
279
|
if isinstance(last_event.task_output, ErrorArtifact):
|
|
278
280
|
return RunAgentResultFailure(
|
|
@@ -40,7 +40,25 @@ class ArbitraryCodeExecManager:
|
|
|
40
40
|
try:
|
|
41
41
|
string_buffer = io.StringIO()
|
|
42
42
|
with redirect_stdout(string_buffer):
|
|
43
|
-
|
|
43
|
+
# Use a shared namespace for both globals and locals in exec() to make some behavior possible and more intuitive:
|
|
44
|
+
#
|
|
45
|
+
# 1. RECURSION: Without this namespace, recursive functions defined inside exec() fail with
|
|
46
|
+
# "NameError: name 'function_name' is not defined" when they try to call themselves.
|
|
47
|
+
# Why? When exec() runs with default parameters, functions defined in the exec'd code
|
|
48
|
+
# exist in this method's local scope. But inside the exec'd functions, Python looks in the program's
|
|
49
|
+
# global scope (outside this method) and the function's own local scope - neither of which
|
|
50
|
+
# contains the recursive function definition. By passing the same dict as both globals and locals,
|
|
51
|
+
# any function defined in exec'd code becomes visible in what exec'd code sees as
|
|
52
|
+
# "global" scope, allowing recursive calls to find the function definition.
|
|
53
|
+
#
|
|
54
|
+
# 2. ISOLATION: An isolated namespace prevents exec'd code from accessing or modifying
|
|
55
|
+
# variables in the outer program scope, protecting read/write access to sensitive engine data.
|
|
56
|
+
# For the PR that implements this behavior alongside an Execute Python and List Files node, see https://github.com/griptape-ai/griptape-nodes/pull/2087
|
|
57
|
+
|
|
58
|
+
namespace = {"__builtins__": __builtins__}
|
|
59
|
+
python_output = exec( # noqa: S102
|
|
60
|
+
request.python_string, namespace, namespace
|
|
61
|
+
)
|
|
44
62
|
|
|
45
63
|
captured_output = strip_ansi_codes(string_buffer.getvalue())
|
|
46
64
|
result = RunArbitraryPythonStringResultSuccess(
|
|
@@ -169,8 +169,6 @@ class LibraryManager:
|
|
|
169
169
|
self._library_event_handler_mappings: dict[type[Payload], dict[str, LibraryManager.RegisteredEventHandler]] = {}
|
|
170
170
|
# LibraryDirectory owns the FSMs and manages library lifecycle
|
|
171
171
|
self._library_directory = LibraryDirectory()
|
|
172
|
-
# Lock for synchronizing sys.path modifications during parallel library installation
|
|
173
|
-
self._sys_path_lock = asyncio.Lock()
|
|
174
172
|
|
|
175
173
|
event_manager.assign_manager_to_request_type(
|
|
176
174
|
ListRegisteredLibrariesRequest, self.on_list_registered_libraries_request
|
|
@@ -726,8 +724,7 @@ class LibraryManager:
|
|
|
726
724
|
# Get the directory containing the JSON file to resolve relative paths
|
|
727
725
|
base_dir = json_path.parent.absolute()
|
|
728
726
|
# Add the directory to the Python path to allow for relative imports
|
|
729
|
-
|
|
730
|
-
sys.path.insert(0, str(base_dir))
|
|
727
|
+
sys.path.insert(0, str(base_dir))
|
|
731
728
|
|
|
732
729
|
# Load the advanced library module if specified
|
|
733
730
|
advanced_library_instance = None
|
|
@@ -1060,8 +1057,7 @@ class LibraryManager:
|
|
|
1060
1057
|
)
|
|
1061
1058
|
)
|
|
1062
1059
|
)
|
|
1063
|
-
|
|
1064
|
-
sys.path.insert(0, site_packages)
|
|
1060
|
+
sys.path.insert(0, site_packages)
|
|
1065
1061
|
|
|
1066
1062
|
return library_venv_python_path
|
|
1067
1063
|
|
|
@@ -1503,27 +1499,6 @@ class LibraryManager:
|
|
|
1503
1499
|
|
|
1504
1500
|
return node_class
|
|
1505
1501
|
|
|
1506
|
-
async def _register_single_library(self, library_result: LoadLibraryMetadataFromFileResultSuccess) -> None:
|
|
1507
|
-
"""Register a single library (sandbox or config-based) and handle errors.
|
|
1508
|
-
|
|
1509
|
-
Args:
|
|
1510
|
-
library_result: The metadata result for the library to register
|
|
1511
|
-
"""
|
|
1512
|
-
try:
|
|
1513
|
-
if library_result.library_schema.name == LibraryManager.SANDBOX_LIBRARY_NAME:
|
|
1514
|
-
await self._attempt_generate_sandbox_library_from_schema(
|
|
1515
|
-
library_schema=library_result.library_schema, sandbox_directory=library_result.file_path
|
|
1516
|
-
)
|
|
1517
|
-
else:
|
|
1518
|
-
register_request = RegisterLibraryFromFileRequest(
|
|
1519
|
-
file_path=library_result.file_path, load_as_default_library=False
|
|
1520
|
-
)
|
|
1521
|
-
register_result = await self.register_library_from_file_request(register_request)
|
|
1522
|
-
if isinstance(register_result, RegisterLibraryFromFileResultFailure):
|
|
1523
|
-
logger.warning("Failed to register library from %s", library_result.file_path)
|
|
1524
|
-
except Exception as e:
|
|
1525
|
-
logger.warning("Failed to register library from %s with exception: %s", library_result.file_path, e)
|
|
1526
|
-
|
|
1527
1502
|
async def load_all_libraries_from_config(self) -> None:
|
|
1528
1503
|
# Load metadata for all libraries to determine which ones can be safely loaded
|
|
1529
1504
|
metadata_request = LoadMetadataForAllLibrariesRequest()
|
|
@@ -1543,10 +1518,23 @@ class LibraryManager:
|
|
|
1543
1518
|
problems=failed_library.problems,
|
|
1544
1519
|
)
|
|
1545
1520
|
|
|
1546
|
-
# Use
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1521
|
+
# Use metadata results to selectively load libraries
|
|
1522
|
+
for library_result in metadata_result.successful_libraries:
|
|
1523
|
+
if library_result.library_schema.name == LibraryManager.SANDBOX_LIBRARY_NAME:
|
|
1524
|
+
# Handle sandbox library - use the schema we already have
|
|
1525
|
+
await self._attempt_generate_sandbox_library_from_schema(
|
|
1526
|
+
library_schema=library_result.library_schema, sandbox_directory=library_result.file_path
|
|
1527
|
+
)
|
|
1528
|
+
else:
|
|
1529
|
+
# Handle config-based library - register it directly using the file path
|
|
1530
|
+
register_request = RegisterLibraryFromFileRequest(
|
|
1531
|
+
file_path=library_result.file_path, load_as_default_library=False
|
|
1532
|
+
)
|
|
1533
|
+
register_result = await self.register_library_from_file_request(register_request)
|
|
1534
|
+
if isinstance(register_result, RegisterLibraryFromFileResultFailure):
|
|
1535
|
+
# Registration failed - the failure info is already recorded in _library_file_path_to_info
|
|
1536
|
+
# by register_library_from_file_request, so we just log it here for visibility
|
|
1537
|
+
logger.warning("Failed to register library from %s", library_result.file_path)
|
|
1550
1538
|
|
|
1551
1539
|
# Print 'em all pretty
|
|
1552
1540
|
self.print_library_load_status()
|
|
@@ -1604,6 +1592,13 @@ class LibraryManager:
|
|
|
1604
1592
|
session_id = GriptapeNodes.get_session_id()
|
|
1605
1593
|
session_info = f" | Session: {session_id[:8]}..." if session_id else " | No Session"
|
|
1606
1594
|
|
|
1595
|
+
# Get user and organization
|
|
1596
|
+
user = GriptapeNodes.UserManager().user
|
|
1597
|
+
user_info = f" | User: {user.email if user else 'Not available'}"
|
|
1598
|
+
|
|
1599
|
+
user_organization = GriptapeNodes.UserManager().user_organization
|
|
1600
|
+
org_info = f" | Org: {user_organization.name if user_organization else 'Not available'}"
|
|
1601
|
+
|
|
1607
1602
|
nodes_app_url = os.getenv("GRIPTAPE_NODES_UI_BASE_URL", "https://nodes.griptape.ai")
|
|
1608
1603
|
message = Panel(
|
|
1609
1604
|
Align.center(
|
|
@@ -1612,7 +1607,7 @@ class LibraryManager:
|
|
|
1612
1607
|
vertical="middle",
|
|
1613
1608
|
),
|
|
1614
1609
|
title="Griptape Nodes Engine Started",
|
|
1615
|
-
subtitle=f"[green]{engine_version}{session_info}[/green]",
|
|
1610
|
+
subtitle=f"[green]Version: {engine_version}{session_info}{user_info}{org_info}[/green]",
|
|
1616
1611
|
border_style="green",
|
|
1617
1612
|
padding=(1, 4),
|
|
1618
1613
|
)
|
|
@@ -918,6 +918,7 @@ class NodeManager:
|
|
|
918
918
|
def on_add_parameter_to_node_request(self, request: AddParameterToNodeRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912, PLR0915
|
|
919
919
|
node_name = request.node_name
|
|
920
920
|
node = None
|
|
921
|
+
parent_group: ParameterGroup | None = None
|
|
921
922
|
|
|
922
923
|
if node_name is None:
|
|
923
924
|
# Get from the current context.
|
|
@@ -968,6 +969,15 @@ class NodeManager:
|
|
|
968
969
|
node_name=node_name,
|
|
969
970
|
result_details=f"Successfully added parameter '{new_param.name}' to container parameter '{request.parent_container_name}' in node '{node_name}'.",
|
|
970
971
|
)
|
|
972
|
+
if request.parent_element_name is not None:
|
|
973
|
+
parent_element = node.get_element_by_name_and_type(request.parent_element_name)
|
|
974
|
+
if parent_element is None:
|
|
975
|
+
details = f"Attempted to add Parameter to Parent Element '{request.parent_element_name}' in node '{node_name}'. Failed because element didn't exist."
|
|
976
|
+
result = AddParameterToNodeResultFailure(result_details=details)
|
|
977
|
+
return result
|
|
978
|
+
# Handle ParameterGroup parentage with potential to expand in future to other element types.
|
|
979
|
+
if isinstance(parent_element, ParameterGroup):
|
|
980
|
+
parent_group = parent_element
|
|
971
981
|
if request.parameter_name is None or request.tooltip is None:
|
|
972
982
|
details = f"Attempted to add Parameter to node '{node_name}'. Failed because default_value, tooltip, or parameter_name was not defined."
|
|
973
983
|
result = AddParameterToNodeResultFailure(result_details=details)
|
|
@@ -1032,6 +1042,7 @@ class NodeManager:
|
|
|
1032
1042
|
allowed_modes=allowed_modes,
|
|
1033
1043
|
ui_options=request.ui_options,
|
|
1034
1044
|
parent_container_name=request.parent_container_name,
|
|
1045
|
+
parent_element_name=parent_group.name if parent_group is not None else None,
|
|
1035
1046
|
settable=request.settable,
|
|
1036
1047
|
)
|
|
1037
1048
|
try:
|
|
@@ -1039,6 +1050,8 @@ class NodeManager:
|
|
|
1039
1050
|
parameter_parent = node.get_parameter_by_name(request.parent_container_name)
|
|
1040
1051
|
if parameter_parent is not None:
|
|
1041
1052
|
parameter_parent.add_child(new_param)
|
|
1053
|
+
elif parent_group is not None:
|
|
1054
|
+
parent_group.add_child(new_param)
|
|
1042
1055
|
else:
|
|
1043
1056
|
node.add_parameter(new_param)
|
|
1044
1057
|
except Exception as e:
|
|
@@ -2586,7 +2599,7 @@ class NodeManager:
|
|
|
2586
2599
|
) -> SerializedNodeCommands.IndirectSetParameterValueCommand | None:
|
|
2587
2600
|
try:
|
|
2588
2601
|
hash(value)
|
|
2589
|
-
value_id = value
|
|
2602
|
+
value_id = (type(value), value)
|
|
2590
2603
|
except TypeError:
|
|
2591
2604
|
# Couldn't get a hash. Use the object's ID
|
|
2592
2605
|
value_id = id(value)
|