griptape-nodes 0.42.0__py3-none-any.whl → 0.43.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.
Files changed (132) hide show
  1. griptape_nodes/__init__.py +0 -0
  2. griptape_nodes/app/.python-version +0 -0
  3. griptape_nodes/app/__init__.py +1 -6
  4. griptape_nodes/app/api.py +199 -0
  5. griptape_nodes/app/app.py +140 -225
  6. griptape_nodes/app/watch.py +1 -1
  7. griptape_nodes/bootstrap/__init__.py +0 -0
  8. griptape_nodes/bootstrap/bootstrap_script.py +0 -0
  9. griptape_nodes/bootstrap/register_libraries_script.py +0 -0
  10. griptape_nodes/bootstrap/structure_config.yaml +0 -0
  11. griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
  12. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +0 -0
  13. griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +0 -0
  14. griptape_nodes/bootstrap/workflow_runners/__init__.py +0 -0
  15. griptape_nodes/bootstrap/workflow_runners/bootstrap_workflow_runner.py +0 -0
  16. griptape_nodes/bootstrap/workflow_runners/local_workflow_runner.py +0 -0
  17. griptape_nodes/bootstrap/workflow_runners/subprocess_workflow_runner.py +6 -2
  18. griptape_nodes/bootstrap/workflow_runners/workflow_runner.py +0 -0
  19. griptape_nodes/drivers/__init__.py +0 -0
  20. griptape_nodes/drivers/storage/__init__.py +0 -0
  21. griptape_nodes/drivers/storage/base_storage_driver.py +0 -0
  22. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +0 -0
  23. griptape_nodes/drivers/storage/local_storage_driver.py +2 -1
  24. griptape_nodes/drivers/storage/storage_backend.py +0 -0
  25. griptape_nodes/exe_types/__init__.py +0 -0
  26. griptape_nodes/exe_types/connections.py +0 -0
  27. griptape_nodes/exe_types/core_types.py +0 -0
  28. griptape_nodes/exe_types/flow.py +0 -0
  29. griptape_nodes/exe_types/node_types.py +17 -1
  30. griptape_nodes/exe_types/type_validator.py +0 -0
  31. griptape_nodes/machines/__init__.py +0 -0
  32. griptape_nodes/machines/control_flow.py +41 -12
  33. griptape_nodes/machines/fsm.py +16 -2
  34. griptape_nodes/machines/node_resolution.py +0 -0
  35. griptape_nodes/mcp_server/__init__.py +1 -0
  36. griptape_nodes/mcp_server/server.py +126 -0
  37. griptape_nodes/mcp_server/ws_request_manager.py +268 -0
  38. griptape_nodes/node_library/__init__.py +0 -0
  39. griptape_nodes/node_library/advanced_node_library.py +0 -0
  40. griptape_nodes/node_library/library_registry.py +0 -0
  41. griptape_nodes/node_library/workflow_registry.py +1 -1
  42. griptape_nodes/py.typed +0 -0
  43. griptape_nodes/retained_mode/__init__.py +0 -0
  44. griptape_nodes/retained_mode/events/__init__.py +0 -0
  45. griptape_nodes/retained_mode/events/agent_events.py +0 -0
  46. griptape_nodes/retained_mode/events/app_events.py +6 -2
  47. griptape_nodes/retained_mode/events/arbitrary_python_events.py +0 -0
  48. griptape_nodes/retained_mode/events/base_events.py +6 -6
  49. griptape_nodes/retained_mode/events/config_events.py +0 -0
  50. griptape_nodes/retained_mode/events/connection_events.py +0 -0
  51. griptape_nodes/retained_mode/events/context_events.py +0 -0
  52. griptape_nodes/retained_mode/events/execution_events.py +0 -0
  53. griptape_nodes/retained_mode/events/flow_events.py +0 -0
  54. griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
  55. griptape_nodes/retained_mode/events/library_events.py +2 -2
  56. griptape_nodes/retained_mode/events/logger_events.py +0 -0
  57. griptape_nodes/retained_mode/events/node_events.py +0 -0
  58. griptape_nodes/retained_mode/events/object_events.py +0 -0
  59. griptape_nodes/retained_mode/events/os_events.py +104 -2
  60. griptape_nodes/retained_mode/events/parameter_events.py +0 -0
  61. griptape_nodes/retained_mode/events/payload_registry.py +0 -0
  62. griptape_nodes/retained_mode/events/secrets_events.py +0 -0
  63. griptape_nodes/retained_mode/events/static_file_events.py +0 -0
  64. griptape_nodes/retained_mode/events/validation_events.py +0 -0
  65. griptape_nodes/retained_mode/events/workflow_events.py +0 -0
  66. griptape_nodes/retained_mode/griptape_nodes.py +43 -40
  67. griptape_nodes/retained_mode/managers/__init__.py +0 -0
  68. griptape_nodes/retained_mode/managers/agent_manager.py +48 -22
  69. griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
  70. griptape_nodes/retained_mode/managers/config_manager.py +0 -0
  71. griptape_nodes/retained_mode/managers/context_manager.py +0 -0
  72. griptape_nodes/retained_mode/managers/engine_identity_manager.py +0 -0
  73. griptape_nodes/retained_mode/managers/event_manager.py +0 -0
  74. griptape_nodes/retained_mode/managers/flow_manager.py +2 -0
  75. griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +45 -0
  76. griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +191 -0
  77. griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +346 -0
  78. griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +439 -0
  79. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +17 -0
  80. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +82 -0
  81. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +116 -0
  82. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +352 -0
  83. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +104 -0
  84. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +155 -0
  85. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +18 -0
  86. griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +12 -0
  87. griptape_nodes/retained_mode/managers/library_manager.py +144 -39
  88. griptape_nodes/retained_mode/managers/node_manager.py +86 -72
  89. griptape_nodes/retained_mode/managers/object_manager.py +0 -0
  90. griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
  91. griptape_nodes/retained_mode/managers/os_manager.py +517 -12
  92. griptape_nodes/retained_mode/managers/secrets_manager.py +0 -0
  93. griptape_nodes/retained_mode/managers/session_manager.py +0 -0
  94. griptape_nodes/retained_mode/managers/settings.py +0 -0
  95. griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
  96. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +2 -2
  97. griptape_nodes/retained_mode/managers/workflow_manager.py +199 -2
  98. griptape_nodes/retained_mode/retained_mode.py +0 -0
  99. griptape_nodes/retained_mode/utils/__init__.py +0 -0
  100. griptape_nodes/retained_mode/utils/engine_identity.py +0 -0
  101. griptape_nodes/retained_mode/utils/name_generator.py +0 -0
  102. griptape_nodes/traits/__init__.py +0 -0
  103. griptape_nodes/traits/add_param_button.py +0 -0
  104. griptape_nodes/traits/button.py +0 -0
  105. griptape_nodes/traits/clamp.py +0 -0
  106. griptape_nodes/traits/compare.py +0 -0
  107. griptape_nodes/traits/compare_images.py +0 -0
  108. griptape_nodes/traits/file_system_picker.py +127 -0
  109. griptape_nodes/traits/minmax.py +0 -0
  110. griptape_nodes/traits/options.py +0 -0
  111. griptape_nodes/traits/slider.py +0 -0
  112. griptape_nodes/traits/trait_registry.py +0 -0
  113. griptape_nodes/traits/traits.json +0 -0
  114. griptape_nodes/updater/__init__.py +2 -2
  115. griptape_nodes/updater/__main__.py +0 -0
  116. griptape_nodes/utils/__init__.py +0 -0
  117. griptape_nodes/utils/dict_utils.py +0 -0
  118. griptape_nodes/utils/image_preview.py +128 -0
  119. griptape_nodes/utils/metaclasses.py +0 -0
  120. griptape_nodes/version_compatibility/__init__.py +0 -0
  121. griptape_nodes/version_compatibility/versions/__init__.py +0 -0
  122. griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
  123. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +5 -5
  124. griptape_nodes-0.43.0.dist-info/METADATA +90 -0
  125. griptape_nodes-0.43.0.dist-info/RECORD +129 -0
  126. griptape_nodes-0.43.0.dist-info/WHEEL +4 -0
  127. {griptape_nodes-0.42.0.dist-info → griptape_nodes-0.43.0.dist-info}/entry_points.txt +1 -0
  128. griptape_nodes/app/app_sessions.py +0 -554
  129. griptape_nodes-0.42.0.dist-info/METADATA +0 -78
  130. griptape_nodes-0.42.0.dist-info/RECORD +0 -113
  131. griptape_nodes-0.42.0.dist-info/WHEEL +0 -4
  132. griptape_nodes-0.42.0.dist-info/licenses/LICENSE +0 -201
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import json
4
4
  from abc import ABC, abstractmethod
