griptape-nodes 0.55.1__py3-none-any.whl → 0.56.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.
Files changed (60) hide show
  1. griptape_nodes/app/app.py +10 -15
  2. griptape_nodes/app/watch.py +35 -67
  3. griptape_nodes/bootstrap/utils/__init__.py +1 -0
  4. griptape_nodes/bootstrap/utils/python_subprocess_executor.py +122 -0
  5. griptape_nodes/bootstrap/workflow_executors/local_session_workflow_executor.py +418 -0
  6. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +37 -8
  7. griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +326 -0
  8. griptape_nodes/bootstrap/workflow_executors/utils/__init__.py +1 -0
  9. griptape_nodes/bootstrap/workflow_executors/utils/subprocess_script.py +51 -0
  10. griptape_nodes/bootstrap/workflow_publishers/__init__.py +1 -0
  11. griptape_nodes/bootstrap/workflow_publishers/local_workflow_publisher.py +43 -0
  12. griptape_nodes/bootstrap/workflow_publishers/subprocess_workflow_publisher.py +84 -0
  13. griptape_nodes/bootstrap/workflow_publishers/utils/__init__.py +1 -0
  14. griptape_nodes/bootstrap/workflow_publishers/utils/subprocess_script.py +54 -0
  15. griptape_nodes/cli/commands/engine.py +4 -15
  16. griptape_nodes/cli/commands/init.py +88 -0
  17. griptape_nodes/cli/commands/models.py +2 -0
  18. griptape_nodes/cli/main.py +6 -1
  19. griptape_nodes/cli/shared.py +1 -0
  20. griptape_nodes/exe_types/core_types.py +130 -0
  21. griptape_nodes/exe_types/node_types.py +125 -13
  22. griptape_nodes/machines/control_flow.py +10 -0
  23. griptape_nodes/machines/dag_builder.py +21 -2
  24. griptape_nodes/machines/parallel_resolution.py +25 -10
  25. griptape_nodes/node_library/workflow_registry.py +73 -3
  26. griptape_nodes/retained_mode/events/agent_events.py +2 -0
  27. griptape_nodes/retained_mode/events/base_events.py +18 -17
  28. griptape_nodes/retained_mode/events/execution_events.py +15 -3
  29. griptape_nodes/retained_mode/events/flow_events.py +63 -7
  30. griptape_nodes/retained_mode/events/mcp_events.py +363 -0
  31. griptape_nodes/retained_mode/events/node_events.py +3 -4
  32. griptape_nodes/retained_mode/events/resource_events.py +290 -0
  33. griptape_nodes/retained_mode/events/workflow_events.py +57 -2
  34. griptape_nodes/retained_mode/griptape_nodes.py +17 -1
  35. griptape_nodes/retained_mode/managers/agent_manager.py +67 -4
  36. griptape_nodes/retained_mode/managers/event_manager.py +31 -13
  37. griptape_nodes/retained_mode/managers/flow_manager.py +731 -33
  38. griptape_nodes/retained_mode/managers/library_manager.py +15 -23
  39. griptape_nodes/retained_mode/managers/mcp_manager.py +364 -0
  40. griptape_nodes/retained_mode/managers/model_manager.py +184 -83
  41. griptape_nodes/retained_mode/managers/node_manager.py +15 -4
  42. griptape_nodes/retained_mode/managers/os_manager.py +118 -1
  43. griptape_nodes/retained_mode/managers/resource_components/__init__.py +1 -0
  44. griptape_nodes/retained_mode/managers/resource_components/capability_field.py +41 -0
  45. griptape_nodes/retained_mode/managers/resource_components/comparator.py +18 -0
  46. griptape_nodes/retained_mode/managers/resource_components/resource_instance.py +236 -0
  47. griptape_nodes/retained_mode/managers/resource_components/resource_type.py +79 -0
  48. griptape_nodes/retained_mode/managers/resource_manager.py +306 -0
  49. griptape_nodes/retained_mode/managers/resource_types/__init__.py +1 -0
  50. griptape_nodes/retained_mode/managers/resource_types/cpu_resource.py +108 -0
  51. griptape_nodes/retained_mode/managers/resource_types/os_resource.py +87 -0
  52. griptape_nodes/retained_mode/managers/settings.py +45 -0
  53. griptape_nodes/retained_mode/managers/sync_manager.py +10 -3
  54. griptape_nodes/retained_mode/managers/workflow_manager.py +447 -263
  55. griptape_nodes/traits/multi_options.py +5 -1
  56. griptape_nodes/traits/options.py +10 -2
  57. {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.1.dist-info}/METADATA +2 -2
  58. {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.1.dist-info}/RECORD +60 -37
  59. {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.1.dist-info}/WHEEL +1 -1
  60. {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,108 @@
1
+ import logging
2
+ from typing import TYPE_CHECKING, Any, Literal
3
+
4
+ from griptape_nodes.retained_mode.managers.resource_components.capability_field import (
5
+ CapabilityField,
6
+ validate_capabilities,
7
+ )
8
+ from griptape_nodes.retained_mode.managers.resource_components.resource_instance import ResourceInstance
9
+ from griptape_nodes.retained_mode.managers.resource_components.resource_type import ResourceType
10
+
11
+ if TYPE_CHECKING:
12
+ from griptape_nodes.retained_mode.managers.resource_components.resource_instance import Requirements
13
+
14
+ logger = logging.getLogger("griptape_nodes")
15
+
16
+
17
+ # CPU capability field names
18
+ CPUCapability = Literal[
19
+ "cores",
20
+ "threads",
21
+ "architecture",
22
+ "clock_speed_ghz",
23
+ ]
24
+
25
+
26
+ class CPUInstance(ResourceInstance):
27
+ """Resource instance representing CPU compute resources."""
28
+
29
+ def can_be_freed(self) -> bool:
30
+ """CPU resources can typically be freed when not in use."""
31
+ return True
32
+
33
+ def free(self) -> None:
34
+ """Free CPU resource instance."""
35
+ logger.debug("Freeing CPU resource instance %s", self.get_instance_id())
36
+
37
+ def get_capability_typed(self, key: CPUCapability) -> Any:
38
+ """Type-safe capability getter using Literal types."""
39
+ return self.get_capability_value(key)
40
+
41
+
42
+ class CPUResourceType(ResourceType):
43
+ """Resource type for CPU compute resources."""
44
+
45
+ def get_capability_schema(self) -> list[CapabilityField]:
46
+ """Get the capability schema for CPU resources."""
47
+ return [
48
+ CapabilityField(
49
+ name="cores",
50
+ type_hint=int,
51
+ description="Number of CPU cores",
52
+ required=True,
53
+ ),
54
+ CapabilityField(
55
+ name="threads",
56
+ type_hint=int,
57
+ description="Number of threads (with hyperthreading)",
58
+ required=False,
59
+ ),
60
+ CapabilityField(
61
+ name="architecture",
62
+ type_hint=str,
63
+ description="CPU architecture: 'x86_64', 'arm64', 'aarch64'",
64
+ required=True,
65
+ ),
66
+ CapabilityField(
67
+ name="clock_speed_ghz",
68
+ type_hint=float,
69
+ description="Base clock speed in GHz",
70
+ required=False,
71
+ ),
72
+ ]
73
+
74
+ def create_instance(self, capabilities: dict[str, Any]) -> ResourceInstance:
75
+ """Create a new CPU resource instance."""
76
+ # Validate capabilities against schema
77
+ validation_errors = validate_capabilities(self.get_capability_schema(), capabilities)
78
+ if validation_errors:
79
+ error_msg = f"Invalid CPU capabilities: {', '.join(validation_errors)}"
80
+ raise ValueError(error_msg)
81
+
82
+ return CPUInstance(resource_type=self, instance_id_prefix="cpu", capabilities=capabilities)
83
+
84
+ def select_best_compatible_instance(
85
+ self, compatible_instances: list[ResourceInstance], _requirements: "Requirements | None" = None
86
+ ) -> ResourceInstance | None:
87
+ """Select the best CPU instance from compatible ones.
88
+
89
+ Prioritizes CPUs with:
90
+ 1. More cores
91
+ 2. Higher clock speed
92
+ """
93
+ if not compatible_instances:
94
+ return None
95
+
96
+ def sort_key(instance: ResourceInstance) -> tuple[int, float]:
97
+ # More cores is better
98
+ cores = instance.get_capability_value("cores") if instance.has_capability("cores") else 1
99
+
100
+ # Higher clock speed is better
101
+ clock_speed = (
102
+ instance.get_capability_value("clock_speed_ghz") if instance.has_capability("clock_speed_ghz") else 0
103
+ )
104
+
105
+ return (int(cores), float(clock_speed))
106
+
107
+ sorted_instances = sorted(compatible_instances, key=sort_key, reverse=True)
108
+ return sorted_instances[0]
@@ -0,0 +1,87 @@
1
+ import logging
2
+ from typing import TYPE_CHECKING, Any, Literal
3
+
4
+ from griptape_nodes.retained_mode.managers.resource_components.capability_field import (
5
+ CapabilityField,
6
+ validate_capabilities,
7
+ )
8
+ from griptape_nodes.retained_mode.managers.resource_components.resource_instance import ResourceInstance
9
+ from griptape_nodes.retained_mode.managers.resource_components.resource_type import ResourceType
10
+
11
+ if TYPE_CHECKING:
12
+ from griptape_nodes.retained_mode.managers.resource_components.resource_instance import Requirements
13
+
14
+ logger = logging.getLogger("griptape_nodes")
15
+
16
+
17
+ # OS capability field names
18
+ OSCapability = Literal[
19
+ "platform",
20
+ "arch",
21
+ "version",
22
+ ]
23
+
24
+
25
+ class OSInstance(ResourceInstance):
26
+ """Resource instance representing an operating system environment."""
27
+
28
+ def can_be_freed(self) -> bool:
29
+ """OS resources can be freed when no longer needed."""
30
+ return True
31
+
32
+ def free(self) -> None:
33
+ """Free OS resource instance."""
34
+ logger.debug("Freeing OS resource instance %s", self.get_instance_id())
35
+
36
+ def get_capability_typed(self, key: OSCapability) -> Any:
37
+ """Type-safe capability getter using Literal types."""
38
+ return self.get_capability_value(key)
39
+
40
+
41
+ class OSResourceType(ResourceType):
42
+ """Resource type for operating system environments."""
43
+
44
+ def get_capability_schema(self) -> list[CapabilityField]:
45
+ """Get the capability schema for OS resources."""
46
+ return [
47
+ CapabilityField(
48
+ name="platform",
49
+ type_hint=str,
50
+ description="Operating system platform: 'linux', 'darwin', 'windows'",
51
+ required=True,
52
+ ),
53
+ CapabilityField(
54
+ name="arch",
55
+ type_hint=str,
56
+ description="System architecture: 'x86_64', 'arm64', 'aarch64'",
57
+ required=True,
58
+ ),
59
+ CapabilityField(
60
+ name="version",
61
+ type_hint=str,
62
+ description="Operating system version string",
63
+ required=False,
64
+ ),
65
+ ]
66
+
67
+ def create_instance(self, capabilities: dict[str, Any]) -> ResourceInstance:
68
+ """Create a new OS resource instance."""
69
+ # Validate capabilities against schema
70
+ validation_errors = validate_capabilities(self.get_capability_schema(), capabilities)
71
+ if validation_errors:
72
+ error_msg = f"Invalid OS capabilities: {', '.join(validation_errors)}"
73
+ raise ValueError(error_msg)
74
+
75
+ return OSInstance(resource_type=self, instance_id_prefix="os", capabilities=capabilities)
76
+
77
+ def select_best_compatible_instance(
78
+ self, compatible_instances: list[ResourceInstance], _requirements: "Requirements | None" = None
79
+ ) -> ResourceInstance | None:
80
+ """Select the best OS instance from compatible ones.
81
+
82
+ Returns the first compatible instance (no special selection criteria for now).
83
+ """
84
+ if not compatible_instances:
85
+ return None
86
+
87
+ return compatible_instances[0]
@@ -23,6 +23,7 @@ API_KEYS = Category(name="API Keys", description="API keys and authentication cr
23
23
  EXECUTION = Category(name="Execution", description="Workflow execution and processing settings")
24
24
  STORAGE = Category(name="Storage", description="Data storage and persistence configuration")
25
25
  SYSTEM_REQUIREMENTS = Category(name="System Requirements", description="System resource requirements and limits")
26
+ MCP_SERVERS = Category(name="MCP Servers", description="Model Context Protocol server configurations")
26
27
 
27
28
 
28
29
  def Field(category: str | Category = "General", **kwargs) -> Any:
@@ -56,6 +57,40 @@ class LogLevel(StrEnum):
56
57
  DEBUG = "DEBUG"
57
58
 
58
59
 
60
+ class MCPServerConfig(BaseModel):
61
+ """Configuration for a single MCP server."""
62
+
63
+ name: str = Field(description="Unique name/identifier for the MCP server")
64
+ enabled: bool = Field(default=True, description="Whether this MCP server is enabled")
65
+ transport: str = Field(default="stdio", description="Transport type: stdio, sse, streamable_http, or websocket")
66
+
67
+ # StdioConnection fields
68
+ command: str | None = Field(default=None, description="Command to start the MCP server (required for stdio)")
69
+ args: list[str] = Field(default_factory=list, description="Arguments to pass to the MCP server command (stdio)")
70
+ env: dict[str, str] = Field(default_factory=dict, description="Environment variables for the MCP server (stdio)")
71
+ cwd: str | None = Field(default=None, description="Working directory for the MCP server (stdio)")
72
+ encoding: str = Field(default="utf-8", description="Text encoding for stdio communication")
73
+ encoding_error_handler: str = Field(default="strict", description="Encoding error handler for stdio")
74
+
75
+ # HTTP-based connection fields (sse, streamable_http, websocket)
76
+ url: str | None = Field(
77
+ default=None, description="URL for HTTP-based connections (sse, streamable_http, websocket)"
78
+ )
79
+ headers: dict[str, str] | None = Field(default=None, description="HTTP headers for HTTP-based connections")
80
+ timeout: float | None = Field(default=None, description="HTTP timeout in seconds")
81
+ sse_read_timeout: float | None = Field(default=None, description="SSE read timeout in seconds")
82
+ terminate_on_close: bool = Field(
83
+ default=True, description="Whether to terminate session on close (streamable_http)"
84
+ )
85
+
86
+ # Common fields
87
+ description: str | None = Field(default=None, description="Optional description of what this MCP server provides")
88
+ capabilities: list[str] = Field(default_factory=list, description="List of capabilities this MCP server provides")
89
+
90
+ def __str__(self) -> str:
91
+ return f"{self.name} ({'enabled' if self.enabled else 'disabled'})"
92
+
93
+
59
94
  class AppInitializationComplete(BaseModel):
60
95
  libraries_to_register: list[str] = Field(default_factory=list)
61
96
  workflows_to_register: list[str] = Field(default_factory=list)
@@ -210,3 +245,13 @@ class Settings(BaseModel):
210
245
  default="synced_workflows",
211
246
  description="Path to the synced workflows directory, relative to the workspace directory.",
212
247
  )
248
+ enable_workspace_file_watching: bool = Field(
249
+ category=FILE_SYSTEM,
250
+ default=True,
251
+ description="Enable file watching for synced workflows directory",
252
+ )
253
+ mcp_servers: list[MCPServerConfig] = Field(
254
+ category=MCP_SERVERS,
255
+ default_factory=list,
256
+ description="List of Model Context Protocol server configurations",
257
+ )
@@ -164,8 +164,15 @@ class SyncManager:
164
164
  try:
165
165
  # Check if cloud storage is configured before attempting sync
166
166
  self._get_cloud_storage_driver()
167
- # Start file watching after successful sync
168
- self._start_file_watching()
167
+
168
+ # Check if file watching is enabled before starting it
169
+ enable_file_watching = self._config_manager.get_config_value("enable_workspace_file_watching", default=True)
170
+ if enable_file_watching:
171
+ # Start file watching after successful sync
172
+ self._start_file_watching()
173
+ logger.debug("File watching enabled - started watching synced workflows directory")
174
+ else:
175
+ logger.debug("File watching disabled - skipping file watching startup")
169
176
 
170
177
  logger.info("App initialization complete - starting automatic cloud workflow sync")
171
178
 
@@ -449,7 +456,7 @@ class SyncManager:
449
456
 
450
457
  # Collect results as they complete
451
458
  for future in future_to_filename:
452
- filename, success, error = future.result()
459
+ filename, success, _error = future.result()
453
460
  if success:
454
461
  synced_workflows.append(filename)
455
462
  else: