griptape-nodes 0.52.1__py3-none-any.whl → 0.54.0__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 +8 -942
- griptape_nodes/__main__.py +6 -0
- griptape_nodes/app/app.py +48 -86
- griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +35 -5
- griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +15 -1
- griptape_nodes/cli/__init__.py +1 -0
- griptape_nodes/cli/commands/__init__.py +1 -0
- griptape_nodes/cli/commands/config.py +74 -0
- griptape_nodes/cli/commands/engine.py +80 -0
- griptape_nodes/cli/commands/init.py +550 -0
- griptape_nodes/cli/commands/libraries.py +96 -0
- griptape_nodes/cli/commands/models.py +504 -0
- griptape_nodes/cli/commands/self.py +120 -0
- griptape_nodes/cli/main.py +56 -0
- griptape_nodes/cli/shared.py +75 -0
- griptape_nodes/common/__init__.py +1 -0
- griptape_nodes/common/directed_graph.py +71 -0
- griptape_nodes/drivers/storage/base_storage_driver.py +40 -20
- griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +24 -29
- griptape_nodes/drivers/storage/local_storage_driver.py +23 -14
- griptape_nodes/exe_types/core_types.py +60 -2
- griptape_nodes/exe_types/node_types.py +257 -38
- griptape_nodes/exe_types/param_components/__init__.py +1 -0
- griptape_nodes/exe_types/param_components/execution_status_component.py +138 -0
- griptape_nodes/machines/control_flow.py +195 -94
- griptape_nodes/machines/dag_builder.py +207 -0
- griptape_nodes/machines/fsm.py +10 -1
- griptape_nodes/machines/parallel_resolution.py +558 -0
- griptape_nodes/machines/{node_resolution.py → sequential_resolution.py} +30 -57
- griptape_nodes/node_library/library_registry.py +34 -1
- griptape_nodes/retained_mode/events/app_events.py +5 -1
- griptape_nodes/retained_mode/events/base_events.py +9 -9
- griptape_nodes/retained_mode/events/config_events.py +30 -0
- griptape_nodes/retained_mode/events/execution_events.py +2 -2
- griptape_nodes/retained_mode/events/model_events.py +296 -0
- griptape_nodes/retained_mode/events/node_events.py +4 -3
- griptape_nodes/retained_mode/griptape_nodes.py +34 -12
- griptape_nodes/retained_mode/managers/agent_manager.py +23 -5
- griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +3 -1
- griptape_nodes/retained_mode/managers/config_manager.py +44 -3
- griptape_nodes/retained_mode/managers/context_manager.py +6 -5
- griptape_nodes/retained_mode/managers/event_manager.py +8 -2
- griptape_nodes/retained_mode/managers/flow_manager.py +150 -206
- griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +1 -1
- griptape_nodes/retained_mode/managers/library_manager.py +35 -25
- griptape_nodes/retained_mode/managers/model_manager.py +1107 -0
- griptape_nodes/retained_mode/managers/node_manager.py +102 -220
- griptape_nodes/retained_mode/managers/object_manager.py +11 -5
- griptape_nodes/retained_mode/managers/os_manager.py +28 -13
- griptape_nodes/retained_mode/managers/secrets_manager.py +8 -4
- griptape_nodes/retained_mode/managers/settings.py +116 -7
- griptape_nodes/retained_mode/managers/static_files_manager.py +85 -12
- griptape_nodes/retained_mode/managers/sync_manager.py +17 -9
- griptape_nodes/retained_mode/managers/workflow_manager.py +186 -192
- griptape_nodes/retained_mode/retained_mode.py +19 -0
- griptape_nodes/servers/__init__.py +1 -0
- griptape_nodes/{mcp_server/server.py → servers/mcp.py} +1 -1
- griptape_nodes/{app/api.py → servers/static.py} +43 -40
- griptape_nodes/traits/add_param_button.py +1 -1
- griptape_nodes/traits/button.py +334 -6
- griptape_nodes/traits/color_picker.py +66 -0
- griptape_nodes/traits/multi_options.py +188 -0
- griptape_nodes/traits/numbers_selector.py +77 -0
- griptape_nodes/traits/options.py +93 -2
- griptape_nodes/traits/traits.json +4 -0
- griptape_nodes/utils/async_utils.py +31 -0
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/METADATA +4 -1
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/RECORD +71 -48
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/WHEEL +1 -1
- /griptape_nodes/{mcp_server → servers}/ws_request_manager.py +0 -0
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/entry_points.txt +0 -0
griptape_nodes/app/app.py
CHANGED
|
@@ -7,7 +7,6 @@ import os
|
|
|
7
7
|
import sys
|
|
8
8
|
import threading
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
-
from pathlib import Path
|
|
11
10
|
from typing import Any
|
|
12
11
|
from urllib.parse import urljoin
|
|
13
12
|
|
|
@@ -16,10 +15,8 @@ from rich.console import Console
|
|
|
16
15
|
from rich.logging import RichHandler
|
|
17
16
|
from rich.panel import Panel
|
|
18
17
|
from websockets.asyncio.client import connect
|
|
19
|
-
from websockets.exceptions import ConnectionClosed, WebSocketException
|
|
18
|
+
from websockets.exceptions import ConnectionClosed, ConnectionClosedError, WebSocketException
|
|
20
19
|
|
|
21
|
-
from griptape_nodes.app.api import start_static_server
|
|
22
|
-
from griptape_nodes.mcp_server.server import start_mcp_server
|
|
23
20
|
from griptape_nodes.retained_mode.events import app_events, execution_events
|
|
24
21
|
|
|
25
22
|
# This import is necessary to register all events, even if not technically used
|
|
@@ -63,6 +60,11 @@ class UnsubscribeCommand:
|
|
|
63
60
|
topic: str
|
|
64
61
|
|
|
65
62
|
|
|
63
|
+
# Important to bootstrap singleton here so that we don't
|
|
64
|
+
# get any weird circular import issues from the EventLogHandler
|
|
65
|
+
# initializing it from a log during it's own initialization.
|
|
66
|
+
griptape_nodes: GriptapeNodes = GriptapeNodes()
|
|
67
|
+
|
|
66
68
|
# WebSocket outgoing queue for messages and commands.
|
|
67
69
|
# Appears to be fine to create outside event loop
|
|
68
70
|
# https://discuss.python.org/t/can-asyncio-queue-be-safely-created-outside-of-the-event-loop-thread/49215/8
|
|
@@ -75,19 +77,10 @@ websocket_event_loop: asyncio.AbstractEventLoop | None = None
|
|
|
75
77
|
websocket_event_loop_ready = threading.Event()
|
|
76
78
|
|
|
77
79
|
|
|
78
|
-
# Whether to enable the static server
|
|
79
|
-
STATIC_SERVER_ENABLED = os.getenv("STATIC_SERVER_ENABLED", "true").lower() == "true"
|
|
80
|
-
|
|
81
80
|
# Semaphore to limit concurrent requests
|
|
82
81
|
REQUEST_SEMAPHORE = asyncio.Semaphore(100)
|
|
83
82
|
|
|
84
83
|
|
|
85
|
-
# Important to bootstrap singleton here so that we don't
|
|
86
|
-
# get any weird circular import issues from the EventLogHandler
|
|
87
|
-
# initializing it from a log during it's own initialization.
|
|
88
|
-
griptape_nodes: GriptapeNodes = GriptapeNodes()
|
|
89
|
-
|
|
90
|
-
|
|
91
84
|
class EventLogHandler(logging.Handler):
|
|
92
85
|
"""Custom logging handler that emits log messages as AppEvents.
|
|
93
86
|
|
|
@@ -105,10 +98,12 @@ class EventLogHandler(logging.Handler):
|
|
|
105
98
|
logger = logging.getLogger("griptape_nodes_app")
|
|
106
99
|
|
|
107
100
|
griptape_nodes_logger = logging.getLogger("griptape_nodes")
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
101
|
+
logging.basicConfig(
|
|
102
|
+
level=logging.INFO,
|
|
103
|
+
format="%(message)s",
|
|
104
|
+
datefmt="[%X]",
|
|
105
|
+
handlers=[EventLogHandler(), RichHandler(show_time=True, show_path=False, markup=True, rich_tracebacks=True)],
|
|
106
|
+
)
|
|
112
107
|
|
|
113
108
|
console = Console()
|
|
114
109
|
|
|
@@ -134,14 +129,6 @@ async def astart_app() -> None:
|
|
|
134
129
|
main_loop = asyncio.get_running_loop()
|
|
135
130
|
|
|
136
131
|
try:
|
|
137
|
-
# Start MCP server in daemon thread
|
|
138
|
-
threading.Thread(target=start_mcp_server, args=(api_key,), daemon=True, name="mcp-server").start()
|
|
139
|
-
|
|
140
|
-
# Start static server in daemon thread if enabled
|
|
141
|
-
if STATIC_SERVER_ENABLED:
|
|
142
|
-
static_dir = _build_static_dir()
|
|
143
|
-
threading.Thread(target=start_static_server, args=(static_dir,), daemon=True, name="static-server").start()
|
|
144
|
-
|
|
145
132
|
# Start WebSocket tasks in daemon thread
|
|
146
133
|
threading.Thread(
|
|
147
134
|
target=_start_websocket_connection, args=(api_key, main_loop), daemon=True, name="websocket-tasks"
|
|
@@ -186,12 +173,11 @@ async def _run_websocket_tasks(api_key: str, main_loop: asyncio.AbstractEventLoo
|
|
|
186
173
|
initialized = False
|
|
187
174
|
|
|
188
175
|
async for ws_connection in connection_stream:
|
|
176
|
+
logger.debug("WebSocket connection established")
|
|
189
177
|
try:
|
|
190
178
|
# Emit initialization event only for the first connection
|
|
191
179
|
if not initialized:
|
|
192
|
-
griptape_nodes.EventManager().
|
|
193
|
-
main_loop, AppEvent(payload=app_events.AppInitializationComplete())
|
|
194
|
-
)
|
|
180
|
+
griptape_nodes.EventManager().put_event(AppEvent(payload=app_events.AppInitializationComplete()))
|
|
195
181
|
initialized = True
|
|
196
182
|
|
|
197
183
|
# Emit connection established event for every connection
|
|
@@ -202,9 +188,13 @@ async def _run_websocket_tasks(api_key: str, main_loop: asyncio.AbstractEventLoo
|
|
|
202
188
|
async with asyncio.TaskGroup() as tg:
|
|
203
189
|
tg.create_task(_process_incoming_messages(ws_connection, main_loop))
|
|
204
190
|
tg.create_task(_send_outgoing_messages(ws_connection))
|
|
205
|
-
except
|
|
206
|
-
logger.
|
|
191
|
+
except (ExceptionGroup, ConnectionClosed, ConnectionClosedError):
|
|
192
|
+
logger.info("WebSocket connection closed, reconnecting...")
|
|
193
|
+
continue
|
|
194
|
+
except Exception:
|
|
195
|
+
logger.exception("WebSocket tasks failed")
|
|
207
196
|
await asyncio.sleep(2.0) # Wait before retry
|
|
197
|
+
continue
|
|
208
198
|
|
|
209
199
|
|
|
210
200
|
def _ensure_api_key() -> str:
|
|
@@ -227,36 +217,16 @@ def _ensure_api_key() -> str:
|
|
|
227
217
|
return api_key
|
|
228
218
|
|
|
229
219
|
|
|
230
|
-
def _build_static_dir() -> Path:
|
|
231
|
-
"""Build the static directory path based on the workspace configuration."""
|
|
232
|
-
config_manager = griptape_nodes.ConfigManager()
|
|
233
|
-
return Path(config_manager.workspace_path) / config_manager.merged_config["static_files_directory"]
|
|
234
|
-
|
|
235
|
-
|
|
236
220
|
async def _process_incoming_messages(ws_connection: Any, main_loop: asyncio.AbstractEventLoop) -> None:
|
|
237
221
|
"""Process incoming WebSocket requests from Nodes API."""
|
|
238
|
-
logger.
|
|
222
|
+
logger.debug("Processing incoming WebSocket requests from WebSocket connection")
|
|
239
223
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
logger.exception("Error processing event, skipping.")
|
|
247
|
-
|
|
248
|
-
except ConnectionClosed:
|
|
249
|
-
logger.info("WebSocket connection closed, will retry")
|
|
250
|
-
except asyncio.CancelledError:
|
|
251
|
-
# Clean shutdown when task is cancelled
|
|
252
|
-
logger.info("WebSocket listener shutdown complete")
|
|
253
|
-
raise
|
|
254
|
-
except Exception as e:
|
|
255
|
-
logger.error("Error in WebSocket connection. Retrying in 2 seconds... %s", e)
|
|
256
|
-
await asyncio.sleep(2.0)
|
|
257
|
-
raise
|
|
258
|
-
finally:
|
|
259
|
-
logger.info("WebSocket listener shutdown complete")
|
|
224
|
+
async for message in ws_connection:
|
|
225
|
+
try:
|
|
226
|
+
data = json.loads(message)
|
|
227
|
+
await _process_api_event(data, main_loop)
|
|
228
|
+
except Exception:
|
|
229
|
+
logger.exception("Error processing event, skipping.")
|
|
260
230
|
|
|
261
231
|
|
|
262
232
|
def _create_websocket_connection(api_key: str) -> Any:
|
|
@@ -313,33 +283,25 @@ async def _process_api_event(event: dict, main_loop: asyncio.AbstractEventLoop)
|
|
|
313
283
|
|
|
314
284
|
async def _send_outgoing_messages(ws_connection: Any) -> None:
|
|
315
285
|
"""Send outgoing WebSocket requests from queue on background thread."""
|
|
316
|
-
logger.
|
|
286
|
+
logger.debug("Starting outgoing WebSocket request sender")
|
|
317
287
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
message = await ws_outgoing_queue.get()
|
|
322
|
-
|
|
323
|
-
try:
|
|
324
|
-
if isinstance(message, WebSocketMessage):
|
|
325
|
-
await _send_websocket_message(ws_connection, message.event_type, message.payload, message.topic)
|
|
326
|
-
elif isinstance(message, SubscribeCommand):
|
|
327
|
-
await _send_subscribe_command(ws_connection, message.topic)
|
|
328
|
-
elif isinstance(message, UnsubscribeCommand):
|
|
329
|
-
await _send_unsubscribe_command(ws_connection, message.topic)
|
|
330
|
-
else:
|
|
331
|
-
logger.warning("Unknown outgoing message type: %s", type(message))
|
|
332
|
-
except Exception as e:
|
|
333
|
-
logger.error("Error sending outgoing WebSocket request: %s", e)
|
|
334
|
-
finally:
|
|
335
|
-
ws_outgoing_queue.task_done()
|
|
288
|
+
while True:
|
|
289
|
+
# Get message from outgoing queue
|
|
290
|
+
message = await ws_outgoing_queue.get()
|
|
336
291
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
292
|
+
try:
|
|
293
|
+
if isinstance(message, WebSocketMessage):
|
|
294
|
+
await _send_websocket_message(ws_connection, message.event_type, message.payload, message.topic)
|
|
295
|
+
elif isinstance(message, SubscribeCommand):
|
|
296
|
+
await _send_subscribe_command(ws_connection, message.topic)
|
|
297
|
+
elif isinstance(message, UnsubscribeCommand):
|
|
298
|
+
await _send_unsubscribe_command(ws_connection, message.topic)
|
|
299
|
+
else:
|
|
300
|
+
logger.warning("Unknown outgoing message type: %s", type(message))
|
|
301
|
+
except Exception as e:
|
|
302
|
+
logger.error("Error sending outgoing WebSocket request: %s", e)
|
|
303
|
+
finally:
|
|
304
|
+
ws_outgoing_queue.task_done()
|
|
343
305
|
|
|
344
306
|
|
|
345
307
|
async def _send_websocket_message(ws_connection: Any, event_type: str, payload: str, topic: str | None) -> None:
|
|
@@ -361,7 +323,7 @@ async def _send_subscribe_command(ws_connection: Any, topic: str) -> None:
|
|
|
361
323
|
try:
|
|
362
324
|
body = {"type": "subscribe", "topic": topic, "payload": {}}
|
|
363
325
|
await ws_connection.send(json.dumps(body))
|
|
364
|
-
logger.
|
|
326
|
+
logger.debug("Subscribed to topic: %s", topic)
|
|
365
327
|
except WebSocketException as e:
|
|
366
328
|
logger.error("Error subscribing to topic %s: %s", topic, e)
|
|
367
329
|
except Exception as e:
|
|
@@ -373,7 +335,7 @@ async def _send_unsubscribe_command(ws_connection: Any, topic: str) -> None:
|
|
|
373
335
|
try:
|
|
374
336
|
body = {"type": "unsubscribe", "topic": topic, "payload": {}}
|
|
375
337
|
await ws_connection.send(json.dumps(body))
|
|
376
|
-
logger.
|
|
338
|
+
logger.debug("Unsubscribed from topic: %s", topic)
|
|
377
339
|
except WebSocketException as e:
|
|
378
340
|
logger.error("Error unsubscribing from topic %s: %s", topic, e)
|
|
379
341
|
except Exception as e:
|
|
@@ -382,7 +344,7 @@ async def _send_unsubscribe_command(ws_connection: Any, topic: str) -> None:
|
|
|
382
344
|
|
|
383
345
|
async def _process_event_queue() -> None:
|
|
384
346
|
"""Process events concurrently - runs on main thread."""
|
|
385
|
-
logger.
|
|
347
|
+
logger.debug("Starting event queue processor on main thread")
|
|
386
348
|
background_tasks = set()
|
|
387
349
|
|
|
388
350
|
def _handle_task_result(task: asyncio.Task) -> None:
|
|
@@ -415,7 +377,7 @@ async def _process_event_queue() -> None:
|
|
|
415
377
|
task.add_done_callback(_handle_task_result)
|
|
416
378
|
event_queue.task_done()
|
|
417
379
|
except asyncio.CancelledError:
|
|
418
|
-
logger.
|
|
380
|
+
logger.debug("Event queue processor shutdown complete")
|
|
419
381
|
raise
|
|
420
382
|
|
|
421
383
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import logging
|
|
2
|
-
from typing import Any
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
3
5
|
|
|
4
6
|
from griptape_nodes.bootstrap.workflow_executors.workflow_executor import WorkflowExecutor
|
|
5
7
|
from griptape_nodes.drivers.storage import StorageBackend
|
|
6
8
|
from griptape_nodes.exe_types.node_types import EndNode, StartNode
|
|
9
|
+
from griptape_nodes.retained_mode.events.app_events import AppInitializationComplete
|
|
7
10
|
from griptape_nodes.retained_mode.events.base_events import (
|
|
8
11
|
EventRequest,
|
|
9
12
|
ExecutionGriptapeNodeEvent,
|
|
@@ -15,6 +18,9 @@ from griptape_nodes.retained_mode.events.workflow_events import (
|
|
|
15
18
|
)
|
|
16
19
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
17
20
|
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from types import TracebackType
|
|
23
|
+
|
|
18
24
|
logger = logging.getLogger(__name__)
|
|
19
25
|
|
|
20
26
|
|
|
@@ -23,6 +29,29 @@ class LocalExecutorError(Exception):
|
|
|
23
29
|
|
|
24
30
|
|
|
25
31
|
class LocalWorkflowExecutor(WorkflowExecutor):
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
storage_backend: StorageBackend = StorageBackend.LOCAL,
|
|
35
|
+
):
|
|
36
|
+
super().__init__()
|
|
37
|
+
self._set_storage_backend(storage_backend=storage_backend)
|
|
38
|
+
|
|
39
|
+
async def __aenter__(self) -> Self:
|
|
40
|
+
"""Async context manager entry: initialize queue and broadcast app initialization."""
|
|
41
|
+
GriptapeNodes.EventManager().initialize_queue()
|
|
42
|
+
await GriptapeNodes.EventManager().broadcast_app_event(AppInitializationComplete())
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
async def __aexit__(
|
|
46
|
+
self,
|
|
47
|
+
exc_type: type[BaseException] | None,
|
|
48
|
+
exc_val: BaseException | None,
|
|
49
|
+
exc_tb: TracebackType | None,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Async context manager exit."""
|
|
52
|
+
# TODO: Broadcast shutdown https://github.com/griptape-ai/griptape-nodes/issues/2149
|
|
53
|
+
return
|
|
54
|
+
|
|
26
55
|
def _load_flow_for_workflow(self) -> str:
|
|
27
56
|
try:
|
|
28
57
|
context_manager = GriptapeNodes.ContextManager()
|
|
@@ -124,7 +153,7 @@ class LocalWorkflowExecutor(WorkflowExecutor):
|
|
|
124
153
|
self,
|
|
125
154
|
workflow_name: str,
|
|
126
155
|
flow_input: Any,
|
|
127
|
-
storage_backend: StorageBackend =
|
|
156
|
+
storage_backend: StorageBackend | None = None,
|
|
128
157
|
**kwargs: Any,
|
|
129
158
|
) -> None:
|
|
130
159
|
"""Executes a local workflow.
|
|
@@ -140,12 +169,13 @@ class LocalWorkflowExecutor(WorkflowExecutor):
|
|
|
140
169
|
Returns:
|
|
141
170
|
None
|
|
142
171
|
"""
|
|
172
|
+
if storage_backend is not None:
|
|
173
|
+
msg = "The storage_backend parameter is deprecated. Pass `storage_backend` to the constructor instead."
|
|
174
|
+
raise ValueError(msg)
|
|
175
|
+
|
|
143
176
|
logger.info("Executing workflow: %s", workflow_name)
|
|
144
177
|
GriptapeNodes.EventManager().initialize_queue()
|
|
145
178
|
|
|
146
|
-
# Set the storage backend
|
|
147
|
-
self._set_storage_backend(storage_backend=storage_backend)
|
|
148
|
-
|
|
149
179
|
# Load workflow from file if workflow_path is provided
|
|
150
180
|
workflow_path = kwargs.get("workflow_path")
|
|
151
181
|
if workflow_path:
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
3
|
from abc import abstractmethod
|
|
4
|
-
from
|
|
4
|
+
from types import TracebackType
|
|
5
|
+
from typing import Any, Self
|
|
5
6
|
|
|
6
7
|
from griptape_nodes.drivers.storage import StorageBackend
|
|
7
8
|
|
|
@@ -12,6 +13,19 @@ class WorkflowExecutor:
|
|
|
12
13
|
def __init__(self) -> None:
|
|
13
14
|
self.output: dict | None = None
|
|
14
15
|
|
|
16
|
+
async def __aenter__(self) -> Self:
|
|
17
|
+
"""Async context manager entry."""
|
|
18
|
+
return self
|
|
19
|
+
|
|
20
|
+
async def __aexit__(
|
|
21
|
+
self,
|
|
22
|
+
exc_type: type[BaseException] | None,
|
|
23
|
+
exc_val: BaseException | None,
|
|
24
|
+
exc_tb: TracebackType | None,
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Async context manager exit."""
|
|
27
|
+
return
|
|
28
|
+
|
|
15
29
|
def run(
|
|
16
30
|
self,
|
|
17
31
|
workflow_name: str,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Griptape Nodes CLI module."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Griptape Nodes CLI commands."""
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Config command for Griptape Nodes CLI."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from griptape_nodes.cli.shared import console
|
|
9
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
10
|
+
|
|
11
|
+
config_manager = GriptapeNodes.ConfigManager()
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(help="Manage configuration.")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command()
|
|
17
|
+
def show(
|
|
18
|
+
config_path: str = typer.Argument(
|
|
19
|
+
None,
|
|
20
|
+
help="Optional config path to show specific value (e.g., 'workspace_directory').",
|
|
21
|
+
),
|
|
22
|
+
) -> None:
|
|
23
|
+
"""Show configuration values."""
|
|
24
|
+
_print_user_config(config_path)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.command("list")
|
|
28
|
+
def list_configs() -> None:
|
|
29
|
+
"""List configuration values."""
|
|
30
|
+
_list_user_configs()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.command()
|
|
34
|
+
def reset() -> None:
|
|
35
|
+
"""Reset configuration to defaults."""
|
|
36
|
+
_reset_user_config()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _print_user_config(config_path: str | None = None) -> None:
|
|
40
|
+
"""Prints the user configuration from the config file.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
config_path: Optional path to specific config value. If None, prints entire config.
|
|
44
|
+
"""
|
|
45
|
+
if config_path is None:
|
|
46
|
+
config = config_manager.merged_config
|
|
47
|
+
sys.stdout.write(json.dumps(config, indent=2))
|
|
48
|
+
else:
|
|
49
|
+
try:
|
|
50
|
+
value = config_manager.get_config_value(config_path)
|
|
51
|
+
if isinstance(value, (dict, list)):
|
|
52
|
+
sys.stdout.write(json.dumps(value, indent=2))
|
|
53
|
+
else:
|
|
54
|
+
sys.stdout.write(str(value))
|
|
55
|
+
except (KeyError, AttributeError, ValueError):
|
|
56
|
+
console.print(f"[bold red]Config path '{config_path}' not found[/bold red]")
|
|
57
|
+
sys.exit(1)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _list_user_configs() -> None:
|
|
61
|
+
"""Lists user configuration files in ascending precedence."""
|
|
62
|
+
num_config_files = len(config_manager.config_files)
|
|
63
|
+
console.print(
|
|
64
|
+
f"[bold]User Configuration Files (lowest precedence (1.) ⟶ highest precedence ({num_config_files}.)):[/bold]"
|
|
65
|
+
)
|
|
66
|
+
for idx, config in enumerate(config_manager.config_files):
|
|
67
|
+
console.print(f"[green]{idx + 1}. {config}[/green]")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _reset_user_config() -> None:
|
|
71
|
+
"""Resets the user configuration to the default values."""
|
|
72
|
+
console.print("[bold]Resetting user configuration to default values...[/bold]")
|
|
73
|
+
config_manager.reset_user_config()
|
|
74
|
+
console.print("[bold green]User configuration reset complete![/bold green]")
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Engine command for Griptape Nodes CLI."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from rich.prompt import Confirm
|
|
5
|
+
|
|
6
|
+
from griptape_nodes.app import start_app
|
|
7
|
+
from griptape_nodes.cli.commands.init import _run_init
|
|
8
|
+
from griptape_nodes.cli.commands.self import _get_latest_version, _update_self
|
|
9
|
+
from griptape_nodes.cli.shared import (
|
|
10
|
+
CONFIG_DIR,
|
|
11
|
+
ENV_API_KEY,
|
|
12
|
+
ENV_GTN_BUCKET_NAME,
|
|
13
|
+
ENV_LIBRARIES_SYNC,
|
|
14
|
+
ENV_REGISTER_ADVANCED_LIBRARY,
|
|
15
|
+
ENV_STORAGE_BACKEND,
|
|
16
|
+
ENV_WORKSPACE_DIRECTORY,
|
|
17
|
+
PACKAGE_NAME,
|
|
18
|
+
InitConfig,
|
|
19
|
+
console,
|
|
20
|
+
)
|
|
21
|
+
from griptape_nodes.utils.version_utils import get_current_version, get_install_source
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def engine_command(
|
|
25
|
+
no_update: bool = typer.Option(False, "--no-update", help="Skip the auto-update check."), # noqa: FBT001
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Run the Griptape Nodes engine."""
|
|
28
|
+
_start_engine(no_update=no_update)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _start_engine(*, no_update: bool = False) -> None:
|
|
32
|
+
"""Starts the Griptape Nodes engine.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
no_update (bool): If True, skips the auto-update check.
|
|
36
|
+
"""
|
|
37
|
+
if not CONFIG_DIR.exists():
|
|
38
|
+
# Default init flow if there is no config directory
|
|
39
|
+
console.print("[bold green]Config directory not found. Initializing...[/bold green]")
|
|
40
|
+
_run_init(
|
|
41
|
+
InitConfig(
|
|
42
|
+
workspace_directory=ENV_WORKSPACE_DIRECTORY,
|
|
43
|
+
api_key=ENV_API_KEY,
|
|
44
|
+
storage_backend=ENV_STORAGE_BACKEND,
|
|
45
|
+
register_advanced_library=ENV_REGISTER_ADVANCED_LIBRARY,
|
|
46
|
+
interactive=True,
|
|
47
|
+
config_values=None,
|
|
48
|
+
secret_values=None,
|
|
49
|
+
libraries_sync=ENV_LIBRARIES_SYNC,
|
|
50
|
+
bucket_name=ENV_GTN_BUCKET_NAME,
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Confusing double negation -- If `no_update` is set, we want to skip the update
|
|
55
|
+
if not no_update:
|
|
56
|
+
_auto_update_self()
|
|
57
|
+
|
|
58
|
+
console.print("[bold green]Starting Griptape Nodes engine...[/bold green]")
|
|
59
|
+
start_app()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _auto_update_self() -> None:
|
|
63
|
+
"""Automatically updates the script to the latest version if the user confirms."""
|
|
64
|
+
console.print("[bold green]Checking for updates...[/bold green]")
|
|
65
|
+
source, commit_id = get_install_source()
|
|
66
|
+
current_version = get_current_version()
|
|
67
|
+
latest_version = _get_latest_version(PACKAGE_NAME, source)
|
|
68
|
+
|
|
69
|
+
if source == "git" and commit_id is not None:
|
|
70
|
+
can_update = commit_id != latest_version
|
|
71
|
+
update_message = f"Your current engine version, {current_version} ({source} - {commit_id}), doesn't match the latest release, {latest_version}. Update now?"
|
|
72
|
+
else:
|
|
73
|
+
can_update = current_version < latest_version
|
|
74
|
+
update_message = f"Your current engine version, {current_version}, is behind the latest release, {latest_version}. Update now?"
|
|
75
|
+
|
|
76
|
+
if can_update:
|
|
77
|
+
update = Confirm.ask(update_message, default=True)
|
|
78
|
+
|
|
79
|
+
if update:
|
|
80
|
+
_update_self()
|