5
5
  from dataclasses import asdict, dataclass, field, is_dataclass
6
- from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar
6
+ from typing import TYPE_CHECKING, Any, ClassVar, TypeVar
7
7
 
8
8
  from griptape.artifacts import BaseArtifact
9
9
  from griptape.events import BaseEvent as GtBaseEvent
@@ -101,7 +101,7 @@ class AppPayload(Payload):
101
101
 
102
102
 
103
103
  # Type variables for our generic payloads
104
- P = TypeVar("P", bound=Payload)
104
+ P = TypeVar("P", bound=RequestPayload)
105
105
  R = TypeVar("R", bound=ResultPayload)
106
106
  E = TypeVar("E", bound=ExecutionPayload)
107
107
  A = TypeVar("A", bound=AppPayload)
@@ -181,7 +181,7 @@ class BaseEvent(BaseModel, ABC):
181
181
  """
182
182
 
183
183
 
184
- class EventRequest(BaseEvent, Generic[P]):
184
+ class EventRequest[P: Payload](BaseEvent):
185
185
  """Request event."""
186
186
 
187
187
  request: P
@@ -245,7 +245,7 @@ class EventRequest(BaseEvent, Generic[P]):
245
245
  return cls(request=request_payload, **event_data)
246
246
 
247
247
 
248
- class EventResult(BaseEvent, Generic[P, R], ABC):
248
+ class EventResult[P: RequestPayload, R: ResultPayload](BaseEvent, ABC):
249
249
  """Abstract base class for result events."""
250
250
 
251
251
  request: P
@@ -429,7 +429,7 @@ def deserialize_event(json_data: str | dict | Any) -> BaseEvent:
429
429
 
430
430
 
431
431
  # EXECUTION EVENT BASE (this event type is used for the execution of a Griptape Nodes flow)
432
- class ExecutionEvent(BaseEvent, Generic[E]):
432
+ class ExecutionEvent[E: ExecutionPayload](BaseEvent):
433
433
  payload: E
434
434
 
435
435
  def __init__(self, **data) -> None:
@@ -490,7 +490,7 @@ class ExecutionEvent(BaseEvent, Generic[E]):
490
490
 
491
491
 
492
492
  # Events sent as part of the lifecycle of the Griptape Nodes application.
493
- class AppEvent(BaseEvent, Generic[A]):
493
+ class AppEvent[A: AppPayload](BaseEvent):
494
494
  payload: A
495
495
 
496
496
  def __init__(self, **data) -> None:
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -14,7 +14,7 @@ from griptape_nodes.retained_mode.events.payload_registry import PayloadRegistry
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from griptape_nodes.node_library.library_registry import LibraryMetadata, LibrarySchema, NodeMetadata
17
- from griptape_nodes.retained_mode.managers.library_manager import LibraryManager
17
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
18
18
 
19
19
 
20
20
  @dataclass
@@ -207,7 +207,7 @@ class LoadLibraryMetadataFromFileResultFailure(WorkflowNotAlteredMixin, ResultPa
207
207
 
208
208
  library_path: str
209
209
  library_name: str | None
210
- status: LibraryManager.LibraryStatus
210
+ status: LibraryStatus
211
211
  problems: list[str]
212
212
 
213
213
 
File without changes
File without changes
File without changes
@@ -9,6 +9,17 @@ from griptape_nodes.retained_mode.events.base_events import (
9
9
  from griptape_nodes.retained_mode.events.payload_registry import PayloadRegistry
10
10
 
11
11
 
12
+ @dataclass
13
+ class FileSystemEntry:
14
+ """Represents a file or directory in the file system."""
15
+
16
+ name: str
17
+ path: str
18
+ is_dir: bool
19
+ size: int
20
+ modified_time: float
21
+
22
+
12
23
  @dataclass
13
24
  @PayloadRegistry.register
14
25
  class OpenAssociatedFileRequest(RequestPayload):
@@ -18,12 +29,14 @@ class OpenAssociatedFileRequest(RequestPayload):
18
29
  providing file viewing capabilities, implementing file associations.
19
30
 
20
31
  Args:
21
- path_to_file: Path to the file to open with the associated application
32
+ path_to_file: Path to the file to open (mutually exclusive with file_entry)
33
+ file_entry: FileSystemEntry object from directory listing (mutually exclusive with path_to_file)
22
34
 
23
35
  Results: OpenAssociatedFileResultSuccess | OpenAssociatedFileResultFailure (file not found, no association)
24
36
  """
