griptape-nodes 0.57.1__tar.gz → 0.58.0__tar.gz
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-0.57.1 → griptape_nodes-0.58.0}/PKG-INFO +4 -3
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/pyproject.toml +4 -3
- griptape_nodes-0.58.0/src/griptape_nodes/api_client/__init__.py +9 -0
- griptape_nodes-0.58.0/src/griptape_nodes/api_client/client.py +279 -0
- griptape_nodes-0.58.0/src/griptape_nodes/api_client/request_client.py +273 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/app/app.py +57 -150
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/bootstrap/utils/python_subprocess_executor.py +1 -1
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/bootstrap/workflow_executors/local_session_workflow_executor.py +22 -50
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +6 -1
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +27 -46
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/bootstrap/workflow_executors/utils/subprocess_script.py +7 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/bootstrap/workflow_publishers/local_workflow_publisher.py +3 -1
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/bootstrap/workflow_publishers/subprocess_workflow_publisher.py +3 -1
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/bootstrap/workflow_publishers/utils/subprocess_script.py +16 -1
- griptape_nodes-0.58.0/src/griptape_nodes/common/node_executor.py +466 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/drivers/storage/base_storage_driver.py +0 -11
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +7 -25
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/drivers/storage/local_storage_driver.py +2 -2
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/exe_types/connections.py +37 -9
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/exe_types/core_types.py +1 -1
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/exe_types/node_types.py +115 -22
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/machines/control_flow.py +48 -7
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/machines/parallel_resolution.py +98 -29
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/machines/sequential_resolution.py +61 -22
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/node_library/library_registry.py +24 -1
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/node_library/workflow_registry.py +38 -2
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/execution_events.py +8 -1
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/flow_events.py +90 -3
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/node_events.py +17 -10
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/workflow_events.py +5 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/griptape_nodes.py +16 -219
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/config_manager.py +0 -46
- griptape_nodes-0.58.0/src/griptape_nodes/retained_mode/managers/engine_identity_manager.py +297 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/flow_manager.py +1276 -230
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/library_manager.py +7 -8
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/node_manager.py +197 -9
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/secrets_manager.py +26 -0
- griptape_nodes-0.58.0/src/griptape_nodes/retained_mode/managers/session_manager.py +365 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/settings.py +4 -38
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/static_files_manager.py +3 -3
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/version_compatibility_manager.py +135 -6
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/workflow_manager.py +206 -78
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/servers/mcp.py +23 -15
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/utils/async_utils.py +36 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/utils/dict_utils.py +8 -2
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +11 -6
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/version_compatibility/workflow_versions/v0_7_0/local_executor_argument_addition.py +12 -5
- griptape_nodes-0.57.1/src/griptape_nodes/retained_mode/managers/engine_identity_manager.py +0 -146
- griptape_nodes-0.57.1/src/griptape_nodes/retained_mode/managers/session_manager.py +0 -328
- griptape_nodes-0.57.1/src/griptape_nodes/retained_mode/utils/engine_identity.py +0 -245
- griptape_nodes-0.57.1/src/griptape_nodes/servers/ws_request_manager.py +0 -268
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/README.md +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/__main__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/app/.python-version +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/app/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/app/watch.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/bootstrap/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/bootstrap/utils/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/bootstrap/workflow_executors/utils/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/bootstrap/workflow_publishers/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/bootstrap/workflow_publishers/utils/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/cli/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/cli/commands/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/cli/commands/config.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/cli/commands/engine.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/cli/commands/init.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/cli/commands/libraries.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/cli/commands/models.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/cli/commands/self.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/cli/main.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/cli/shared.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/common/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/common/directed_graph.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/drivers/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/drivers/storage/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/drivers/storage/storage_backend.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/exe_types/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/exe_types/flow.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/exe_types/param_components/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/exe_types/param_components/execution_status_component.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/exe_types/type_validator.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/machines/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/machines/dag_builder.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/machines/fsm.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/mcp_server/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/node_library/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/node_library/advanced_node_library.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/py.typed +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/agent_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/app_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/arbitrary_python_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/base_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/config_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/connection_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/context_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/library_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/logger_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/mcp_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/model_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/object_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/os_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/parameter_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/payload_registry.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/resource_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/secrets_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/static_file_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/sync_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/validation_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/events/variable_events.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/agent_manager.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/context_manager.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/event_manager.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/mcp_manager.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/model_manager.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/object_manager.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/os_manager.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/resource_components/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/resource_components/capability_field.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/resource_components/comparator.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/resource_components/resource_instance.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/resource_components/resource_type.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/resource_manager.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/resource_types/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/resource_types/cpu_resource.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/resource_types/os_resource.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/sync_manager.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/managers/variable_manager.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/retained_mode.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/utils/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/utils/name_generator.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/retained_mode/variable_types.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/servers/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/servers/static.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/traits/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/traits/add_param_button.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/traits/button.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/traits/clamp.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/traits/color_picker.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/traits/compare.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/traits/compare_images.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/traits/file_system_picker.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/traits/minmax.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/traits/multi_options.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/traits/numbers_selector.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/traits/options.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/traits/slider.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/traits/trait_registry.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/traits/traits.json +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/updater/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/updater/__main__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/utils/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/utils/image_preview.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/utils/metaclasses.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/utils/uv_utils.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/utils/version_utils.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/version_compatibility/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/version_compatibility/versions/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/version_compatibility/workflow_versions/__init__.py +0 -0
- {griptape_nodes-0.57.1 → griptape_nodes-0.58.0}/src/griptape_nodes/version_compatibility/workflow_versions/v0_7_0/__init__.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: griptape-nodes
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.58.0
|
|
4
4
|
Summary: Add your description here
|
|
5
|
-
Requires-Dist: griptape>=1.8.
|
|
5
|
+
Requires-Dist: griptape>=1.8.5
|
|
6
6
|
Requires-Dist: pydantic>=2.10.6
|
|
7
7
|
Requires-Dist: python-dotenv>=1.0.1
|
|
8
8
|
Requires-Dist: xdg-base-dirs>=6.0.2
|
|
@@ -20,8 +20,9 @@ Requires-Dist: binaryornot>=0.4.4
|
|
|
20
20
|
Requires-Dist: pillow>=11.3.0
|
|
21
21
|
Requires-Dist: watchfiles>=1.1.0
|
|
22
22
|
Requires-Dist: typer>=0.15.0
|
|
23
|
-
Requires-Dist: huggingface-hub>=0.28.0
|
|
23
|
+
Requires-Dist: huggingface-hub[xf-xet]>=0.28.0
|
|
24
24
|
Requires-Dist: rich>=14.1.0
|
|
25
|
+
Requires-Dist: semver>=3.0.4
|
|
25
26
|
Requires-Dist: austin-dist>=3.7.0 ; extra == 'profiling'
|
|
26
27
|
Requires-Python: >=3.12.0, <3.13
|
|
27
28
|
Provides-Extra: profiling
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "griptape-nodes"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.58.0"
|
|
4
4
|
description = "Add your description here"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12.0, <3.13"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"griptape>=1.8.
|
|
8
|
+
"griptape>=1.8.5",
|
|
9
9
|
"pydantic>=2.10.6",
|
|
10
10
|
"python-dotenv>=1.0.1",
|
|
11
11
|
"xdg-base-dirs>=6.0.2",
|
|
@@ -24,8 +24,9 @@ dependencies = [
|
|
|
24
24
|
"pillow>=11.3.0",
|
|
25
25
|
"watchfiles>=1.1.0",
|
|
26
26
|
"typer>=0.15.0",
|
|
27
|
-
"huggingface-hub>=0.28.0",
|
|
27
|
+
"huggingface-hub[xf_xet]>=0.28.0",
|
|
28
28
|
"rich>=14.1.0",
|
|
29
|
+
"semver>=3.0.4",
|
|
29
30
|
]
|
|
30
31
|
|
|
31
32
|
[project.optional-dependencies]
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""Unified WebSocket client for Nodes API communication."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import contextlib
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
11
|
+
from urllib.parse import urljoin
|
|
12
|
+
|
|
13
|
+
from websockets.asyncio.client import connect
|
|
14
|
+
from websockets.exceptions import ConnectionClosed
|
|
15
|
+
|
|
16
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from collections.abc import AsyncIterator
|
|
20
|
+
from types import TracebackType
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("griptape_nodes_client")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_default_websocket_url() -> str:
|
|
26
|
+
"""Get the default WebSocket endpoint URL for connecting to Nodes API.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
WebSocket URL for Nodes API events endpoint
|
|
30
|
+
"""
|
|
31
|
+
return urljoin(
|
|
32
|
+
os.getenv("GRIPTAPE_NODES_API_BASE_URL", "https://api.nodes.griptape.ai").replace("http", "ws"),
|
|
33
|
+
"/ws/engines/events?version=v2",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Client:
|
|
38
|
+
"""WebSocket client for Nodes API pub/sub communication.
|
|
39
|
+
|
|
40
|
+
Provides connection management, topic-based pub/sub, and message routing.
|
|
41
|
+
Handles WebSocket reconnection and async event streaming.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
api_key: str | None = None,
|
|
47
|
+
url: str | None = None,
|
|
48
|
+
):
|
|
49
|
+
"""Initialize Nodes API client.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
api_key: API key for authentication (defaults to GT_CLOUD_API_KEY from SecretsManager)
|
|
53
|
+
url: WebSocket URL to connect to (defaults to Nodes API endpoint)
|
|
54
|
+
"""
|
|
55
|
+
self.url = url if url is not None else get_default_websocket_url()
|
|
56
|
+
|
|
57
|
+
# Get API key from SecretsManager if not provided
|
|
58
|
+
if api_key is None:
|
|
59
|
+
api_key = GriptapeNodes.SecretsManager().get_secret("GT_CLOUD_API_KEY")
|
|
60
|
+
|
|
61
|
+
self.api_key = api_key
|
|
62
|
+
|
|
63
|
+
self.headers = {"Authorization": f"Bearer {api_key}"} if api_key else {}
|
|
64
|
+
|
|
65
|
+
# Event streaming management
|
|
66
|
+
self._message_queue: asyncio.Queue = asyncio.Queue()
|
|
67
|
+
self._subscribed_topics: set[str] = set()
|
|
68
|
+
self._receiving_task: asyncio.Task | None = None
|
|
69
|
+
self._sending_task: asyncio.Task | None = None
|
|
70
|
+
self._websocket: Any = None
|
|
71
|
+
self._connection_ready = asyncio.Event()
|
|
72
|
+
self._reconnect_delay = 2.0
|
|
73
|
+
|
|
74
|
+
async def __aenter__(self) -> Self:
|
|
75
|
+
"""Async context manager entry: connect to WebSocket server."""
|
|
76
|
+
await self._connect()
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
async def __aexit__(
|
|
80
|
+
self,
|
|
81
|
+
exc_type: type[BaseException] | None,
|
|
82
|
+
exc_val: BaseException | None,
|
|
83
|
+
exc_tb: TracebackType | None,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Async context manager exit: disconnect from WebSocket server."""
|
|
86
|
+
await self._disconnect()
|
|
87
|
+
|
|
88
|
+
def __aiter__(self) -> AsyncIterator[dict[str, Any]]:
|
|
89
|
+
"""Return self as async iterator."""
|
|
90
|
+
return self
|
|
91
|
+
|
|
92
|
+
async def __anext__(self) -> dict[str, Any]:
|
|
93
|
+
"""Get next message from the message queue.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Next message dictionary from subscribed topics
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
StopAsyncIteration: When iteration is cancelled
|
|
100
|
+
"""
|
|
101
|
+
try:
|
|
102
|
+
return await self._message_queue.get()
|
|
103
|
+
except asyncio.CancelledError:
|
|
104
|
+
raise StopAsyncIteration from None
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def messages(self) -> AsyncIterator[dict[str, Any]]:
|
|
108
|
+
"""Async iterator for receiving messages from subscribed topics.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Async iterator yielding message dictionaries
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
async with Client(...) as client:
|
|
115
|
+
await client.subscribe("topic")
|
|
116
|
+
async for message in client.messages:
|
|
117
|
+
print(message)
|
|
118
|
+
"""
|
|
119
|
+
return self
|
|
120
|
+
|
|
121
|
+
async def subscribe(self, topic: str) -> None:
|
|
122
|
+
"""Subscribe to a topic by sending subscribe command to server.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
topic: Topic name to subscribe to
|
|
126
|
+
|
|
127
|
+
Example:
|
|
128
|
+
await client.subscribe("sessions/123/response")
|
|
129
|
+
"""
|
|
130
|
+
self._subscribed_topics.add(topic)
|
|
131
|
+
await self._send_subscribe_command(topic)
|
|
132
|
+
|
|
133
|
+
async def unsubscribe(self, topic: str) -> None:
|
|
134
|
+
"""Unsubscribe from a topic.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
topic: Topic name to unsubscribe from
|
|
138
|
+
"""
|
|
139
|
+
self._subscribed_topics.discard(topic)
|
|
140
|
+
await self._send_unsubscribe_command(topic)
|
|
141
|
+
|
|
142
|
+
async def publish(self, event_type: str, payload: dict[str, Any], topic: str) -> None:
|
|
143
|
+
"""Publish an event to the server.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
event_type: Type of event to publish
|
|
147
|
+
payload: Event payload data
|
|
148
|
+
topic: Topic to publish to
|
|
149
|
+
"""
|
|
150
|
+
message = {"type": event_type, "payload": payload, "topic": topic}
|
|
151
|
+
await self._send_message(message)
|
|
152
|
+
|
|
153
|
+
async def _connect(self) -> None:
|
|
154
|
+
"""Connect to the WebSocket server and start receiving messages.
|
|
155
|
+
|
|
156
|
+
This method starts the connection manager task.
|
|
157
|
+
It returns once the initial connection is established.
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
ConnectionError: If connection fails
|
|
161
|
+
"""
|
|
162
|
+
# Start connection manager task
|
|
163
|
+
self._receiving_task = asyncio.create_task(self._manage_connection())
|
|
164
|
+
|
|
165
|
+
# Wait for initial connection to be established
|
|
166
|
+
try:
|
|
167
|
+
await asyncio.wait_for(self._connection_ready.wait(), timeout=10.0)
|
|
168
|
+
logger.debug("WebSocket client connected")
|
|
169
|
+
except TimeoutError as e:
|
|
170
|
+
logger.error("Failed to connect WebSocket client: timeout")
|
|
171
|
+
msg = "Connection timeout"
|
|
172
|
+
raise ConnectionError(msg) from e
|
|
173
|
+
|
|
174
|
+
async def _disconnect(self) -> None:
|
|
175
|
+
"""Disconnect from the WebSocket server and clean up tasks."""
|
|
176
|
+
# Cancel tasks
|
|
177
|
+
if self._receiving_task:
|
|
178
|
+
self._receiving_task.cancel()
|
|
179
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
180
|
+
await self._receiving_task
|
|
181
|
+
|
|
182
|
+
if self._sending_task:
|
|
183
|
+
self._sending_task.cancel()
|
|
184
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
185
|
+
await self._sending_task
|
|
186
|
+
|
|
187
|
+
# Close websocket connection
|
|
188
|
+
if self._websocket:
|
|
189
|
+
await self._websocket.close()
|
|
190
|
+
logger.info("WebSocket client disconnected")
|
|
191
|
+
|
|
192
|
+
async def _manage_connection(self) -> None:
|
|
193
|
+
"""Manage WebSocket connection lifecycle with automatic reconnection.
|
|
194
|
+
|
|
195
|
+
This method establishes and maintains the WebSocket connection,
|
|
196
|
+
automatically reconnecting on failures.
|
|
197
|
+
"""
|
|
198
|
+
try:
|
|
199
|
+
async for websocket in connect(self.url, additional_headers=self.headers):
|
|
200
|
+
self._websocket = websocket
|
|
201
|
+
self._connection_ready.set()
|
|
202
|
+
logger.debug("WebSocket connection established: %s", self.url)
|
|
203
|
+
|
|
204
|
+
# Resubscribe to all topics after reconnection
|
|
205
|
+
if self._subscribed_topics:
|
|
206
|
+
logger.debug("Resubscribing to %d topics after reconnection", len(self._subscribed_topics))
|
|
207
|
+
for topic in self._subscribed_topics:
|
|
208
|
+
await self._send_subscribe_command(topic)
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
await self._receive_messages(websocket)
|
|
212
|
+
except ConnectionClosed:
|
|
213
|
+
logger.info("WebSocket connection closed, reconnecting...")
|
|
214
|
+
self._connection_ready.clear()
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
except asyncio.CancelledError:
|
|
218
|
+
logger.debug("Connection manager task cancelled")
|
|
219
|
+
|
|
220
|
+
async def _receive_messages(self, websocket: Any) -> None:
|
|
221
|
+
"""Receive messages from WebSocket and put them in message queue.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
websocket: WebSocket connection to receive messages from
|
|
225
|
+
|
|
226
|
+
Raises:
|
|
227
|
+
ConnectionClosed: When the WebSocket connection is closed
|
|
228
|
+
"""
|
|
229
|
+
try:
|
|
230
|
+
async for message in websocket:
|
|
231
|
+
try:
|
|
232
|
+
data = json.loads(message)
|
|
233
|
+
await self._message_queue.put(data)
|
|
234
|
+
except json.JSONDecodeError:
|
|
235
|
+
logger.error("Failed to parse message: %s", message)
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.error("Error receiving message: %s", e)
|
|
238
|
+
except asyncio.CancelledError:
|
|
239
|
+
logger.debug("Receive messages task cancelled")
|
|
240
|
+
raise
|
|
241
|
+
|
|
242
|
+
async def _send_message(self, message: dict[str, Any]) -> None:
|
|
243
|
+
"""Send a message through the WebSocket connection.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
message: Message dictionary to send
|
|
247
|
+
|
|
248
|
+
Raises:
|
|
249
|
+
ConnectionError: If not connected
|
|
250
|
+
"""
|
|
251
|
+
if not self._websocket:
|
|
252
|
+
msg = "Not connected to WebSocket"
|
|
253
|
+
raise ConnectionError(msg)
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
await self._websocket.send(json.dumps(message))
|
|
257
|
+
logger.debug("Sent message type: %s", message.get("type"))
|
|
258
|
+
except Exception as e:
|
|
259
|
+
logger.error("Failed to send message: %s", e)
|
|
260
|
+
|
|
261
|
+
async def _send_subscribe_command(self, topic: str) -> None:
|
|
262
|
+
"""Send subscribe command to server.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
topic: Topic to subscribe to
|
|
266
|
+
"""
|
|
267
|
+
message = {"type": "subscribe", "topic": topic, "payload": {}}
|
|
268
|
+
await self._send_message(message)
|
|
269
|
+
logger.debug("Sent subscribe command for topic: %s", topic)
|
|
270
|
+
|
|
271
|
+
async def _send_unsubscribe_command(self, topic: str) -> None:
|
|
272
|
+
"""Send unsubscribe command to server.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
topic: Topic to unsubscribe from
|
|
276
|
+
"""
|
|
277
|
+
message = {"type": "unsubscribe", "topic": topic, "payload": {}}
|
|
278
|
+
await self._send_message(message)
|
|
279
|
+
logger.debug("Sent unsubscribe command for topic: %s", topic)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""Request/response tracking with futures and timeouts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import contextlib
|
|
7
|
+
import logging
|
|
8
|
+
import uuid
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Self, TypeVar
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import Callable
|
|
13
|
+
from types import TracebackType
|
|
14
|
+
|
|
15
|
+
from griptape_nodes.api_client.client import Client
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
T = TypeVar("T")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RequestClient:
|
|
23
|
+
"""Request/response client built on top of Client.
|
|
24
|
+
|
|
25
|
+
Wraps a Client to provide request/response semantics on top of
|
|
26
|
+
pub/sub messaging. Tracks pending requests by request_id and resolves/rejects
|
|
27
|
+
futures when responses arrive. Supports timeouts for requests that don't
|
|
28
|
+
receive responses.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
client: Client,
|
|
34
|
+
request_topic_fn: Callable[[], str] | None = None,
|
|
35
|
+
response_topic_fn: Callable[[], str] | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Initialize request/response client.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
client: Client instance to use for communication
|
|
41
|
+
request_topic_fn: Function to determine request topic (defaults to "request")
|
|
42
|
+
response_topic_fn: Function to determine response topic (defaults to "response")
|
|
43
|
+
"""
|
|
44
|
+
self.client = client
|
|
45
|
+
self.request_topic_fn = request_topic_fn or (lambda: "request")
|
|
46
|
+
self.response_topic_fn = response_topic_fn or (lambda: "response")
|
|
47
|
+
|
|
48
|
+
# Map of request_id -> Future that will be resolved when response arrives
|
|
49
|
+
self._pending_requests: dict[str, asyncio.Future] = {}
|
|
50
|
+
self._lock = asyncio.Lock()
|
|
51
|
+
|
|
52
|
+
# Track subscribed response topics
|
|
53
|
+
self._subscribed_response_topics: set[str] = set()
|
|
54
|
+
|
|
55
|
+
# Background task for listening to responses
|
|
56
|
+
self._response_listener_task: asyncio.Task | None = None
|
|
57
|
+
|
|
58
|
+
async def __aenter__(self) -> Self:
|
|
59
|
+
"""Async context manager entry: start response listener."""
|
|
60
|
+
self._response_listener_task = asyncio.create_task(self._listen_for_responses())
|
|
61
|
+
logger.debug("RequestClient started")
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
async def __aexit__(
|
|
65
|
+
self,
|
|
66
|
+
exc_type: type[BaseException] | None,
|
|
67
|
+
exc_val: BaseException | None,
|
|
68
|
+
exc_tb: TracebackType | None,
|
|
69
|
+
) -> None:
|
|
70
|
+
"""Async context manager exit: stop response listener."""
|
|
71
|
+
if self._response_listener_task:
|
|
72
|
+
self._response_listener_task.cancel()
|
|
73
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
74
|
+
await self._response_listener_task
|
|
75
|
+
logger.debug("RequestClient stopped")
|
|
76
|
+
|
|
77
|
+
async def request(
|
|
78
|
+
self, request_type: str, payload: dict[str, Any], timeout_ms: int | None = None
|
|
79
|
+
) -> dict[str, Any]:
|
|
80
|
+
"""Send a request and wait for its response.
|
|
81
|
+
|
|
82
|
+
This method automatically:
|
|
83
|
+
- Generates a request_id
|
|
84
|
+
- Determines request and response topics
|
|
85
|
+
- Subscribes to response topic if needed
|
|
86
|
+
- Sends the request
|
|
87
|
+
- Waits for and returns the response
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
request_type: Type of request to send
|
|
91
|
+
payload: Request payload data
|
|
92
|
+
timeout_ms: Optional timeout in milliseconds
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Response data from the server
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
TimeoutError: If request times out
|
|
99
|
+
Exception: If request fails
|
|
100
|
+
"""
|
|
101
|
+
# Generate request ID and track it
|
|
102
|
+
request_id = str(uuid.uuid4())
|
|
103
|
+
payload["request_id"] = request_id
|
|
104
|
+
|
|
105
|
+
response_future = await self._track_request(request_id)
|
|
106
|
+
|
|
107
|
+
# Determine topics
|
|
108
|
+
request_topic = self.request_topic_fn()
|
|
109
|
+
response_topic = self.response_topic_fn()
|
|
110
|
+
|
|
111
|
+
# Subscribe to response topic if not already subscribed
|
|
112
|
+
if response_topic not in self._subscribed_response_topics:
|
|
113
|
+
await self.client.subscribe(response_topic)
|
|
114
|
+
self._subscribed_response_topics.add(response_topic)
|
|
115
|
+
|
|
116
|
+
# Send the request as an EventRequest
|
|
117
|
+
event_payload = {"event_type": "EventRequest", "request_type": request_type, "request": payload}
|
|
118
|
+
|
|
119
|
+
logger.debug("Sending request %s: %s", request_id, request_type)
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
await self.client.publish("EventRequest", event_payload, request_topic)
|
|
123
|
+
|
|
124
|
+
# Wait for response with optional timeout
|
|
125
|
+
if timeout_ms:
|
|
126
|
+
timeout_sec = timeout_ms / 1000
|
|
127
|
+
result = await asyncio.wait_for(response_future, timeout=timeout_sec)
|
|
128
|
+
else:
|
|
129
|
+
result = await response_future
|
|
130
|
+
|
|
131
|
+
except TimeoutError:
|
|
132
|
+
logger.error("Request %s timed out", request_id)
|
|
133
|
+
await self._cancel_request(request_id)
|
|
134
|
+
raise
|
|
135
|
+
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error("Request %s failed: %s", request_id, e)
|
|
138
|
+
await self._cancel_request(request_id)
|
|
139
|
+
raise
|
|
140
|
+
else:
|
|
141
|
+
logger.debug("Request %s completed successfully", request_id)
|
|
142
|
+
return result
|
|
143
|
+
|
|
144
|
+
async def _track_request(self, request_id: str) -> asyncio.Future:
|
|
145
|
+
"""Start tracking a request and return a future that will be resolved on response.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
request_id: Unique identifier for this request
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Future that will be resolved when response arrives
|
|
152
|
+
|
|
153
|
+
Raises:
|
|
154
|
+
ValueError: If request_id is already being tracked
|
|
155
|
+
"""
|
|
156
|
+
async with self._lock:
|
|
157
|
+
if request_id in self._pending_requests:
|
|
158
|
+
msg = f"Request ID already exists: {request_id}"
|
|
159
|
+
raise ValueError(msg)
|
|
160
|
+
|
|
161
|
+
future: asyncio.Future = asyncio.Future()
|
|
162
|
+
self._pending_requests[request_id] = future
|
|
163
|
+
logger.debug("Tracking request: %s", request_id)
|
|
164
|
+
return future
|
|
165
|
+
|
|
166
|
+
async def _resolve_request(self, request_id: str, result: Any) -> None:
|
|
167
|
+
"""Mark a request as successful and resolve its future with a result.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
request_id: Request identifier
|
|
171
|
+
result: Result data to return to the requester
|
|
172
|
+
"""
|
|
173
|
+
async with self._lock:
|
|
174
|
+
future = self._pending_requests.pop(request_id, None)
|
|
175
|
+
|
|
176
|
+
if future is None:
|
|
177
|
+
logger.warning("Received response for unknown request: %s", request_id)
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
if not future.done():
|
|
181
|
+
future.set_result(result)
|
|
182
|
+
logger.debug("Resolved request: %s", request_id)
|
|
183
|
+
|
|
184
|
+
async def _reject_request(self, request_id: str, error: Exception) -> None:
|
|
185
|
+
"""Mark a request as failed and reject its future with an exception.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
request_id: Request identifier
|
|
189
|
+
error: Exception to raise for the requester
|
|
190
|
+
"""
|
|
191
|
+
async with self._lock:
|
|
192
|
+
future = self._pending_requests.pop(request_id, None)
|
|
193
|
+
|
|
194
|
+
if future is None:
|
|
195
|
+
logger.warning("Received error for unknown request: %s", request_id)
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
if not future.done():
|
|
199
|
+
future.set_exception(error)
|
|
200
|
+
logger.debug("Rejected request: %s with error: %s", request_id, error)
|
|
201
|
+
|
|
202
|
+
async def _cancel_request(self, request_id: str) -> None:
|
|
203
|
+
"""Cancel a pending request and clean up its tracking.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
request_id: Request identifier
|
|
207
|
+
"""
|
|
208
|
+
async with self._lock:
|
|
209
|
+
future = self._pending_requests.pop(request_id, None)
|
|
210
|
+
|
|
211
|
+
if future is None:
|
|
212
|
+
logger.debug("Request already completed or unknown: %s", request_id)
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
if not future.done():
|
|
216
|
+
future.cancel()
|
|
217
|
+
logger.debug("Cancelled request: %s", request_id)
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def pending_count(self) -> int:
|
|
221
|
+
"""Get number of currently pending requests.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Count of pending requests
|
|
225
|
+
"""
|
|
226
|
+
return len(self._pending_requests)
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def pending_request_ids(self) -> list[str]:
|
|
230
|
+
"""Get list of all pending request IDs.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
List of request_id strings
|
|
234
|
+
"""
|
|
235
|
+
return list(self._pending_requests.keys())
|
|
236
|
+
|
|
237
|
+
async def _listen_for_responses(self) -> None:
|
|
238
|
+
"""Listen for response messages from subscribed topics."""
|
|
239
|
+
try:
|
|
240
|
+
async for message in self.client.messages:
|
|
241
|
+
try:
|
|
242
|
+
await self._handle_response(message)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logger.error("Error handling response message: %s", e)
|
|
245
|
+
except asyncio.CancelledError:
|
|
246
|
+
logger.debug("Response listener cancelled")
|
|
247
|
+
raise
|
|
248
|
+
|
|
249
|
+
async def _handle_response(self, message: dict[str, Any]) -> None:
|
|
250
|
+
"""Handle response messages by resolving tracked requests.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
message: WebSocket message containing response
|
|
254
|
+
"""
|
|
255
|
+
message_type = message.get("type")
|
|
256
|
+
|
|
257
|
+
# Only handle success/failure result messages
|
|
258
|
+
if message_type not in ("success_result", "failure_result"):
|
|
259
|
+
return
|
|
260
|
+
|
|
261
|
+
payload = message.get("payload", {})
|
|
262
|
+
request_id = payload.get("request", {}).get("request_id")
|
|
263
|
+
|
|
264
|
+
if not request_id:
|
|
265
|
+
logger.debug("Response message has no request_id")
|
|
266
|
+
return
|
|
267
|
+
|
|
268
|
+
if message_type == "success_result":
|
|
269
|
+
result = payload.get("result", "Success")
|
|
270
|
+
await self._resolve_request(request_id, result)
|
|
271
|
+
else:
|
|
272
|
+
error_msg = payload.get("result", {}).get("exception", "Unknown error") or "Unknown error"
|
|
273
|
+
await self._reject_request(request_id, Exception(error_msg))
|