a2a-ruby 1.0.0

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 (128) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +137 -0
  4. data/.simplecov +46 -0
  5. data/.yardopts +10 -0
  6. data/CHANGELOG.md +33 -0
  7. data/CODE_OF_CONDUCT.md +128 -0
  8. data/CONTRIBUTING.md +165 -0
  9. data/Gemfile +43 -0
  10. data/Guardfile +34 -0
  11. data/LICENSE.txt +21 -0
  12. data/PUBLISHING_CHECKLIST.md +214 -0
  13. data/README.md +171 -0
  14. data/Rakefile +165 -0
  15. data/docs/agent_execution.md +309 -0
  16. data/docs/api_reference.md +792 -0
  17. data/docs/configuration.md +780 -0
  18. data/docs/events.md +475 -0
  19. data/docs/getting_started.md +668 -0
  20. data/docs/integration.md +262 -0
  21. data/docs/server_apps.md +621 -0
  22. data/docs/troubleshooting.md +765 -0
  23. data/lib/a2a/client/api_methods.rb +263 -0
  24. data/lib/a2a/client/auth/api_key.rb +161 -0
  25. data/lib/a2a/client/auth/interceptor.rb +288 -0
  26. data/lib/a2a/client/auth/jwt.rb +189 -0
  27. data/lib/a2a/client/auth/oauth2.rb +146 -0
  28. data/lib/a2a/client/auth.rb +137 -0
  29. data/lib/a2a/client/base.rb +316 -0
  30. data/lib/a2a/client/config.rb +210 -0
  31. data/lib/a2a/client/connection_pool.rb +233 -0
  32. data/lib/a2a/client/http_client.rb +524 -0
  33. data/lib/a2a/client/json_rpc_handler.rb +136 -0
  34. data/lib/a2a/client/middleware/circuit_breaker_interceptor.rb +245 -0
  35. data/lib/a2a/client/middleware/logging_interceptor.rb +371 -0
  36. data/lib/a2a/client/middleware/rate_limit_interceptor.rb +142 -0
  37. data/lib/a2a/client/middleware/retry_interceptor.rb +161 -0
  38. data/lib/a2a/client/middleware.rb +116 -0
  39. data/lib/a2a/client/performance_tracker.rb +60 -0
  40. data/lib/a2a/configuration/defaults.rb +34 -0
  41. data/lib/a2a/configuration/environment_loader.rb +76 -0
  42. data/lib/a2a/configuration/file_loader.rb +115 -0
  43. data/lib/a2a/configuration/inheritance.rb +101 -0
  44. data/lib/a2a/configuration/validator.rb +180 -0
  45. data/lib/a2a/configuration.rb +201 -0
  46. data/lib/a2a/errors.rb +291 -0
  47. data/lib/a2a/modules.rb +50 -0
  48. data/lib/a2a/monitoring/alerting.rb +490 -0
  49. data/lib/a2a/monitoring/distributed_tracing.rb +398 -0
  50. data/lib/a2a/monitoring/health_endpoints.rb +204 -0
  51. data/lib/a2a/monitoring/metrics_collector.rb +438 -0
  52. data/lib/a2a/monitoring.rb +463 -0
  53. data/lib/a2a/plugin.rb +358 -0
  54. data/lib/a2a/plugin_manager.rb +159 -0
  55. data/lib/a2a/plugins/example_auth.rb +81 -0
  56. data/lib/a2a/plugins/example_middleware.rb +118 -0
  57. data/lib/a2a/plugins/example_transport.rb +76 -0
  58. data/lib/a2a/protocol/agent_card.rb +8 -0
  59. data/lib/a2a/protocol/agent_card_server.rb +584 -0
  60. data/lib/a2a/protocol/capability.rb +496 -0
  61. data/lib/a2a/protocol/json_rpc.rb +254 -0
  62. data/lib/a2a/protocol/message.rb +8 -0
  63. data/lib/a2a/protocol/task.rb +8 -0
  64. data/lib/a2a/rails/a2a_controller.rb +258 -0
  65. data/lib/a2a/rails/controller_helpers.rb +499 -0
  66. data/lib/a2a/rails/engine.rb +167 -0
  67. data/lib/a2a/rails/generators/agent_generator.rb +311 -0
  68. data/lib/a2a/rails/generators/install_generator.rb +209 -0
  69. data/lib/a2a/rails/generators/migration_generator.rb +232 -0
  70. data/lib/a2a/rails/generators/templates/add_a2a_indexes.rb +57 -0
  71. data/lib/a2a/rails/generators/templates/agent_controller.rb +122 -0
  72. data/lib/a2a/rails/generators/templates/agent_controller_spec.rb +160 -0
  73. data/lib/a2a/rails/generators/templates/agent_readme.md +200 -0
  74. data/lib/a2a/rails/generators/templates/create_a2a_push_notification_configs.rb +68 -0
  75. data/lib/a2a/rails/generators/templates/create_a2a_tasks.rb +83 -0
  76. data/lib/a2a/rails/generators/templates/example_agent_controller.rb +228 -0
  77. data/lib/a2a/rails/generators/templates/initializer.rb +108 -0
  78. data/lib/a2a/rails/generators/templates/push_notification_config_model.rb +228 -0
  79. data/lib/a2a/rails/generators/templates/task_model.rb +200 -0
  80. data/lib/a2a/rails/tasks/a2a.rake +228 -0
  81. data/lib/a2a/server/a2a_methods.rb +520 -0
  82. data/lib/a2a/server/agent.rb +537 -0
  83. data/lib/a2a/server/agent_execution/agent_executor.rb +279 -0
  84. data/lib/a2a/server/agent_execution/request_context.rb +219 -0
  85. data/lib/a2a/server/apps/rack_app.rb +311 -0
  86. data/lib/a2a/server/apps/sinatra_app.rb +261 -0
  87. data/lib/a2a/server/default_request_handler.rb +350 -0
  88. data/lib/a2a/server/events/event_consumer.rb +116 -0
  89. data/lib/a2a/server/events/event_queue.rb +226 -0
  90. data/lib/a2a/server/example_agent.rb +248 -0
  91. data/lib/a2a/server/handler.rb +281 -0
  92. data/lib/a2a/server/middleware/authentication_middleware.rb +212 -0
  93. data/lib/a2a/server/middleware/cors_middleware.rb +171 -0
  94. data/lib/a2a/server/middleware/logging_middleware.rb +362 -0
  95. data/lib/a2a/server/middleware/rate_limit_middleware.rb +382 -0
  96. data/lib/a2a/server/middleware.rb +213 -0
  97. data/lib/a2a/server/push_notification_manager.rb +327 -0
  98. data/lib/a2a/server/request_handler.rb +136 -0
  99. data/lib/a2a/server/storage/base.rb +141 -0
  100. data/lib/a2a/server/storage/database.rb +266 -0
  101. data/lib/a2a/server/storage/memory.rb +274 -0
  102. data/lib/a2a/server/storage/redis.rb +320 -0
  103. data/lib/a2a/server/storage.rb +38 -0
  104. data/lib/a2a/server/task_manager.rb +534 -0
  105. data/lib/a2a/transport/grpc.rb +481 -0
  106. data/lib/a2a/transport/http.rb +415 -0
  107. data/lib/a2a/transport/sse.rb +499 -0
  108. data/lib/a2a/types/agent_card.rb +540 -0
  109. data/lib/a2a/types/artifact.rb +99 -0
  110. data/lib/a2a/types/base_model.rb +223 -0
  111. data/lib/a2a/types/events.rb +117 -0
  112. data/lib/a2a/types/message.rb +106 -0
  113. data/lib/a2a/types/part.rb +288 -0
  114. data/lib/a2a/types/push_notification.rb +139 -0
  115. data/lib/a2a/types/security.rb +167 -0
  116. data/lib/a2a/types/task.rb +154 -0
  117. data/lib/a2a/types.rb +88 -0
  118. data/lib/a2a/utils/helpers.rb +245 -0
  119. data/lib/a2a/utils/message_buffer.rb +278 -0
  120. data/lib/a2a/utils/performance.rb +247 -0
  121. data/lib/a2a/utils/rails_detection.rb +97 -0
  122. data/lib/a2a/utils/structured_logger.rb +306 -0
  123. data/lib/a2a/utils/time_helpers.rb +167 -0
  124. data/lib/a2a/utils/validation.rb +8 -0
  125. data/lib/a2a/version.rb +6 -0
  126. data/lib/a2a-rails.rb +58 -0
  127. data/lib/a2a.rb +198 -0
  128. metadata +437 -0