25
37
 
26
- path_to_file: str
38
+ path_to_file: str | None = None
39
+ file_entry: FileSystemEntry | None = None
27
40
 
28
41
 
29
42
  @dataclass
@@ -36,3 +49,92 @@ class OpenAssociatedFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSucc
36
49
  @PayloadRegistry.register
37
50
  class OpenAssociatedFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
38
51
  """File opening failed. Common causes: file not found, no associated application, permission denied."""
52
+
53
+
54
+ @dataclass
55
+ @PayloadRegistry.register
56
+ class ListDirectoryRequest(RequestPayload):
57
+ """List contents of a directory.
58
+
59
+ Use when: Browsing file system, showing directory contents,
60
+ implementing file pickers, navigating folder structures.
61
+
62
+ Args:
63
+ directory_path: Path to the directory to list (None for current directory)
64
+ show_hidden: Whether to show hidden files/folders
65
+ workspace_only: If True, constrain to workspace directory. If False, allow system-wide browsing.
66
+ If None, workspace constraints don't apply (e.g., cloud environments).
67
+
68
+ Results: ListDirectoryResultSuccess (with entries) | ListDirectoryResultFailure (access denied, not found)
69
+ """
70
+
71
+ directory_path: str | None = None
72
+ show_hidden: bool = False
73
+ workspace_only: bool | None = True
74
+
75
+
76
+ @dataclass
77
+ @PayloadRegistry.register
78
+ class ListDirectoryResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
79
+ """Directory listing retrieved successfully."""
80
+
81
+ entries: list[FileSystemEntry]
82
+ current_path: str
83
+ is_workspace_path: bool
84
+
85
+
86
+ @dataclass
87
+ @PayloadRegistry.register
88
+ class ListDirectoryResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
89
+ """Directory listing failed. Common causes: access denied, path not found."""
90
+
91
+
92
+ @dataclass
93
+ @PayloadRegistry.register
94
+ class ReadFileRequest(RequestPayload):
95
+ """Read contents of a file, automatically detecting if it's text or binary using MIME types.
96
+
97
+ Use when: Reading file contents for display, processing, or analysis.
98
+ Automatically detects file type using MIME type detection and returns appropriate content format.
99
+
100
+ Args:
101
+ file_path: Path to the file to read (mutually exclusive with file_entry)
102
+ file_entry: FileSystemEntry object from directory listing (mutually exclusive with file_path)
103
+ encoding: Text encoding to use if file is detected as text (default: 'utf-8')
104
+ workspace_only: If True, constrain to workspace directory. If False, allow system-wide access.
105
+ If None, workspace constraints don't apply (e.g., cloud environments).
106
+
107
+ Results: ReadFileResultSuccess (with content) | ReadFileResultFailure (file not found, permission denied)
108
+ """
109
+
110
+ file_path: str | None = None
111
+ file_entry: FileSystemEntry | None = None
112
+ encoding: str = "utf-8"
113
+ workspace_only: bool | None = True
114
+
115
+
116
+ @dataclass
117
+ @PayloadRegistry.register
118
+ class ReadFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
119
+ """File contents read successfully."""
120
+
121
+ content: str | bytes # String for text files, bytes for binary files
122
+ file_size: int
123
+ mime_type: str # e.g., "text/plain", "image/png", "application/pdf"
124
+ encoding: str | None # Text encoding used (None for binary files)
125
+ compression_encoding: str | None = None # Compression encoding (e.g., "gzip", "bzip2", None)
126
+ is_text: bool = False # Will be computed from content type
127
+
128
+ def __post_init__(self) -> None:
129
+ """Compute is_text from content type after initialization."""
130
+ # For images, even though content is a string (base64), it's not text content
131
+ if self.mime_type.startswith("image/"):
132
+ self.is_text = False
133
+ else:
134
+ self.is_text = isinstance(self.content, str)
135
+
136
+
137
+ @dataclass
138
+ @PayloadRegistry.register
139
+ class ReadFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
140
+ """File reading failed. Common causes: file not found, permission denied, encoding error."""
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -9,11 +9,10 @@ from dataclasses import dataclass
9
9
  from datetime import UTC, datetime
