griptape-nodes 0.57.1__py3-none-any.whl → 0.58.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/api_client/__init__.py +9 -0
- griptape_nodes/api_client/client.py +279 -0
- griptape_nodes/api_client/request_client.py +273 -0
- griptape_nodes/app/app.py +57 -150
- griptape_nodes/bootstrap/utils/python_subprocess_executor.py +1 -1
- griptape_nodes/bootstrap/workflow_executors/local_session_workflow_executor.py +22 -50
- griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +6 -1
- griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +27 -46
- griptape_nodes/bootstrap/workflow_executors/utils/subprocess_script.py +7 -0
- griptape_nodes/bootstrap/workflow_publishers/local_workflow_publisher.py +3 -1
- griptape_nodes/bootstrap/workflow_publishers/subprocess_workflow_publisher.py +3 -1
- griptape_nodes/bootstrap/workflow_publishers/utils/subprocess_script.py +16 -1
- griptape_nodes/common/node_executor.py +466 -0
- griptape_nodes/drivers/storage/base_storage_driver.py +0 -11
- griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +7 -25
- griptape_nodes/drivers/storage/local_storage_driver.py +2 -2
- griptape_nodes/exe_types/connections.py +37 -9
- griptape_nodes/exe_types/core_types.py +1 -1
- griptape_nodes/exe_types/node_types.py +115 -22
- griptape_nodes/machines/control_flow.py +48 -7
- griptape_nodes/machines/parallel_resolution.py +98 -29
- griptape_nodes/machines/sequential_resolution.py +61 -22
- griptape_nodes/node_library/library_registry.py +24 -1
- griptape_nodes/node_library/workflow_registry.py +38 -2
- griptape_nodes/retained_mode/events/execution_events.py +8 -1
- griptape_nodes/retained_mode/events/flow_events.py +90 -3
- griptape_nodes/retained_mode/events/node_events.py +17 -10
- griptape_nodes/retained_mode/events/workflow_events.py +5 -0
- griptape_nodes/retained_mode/griptape_nodes.py +16 -219
- griptape_nodes/retained_mode/managers/config_manager.py +0 -46
- griptape_nodes/retained_mode/managers/engine_identity_manager.py +225 -74
- griptape_nodes/retained_mode/managers/flow_manager.py +1276 -230
- griptape_nodes/retained_mode/managers/library_manager.py +7 -8
- griptape_nodes/retained_mode/managers/node_manager.py +197 -9
- griptape_nodes/retained_mode/managers/secrets_manager.py +26 -0
- griptape_nodes/retained_mode/managers/session_manager.py +264 -227
- griptape_nodes/retained_mode/managers/settings.py +4 -38
- griptape_nodes/retained_mode/managers/static_files_manager.py +3 -3
- griptape_nodes/retained_mode/managers/version_compatibility_manager.py +135 -6
- griptape_nodes/retained_mode/managers/workflow_manager.py +206 -78
- griptape_nodes/servers/mcp.py +23 -15
- griptape_nodes/utils/async_utils.py +36 -0
- griptape_nodes/utils/dict_utils.py +8 -2
- griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +11 -6
- griptape_nodes/version_compatibility/workflow_versions/v0_7_0/local_executor_argument_addition.py +12 -5
- {griptape_nodes-0.57.1.dist-info → griptape_nodes-0.58.1.dist-info}/METADATA +4 -3
- {griptape_nodes-0.57.1.dist-info → griptape_nodes-0.58.1.dist-info}/RECORD +49 -47
- {griptape_nodes-0.57.1.dist-info → griptape_nodes-0.58.1.dist-info}/WHEEL +1 -1
- griptape_nodes/retained_mode/utils/engine_identity.py +0 -245
- griptape_nodes/servers/ws_request_manager.py +0 -268
- {griptape_nodes-0.57.1.dist-info → griptape_nodes-0.58.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import contextlib
|
|
3
|
-
import json
|
|
4
|
-
import logging
|
|
5
|
-
import os
|
|
6
|
-
import uuid
|
|
7
|
-
from collections.abc import Callable
|
|
8
|
-
from typing import Any, Generic, TypeVar
|
|
9
|
-
from urllib.parse import urljoin
|
|
10
|
-
|
|
11
|
-
import websockets
|
|
12
|
-
|
|
13
|
-
logger = logging.getLogger("griptape_nodes_mcp_server")
|
|
14
|
-
|
|
15
|
-
T = TypeVar("T")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class WebSocketConnectionManager:
|
|
19
|
-
"""Python equivalent of WebSocketConnectionManager in TypeScript."""
|
|
20
|
-
|
|
21
|
-
def __init__(
|
|
22
|
-
self,
|
|
23
|
-
websocket_url: str = urljoin(
|
|
24
|
-
os.getenv("GRIPTAPE_NODES_API_BASE_URL", "https://api.nodes.griptape.ai").replace("http", "ws"),
|
|
25
|
-
"/ws/engines/events?version=v2",
|
|
26
|
-
),
|
|
27
|
-
):
|
|
28
|
-
self.websocket_url = websocket_url
|
|
29
|
-
self.websocket: Any = None
|
|
30
|
-
self.connected = False
|
|
31
|
-
self.event_handlers: dict[str, list[Callable]] = {}
|
|
32
|
-
self.request_handlers: dict[str, tuple[Callable, Callable]] = {}
|
|
33
|
-
self._process_task: asyncio.Task | None = None
|
|
34
|
-
|
|
35
|
-
async def send(self, data: dict[str, Any]) -> None:
|
|
36
|
-
"""Send a message to the WebSocket server."""
|
|
37
|
-
if not self.websocket:
|
|
38
|
-
msg = "Not connected to WebSocket server"
|
|
39
|
-
raise ConnectionError(msg)
|
|
40
|
-
|
|
41
|
-
try:
|
|
42
|
-
message = json.dumps(data)
|
|
43
|
-
await self.websocket.send(message)
|
|
44
|
-
logger.debug("Sent message: %s", message)
|
|
45
|
-
except Exception as e:
|
|
46
|
-
logger.error("Failed to send message: %s", e)
|
|
47
|
-
raise
|
|
48
|
-
|
|
49
|
-
async def _process_messages(self) -> None:
|
|
50
|
-
"""Process incoming WebSocket messages."""
|
|
51
|
-
if not self.websocket:
|
|
52
|
-
logger.warning("WebSocket is not connected, cannot process messages")
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
try:
|
|
56
|
-
async for message in self.websocket:
|
|
57
|
-
try:
|
|
58
|
-
data = json.loads(message)
|
|
59
|
-
logger.debug("📥 Received message: %s", message)
|
|
60
|
-
await self._handle_message(data)
|
|
61
|
-
except json.JSONDecodeError:
|
|
62
|
-
logger.error("Failed to parse message: %s", message)
|
|
63
|
-
except Exception as e:
|
|
64
|
-
logger.error("Error processing message: %s", e)
|
|
65
|
-
except websockets.exceptions.ConnectionClosed:
|
|
66
|
-
logger.warning("WebSocket connection closed")
|
|
67
|
-
self.connected = False
|
|
68
|
-
except asyncio.CancelledError:
|
|
69
|
-
# Task was cancelled, just exit
|
|
70
|
-
pass
|
|
71
|
-
except Exception as e:
|
|
72
|
-
logger.error("Error in message processing loop: %s", e)
|
|
73
|
-
self.connected = False
|
|
74
|
-
|
|
75
|
-
async def _handle_message(self, data: dict[str, Any]) -> None:
|
|
76
|
-
request = data.get("payload", {}).get("request", {})
|
|
77
|
-
request_id = request.get("request_id")
|
|
78
|
-
|
|
79
|
-
if request_id and request_id in self.request_handlers:
|
|
80
|
-
success_handler, failure_handler = self.request_handlers[request_id]
|
|
81
|
-
try:
|
|
82
|
-
if data.get("type") == "success_result":
|
|
83
|
-
success_handler(data, request)
|
|
84
|
-
else:
|
|
85
|
-
failure_handler(data, request)
|
|
86
|
-
except Exception as e:
|
|
87
|
-
logger.error("Error in request handler: %s", e)
|
|
88
|
-
|
|
89
|
-
def subscribe_to_request_event(
|
|
90
|
-
self, success_handler: Callable[[Any, Any], None], failure_handler: Callable[[Any, Any], None]
|
|
91
|
-
) -> str:
|
|
92
|
-
"""Subscribe to a request-response event."""
|
|
93
|
-
request_id = str(uuid.uuid4())
|
|
94
|
-
self.request_handlers[request_id] = (success_handler, failure_handler)
|
|
95
|
-
return request_id
|
|
96
|
-
|
|
97
|
-
def unsubscribe_from_request_event(self, request_id: str) -> None:
|
|
98
|
-
"""Unsubscribe from a request-response event."""
|
|
99
|
-
if request_id in self.request_handlers:
|
|
100
|
-
del self.request_handlers[request_id]
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
class AsyncRequestManager(Generic[T]): # noqa: UP046
|
|
104
|
-
def __init__(self, connection_manager: WebSocketConnectionManager, api_key: str):
|
|
105
|
-
self.connection_manager = connection_manager
|
|
106
|
-
self.api_key = api_key
|
|
107
|
-
|
|
108
|
-
async def _subscribe_to_topic(self, topic: str) -> None:
|
|
109
|
-
"""Subscribe to a specific topic in the message bus."""
|
|
110
|
-
if self.connection_manager.websocket is None:
|
|
111
|
-
logger.warning("WebSocket connection not available for subscribing to topic")
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
try:
|
|
115
|
-
body = {"type": "subscribe", "topic": topic, "payload": {}}
|
|
116
|
-
await self.connection_manager.websocket.send(json.dumps(body))
|
|
117
|
-
logger.debug("Subscribed to topic: %s", topic)
|
|
118
|
-
except websockets.exceptions.WebSocketException as e:
|
|
119
|
-
logger.error("Error subscribing to topic %s: %s", topic, e)
|
|
120
|
-
except Exception as e:
|
|
121
|
-
logger.error("Unexpected error while subscribing to topic %s: %s", topic, e)
|
|
122
|
-
|
|
123
|
-
async def _subscribe_to_topics(self) -> None:
|
|
124
|
-
from griptape_nodes.retained_mode.managers.session_manager import SessionManager
|
|
125
|
-
from griptape_nodes.retained_mode.utils.engine_identity import EngineIdentity
|
|
126
|
-
|
|
127
|
-
# Subscribe to response topic (engine discovery)
|
|
128
|
-
await self._subscribe_to_topic("response")
|
|
129
|
-
|
|
130
|
-
# Get engine ID and subscribe to engine_id/response
|
|
131
|
-
engine_id = EngineIdentity.get_engine_id()
|
|
132
|
-
if engine_id:
|
|
133
|
-
await self._subscribe_to_topic(f"engines/{engine_id}/response")
|
|
134
|
-
else:
|
|
135
|
-
logger.warning("Engine ID not available for subscription")
|
|
136
|
-
|
|
137
|
-
# Get session ID and subscribe to session_id/response if available
|
|
138
|
-
session_id = SessionManager.get_saved_session_id()
|
|
139
|
-
if session_id:
|
|
140
|
-
topic = f"sessions/{session_id}/response"
|
|
141
|
-
await self._subscribe_to_topic(topic)
|
|
142
|
-
else:
|
|
143
|
-
logger.info("No session ID available for subscription")
|
|
144
|
-
|
|
145
|
-
async def connect(self) -> None:
|
|
146
|
-
"""Connect to the WebSocket server."""
|
|
147
|
-
from griptape_nodes.app.app import _create_websocket_connection
|
|
148
|
-
|
|
149
|
-
try:
|
|
150
|
-
self.connection_manager.websocket = await _create_websocket_connection(self.api_key)
|
|
151
|
-
logger.debug("🟢 WebSocket connection established: %s", self.connection_manager.websocket)
|
|
152
|
-
|
|
153
|
-
await self._subscribe_to_topics()
|
|
154
|
-
|
|
155
|
-
# Start processing messages
|
|
156
|
-
self.connection_manager._process_task = asyncio.create_task(self.connection_manager._process_messages())
|
|
157
|
-
|
|
158
|
-
except Exception as e:
|
|
159
|
-
self.connection_manager.connected = False
|
|
160
|
-
logger.error("[red]X[/red] WebSocket connection failed: %s", str(e))
|
|
161
|
-
msg = f"Failed to connect to WebSocket: {e!s}"
|
|
162
|
-
raise ConnectionError(msg) from e
|
|
163
|
-
|
|
164
|
-
async def disconnect(self) -> None:
|
|
165
|
-
"""Disconnect from the WebSocket server."""
|
|
166
|
-
if self.connection_manager.websocket:
|
|
167
|
-
await self.connection_manager.websocket.close()
|
|
168
|
-
self.connection_manager.websocket = None
|
|
169
|
-
self.connection_manager.connected = False
|
|
170
|
-
|
|
171
|
-
# Cancel processing task if it's running
|
|
172
|
-
if self.connection_manager._process_task:
|
|
173
|
-
self.connection_manager._process_task.cancel()
|
|
174
|
-
with contextlib.suppress(asyncio.CancelledError):
|
|
175
|
-
await self.connection_manager._process_task
|
|
176
|
-
self.connection_manager._process_task = None
|
|
177
|
-
|
|
178
|
-
logger.debug("WebSocket disconnected")
|
|
179
|
-
|
|
180
|
-
async def send_api_message(self, data: dict[str, Any]) -> None:
|
|
181
|
-
"""Send a message to the API via WebSocket."""
|
|
182
|
-
try:
|
|
183
|
-
await self.connection_manager.send(data)
|
|
184
|
-
except ConnectionError as e:
|
|
185
|
-
logger.error("Failed to send API message: %s", e)
|
|
186
|
-
raise
|
|
187
|
-
except Exception as e:
|
|
188
|
-
logger.error("Unexpected error sending API message: %s", e)
|
|
189
|
-
raise
|
|
190
|
-
|
|
191
|
-
async def create_event(self, request_type: str, payload: dict[str, Any]) -> None:
|
|
192
|
-
"""Send an event to the API without waiting for a response."""
|
|
193
|
-
from griptape_nodes.app.app import determine_request_topic
|
|
194
|
-
|
|
195
|
-
logger.debug("Creating Event: %s - %s", request_type, json.dumps(payload))
|
|
196
|
-
|
|
197
|
-
data = {"event_type": "EventRequest", "request_type": request_type, "request": payload}
|
|
198
|
-
topic = determine_request_topic()
|
|
199
|
-
|
|
200
|
-
request_data = {"payload": data, "type": data["event_type"], "topic": topic}
|
|
201
|
-
|
|
202
|
-
if not request_data["payload"]["request"].get("request_id"):
|
|
203
|
-
request_data["payload"]["request"]["request_id"] = ""
|
|
204
|
-
|
|
205
|
-
await self.send_api_message(request_data)
|
|
206
|
-
|
|
207
|
-
async def create_request_event(
|
|
208
|
-
self, request_type: str, payload: dict[str, Any], timeout_ms: int | None = None
|
|
209
|
-
) -> T:
|
|
210
|
-
"""Send a request and wait for its response.
|
|
211
|
-
|
|
212
|
-
Args:
|
|
213
|
-
request_type: Type of request to send
|
|
214
|
-
payload: Data to send with the request
|
|
215
|
-
timeout_ms: Optional timeout in milliseconds
|
|
216
|
-
|
|
217
|
-
Returns:
|
|
218
|
-
The response data
|
|
219
|
-
|
|
220
|
-
Raises:
|
|
221
|
-
asyncio.TimeoutError: If the request times out
|
|
222
|
-
Exception: If the request fails
|
|
223
|
-
"""
|
|
224
|
-
# Create a future that will be resolved when the response arrives
|
|
225
|
-
response_future = asyncio.Future()
|
|
226
|
-
|
|
227
|
-
# Convert timeout from milliseconds to seconds for asyncio
|
|
228
|
-
timeout_sec = timeout_ms / 1000 if timeout_ms else None
|
|
229
|
-
|
|
230
|
-
# Define handlers that will resolve/reject the future
|
|
231
|
-
def success_handler(response: Any, _: Any) -> None:
|
|
232
|
-
if not response_future.done():
|
|
233
|
-
result = response.get("payload", {}).get("result", "Success")
|
|
234
|
-
logger.debug("[green]OK[/green] Request succeeded: %s", result)
|
|
235
|
-
response_future.set_result(result)
|
|
236
|
-
|
|
237
|
-
def failure_handler(response: Any, _: Any) -> None:
|
|
238
|
-
if not response_future.done():
|
|
239
|
-
error = (
|
|
240
|
-
response.get("payload", {}).get("result", {}).get("exception", "Unknown error") or "Unknown error"
|
|
241
|
-
)
|
|
242
|
-
logger.error("[red]X[/red] Request failed: %s", error)
|
|
243
|
-
response_future.set_exception(Exception(error))
|
|
244
|
-
|
|
245
|
-
# Generate request ID and subscribe
|
|
246
|
-
request_id = self.connection_manager.subscribe_to_request_event(success_handler, failure_handler)
|
|
247
|
-
payload["request_id"] = request_id
|
|
248
|
-
|
|
249
|
-
logger.debug("Request (%s): %s %s", request_id, request_type, json.dumps(payload))
|
|
250
|
-
|
|
251
|
-
try:
|
|
252
|
-
# Send the event
|
|
253
|
-
await self.create_event(request_type, payload)
|
|
254
|
-
|
|
255
|
-
# Wait for the response with optional timeout
|
|
256
|
-
if timeout_sec:
|
|
257
|
-
return await asyncio.wait_for(response_future, timeout=timeout_sec)
|
|
258
|
-
return await response_future
|
|
259
|
-
|
|
260
|
-
except TimeoutError:
|
|
261
|
-
logger.error("Request timed out after %s ms: %s", timeout_ms, request_id)
|
|
262
|
-
self.connection_manager.unsubscribe_from_request_event(request_id)
|
|
263
|
-
raise
|
|
264
|
-
|
|
265
|
-
except Exception as e:
|
|
266
|
-
logger.error("Request failed: %s - %s", request_id, e)
|
|
267
|
-
self.connection_manager.unsubscribe_from_request_event(request_id)
|
|
268
|
-
raise
|
|
File without changes
|