griptape-nodes 0.51.2__tar.gz → 0.52.0__tar.gz
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-0.51.2 → griptape_nodes-0.52.0}/PKG-INFO +2 -3
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/pyproject.toml +18 -7
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/__init__.py +5 -4
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/app/api.py +27 -24
- griptape_nodes-0.52.0/src/griptape_nodes/app/app.py +487 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/app/watch.py +17 -2
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +66 -103
- griptape_nodes-0.52.0/src/griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +31 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/exe_types/core_types.py +16 -4
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/exe_types/node_types.py +74 -16
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/machines/control_flow.py +21 -26
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/machines/fsm.py +16 -16
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/machines/node_resolution.py +30 -119
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/mcp_server/server.py +14 -10
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/mcp_server/ws_request_manager.py +2 -2
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/node_library/workflow_registry.py +5 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/base_events.py +12 -7
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/execution_events.py +0 -6
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/node_events.py +38 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/parameter_events.py +11 -0
- griptape_nodes-0.52.0/src/griptape_nodes/retained_mode/events/variable_events.py +361 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/workflow_events.py +35 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/griptape_nodes.py +61 -26
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/agent_manager.py +8 -9
- griptape_nodes-0.52.0/src/griptape_nodes/retained_mode/managers/event_manager.py +298 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/flow_manager.py +39 -33
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +14 -14
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +20 -20
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +1 -1
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +1 -1
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +4 -3
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +1 -1
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +1 -1
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_manager.py +20 -19
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/node_manager.py +83 -8
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/object_manager.py +4 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/settings.py +1 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/sync_manager.py +3 -9
- griptape_nodes-0.52.0/src/griptape_nodes/retained_mode/managers/variable_manager.py +529 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/workflow_manager.py +156 -50
- griptape_nodes-0.52.0/src/griptape_nodes/retained_mode/variable_types.py +18 -0
- griptape_nodes-0.52.0/src/griptape_nodes/utils/__init__.py +5 -0
- griptape_nodes-0.52.0/src/griptape_nodes/utils/async_utils.py +89 -0
- griptape_nodes-0.51.2/src/griptape_nodes/app/app.py +0 -465
- griptape_nodes-0.51.2/src/griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +0 -90
- griptape_nodes-0.51.2/src/griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +0 -19
- griptape_nodes-0.51.2/src/griptape_nodes/retained_mode/managers/event_manager.py +0 -157
- griptape_nodes-0.51.2/src/griptape_nodes/utils/__init__.py +0 -1
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/README.md +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/app/.python-version +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/app/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/bootstrap/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/drivers/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/drivers/storage/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/drivers/storage/base_storage_driver.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/drivers/storage/local_storage_driver.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/drivers/storage/storage_backend.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/exe_types/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/exe_types/connections.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/exe_types/flow.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/exe_types/type_validator.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/machines/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/mcp_server/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/node_library/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/node_library/advanced_node_library.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/node_library/library_registry.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/py.typed +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/agent_events.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/app_events.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/arbitrary_python_events.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/config_events.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/connection_events.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/context_events.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/flow_events.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/library_events.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/logger_events.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/object_events.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/os_events.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/payload_registry.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/secrets_events.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/static_file_events.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/sync_events.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/validation_events.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/config_manager.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/context_manager.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/engine_identity_manager.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/os_manager.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/secrets_manager.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/session_manager.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/version_compatibility_manager.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/retained_mode.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/utils/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/utils/engine_identity.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/utils/name_generator.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/add_param_button.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/button.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/clamp.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/compare.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/compare_images.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/file_system_picker.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/minmax.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/options.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/slider.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/trait_registry.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/traits.json +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/updater/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/updater/__main__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/utils/dict_utils.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/utils/image_preview.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/utils/metaclasses.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/utils/uv_utils.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/utils/version_utils.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/version_compatibility/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/version_compatibility/versions/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/version_compatibility/workflow_versions/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/version_compatibility/workflow_versions/v0_7_0/__init__.py +0 -0
- {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/version_compatibility/workflow_versions/v0_7_0/local_executor_argument_addition.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: griptape-nodes
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.52.0
|
|
4
4
|
Summary: Add your description here
|
|
5
|
-
Requires-Dist: griptape>=1.8.
|
|
5
|
+
Requires-Dist: griptape>=1.8.2
|
|
6
6
|
Requires-Dist: pydantic>=2.10.6
|
|
7
7
|
Requires-Dist: python-dotenv>=1.0.1
|
|
8
8
|
Requires-Dist: xdg-base-dirs>=6.0.2
|
|
@@ -15,7 +15,6 @@ Requires-Dist: uvicorn>=0.34.2
|
|
|
15
15
|
Requires-Dist: packaging>=25.0
|
|
16
16
|
Requires-Dist: python-multipart>=0.0.20
|
|
17
17
|
Requires-Dist: json-repair>=0.46.1
|
|
18
|
-
Requires-Dist: imageio-ffmpeg>=0.6.0
|
|
19
18
|
Requires-Dist: mcp[ws]>=1.10.1
|
|
20
19
|
Requires-Dist: binaryornot>=0.4.4
|
|
21
20
|
Requires-Dist: pillow>=11.3.0
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "griptape-nodes"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.52.0"
|
|
4
4
|
description = "Add your description here"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12.0, <3.13"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"griptape>=1.8.
|
|
8
|
+
"griptape>=1.8.2",
|
|
9
9
|
"pydantic>=2.10.6",
|
|
10
10
|
"python-dotenv>=1.0.1",
|
|
11
11
|
"xdg-base-dirs>=6.0.2",
|
|
@@ -19,7 +19,6 @@ dependencies = [
|
|
|
19
19
|
"packaging>=25.0",
|
|
20
20
|
"python-multipart>=0.0.20",
|
|
21
21
|
"json-repair>=0.46.1",
|
|
22
|
-
"imageio-ffmpeg>=0.6.0",
|
|
23
22
|
"mcp[ws]>=1.10.1",
|
|
24
23
|
"binaryornot>=0.4.4",
|
|
25
24
|
"pillow>=11.3.0",
|
|
@@ -46,7 +45,14 @@ docs = [
|
|
|
46
45
|
"mkdocs>=1.5.2",
|
|
47
46
|
"mkdocstrings[python]>=0.29.1",
|
|
48
47
|
]
|
|
49
|
-
test = [
|
|
48
|
+
test = [
|
|
49
|
+
"pytest>=8.3.5",
|
|
50
|
+
"pytest-asyncio>=1.1.0",
|
|
51
|
+
"pytest-mock>=3.14.0",
|
|
52
|
+
"pytest-xdist>=3.6.1",
|
|
53
|
+
"pytest-cov>=6.0.0",
|
|
54
|
+
"coverage>=7.0.0",
|
|
55
|
+
]
|
|
50
56
|
|
|
51
57
|
[project.scripts]
|
|
52
58
|
griptape-nodes = "griptape_nodes:main"
|
|
@@ -101,6 +107,14 @@ mypy-init-return = true
|
|
|
101
107
|
[tool.ruff.lint.pydocstyle]
|
|
102
108
|
convention = "google"
|
|
103
109
|
|
|
110
|
+
[tool.typos]
|
|
111
|
+
default.extend-ignore-re = [
|
|
112
|
+
# Ignore any line that ends with this comment:
|
|
113
|
+
"(?Rm)^.*(#|//)\\s*spellchecker:disable-line$",
|
|
114
|
+
# Or: ignore the *next* line after this comment:
|
|
115
|
+
"(?m)(#|//)\\s*spellchecker:ignore-next-line\\n.*",
|
|
116
|
+
]
|
|
117
|
+
|
|
104
118
|
[tool.pyright]
|
|
105
119
|
venvPath = "."
|
|
106
120
|
venv = ".venv"
|
|
@@ -115,6 +129,3 @@ exclude = [
|
|
|
115
129
|
]
|
|
116
130
|
pythonVersion = "3.12"
|
|
117
131
|
reportIncompatibleMethodOverride = false
|
|
118
|
-
|
|
119
|
-
[tool.uv.sources]
|
|
120
|
-
griptape = { git = "https://github.com/griptape-ai/griptape", rev = "main" }
|
|
@@ -6,6 +6,7 @@ console = Console()
|
|
|
6
6
|
|
|
7
7
|
with console.status("Loading Griptape Nodes...") as status:
|
|
8
8
|
import argparse
|
|
9
|
+
import asyncio
|
|
9
10
|
import json
|
|
10
11
|
import os
|
|
11
12
|
import shutil
|
|
@@ -113,7 +114,7 @@ def _run_init(config: InitConfig) -> None:
|
|
|
113
114
|
|
|
114
115
|
# Sync libraries
|
|
115
116
|
if config.libraries_sync is not False:
|
|
116
|
-
_sync_libraries()
|
|
117
|
+
asyncio.run(_sync_libraries())
|
|
117
118
|
|
|
118
119
|
console.print("[bold green]Initialization complete![/bold green]")
|
|
119
120
|
|
|
@@ -683,7 +684,7 @@ def _update_self() -> None:
|
|
|
683
684
|
os_manager.replace_process([sys.executable, "-m", "griptape_nodes.updater"])
|
|
684
685
|
|
|
685
686
|
|
|
686
|
-
def _sync_libraries() -> None:
|
|
687
|
+
async def _sync_libraries() -> None:
|
|
687
688
|
"""Download and sync Griptape Nodes libraries, copying only directories from synced libraries."""
|
|
688
689
|
install_source, _ = get_install_source()
|
|
689
690
|
# Unless we're installed from PyPi, grab libraries from the 'latest' tag
|
|
@@ -737,7 +738,7 @@ def _sync_libraries() -> None:
|
|
|
737
738
|
# Re-initialize all libraries from config
|
|
738
739
|
console.print("[bold cyan]Initializing libraries...[/bold cyan]")
|
|
739
740
|
try:
|
|
740
|
-
GriptapeNodes.LibraryManager().load_all_libraries_from_config()
|
|
741
|
+
await GriptapeNodes.LibraryManager().load_all_libraries_from_config()
|
|
741
742
|
console.print("[bold green]Libraries Initialized successfully.[/bold green]")
|
|
742
743
|
except Exception as e:
|
|
743
744
|
console.print(f"[red]Error initializing libraries: {e}[/red]")
|
|
@@ -904,7 +905,7 @@ def _process_args(args: argparse.Namespace) -> None: # noqa: C901, PLR0912
|
|
|
904
905
|
_print_current_version()
|
|
905
906
|
elif args.command == "libraries":
|
|
906
907
|
if args.subcommand == "sync":
|
|
907
|
-
_sync_libraries()
|
|
908
|
+
asyncio.run(_sync_libraries())
|
|
908
909
|
else:
|
|
909
910
|
msg = f"Unknown command: {args.command}"
|
|
910
911
|
raise ValueError(msg)
|
|
@@ -4,7 +4,7 @@ import binascii
|
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import
|
|
7
|
+
from typing import Annotated
|
|
8
8
|
from urllib.parse import urljoin
|
|
9
9
|
|
|
10
10
|
import uvicorn
|
|
@@ -13,9 +13,6 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
|
13
13
|
from fastapi.staticfiles import StaticFiles
|
|
14
14
|
from rich.logging import RichHandler
|
|
15
15
|
|
|
16
|
-
if TYPE_CHECKING:
|
|
17
|
-
from queue import Queue
|
|
18
|
-
|
|
19
16
|
# Whether to enable the static server
|
|
20
17
|
STATIC_SERVER_ENABLED = os.getenv("STATIC_SERVER_ENABLED", "true").lower() == "true"
|
|
21
18
|
# Host of the static server
|
|
@@ -31,21 +28,10 @@ logger = logging.getLogger("griptape_nodes_api")
|
|
|
31
28
|
logging.getLogger("uvicorn").addHandler(RichHandler(show_time=True, show_path=False, markup=True, rich_tracebacks=True))
|
|
32
29
|
|
|
33
30
|
|
|
34
|
-
# Global event queue - initialized as None and set when starting the API
|
|
35
|
-
event_queue: Queue | None = None
|
|
36
|
-
|
|
37
31
|
# Global static directory - initialized as None and set when starting the API
|
|
38
32
|
static_dir: Path | None = None
|
|
39
33
|
|
|
40
34
|
|
|
41
|
-
def get_event_queue() -> Queue:
|
|
42
|
-
"""FastAPI dependency to get the event queue."""
|
|
43
|
-
if event_queue is None:
|
|
44
|
-
msg = "Event queue is not initialized"
|
|
45
|
-
raise HTTPException(status_code=500, detail=msg)
|
|
46
|
-
return event_queue
|
|
47
|
-
|
|
48
|
-
|
|
49
35
|
def get_static_dir() -> Path:
|
|
50
36
|
"""FastAPI dependency to get the static directory."""
|
|
51
37
|
if static_dir is None:
|
|
@@ -156,17 +142,19 @@ async def _delete_static_file(file_path: str, static_directory: Annotated[Path,
|
|
|
156
142
|
|
|
157
143
|
|
|
158
144
|
@app.post("/engines/request")
|
|
159
|
-
async def _create_event(request: Request
|
|
145
|
+
async def _create_event(request: Request) -> None:
|
|
146
|
+
"""Create event using centralized event utilities."""
|
|
160
147
|
from .app import _process_api_event
|
|
161
148
|
|
|
162
149
|
body = await request.json()
|
|
163
|
-
_process_api_event(body, queue)
|
|
164
150
|
|
|
151
|
+
# Use centralized event processing
|
|
152
|
+
await _process_api_event(body)
|
|
165
153
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
154
|
+
|
|
155
|
+
def _setup_app(static_directory: Path) -> None:
|
|
156
|
+
"""Setup FastAPI app with middleware and static files."""
|
|
157
|
+
global static_dir # noqa: PLW0603
|
|
170
158
|
static_dir = static_directory
|
|
171
159
|
|
|
172
160
|
if not static_dir.exists():
|
|
@@ -190,6 +178,21 @@ def start_api(static_directory: Path, queue: Queue) -> None:
|
|
|
190
178
|
name="static",
|
|
191
179
|
)
|
|
192
180
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
181
|
+
|
|
182
|
+
def start_api(static_directory: Path) -> None:
|
|
183
|
+
"""Run uvicorn server synchronously using uvicorn.run."""
|
|
184
|
+
# Setup the FastAPI app
|
|
185
|
+
_setup_app(static_directory)
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
# Run server using uvicorn.run
|
|
189
|
+
uvicorn.run(
|
|
190
|
+
app,
|
|
191
|
+
host=STATIC_SERVER_HOST,
|
|
192
|
+
port=STATIC_SERVER_PORT,
|
|
193
|
+
log_level=STATIC_SERVER_LOG_LEVEL,
|
|
194
|
+
log_config=None,
|
|
195
|
+
)
|
|
196
|
+
except Exception as e:
|
|
197
|
+
logger.error("API server failed: %s", e)
|
|
198
|
+
raise
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import contextlib
|
|
5
|
+
import contextvars
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
from urllib.parse import urljoin
|
|
13
|
+
|
|
14
|
+
from rich.align import Align
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.logging import RichHandler
|
|
17
|
+
from rich.panel import Panel
|
|
18
|
+
from websockets.asyncio.client import connect
|
|
19
|
+
from websockets.exceptions import ConnectionClosed, WebSocketException
|
|
20
|
+
|
|
21
|
+
from griptape_nodes.retained_mode.events import app_events, execution_events
|
|
22
|
+
|
|
23
|
+
# This import is necessary to register all events, even if not technically used
|
|
24
|
+
from griptape_nodes.retained_mode.events.base_events import (
|
|
25
|
+
AppEvent,
|
|
26
|
+
EventRequest,
|
|
27
|
+
EventResultFailure,
|
|
28
|
+
EventResultSuccess,
|
|
29
|
+
ExecutionEvent,
|
|
30
|
+
ExecutionGriptapeNodeEvent,
|
|
31
|
+
GriptapeNodeEvent,
|
|
32
|
+
ProgressEvent,
|
|
33
|
+
SkipTheLineMixin,
|
|
34
|
+
deserialize_event,
|
|
35
|
+
)
|
|
36
|
+
from griptape_nodes.retained_mode.events.logger_events import LogHandlerEvent
|
|
37
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
38
|
+
|
|
39
|
+
# Context variable for WebSocket connection - avoids global state
|
|
40
|
+
ws_connection_context: contextvars.ContextVar[Any | None] = contextvars.ContextVar("ws_connection", default=None)
|
|
41
|
+
|
|
42
|
+
# Event to signal when WebSocket connection is ready
|
|
43
|
+
ws_ready_event = asyncio.Event()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Whether to enable the static server
|
|
47
|
+
STATIC_SERVER_ENABLED = os.getenv("STATIC_SERVER_ENABLED", "true").lower() == "true"
|
|
48
|
+
|
|
49
|
+
# Semaphore to limit concurrent requests
|
|
50
|
+
REQUEST_SEMAPHORE = asyncio.Semaphore(100)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Important to bootstrap singleton here so that we don't
|
|
54
|
+
# get any weird circular import issues from the EventLogHandler
|
|
55
|
+
# initializing it from a log during it's own initialization.
|
|
56
|
+
griptape_nodes: GriptapeNodes = GriptapeNodes()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class EventLogHandler(logging.Handler):
|
|
60
|
+
"""Custom logging handler that emits log messages as AppEvents.
|
|
61
|
+
|
|
62
|
+
This is used to forward log messages to the event queue so they can be sent to the GUI.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
66
|
+
log_event = AppEvent(
|
|
67
|
+
payload=LogHandlerEvent(message=record.getMessage(), levelname=record.levelname, created=record.created)
|
|
68
|
+
)
|
|
69
|
+
griptape_nodes.EventManager().put_event(log_event)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Logger for this module. Important that this is not the same as the griptape_nodes logger or else we'll have infinite log events.
|
|
73
|
+
logger = logging.getLogger("griptape_nodes_app")
|
|
74
|
+
|
|
75
|
+
griptape_nodes_logger = logging.getLogger("griptape_nodes")
|
|
76
|
+
# When running as an app, we want to forward all log messages to the event queue so they can be sent to the GUI
|
|
77
|
+
griptape_nodes_logger.addHandler(EventLogHandler())
|
|
78
|
+
griptape_nodes_logger.addHandler(RichHandler(show_time=True, show_path=False, markup=True, rich_tracebacks=True))
|
|
79
|
+
griptape_nodes_logger.setLevel(logging.INFO)
|
|
80
|
+
|
|
81
|
+
console = Console()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def start_app() -> None:
|
|
85
|
+
"""Legacy sync entry point - runs async app."""
|
|
86
|
+
try:
|
|
87
|
+
asyncio.run(astart_app())
|
|
88
|
+
except KeyboardInterrupt:
|
|
89
|
+
logger.info("Application stopped by user")
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error("Application error: %s", e)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
async def astart_app() -> None:
|
|
95
|
+
"""New async app entry point."""
|
|
96
|
+
api_key = _ensure_api_key()
|
|
97
|
+
|
|
98
|
+
griptape_nodes.EventManager().initialize_queue()
|
|
99
|
+
|
|
100
|
+
# Create shared context for all tasks to inherit WebSocket connection
|
|
101
|
+
shared_context = contextvars.copy_context()
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
# We need to run the servers in a separate thread otherwise
|
|
105
|
+
# blocking requests to them in the main thread would deadlock the event loop.
|
|
106
|
+
server_tasks = []
|
|
107
|
+
|
|
108
|
+
# Start MCP server in thread
|
|
109
|
+
server_tasks.append(asyncio.to_thread(_run_mcp_server_sync, api_key))
|
|
110
|
+
|
|
111
|
+
# Start static server in thread if enabled
|
|
112
|
+
if STATIC_SERVER_ENABLED:
|
|
113
|
+
static_dir = _build_static_dir()
|
|
114
|
+
server_tasks.append(asyncio.to_thread(_run_static_server_sync, static_dir))
|
|
115
|
+
|
|
116
|
+
# Run main event loop tasks
|
|
117
|
+
main_tasks = [
|
|
118
|
+
_listen_for_api_requests(api_key),
|
|
119
|
+
_process_event_queue(),
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
# Combine server tasks and main tasks
|
|
123
|
+
all_tasks = server_tasks + main_tasks
|
|
124
|
+
|
|
125
|
+
async with asyncio.TaskGroup() as tg:
|
|
126
|
+
for task in all_tasks:
|
|
127
|
+
# Context is supposed to be copied automatically, but it isn't working for some reason so we do it manually here
|
|
128
|
+
tg.create_task(task, context=shared_context)
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.error("Application startup failed: %s", e)
|
|
131
|
+
raise
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _run_mcp_server_sync(api_key: str) -> None:
|
|
135
|
+
"""Run MCP server in a separate thread."""
|
|
136
|
+
try:
|
|
137
|
+
from griptape_nodes.mcp_server.server import main_sync
|
|
138
|
+
|
|
139
|
+
main_sync(api_key)
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.error("MCP server thread error: %s", e)
|
|
142
|
+
raise
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _run_static_server_sync(static_dir: Path) -> None:
|
|
146
|
+
"""Run static server in a separate thread."""
|
|
147
|
+
try:
|
|
148
|
+
from .api import start_api
|
|
149
|
+
|
|
150
|
+
start_api(static_dir)
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logger.error("Static server thread error: %s", e)
|
|
153
|
+
raise
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _ensure_api_key() -> str:
|
|
157
|
+
secrets_manager = griptape_nodes.SecretsManager()
|
|
158
|
+
api_key = secrets_manager.get_secret("GT_CLOUD_API_KEY")
|
|
159
|
+
if api_key is None:
|
|
160
|
+
message = Panel(
|
|
161
|
+
Align.center(
|
|
162
|
+
"[bold red]Nodes API key is not set, please run [code]gtn init[/code] with a valid key: [/bold red]"
|
|
163
|
+
"[code]gtn init --api-key <your key>[/code]\n"
|
|
164
|
+
"[bold red]You can generate a new key from [/bold red][bold blue][link=https://nodes.griptape.ai]https://nodes.griptape.ai[/link][/bold blue]",
|
|
165
|
+
),
|
|
166
|
+
title="[red]X[/red] Missing Nodes API Key",
|
|
167
|
+
border_style="red",
|
|
168
|
+
padding=(1, 4),
|
|
169
|
+
)
|
|
170
|
+
console.print(message)
|
|
171
|
+
sys.exit(1)
|
|
172
|
+
|
|
173
|
+
return api_key
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _build_static_dir() -> Path:
|
|
177
|
+
"""Build the static directory path based on the workspace configuration."""
|
|
178
|
+
config_manager = griptape_nodes.ConfigManager()
|
|
179
|
+
return Path(config_manager.workspace_path) / config_manager.merged_config["static_files_directory"]
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
async def _listen_for_api_requests(api_key: str) -> None:
|
|
183
|
+
"""Listen for events and add to async queue."""
|
|
184
|
+
logger.info("Listening for events from Nodes API via async WebSocket")
|
|
185
|
+
|
|
186
|
+
connection_stream = _create_websocket_connection(api_key)
|
|
187
|
+
initialized = False
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
async for ws_connection in connection_stream:
|
|
191
|
+
await _handle_websocket_connection(ws_connection, initialized=initialized)
|
|
192
|
+
initialized = True
|
|
193
|
+
|
|
194
|
+
except asyncio.CancelledError:
|
|
195
|
+
# Clean shutdown when task is cancelled
|
|
196
|
+
logger.info("WebSocket listener shutdown complete")
|
|
197
|
+
raise
|
|
198
|
+
except Exception as e:
|
|
199
|
+
logger.error("Fatal error in WebSocket listener: %s", e)
|
|
200
|
+
raise
|
|
201
|
+
finally:
|
|
202
|
+
await _cleanup_websocket_connection()
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
async def _handle_websocket_connection(ws_connection: Any, *, initialized: bool) -> None:
|
|
206
|
+
"""Handle a single WebSocket connection."""
|
|
207
|
+
try:
|
|
208
|
+
ws_connection_context.set(ws_connection)
|
|
209
|
+
ws_ready_event.set()
|
|
210
|
+
|
|
211
|
+
if not initialized:
|
|
212
|
+
await griptape_nodes.EventManager().aput_event(AppEvent(payload=app_events.AppInitializationComplete()))
|
|
213
|
+
|
|
214
|
+
await griptape_nodes.EventManager().aput_event(AppEvent(payload=app_events.AppConnectionEstablished()))
|
|
215
|
+
|
|
216
|
+
async for message in ws_connection:
|
|
217
|
+
try:
|
|
218
|
+
data = json.loads(message)
|
|
219
|
+
await _process_api_event(data)
|
|
220
|
+
except Exception:
|
|
221
|
+
logger.exception("Error processing event, skipping.")
|
|
222
|
+
|
|
223
|
+
except ConnectionClosed:
|
|
224
|
+
logger.info("WebSocket connection closed, will retry")
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logger.error("Error in WebSocket connection. Retrying in 2 seconds... %s", e)
|
|
227
|
+
await asyncio.sleep(2.0)
|
|
228
|
+
finally:
|
|
229
|
+
ws_connection_context.set(None)
|
|
230
|
+
ws_ready_event.clear()
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
async def _cleanup_websocket_connection() -> None:
|
|
234
|
+
"""Clean up WebSocket connection on shutdown."""
|
|
235
|
+
ws_connection = ws_connection_context.get()
|
|
236
|
+
if ws_connection:
|
|
237
|
+
with contextlib.suppress(Exception):
|
|
238
|
+
await ws_connection.close()
|
|
239
|
+
logger.info("WebSocket listener shutdown complete")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
async def _process_event_queue() -> None:
|
|
243
|
+
"""Process events concurrently - all events can run simultaneously."""
|
|
244
|
+
# Wait for WebSocket connection (convert to async)
|
|
245
|
+
await _await_websocket_ready()
|
|
246
|
+
background_tasks = set()
|
|
247
|
+
|
|
248
|
+
def _handle_task_result(task: asyncio.Task) -> None:
|
|
249
|
+
background_tasks.discard(task)
|
|
250
|
+
if task.exception() and not task.cancelled():
|
|
251
|
+
logger.exception("Background task failed", exc_info=task.exception())
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
event_queue = griptape_nodes.EventManager().event_queue
|
|
255
|
+
while True:
|
|
256
|
+
event = await event_queue.get()
|
|
257
|
+
|
|
258
|
+
async with REQUEST_SEMAPHORE:
|
|
259
|
+
if isinstance(event, EventRequest):
|
|
260
|
+
task = asyncio.create_task(_process_event_request(event))
|
|
261
|
+
elif isinstance(event, AppEvent):
|
|
262
|
+
task = asyncio.create_task(_process_app_event(event))
|
|
263
|
+
elif isinstance(event, GriptapeNodeEvent):
|
|
264
|
+
task = asyncio.create_task(_process_node_event(event))
|
|
265
|
+
elif isinstance(event, ExecutionGriptapeNodeEvent):
|
|
266
|
+
task = asyncio.create_task(_process_execution_node_event(event))
|
|
267
|
+
elif isinstance(event, ProgressEvent):
|
|
268
|
+
task = asyncio.create_task(_process_progress_event(event))
|
|
269
|
+
else:
|
|
270
|
+
logger.warning("Unknown event type: %s", type(event))
|
|
271
|
+
event_queue.task_done()
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
background_tasks.add(task)
|
|
275
|
+
task.add_done_callback(_handle_task_result)
|
|
276
|
+
event_queue.task_done()
|
|
277
|
+
except asyncio.CancelledError:
|
|
278
|
+
logger.info("Event queue processor shutdown complete")
|
|
279
|
+
raise
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
async def _process_event_request(event: EventRequest) -> None:
|
|
283
|
+
"""Handle request and emit success/failure events based on result."""
|
|
284
|
+
result_event = await griptape_nodes.EventManager().ahandle_request(
|
|
285
|
+
event.request,
|
|
286
|
+
result_context={"response_topic": event.response_topic, "request_id": event.request_id},
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
if result_event.result.succeeded():
|
|
290
|
+
dest_socket = "success_result"
|
|
291
|
+
else:
|
|
292
|
+
dest_socket = "failure_result"
|
|
293
|
+
|
|
294
|
+
await __emit_message(dest_socket, result_event.json(), topic=result_event.response_topic)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
async def _await_websocket_ready() -> None:
|
|
298
|
+
"""Wait for WebSocket connection to be ready using event coordination."""
|
|
299
|
+
websocket_timeout = 15
|
|
300
|
+
try:
|
|
301
|
+
await asyncio.wait_for(ws_ready_event.wait(), timeout=websocket_timeout)
|
|
302
|
+
except TimeoutError:
|
|
303
|
+
console.print("[red]WebSocket connection timeout[/red]")
|
|
304
|
+
raise
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
async def _process_app_event(event: AppEvent) -> None:
|
|
308
|
+
"""Process AppEvents and send them to the API (async version)."""
|
|
309
|
+
# Let Griptape Nodes broadcast it.
|
|
310
|
+
await griptape_nodes.broadcast_app_event(event.payload)
|
|
311
|
+
|
|
312
|
+
await __emit_message("app_event", event.json())
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
async def _process_node_event(event: GriptapeNodeEvent) -> None:
|
|
316
|
+
"""Process GriptapeNodeEvents and send them to the API (async version)."""
|
|
317
|
+
# Emit the result back to the GUI
|
|
318
|
+
result_event = event.wrapped_event
|
|
319
|
+
if isinstance(result_event, EventResultSuccess):
|
|
320
|
+
dest_socket = "success_result"
|
|
321
|
+
elif isinstance(result_event, EventResultFailure):
|
|
322
|
+
dest_socket = "failure_result"
|
|
323
|
+
else:
|
|
324
|
+
msg = f"Unknown/unsupported result event type encountered: '{type(result_event)}'."
|
|
325
|
+
raise TypeError(msg) from None
|
|
326
|
+
|
|
327
|
+
await __emit_message(dest_socket, result_event.json(), topic=result_event.response_topic)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
async def _process_execution_node_event(event: ExecutionGriptapeNodeEvent) -> None:
|
|
331
|
+
"""Process ExecutionGriptapeNodeEvents and send them to the API (async version)."""
|
|
332
|
+
await __emit_message("execution_event", event.wrapped_event.json())
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
async def _process_progress_event(gt_event: ProgressEvent) -> None:
|
|
336
|
+
"""Process Griptape framework events and send them to the API (async version)."""
|
|
337
|
+
node_name = gt_event.node_name
|
|
338
|
+
if node_name:
|
|
339
|
+
value = gt_event.value
|
|
340
|
+
payload = execution_events.GriptapeEvent(
|
|
341
|
+
node_name=node_name, parameter_name=gt_event.parameter_name, type=type(gt_event).__name__, value=value
|
|
342
|
+
)
|
|
343
|
+
event_to_emit = ExecutionEvent(payload=payload)
|
|
344
|
+
await __emit_message("execution_event", event_to_emit.json())
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _create_websocket_connection(api_key: str) -> Any:
|
|
348
|
+
"""Create an async WebSocket connection to the Nodes API."""
|
|
349
|
+
endpoint = urljoin(
|
|
350
|
+
os.getenv("GRIPTAPE_NODES_API_BASE_URL", "https://api.nodes.griptape.ai").replace("http", "ws"),
|
|
351
|
+
"/ws/engines/events?version=v2",
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
return connect(
|
|
355
|
+
endpoint,
|
|
356
|
+
additional_headers={"Authorization": f"Bearer {api_key}"},
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
async def __emit_message(event_type: str, payload: str, topic: str | None = None) -> None:
|
|
361
|
+
"""Send a message via WebSocket asynchronously."""
|
|
362
|
+
ws_connection = ws_connection_context.get()
|
|
363
|
+
if ws_connection is None:
|
|
364
|
+
logger.warning("WebSocket connection not available for sending message")
|
|
365
|
+
return
|
|
366
|
+
|
|
367
|
+
try:
|
|
368
|
+
# Determine topic based on session_id and engine_id in the payload
|
|
369
|
+
if topic is None:
|
|
370
|
+
topic = determine_response_topic()
|
|
371
|
+
|
|
372
|
+
body = {"type": event_type, "payload": json.loads(payload), "topic": topic}
|
|
373
|
+
|
|
374
|
+
await ws_connection.send(json.dumps(body))
|
|
375
|
+
except WebSocketException as e:
|
|
376
|
+
logger.error("Error sending event to Nodes API: %s", e)
|
|
377
|
+
except Exception as e:
|
|
378
|
+
logger.error("Unexpected error while sending event to Nodes API: %s", e)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def determine_response_topic() -> str | None:
|
|
382
|
+
"""Determine the response topic based on session_id and engine_id in the payload."""
|
|
383
|
+
engine_id = griptape_nodes.get_engine_id()
|
|
384
|
+
session_id = griptape_nodes.get_session_id()
|
|
385
|
+
|
|
386
|
+
# Normal topic determination logic
|
|
387
|
+
# Check for session_id first (highest priority)
|
|
388
|
+
if session_id:
|
|
389
|
+
return f"sessions/{session_id}/response"
|
|
390
|
+
|
|
391
|
+
# Check for engine_id if no session_id
|
|
392
|
+
if engine_id:
|
|
393
|
+
return f"engines/{engine_id}/response"
|
|
394
|
+
|
|
395
|
+
# Default to generic response topic
|
|
396
|
+
return "response"
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def determine_request_topic() -> str | None:
|
|
400
|
+
"""Determine the request topic based on session_id and engine_id in the payload."""
|
|
401
|
+
engine_id = griptape_nodes.get_engine_id()
|
|
402
|
+
session_id = griptape_nodes.get_session_id()
|
|
403
|
+
|
|
404
|
+
# Normal topic determination logic
|
|
405
|
+
# Check for session_id first (highest priority)
|
|
406
|
+
if session_id:
|
|
407
|
+
return f"sessions/{session_id}/request"
|
|
408
|
+
|
|
409
|
+
# Check for engine_id if no session_id
|
|
410
|
+
if engine_id:
|
|
411
|
+
return f"engines/{engine_id}/request"
|
|
412
|
+
|
|
413
|
+
# Default to generic request topic
|
|
414
|
+
return "request"
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
async def subscribe_to_topic(topic: str) -> None:
|
|
418
|
+
"""Subscribe to a specific topic in the message bus."""
|
|
419
|
+
ws_connection = ws_connection_context.get()
|
|
420
|
+
if ws_connection is None:
|
|
421
|
+
logger.warning("WebSocket connection not available for subscribing to topic")
|
|
422
|
+
return
|
|
423
|
+
|
|
424
|
+
try:
|
|
425
|
+
body = {"type": "subscribe", "topic": topic, "payload": {}}
|
|
426
|
+
await ws_connection.send(json.dumps(body))
|
|
427
|
+
logger.info("Subscribed to topic: %s", topic)
|
|
428
|
+
except WebSocketException as e:
|
|
429
|
+
logger.error("Error subscribing to topic %s: %s", topic, e)
|
|
430
|
+
except Exception as e:
|
|
431
|
+
logger.error("Unexpected error while subscribing to topic %s: %s", topic, e)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
async def unsubscribe_from_topic(topic: str) -> None:
|
|
435
|
+
"""Unsubscribe from a specific topic in the message bus."""
|
|
436
|
+
ws_connection = ws_connection_context.get()
|
|
437
|
+
if ws_connection is None:
|
|
438
|
+
logger.warning("WebSocket connection not available for unsubscribing from topic")
|
|
439
|
+
return
|
|
440
|
+
|
|
441
|
+
try:
|
|
442
|
+
body = {"type": "unsubscribe", "topic": topic, "payload": {}}
|
|
443
|
+
await ws_connection.send(json.dumps(body))
|
|
444
|
+
logger.info("Unsubscribed from topic: %s", topic)
|
|
445
|
+
except WebSocketException as e:
|
|
446
|
+
logger.error("Error unsubscribing from topic %s: %s", topic, e)
|
|
447
|
+
except Exception as e:
|
|
448
|
+
logger.error("Unexpected error while unsubscribing from topic %s: %s", topic, e)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
async def _process_api_event(event: dict) -> None:
|
|
452
|
+
"""Process API events and add to async queue."""
|
|
453
|
+
payload = event.get("payload", {})
|
|
454
|
+
|
|
455
|
+
try:
|
|
456
|
+
payload["request"]
|
|
457
|
+
except KeyError:
|
|
458
|
+
msg = "Error: 'request' was expected but not found."
|
|
459
|
+
raise RuntimeError(msg) from None
|
|
460
|
+
|
|
461
|
+
try:
|
|
462
|
+
event_type = payload["event_type"]
|
|
463
|
+
if event_type != "EventRequest":
|
|
464
|
+
msg = "Error: 'event_type' was found on request, but did not match 'EventRequest' as expected."
|
|
465
|
+
raise RuntimeError(msg) from None
|
|
466
|
+
except KeyError:
|
|
467
|
+
msg = "Error: 'event_type' not found in request."
|
|
468
|
+
raise RuntimeError(msg) from None
|
|
469
|
+
|
|
470
|
+
# Now attempt to convert it into an EventRequest.
|
|
471
|
+
try:
|
|
472
|
+
request_event = deserialize_event(json_data=payload)
|
|
473
|
+
except Exception as e:
|
|
474
|
+
msg = f"Unable to convert request JSON into a valid EventRequest object. Error Message: '{e}'"
|
|
475
|
+
raise RuntimeError(msg) from None
|
|
476
|
+
|
|
477
|
+
if not isinstance(request_event, EventRequest):
|
|
478
|
+
msg = f"Deserialized event is not an EventRequest: {type(request_event)}"
|
|
479
|
+
raise TypeError(msg)
|
|
480
|
+
|
|
481
|
+
# Check if the event implements SkipTheLineMixin for priority processing
|
|
482
|
+
if isinstance(request_event.request, SkipTheLineMixin):
|
|
483
|
+
# Handle the event immediately without queuing
|
|
484
|
+
await _process_event_request(request_event)
|
|
485
|
+
else:
|
|
486
|
+
# Add the event to the async queue for normal processing
|
|
487
|
+
await griptape_nodes.EventManager().aput_event(request_event)
|