10
10
  from typing import TYPE_CHECKING, Any
11
11
 
12
- import httpx
13
-
14
12
  from griptape_nodes.exe_types.flow import ControlFlow
15
13
  from griptape_nodes.node_library.workflow_registry import WorkflowRegistry
16
14
  from griptape_nodes.retained_mode.events.app_events import (
15
+ AppConnectionEstablished,
17
16
  AppEndSessionRequest,
18
17
  AppEndSessionResultFailure,
19
18
  AppEndSessionResultSuccess,
@@ -119,6 +118,10 @@ class Version:
119
118
  """Equality comparison."""
120
119
  return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch)
121
120
 
121
+ def __hash__(self) -> int:
122
+ """Hash function for Version."""
123
+ return hash((self.major, self.minor, self.patch))
124
+
122
125
 
123
126
  class GriptapeNodes(metaclass=SingletonMeta):
124
127
  _event_manager: EventManager
@@ -171,8 +174,8 @@ class GriptapeNodes(metaclass=SingletonMeta):
171
174
  # Initialize only if our managers haven't been created yet
172
175
  if not hasattr(self, "_event_manager"):
173
176
  self._event_manager = EventManager()
174
- self._os_manager = OSManager(self._event_manager)
175
177
  self._config_manager = ConfigManager(self._event_manager)
