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,463 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "json"
5
+ require_relative "monitoring/metrics_collector"
6
+ require_relative "monitoring/distributed_tracing"
7
+ require_relative "monitoring/alerting"
8
+ require_relative "monitoring/health_endpoints"
9
+
10
+ ##
11
+ # Monitoring and metrics collection for A2A SDK
12
+ #
13
+ # Provides structured logging, metrics collection, and health checks
14
+ # with support for multiple backends including Prometheus.
15
+ #
16
+ module A2A
17
+ module Monitoring
18
+ class << self
19
+ # Global metrics collector
20
+ # @return [A2A::Monitoring::MetricsCollector]
21
+ attr_accessor :metrics
22
+
23
+ # Global logger with correlation ID support
24
+ # @return [A2A::Monitoring::StructuredLogger]
25
+ attr_accessor :logger
26
+
27
+ # Initialize monitoring system
28
+ # @param config [A2A::Configuration] Configuration instance
29
+ def initialize!(config = A2A.config)
30
+ @config = config
31
+ @metrics = MetricsCollector.new(config)
32
+ @logger = StructuredLogger.new(config)
33
+ @health_checks = HealthChecker.new(config)
34
+ end
35
+
36
+ # Get metrics collector
37
+ # @return [A2A::Monitoring::MetricsCollector]
38
+ def metrics
39
+ @metrics ||= MetricsCollector.new(A2A.config)
40
+ end
41
+
42
+ # Get structured logger
43
+ # @return [A2A::Monitoring::StructuredLogger]
44
+ def logger
45
+ @logger ||= StructuredLogger.new(A2A.config)
46
+ end
47
+
48
+ # Get health checker
49
+ # @return [A2A::Monitoring::HealthChecker]
50
+ def health_checks
51
+ @health_checks ||= HealthChecker.new(A2A.config)
52
+ end
53
+
54
+ # Record a metric
55
+ # @param name [String] Metric name
56
+ # @param value [Numeric] Metric value
57
+ # @param **labels [Hash] Metric labels
58
+ def record_metric(name, value, **labels)
59
+ metrics.record(name, value, **labels)
60
+ end
61
+
62
+ # Increment a counter
63
+ # @param name [String] Counter name
64
+ # @param **labels [Hash] Counter labels
65
+ def increment_counter(name, **labels)
66
+ metrics.increment(name, **labels)
67
+ end
68
+
69
+ # Record timing information
70
+ # @param name [String] Timer name
71
+ # @param duration [Numeric] Duration in seconds
72
+ # @param **labels [Hash] Timer labels
73
+ def record_timing(name, duration, **labels)
74
+ metrics.timing(name, duration, **labels)
75
+ end
76
+
77
+ # Time a block of code
78
+ # @param name [String] Timer name
79
+ # @param **labels [Hash] Timer labels
80
+ # @yield Block to time
81
+ # @return [Object] Block result
82
+ def time(name, **labels)
83
+ start_time = Time.now
84
+ result = yield
85
+ duration = Time.now - start_time
86
+ record_timing(name, duration, **labels)
87
+ result
88
+ rescue StandardError
89
+ duration = Time.now - start_time
90
+ record_timing(name, duration, status: "error", **labels)
91
+ raise
92
+ end
93
+
94
+ # Log with correlation ID
95
+ # @param level [Symbol] Log level
96
+ # @param message [String] Log message
97
+ # @param **context [Hash] Additional context
98
+ def log(level, message, **context)
99
+ logger.log(level, message, **context)
100
+ end
101
+ end
102
+
103
+ ##
104
+ # Metrics collection interface
105
+ #
106
+ class MetricsCollector
107
+ # Initialize metrics collector
108
+ # @param config [A2A::Configuration] Configuration instance
109
+ def initialize(config)
110
+ @config = config
111
+ @backends = []
112
+ @metrics_buffer = []
113
+ @mutex = Mutex.new
114
+
115
+ setup_backends
116
+ end
117
+
118
+ # Record a metric
119
+ # @param name [String] Metric name
120
+ # @param value [Numeric] Metric value
121
+ # @param **labels [Hash] Metric labels
122
+ def record(name, value, **labels)
123
+ metric = {
124
+ name: name,
125
+ value: value,
126
+ labels: labels,
127
+ timestamp: Time.now.to_f
128
+ }
129
+
130
+ @mutex.synchronize do
131
+ @metrics_buffer << metric
132
+ flush_if_needed
133
+ end
134
+
135
+ @backends.each { |backend| backend.record(name, value, **labels) }
136
+ end
137
+
138
+ # Increment a counter
139
+ # @param name [String] Counter name
140
+ # @param **labels [Hash] Counter labels
141
+ def increment(name, **labels)
142
+ record("#{name}_total", 1, **labels)
143
+ end
144
+
145
+ # Record timing information
146
+ # @param name [String] Timer name
147
+ # @param duration [Numeric] Duration in seconds
148
+ # @param **labels [Hash] Timer labels
149
+ def timing(name, duration, **labels)
150
+ record("#{name}_duration_seconds", duration, **labels)
151
+ end
152
+
153
+ # Add metrics backend
154
+ # @param backend [Object] Metrics backend
155
+ def add_backend(backend)
156
+ @backends << backend
157
+ end
158
+
159
+ # Get current metrics
160
+ # @return [Array<Hash>] Current metrics buffer
161
+ def current_metrics
162
+ @mutex.synchronize { @metrics_buffer.dup }
163
+ end
164
+
165
+ # Flush metrics buffer
166
+ def flush!
167
+ @mutex.synchronize do
168
+ @metrics_buffer.clear
169
+ end
170
+ end
171
+
172
+ private
173
+
174
+ def setup_backends
175
+ # Add Prometheus backend if available
176
+ add_backend(PrometheusBackend.new) if defined?(Prometheus)
177
+
178
+ # Add logging backend
179
+ add_backend(LoggingBackend.new(@config))
180
+ end
181
+
182
+ def flush_if_needed
183
+ return unless @metrics_buffer.size >= 100
184
+
185
+ # In a real implementation, you might want to flush to persistent storage
186
+ @config.logger&.debug("Metrics buffer size: #{@metrics_buffer.size}")
187
+ end
188
+ end
189
+
190
+ ##
191
+ # Structured logger with correlation ID support
192
+ #
193
+ class StructuredLogger
194
+ # Initialize structured logger
195
+ # @param config [A2A::Configuration] Configuration instance
196
+ def initialize(config)
197
+ @config = config
198
+ @base_logger = config.logger
199
+ @correlation_ids = {}
200
+ end
201
+
202
+ # Log with structured format
203
+ # @param level [Symbol] Log level
204
+ # @param message [String] Log message
205
+ # @param **context [Hash] Additional context
206
+ def log(level, message, **context)
207
+ return unless @base_logger
208
+
209
+ correlation_id = current_correlation_id
210
+
211
+ structured_data = {
212
+ timestamp: Time.now.iso8601,
213
+ level: level.to_s.upcase,
214
+ message: message,
215
+ correlation_id: correlation_id,
216
+ component: "a2a-ruby"
217
+ }.merge(context)
218
+
219
+ @base_logger.send(level, structured_data.to_json)
220
+ end
221
+
222
+ # Set correlation ID for current thread
223
+ # @param id [String] Correlation ID
224
+ def set_correlation_id(id)
225
+ @correlation_ids[Thread.current.object_id] = id
226
+ end
227
+
228
+ # Get correlation ID for current thread
229
+ # @return [String] Correlation ID
230
+ def current_correlation_id
231
+ @correlation_ids[Thread.current.object_id] || generate_correlation_id
232
+ end
233
+
234
+ # Clear correlation ID for current thread
235
+ def clear_correlation_id
236
+ @correlation_ids.delete(Thread.current.object_id)
237
+ end
238
+
239
+ # Execute block with correlation ID
240
+ # @param id [String] Correlation ID
241
+ # @yield Block to execute
242
+ # @return [Object] Block result
243
+ def with_correlation_id(id)
244
+ old_id = current_correlation_id
245
+ set_correlation_id(id)
246
+ yield
247
+ ensure
248
+ if old_id
249
+ set_correlation_id(old_id)
250
+ else
251
+ clear_correlation_id
252
+ end
253
+ end
254
+
255
+ private
256
+
257
+ def generate_correlation_id
258
+ SecureRandom.hex(8)
259
+ end
260
+ end
261
+
262
+ ##
263
+ # Health check system
264
+ #
265
+ class HealthChecker
266
+ # Initialize health checker
267
+ # @param config [A2A::Configuration] Configuration instance
268
+ def initialize(config)
269
+ @config = config
270
+ @checks = {}
271
+ end
272
+
273
+ # Register a health check
274
+ # @param name [Symbol] Check name
275
+ # @param check [Proc] Check procedure
276
+ def register_check(name, &check)
277
+ @checks[name] = check
278
+ end
279
+
280
+ # Run all health checks
281
+ # @return [Hash] Health check results
282
+ def check_health
283
+ results = {}
284
+ overall_status = :healthy
285
+
286
+ @checks.each do |name, check|
287
+ result = check.call
288
+ status = result[:status] || :healthy
289
+ results[name] = {
290
+ status: status,
291
+ message: result[:message],
292
+ timestamp: Time.now.iso8601
293
+ }
294
+
295
+ overall_status = :unhealthy if status == :unhealthy
296
+ rescue StandardError => e
297
+ results[name] = {
298
+ status: :error,
299
+ message: e.message,
300
+ timestamp: Time.now.iso8601
301
+ }
302
+ overall_status = :unhealthy
303
+ end
304
+
305
+ {
306
+ status: overall_status,
307
+ checks: results,
308
+ timestamp: Time.now.iso8601
309
+ }
310
+ end
311
+
312
+ # Check if system is healthy
313
+ # @return [Boolean]
314
+ def healthy?
315
+ check_health[:status] == :healthy
316
+ end
317
+ end
318
+
319
+ ##
320
+ # Prometheus metrics backend
321
+ #
322
+ class PrometheusBackend
323
+ def initialize
324
+ @registry = Prometheus::Client.registry
325
+ @counters = {}
326
+ @histograms = {}
327
+ end
328
+
329
+ # Record metric in Prometheus
330
+ # @param name [String] Metric name
331
+ # @param value [Numeric] Metric value
332
+ # @param **labels [Hash] Metric labels
333
+ def record(name, value, **labels)
334
+ if name.end_with?("_total")
335
+ increment_counter(name, **labels)
336
+ elsif name.end_with?("_duration_seconds")
337
+ record_histogram(name, value, **labels)
338
+ else
339
+ # Generic gauge
340
+ record_gauge(name, value, **labels)
341
+ end
342
+ end
343
+
344
+ private
345
+
346
+ def increment_counter(name, **labels)
347
+ counter = @counters[name] ||= @registry.counter(
348
+ name.to_sym,
349
+ docstring: "A2A counter: #{name}",
350
+ labels: labels.keys
351
+ )
352
+ counter.increment(labels: labels)
353
+ end
354
+
355
+ def record_histogram(name, value, **labels)
356
+ histogram = @histograms[name] ||= @registry.histogram(
357
+ name.to_sym,
358
+ docstring: "A2A histogram: #{name}",
359
+ labels: labels.keys
360
+ )
361
+ histogram.observe(value, labels: labels)
362
+ end
363
+
364
+ def record_gauge(name, value, **labels)
365
+ # Prometheus gauge implementation would go here
366
+ # For now, just log it
367
+ end
368
+ end
369
+
370
+ ##
371
+ # Logging metrics backend
372
+ #
373
+ class LoggingBackend
374
+ def initialize(config)
375
+ @config = config
376
+ @logger = config.logger
377
+ end
378
+
379
+ # Record metric to logs
380
+ # @param name [String] Metric name
381
+ # @param value [Numeric] Metric value
382
+ # @param **labels [Hash] Metric labels
383
+ def record(name, value, **labels)
384
+ return unless @logger
385
+
386
+ metric_data = {
387
+ metric_name: name,
388
+ metric_value: value,
389
+ metric_labels: labels,
390
+ timestamp: Time.now.iso8601
391
+ }
392
+
393
+ @logger.info("METRIC: #{metric_data.to_json}")
394
+ end
395
+ end
396
+
397
+ ##
398
+ # Request/Response instrumentation
399
+ #
400
+ module Instrumentation
401
+ # Instrument A2A request
402
+ # @param request [Hash] Request data
403
+ # @yield Block to execute
404
+ # @return [Object] Block result
405
+ def self.instrument_request(request)
406
+ method = request[:method] || "unknown"
407
+ labels = { method: method }
408
+
409
+ A2A::Monitoring.increment_counter("a2a_requests", **labels)
410
+
411
+ A2A::Monitoring.time("a2a_request_duration", **labels) do
412
+ correlation_id = request[:id] || SecureRandom.hex(8)
413
+
414
+ A2A::Monitoring.logger.with_correlation_id(correlation_id) do
415
+ A2A::Monitoring.log(:info, "Processing A2A request", method: method, request_id: correlation_id)
416
+
417
+ begin
418
+ result = yield
419
+ A2A::Monitoring.increment_counter("a2a_requests_success", **labels)
420
+ A2A::Monitoring.log(:info, "A2A request completed", method: method, request_id: correlation_id)
421
+ result
422
+ rescue StandardError => e
423
+ A2A::Monitoring.increment_counter("a2a_requests_error", **labels, error_type: e.class.name)
424
+ A2A::Monitoring.log(:error, "A2A request failed", method: method, request_id: correlation_id,
425
+ error: e.message)
426
+ raise
427
+ end
428
+ end
429
+ end
430
+ end
431
+
432
+ # Instrument task operations
433
+ # @param task_id [String] Task ID
434
+ # @param operation [String] Operation name
435
+ # @yield Block to execute
436
+ # @return [Object] Block result
437
+ def self.instrument_task(task_id, operation)
438
+ labels = { operation: operation }
439
+
440
+ A2A::Monitoring.increment_counter("a2a_task_operations", **labels)
441
+
442
+ A2A::Monitoring.time("a2a_task_operation_duration", **labels) do
443
+ A2A::Monitoring.logger.with_correlation_id(task_id) do
444
+ A2A::Monitoring.log(:info, "Task operation started", task_id: task_id, operation: operation)
445
+
446
+ begin
447
+ result = yield
448
+ A2A::Monitoring.increment_counter("a2a_task_operations_success", **labels)
449
+ A2A::Monitoring.log(:info, "Task operation completed", task_id: task_id, operation: operation)
450
+ result
451
+ rescue StandardError => e
452
+ A2A::Monitoring.increment_counter("a2a_task_operations_error",
453
+ **labels, error_type: e.class.name)
454
+ A2A::Monitoring.log(:error, "Task operation failed", task_id: task_id, operation: operation,
455
+ error: e.message)
456
+ raise
457
+ end
458
+ end
459
+ end
460
+ end
461
+ end
462
+ end
463
+ end