@@ -0,0 +1,398 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module A2A
6
+ module Monitoring
7
+ end
8
+ end
9
+
10
+ ##
11
+ # Distributed tracing implementation for A2A operations
12
+ #
13
+ # Provides OpenTelemetry-compatible distributed tracing to track requests
14
+ # across service boundaries and identify performance bottlenecks.
15
+ #
16
+ module A2A
17
+ module Monitoring
18
+ class DistributedTracing
19
+ # Trace context headers
20
+ TRACE_PARENT_HEADER = "traceparent"
21
+ TRACE_STATE_HEADER = "tracestate"
22
+
23
+ # Span kinds
24
+ SPAN_KIND_CLIENT = "client"
25
+ SPAN_KIND_SERVER = "server"
26
+ SPAN_KIND_INTERNAL = "internal"
27
+
28
+ class << self
29
+ attr_accessor :tracer, :enabled
30
+
31
+ ##
32
+ # Initialize distributed tracing
33
+ #
34
+ # @param tracer [Object] OpenTelemetry tracer instance
35
+ # @param enabled [Boolean] Whether tracing is enabled
36
+ def initialize!(tracer: nil, enabled: true)
37
+ @tracer = tracer
38
+ @enabled = enabled
39
+ end
40
+
41
+ ##
42
+ # Start a new span
43
+ #
44
+ # @param name [String] Span name
45
+ # @param kind [String] Span kind
46
+ # @param parent [Span, nil] Parent span
47
+ # @param attributes [Hash] Span attributes
48
+ # @yield [span] Block to execute within span
49
+ # @return [Object] Result of the block
50
+ def trace(name, kind: SPAN_KIND_INTERNAL, parent: nil, **attributes)
51
+ return yield(NoOpSpan.new) unless @enabled
52
+
53
+ span = start_span(name, kind: kind, parent: parent, **attributes)
54
+
55
+ begin
56
+ result = yield(span)
57
+ span.set_status(:ok)
58
+ result
59
+ rescue StandardError => e
60
+ span.set_status(:error, description: e.message)
61
+ span.record_exception(e)
62
+ raise
63
+ ensure
64
+ span.finish
65
+ end
66
+ end
67
+
68
+ ##
69
+ # Start a span without automatic finishing
70
+ #
71
+ # @param name [String] Span name
72
+ # @param kind [String] Span kind
73
+ # @param parent [Span, nil] Parent span
74
+ # @param attributes [Hash] Span attributes
75
+ # @return [Span] Started span
76
+ def start_span(name, kind: SPAN_KIND_INTERNAL, parent: nil, **attributes)
77
+ return NoOpSpan.new unless @enabled
78
+
79
+ if @tracer.respond_to?(:start_span)
80
+ # Use OpenTelemetry tracer if available
81
+ @tracer.start_span(name, kind: kind, parent: parent, attributes: attributes)
82
+ else
83
+ # Use built-in span implementation
84
+ Span.new(name, kind: kind, parent: parent, **attributes)
85
+ end
86
+ end
87
+
88
+ ##
89
+ # Extract trace context from headers
90
+ #
91
+ # @param headers [Hash] HTTP headers
92
+ # @return [TraceContext, nil] Extracted trace context
93
+ def extract_context(headers)
94
+ return nil unless @enabled
95
+
96
+ traceparent = headers[TRACE_PARENT_HEADER] || headers[TRACE_PARENT_HEADER.upcase]
97
+ tracestate = headers[TRACE_STATE_HEADER] || headers[TRACE_STATE_HEADER.upcase]
98
+
99
+ return nil unless traceparent
100
+
101
+ TraceContext.parse(traceparent, tracestate)
102
+ end
103
+
104
+ ##
105
+ # Inject trace context into headers
106
+ #
107
+ # @param headers [Hash] HTTP headers to modify
108
+ # @param context [TraceContext] Trace context to inject
109
+ def inject_context(headers, context)
110
+ return unless @enabled && context
111
+
112
+ headers[TRACE_PARENT_HEADER] = context.to_traceparent
113
+ headers[TRACE_STATE_HEADER] = context.tracestate if context.tracestate
114
+ end
115
+
116
+ ##
117
+ # Get current span from context
118
+ #
119
+ # @return [Span, nil] Current active span
120
+ def current_span
121
+ return nil unless @enabled
122
+
123
+ Thread.current[:a2a_current_span]
124
+ end
125
+
126
+ ##
127
+ # Set current span in context
128
+ #
129
+ # @param span [Span] Span to set as current
130
+ def set_current_span(span)
131
+ Thread.current[:a2a_current_span] = span
132
+ end
133
+ end
134
+
135
+ ##
136
+ # Trace context for distributed tracing
137
+ #
138
+ class TraceContext
139
+ attr_reader :trace_id, :span_id, :trace_flags, :tracestate
140
+
141
+ ##
142
+ # Initialize trace context
143
+ #
144
+ # @param trace_id [String] Trace ID (32 hex characters)
145
+ # @param span_id [String] Span ID (16 hex characters)
146
+ # @param trace_flags [Integer] Trace flags
147
+ # @param tracestate [String, nil] Trace state
148
+ def initialize(trace_id:, span_id:, trace_flags: 1, tracestate: nil)
149
+ @trace_id = trace_id
150
+ @span_id = span_id
151
+ @trace_flags = trace_flags
152
+ @tracestate = tracestate
153
+ end
154
+
155
+ ##
156
+ # Parse trace context from traceparent header
157
+ #
158
+ # @param traceparent [String] Traceparent header value
159
+ # @param tracestate [String, nil] Tracestate header value
160
+ # @return [TraceContext, nil] Parsed trace context
161
+ def self.parse(traceparent, tracestate = nil)
162
+ # Format: 00-{trace_id}-{span_id}-{trace_flags}
163
+ parts = traceparent.split("-")
164
+ return nil unless parts.size == 4 && parts[0] == "00"
165
+
166
+ new(
167
+ trace_id: parts[1],
168
+ span_id: parts[2],
169
+ trace_flags: parts[3].to_i(16),
170
+ tracestate: tracestate
171
+ )
172
+ rescue StandardError
173
+ nil
174
+ end
175
+
176
+ ##
177
+ # Convert to traceparent header format
178
+ #
179
+ # @return [String] Traceparent header value
180
+ def to_traceparent
181
+ "00-#{@trace_id}-#{@span_id}-#{@trace_flags.to_s(16).rjust(2, '0')}"
182
+ end
183
+
184
+ ##
185
+ # Create child context with new span ID
186
+ #
187
+ # @return [TraceContext] Child trace context
188
+ def create_child
189
+ self.class.new(
190
+ trace_id: @trace_id,
191
+ span_id: generate_span_id,
192
+ trace_flags: @trace_flags,
193
+ tracestate: @tracestate
194
+ )
195
+ end
196
+
197
+ private
198
+
199
+ ##
200
+ # Generate a new span ID
201
+ #
202
+ # @return [String] 16-character hex span ID
203
+ def generate_span_id
204
+ SecureRandom.hex(8)
205
+ end
206
+ end
207
+
208
+ ##
209
+ # Span implementation for distributed tracing
210
+ #
211
+ class Span
212
+ attr_reader :name, :kind, :trace_id, :span_id, :parent_span_id, :start_time, :end_time, :attributes, :events,
213
+ :status
214
+
215
+ ##
216
+ # Initialize a new span
217
+ #
218
+ # @param name [String] Span name
219
+ # @param kind [String] Span kind
220
+ # @param parent [Span, nil] Parent span
221
+ # @param attributes [Hash] Initial attributes
222
+ def initialize(name, kind: SPAN_KIND_INTERNAL, parent: nil, **attributes)
223
+ @name = name
224
+ @kind = kind
225
+ @trace_id = parent&.trace_id || generate_trace_id
226
+ @span_id = generate_span_id
227
+ @parent_span_id = parent&.span_id
228
+ @start_time = Time.now
229
+ @end_time = nil
230
+ @attributes = attributes
231
+ @events = []
232
+ @status = { code: :unset }
233
+
234
+ # Set as current span
235
+ DistributedTracing.set_current_span(self)
236
+ end
237
+
238
+ ##
239
+ # Set span attribute
240
+ #
241
+ # @param key [String, Symbol] Attribute key
242
+ # @param value [Object] Attribute value
243
+ def set_attribute(key, value)
244
+ @attributes[key.to_s] = value
245
+ end
246
+
247
+ ##
248
+ # Set multiple attributes
249
+ #
250
+ # @param attributes [Hash] Attributes to set
251
+ def set_attributes(**attributes)
252
+ @attributes.merge!(attributes.transform_keys(&:to_s))
253
+ end
254
+
255
+ ##
256
+ # Add an event to the span
257
+ #
258
+ # @param name [String] Event name
259
+ # @param attributes [Hash] Event attributes
260
+ # @param timestamp [Time] Event timestamp
261
+ def add_event(name, attributes: {}, timestamp: Time.now)
262
+ @events << {
263
+ name: name,
264
+ attributes: attributes,
265
+ timestamp: timestamp
266
+ }
267
+ end
268
+
269
+ ##
270
+ # Record an exception
271
+ #
272
+ # @param exception [Exception] Exception to record
273
+ def record_exception(exception)
274
+ add_event("exception", attributes: {
275
+ "exception.type" => exception.class.name,
276
+ "exception.message" => exception.message,
277
+ "exception.stacktrace" => exception.backtrace&.join("\n")
278
+ })
279
+ end
280
+
281
+ ##
282
+ # Set span status
283
+ #
284
+ # @param code [Symbol] Status code (:ok, :error, :unset)
285
+ # @param description [String, nil] Status description
286
+ def set_status(code, description: nil)
287
+ @status = { code: code, description: description }.compact
288
+ end
289
+
290
+ ##
291
+ # Finish the span
292
+ #
293
+ def finish
294
+ @end_time = Time.now
295
+
296
+ # Clear from current context if this is the current span
297
+ DistributedTracing.set_current_span(@parent) if DistributedTracing.current_span == self
298
+
299
+ # Export span if exporter is available
300
+ export_span
301
+ end
302
+
303
+ ##
304
+ # Get span duration in milliseconds
305
+ #
306
+ # @return [Float, nil] Duration in milliseconds
307
+ def duration_ms
308
+ return nil unless @end_time
309
+
310
+ (@end_time - @start_time) * 1000
311
+ end
312
+
313
+ ##
314
+ # Convert span to hash representation
315
+ #
316
+ # @return [Hash] Span data
317
+ def to_h
318
+ {
319
+ name: @name,
320
+ kind: @kind,
321
+ trace_id: @trace_id,
322
+ span_id: @span_id,
323
+ parent_span_id: @parent_span_id,
324
+ start_time: @start_time.to_f,
325
+ end_time: @end_time&.to_f,
326
+ duration_ms: duration_ms,
327
+ attributes: @attributes,
328
+ events: @events,
329
+ status: @status
330
+ }.compact
331
+ end
332
+
333
+ ##
334
+ # Get trace context for this span
335
+ #
336
+ # @return [TraceContext] Trace context
337
+ def trace_context
338
+ TraceContext.new(
339
+ trace_id: @trace_id,
340
+ span_id: @span_id,
341
+ trace_flags: 1
342
+ )
343
+ end
344
+
345
+ private
346
+
347
+ ##
348
+ # Generate a new trace ID
349
+ #
350
+ # @return [String] 32-character hex trace ID
351
+ def generate_trace_id
352
+ SecureRandom.hex(16)
353
+ end
354
+
355
+ ##
356
+ # Generate a new span ID
357
+ #
358
+ # @return [String] 16-character hex span ID
359
+ def generate_span_id
360
+ SecureRandom.hex(8)
361
+ end
362
+
363
+ ##
364
+ # Export span to configured exporters
365
+ #
366
+ def export_span
367
+ # This would integrate with OpenTelemetry exporters
368
+ # For now, just log the span if debugging is enabled
369
+ return unless A2A.configuration.debug_tracing
370
+
371
+ logger = if defined?(::Rails) && ::Rails.respond_to?(:logger) && ::Rails.logger
372
+ ::Rails.logger
373
+ else
374
+ require "logger"
375
+ Logger.new($stdout)
376
+ end
377
+ logger.debug { "Span: #{to_h.to_json}" }
378
+ end
379
+ end
380
+
381
+ ##
382
+ # No-op span for when tracing is disabled
383
+ #
384
+ class NoOpSpan
385
+ def set_attribute(key, value); end
386
+ def set_attributes(**attributes); end
387
+ def add_event(name, **options); end
388
+ def record_exception(exception); end
389
+ def set_status(code, **options); end
390
+ def finish; end
391
+
392
+ def trace_context
393
+ nil
394
+ end
395
+ end
396
+ end
397
+ end
398
+ end
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ module Monitoring
5
+ ##
6
+ # Health check endpoints for A2A applications
7
+ #
8
+ # Provides standard health check endpoints that can be used
9
+ # by load balancers, monitoring systems, and orchestrators.
10
+ #
11
+ class HealthEndpoints
12
+ # Initialize health endpoints
13
+ # @param health_checker [A2A::Monitoring::HealthChecker] Health checker instance
14
+ def initialize(health_checker = A2A::Monitoring.health_checks)
15
+ @health_checker = health_checker
16
+ setup_default_checks
17
+ end
18
+
19
+ # Handle health check request
20
+ # @param request [Hash] HTTP request data
21
+ # @return [Array] Rack response array
22
+ def call(env)
23
+ path = env["PATH_INFO"]
24
+
25
+ case path
26
+ when "/health"
27
+ handle_health_check
28
+ when "/health/ready"
29
+ handle_readiness_check
30
+ when "/health/live"
31
+ handle_liveness_check
32
+ when "/metrics"
33
+ handle_metrics_endpoint
34
+ else
35
+ [404, { "Content-Type" => "application/json" }, ['{"error":"Not Found"}']]
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ # Handle general health check
42
+ def handle_health_check
43
+ health_result = @health_checker.check_health
44
+ status_code = health_result[:status] == :healthy ? 200 : 503
45
+
46
+ [status_code, json_headers, [health_result.to_json]]
47
+ end
48
+
49
+ # Handle readiness check (can serve traffic)
50
+ def handle_readiness_check
51
+ # Check if application is ready to serve requests
52
+ ready = check_readiness
53
+ status_code = ready ? 200 : 503
54
+
55
+ response = {
56
+ status: ready ? "ready" : "not_ready",
57
+ timestamp: Time.now.iso8601
58
+ }
59
+
60
+ [status_code, json_headers, [response.to_json]]
61
+ end
62
+
63
+ # Handle liveness check (application is running)
64
+ def handle_liveness_check
65
+ # Simple liveness check - if we can respond, we're alive
66
+ response = {
67
+ status: "alive",
68
+ timestamp: Time.now.iso8601,
69
+ uptime: Process.clock_gettime(Process::CLOCK_MONOTONIC)
70
+ }
71
+
72
+ [200, json_headers, [response.to_json]]
73
+ end
74
+
75
+ # Handle metrics endpoint
76
+ def handle_metrics_endpoint
77
+ if defined?(Prometheus)
78
+ # Return Prometheus metrics format
79
+ metrics = Prometheus::Client.registry.metrics
80
+ prometheus_output = Prometheus::Client::Formats::Text.marshal(metrics)
81
+ [200, { "Content-Type" => "text/plain" }, [prometheus_output]]
82
+ else
83
+ # Return JSON metrics
84
+ metrics = A2A::Monitoring.metrics.current_metrics
85
+ [200, json_headers, [{ metrics: metrics }.to_json]]
86
+ end
87
+ end
88
+
89
+ def json_headers
90
+ { "Content-Type" => "application/json" }
91
+ end
92
+
93
+ def check_readiness
94
+ # Check database connectivity if using database storage
95
+ return false unless check_database_connection
96
+
97
+ # Check Redis connectivity if using Redis
98
+ return false unless check_redis_connection
99
+
100
+ # Check plugin system
101
+ return false unless check_plugins_loaded
102
+
103
+ true
104
+ end
105
+
106
+ def check_database_connection
107
+ return true unless A2A.config.rails_integration
108
+
109
+ if defined?(ActiveRecord)
110
+ ActiveRecord::Base.connection.active?
111
+ else
112
+ true
113
+ end
114
+ rescue StandardError
115
+ false
116
+ end
117
+
118
+ def check_redis_connection
119
+ redis_config = A2A.config.redis_config
120
+ return true unless redis_config && redis_config[:url]
121
+
122
+ if defined?(Redis)
123
+ redis = Redis.new(url: redis_config[:url])
124
+ redis.ping == "PONG"
125
+ else
126
+ true
127
+ end
128
+ rescue StandardError
129
+ false
130
+ end
131
+
132
+ def check_plugins_loaded
133
+ # Check if critical plugins are loaded
134
+ critical_plugins = A2A.config.get(:critical_plugins) || []
135
+
136
+ critical_plugins.all? do |plugin_name|
137
+ A2A::Plugin.loaded?(plugin_name)
138
+ end
139
+ end
140
+
141
+ def setup_default_checks
142
+ # Register default health checks
143
+ @health_checker.register_check(:configuration) do
144
+ A2A.config.validate!
145
+ { status: :healthy, message: "Configuration is valid" }
146
+ rescue StandardError => e
147
+ { status: :unhealthy, message: "Configuration error: #{e.message}" }
148
+ end
149
+
150
+ @health_checker.register_check(:memory_usage) do
151
+ # Check memory usage (basic check)
152
+ if defined?(GC)
153
+ stat = GC.stat
154
+ heap_used = stat[:heap_allocated_pages] * stat[:heap_page_size]
155
+
156
+ # Simple threshold check (adjust as needed)
157
+ if heap_used > 500_000_000 # 500MB
158
+ { status: :warning, message: "High memory usage: #{heap_used} bytes" }
159
+ else
160
+ { status: :healthy, message: "Memory usage: #{heap_used} bytes" }
161
+ end
162
+ else
163
+ { status: :healthy, message: "Memory check not available" }
164
+ end
165
+ end
166
+
167
+ @health_checker.register_check(:plugin_system) do
168
+ loaded_count = A2A::Plugin.loaded_plugins.size
169
+ registered_count = A2A::Plugin.registry.size
170
+
171
+ {
172
+ status: :healthy,
173
+ message: "Plugins: #{loaded_count}/#{registered_count} loaded"
174
+ }
175
+ end
176
+ end
177
+ end
178
+
179
+ ##
180
+ # Rack middleware for health endpoints
181
+ #
182
+ class HealthMiddleware
183
+ def initialize(app, health_endpoints = nil)
184
+ @app = app
185
+ @health_endpoints = health_endpoints || HealthEndpoints.new
186
+ end
187
+
188
+ def call(env)
189
+ # Check if this is a health endpoint request
190
+ if health_endpoint?(env["PATH_INFO"])
191
+ @health_endpoints.call(env)
192
+ else
193
+ @app.call(env)
194
+ end
195
+ end
196
+
197
+ private
198
+
199
+ def health_endpoint?(path)
200
+ path&.start_with?("/health") || path == "/metrics"
201
+ end
202
+ end
203
+ end
204
+ end