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.
Files changed (71) hide show
  1. griptape_nodes/__init__.py +8 -942
  2. griptape_nodes/__main__.py +6 -0
  3. griptape_nodes/app/app.py +48 -86
  4. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +35 -5
  5. griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +15 -1
  6. griptape_nodes/cli/__init__.py +1 -0
  7. griptape_nodes/cli/commands/__init__.py +1 -0
  8. griptape_nodes/cli/commands/config.py +74 -0
  9. griptape_nodes/cli/commands/engine.py +80 -0
  10. griptape_nodes/cli/commands/init.py +550 -0
  11. griptape_nodes/cli/commands/libraries.py +96 -0
  12. griptape_nodes/cli/commands/models.py +504 -0
  13. griptape_nodes/cli/commands/self.py +120 -0
  14. griptape_nodes/cli/main.py +56 -0
  15. griptape_nodes/cli/shared.py +75 -0
  16. griptape_nodes/common/__init__.py +1 -0
  17. griptape_nodes/common/directed_graph.py +71 -0
  18. griptape_nodes/drivers/storage/base_storage_driver.py +40 -20
  19. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +24 -29
  20. griptape_nodes/drivers/storage/local_storage_driver.py +23 -14
  21. griptape_nodes/exe_types/core_types.py +60 -2
  22. griptape_nodes/exe_types/node_types.py +257 -38
  23. griptape_nodes/exe_types/param_components/__init__.py +1 -0
  24. griptape_nodes/exe_types/param_components/execution_status_component.py +138 -0
  25. griptape_nodes/machines/control_flow.py +195 -94
  26. griptape_nodes/machines/dag_builder.py +207 -0
  27. griptape_nodes/machines/fsm.py +10 -1
  28. griptape_nodes/machines/parallel_resolution.py +558 -0
  29. griptape_nodes/machines/{node_resolution.py → sequential_resolution.py} +30 -57
  30. griptape_nodes/node_library/library_registry.py +34 -1
  31. griptape_nodes/retained_mode/events/app_events.py +5 -1
  32. griptape_nodes/retained_mode/events/base_events.py +9 -9
  33. griptape_nodes/retained_mode/events/config_events.py +30 -0
  34. griptape_nodes/retained_mode/events/execution_events.py +2 -2
  35. griptape_nodes/retained_mode/events/model_events.py +296 -0
  36. griptape_nodes/retained_mode/events/node_events.py +4 -3
  37. griptape_nodes/retained_mode/griptape_nodes.py +34 -12
  38. griptape_nodes/retained_mode/managers/agent_manager.py +23 -5
  39. griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +3 -1
  40. griptape_nodes/retained_mode/managers/config_manager.py +44 -3
  41. griptape_nodes/retained_mode/managers/context_manager.py +6 -5
  42. griptape_nodes/retained_mode/managers/event_manager.py +8 -2
  43. griptape_nodes/retained_mode/managers/flow_manager.py +150 -206
  44. griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +1 -1
  45. griptape_nodes/retained_mode/managers/library_manager.py +35 -25
  46. griptape_nodes/retained_mode/managers/model_manager.py +1107 -0
  47. griptape_nodes/retained_mode/managers/node_manager.py +102 -220
  48. griptape_nodes/retained_mode/managers/object_manager.py +11 -5
  49. griptape_nodes/retained_mode/managers/os_manager.py +28 -13
  50. griptape_nodes/retained_mode/managers/secrets_manager.py +8 -4
  51. griptape_nodes/retained_mode/managers/settings.py +116 -7
  52. griptape_nodes/retained_mode/managers/static_files_manager.py +85 -12
  53. griptape_nodes/retained_mode/managers/sync_manager.py +17 -9
  54. griptape_nodes/retained_mode/managers/workflow_manager.py +186 -192
  55. griptape_nodes/retained_mode/retained_mode.py +19 -0
  56. griptape_nodes/servers/__init__.py +1 -0
  57. griptape_nodes/{mcp_server/server.py → servers/mcp.py} +1 -1
  58. griptape_nodes/{app/api.py → servers/static.py} +43 -40
  59. griptape_nodes/traits/add_param_button.py +1 -1
  60. griptape_nodes/traits/button.py +334 -6
  61. griptape_nodes/traits/color_picker.py +66 -0
  62. griptape_nodes/traits/multi_options.py +188 -0
  63. griptape_nodes/traits/numbers_selector.py +77 -0
  64. griptape_nodes/traits/options.py +93 -2
  65. griptape_nodes/traits/traits.json +4 -0
  66. griptape_nodes/utils/async_utils.py +31 -0
  67. {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/METADATA +4 -1
  68. {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/RECORD +71 -48
  69. {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/WHEEL +1 -1
  70. /griptape_nodes/{mcp_server → servers}/ws_request_manager.py +0 -0
  71. {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/entry_points.txt +0 -0
@@ -42,6 +42,14 @@ class ResolutionContext:
42
42
  self.focus_stack = []
43
43
  self.paused = False
44
44
 
45
+ @property
46
+ def current_node(self) -> BaseNode:
47
+ """Get the currently focused node from the focus stack."""
48
+ if not self.focus_stack:
49
+ msg = "No node is currently in focus - focus stack is empty"
50
+ raise RuntimeError(msg)
51
+ return self.focus_stack[-1].node
52
+
45
53
  def reset(self) -> None:
46
54
  if self.focus_stack:
47
55
  node = self.focus_stack[-1].node
@@ -55,12 +63,6 @@ class InitializeSpotlightState(State):
55
63
  @staticmethod
56
64
  async def on_enter(context: ResolutionContext) -> type[State] | None:
57
65
  # If the focus stack is empty
58
- current_node = context.focus_stack[-1].node
59
- GriptapeNodes.EventManager().put_event(
60
- ExecutionGriptapeNodeEvent(
61
- wrapped_event=ExecutionEvent(payload=CurrentDataNodeEvent(node_name=current_node.name))
62
- )
63
- )
64
66
  if not context.paused:
65
67
  return InitializeSpotlightState
66
68
  return None
@@ -70,7 +72,7 @@ class InitializeSpotlightState(State):
70
72
  # If the focus stack is empty
71
73
  if not len(context.focus_stack):
72
74
  return CompleteState
73
- current_node = context.focus_stack[-1].node
75
+ current_node = context.current_node
74
76
  if current_node.state == NodeResolutionState.UNRESOLVED:
75
77
  # Mark all future nodes unresolved.
76
78
  # TODO: https://github.com/griptape-ai/griptape-nodes/issues/862
@@ -95,7 +97,7 @@ class InitializeSpotlightState(State):
95
97
  class EvaluateParameterState(State):
96
98
  @staticmethod
97
99
  async def on_enter(context: ResolutionContext) -> type[State] | None:
98
- current_node = context.focus_stack[-1].node
100
+ current_node = context.current_node
99
101
  current_parameter = current_node.get_current_parameter()
100
102
  if current_parameter is None:
101
103
  return ExecuteNodeState
@@ -116,7 +118,7 @@ class EvaluateParameterState(State):
116
118
 
117
119
  @staticmethod
118
120
  async def on_update(context: ResolutionContext) -> type[State] | None:
119
- current_node = context.focus_stack[-1].node
121
+ current_node = context.current_node
120
122
  current_parameter = current_node.get_current_parameter()
121
123
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
122
124
 
@@ -143,48 +145,6 @@ class EvaluateParameterState(State):
143
145
 
144
146
 
145
147
  class ExecuteNodeState(State):
146
- # TODO: https://github.com/griptape-ai/griptape-nodes/issues/864
147
- @staticmethod
148
- async def clear_parameter_output_values(context: ResolutionContext) -> None:
149
- """Clears all parameter output values for the currently focused node in the resolution context.
150
-
151
- This method iterates through each parameter output value stored in the current node,
152
- removes it from the node's parameter_output_values dictionary, and publishes an event
153
- to notify the system about the parameter value being set to None.
154
-
155
- Args:
156
- context (ResolutionContext): The resolution context containing the focus stack
157
- with the current node being processed.
158
-
159
- Raises:
160
- ValueError: If a parameter name in parameter_output_values doesn't correspond
161
- to an actual parameter in the node.
162
-
163
- Note:
164
- - Uses a copy of parameter_output_values to safely modify the dictionary during iteration
165
- - For each parameter, publishes a ParameterValueUpdateEvent with value=None
166
- - Events are wrapped in ExecutionGriptapeNodeEvent before publishing
167
- """
168
- current_node = context.focus_stack[-1].node
169
- for parameter_name in current_node.parameter_output_values.copy():
170
- parameter = current_node.get_parameter_by_name(parameter_name)
171
- if parameter is None:
172
- err = f"Attempted to execute node '{current_node.name}' but could not find parameter '{parameter_name}' that was indicated as having a value."
173
- raise ValueError(err)
174
- parameter_type = parameter.type
175
- if parameter_type is None:
176
- parameter_type = ParameterTypeBuiltin.NONE.value
177
- payload = ParameterValueUpdateEvent(
178
- node_name=current_node.name,
179
- parameter_name=parameter_name,
180
- data_type=parameter_type,
181
- value=None,
182
- )
183
- GriptapeNodes.EventManager().put_event(
184
- ExecutionGriptapeNodeEvent(wrapped_event=ExecutionEvent(payload=payload))
185
- )
186
- current_node.parameter_output_values.clear()
187
-
188
148
  @staticmethod
189
149
  async def collect_values_from_upstream_nodes(context: ResolutionContext) -> None:
190
150
  """Collect output values from resolved upstream nodes and pass them to the current node.
@@ -199,7 +159,7 @@ class ExecuteNodeState(State):
199
159
  """
200
160
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
201
161
 
202
- current_node = context.focus_stack[-1].node
162
+ current_node = context.current_node
203
163
  connections = GriptapeNodes.FlowManager().get_connections()
204
164
 
205
165
  for parameter in current_node.parameters:
@@ -237,14 +197,23 @@ class ExecuteNodeState(State):
237
197
 
238
198
  @staticmethod
239
199
  async def on_enter(context: ResolutionContext) -> type[State] | None:
240
- current_node = context.focus_stack[-1].node
200
+ current_node = context.current_node
241
201
 
242
202
  # Clear all of the current output values
243
203
  # if node is locked, don't clear anything. skip all of this.
204
+ GriptapeNodes.EventManager().put_event(
205
+ ExecutionGriptapeNodeEvent(
206
+ wrapped_event=ExecutionEvent(payload=CurrentDataNodeEvent(node_name=current_node.name))
207
+ )
208
+ )
244
209
  if current_node.lock:
245
210
  return ExecuteNodeState
246
211
  await ExecuteNodeState.collect_values_from_upstream_nodes(context)
247
- await ExecuteNodeState.clear_parameter_output_values(context)
212
+
213
+ # Clear all of the current output values but don't broadcast the clearing.
214
+ # to avoid any flickering in subscribers (UI).
215
+ context.current_node.parameter_output_values.silent_clear()
216
+
248
217
  for parameter in current_node.parameters:
249
218
  if ParameterTypeBuiltin.CONTROL_TYPE.value.lower() == parameter.output_type:
250
219
  continue
@@ -398,14 +367,17 @@ class CompleteState(State):
398
367
  return None
399
368
 
400
369
 
401
- class NodeResolutionMachine(FSM[ResolutionContext]):
370
+ class SequentialResolutionMachine(FSM[ResolutionContext]):
402
371
  """State machine for resolving node dependencies."""
403
372
 
404
373
  def __init__(self) -> None:
405
374
  resolution_context = ResolutionContext()
406
375
  super().__init__(resolution_context)
407
376
 
408
- async def resolve_node(self, node: BaseNode) -> None:
377
+ async def resolve_node(self, node: BaseNode | None = None) -> None:
378
+ if node is None:
379
+ msg = "SequentialResolutionMachine requires a node to resolve"
380
+ raise ValueError(msg)
409
381
  self._context.focus_stack.append(Focus(node=node))
410
382
  await self.start(InitializeSpotlightState)
411
383
 
@@ -418,6 +390,7 @@ class NodeResolutionMachine(FSM[ResolutionContext]):
418
390
  def is_started(self) -> bool:
419
391
  return self._current_state is not None
420
392
 
421
- def reset_machine(self) -> None:
393
+ # Unused argument but necessary for parallel_resolution because of futures ending during cancel but not reset.
394
+ def reset_machine(self, *, cancel: bool = False) -> None: # noqa: ARG002
422
395
  self._context.reset()
423
396
  self._current_state = None
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
  from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple
5
5
 
6
- from pydantic import BaseModel
6
+ from pydantic import BaseModel, Field
7
7
 
8
8
  from griptape_nodes.utils.metaclasses import SingletonMeta
9
9
 
@@ -85,6 +85,9 @@ class Setting(BaseModel):
85
85
  category: str # Name of the category in the config
86
86
  contents: dict[str, Any] # The actual settings content
87
87
  description: str | None = None # Optional description for the setting
88
+ json_schema: dict[str, Any] | None = Field(
89
+ default=None, alias="schema"
90
+ ) # JSON schema for the setting (including enums)
88
91
 
89
92
 
90
93
  class LibrarySchema(BaseModel):
@@ -230,6 +233,36 @@ class LibraryRegistry(metaclass=SingletonMeta):
230
233
  # Ask the library to create the node.
231
234
  return dest_library.create_node(node_type=node_type, name=name, metadata=metadata)
232
235
 
236
+ @classmethod
237
+ def get_all_library_schemas(cls) -> dict[str, dict]:
238
+ """Get schemas from all loaded libraries.
239
+
240
+ Returns:
241
+ Dictionary mapping category names to their JSON Schema dicts
242
+ """
243
+ instance = cls()
244
+ schemas = {}
245
+
246
+ # Get explicit schemas from loaded libraries
247
+ for library in instance._libraries.values():
248
+ library_data = library.get_library_data()
249
+ if library_data.settings:
250
+ for setting in library_data.settings:
251
+ if setting.json_schema:
252
+ schemas[setting.category] = {
253
+ "type": "object",
254
+ "properties": setting.json_schema,
255
+ "title": setting.description or f"{setting.category.title()} Settings",
256
+ }
257
+ else:
258
+ # Create fallback schema for settings without explicit schemas
259
+ schemas[setting.category] = {
260
+ "type": "object",
261
+ "title": setting.description or f"{setting.category.title()} Settings",
262
+ }
263
+
264
+ return schemas
265
+
233
266
 
234
267
  class Library:
235
268
  """A collection of nodes curated by library author.
@@ -1,4 +1,4 @@
1
- from dataclasses import dataclass
1
+ from dataclasses import dataclass, field
2
2
 
3
3
  from griptape_nodes.retained_mode.events.base_events import (
4
4
  AppPayload,
@@ -76,6 +76,10 @@ class AppGetSessionResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
76
76
  class AppInitializationComplete(AppPayload):
77
77
  """Application initialization completed successfully. All subsystems ready."""
78
78
 
79
+ libraries_to_register: list[str] = field(default_factory=list)
80
+ workflows_to_register: list[str] = field(default_factory=list)
81
+ models_to_download: list[str] = field(default_factory=list)
82
+
79
83
 
80
84
  @dataclass
81
85
  @PayloadRegistry.register
@@ -4,7 +4,7 @@ import json
4
4
  import logging
5
5
  from abc import ABC, abstractmethod
6
6
  from dataclasses import asdict, dataclass, field, is_dataclass
7
- from typing import TYPE_CHECKING, Any, ClassVar, Literal, TypeVar
7
+ from typing import TYPE_CHECKING, Any, ClassVar, TypeVar
8
8
 
9
9
  from griptape.artifacts import BaseArtifact
10
10
  from griptape.mixins.serializable_mixin import SerializableMixin
@@ -20,7 +20,7 @@ if TYPE_CHECKING:
20
20
  class ResultDetail:
21
21
  """A single detail about an operation result, including logging level and human readable message."""
22
22
 
23
- level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
23
+ level: int
24
24
  message: str
25
25
 
26
26
 
@@ -34,7 +34,7 @@ class ResultDetails:
34
34
  self,
35
35
  *result_details: ResultDetail,
36
36
  message: str | None = None,
37
- level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None = None,
37
+ level: int | None = None,
38
38
  logger: logging.Logger | str | None = "griptape_nodes",
39
39
  ):
40
40
  """Initialize with ResultDetail objects or create a single one from message/level.
@@ -67,8 +67,8 @@ class ResultDetails:
67
67
  logger = logging.getLogger(logger)
68
68
 
69
69
  for detail in self.result_details:
70
- numeric_level = getattr(logging, detail.level)
71
- logger.log(numeric_level, detail.message)
70
+ # Handle both string and int levels
71
+ logger.log(detail.level, detail.message)
72
72
  except Exception: # noqa: S110
73
73
  # If logging fails for any reason, don't let it break the ResultDetails creation
74
74
  pass
@@ -135,12 +135,12 @@ class SkipTheLineMixin:
135
135
  class ResultPayloadSuccess(ResultPayload, ABC):
136
136
  """Abstract base class for success result payloads."""
137
137
 
138
- result_details: ResultDetails | str = "Success"
138
+ result_details: ResultDetails | str
139
139
 
140
140
  def __post_init__(self) -> None:
141
141
  """Initialize success result with INFO level default for strings."""
142
142
  if isinstance(self.result_details, str):
143
- self.result_details = ResultDetails(message=self.result_details, level="DEBUG")
143
+ self.result_details = ResultDetails(message=self.result_details, level=logging.DEBUG)
144
144
 
145
145
  def succeeded(self) -> bool:
146
146
  """Returns True as this is a success result.
@@ -156,13 +156,13 @@ class ResultPayloadSuccess(ResultPayload, ABC):
156
156
  class ResultPayloadFailure(ResultPayload, ABC):
157
157
  """Abstract base class for failure result payloads."""
158
158
 
159
- result_details: ResultDetails | str = "Failure"
159
+ result_details: ResultDetails | str
160
160
  exception: Exception | None = None
161
161
 
162
162
  def __post_init__(self) -> None:
163
163
  """Initialize failure result with ERROR level default for strings."""
164
164
  if isinstance(self.result_details, str):
165
- self.result_details = ResultDetails(message=self.result_details, level="ERROR")
165
+ self.result_details = ResultDetails(message=self.result_details, level=logging.ERROR)
166
166
 
167
167
  def succeeded(self) -> bool:
168
168
  """Returns False as this is a failure result.
@@ -194,3 +194,33 @@ class ResetConfigResultSuccess(ResultPayloadSuccess):
194
194
  @PayloadRegistry.register
195
195
  class ResetConfigResultFailure(ResultPayloadFailure):
196
196
  """Configuration reset failed. Common causes: file system errors, permission issues, initialization errors."""
197
+
198
+
199
+ @dataclass
200
+ @PayloadRegistry.register
201
+ class GetConfigSchemaRequest(RequestPayload):
202
+ """Get the JSON schema for the configuration model.
203
+
204
+ Use when: Frontend needs to understand field types, enums, and validation rules
205
+ for rendering appropriate UI components (dropdowns, text inputs, etc.).
206
+
207
+ Results: GetConfigSchemaResultSuccess (with schema) | GetConfigSchemaResultFailure (schema generation error)
208
+ """
209
+
210
+
211
+ @dataclass
212
+ @PayloadRegistry.register
213
+ class GetConfigSchemaResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
214
+ """Configuration schema retrieved successfully.
215
+
216
+ Args:
217
+ schema: The JSON schema for the configuration model
218
+ """
219
+
220
+ schema: dict[str, Any]
221
+
222
+
223
+ @dataclass
224
+ @PayloadRegistry.register
225
+ class GetConfigSchemaResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
226
+ """Configuration schema retrieval failed. Common causes: schema generation error, model validation issues."""
@@ -227,8 +227,8 @@ class GetFlowStateResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
227
227
  resolving_node: Name of the node currently being resolved (if any)
228
228
  """
229
229
 
230
- control_node: str | None
231
- resolving_node: str | None
230
+ control_nodes: list[str] | None
231
+ resolving_node: list[str] | None
232
232
 
233
233
 
234
234
  @dataclass
@@ -0,0 +1,296 @@
1
+ from dataclasses import dataclass
2
+
3
+ from griptape_nodes.retained_mode.events.base_events import (
4
+ RequestPayload,
5
+ ResultPayloadFailure,
6
+ ResultPayloadSuccess,
7
+ WorkflowNotAlteredMixin,
8
+ )
9
+ from griptape_nodes.retained_mode.events.payload_registry import PayloadRegistry
10
+
11
+
12
+ @dataclass
13
+ @PayloadRegistry.register
14
+ class DownloadModelRequest(RequestPayload):
15
+ """Download a model from Hugging Face Hub.
16
+
17
+ Use when: Downloading models for local inference, caching models for offline use,
18
+ retrieving specific model versions or files from Hugging Face repositories.
19
+
20
+ Args:
21
+ model_id: Model identifier (e.g., "microsoft/DialoGPT-medium") or full URL to Hugging Face model
22
+ local_dir: Optional local directory to download the model to (defaults to Hugging Face cache)
23
+ repo_type: Type of repository ("model", "dataset", or "space"). Defaults to "model"
24
+ revision: Git revision (branch, tag, or commit hash) to download. Defaults to "main"
25
+ allow_patterns: List of glob patterns to include when downloading. None means all files
26
+ ignore_patterns: List of glob patterns to exclude when downloading
27
+
28
+ Results: DownloadModelResultSuccess (with local_path) | DownloadModelResultFailure (download error)
29
+ """
30
+
31
+ model_id: str
32
+ local_dir: str | None = None
33
+ repo_type: str = "model"
34
+ revision: str = "main"
35
+ allow_patterns: list[str] | None = None
36
+ ignore_patterns: list[str] | None = None
37
+
38
+
39
+ @dataclass
40
+ @PayloadRegistry.register
41
+ class DownloadModelResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
42
+ """Model download completed successfully.
43
+
44
+ Args:
45
+ model_id: The model ID that was downloaded
46
+ repo_info: Additional repository information returned from the download
47
+ """
48
+
49
+ model_id: str
50
+
51
+
52
+ @dataclass
53
+ @PayloadRegistry.register
54
+ class DownloadModelResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
55
+ """Model download failed. Common causes: invalid model ID, network error, authentication required, storage full."""
56
+
57
+
58
+ @dataclass
59
+ class ModelInfo:
60
+ """Information about a model."""
61
+
62
+ model_id: str
63
+ local_path: str | None = None
64
+ size_bytes: int | None = None
65
+ author: str | None = None
66
+ downloads: int | None = None
67
+ likes: int | None = None
68
+ created_at: str | None = None
69
+ updated_at: str | None = None
70
+ task: str | None = None
71
+ library: str | None = None
72
+ tags: list[str] | None = None
73
+
74
+
75
+ @dataclass
76
+ class QueryInfo:
77
+ """Information about a search query."""
78
+
79
+ query: str | None = None
80
+ task: str | None = None
81
+ library: str | None = None
82
+ author: str | None = None
83
+ tags: list[str] | None = None
84
+ limit: int = 20
85
+ sort: str = "downloads"
86
+ direction: str = "desc"
87
+
88
+
89
+ @dataclass
90
+ @PayloadRegistry.register
91
+ class ListModelsRequest(RequestPayload):
92
+ """List all downloaded models from the local cache.
93
+
94
+ Use when: Viewing what models are available locally, checking cache usage,
95
+ managing local model storage.
96
+
97
+ Results: ListModelsResultSuccess (with model list) | ListModelsResultFailure (listing error)
98
+ """
99
+
100
+
101
+ @dataclass
102
+ @PayloadRegistry.register
103
+ class ListModelsResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
104
+ """Model listing completed successfully.
105
+
106
+ Args:
107
+ models: List of model information containing model_id, local_path, size_bytes, etc.
108
+ """
109
+
110
+ models: list[ModelInfo]
111
+
112
+
113
+ @dataclass
114
+ @PayloadRegistry.register
115
+ class ListModelsResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
116
+ """Model listing failed. Common causes: cache directory access error, filesystem error."""
117
+
118
+
119
+ @dataclass
120
+ @PayloadRegistry.register
121
+ class DeleteModelRequest(RequestPayload):
122
+ """Delete a downloaded model from the local cache.
123
+
124
+ Use when: Cleaning up disk space, removing unused models, managing local storage.
125
+
126
+ Args:
127
+ model_id: Model identifier to delete from local cache
128
+
129
+ Results: DeleteModelResultSuccess (deletion confirmed) | DeleteModelResultFailure (deletion error)
130
+ """
131
+
132
+ model_id: str
133
+
134
+
135
+ @dataclass
136
+ @PayloadRegistry.register
137
+ class DeleteModelResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
138
+ """Model deletion completed successfully.
139
+
140
+ Args:
141
+ model_id: The model ID that was deleted
142
+ deleted_path: Local path that was removed
143
+ """
144
+
145
+ model_id: str
146
+ deleted_path: str
147
+
148
+
149
+ @dataclass
150
+ @PayloadRegistry.register
151
+ class DeleteModelResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
152
+ """Model deletion failed. Common causes: model not found, filesystem error, permission denied."""
153
+
154
+
155
+ @dataclass
156
+ @PayloadRegistry.register
157
+ class ListModelDownloadsRequest(RequestPayload):
158
+ """List download status for a specific model or all downloads.
159
+
160
+ Use when: Checking progress of ongoing downloads, viewing download history,
161
+ monitoring download completion.
162
+
163
+ Args:
164
+ model_id: Optional model identifier to get status for. If None, returns all downloads.
165
+
166
+ Results: ListModelDownloadsResultSuccess (with status data) | ListModelDownloadsResultFailure (query error)
167
+ """
168
+
169
+ model_id: str | None = None
170
+
171
+
172
+ @dataclass
173
+ class ModelDownloadStatus:
174
+ """Model download status tracking multiple files."""
175
+
176
+ model_id: str
177
+ status: str # "downloading", "completed", "failed"
178
+ started_at: str
179
+ updated_at: str
180
+ total_files: int | None = None
181
+ completed_files: int | None = None
182
+ failed_files: int | None = None
183
+ # Optional fields for completed downloads
184
+ completed_at: str | None = None
185
+ local_path: str | None = None
186
+ # Optional fields for failed downloads
187
+ failed_at: str | None = None
188
+ error_message: str | None = None
189
+
190
+
191
+ @dataclass
192
+ @PayloadRegistry.register
193
+ class ListModelDownloadsResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
194
+ """Model download status retrieved successfully.
195
+
196
+ Args:
197
+ downloads: List of download status records or single status if model_id was specified
198
+ """
199
+
200
+ downloads: list[ModelDownloadStatus]
201
+
202
+
203
+ @dataclass
204
+ @PayloadRegistry.register
205
+ class ListModelDownloadsResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
206
+ """Model download status query failed. Common causes: filesystem error, invalid model ID."""
207
+
208
+
209
+ @dataclass
210
+ @PayloadRegistry.register
211
+ class DeleteModelDownloadRequest(RequestPayload):
212
+ """Delete download status tracking records for a model.
213
+
214
+ Use when: Cleaning up orphaned download status files, removing tracking data
215
+ for models that are no longer needed.
216
+
217
+ Args:
218
+ model_id: Model identifier to remove download status for
219
+
220
+ Results: DeleteModelDownloadResultSuccess (deletion confirmed) | DeleteModelDownloadResultFailure (deletion error)
221
+ """
222
+
223
+ model_id: str
224
+
225
+
226
+ @dataclass
227
+ @PayloadRegistry.register
228
+ class DeleteModelDownloadResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
229
+ """Model download status deletion completed successfully.
230
+
231
+ Args:
232
+ model_id: The model ID whose download status was deleted
233
+ deleted_path: Path to the status file that was removed
234
+ """
235
+
236
+ model_id: str
237
+ deleted_path: str
238
+
239
+
240
+ @dataclass
241
+ @PayloadRegistry.register
242
+ class DeleteModelDownloadResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
243
+ """Model download status deletion failed. Common causes: status not found, filesystem error, permission denied."""
244
+
245
+
246
+ @dataclass
247
+ @PayloadRegistry.register
248
+ class SearchModelsRequest(RequestPayload):
249
+ """Search for models on Hugging Face Hub.
250
+
251
+ Use when: Finding models by name, filtering models by task or library,
252
+ discovering available models for specific use cases.
253
+
254
+ Args:
255
+ query: Search query string to match against model names and descriptions
256
+ task: Filter by task type (e.g., "text-generation", "image-classification")
257
+ library: Filter by library (e.g., "transformers", "diffusers", "timm")
258
+ author: Filter by author/organization name
259
+ tags: List of tags to filter by
260
+ limit: Maximum number of results to return (default: 20, max: 100)
261
+ sort: Sort results by "downloads", "likes", "updated", or "created" (default: "downloads")
262
+ direction: Sort direction "asc" or "desc" (default: "desc")
263
+
264
+ Results: SearchModelsResultSuccess (with model list) | SearchModelsResultFailure (search error)
265
+ """
266
+
267
+ query: str | None = None
268
+ task: str | None = None
269
+ library: str | None = None
270
+ author: str | None = None
271
+ tags: list[str] | None = None
272
+ limit: int = 20
273
+ sort: str = "downloads"
274
+ direction: str = "desc"
275
+
276
+
277
+ @dataclass
278
+ @PayloadRegistry.register
279
+ class SearchModelsResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
280
+ """Model search completed successfully.
281
+
282
+ Args:
283
+ models: List of model information containing id, author, downloads, etc.
284
+ total_results: Total number of models matching the search criteria
285
+ query_info: Information about the search query parameters used
286
+ """
287
+
288
+ models: list[ModelInfo]
289
+ total_results: int
290
+ query_info: QueryInfo
291
+
292
+
293
+ @dataclass
294
+ @PayloadRegistry.register
295
+ class SearchModelsResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
296
+ """Model search failed. Common causes: network error, invalid parameters, API limits."""
@@ -3,6 +3,7 @@ from enum import Enum, auto
3
3
  from typing import Any, NamedTuple, NewType
4
4
  from uuid import uuid4
5
5
 
6
+ from griptape_nodes.exe_types.core_types import NodeMessagePayload
6
7
  from griptape_nodes.exe_types.node_types import NodeResolutionState
7
8
  from griptape_nodes.node_library.library_registry import LibraryNameAndVersion
8
9
  from griptape_nodes.retained_mode.events.base_events import (
@@ -744,7 +745,7 @@ class SendNodeMessageRequest(RequestPayload):
744
745
  """
745
746
 
746
747
  message_type: str
747
- message: Any
748
+ message: NodeMessagePayload | None
748
749
  node_name: str | None = None
749
750
  optional_element_name: str | None = None
750
751
 
@@ -758,7 +759,7 @@ class SendNodeMessageResultSuccess(ResultPayloadSuccess):
758
759
  response: Optional response data from the node's message handler
759
760
  """
760
761
 
761
- response: Any = None
762
+ response: NodeMessagePayload | None = None
762
763
 
763
764
 
764
765
  @dataclass
@@ -773,7 +774,7 @@ class SendNodeMessageResultFailure(ResultPayloadFailure):
773
774
  response: Optional response data from the node's message handler (even on failure)
774
775
  """
775
776
 
776
- response: Any = None
777
+ response: NodeMessagePayload | None = None
777
778
 
778
779
 
779
780
  @dataclass