griptape-nodes 0.51.2__py3-none-any.whl → 0.52.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- griptape_nodes/__init__.py +5 -4
- griptape_nodes/app/api.py +22 -30
- griptape_nodes/app/app.py +374 -289
- griptape_nodes/app/watch.py +17 -2
- griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +66 -103
- griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +16 -4
- griptape_nodes/exe_types/core_types.py +16 -4
- griptape_nodes/exe_types/node_types.py +74 -16
- griptape_nodes/machines/control_flow.py +21 -26
- griptape_nodes/machines/fsm.py +16 -16
- griptape_nodes/machines/node_resolution.py +30 -119
- griptape_nodes/mcp_server/server.py +14 -10
- griptape_nodes/mcp_server/ws_request_manager.py +2 -2
- griptape_nodes/node_library/workflow_registry.py +5 -0
- griptape_nodes/retained_mode/events/base_events.py +12 -7
- griptape_nodes/retained_mode/events/execution_events.py +0 -6
- griptape_nodes/retained_mode/events/node_events.py +38 -0
- griptape_nodes/retained_mode/events/parameter_events.py +11 -0
- griptape_nodes/retained_mode/events/variable_events.py +361 -0
- griptape_nodes/retained_mode/events/workflow_events.py +35 -0
- griptape_nodes/retained_mode/griptape_nodes.py +61 -26
- griptape_nodes/retained_mode/managers/agent_manager.py +8 -9
- griptape_nodes/retained_mode/managers/event_manager.py +215 -74
- griptape_nodes/retained_mode/managers/flow_manager.py +39 -33
- griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +14 -14
- griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +20 -20
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +1 -1
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +1 -1
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +4 -3
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +1 -1
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +1 -1
- griptape_nodes/retained_mode/managers/library_manager.py +20 -19
- griptape_nodes/retained_mode/managers/node_manager.py +83 -8
- griptape_nodes/retained_mode/managers/object_manager.py +4 -0
- griptape_nodes/retained_mode/managers/settings.py +1 -0
- griptape_nodes/retained_mode/managers/sync_manager.py +3 -9
- griptape_nodes/retained_mode/managers/variable_manager.py +529 -0
- griptape_nodes/retained_mode/managers/workflow_manager.py +156 -50
- griptape_nodes/retained_mode/variable_types.py +18 -0
- griptape_nodes/utils/__init__.py +4 -0
- griptape_nodes/utils/async_utils.py +89 -0
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/METADATA +2 -3
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/RECORD +45 -42
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/WHEEL +1 -1
- griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +0 -90
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.1.dist-info}/entry_points.txt +0 -0
|
@@ -36,9 +36,7 @@ from griptape_nodes.retained_mode.events.app_events import (
|
|
|
36
36
|
SetEngineNameResultSuccess,
|
|
37
37
|
)
|
|
38
38
|
from griptape_nodes.retained_mode.events.base_events import (
|
|
39
|
-
|
|
40
|
-
RequestPayload,
|
|
41
|
-
ResultPayload,
|
|
39
|
+
GriptapeNodeEvent,
|
|
42
40
|
ResultPayloadFailure,
|
|
43
41
|
)
|
|
44
42
|
from griptape_nodes.retained_mode.events.flow_events import (
|
|
@@ -48,6 +46,11 @@ from griptape_nodes.utils.metaclasses import SingletonMeta
|
|
|
48
46
|
from griptape_nodes.utils.version_utils import engine_version
|
|
49
47
|
|
|
50
48
|
if TYPE_CHECKING:
|
|
49
|
+
from griptape_nodes.retained_mode.events.base_events import (
|
|
50
|
+
AppPayload,
|
|
51
|
+
RequestPayload,
|
|
52
|
+
ResultPayload,
|
|
53
|
+
)
|
|
51
54
|
from griptape_nodes.retained_mode.managers.agent_manager import AgentManager
|
|
52
55
|
from griptape_nodes.retained_mode.managers.arbitrary_code_exec_manager import (
|
|
53
56
|
ArbitraryCodeExecManager,
|
|
@@ -70,6 +73,9 @@ if TYPE_CHECKING:
|
|
|
70
73
|
StaticFilesManager,
|
|
71
74
|
)
|
|
72
75
|
from griptape_nodes.retained_mode.managers.sync_manager import SyncManager
|
|
76
|
+
from griptape_nodes.retained_mode.managers.variable_manager import (
|
|
77
|
+
VariablesManager,
|
|
78
|
+
)
|
|
73
79
|
from griptape_nodes.retained_mode.managers.version_compatibility_manager import (
|
|
74
80
|
VersionCompatibilityManager,
|
|
75
81
|
)
|
|
@@ -132,6 +138,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
132
138
|
_context_manager: ContextManager
|
|
133
139
|
_library_manager: LibraryManager
|
|
134
140
|
_workflow_manager: WorkflowManager
|
|
141
|
+
_workflow_variables_manager: VariablesManager
|
|
135
142
|
_arbitrary_code_exec_manager: ArbitraryCodeExecManager
|
|
136
143
|
_operation_depth_manager: OperationDepthManager
|
|
137
144
|
_static_files_manager: StaticFilesManager
|
|
@@ -164,6 +171,9 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
164
171
|
StaticFilesManager,
|
|
165
172
|
)
|
|
166
173
|
from griptape_nodes.retained_mode.managers.sync_manager import SyncManager
|
|
174
|
+
from griptape_nodes.retained_mode.managers.variable_manager import (
|
|
175
|
+
VariablesManager,
|
|
176
|
+
)
|
|
167
177
|
from griptape_nodes.retained_mode.managers.version_compatibility_manager import (
|
|
168
178
|
VersionCompatibilityManager,
|
|
169
179
|
)
|
|
@@ -183,6 +193,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
183
193
|
self._context_manager = ContextManager(self._event_manager)
|
|
184
194
|
self._library_manager = LibraryManager(self._event_manager)
|
|
185
195
|
self._workflow_manager = WorkflowManager(self._event_manager)
|
|
196
|
+
self._workflow_variables_manager = VariablesManager(self._event_manager)
|
|
186
197
|
self._arbitrary_code_exec_manager = ArbitraryCodeExecManager(self._event_manager)
|
|
187
198
|
self._operation_depth_manager = OperationDepthManager(self._config_manager)
|
|
188
199
|
self._static_files_manager = StaticFilesManager(
|
|
@@ -226,33 +237,53 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
226
237
|
def handle_request(
|
|
227
238
|
cls,
|
|
228
239
|
request: RequestPayload,
|
|
229
|
-
*,
|
|
230
|
-
response_topic: str | None = None,
|
|
231
|
-
request_id: str | None = None,
|
|
232
240
|
) -> ResultPayload:
|
|
241
|
+
"""Synchronous request handler."""
|
|
233
242
|
event_mgr = GriptapeNodes.EventManager()
|
|
234
|
-
|
|
235
|
-
workflow_mgr = GriptapeNodes.WorkflowManager()
|
|
243
|
+
|
|
236
244
|
try:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
operation_depth_mgr=obj_depth_mgr,
|
|
240
|
-
workflow_mgr=workflow_mgr,
|
|
241
|
-
response_topic=response_topic,
|
|
242
|
-
request_id=request_id,
|
|
243
|
-
)
|
|
245
|
+
result_event = event_mgr.handle_request(request=request)
|
|
246
|
+
event_mgr.put_event(GriptapeNodeEvent(wrapped_event=result_event))
|
|
244
247
|
except Exception as e:
|
|
245
248
|
logger.exception(
|
|
246
249
|
"Unhandled exception while processing request of type %s. "
|
|
247
|
-
"Consider saving your work and restarting the engine if issues persist."
|
|
250
|
+
"Consider saving your work and restarting the engine if issues persist."
|
|
251
|
+
"Request: %s",
|
|
248
252
|
type(request).__name__,
|
|
253
|
+
request,
|
|
249
254
|
)
|
|
250
255
|
return ResultPayloadFailure(exception=e)
|
|
256
|
+
else:
|
|
257
|
+
return result_event.result
|
|
251
258
|
|
|
252
259
|
@classmethod
|
|
253
|
-
def
|
|
260
|
+
async def ahandle_request(cls, request: RequestPayload) -> ResultPayload:
|
|
261
|
+
"""Asynchronous request handler.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
request: The request payload to handle.
|
|
265
|
+
"""
|
|
266
|
+
event_mgr = GriptapeNodes.EventManager()
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
result_event = await event_mgr.ahandle_request(request=request)
|
|
270
|
+
await event_mgr.aput_event(GriptapeNodeEvent(wrapped_event=result_event))
|
|
271
|
+
except Exception as e:
|
|
272
|
+
logger.exception(
|
|
273
|
+
"Unhandled exception while processing async request of type %s. "
|
|
274
|
+
"Consider saving your work and restarting the engine if issues persist."
|
|
275
|
+
"Request: %s",
|
|
276
|
+
type(request).__name__,
|
|
277
|
+
request,
|
|
278
|
+
)
|
|
279
|
+
return ResultPayloadFailure(exception=e)
|
|
280
|
+
else:
|
|
281
|
+
return result_event.result
|
|
282
|
+
|
|
283
|
+
@classmethod
|
|
284
|
+
async def broadcast_app_event(cls, app_event: AppPayload) -> None:
|
|
254
285
|
event_mgr = GriptapeNodes.get_instance()._event_manager
|
|
255
|
-
|
|
286
|
+
await event_mgr.broadcast_app_event(app_event)
|
|
256
287
|
|
|
257
288
|
@classmethod
|
|
258
289
|
def get_session_id(cls) -> str | None:
|
|
@@ -334,6 +365,10 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
334
365
|
def SyncManager(cls) -> SyncManager:
|
|
335
366
|
return GriptapeNodes.get_instance()._sync_manager
|
|
336
367
|
|
|
368
|
+
@classmethod
|
|
369
|
+
def VariablesManager(cls) -> VariablesManager:
|
|
370
|
+
return GriptapeNodes.get_instance()._workflow_variables_manager
|
|
371
|
+
|
|
337
372
|
@classmethod
|
|
338
373
|
def clear_data(cls) -> None:
|
|
339
374
|
# Get canvas
|
|
@@ -357,16 +392,16 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
357
392
|
msg = "Failed to successfully delete all objects"
|
|
358
393
|
raise ValueError(msg)
|
|
359
394
|
|
|
360
|
-
def on_app_connection_established(self, _payload: AppConnectionEstablished) -> None:
|
|
395
|
+
async def on_app_connection_established(self, _payload: AppConnectionEstablished) -> None:
|
|
361
396
|
from griptape_nodes.app.app import subscribe_to_topic
|
|
362
397
|
|
|
363
398
|
# Subscribe to request topic (engine discovery)
|
|
364
|
-
subscribe_to_topic("request")
|
|
399
|
+
await subscribe_to_topic("request")
|
|
365
400
|
|
|
366
401
|
# Get engine ID and subscribe to engine_id/request
|
|
367
402
|
engine_id = GriptapeNodes.get_engine_id()
|
|
368
403
|
if engine_id:
|
|
369
|
-
subscribe_to_topic(f"engines/{engine_id}/request")
|
|
404
|
+
await subscribe_to_topic(f"engines/{engine_id}/request")
|
|
370
405
|
else:
|
|
371
406
|
logger.warning("Engine ID not available for subscription")
|
|
372
407
|
|
|
@@ -374,7 +409,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
374
409
|
session_id = GriptapeNodes.get_session_id()
|
|
375
410
|
if session_id:
|
|
376
411
|
topic = f"sessions/{session_id}/request"
|
|
377
|
-
subscribe_to_topic(topic)
|
|
412
|
+
await subscribe_to_topic(topic)
|
|
378
413
|
else:
|
|
379
414
|
logger.info("No session ID available for subscription")
|
|
380
415
|
|
|
@@ -395,7 +430,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
395
430
|
logger.error(details)
|
|
396
431
|
return GetEngineVersionResultFailure(result_details=details)
|
|
397
432
|
|
|
398
|
-
def handle_session_start_request(self, request: AppStartSessionRequest) -> ResultPayload: # noqa: ARG002
|
|
433
|
+
async def handle_session_start_request(self, request: AppStartSessionRequest) -> ResultPayload: # noqa: ARG002
|
|
399
434
|
from griptape_nodes.app.app import subscribe_to_topic
|
|
400
435
|
|
|
401
436
|
current_session_id = GriptapeNodes.SessionManager().get_active_session_id()
|
|
@@ -409,12 +444,12 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
409
444
|
details = f"Session '{current_session_id}' already active. Joining..."
|
|
410
445
|
|
|
411
446
|
topic = f"sessions/{current_session_id}/request"
|
|
412
|
-
subscribe_to_topic(topic)
|
|
447
|
+
await subscribe_to_topic(topic)
|
|
413
448
|
logger.info("Subscribed to new session topic: %s", topic)
|
|
414
449
|
|
|
415
450
|
return AppStartSessionResultSuccess(current_session_id)
|
|
416
451
|
|
|
417
|
-
def handle_session_end_request(self, _: AppEndSessionRequest) -> ResultPayload:
|
|
452
|
+
async def handle_session_end_request(self, _: AppEndSessionRequest) -> ResultPayload:
|
|
418
453
|
from griptape_nodes.app.app import unsubscribe_from_topic
|
|
419
454
|
|
|
420
455
|
try:
|
|
@@ -428,7 +463,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
428
463
|
GriptapeNodes.SessionManager().clear_saved_session()
|
|
429
464
|
|
|
430
465
|
unsubscribe_topic = f"sessions/{previous_session_id}/request"
|
|
431
|
-
unsubscribe_from_topic(unsubscribe_topic)
|
|
466
|
+
await unsubscribe_from_topic(unsubscribe_topic)
|
|
432
467
|
|
|
433
468
|
return AppEndSessionResultSuccess(session_id=previous_session_id)
|
|
434
469
|
except Exception as err:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import json
|
|
2
3
|
import logging
|
|
3
4
|
import os
|
|
4
|
-
import threading
|
|
5
5
|
import uuid
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
@@ -10,7 +10,7 @@ from griptape.artifacts import ErrorArtifact, ImageUrlArtifact, JsonArtifact
|
|
|
10
10
|
from griptape.drivers.image_generation import BaseImageGenerationDriver
|
|
11
11
|
from griptape.drivers.image_generation.griptape_cloud import GriptapeCloudImageGenerationDriver
|
|
12
12
|
from griptape.drivers.prompt.griptape_cloud import GriptapeCloudPromptDriver
|
|
13
|
-
from griptape.events import
|
|
13
|
+
from griptape.events import FinishTaskEvent, TextChunkEvent
|
|
14
14
|
from griptape.loaders import ImageLoader
|
|
15
15
|
from griptape.memory.structure import ConversationMemory
|
|
16
16
|
from griptape.rules import Rule, Ruleset
|
|
@@ -38,6 +38,7 @@ from griptape_nodes.retained_mode.events.agent_events import (
|
|
|
38
38
|
RunAgentResultSuccess,
|
|
39
39
|
)
|
|
40
40
|
from griptape_nodes.retained_mode.events.base_events import ExecutionEvent, ExecutionGriptapeNodeEvent, ResultPayload
|
|
41
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
41
42
|
from griptape_nodes.retained_mode.managers.config_manager import ConfigManager
|
|
42
43
|
from griptape_nodes.retained_mode.managers.event_manager import EventManager
|
|
43
44
|
from griptape_nodes.retained_mode.managers.secrets_manager import SecretsManager
|
|
@@ -128,14 +129,14 @@ class AgentManager:
|
|
|
128
129
|
}
|
|
129
130
|
return MCPTool(connection=connection)
|
|
130
131
|
|
|
131
|
-
def on_handle_run_agent_request(self, request: RunAgentRequest) -> ResultPayload:
|
|
132
|
+
async def on_handle_run_agent_request(self, request: RunAgentRequest) -> ResultPayload:
|
|
132
133
|
if self.prompt_driver is None:
|
|
133
134
|
self.prompt_driver = self._initialize_prompt_driver()
|
|
134
135
|
if self.image_tool is None:
|
|
135
136
|
self.image_tool = self._initialize_image_tool()
|
|
136
137
|
if self.mcp_tool is None:
|
|
137
138
|
self.mcp_tool = self._initialize_mcp_tool()
|
|
138
|
-
|
|
139
|
+
await asyncio.to_thread(self._on_handle_run_agent_request, request)
|
|
139
140
|
return RunAgentResultStarted()
|
|
140
141
|
|
|
141
142
|
def _create_agent(self) -> Agent:
|
|
@@ -168,10 +169,8 @@ class AgentManager:
|
|
|
168
169
|
],
|
|
169
170
|
)
|
|
170
171
|
|
|
171
|
-
def _on_handle_run_agent_request(
|
|
172
|
-
|
|
173
|
-
) -> ResultPayload:
|
|
174
|
-
EventBus.event_listeners = event_listeners
|
|
172
|
+
def _on_handle_run_agent_request(self, request: RunAgentRequest) -> ResultPayload:
|
|
173
|
+
# EventBus functionality removed - events now go directly to event queue
|
|
175
174
|
try:
|
|
176
175
|
artifacts = [
|
|
177
176
|
ImageLoader().parse(ImageUrlArtifact.from_dict(url_artifact).to_bytes())
|
|
@@ -190,7 +189,7 @@ class AgentManager:
|
|
|
190
189
|
if "conversation_output" in result_json:
|
|
191
190
|
new_conversation_output = result_json["conversation_output"]
|
|
192
191
|
if new_conversation_output != last_conversation_output:
|
|
193
|
-
|
|
192
|
+
GriptapeNodes.EventManager().put_event(
|
|
194
193
|
ExecutionGriptapeNodeEvent(
|
|
195
194
|
wrapped_event=ExecutionEvent(
|
|
196
195
|
payload=AgentStreamEvent(
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import inspect
|
|
1
5
|
from collections import defaultdict
|
|
2
|
-
from collections.abc import Callable
|
|
3
6
|
from dataclasses import fields
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
7
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
5
8
|
|
|
6
|
-
from
|
|
7
|
-
from typing_extensions import TypeVar
|
|
9
|
+
from typing_extensions import TypedDict, TypeVar
|
|
8
10
|
|
|
9
11
|
from griptape_nodes.retained_mode.events.base_events import (
|
|
10
12
|
AppPayload,
|
|
@@ -12,37 +14,99 @@ from griptape_nodes.retained_mode.events.base_events import (
|
|
|
12
14
|
EventResultFailure,
|
|
13
15
|
EventResultSuccess,
|
|
14
16
|
FlushParameterChangesRequest,
|
|
15
|
-
GriptapeNodeEvent,
|
|
16
17
|
RequestPayload,
|
|
17
18
|
ResultPayload,
|
|
18
19
|
WorkflowAlteredMixin,
|
|
19
20
|
)
|
|
21
|
+
from griptape_nodes.utils.async_utils import call_function
|
|
20
22
|
|
|
21
23
|
if TYPE_CHECKING:
|
|
22
|
-
from
|
|
23
|
-
|
|
24
|
+
from collections.abc import Awaitable, Callable
|
|
25
|
+
|
|
24
26
|
|
|
25
27
|
RP = TypeVar("RP", bound=RequestPayload, default=RequestPayload)
|
|
26
28
|
AP = TypeVar("AP", bound=AppPayload, default=AppPayload)
|
|
27
29
|
|
|
28
30
|
|
|
31
|
+
class ResultContext(TypedDict, total=False):
|
|
32
|
+
response_topic: str | None
|
|
33
|
+
request_id: str | None
|
|
34
|
+
|
|
35
|
+
|
|
29
36
|
class EventManager:
|
|
30
37
|
def __init__(self) -> None:
|
|
31
38
|
# Dictionary to store the SPECIFIC manager for each request type
|
|
32
39
|
self._request_type_to_manager: dict[type[RequestPayload], Callable] = defaultdict(list) # pyright: ignore[reportAttributeAccessIssue]
|
|
33
40
|
# Dictionary to store ALL SUBSCRIBERS to app events.
|
|
34
41
|
self._app_event_listeners: dict[type[AppPayload], set[Callable]] = {}
|
|
35
|
-
self.current_active_node: str | None = None
|
|
36
42
|
# Boolean that lets us know if there is currently a FlushParameterChangesRequest in the event queue.
|
|
37
43
|
self._flush_in_queue: bool = False
|
|
44
|
+
# Event queue for publishing events
|
|
45
|
+
self._event_queue: asyncio.Queue | None = None
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def event_queue(self) -> asyncio.Queue:
|
|
49
|
+
if self._event_queue is None:
|
|
50
|
+
msg = "Event queue has not been initialized. Please call 'initialize_queue' with an asyncio.Queue instance before accessing the event queue."
|
|
51
|
+
raise ValueError(msg)
|
|
52
|
+
return self._event_queue
|
|
38
53
|
|
|
39
54
|
def clear_flush_in_queue(self) -> None:
|
|
40
55
|
self._flush_in_queue = False
|
|
41
56
|
|
|
57
|
+
def initialize_queue(self, queue: asyncio.Queue | None = None) -> None:
|
|
58
|
+
"""Set the event queue for this manager.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
queue: The asyncio.Queue to use for events, or None to clear
|
|
62
|
+
"""
|
|
63
|
+
if queue is not None:
|
|
64
|
+
self._event_queue = queue
|
|
65
|
+
else:
|
|
66
|
+
try:
|
|
67
|
+
self._event_queue = asyncio.Queue()
|
|
68
|
+
except RuntimeError:
|
|
69
|
+
# Defer queue creation until we're in an event loop
|
|
70
|
+
self._event_queue = None
|
|
71
|
+
|
|
72
|
+
def put_event(self, event: Any) -> None:
|
|
73
|
+
"""Put event into async queue from sync context (non-blocking).
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
event: The event to publish to the queue
|
|
77
|
+
"""
|
|
78
|
+
if self._event_queue is None:
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
self._event_queue.put_nowait(event)
|
|
82
|
+
|
|
83
|
+
async def aput_event(self, event: Any) -> None:
|
|
84
|
+
"""Put event into async queue from async context.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
event: The event to publish to the queue
|
|
88
|
+
"""
|
|
89
|
+
if self._event_queue is None:
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
await self._event_queue.put(event)
|
|
93
|
+
|
|
94
|
+
def put_event_threadsafe(self, loop: Any, event: Any) -> None:
|
|
95
|
+
"""Put event into async queue from sync context in a thread-safe manner.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
loop: The asyncio event loop to use for thread-safe operation
|
|
99
|
+
event: The event to publish to the queue
|
|
100
|
+
"""
|
|
101
|
+
if self._event_queue is None:
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
loop.call_soon_threadsafe(self._event_queue.put_nowait, event)
|
|
105
|
+
|
|
42
106
|
def assign_manager_to_request_type(
|
|
43
107
|
self,
|
|
44
108
|
request_type: type[RP],
|
|
45
|
-
callback: Callable[[RP], ResultPayload],
|
|
109
|
+
callback: Callable[[RP], ResultPayload] | Callable[[RP], Awaitable[ResultPayload]],
|
|
46
110
|
) -> None:
|
|
47
111
|
"""Assign a manager to handle a request.
|
|
48
112
|
|
|
@@ -65,79 +129,154 @@ class EventManager:
|
|
|
65
129
|
if request_type in self._request_type_to_manager:
|
|
66
130
|
del self._request_type_to_manager[request_type]
|
|
67
131
|
|
|
68
|
-
def
|
|
132
|
+
def _handle_request_core(
|
|
133
|
+
self,
|
|
134
|
+
request: RP,
|
|
135
|
+
callback_result: ResultPayload,
|
|
136
|
+
*,
|
|
137
|
+
context: ResultContext,
|
|
138
|
+
) -> EventResultSuccess | EventResultFailure:
|
|
139
|
+
"""Core logic for handling requests, shared between sync and async methods."""
|
|
140
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
141
|
+
|
|
142
|
+
operation_depth_mgr = GriptapeNodes.OperationDepthManager()
|
|
143
|
+
workflow_mgr = GriptapeNodes.WorkflowManager()
|
|
144
|
+
|
|
145
|
+
with operation_depth_mgr as depth_manager:
|
|
146
|
+
# Now see if the WorkflowManager was asking us to squelch altered_workflow_state commands
|
|
147
|
+
# This prevents situations like loading a workflow (which naturally alters the workflow state)
|
|
148
|
+
# from coming in and immediately being flagged as being dirty.
|
|
149
|
+
if workflow_mgr.should_squelch_workflow_altered():
|
|
150
|
+
callback_result.altered_workflow_state = False
|
|
151
|
+
|
|
152
|
+
retained_mode_str = None
|
|
153
|
+
# If request_id exists, that means it's a direct request from the GUI (not internal), and should be echoed by retained mode.
|
|
154
|
+
if depth_manager.is_top_level() and context.get("request_id") is not None:
|
|
155
|
+
retained_mode_str = depth_manager.request_retained_mode_translation(request)
|
|
156
|
+
|
|
157
|
+
# Some requests have fields marked as "omit_from_result" which should be removed from the request
|
|
158
|
+
for field in fields(request):
|
|
159
|
+
if field.metadata.get("omit_from_result", False):
|
|
160
|
+
setattr(request, field.name, None)
|
|
161
|
+
if callback_result.succeeded():
|
|
162
|
+
result_event = EventResultSuccess(
|
|
163
|
+
request=request,
|
|
164
|
+
request_id=context.get("request_id"),
|
|
165
|
+
result=callback_result,
|
|
166
|
+
retained_mode=retained_mode_str,
|
|
167
|
+
response_topic=context.get("response_topic"),
|
|
168
|
+
)
|
|
169
|
+
else:
|
|
170
|
+
result_event = EventResultFailure(
|
|
171
|
+
request=request,
|
|
172
|
+
request_id=context.get("request_id"),
|
|
173
|
+
result=callback_result,
|
|
174
|
+
retained_mode=retained_mode_str,
|
|
175
|
+
response_topic=context.get("response_topic"),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
return result_event
|
|
179
|
+
|
|
180
|
+
async def ahandle_request(
|
|
69
181
|
self,
|
|
70
182
|
request: RP,
|
|
71
183
|
*,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
response_topic: str | None = None,
|
|
75
|
-
request_id: str | None = None,
|
|
76
|
-
) -> ResultPayload:
|
|
184
|
+
result_context: ResultContext | None = None,
|
|
185
|
+
) -> EventResultSuccess | EventResultFailure:
|
|
77
186
|
"""Publish an event to the manager assigned to its type.
|
|
78
187
|
|
|
79
188
|
Args:
|
|
80
189
|
request: The request to handle
|
|
81
|
-
|
|
82
|
-
workflow_mgr: The workflow manager to use
|
|
83
|
-
response_topic: The topic to send the response to (optional)
|
|
84
|
-
request_id: The ID of the request to correlate with the response (optional)
|
|
190
|
+
result_context: The result context containing response_topic and request_id
|
|
85
191
|
"""
|
|
192
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
193
|
+
|
|
194
|
+
operation_depth_mgr = GriptapeNodes.OperationDepthManager()
|
|
195
|
+
if result_context is None:
|
|
196
|
+
result_context = ResultContext()
|
|
197
|
+
|
|
86
198
|
# Notify the manager of the event type
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
result_event = EventResultFailure(
|
|
126
|
-
request=request,
|
|
127
|
-
request_id=request_id,
|
|
128
|
-
result=result_payload,
|
|
129
|
-
retained_mode=retained_mode_str,
|
|
130
|
-
response_topic=response_topic,
|
|
131
|
-
)
|
|
132
|
-
wrapped_event = GriptapeNodeEvent(wrapped_event=result_event)
|
|
133
|
-
EventBus.publish_event(wrapped_event)
|
|
134
|
-
else:
|
|
135
|
-
msg = f"No manager found to handle request of type '{request_type.__name__}."
|
|
136
|
-
raise TypeError(msg)
|
|
199
|
+
request_type = type(request)
|
|
200
|
+
callback = self._request_type_to_manager.get(request_type)
|
|
201
|
+
if not callback:
|
|
202
|
+
msg = f"No manager found to handle request of type '{request_type.__name__}'."
|
|
203
|
+
raise TypeError(msg)
|
|
204
|
+
|
|
205
|
+
# Actually make the handler callback (support both sync and async):
|
|
206
|
+
result_payload: ResultPayload = await call_function(callback, request)
|
|
207
|
+
|
|
208
|
+
# Handle workflow alteration events for async context
|
|
209
|
+
with operation_depth_mgr:
|
|
210
|
+
if (
|
|
211
|
+
result_payload.succeeded()
|
|
212
|
+
and isinstance(result_payload, WorkflowAlteredMixin)
|
|
213
|
+
and not self._flush_in_queue
|
|
214
|
+
):
|
|
215
|
+
await self.aput_event(EventRequest(request=FlushParameterChangesRequest()))
|
|
216
|
+
self._flush_in_queue = True
|
|
217
|
+
|
|
218
|
+
return self._handle_request_core(
|
|
219
|
+
request,
|
|
220
|
+
cast("ResultPayload", result_payload),
|
|
221
|
+
context=result_context,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def handle_request(
|
|
225
|
+
self,
|
|
226
|
+
request: RP,
|
|
227
|
+
*,
|
|
228
|
+
result_context: ResultContext | None = None,
|
|
229
|
+
) -> EventResultSuccess | EventResultFailure:
|
|
230
|
+
"""Publish an event to the manager assigned to its type (sync version).
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
request: The request to handle
|
|
234
|
+
result_context: The result context containing response_topic and request_id
|
|
235
|
+
"""
|
|
236
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
137
237
|
|
|
138
|
-
|
|
238
|
+
operation_depth_mgr = GriptapeNodes.OperationDepthManager()
|
|
239
|
+
if result_context is None:
|
|
240
|
+
result_context = ResultContext()
|
|
139
241
|
|
|
140
|
-
|
|
242
|
+
# Notify the manager of the event type
|
|
243
|
+
request_type = type(request)
|
|
244
|
+
callback = self._request_type_to_manager.get(request_type)
|
|
245
|
+
if not callback:
|
|
246
|
+
msg = f"No manager found to handle request of type '{request_type.__name__}'."
|
|
247
|
+
raise TypeError(msg)
|
|
248
|
+
|
|
249
|
+
# Support async callbacks for sync method ONLY if there is no running event loop
|
|
250
|
+
if inspect.iscoroutinefunction(callback):
|
|
251
|
+
try:
|
|
252
|
+
asyncio.get_running_loop()
|
|
253
|
+
msg = "Async handler cannot be called with sync handle_request. Use ahandle_request instead."
|
|
254
|
+
raise ValueError(msg)
|
|
255
|
+
except RuntimeError:
|
|
256
|
+
# No event loop running, safe to use asyncio.run
|
|
257
|
+
result_payload: ResultPayload = asyncio.run(callback(request))
|
|
258
|
+
else:
|
|
259
|
+
result_payload: ResultPayload = callback(request)
|
|
260
|
+
|
|
261
|
+
# Handle workflow alteration events for sync context
|
|
262
|
+
with operation_depth_mgr:
|
|
263
|
+
if (
|
|
264
|
+
result_payload.succeeded()
|
|
265
|
+
and isinstance(result_payload, WorkflowAlteredMixin)
|
|
266
|
+
and not self._flush_in_queue
|
|
267
|
+
):
|
|
268
|
+
self.put_event(EventRequest(request=FlushParameterChangesRequest()))
|
|
269
|
+
self._flush_in_queue = True
|
|
270
|
+
|
|
271
|
+
return self._handle_request_core(
|
|
272
|
+
request,
|
|
273
|
+
cast("ResultPayload", result_payload),
|
|
274
|
+
context=result_context,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
def add_listener_to_app_event(
|
|
278
|
+
self, app_event_type: type[AP], callback: Callable[[AP], None] | Callable[[AP], Awaitable[None]]
|
|
279
|
+
) -> None:
|
|
141
280
|
listener_set = self._app_event_listeners.get(app_event_type)
|
|
142
281
|
if listener_set is None:
|
|
143
282
|
listener_set = set()
|
|
@@ -145,13 +284,15 @@ class EventManager:
|
|
|
145
284
|
|
|
146
285
|
listener_set.add(callback)
|
|
147
286
|
|
|
148
|
-
def remove_listener_for_app_event(
|
|
287
|
+
def remove_listener_for_app_event(
|
|
288
|
+
self, app_event_type: type[AP], callback: Callable[[AP], None] | Callable[[AP], Awaitable[None]]
|
|
289
|
+
) -> None:
|
|
149
290
|
listener_set = self._app_event_listeners[app_event_type]
|
|
150
291
|
listener_set.remove(callback)
|
|
151
292
|
|
|
152
|
-
def broadcast_app_event(self, app_event: AP) -> None:
|
|
293
|
+
async def broadcast_app_event(self, app_event: AP) -> None:
|
|
153
294
|
app_event_type = type(app_event)
|
|
154
295
|
if app_event_type in self._app_event_listeners:
|
|
155
296
|
listener_set = self._app_event_listeners[app_event_type]
|
|
156
297
|
for listener_callback in listener_set:
|
|
157
|
-
listener_callback
|
|
298
|
+
await call_function(listener_callback, app_event)
|