griptape-nodes 0.52.1__py3-none-any.whl → 0.53.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/__init__.py +6 -943
- griptape_nodes/__main__.py +6 -0
- griptape_nodes/app/app.py +45 -61
- griptape_nodes/cli/__init__.py +1 -0
- griptape_nodes/cli/commands/__init__.py +1 -0
- griptape_nodes/cli/commands/config.py +71 -0
- griptape_nodes/cli/commands/engine.py +80 -0
- griptape_nodes/cli/commands/init.py +548 -0
- griptape_nodes/cli/commands/libraries.py +90 -0
- griptape_nodes/cli/commands/self.py +117 -0
- griptape_nodes/cli/main.py +46 -0
- griptape_nodes/cli/shared.py +84 -0
- griptape_nodes/common/__init__.py +1 -0
- griptape_nodes/common/directed_graph.py +55 -0
- griptape_nodes/drivers/storage/local_storage_driver.py +7 -2
- griptape_nodes/exe_types/core_types.py +60 -2
- griptape_nodes/exe_types/node_types.py +38 -24
- griptape_nodes/machines/control_flow.py +86 -22
- griptape_nodes/machines/fsm.py +10 -1
- griptape_nodes/machines/parallel_resolution.py +570 -0
- griptape_nodes/machines/{node_resolution.py → sequential_resolution.py} +22 -51
- griptape_nodes/retained_mode/events/base_events.py +2 -2
- griptape_nodes/retained_mode/events/node_events.py +4 -3
- griptape_nodes/retained_mode/griptape_nodes.py +25 -12
- griptape_nodes/retained_mode/managers/agent_manager.py +9 -5
- griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +3 -1
- griptape_nodes/retained_mode/managers/context_manager.py +6 -5
- griptape_nodes/retained_mode/managers/flow_manager.py +117 -204
- griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +1 -1
- griptape_nodes/retained_mode/managers/library_manager.py +35 -25
- griptape_nodes/retained_mode/managers/node_manager.py +81 -199
- griptape_nodes/retained_mode/managers/object_manager.py +11 -5
- griptape_nodes/retained_mode/managers/os_manager.py +24 -9
- griptape_nodes/retained_mode/managers/secrets_manager.py +8 -4
- griptape_nodes/retained_mode/managers/settings.py +32 -1
- griptape_nodes/retained_mode/managers/static_files_manager.py +8 -3
- griptape_nodes/retained_mode/managers/sync_manager.py +8 -5
- griptape_nodes/retained_mode/managers/workflow_manager.py +110 -122
- griptape_nodes/traits/add_param_button.py +1 -1
- griptape_nodes/traits/button.py +216 -6
- griptape_nodes/traits/color_picker.py +66 -0
- griptape_nodes/traits/traits.json +4 -0
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.53.0.dist-info}/METADATA +2 -1
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.53.0.dist-info}/RECORD +46 -32
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.53.0.dist-info}/WHEEL +0 -0
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.53.0.dist-info}/entry_points.txt +0 -0
|
@@ -10,6 +10,7 @@ from griptape_nodes.exe_types.core_types import (
|
|
|
10
10
|
from griptape_nodes.exe_types.flow import ControlFlow
|
|
11
11
|
from griptape_nodes.exe_types.node_types import BaseNode
|
|
12
12
|
from griptape_nodes.retained_mode.events.base_events import (
|
|
13
|
+
ResultDetails,
|
|
13
14
|
ResultPayload,
|
|
14
15
|
)
|
|
15
16
|
from griptape_nodes.retained_mode.events.execution_events import (
|
|
@@ -50,7 +51,10 @@ class ObjectManager:
|
|
|
50
51
|
def on_rename_object_request(self, request: RenameObjectRequest) -> ResultPayload:
|
|
51
52
|
# Does the source object exist?
|
|
52
53
|
if request.object_name == request.requested_name:
|
|
53
|
-
return RenameObjectResultSuccess(
|
|
54
|
+
return RenameObjectResultSuccess(
|
|
55
|
+
final_name=request.requested_name,
|
|
56
|
+
result_details=f"Object '{request.requested_name}' already has the requested name",
|
|
57
|
+
)
|
|
54
58
|
source_obj = self.attempt_get_object_by_name(request.object_name)
|
|
55
59
|
if source_obj is None:
|
|
56
60
|
details = f"Attempted to rename object '{request.object_name}', but no object of that name could be found."
|
|
@@ -97,8 +101,11 @@ class ObjectManager:
|
|
|
97
101
|
if final_name != request.requested_name:
|
|
98
102
|
details += " WARNING: Originally requested the name '{request.requested_name}', but that was taken."
|
|
99
103
|
log_level = logging.WARNING
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
if log_level == logging.WARNING:
|
|
105
|
+
result_details = ResultDetails(message=details, level="WARNING")
|
|
106
|
+
else:
|
|
107
|
+
result_details = details
|
|
108
|
+
return RenameObjectResultSuccess(final_name=final_name, result_details=result_details)
|
|
102
109
|
|
|
103
110
|
def on_clear_all_object_state_request(self, request: ClearAllObjectStateRequest) -> ResultPayload: # noqa: C901
|
|
104
111
|
if not request.i_know_what_im_doing:
|
|
@@ -148,8 +155,7 @@ class ObjectManager:
|
|
|
148
155
|
GriptapeNodes.VariablesManager().on_clear_object_state()
|
|
149
156
|
|
|
150
157
|
details = "Successfully cleared all object state (deleted everything)."
|
|
151
|
-
|
|
152
|
-
return ClearAllObjectStateResultSuccess()
|
|
158
|
+
return ClearAllObjectStateResultSuccess(result_details=details)
|
|
153
159
|
|
|
154
160
|
def get_filtered_subset[T](
|
|
155
161
|
self,
|
|
@@ -11,7 +11,7 @@ from typing import Any
|
|
|
11
11
|
from binaryornot.check import is_binary
|
|
12
12
|
from rich.console import Console
|
|
13
13
|
|
|
14
|
-
from griptape_nodes.retained_mode.events.base_events import ResultPayload
|
|
14
|
+
from griptape_nodes.retained_mode.events.base_events import ResultDetails, ResultPayload
|
|
15
15
|
from griptape_nodes.retained_mode.events.os_events import (
|
|
16
16
|
CreateFileRequest,
|
|
17
17
|
CreateFileResultFailure,
|
|
@@ -304,7 +304,7 @@ class OSManager:
|
|
|
304
304
|
logger.info(details)
|
|
305
305
|
return OpenAssociatedFileResultFailure(result_details=details)
|
|
306
306
|
|
|
307
|
-
return OpenAssociatedFileResultSuccess()
|
|
307
|
+
return OpenAssociatedFileResultSuccess(result_details="File opened successfully in associated application.")
|
|
308
308
|
except subprocess.CalledProcessError as e:
|
|
309
309
|
details = (
|
|
310
310
|
f"Process error when opening file: return code={e.returncode}, stdout={e.stdout}, stderr={e.stderr}"
|
|
@@ -399,11 +399,17 @@ class OSManager:
|
|
|
399
399
|
if request.workspace_only:
|
|
400
400
|
# In workspace mode, return relative path if within workspace, absolute if outside
|
|
401
401
|
return ListDirectoryResultSuccess(
|
|
402
|
-
entries=entries,
|
|
402
|
+
entries=entries,
|
|
403
|
+
current_path=str(relative_or_abs_path),
|
|
404
|
+
is_workspace_path=is_workspace_path,
|
|
405
|
+
result_details="Directory listing retrieved successfully.",
|
|
403
406
|
)
|
|
404
407
|
# In system-wide mode, always return the full absolute path
|
|
405
408
|
return ListDirectoryResultSuccess(
|
|
406
|
-
entries=entries,
|
|
409
|
+
entries=entries,
|
|
410
|
+
current_path=str(directory),
|
|
411
|
+
is_workspace_path=is_workspace_path,
|
|
412
|
+
result_details="Directory listing retrieved successfully.",
|
|
407
413
|
)
|
|
408
414
|
|
|
409
415
|
except Exception as e:
|
|
@@ -430,6 +436,7 @@ class OSManager:
|
|
|
430
436
|
mime_type=mime_type,
|
|
431
437
|
encoding=encoding,
|
|
432
438
|
compression_encoding=compression_encoding,
|
|
439
|
+
result_details="File read successfully.",
|
|
433
440
|
)
|
|
434
441
|
|
|
435
442
|
except (ValueError, FileNotFoundError) as e:
|
|
@@ -759,8 +766,9 @@ class OSManager:
|
|
|
759
766
|
# Check if it already exists - warn but treat as success
|
|
760
767
|
if file_path.exists():
|
|
761
768
|
msg = f"Path already exists: {file_path}"
|
|
762
|
-
|
|
763
|
-
|
|
769
|
+
return CreateFileResultSuccess(
|
|
770
|
+
created_path=str(file_path), result_details=ResultDetails(message=msg, level="WARNING")
|
|
771
|
+
)
|
|
764
772
|
|
|
765
773
|
# Create parent directories if needed
|
|
766
774
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -777,7 +785,10 @@ class OSManager:
|
|
|
777
785
|
file_path.touch()
|
|
778
786
|
logger.info("Created empty file: %s", file_path)
|
|
779
787
|
|
|
780
|
-
return CreateFileResultSuccess(
|
|
788
|
+
return CreateFileResultSuccess(
|
|
789
|
+
created_path=str(file_path),
|
|
790
|
+
result_details=f"{'Directory' if request.is_directory else 'File'} created successfully at {file_path}",
|
|
791
|
+
)
|
|
781
792
|
|
|
782
793
|
except Exception as e:
|
|
783
794
|
path_info = request.get_full_path() if hasattr(request, "get_full_path") else str(request.path)
|
|
@@ -820,9 +831,13 @@ class OSManager:
|
|
|
820
831
|
|
|
821
832
|
# Perform the rename operation
|
|
822
833
|
old_path.rename(new_path)
|
|
823
|
-
|
|
834
|
+
details = f"Renamed: {old_path} -> {new_path}"
|
|
824
835
|
|
|
825
|
-
return RenameFileResultSuccess(
|
|
836
|
+
return RenameFileResultSuccess(
|
|
837
|
+
old_path=str(old_path),
|
|
838
|
+
new_path=str(new_path),
|
|
839
|
+
result_details=ResultDetails(message=details, level="INFO"),
|
|
840
|
+
)
|
|
826
841
|
|
|
827
842
|
except Exception as e:
|
|
828
843
|
msg = f"Failed to rename {request.old_path} to {request.new_path}: {e}"
|
|
@@ -60,7 +60,9 @@ class SecretsManager:
|
|
|
60
60
|
logger.error(details)
|
|
61
61
|
return GetSecretValueResultFailure(result_details=details)
|
|
62
62
|
|
|
63
|
-
return GetSecretValueResultSuccess(
|
|
63
|
+
return GetSecretValueResultSuccess(
|
|
64
|
+
value=secret_value, result_details=f"Successfully retrieved secret value for key: {secret_key}"
|
|
65
|
+
)
|
|
64
66
|
|
|
65
67
|
def on_handle_set_secret_request(self, request: SetSecretValueRequest) -> ResultPayload:
|
|
66
68
|
secret_name = SecretsManager._apply_secret_name_compliance(request.key)
|
|
@@ -77,12 +79,14 @@ class SecretsManager:
|
|
|
77
79
|
|
|
78
80
|
self.set_secret(secret_name, secret_value)
|
|
79
81
|
|
|
80
|
-
return SetSecretValueResultSuccess()
|
|
82
|
+
return SetSecretValueResultSuccess(result_details=f"Successfully set secret value for key: {secret_name}")
|
|
81
83
|
|
|
82
84
|
def on_handle_get_all_secret_values_request(self, request: GetAllSecretValuesRequest) -> ResultPayload: # noqa: ARG002
|
|
83
85
|
secret_values = dotenv_values(ENV_VAR_PATH)
|
|
84
86
|
|
|
85
|
-
return GetAllSecretValuesResultSuccess(
|
|
87
|
+
return GetAllSecretValuesResultSuccess(
|
|
88
|
+
values=secret_values, result_details=f"Successfully retrieved {len(secret_values)} secret values"
|
|
89
|
+
)
|
|
86
90
|
|
|
87
91
|
def on_handle_delete_secret_value_request(self, request: DeleteSecretValueRequest) -> ResultPayload:
|
|
88
92
|
secret_name = SecretsManager._apply_secret_name_compliance(request.key)
|
|
@@ -101,7 +105,7 @@ class SecretsManager:
|
|
|
101
105
|
|
|
102
106
|
logger.info("Secret '%s' deleted.", secret_name)
|
|
103
107
|
|
|
104
|
-
return DeleteSecretValueResultSuccess()
|
|
108
|
+
return DeleteSecretValueResultSuccess(result_details=f"Successfully deleted secret: {secret_name}")
|
|
105
109
|
|
|
106
110
|
def get_secret(self, secret_name: str, *, should_error_on_not_found: bool = True) -> str | None:
|
|
107
111
|
"""Return the secret value with the following search precedence (highest to lowest priority).
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
from typing import Any, Literal
|
|
3
4
|
|
|
4
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class WorkflowExecutionMode(StrEnum):
|
|
9
|
+
"""Execution type for node processing."""
|
|
10
|
+
|
|
11
|
+
SEQUENTIAL = "sequential"
|
|
12
|
+
PARALLEL = "parallel"
|
|
5
13
|
|
|
6
14
|
|
|
7
15
|
class AppInitializationComplete(BaseModel):
|
|
@@ -89,6 +97,29 @@ class Settings(BaseModel):
|
|
|
89
97
|
}
|
|
90
98
|
)
|
|
91
99
|
log_level: str = Field(default="INFO")
|
|
100
|
+
workflow_execution_mode: WorkflowExecutionMode = Field(
|
|
101
|
+
default=WorkflowExecutionMode.SEQUENTIAL, description="Workflow execution mode for node processing"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
@field_validator("workflow_execution_mode", mode="before")
|
|
105
|
+
@classmethod
|
|
106
|
+
def validate_workflow_execution_mode(cls, v: Any) -> WorkflowExecutionMode:
|
|
107
|
+
"""Convert string values to WorkflowExecutionMode enum."""
|
|
108
|
+
if isinstance(v, str):
|
|
109
|
+
try:
|
|
110
|
+
return WorkflowExecutionMode(v.lower())
|
|
111
|
+
except ValueError:
|
|
112
|
+
# Return default if invalid string
|
|
113
|
+
return WorkflowExecutionMode.SEQUENTIAL
|
|
114
|
+
elif isinstance(v, WorkflowExecutionMode):
|
|
115
|
+
return v
|
|
116
|
+
else:
|
|
117
|
+
# Return default for any other type
|
|
118
|
+
return WorkflowExecutionMode.SEQUENTIAL
|
|
119
|
+
|
|
120
|
+
max_nodes_in_parallel: int | None = Field(
|
|
121
|
+
default=5, description="Maximum number of nodes executing at a time for parallel execution."
|
|
122
|
+
)
|
|
92
123
|
storage_backend: Literal["local", "gtc"] = Field(default="local")
|
|
93
124
|
minimum_disk_space_gb_libraries: float = Field(
|
|
94
125
|
default=10.0,
|
|
@@ -103,7 +103,7 @@ class StaticFilesManager:
|
|
|
103
103
|
logger.error(msg)
|
|
104
104
|
return CreateStaticFileResultFailure(error=msg, result_details=msg)
|
|
105
105
|
|
|
106
|
-
return CreateStaticFileResultSuccess(url=url)
|
|
106
|
+
return CreateStaticFileResultSuccess(url=url, result_details=f"Successfully created static file: {url}")
|
|
107
107
|
|
|
108
108
|
def on_handle_create_static_file_upload_url_request(
|
|
109
109
|
self,
|
|
@@ -126,7 +126,10 @@ class StaticFilesManager:
|
|
|
126
126
|
return CreateStaticFileUploadUrlResultFailure(error=msg, result_details=msg)
|
|
127
127
|
|
|
128
128
|
return CreateStaticFileUploadUrlResultSuccess(
|
|
129
|
-
url=response["url"],
|
|
129
|
+
url=response["url"],
|
|
130
|
+
headers=response["headers"],
|
|
131
|
+
method=response["method"],
|
|
132
|
+
result_details="Successfully created static file upload URL",
|
|
130
133
|
)
|
|
131
134
|
|
|
132
135
|
def on_handle_create_static_file_download_url_request(
|
|
@@ -149,7 +152,9 @@ class StaticFilesManager:
|
|
|
149
152
|
logger.error(msg)
|
|
150
153
|
return CreateStaticFileDownloadUrlResultFailure(error=msg, result_details=msg)
|
|
151
154
|
|
|
152
|
-
return CreateStaticFileDownloadUrlResultSuccess(
|
|
155
|
+
return CreateStaticFileDownloadUrlResultSuccess(
|
|
156
|
+
url=url, result_details="Successfully created static file download URL"
|
|
157
|
+
)
|
|
153
158
|
|
|
154
159
|
def save_static_file(self, data: bytes, file_name: str) -> str:
|
|
155
160
|
"""Saves a static file to the workspace directory.
|
|
@@ -13,7 +13,7 @@ from watchfiles import Change, PythonFilter, watch
|
|
|
13
13
|
|
|
14
14
|
from griptape_nodes.drivers.storage.griptape_cloud_storage_driver import GriptapeCloudStorageDriver
|
|
15
15
|
from griptape_nodes.retained_mode.events.app_events import AppInitializationComplete
|
|
16
|
-
from griptape_nodes.retained_mode.events.base_events import AppEvent
|
|
16
|
+
from griptape_nodes.retained_mode.events.base_events import AppEvent, ResultDetails
|
|
17
17
|
from griptape_nodes.retained_mode.events.sync_events import (
|
|
18
18
|
StartSyncAllCloudWorkflowsRequest,
|
|
19
19
|
StartSyncAllCloudWorkflowsResultFailure,
|
|
@@ -130,8 +130,11 @@ class SyncManager:
|
|
|
130
130
|
workflow_files = [file for file in files if file.endswith(".py")]
|
|
131
131
|
|
|
132
132
|
if not workflow_files:
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
return StartSyncAllCloudWorkflowsResultSuccess(
|
|
134
|
+
sync_directory=str(sync_dir),
|
|
135
|
+
total_workflows=0,
|
|
136
|
+
result_details=ResultDetails(message="No workflow files found in cloud storage.", level="INFO"),
|
|
137
|
+
)
|
|
135
138
|
|
|
136
139
|
# Start background sync with unique ID
|
|
137
140
|
sync_task_id = str(uuid.uuid4())
|
|
@@ -149,9 +152,9 @@ class SyncManager:
|
|
|
149
152
|
logger.error(details)
|
|
150
153
|
return StartSyncAllCloudWorkflowsResultFailure(result_details=details)
|
|
151
154
|
else:
|
|
152
|
-
|
|
155
|
+
details = f"Started background sync for {len(workflow_files)} workflow files"
|
|
153
156
|
return StartSyncAllCloudWorkflowsResultSuccess(
|
|
154
|
-
sync_directory=str(sync_dir), total_workflows=len(workflow_files)
|
|
157
|
+
sync_directory=str(sync_dir), total_workflows=len(workflow_files), result_details=details
|
|
155
158
|
)
|
|
156
159
|
|
|
157
160
|
def on_app_initialization_complete(self, _payload: AppInitializationComplete) -> None:
|