178
+ self._os_manager = OSManager(self._event_manager)
176
179
  self._secrets_manager = SecretsManager(self._config_manager, self._event_manager)
177
180
  self._object_manager = ObjectManager(self._event_manager)
178
181
  self._node_manager = NodeManager(self._event_manager)
@@ -198,6 +201,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
198
201
  AppStartSessionRequest, self.handle_session_start_request
199
202
  )
200
203
  self._event_manager.assign_manager_to_request_type(AppEndSessionRequest, self.handle_session_end_request)
204
+ self._event_manager.add_listener_to_app_event(AppConnectionEstablished, self.on_app_connection_established)
201
205
  self._event_manager.assign_manager_to_request_type(AppGetSessionRequest, self.handle_get_session_request)
202
206
  self._event_manager.assign_manager_to_request_type(
203
207
  SessionHeartbeatRequest, self.handle_session_heartbeat_request
@@ -293,6 +297,10 @@ class GriptapeNodes(metaclass=SingletonMeta):
293
297
  def ConfigManager(cls) -> ConfigManager:
294
298
  return GriptapeNodes.get_instance()._config_manager
295
299
 
300
+ @classmethod
301
+ def OSManager(cls) -> OSManager:
302
+ return GriptapeNodes.get_instance()._os_manager
303
+
296
304
  @classmethod
297
305
  def SecretsManager(cls) -> SecretsManager:
298
306
  return GriptapeNodes.get_instance()._secrets_manager
@@ -344,6 +352,27 @@ class GriptapeNodes(metaclass=SingletonMeta):
344
352
  msg = "Failed to successfully delete all objects"
345
353
  raise ValueError(msg)
346
354
 
355
+ def on_app_connection_established(self, _payload: AppConnectionEstablished) -> None:
356
+ from griptape_nodes.app.app import subscribe_to_topic
357
+
358
+ # Subscribe to request topic (engine discovery)
359
+ subscribe_to_topic("request")
360
+
361
+ # Get engine ID and subscribe to engine_id/request
362
+ engine_id = GriptapeNodes.get_engine_id()
363
+ if engine_id:
364
+ subscribe_to_topic(f"engines/{engine_id}/request")
365
+ else:
366
+ logger.warning("Engine ID not available for subscription")
367
+
368
+ # Get session ID and subscribe to session_id/request if available
369
+ session_id = GriptapeNodes.get_session_id()
370
+ if session_id:
371
+ topic = f"sessions/{session_id}/request"
372
+ subscribe_to_topic(topic)
373
+ else:
374
+ logger.info("No session ID available for subscription")
375
+
347
376
  def handle_engine_version_request(self, request: GetEngineVersionRequest) -> ResultPayload: # noqa: ARG002
348
377
  try:
349
378
  engine_ver = Version.from_string(engine_version)
@@ -362,6 +391,8 @@ class GriptapeNodes(metaclass=SingletonMeta):
362
391
  return GetEngineVersionResultFailure()
363
392
 
364
393
  def handle_session_start_request(self, request: AppStartSessionRequest) -> ResultPayload: # noqa: ARG002
394
+ from griptape_nodes.app.app import subscribe_to_topic
395
+
365
396
  current_session_id = GriptapeNodes.SessionManager().get_active_session_id()
366
397
  if current_session_id is None:
367
398
  # Client wants a new session
@@ -372,9 +403,15 @@ class GriptapeNodes(metaclass=SingletonMeta):
372
403
  else:
373
404
  details = f"Session '{current_session_id}' already active. Joining..."
374
405
 
406
+ topic = f"sessions/{current_session_id}/request"
407
+ subscribe_to_topic(topic)
408
+ logger.info("Subscribed to new session topic: %s", topic)
409
+
375
410
  return AppStartSessionResultSuccess(current_session_id)
376
411
 
377
412
  def handle_session_end_request(self, _: AppEndSessionRequest) -> ResultPayload:
413
+ from griptape_nodes.app.app import unsubscribe_from_topic
414
+
378
415
  try:
379
416
  previous_session_id = GriptapeNodes.SessionManager().get_active_session_id()
380
417
  if previous_session_id is None:
@@ -385,6 +422,9 @@ class GriptapeNodes(metaclass=SingletonMeta):
385
422
  logger.info(details)
386
423
  GriptapeNodes.SessionManager().clear_saved_session()
387
424
 
425
+ unsubscribe_topic = f"sessions/{previous_session_id}/request"
426
+ unsubscribe_from_topic(unsubscribe_topic)
427
+
388
428
  return AppEndSessionResultSuccess(session_id=previous_session_id)
389
429
  except Exception as err:
390
430
  details = f"Failed to end session due to '{err}'."
@@ -485,45 +525,8 @@ class GriptapeNodes(metaclass=SingletonMeta):
485
525
  # Determine deployment type based on presence of instance environment variables
486
526
  instance_info["deployment_type"] = "griptape_hosted" if any(instance_info.values()) else "local"
487
527
 
488
- # Get public IP address
489
- public_ip = self._get_public_ip()
490
- if public_ip:
491
- instance_info["public_ip"] = public_ip
492
-
493
528
  return instance_info
494
529
 
495
- def _get_public_ip(self) -> str | None:
496
- """Get the public IP address of this device.
497
-
498
- Returns the public IP address if available, None otherwise.
499
- """
500
- try:
501
- # Try multiple services in case one is down
502
- services = [
503
- "https://api.ipify.org",
504
- "https://ipinfo.io/ip",
505
- "https://icanhazip.com",
506
- ]
507
-
508
- for service in services:
509
- try:
510
- with httpx.Client(timeout=5.0) as client:
511
- response = client.get(service)
512
- response.raise_for_status()
513
- public_ip = response.text.strip()
514
- if public_ip:
515
- logger.debug("Retrieved public IP from %s: %s", service, public_ip)
516
- return public_ip
517
- except Exception as err:
518
- logger.debug("Failed to get public IP from %s: %s", service, err)
519
- continue
520
- logger.warning("Unable to retrieve public IP from any service")
521
- except Exception as err:
522
- logger.warning("Failed to get public IP: %s", err)
523
- return None
524
- else:
525
- return None
526
-
527
530
  def _get_current_workflow_info(self) -> dict[str, Any]:
528
531
  """Get information about the currently loaded workflow.
529
532
 
File without changes
@@ -1,7 +1,9 @@
1
1
  import json
2
2
  import logging
3
+ import os
3
4
  import threading
4
5
  import uuid
6
+ from typing import TYPE_CHECKING
5
7
 
6
8
  from attrs import define, field
7
9
  from griptape.artifacts import ErrorArtifact, ImageUrlArtifact, JsonArtifact
@@ -14,6 +16,7 @@ from griptape.memory.structure import ConversationMemory
14
16
  from griptape.rules import Rule, Ruleset
15
17
  from griptape.structures import Agent
16
18
  from griptape.tools import BaseImageGenerationTool
19
+ from griptape.tools.mcp.tool import MCPTool
17
20
  from griptape.utils.decorators import activity
18
21
  from json_repair import repair_json
19
22
  from schema import Literal, Schema
@@ -42,10 +45,14 @@ from griptape_nodes.retained_mode.managers.static_files_manager import (
42
45
  StaticFilesManager,
43
46
  )
44
47
 
48
+ if TYPE_CHECKING:
49
+ from griptape.tools.mcp.sessions import StreamableHttpConnection
50
+
45
51
  logger = logging.getLogger("griptape_nodes")
46
52
 
47
53
  API_KEY_ENV_VAR = "GT_CLOUD_API_KEY"
48
54
  SERVICE = "Griptape"
55
+ GTN_MCP_SERVER_PORT = int(os.getenv("GTN_MCP_SERVER_PORT", "9927"))
49
56
 
50
57
  config_manager = ConfigManager()
51
58
  secrets_manager = SecretsManager(config_manager)
@@ -84,6 +91,7 @@ class AgentManager:
84
91
  self.conversation_memory = ConversationMemory()
85
92
  self.prompt_driver = None
86
93
  self.image_tool = None
94
+ self.mcp_tool = None
87
95
  self.static_files_manager = static_files_manager
88
96
 
89
97
  if event_manager is not None:
@@ -113,14 +121,53 @@ class AgentManager:
113
121
  static_files_manager=self.static_files_manager,
114
122
  )
115
123
 
124
+ def _initialize_mcp_tool(self) -> MCPTool:
125
+ connection: StreamableHttpConnection = { # type: ignore[reportAssignmentType]
126
+ "transport": "streamable_http",
127
+ "url": f"http://localhost:{GTN_MCP_SERVER_PORT}/mcp/",
128
+ }
129
+ return MCPTool(connection=connection)
130
+
116
131
  def on_handle_run_agent_request(self, request: RunAgentRequest) -> ResultPayload:
117
132
  if self.prompt_driver is None:
118
133
  self.prompt_driver = self._initialize_prompt_driver()
119
134
  if self.image_tool is None:
120
135
  self.image_tool = self._initialize_image_tool()
136
+ if self.mcp_tool is None:
137
+ self.mcp_tool = self._initialize_mcp_tool()
121
138
  threading.Thread(target=self._on_handle_run_agent_request, args=(request, EventBus.event_listeners)).start()
122
139
  return RunAgentResultStarted()
123
140
 
141
+ def _create_agent(self) -> Agent:
142
+ output_schema = Schema(
143
+ {
144
+ "generated_image_urls": [str],
145
+ "conversation_output": str,
146
+ }
147
+ )
148
+
149
+ tools = []
150
+ if self.image_tool is not None:
151
+ tools.append(self.image_tool)
152
+ if self.mcp_tool is not None:
153
+ tools.append(self.mcp_tool)
154
+
155
+ return Agent(
156
+ prompt_driver=self.prompt_driver,
157
+ conversation_memory=self.conversation_memory,
158
+ tools=tools,
159
+ output_schema=output_schema,
160
+ rulesets=[
161
+ Ruleset(
162
+ name="generated_image_urls",
163
+ rules=[
164
+ Rule("Do not hallucinate generated_image_urls."),
165
+ Rule("Only set generated_image_urls with images generated with your tools."),
166
+ ],
167
+ ),
168
+ ],
169
+ )
170
+
124
171
  def _on_handle_run_agent_request(
125
172
  self, request: RunAgentRequest, event_listeners: list[EventListener]
126
173
  ) -> ResultPayload:
@@ -131,28 +178,7 @@ class AgentManager:
131
178
  for url_artifact in request.url_artifacts
132
179
  if url_artifact["type"] == "ImageUrlArtifact"
133
180
  ]
134
-
135
- output_schema = Schema(
136
- {
137
- "generated_image_urls": [str],
138
- "conversation_output": str,
139
- }
140
- )
141
- agent = Agent(
142
- prompt_driver=self.prompt_driver,
143
- conversation_memory=self.conversation_memory,
144
- tools=[self.image_tool] if self.image_tool else [],
145
- output_schema=output_schema,
146
- rulesets=[
147
- Ruleset(
148
- name="generated_image_urls",
149
- rules=[
150
- Rule("Do not hallucinate generated_image_urls."),
151
- Rule("Only set generated_image_urls with images generated with your tools."),
152
- ],
153
- ),
154
- ],
155
- )
181
+ agent = self._create_agent()
156
182
  *events, last_event = agent.run_stream([request.input, *artifacts])
157
183
  full_result = ""
158
184
  last_conversation_output = ""
File without changes
File without changes
File without changes
@@ -1810,6 +1810,8 @@ class FlowManager:
1810
1810
  def unresolve_whole_flow(self, flow: ControlFlow) -> None:
1811
1811
  for node in flow.nodes.values():
1812
1812
  node.make_node_unresolved(current_states_to_trigger_change_event=None)
1813
+ # Clear entry control parameter for new execution
1814
+ node.set_entry_control_parameter(None)
1813
1815
 
1814
1816
  def flow_state(self, flow: ControlFlow) -> tuple[str | None, str | None]: # noqa: ARG002
1815
1817
  if not self.check_for_existing_running_flow():
@@ -0,0 +1,45 @@
1
+ """Library lifecycle management subsystem."""
2
+
3
+ from griptape_nodes.retained_mode.managers.library_lifecycle.data_models import (
4
+ EvaluationResult,
5
+ InspectionResult,
6
+ InstallationResult,
7
+ LibraryByType,
8
+ LibraryEntry,
9
+ LibraryLoadedResult,
10
+ LibraryPreferences,
11
+ LifecycleIssue,
12
+ )
13
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_directory import LibraryDirectory
14
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_fsm import (
15
+ LibraryLifecycleContext,
16
+ LibraryLifecycleFSM,
17
+ )
18
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance import (
19
+ LibraryProvenance,
20
+ LibraryProvenanceGitHub,
21
+ LibraryProvenanceLocalFile,
22
+ LibraryProvenancePackage,
23
+ LibraryProvenanceSandbox,
24
+ )
25
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
26
+
27
+ __all__ = [
28
+ "EvaluationResult",
29
+ "InspectionResult",
30
+ "InstallationResult",
31
+ "LibraryByType",
32
+ "LibraryDirectory",
33
+ "LibraryEntry",
34
+ "LibraryLifecycleContext",
35
+ "LibraryLifecycleFSM",
36
+ "LibraryLoadedResult",
37
+ "LibraryPreferences",
38
+ "LibraryProvenance",
39
+ "LibraryProvenanceGitHub",
40
+ "LibraryProvenanceLocalFile",
41
+ "LibraryProvenancePackage",
42
+ "LibraryProvenanceSandbox",
43
+ "LibraryStatus",
44
+ "LifecycleIssue",
45
+ ]