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
|
@@ -6,323 +6,360 @@ Supports multiple concurrent sessions per engine with one active session managed
|
|
|
6
6
|
Storage structure: ~/.local/state/griptape_nodes/engines/{engine_id}/sessions.json
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
9
11
|
import json
|
|
10
12
|
import logging
|
|
13
|
+
import uuid
|
|
11
14
|
from datetime import UTC, datetime
|
|
12
|
-
from
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
13
16
|
|
|
17
|
+
from pydantic import BaseModel
|
|
14
18
|
from xdg_base_dirs import xdg_state_home
|
|
15
19
|
|
|
16
|
-
from griptape_nodes.retained_mode.events.
|
|
17
|
-
|
|
20
|
+
from griptape_nodes.retained_mode.events.app_events import (
|
|
21
|
+
AppEndSessionRequest,
|
|
22
|
+
AppEndSessionResultFailure,
|
|
23
|
+
AppEndSessionResultSuccess,
|
|
24
|
+
AppGetSessionRequest,
|
|
25
|
+
AppGetSessionResultSuccess,
|
|
26
|
+
AppStartSessionRequest,
|
|
27
|
+
AppStartSessionResultSuccess,
|
|
28
|
+
SessionHeartbeatRequest,
|
|
29
|
+
SessionHeartbeatResultFailure,
|
|
30
|
+
SessionHeartbeatResultSuccess,
|
|
31
|
+
)
|
|
32
|
+
from griptape_nodes.retained_mode.events.base_events import BaseEvent, ResultPayload
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
|
|
37
|
+
from griptape_nodes.retained_mode.managers.engine_identity_manager import EngineIdentityManager
|
|
38
|
+
from griptape_nodes.retained_mode.managers.event_manager import EventManager
|
|
18
39
|
|
|
19
40
|
logger = logging.getLogger("griptape_nodes")
|
|
20
41
|
|
|
21
42
|
|
|
43
|
+
class SessionData(BaseModel):
|
|
44
|
+
"""Represents a single session's data."""
|
|
45
|
+
|
|
46
|
+
session_id: str
|
|
47
|
+
engine_id: str | None = None
|
|
48
|
+
started_at: str
|
|
49
|
+
last_updated: str
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SessionsStorage(BaseModel):
|
|
53
|
+
"""Represents the sessions storage structure."""
|
|
54
|
+
|
|
55
|
+
sessions: list[SessionData]
|
|
56
|
+
|
|
57
|
+
|
|
22
58
|
class SessionManager:
|
|
23
59
|
"""Manages session saving and active session state."""
|
|
24
60
|
|
|
25
|
-
_active_session_id: str | None = None
|
|
26
|
-
|
|
27
61
|
_SESSION_STATE_FILE = "sessions.json"
|
|
28
62
|
|
|
29
|
-
def __init__(
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
engine_identity_manager: EngineIdentityManager,
|
|
66
|
+
event_manager: EventManager | None = None,
|
|
67
|
+
) -> None:
|
|
30
68
|
"""Initialize the SessionManager.
|
|
31
69
|
|
|
32
70
|
Args:
|
|
71
|
+
engine_identity_manager: The EngineIdentityManager instance to use for engine ID operations.
|
|
33
72
|
event_manager: The EventManager instance to use for event handling.
|
|
34
73
|
"""
|
|
74
|
+
self._engine_identity_manager = engine_identity_manager
|
|
75
|
+
self._sessions_data = self._load_sessions_data()
|
|
76
|
+
self._active_session_id = self._get_or_initialize_active_session()
|
|
35
77
|
BaseEvent._session_id = self._active_session_id
|
|
36
78
|
if event_manager is not None:
|
|
37
|
-
|
|
38
|
-
|
|
79
|
+
event_manager.assign_manager_to_request_type(AppStartSessionRequest, self.handle_session_start_request)
|
|
80
|
+
event_manager.assign_manager_to_request_type(AppEndSessionRequest, self.handle_session_end_request)
|
|
81
|
+
event_manager.assign_manager_to_request_type(AppGetSessionRequest, self.handle_get_session_request)
|
|
82
|
+
event_manager.assign_manager_to_request_type(SessionHeartbeatRequest, self.handle_session_heartbeat_request)
|
|
39
83
|
|
|
40
|
-
@
|
|
41
|
-
def
|
|
42
|
-
"""Get the
|
|
84
|
+
@property
|
|
85
|
+
def active_session_id(self) -> str | None:
|
|
86
|
+
"""Get the active session ID.
|
|
43
87
|
|
|
44
|
-
|
|
45
|
-
|
|
88
|
+
Returns:
|
|
89
|
+
str | None: The active session ID or None if not set
|
|
46
90
|
"""
|
|
47
|
-
|
|
48
|
-
if engine_id:
|
|
49
|
-
return base_dir / "engines" / engine_id
|
|
50
|
-
return base_dir
|
|
91
|
+
return self._active_session_id
|
|
51
92
|
|
|
52
|
-
@
|
|
53
|
-
def
|
|
54
|
-
"""
|
|
93
|
+
@active_session_id.setter
|
|
94
|
+
def active_session_id(self, session_id: str) -> None:
|
|
95
|
+
"""Set the active session ID.
|
|
55
96
|
|
|
56
97
|
Args:
|
|
57
|
-
|
|
98
|
+
session_id: The session ID to set as active
|
|
58
99
|
"""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def _load_sessions_data(cls, engine_id: str | None = None) -> dict:
|
|
63
|
-
"""Load sessions data from storage.
|
|
100
|
+
self._active_session_id = session_id
|
|
101
|
+
BaseEvent._session_id = session_id
|
|
102
|
+
logger.debug("Set active session ID to: %s", session_id)
|
|
64
103
|
|
|
65
|
-
|
|
66
|
-
|
|
104
|
+
@property
|
|
105
|
+
def all_sessions(self) -> list[SessionData]:
|
|
106
|
+
"""Get all registered sessions for the current engine.
|
|
67
107
|
|
|
68
108
|
Returns:
|
|
69
|
-
|
|
109
|
+
list[SessionData]: List of all session data for the current engine
|
|
70
110
|
"""
|
|
71
|
-
|
|
111
|
+
return self._sessions_data.sessions
|
|
72
112
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
with session_state_file.open("r") as f:
|
|
76
|
-
data = json.load(f)
|
|
77
|
-
if isinstance(data, dict) and "sessions" in data:
|
|
78
|
-
return {"sessions": data["sessions"]}
|
|
79
|
-
except (json.JSONDecodeError, OSError):
|
|
80
|
-
pass
|
|
113
|
+
def save_session(self, session_id: str) -> None:
|
|
114
|
+
"""Save a session and make it the active session.
|
|
81
115
|
|
|
82
|
-
|
|
116
|
+
Args:
|
|
117
|
+
session_id: The session ID to save
|
|
118
|
+
"""
|
|
119
|
+
engine_id = self._get_current_engine_id()
|
|
120
|
+
session_data = SessionData(
|
|
121
|
+
session_id=session_id,
|
|
122
|
+
engine_id=engine_id,
|
|
123
|
+
started_at=datetime.now(tz=UTC).isoformat(),
|
|
124
|
+
last_updated=datetime.now(tz=UTC).isoformat(),
|
|
125
|
+
)
|
|
83
126
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
127
|
+
# Add or update the session
|
|
128
|
+
self._add_or_update_session(session_data)
|
|
129
|
+
|
|
130
|
+
# Set as active session
|
|
131
|
+
self._active_session_id = session_id
|
|
132
|
+
BaseEvent._session_id = session_id
|
|
133
|
+
logger.info("Saved and activated session: %s for engine: %s", session_id, engine_id)
|
|
134
|
+
|
|
135
|
+
def remove_session(self, session_id: str) -> None:
|
|
136
|
+
"""Remove a session from the sessions data for the current engine.
|
|
87
137
|
|
|
88
138
|
Args:
|
|
89
|
-
|
|
90
|
-
engine_id: Optional engine ID to save engine-specific sessions
|
|
139
|
+
session_id: The session ID to remove
|
|
91
140
|
"""
|
|
92
|
-
|
|
93
|
-
session_state_dir.mkdir(parents=True, exist_ok=True)
|
|
141
|
+
engine_id = self._get_current_engine_id()
|
|
94
142
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
143
|
+
# Remove the session
|
|
144
|
+
self._sessions_data.sessions = [
|
|
145
|
+
session for session in self._sessions_data.sessions if session.session_id != session_id
|
|
146
|
+
]
|
|
98
147
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
148
|
+
# Clear active session if it was the removed session
|
|
149
|
+
if self._active_session_id == session_id:
|
|
150
|
+
# Set to first remaining session or None
|
|
151
|
+
self._active_session_id = (
|
|
152
|
+
self._sessions_data.sessions[0].session_id if self._sessions_data.sessions else None
|
|
153
|
+
)
|
|
154
|
+
BaseEvent._session_id = self._active_session_id
|
|
155
|
+
logger.info(
|
|
156
|
+
"Removed active session %s for engine %s, set new active session to: %s",
|
|
157
|
+
session_id,
|
|
158
|
+
engine_id,
|
|
159
|
+
self._active_session_id,
|
|
160
|
+
)
|
|
102
161
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
"""
|
|
106
|
-
# Import here to avoid circular imports
|
|
107
|
-
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
162
|
+
self._save_sessions_data(self._sessions_data, engine_id)
|
|
163
|
+
logger.info("Removed session: %s from engine: %s", session_id, engine_id)
|
|
108
164
|
|
|
109
|
-
|
|
165
|
+
def clear_saved_session(self) -> None:
|
|
166
|
+
"""Clear all saved session data for the current engine."""
|
|
167
|
+
# Clear active session
|
|
168
|
+
self._active_session_id = None
|
|
169
|
+
BaseEvent._session_id = None
|
|
110
170
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
"""Find a session by ID in the sessions data.
|
|
171
|
+
# Clear in-memory session data
|
|
172
|
+
self._sessions_data = SessionsStorage(sessions=[])
|
|
114
173
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
174
|
+
engine_id = self._get_current_engine_id()
|
|
175
|
+
session_state_file = self._get_session_state_file(engine_id)
|
|
176
|
+
if session_state_file.exists():
|
|
177
|
+
try:
|
|
178
|
+
session_state_file.unlink()
|
|
179
|
+
logger.info("Cleared all saved session data for engine: %s", engine_id)
|
|
180
|
+
except OSError:
|
|
181
|
+
# If we can't delete the file, just clear its contents
|
|
182
|
+
self._save_sessions_data(self._sessions_data, engine_id)
|
|
183
|
+
logger.warning("Could not delete session file for engine %s, cleared contents instead", engine_id)
|
|
184
|
+
|
|
185
|
+
def _get_or_initialize_active_session(self) -> str | None:
|
|
186
|
+
"""Get or initialize the active session ID.
|
|
187
|
+
|
|
188
|
+
Falls back to first available session if no active session is set.
|
|
118
189
|
|
|
119
190
|
Returns:
|
|
120
|
-
|
|
191
|
+
str | None: The active session ID or None if no sessions exist
|
|
121
192
|
"""
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
193
|
+
# Fall back to first session if available
|
|
194
|
+
if self._sessions_data.sessions:
|
|
195
|
+
first_session = self._sessions_data.sessions[0]
|
|
196
|
+
logger.debug(
|
|
197
|
+
"Initialized active session to first saved session: %s for engine: %s",
|
|
198
|
+
first_session.session_id,
|
|
199
|
+
first_session.engine_id,
|
|
200
|
+
)
|
|
201
|
+
return first_session.session_id
|
|
202
|
+
|
|
125
203
|
return None
|
|
126
204
|
|
|
127
|
-
|
|
128
|
-
def _add_or_update_session(cls, session_data: dict) -> None:
|
|
205
|
+
def _add_or_update_session(self, session_data: SessionData) -> None:
|
|
129
206
|
"""Add or update a session in the sessions data structure.
|
|
130
207
|
|
|
131
208
|
Args:
|
|
132
209
|
session_data: The session data to add or update
|
|
133
210
|
"""
|
|
134
|
-
engine_id =
|
|
135
|
-
sessions_data = cls._load_sessions_data(engine_id)
|
|
211
|
+
engine_id = self._get_current_engine_id()
|
|
136
212
|
|
|
137
213
|
# Find existing session
|
|
138
|
-
|
|
139
|
-
existing_session = cls._find_session_by_id(sessions_data, session_id)
|
|
214
|
+
existing_session = self._find_session_by_id(self._sessions_data, session_data.session_id)
|
|
140
215
|
|
|
141
216
|
if existing_session:
|
|
142
217
|
# Update existing session
|
|
143
|
-
existing_session.
|
|
144
|
-
existing_session
|
|
218
|
+
existing_session.session_id = session_data.session_id
|
|
219
|
+
existing_session.engine_id = session_data.engine_id
|
|
220
|
+
existing_session.started_at = session_data.started_at
|
|
221
|
+
existing_session.last_updated = datetime.now(tz=UTC).isoformat()
|
|
145
222
|
else:
|
|
146
223
|
# Add new session
|
|
147
|
-
session_data
|
|
148
|
-
sessions_data.setdefault("sessions", []).append(session_data)
|
|
224
|
+
self._sessions_data.sessions.append(session_data)
|
|
149
225
|
|
|
150
|
-
|
|
226
|
+
self._save_sessions_data(self._sessions_data, engine_id)
|
|
151
227
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
"""Get the active session ID.
|
|
228
|
+
def _get_current_engine_id(self) -> str | None:
|
|
229
|
+
"""Get the current engine ID from EngineIdentityManager.
|
|
155
230
|
|
|
156
231
|
Returns:
|
|
157
|
-
str | None: The
|
|
158
|
-
"""
|
|
159
|
-
return cls._active_session_id
|
|
160
|
-
|
|
161
|
-
@classmethod
|
|
162
|
-
def set_active_session_id(cls, session_id: str) -> None:
|
|
163
|
-
"""Set the active session ID.
|
|
164
|
-
|
|
165
|
-
Args:
|
|
166
|
-
session_id: The session ID to set as active
|
|
167
|
-
|
|
168
|
-
Raises:
|
|
169
|
-
ValueError: If session_id is not found in persisted sessions
|
|
170
|
-
"""
|
|
171
|
-
engine_id = cls._get_current_engine_id()
|
|
172
|
-
sessions_data = cls._load_sessions_data(engine_id)
|
|
173
|
-
|
|
174
|
-
# Verify the session exists
|
|
175
|
-
if cls._find_session_by_id(sessions_data, session_id):
|
|
176
|
-
cls._active_session_id = session_id
|
|
177
|
-
logger.debug("Set active session ID to: %s", session_id)
|
|
178
|
-
else:
|
|
179
|
-
msg = f"Session with ID {session_id} not found for engine {engine_id}"
|
|
180
|
-
raise ValueError(msg)
|
|
181
|
-
|
|
182
|
-
@classmethod
|
|
183
|
-
def save_session(cls, session_id: str) -> None:
|
|
184
|
-
"""Save a session and make it the active session.
|
|
185
|
-
|
|
186
|
-
Args:
|
|
187
|
-
session_id: The session ID to save
|
|
232
|
+
str | None: The current engine ID or None if not set
|
|
188
233
|
"""
|
|
189
|
-
|
|
190
|
-
session_data = {
|
|
191
|
-
"session_id": session_id,
|
|
192
|
-
"engine_id": engine_id,
|
|
193
|
-
"started_at": datetime.now(tz=UTC).isoformat(),
|
|
194
|
-
"last_updated": datetime.now(tz=UTC).isoformat(),
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
# Add or update the session
|
|
198
|
-
cls._add_or_update_session(session_data)
|
|
199
|
-
|
|
200
|
-
# Set as active session
|
|
201
|
-
cls._active_session_id = session_id
|
|
202
|
-
BaseEvent._session_id = session_id
|
|
203
|
-
logger.info("Saved and activated session: %s for engine: %s", session_id, engine_id)
|
|
234
|
+
return self._engine_identity_manager.active_engine_id
|
|
204
235
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
"""Get the active session ID if it exists.
|
|
236
|
+
def _load_sessions_data(self) -> SessionsStorage:
|
|
237
|
+
"""Load sessions data from storage.
|
|
208
238
|
|
|
209
239
|
Returns:
|
|
210
|
-
|
|
240
|
+
SessionsStorage: Sessions data structure with sessions array
|
|
211
241
|
"""
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
return cls._active_session_id
|
|
215
|
-
|
|
216
|
-
# Fall back to first session if available
|
|
217
|
-
engine_id = cls._get_current_engine_id()
|
|
218
|
-
sessions_data = cls._load_sessions_data(engine_id)
|
|
219
|
-
sessions = sessions_data.get("sessions", [])
|
|
220
|
-
if sessions:
|
|
221
|
-
first_session_id = sessions[0].get("session_id")
|
|
222
|
-
# Set as active for future calls
|
|
223
|
-
BaseEvent._session_id = first_session_id
|
|
224
|
-
cls._active_session_id = first_session_id
|
|
225
|
-
logger.debug("Retrieved first saved session as active: %s for engine: %s", first_session_id, engine_id)
|
|
226
|
-
return first_session_id
|
|
227
|
-
|
|
228
|
-
return None
|
|
242
|
+
engine_id = self._get_current_engine_id()
|
|
243
|
+
session_state_file = self._get_session_state_file(engine_id)
|
|
229
244
|
|
|
230
|
-
@classmethod
|
|
231
|
-
def clear_saved_session(cls) -> None:
|
|
232
|
-
"""Clear all saved session data for the current engine."""
|
|
233
|
-
# Clear active session
|
|
234
|
-
cls._active_session_id = None
|
|
235
|
-
BaseEvent._session_id = None
|
|
236
|
-
|
|
237
|
-
engine_id = cls._get_current_engine_id()
|
|
238
|
-
session_state_file = cls._get_session_state_file(engine_id)
|
|
239
245
|
if session_state_file.exists():
|
|
240
246
|
try:
|
|
241
|
-
session_state_file.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
@classmethod
|
|
249
|
-
def has_saved_session(cls) -> bool:
|
|
250
|
-
"""Check if there is a saved session.
|
|
251
|
-
|
|
252
|
-
Returns:
|
|
253
|
-
bool: True if there is a saved session, False otherwise
|
|
254
|
-
"""
|
|
255
|
-
return cls.get_saved_session_id() is not None
|
|
256
|
-
|
|
257
|
-
@classmethod
|
|
258
|
-
def get_all_sessions(cls) -> list[dict]:
|
|
259
|
-
"""Get all registered sessions for the current engine.
|
|
247
|
+
with session_state_file.open("r") as f:
|
|
248
|
+
data = json.load(f)
|
|
249
|
+
if isinstance(data, dict) and "sessions" in data:
|
|
250
|
+
return SessionsStorage.model_validate(data)
|
|
251
|
+
except (json.JSONDecodeError, OSError):
|
|
252
|
+
pass
|
|
260
253
|
|
|
261
|
-
|
|
262
|
-
list[dict]: List of all session data for the current engine
|
|
263
|
-
"""
|
|
264
|
-
engine_id = cls._get_current_engine_id()
|
|
265
|
-
sessions_data = cls._load_sessions_data(engine_id)
|
|
266
|
-
return sessions_data.get("sessions", [])
|
|
254
|
+
return SessionsStorage(sessions=[])
|
|
267
255
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
"""Remove a session from the sessions data for the current engine.
|
|
256
|
+
def _save_sessions_data(self, sessions_data: SessionsStorage, engine_id: str | None = None) -> None:
|
|
257
|
+
"""Save sessions data to storage.
|
|
271
258
|
|
|
272
259
|
Args:
|
|
273
|
-
|
|
260
|
+
sessions_data: Sessions data structure to save
|
|
261
|
+
engine_id: Optional engine ID to save engine-specific sessions
|
|
274
262
|
"""
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
# Remove the session
|
|
279
|
-
sessions_data["sessions"] = [
|
|
280
|
-
session for session in sessions_data.get("sessions", []) if session.get("session_id") != session_id
|
|
281
|
-
]
|
|
263
|
+
session_state_dir = self._get_session_state_dir(engine_id)
|
|
264
|
+
session_state_dir.mkdir(parents=True, exist_ok=True)
|
|
282
265
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
266
|
+
session_state_file = self._get_session_state_file(engine_id)
|
|
267
|
+
with session_state_file.open("w") as f:
|
|
268
|
+
json.dump(sessions_data.model_dump(exclude_none=True), f, indent=2)
|
|
269
|
+
|
|
270
|
+
# Update in-memory copy
|
|
271
|
+
self._sessions_data = sessions_data
|
|
272
|
+
|
|
273
|
+
async def handle_session_start_request(self, request: AppStartSessionRequest) -> ResultPayload: # noqa: ARG002
|
|
274
|
+
current_session_id = self.active_session_id
|
|
275
|
+
if current_session_id is None:
|
|
276
|
+
# Client wants a new session
|
|
277
|
+
current_session_id = uuid.uuid4().hex
|
|
278
|
+
self.save_session(current_session_id)
|
|
279
|
+
details = f"New session '{current_session_id}' started at {datetime.now(tz=UTC)}."
|
|
280
|
+
logger.info(details)
|
|
281
|
+
else:
|
|
282
|
+
details = f"Session '{current_session_id}' already active. Joining..."
|
|
283
|
+
|
|
284
|
+
return AppStartSessionResultSuccess(current_session_id, result_details="Session started successfully.")
|
|
285
|
+
|
|
286
|
+
async def handle_session_end_request(self, _: AppEndSessionRequest) -> ResultPayload:
|
|
287
|
+
try:
|
|
288
|
+
previous_session_id = self.active_session_id
|
|
289
|
+
if previous_session_id is None:
|
|
290
|
+
details = "No active session to end."
|
|
291
|
+
logger.info(details)
|
|
292
|
+
else:
|
|
293
|
+
details = f"Session '{previous_session_id}' ended at {datetime.now(tz=UTC)}."
|
|
294
|
+
logger.info(details)
|
|
295
|
+
self.clear_saved_session()
|
|
296
|
+
|
|
297
|
+
return AppEndSessionResultSuccess(
|
|
298
|
+
session_id=previous_session_id, result_details="Session ended successfully."
|
|
293
299
|
)
|
|
300
|
+
except Exception as err:
|
|
301
|
+
details = f"Failed to end session due to '{err}'."
|
|
302
|
+
logger.error(details)
|
|
303
|
+
return AppEndSessionResultFailure(result_details=details)
|
|
294
304
|
|
|
295
|
-
|
|
296
|
-
|
|
305
|
+
def handle_get_session_request(self, _: AppGetSessionRequest) -> ResultPayload:
|
|
306
|
+
return AppGetSessionResultSuccess(
|
|
307
|
+
session_id=self.active_session_id,
|
|
308
|
+
result_details="Session ID retrieved successfully.",
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
def handle_session_heartbeat_request(self, request: SessionHeartbeatRequest) -> ResultPayload: # noqa: ARG002
|
|
312
|
+
"""Handle session heartbeat requests.
|
|
297
313
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
314
|
+
Simply verifies that the session is active and responds with success.
|
|
315
|
+
"""
|
|
316
|
+
try:
|
|
317
|
+
active_session_id = self.active_session_id
|
|
318
|
+
if active_session_id is None:
|
|
319
|
+
details = "Session heartbeat received but no active session found"
|
|
320
|
+
logger.warning(details)
|
|
321
|
+
return SessionHeartbeatResultFailure(result_details=details)
|
|
322
|
+
|
|
323
|
+
details = f"Session heartbeat successful for session: {active_session_id}"
|
|
324
|
+
return SessionHeartbeatResultSuccess(result_details=details)
|
|
325
|
+
except Exception as err:
|
|
326
|
+
details = f"Failed to handle session heartbeat: {err}"
|
|
327
|
+
logger.error(details)
|
|
328
|
+
return SessionHeartbeatResultFailure(result_details=details)
|
|
329
|
+
|
|
330
|
+
@staticmethod
|
|
331
|
+
def _find_session_by_id(sessions_data: SessionsStorage, session_id: str) -> SessionData | None:
|
|
332
|
+
"""Find a session by ID in the sessions data.
|
|
301
333
|
|
|
302
334
|
Args:
|
|
303
|
-
|
|
335
|
+
sessions_data: The sessions data structure
|
|
336
|
+
session_id: The session ID to find
|
|
304
337
|
|
|
305
338
|
Returns:
|
|
306
|
-
|
|
339
|
+
SessionData | None: The session data if found, None otherwise
|
|
307
340
|
"""
|
|
308
|
-
|
|
309
|
-
|
|
341
|
+
for session in sessions_data.sessions:
|
|
342
|
+
if session.session_id == session_id:
|
|
343
|
+
return session
|
|
344
|
+
return None
|
|
310
345
|
|
|
311
|
-
@
|
|
312
|
-
def
|
|
313
|
-
"""Get
|
|
346
|
+
@staticmethod
|
|
347
|
+
def _get_session_state_file(engine_id: str | None = None) -> Path:
|
|
348
|
+
"""Get the path to the session state storage file.
|
|
314
349
|
|
|
315
|
-
|
|
316
|
-
|
|
350
|
+
Args:
|
|
351
|
+
engine_id: Optional engine ID to get engine-specific session file
|
|
317
352
|
"""
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
all_engines = EngineIdentity.get_all_engines()
|
|
321
|
-
result = {}
|
|
353
|
+
return SessionManager._get_session_state_dir(engine_id) / SessionManager._SESSION_STATE_FILE
|
|
322
354
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
result[engine_id] = cls.get_sessions_for_engine(engine_id)
|
|
355
|
+
@staticmethod
|
|
356
|
+
def _get_session_state_dir(engine_id: str | None = None) -> Path:
|
|
357
|
+
"""Get the XDG state directory for session storage.
|
|
327
358
|
|
|
328
|
-
|
|
359
|
+
Args:
|
|
360
|
+
engine_id: Optional engine ID to create engine-specific directory
|
|
361
|
+
"""
|
|
362
|
+
base_dir = xdg_state_home() / "griptape_nodes"
|
|
363
|
+
if engine_id:
|
|
364
|
+
return base_dir / "engines" / engine_id
|
|
365
|
+
return base_dir
|
|
@@ -94,6 +94,10 @@ class MCPServerConfig(BaseModel):
|
|
|
94
94
|
class AppInitializationComplete(BaseModel):
|
|
95
95
|
libraries_to_register: list[str] = Field(default_factory=list)
|
|
96
96
|
workflows_to_register: list[str] = Field(default_factory=list)
|
|
97
|
+
secrets_to_register: list[str] = Field(
|
|
98
|
+
default_factory=lambda: ["HF_TOKEN", "GT_CLOUD_API_KEY"],
|
|
99
|
+
description="Core secrets to register in the secrets manager. Library-specific secrets are registered automatically from library settings.",
|
|
100
|
+
)
|
|
97
101
|
models_to_download: list[str] = Field(default_factory=list)
|
|
98
102
|
|
|
99
103
|
|
|
@@ -147,44 +151,6 @@ class Settings(BaseModel):
|
|
|
147
151
|
category=APPLICATION_EVENTS,
|
|
148
152
|
default_factory=AppEvents,
|
|
149
153
|
)
|
|
150
|
-
nodes: dict[str, Any] = Field(
|
|
151
|
-
category=API_KEYS,
|
|
152
|
-
default_factory=lambda: {
|
|
153
|
-
"Griptape": {"GT_CLOUD_API_KEY": "$GT_CLOUD_API_KEY"},
|
|
154
|
-
"OpenAI": {"OPENAI_API_KEY": "$OPENAI_API_KEY"},
|
|
155
|
-
"Amazon": {
|
|
156
|
-
"AWS_ACCESS_KEY_ID": "$AWS_ACCESS_KEY_ID",
|
|
157
|
-
"AWS_SECRET_ACCESS_KEY": "$AWS_SECRET_ACCESS_KEY",
|
|
158
|
-
"AWS_DEFAULT_REGION": "$AWS_DEFAULT_REGION",
|
|
159
|
-
"AMAZON_OPENSEARCH_HOST": "$AMAZON_OPENSEARCH_HOST",
|
|
160
|
-
"AMAZON_OPENSEARCH_INDEX_NAME": "$AMAZON_OPENSEARCH_INDEX_NAME",
|
|
161
|
-
},
|
|
162
|
-
"Anthropic": {"ANTHROPIC_API_KEY": "$ANTHROPIC_API_KEY"},
|
|
163
|
-
"BlackForest Labs": {"BFL_API_KEY": "$BFL_API_KEY"},
|
|
164
|
-
"Microsoft Azure": {
|
|
165
|
-
"AZURE_OPENAI_ENDPOINT": "$AZURE_OPENAI_ENDPOINT",
|
|
166
|
-
"AZURE_OPENAI_DALL_E_3_ENDPOINT": "$AZURE_OPENAI_DALL_E_3_ENDPOINT",
|
|
167
|
-
"AZURE_OPENAI_DALL_E_3_API_KEY": "$AZURE_OPENAI_DALL_E_3_API_KEY",
|
|
168
|
-
"AZURE_OPENAI_API_KEY": "$AZURE_OPENAI_API_KEY",
|
|
169
|
-
},
|
|
170
|
-
"Cohere": {"COHERE_API_KEY": "$COHERE_API_KEY"},
|
|
171
|
-
"Eleven Labs": {"ELEVEN_LABS_API_KEY": "$ELEVEN_LABS_API_KEY"},
|
|
172
|
-
"Exa": {"EXA_API_KEY": "$EXA_API_KEY"},
|
|
173
|
-
"Grok": {"GROK_API_KEY": "$GROK_API_KEY"},
|
|
174
|
-
"Groq": {"GROQ_API_KEY": "$GROQ_API_KEY"},
|
|
175
|
-
"Nvidia": {"NVIDIA_API_KEY": "$NVIDIA_API_KEY"},
|
|
176
|
-
"Google": {"GOOGLE_API_KEY": "$GOOGLE_API_KEY", "GOOGLE_API_SEARCH_ID": "$GOOGLE_API_SEARCH_ID"},
|
|
177
|
-
"Huggingface": {"HUGGINGFACE_HUB_ACCESS_TOKEN": "$HUGGINGFACE_HUB_ACCESS_TOKEN"},
|
|
178
|
-
"LeonardoAI": {"LEONARDO_API_KEY": "$LEONARDO_API_KEY"},
|
|
179
|
-
"Pinecone": {
|
|
180
|
-
"PINECONE_API_KEY": "$PINECONE_API_KEY",
|
|
181
|
-
"PINECONE_ENVIRONMENT": "$PINECONE_ENVIRONMENT",
|
|
182
|
-
"PINECONE_INDEX_NAME": "$PINECONE_INDEX_NAME",
|
|
183
|
-
},
|
|
184
|
-
"Tavily": {"TAVILY_API_KEY": "$TAVILY_API_KEY"},
|
|
185
|
-
"Serper": {"SERPER_API_KEY": "$SERPER_API_KEY"},
|
|
186
|
-
},
|
|
187
|
-
)
|
|
188
154
|
log_level: LogLevel = Field(category=EXECUTION, default=LogLevel.INFO)
|
|
189
155
|
workflow_execution_mode: WorkflowExecutionMode = Field(
|
|
190
156
|
category=EXECUTION,
|