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.
Files changed (134) hide show
  1. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/PKG-INFO +2 -3
  2. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/pyproject.toml +18 -7
  3. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/__init__.py +5 -4
  4. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/app/api.py +27 -24
  5. griptape_nodes-0.52.0/src/griptape_nodes/app/app.py +487 -0
  6. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/app/watch.py +17 -2
  7. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +66 -103
  8. griptape_nodes-0.52.0/src/griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +31 -0
  9. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/exe_types/core_types.py +16 -4
  10. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/exe_types/node_types.py +74 -16
  11. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/machines/control_flow.py +21 -26
  12. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/machines/fsm.py +16 -16
  13. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/machines/node_resolution.py +30 -119
  14. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/mcp_server/server.py +14 -10
  15. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/mcp_server/ws_request_manager.py +2 -2
  16. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/node_library/workflow_registry.py +5 -0
  17. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/base_events.py +12 -7
  18. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/execution_events.py +0 -6
  19. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/node_events.py +38 -0
  20. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/parameter_events.py +11 -0
  21. griptape_nodes-0.52.0/src/griptape_nodes/retained_mode/events/variable_events.py +361 -0
  22. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/workflow_events.py +35 -0
  23. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/griptape_nodes.py +61 -26
  24. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/agent_manager.py +8 -9
  25. griptape_nodes-0.52.0/src/griptape_nodes/retained_mode/managers/event_manager.py +298 -0
  26. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/flow_manager.py +39 -33
  27. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +14 -14
  28. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +20 -20
  29. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +1 -1
  30. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +1 -1
  31. {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
  32. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +1 -1
  33. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +1 -1
  34. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_manager.py +20 -19
  35. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/node_manager.py +83 -8
  36. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/object_manager.py +4 -0
  37. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/settings.py +1 -0
  38. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/sync_manager.py +3 -9
  39. griptape_nodes-0.52.0/src/griptape_nodes/retained_mode/managers/variable_manager.py +529 -0
  40. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/workflow_manager.py +156 -50
  41. griptape_nodes-0.52.0/src/griptape_nodes/retained_mode/variable_types.py +18 -0
  42. griptape_nodes-0.52.0/src/griptape_nodes/utils/__init__.py +5 -0
  43. griptape_nodes-0.52.0/src/griptape_nodes/utils/async_utils.py +89 -0
  44. griptape_nodes-0.51.2/src/griptape_nodes/app/app.py +0 -465
  45. griptape_nodes-0.51.2/src/griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +0 -90
  46. griptape_nodes-0.51.2/src/griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +0 -19
  47. griptape_nodes-0.51.2/src/griptape_nodes/retained_mode/managers/event_manager.py +0 -157
  48. griptape_nodes-0.51.2/src/griptape_nodes/utils/__init__.py +0 -1
  49. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/README.md +0 -0
  50. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/app/.python-version +0 -0
  51. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/app/__init__.py +0 -0
  52. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/bootstrap/__init__.py +0 -0
  53. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
  54. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/drivers/__init__.py +0 -0
  55. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/drivers/storage/__init__.py +0 -0
  56. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/drivers/storage/base_storage_driver.py +0 -0
  57. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +0 -0
  58. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/drivers/storage/local_storage_driver.py +0 -0
  59. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/drivers/storage/storage_backend.py +0 -0
  60. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/exe_types/__init__.py +0 -0
  61. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/exe_types/connections.py +0 -0
  62. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/exe_types/flow.py +0 -0
  63. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/exe_types/type_validator.py +0 -0
  64. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/machines/__init__.py +0 -0
  65. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/mcp_server/__init__.py +0 -0
  66. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/node_library/__init__.py +0 -0
  67. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/node_library/advanced_node_library.py +0 -0
  68. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/node_library/library_registry.py +0 -0
  69. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/py.typed +0 -0
  70. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/__init__.py +0 -0
  71. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/__init__.py +0 -0
  72. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/agent_events.py +0 -0
  73. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/app_events.py +0 -0
  74. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/arbitrary_python_events.py +0 -0
  75. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/config_events.py +0 -0
  76. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/connection_events.py +0 -0
  77. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/context_events.py +0 -0
  78. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/flow_events.py +0 -0
  79. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
  80. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/library_events.py +0 -0
  81. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/logger_events.py +0 -0
  82. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/object_events.py +0 -0
  83. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/os_events.py +0 -0
  84. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/payload_registry.py +0 -0
  85. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/secrets_events.py +0 -0
  86. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/static_file_events.py +0 -0
  87. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/sync_events.py +0 -0
  88. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/events/validation_events.py +0 -0
  89. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/__init__.py +0 -0
  90. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
  91. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/config_manager.py +0 -0
  92. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/context_manager.py +0 -0
  93. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/engine_identity_manager.py +0 -0
  94. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +0 -0
  95. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +0 -0
  96. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +0 -0
  97. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +0 -0
  98. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +0 -0
  99. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
  100. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/os_manager.py +0 -0
  101. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/secrets_manager.py +0 -0
  102. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/session_manager.py +0 -0
  103. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
  104. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/managers/version_compatibility_manager.py +0 -0
  105. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/retained_mode.py +0 -0
  106. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/utils/__init__.py +0 -0
  107. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/utils/engine_identity.py +0 -0
  108. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/retained_mode/utils/name_generator.py +0 -0
  109. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/__init__.py +0 -0
  110. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/add_param_button.py +0 -0
  111. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/button.py +0 -0
  112. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/clamp.py +0 -0
  113. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/compare.py +0 -0
  114. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/compare_images.py +0 -0
  115. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/file_system_picker.py +0 -0
  116. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/minmax.py +0 -0
  117. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/options.py +0 -0
  118. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/slider.py +0 -0
  119. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/trait_registry.py +0 -0
  120. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/traits/traits.json +0 -0
  121. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/updater/__init__.py +0 -0
  122. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/updater/__main__.py +0 -0
  123. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/utils/dict_utils.py +0 -0
  124. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/utils/image_preview.py +0 -0
  125. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/utils/metaclasses.py +0 -0
  126. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/utils/uv_utils.py +0 -0
  127. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/utils/version_utils.py +0 -0
  128. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/version_compatibility/__init__.py +0 -0
  129. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/version_compatibility/versions/__init__.py +0 -0
  130. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
  131. {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
  132. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/version_compatibility/workflow_versions/__init__.py +0 -0
  133. {griptape_nodes-0.51.2 → griptape_nodes-0.52.0}/src/griptape_nodes/version_compatibility/workflow_versions/v0_7_0/__init__.py +0 -0
  134. {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.51.2
3
+ Version: 0.52.0
4
4
  Summary: Add your description here
5
- Requires-Dist: griptape>=1.8.0
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.51.2"
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.0",
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 = ["pytest>=8.3.5", "pytest-mock>=3.14.0", "pytest-xdist>=3.6.1"]
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 TYPE_CHECKING, Annotated
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, queue: Annotated[Queue, Depends(get_event_queue)]) -> None:
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
- def start_api(static_directory: Path, queue: Queue) -> None:
167
- """Run FastAPI with Uvicorn in order to serve static files produced by nodes."""
168
- global event_queue, static_dir # noqa: PLW0603
169
- event_queue = queue
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
- uvicorn.run(
194
- app, host=STATIC_SERVER_HOST, port=STATIC_SERVER_PORT, log_level=STATIC_SERVER_LOG_LEVEL, log_config=None
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)