mcp-mesh 0.7.21__py3-none-any.whl → 0.8.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 (124) hide show
  1. _mcp_mesh/__init__.py +1 -1
  2. _mcp_mesh/engine/dependency_injector.py +13 -15
  3. _mcp_mesh/engine/http_wrapper.py +69 -10
  4. _mcp_mesh/engine/mesh_llm_agent.py +29 -10
  5. _mcp_mesh/engine/mesh_llm_agent_injector.py +77 -41
  6. _mcp_mesh/engine/provider_handlers/__init__.py +14 -1
  7. _mcp_mesh/engine/provider_handlers/base_provider_handler.py +114 -8
  8. _mcp_mesh/engine/provider_handlers/claude_handler.py +15 -57
  9. _mcp_mesh/engine/provider_handlers/gemini_handler.py +181 -0
  10. _mcp_mesh/engine/provider_handlers/openai_handler.py +8 -63
  11. _mcp_mesh/engine/provider_handlers/provider_handler_registry.py +16 -10
  12. _mcp_mesh/engine/response_parser.py +61 -15
  13. _mcp_mesh/engine/signature_analyzer.py +58 -68
  14. _mcp_mesh/engine/unified_mcp_proxy.py +19 -35
  15. _mcp_mesh/pipeline/__init__.py +9 -20
  16. _mcp_mesh/pipeline/api_heartbeat/__init__.py +12 -7
  17. _mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py +23 -49
  18. _mcp_mesh/pipeline/api_heartbeat/rust_api_heartbeat.py +429 -0
  19. _mcp_mesh/pipeline/api_startup/api_pipeline.py +7 -9
  20. _mcp_mesh/pipeline/api_startup/api_server_setup.py +91 -70
  21. _mcp_mesh/pipeline/api_startup/fastapi_discovery.py +22 -23
  22. _mcp_mesh/pipeline/api_startup/middleware_integration.py +32 -24
  23. _mcp_mesh/pipeline/api_startup/route_collection.py +2 -4
  24. _mcp_mesh/pipeline/mcp_heartbeat/__init__.py +5 -17
  25. _mcp_mesh/pipeline/mcp_heartbeat/rust_heartbeat.py +710 -0
  26. _mcp_mesh/pipeline/mcp_startup/__init__.py +2 -5
  27. _mcp_mesh/pipeline/mcp_startup/configuration.py +1 -1
  28. _mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +31 -8
  29. _mcp_mesh/pipeline/mcp_startup/heartbeat_loop.py +6 -7
  30. _mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +23 -11
  31. _mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +3 -8
  32. _mcp_mesh/pipeline/shared/mesh_pipeline.py +0 -2
  33. _mcp_mesh/reload.py +1 -3
  34. _mcp_mesh/shared/__init__.py +2 -8
  35. _mcp_mesh/shared/config_resolver.py +124 -80
  36. _mcp_mesh/shared/defaults.py +89 -14
  37. _mcp_mesh/shared/fastapi_middleware_manager.py +149 -91
  38. _mcp_mesh/shared/host_resolver.py +8 -46
  39. _mcp_mesh/shared/server_discovery.py +115 -86
  40. _mcp_mesh/shared/simple_shutdown.py +44 -86
  41. _mcp_mesh/tracing/execution_tracer.py +2 -6
  42. _mcp_mesh/tracing/redis_metadata_publisher.py +24 -79
  43. _mcp_mesh/tracing/trace_context_helper.py +3 -13
  44. _mcp_mesh/tracing/utils.py +29 -15
  45. _mcp_mesh/utils/fastmcp_schema_extractor.py +5 -4
  46. {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0.dist-info}/METADATA +7 -5
  47. mcp_mesh-0.8.0.dist-info/RECORD +85 -0
  48. mesh/__init__.py +12 -1
  49. mesh/decorators.py +248 -33
  50. mesh/helpers.py +52 -0
  51. mesh/types.py +40 -13
  52. _mcp_mesh/generated/.openapi-generator/FILES +0 -50
  53. _mcp_mesh/generated/.openapi-generator/VERSION +0 -1
  54. _mcp_mesh/generated/.openapi-generator-ignore +0 -15
  55. _mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +0 -90
  56. _mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py +0 -6
  57. _mcp_mesh/generated/mcp_mesh_registry_client/api/agents_api.py +0 -1088
  58. _mcp_mesh/generated/mcp_mesh_registry_client/api/health_api.py +0 -764
  59. _mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py +0 -303
  60. _mcp_mesh/generated/mcp_mesh_registry_client/api_client.py +0 -798
  61. _mcp_mesh/generated/mcp_mesh_registry_client/api_response.py +0 -21
  62. _mcp_mesh/generated/mcp_mesh_registry_client/configuration.py +0 -577
  63. _mcp_mesh/generated/mcp_mesh_registry_client/exceptions.py +0 -217
  64. _mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +0 -55
  65. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +0 -158
  66. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata.py +0 -126
  67. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner.py +0 -139
  68. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner_one_of.py +0 -92
  69. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration.py +0 -103
  70. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration_metadata.py +0 -136
  71. _mcp_mesh/generated/mcp_mesh_registry_client/models/agents_list_response.py +0 -100
  72. _mcp_mesh/generated/mcp_mesh_registry_client/models/capability_info.py +0 -107
  73. _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_metadata.py +0 -112
  74. _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_request.py +0 -103
  75. _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_info.py +0 -105
  76. _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_info.py +0 -103
  77. _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_resolution_info.py +0 -106
  78. _mcp_mesh/generated/mcp_mesh_registry_client/models/error_response.py +0 -91
  79. _mcp_mesh/generated/mcp_mesh_registry_client/models/health_response.py +0 -103
  80. _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request.py +0 -101
  81. _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request_metadata.py +0 -111
  82. _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_response.py +0 -117
  83. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider.py +0 -93
  84. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider_resolution_info.py +0 -106
  85. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter.py +0 -109
  86. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner.py +0 -139
  87. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner_one_of.py +0 -91
  88. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_info.py +0 -101
  89. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_resolution_info.py +0 -120
  90. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_register_metadata.py +0 -112
  91. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +0 -129
  92. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +0 -153
  93. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response_dependencies_resolved_value_inner.py +0 -101
  94. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_dependency_registration.py +0 -93
  95. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_register_metadata.py +0 -107
  96. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +0 -117
  97. _mcp_mesh/generated/mcp_mesh_registry_client/models/registration_response.py +0 -119
  98. _mcp_mesh/generated/mcp_mesh_registry_client/models/resolved_llm_provider.py +0 -110
  99. _mcp_mesh/generated/mcp_mesh_registry_client/models/rich_dependency.py +0 -93
  100. _mcp_mesh/generated/mcp_mesh_registry_client/models/root_response.py +0 -92
  101. _mcp_mesh/generated/mcp_mesh_registry_client/models/standardized_dependency.py +0 -93
  102. _mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py +0 -106
  103. _mcp_mesh/generated/mcp_mesh_registry_client/py.typed +0 -0
  104. _mcp_mesh/generated/mcp_mesh_registry_client/rest.py +0 -259
  105. _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +0 -418
  106. _mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +0 -117
  107. _mcp_mesh/pipeline/api_heartbeat/api_health_check.py +0 -140
  108. _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py +0 -243
  109. _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +0 -311
  110. _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +0 -386
  111. _mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py +0 -104
  112. _mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py +0 -396
  113. _mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py +0 -116
  114. _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +0 -311
  115. _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +0 -282
  116. _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_send.py +0 -98
  117. _mcp_mesh/pipeline/mcp_heartbeat/lifespan_integration.py +0 -84
  118. _mcp_mesh/pipeline/mcp_heartbeat/llm_tools_resolution.py +0 -264
  119. _mcp_mesh/pipeline/mcp_heartbeat/registry_connection.py +0 -79
  120. _mcp_mesh/pipeline/shared/registry_connection.py +0 -80
  121. _mcp_mesh/shared/registry_client_wrapper.py +0 -515
  122. mcp_mesh-0.7.21.dist-info/RECORD +0 -152
  123. {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0.dist-info}/WHEEL +0 -0
  124. {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,243 +0,0 @@
1
- """
2
- API heartbeat orchestrator for managing periodic pipeline execution.
3
-
4
- Provides a high-level interface for executing API heartbeat pipelines
5
- with proper context management and error handling for FastAPI services.
6
- """
7
-
8
- import json
9
- import logging
10
- from datetime import UTC, datetime
11
- from typing import Any, Dict, Optional
12
-
13
- from .api_heartbeat_pipeline import APIHeartbeatPipeline
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
-
18
- class APIHeartbeatOrchestrator:
19
- """
20
- Orchestrates API heartbeat pipeline execution for periodic registry communication.
21
-
22
- Manages the context preparation, pipeline execution, and result processing
23
- for the periodic heartbeat cycle of FastAPI services using @mesh.route decorators.
24
- """
25
-
26
- def __init__(self):
27
- self.logger = logging.getLogger(f"{__name__}.APIHeartbeatOrchestrator")
28
- self.pipeline = APIHeartbeatPipeline()
29
- self._heartbeat_count = 0
30
-
31
- async def execute_api_heartbeat(
32
- self, service_id: str, context: dict[str, Any]
33
- ) -> bool:
34
- """
35
- Execute a complete API heartbeat cycle with comprehensive error handling.
36
-
37
- Args:
38
- service_id: Service identifier for the FastAPI application
39
- context: Full pipeline context from API startup
40
-
41
- Returns:
42
- bool: True if heartbeat succeeded, False if failed
43
- """
44
- self._heartbeat_count += 1
45
-
46
- try:
47
- # Prepare heartbeat context with validation
48
- heartbeat_context = self._prepare_api_heartbeat_context(service_id, context)
49
-
50
- # Validate required context before proceeding
51
- if not self._validate_api_heartbeat_context(heartbeat_context):
52
- self.logger.error(
53
- f"❌ API heartbeat #{self._heartbeat_count} failed: invalid context"
54
- )
55
- return False
56
-
57
- # Log heartbeat request details for debugging
58
- self._log_api_heartbeat_request(heartbeat_context, self._heartbeat_count)
59
-
60
- # Execute API heartbeat pipeline with timeout protection
61
- self.logger.trace(f"💓 Executing API heartbeat #{self._heartbeat_count} for service '{service_id}'")
62
-
63
- # Add timeout to prevent hanging heartbeats (30 seconds max)
64
- import asyncio
65
-
66
- try:
67
- self.logger.trace("Starting API heartbeat pipeline execution")
68
- result = await asyncio.wait_for(
69
- self.pipeline.execute_api_heartbeat_cycle(heartbeat_context),
70
- timeout=30.0,
71
- )
72
- if result.is_success():
73
- self.logger.trace("✅ API heartbeat pipeline completed successfully")
74
- else:
75
- self.logger.error(f"❌ API heartbeat pipeline failed: {result.message}")
76
- except TimeoutError:
77
- self.logger.error(
78
- f"❌ API heartbeat #{self._heartbeat_count} timed out after 30 seconds"
79
- )
80
- return False
81
- except Exception as e:
82
- self.logger.error(f"❌ [DEBUG] Pipeline execution exception: {e}")
83
- import traceback
84
- self.logger.error(f"❌ [DEBUG] Traceback: {traceback.format_exc()}")
85
- return False
86
-
87
- # Process results
88
- success = self._process_api_heartbeat_result(
89
- result, service_id, self._heartbeat_count
90
- )
91
-
92
- # Log periodic status updates
93
- if self._heartbeat_count % 10 == 0:
94
- elapsed_time = self._heartbeat_count * 5 # Using 5s interval (MeshDefaults.HEALTH_INTERVAL)
95
- self.logger.info(
96
- f"💓 API heartbeat #{self._heartbeat_count} for service '{service_id}' - "
97
- f"running for {elapsed_time} seconds"
98
- )
99
-
100
- return success
101
-
102
- except Exception as e:
103
- # Log detailed error information for debugging
104
- import traceback
105
-
106
- self.logger.error(
107
- f"❌ API heartbeat #{self._heartbeat_count} failed for service '{service_id}': {e}\n"
108
- f"Traceback: {traceback.format_exc()}"
109
- )
110
- return False
111
-
112
- def _prepare_api_heartbeat_context(
113
- self, service_id: str, startup_context: dict[str, Any]
114
- ) -> dict[str, Any]:
115
- """Prepare context for API heartbeat pipeline execution."""
116
-
117
- # Get FastAPI app and other essential components from startup context
118
- fastapi_app = startup_context.get("fastapi_app")
119
- display_config = startup_context.get("display_config", {})
120
-
121
- # Get API service metadata from startup context
122
- api_service_metadata = startup_context.get("api_service_metadata", {})
123
-
124
- # Build heartbeat-specific context
125
- heartbeat_context = {
126
- "service_id": service_id,
127
- "agent_id": service_id, # For compatibility with registry calls
128
- "fastapi_app": fastapi_app,
129
- "display_config": display_config,
130
- # Include registry and configuration from startup
131
- "agent_config": startup_context.get("agent_config", {}),
132
- "registration_data": startup_context.get("registration_data", {}),
133
- "registry_wrapper": startup_context.get("registry_wrapper"),
134
- # CRITICAL: Include API service metadata with route dependencies
135
- "api_service_metadata": api_service_metadata,
136
- }
137
-
138
- return heartbeat_context
139
-
140
- def _validate_api_heartbeat_context(self, heartbeat_context: dict[str, Any]) -> bool:
141
- """Validate that API heartbeat context has all required components."""
142
-
143
- required_fields = ["service_id", "fastapi_app"]
144
-
145
- for field in required_fields:
146
- if field not in heartbeat_context or heartbeat_context[field] is None:
147
- self.logger.error(
148
- f"❌ API heartbeat context validation failed: missing '{field}'"
149
- )
150
- return False
151
-
152
- # Additional validation for FastAPI app
153
- fastapi_app = heartbeat_context.get("fastapi_app")
154
- if not hasattr(fastapi_app, "routes"):
155
- self.logger.error(
156
- "❌ API heartbeat context validation failed: invalid FastAPI app object"
157
- )
158
- return False
159
-
160
- return True
161
-
162
- def _log_api_heartbeat_request(
163
- self, heartbeat_context: dict[str, Any], heartbeat_count: int
164
- ) -> None:
165
- """Log API heartbeat request details for debugging."""
166
-
167
- service_id = heartbeat_context.get("service_id", "unknown")
168
- fastapi_app = heartbeat_context.get("fastapi_app")
169
- display_config = heartbeat_context.get("display_config", {})
170
-
171
- # Extract app information for logging
172
- app_info = {}
173
- if fastapi_app:
174
- app_info = {
175
- "title": getattr(fastapi_app, "title", "Unknown API"),
176
- "version": getattr(fastapi_app, "version", "1.0.0"),
177
- "routes_count": len(getattr(fastapi_app, "routes", [])),
178
- }
179
-
180
- # Log heartbeat details
181
- self.logger.trace(
182
- f"🔍 API Heartbeat #{heartbeat_count} for '{service_id}': "
183
- f"app={app_info}, display={display_config}"
184
- )
185
-
186
- def _process_api_heartbeat_result(
187
- self, result: Any, service_id: str, heartbeat_count: int
188
- ) -> bool:
189
- """Process API heartbeat pipeline result and log appropriately."""
190
-
191
- if result.is_success():
192
- # Check for heartbeat response in result context
193
- heartbeat_response = result.context.get("heartbeat_response")
194
- heartbeat_success = result.context.get("heartbeat_success", False)
195
-
196
- self.logger.trace(f"API heartbeat result - success: {heartbeat_success}")
197
-
198
- # Check if heartbeat was skipped due to optimization
199
- heartbeat_skipped = result.context.get("heartbeat_skipped", False)
200
- skip_reason = result.context.get("skip_reason")
201
-
202
- if heartbeat_success and heartbeat_response:
203
- # Log response details for tracing
204
- try:
205
- response_json = json.dumps(
206
- heartbeat_response, indent=2, default=str
207
- )
208
- self.logger.trace(
209
- f"🔍 API heartbeat response #{heartbeat_count}:\n{response_json}"
210
- )
211
- except Exception as e:
212
- self.logger.trace(
213
- f"🔍 API heartbeat response #{heartbeat_count}: {heartbeat_response} "
214
- f"(json serialization failed: {e})"
215
- )
216
-
217
- self.logger.debug(
218
- f"🚀 API heartbeat #{heartbeat_count} sent for service '{service_id}'"
219
- )
220
- return True
221
- elif heartbeat_success and heartbeat_skipped:
222
- # Heartbeat was skipped for optimization - this is success
223
- self.logger.debug(
224
- f"🚀 API heartbeat #{heartbeat_count} skipped for service '{service_id}' - {skip_reason}"
225
- )
226
- return True
227
- else:
228
- self.logger.warning(
229
- f"💔 [UPDATED] API heartbeat #{heartbeat_count} failed for service '{service_id}' - "
230
- f"no response or unsuccessful (heartbeat_success={heartbeat_success}, heartbeat_response={heartbeat_response})"
231
- )
232
- return False
233
- else:
234
- self.logger.warning(
235
- f"💔 [UPDATED-PIPELINE] API heartbeat #{heartbeat_count} pipeline failed for service '{service_id}': {result.message}"
236
- )
237
-
238
- # Log detailed errors
239
- if hasattr(result, 'errors') and result.errors:
240
- for error in result.errors:
241
- self.logger.warning(f" - API heartbeat error: {error}")
242
-
243
- return False
@@ -1,311 +0,0 @@
1
- """
2
- API heartbeat pipeline for FastAPI service health monitoring.
3
-
4
- Provides structured execution of API service heartbeat operations with proper
5
- error handling and logging. Runs periodically to maintain registry communication
6
- and service health status for FastAPI applications using @mesh.route decorators.
7
- """
8
-
9
- import logging
10
- from typing import Any
11
-
12
- from ...shared.fast_heartbeat_status import FastHeartbeatStatus, FastHeartbeatStatusUtil
13
- from ..shared.mesh_pipeline import MeshPipeline
14
- from ..shared.pipeline_types import PipelineStatus
15
- from .api_dependency_resolution import APIDependencyResolutionStep
16
- from .api_fast_heartbeat_check import APIFastHeartbeatStep
17
- from .api_health_check import APIHealthCheckStep
18
- from .api_heartbeat_send import APIHeartbeatSendStep
19
- from .api_registry_connection import APIRegistryConnectionStep
20
-
21
- logger = logging.getLogger(__name__)
22
-
23
-
24
- class APIHeartbeatPipeline(MeshPipeline):
25
- """
26
- Specialized pipeline for API service heartbeat operations with fast optimization.
27
-
28
- Executes the core API heartbeat steps in sequence:
29
- 1. Registry connection preparation
30
- 2. API health check (validate FastAPI app status)
31
- 3. Fast heartbeat check (HEAD request)
32
- 4. Heartbeat sending (conditional POST request)
33
- 5. Dependency resolution (conditional)
34
-
35
- Steps 4 and 5 only run if fast heartbeat indicates changes are needed.
36
- Provides optimization for NO_CHANGES and resilience for error conditions.
37
-
38
- API services now support:
39
- - Service availability and health status monitoring
40
- - Efficient HEAD->conditional POST pattern (like MCP agents)
41
- - Dynamic dependency resolution and injection
42
- - Route handler dependency updates from registry responses
43
- """
44
-
45
- def __init__(self, name: str = "api-heartbeat-pipeline"):
46
- super().__init__(name=name)
47
- self._setup_api_heartbeat_steps()
48
-
49
- def _setup_api_heartbeat_steps(self) -> None:
50
- """Setup the API heartbeat pipeline steps with fast optimization."""
51
- # API heartbeat steps with fast optimization pattern
52
- steps = [
53
- APIRegistryConnectionStep(), # Prepare registry communication
54
- APIHealthCheckStep(), # Check FastAPI app health status
55
- APIFastHeartbeatStep(), # Fast heartbeat check (HEAD request)
56
- APIHeartbeatSendStep(), # Conditional heartbeat send (POST request)
57
- APIDependencyResolutionStep(), # Conditional dependency resolution
58
- ]
59
-
60
- self.add_steps(steps)
61
- self.logger.trace(f"API heartbeat pipeline configured with {len(steps)} steps")
62
-
63
- # Log the pipeline strategy
64
- self.logger.info(
65
- "🌐 API Heartbeat Pipeline initialized: fast optimization for FastAPI apps"
66
- )
67
- self.logger.trace(f"📋 Pipeline steps: {[step.name for step in steps]}")
68
-
69
- async def execute_api_heartbeat_cycle(
70
- self, heartbeat_context: dict[str, Any]
71
- ) -> Any:
72
- """
73
- Execute a complete API heartbeat cycle with fast optimization and enhanced error handling.
74
-
75
- Args:
76
- heartbeat_context: Context containing registry_wrapper, service_id,
77
- health_status, fastapi_app, etc.
78
-
79
- Returns:
80
- PipelineResult with execution status and any context updates
81
- """
82
- self.logger.trace("Starting API heartbeat pipeline execution")
83
-
84
- # Initialize pipeline context with heartbeat-specific data
85
- self.context.clear()
86
- self.context.update(heartbeat_context)
87
-
88
- try:
89
- # Execute the pipeline with conditional logic for fast optimization
90
- result = await self._execute_with_conditional_logic()
91
-
92
- if result.is_success():
93
- self.logger.trace("✅ API heartbeat pipeline completed successfully")
94
- elif result.status == PipelineStatus.PARTIAL:
95
- self.logger.warning(
96
- f"⚠️ API heartbeat pipeline completed partially: {result.message}"
97
- )
98
- # Log which steps failed
99
- if result.errors:
100
- for error in result.errors:
101
- self.logger.warning(f" - Step error: {error}")
102
- else:
103
- self.logger.error(f"❌ API heartbeat pipeline failed: {result.message}")
104
- # Log detailed error information
105
- if result.errors:
106
- for error in result.errors:
107
- self.logger.error(f" - Pipeline error: {error}")
108
-
109
- return result
110
-
111
- except Exception as e:
112
- # Log detailed error information for debugging
113
- import traceback
114
-
115
- self.logger.error(
116
- f"❌ API heartbeat pipeline failed with exception: {e}\n"
117
- f"Context keys: {list(self.context.keys())}\n"
118
- f"Traceback: {traceback.format_exc()}"
119
- )
120
-
121
- # Create failure result with detailed context
122
- from ..shared.pipeline_types import PipelineResult
123
-
124
- failure_result = PipelineResult(
125
- status=PipelineStatus.FAILED,
126
- message=f"API heartbeat pipeline exception: {str(e)[:200]}...",
127
- context=self.context,
128
- )
129
- failure_result.add_error(str(e))
130
-
131
- return failure_result
132
-
133
- async def _execute_with_conditional_logic(self) -> "PipelineResult":
134
- """
135
- Execute API pipeline with conditional logic based on fast heartbeat status.
136
-
137
- Always executes:
138
- - APIRegistryConnectionStep
139
- - APIHealthCheckStep
140
- - APIFastHeartbeatStep
141
-
142
- Conditionally executes based on fast heartbeat status:
143
- - NO_CHANGES: Skip remaining steps (optimization)
144
- - TOPOLOGY_CHANGED, AGENT_UNKNOWN: Execute all remaining steps
145
- - REGISTRY_ERROR, NETWORK_ERROR: Skip remaining steps (resilience)
146
-
147
- Returns:
148
- PipelineResult with execution status and context
149
- """
150
- from ..shared.pipeline_types import PipelineResult
151
-
152
- overall_result = PipelineResult(
153
- message="API heartbeat pipeline execution completed"
154
- )
155
-
156
- # Track which steps were executed for logging
157
- executed_steps = []
158
- skipped_steps = []
159
-
160
- try:
161
- # Always execute registry connection, health check, and fast heartbeat steps
162
- mandatory_steps = self.steps[
163
- :3
164
- ] # APIRegistryConnectionStep, APIHealthCheckStep, APIFastHeartbeatStep
165
- conditional_steps = self.steps[
166
- 3:
167
- ] # APIHeartbeatSendStep, APIDependencyResolutionStep
168
-
169
- # Execute mandatory steps
170
- for step in mandatory_steps:
171
- self.logger.trace(f"Executing mandatory step: {step.name}")
172
-
173
- step_result = await step.execute(self.context)
174
- executed_steps.append(step.name)
175
-
176
- # Merge step context into pipeline context
177
- self.context.update(step_result.context)
178
-
179
- # If step fails, handle accordingly
180
- if not step_result.is_success():
181
- overall_result.status = PipelineStatus.FAILED
182
- overall_result.message = (
183
- f"Mandatory step '{step.name}' failed: {step_result.message}"
184
- )
185
- overall_result.add_error(
186
- f"Step '{step.name}': {step_result.message}"
187
- )
188
-
189
- if step.required:
190
- # Stop execution if required step fails
191
- for key, value in self.context.items():
192
- overall_result.add_context(key, value)
193
- return overall_result
194
-
195
- # Check fast heartbeat status for conditional execution
196
- fast_heartbeat_status = self.context.get("fast_heartbeat_status")
197
-
198
- if fast_heartbeat_status is None:
199
- # Fast heartbeat step failed to set status - fallback to full execution
200
- self.logger.warning(
201
- "⚠️ API fast heartbeat status not found - falling back to full execution"
202
- )
203
- should_execute_remaining = True
204
- reason = "fallback (missing status)"
205
- elif FastHeartbeatStatusUtil.should_skip_for_optimization(
206
- fast_heartbeat_status
207
- ):
208
- # NO_CHANGES - skip for optimization
209
- should_execute_remaining = False
210
- reason = "optimization (no changes detected)"
211
- self.logger.trace(
212
- f"🚀 API heartbeat: Skipping remaining steps for optimization: {reason}"
213
- )
214
- elif FastHeartbeatStatusUtil.should_skip_for_resilience(
215
- fast_heartbeat_status
216
- ):
217
- # REGISTRY_ERROR, NETWORK_ERROR - skip for resilience
218
- should_execute_remaining = False
219
- reason = "resilience (preserve existing state)"
220
- self.logger.warning(
221
- f"⚠️ API heartbeat: Skipping remaining steps for resilience: {reason}"
222
- )
223
- elif FastHeartbeatStatusUtil.requires_full_heartbeat(fast_heartbeat_status):
224
- # TOPOLOGY_CHANGED, AGENT_UNKNOWN - execute full pipeline
225
- should_execute_remaining = True
226
- reason = "changes detected or re-registration needed"
227
- self.logger.info(
228
- f"🔄 API heartbeat: Executing remaining steps: {reason}"
229
- )
230
- else:
231
- # Unknown status - fallback to full execution
232
- self.logger.warning(
233
- f"⚠️ Unknown API fast heartbeat status '{fast_heartbeat_status}' - falling back to full execution"
234
- )
235
- should_execute_remaining = True
236
- reason = "fallback (unknown status)"
237
-
238
- # Execute or skip conditional steps based on decision
239
- if should_execute_remaining:
240
- for step in conditional_steps:
241
- self.logger.trace(f"Executing conditional step: {step.name}")
242
-
243
- step_result = await step.execute(self.context)
244
- executed_steps.append(step.name)
245
-
246
- # Merge step context into pipeline context
247
- self.context.update(step_result.context)
248
-
249
- # Handle step failure
250
- if not step_result.is_success():
251
- if step.required:
252
- overall_result.status = PipelineStatus.FAILED
253
- overall_result.message = f"Required step '{step.name}' failed: {step_result.message}"
254
- overall_result.add_error(
255
- f"Step '{step.name}': {step_result.message}"
256
- )
257
- break
258
- else:
259
- # Optional step failed - mark as partial success
260
- if overall_result.status == PipelineStatus.SUCCESS:
261
- overall_result.status = PipelineStatus.PARTIAL
262
- overall_result.add_error(
263
- f"Optional step '{step.name}': {step_result.message}"
264
- )
265
- self.logger.warning(
266
- f"⚠️ Optional step '{step.name}' failed: {step_result.message}"
267
- )
268
- else:
269
- # Mark skipped steps
270
- for step in conditional_steps:
271
- skipped_steps.append(step.name)
272
-
273
- # For skipped heartbeat due to NO_CHANGES, set success context
274
- if fast_heartbeat_status == FastHeartbeatStatus.NO_CHANGES:
275
- overall_result.add_context("heartbeat_success", True)
276
- overall_result.add_context("heartbeat_skipped", True)
277
- overall_result.add_context("skip_reason", "no_changes_optimization")
278
-
279
- # Set final result message
280
- if executed_steps and skipped_steps:
281
- overall_result.message = (
282
- f"API pipeline completed with conditional execution - "
283
- f"executed: {executed_steps}, skipped: {skipped_steps} ({reason})"
284
- )
285
- elif executed_steps:
286
- overall_result.message = (
287
- f"API pipeline completed - executed: {executed_steps} ({reason})"
288
- )
289
- else:
290
- overall_result.message = (
291
- f"API pipeline completed - all steps skipped ({reason})"
292
- )
293
-
294
- # Add final context
295
- for key, value in self.context.items():
296
- overall_result.add_context(key, value)
297
-
298
- return overall_result
299
-
300
- except Exception as e:
301
- # Handle unexpected exceptions
302
- overall_result.status = PipelineStatus.FAILED
303
- overall_result.message = (
304
- f"API pipeline execution failed with exception: {e}"
305
- )
306
- overall_result.add_error(str(e))
307
- for key, value in self.context.items():
308
- overall_result.add_context(key, value)
309
-
310
- self.logger.error(f"❌ API conditional pipeline execution failed: {e}")
311
- return overall_result