griptape-nodes 0.52.0__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.
Files changed (48) hide show
  1. griptape_nodes/__init__.py +6 -943
  2. griptape_nodes/__main__.py +6 -0
  3. griptape_nodes/app/api.py +1 -12
  4. griptape_nodes/app/app.py +256 -209
  5. griptape_nodes/cli/__init__.py +1 -0
  6. griptape_nodes/cli/commands/__init__.py +1 -0
  7. griptape_nodes/cli/commands/config.py +71 -0
  8. griptape_nodes/cli/commands/engine.py +80 -0
  9. griptape_nodes/cli/commands/init.py +548 -0
  10. griptape_nodes/cli/commands/libraries.py +90 -0
  11. griptape_nodes/cli/commands/self.py +117 -0
  12. griptape_nodes/cli/main.py +46 -0
  13. griptape_nodes/cli/shared.py +84 -0
  14. griptape_nodes/common/__init__.py +1 -0
  15. griptape_nodes/common/directed_graph.py +55 -0
  16. griptape_nodes/drivers/storage/local_storage_driver.py +7 -2
  17. griptape_nodes/exe_types/core_types.py +60 -2
  18. griptape_nodes/exe_types/node_types.py +38 -24
  19. griptape_nodes/machines/control_flow.py +86 -22
  20. griptape_nodes/machines/fsm.py +10 -1
  21. griptape_nodes/machines/parallel_resolution.py +570 -0
  22. griptape_nodes/machines/{node_resolution.py → sequential_resolution.py} +22 -51
  23. griptape_nodes/mcp_server/server.py +1 -1
  24. griptape_nodes/retained_mode/events/base_events.py +2 -2
  25. griptape_nodes/retained_mode/events/node_events.py +4 -3
  26. griptape_nodes/retained_mode/griptape_nodes.py +25 -12
  27. griptape_nodes/retained_mode/managers/agent_manager.py +9 -5
  28. griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +3 -1
  29. griptape_nodes/retained_mode/managers/context_manager.py +6 -5
  30. griptape_nodes/retained_mode/managers/flow_manager.py +117 -204
  31. griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +1 -1
  32. griptape_nodes/retained_mode/managers/library_manager.py +35 -25
  33. griptape_nodes/retained_mode/managers/node_manager.py +81 -199
  34. griptape_nodes/retained_mode/managers/object_manager.py +11 -5
  35. griptape_nodes/retained_mode/managers/os_manager.py +24 -9
  36. griptape_nodes/retained_mode/managers/secrets_manager.py +8 -4
  37. griptape_nodes/retained_mode/managers/settings.py +32 -1
  38. griptape_nodes/retained_mode/managers/static_files_manager.py +8 -3
  39. griptape_nodes/retained_mode/managers/sync_manager.py +8 -5
  40. griptape_nodes/retained_mode/managers/workflow_manager.py +110 -122
  41. griptape_nodes/traits/add_param_button.py +1 -1
  42. griptape_nodes/traits/button.py +216 -6
  43. griptape_nodes/traits/color_picker.py +66 -0
  44. griptape_nodes/traits/traits.json +4 -0
  45. {griptape_nodes-0.52.0.dist-info → griptape_nodes-0.53.0.dist-info}/METADATA +2 -1
  46. {griptape_nodes-0.52.0.dist-info → griptape_nodes-0.53.0.dist-info}/RECORD +48 -34
  47. {griptape_nodes-0.52.0.dist-info → griptape_nodes-0.53.0.dist-info}/WHEEL +0 -0
  48. {griptape_nodes-0.52.0.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(final_name=request.requested_name)
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
- logger.log(level=log_level, msg=details)
101
- return RenameObjectResultSuccess(final_name=final_name)
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
- logger.debug(details)
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, current_path=str(relative_or_abs_path), is_workspace_path=is_workspace_path
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, current_path=str(directory), is_workspace_path=is_workspace_path
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
- logger.warning(msg)
763
- return CreateFileResultSuccess(created_path=str(file_path))
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(created_path=str(file_path))
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
- logger.info("Renamed: %s -> %s", old_path, new_path)
834
+ details = f"Renamed: {old_path} -> {new_path}"
824
835
 
825
- return RenameFileResultSuccess(old_path=str(old_path), new_path=str(new_path))
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(value=secret_value)
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(values=secret_values)
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"], headers=response["headers"], method=response["method"]
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(url=url)
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
- logger.info("No workflow files found in cloud storage")
134
- return StartSyncAllCloudWorkflowsResultSuccess(sync_directory=str(sync_dir), total_workflows=0)
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
- logger.info("Started background sync for %d workflow files", len(workflow_files))
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: