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,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Rate limiting interceptor using token bucket algorithm
5
+ #
6
+ # Implements client-side rate limiting to prevent overwhelming
7
+ # the target agent with too many requests.
8
+ #
9
+ module A2A
10
+ module Client
11
+ module Middleware
12
+ class RateLimitInterceptor
13
+ attr_reader :requests_per_second, :burst_size, :tokens, :last_refill
14
+
15
+ ##
16
+ # Initialize rate limit interceptor
17
+ #
18
+ # @param requests_per_second [Numeric] Maximum requests per second (default: 10)
19
+ # @param burst_size [Integer] Maximum burst size (default: 20)
20
+ def initialize(requests_per_second: 10, burst_size: 20)
21
+ @requests_per_second = requests_per_second.to_f
22
+ @burst_size = burst_size
23
+ @tokens = @burst_size.to_f
24
+ @last_refill = Time.now
25
+ @mutex = Mutex.new
26
+
27
+ validate_configuration!
28
+ end
29
+
30
+ ##
31
+ # Execute request with rate limiting
32
+ #
33
+ # @param request [Object] The request object
34
+ # @param context [Hash] Request context
35
+ # @param next_middleware [Proc] Next middleware in chain
36
+ # @return [Object] Response from next middleware
37
+ def call(request, context, next_middleware)
38
+ wait_for_token
39
+ next_middleware.call(request, context)
40
+ end
41
+
42
+ ##
43
+ # Check if a request can be made immediately
44
+ #
45
+ # @return [Boolean] True if request can be made without waiting
46
+ def can_make_request?
47
+ @mutex.synchronize do
48
+ refill_tokens
49
+ @tokens >= 1.0
50
+ end
51
+ end
52
+
53
+ ##
54
+ # Get current rate limit status
55
+ #
56
+ # @return [Hash] Rate limit status information
57
+ def status
58
+ @mutex.synchronize do
59
+ refill_tokens
60
+ {
61
+ requests_per_second: @requests_per_second,
62
+ burst_size: @burst_size,
63
+ available_tokens: @tokens.round(2),
64
+ tokens_full: @tokens >= @burst_size,
65
+ can_make_request: @tokens >= 1.0
66
+ }
67
+ end
68
+ end
69
+
70
+ ##
71
+ # Calculate time until next token is available
72
+ #
73
+ # @return [Float] Time in seconds until next token
74
+ def time_until_next_token
75
+ @mutex.synchronize do
76
+ refill_tokens
77
+ return 0.0 if @tokens >= 1.0
78
+
79
+ tokens_needed = 1.0 - @tokens
80
+ tokens_needed / @requests_per_second
81
+ end
82
+ end
83
+
84
+ ##
85
+ # Reset the rate limiter (useful for testing)
86
+ def reset!
87
+ @mutex.synchronize do
88
+ @tokens = @burst_size.to_f
89
+ @last_refill = Time.now
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ ##
96
+ # Wait for a token to become available
97
+ def wait_for_token
98
+ loop do
99
+ @mutex.synchronize do
100
+ refill_tokens
101
+
102
+ if @tokens >= 1.0
103
+ @tokens -= 1.0
104
+ return
105
+ end
106
+ end
107
+
108
+ # Calculate how long to sleep
109
+ sleep_time = time_until_next_token
110
+ sleep(sleep_time) if sleep_time.positive?
111
+ end
112
+ end
113
+
114
+ ##
115
+ # Refill tokens based on elapsed time
116
+ def refill_tokens
117
+ now = Time.now
118
+ elapsed = now - @last_refill
119
+
120
+ return unless elapsed.positive?
121
+
122
+ # Add tokens based on elapsed time
123
+ tokens_to_add = elapsed * @requests_per_second
124
+ @tokens = [@tokens + tokens_to_add, @burst_size].min
125
+ @last_refill = now
126
+ end
127
+
128
+ ##
129
+ # Validate configuration parameters
130
+ def validate_configuration!
131
+ raise ArgumentError, "requests_per_second must be positive" if @requests_per_second <= 0
132
+
133
+ raise ArgumentError, "burst_size must be positive" if @burst_size <= 0
134
+
135
+ return unless @burst_size < @requests_per_second
136
+
137
+ warn "Warning: burst_size (#{@burst_size}) is less than requests_per_second (#{@requests_per_second})"
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Retry interceptor with exponential backoff
5
+ #
6
+ # Automatically retries failed requests with configurable backoff
7
+ # strategy and error filtering.
8
+ #
9
+ module A2A
10
+ module Client
11
+ module Middleware
12
+ class RetryInterceptor
13
+ attr_reader :max_attempts, :initial_delay, :max_delay, :backoff_multiplier, :retryable_errors
14
+
15
+ # Default retryable error classes
16
+ DEFAULT_RETRYABLE_ERRORS = [
17
+ A2A::Errors::TimeoutError,
18
+ A2A::Errors::HTTPError,
19
+ A2A::Errors::TransportError,
20
+ A2A::Errors::AgentUnavailable,
21
+ A2A::Errors::ResourceExhausted,
22
+ Faraday::TimeoutError,
23
+ Faraday::ConnectionFailed
24
+ ].freeze
25
+
26
+ ##
27
+ # Initialize retry interceptor
28
+ #
29
+ # @param max_attempts [Integer] Maximum number of retry attempts (default: 3)
30
+ # @param initial_delay [Float] Initial delay in seconds (default: 1.0)
31
+ # @param max_delay [Float] Maximum delay in seconds (default: 60.0)
32
+ # @param backoff_multiplier [Float] Backoff multiplier (default: 2.0)
33
+ # @param retryable_errors [Array<Class>] List of retryable error classes
34
+ def initialize(max_attempts: 3, initial_delay: 1.0, max_delay: 60.0,
35
+ backoff_multiplier: 2.0, retryable_errors: nil)
36
+ @max_attempts = max_attempts
37
+ @initial_delay = initial_delay
38
+ @max_delay = max_delay
39
+ @backoff_multiplier = backoff_multiplier
40
+ @retryable_errors = retryable_errors || DEFAULT_RETRYABLE_ERRORS
41
+
42
+ validate_configuration!
43
+ end
44
+
45
+ ##
46
+ # Execute request with retry logic
47
+ #
48
+ # @param request [Object] The request object
49
+ # @param context [Hash] Request context
50
+ # @param next_middleware [Proc] Next middleware in chain
51
+ # @return [Object] Response from successful request
52
+ def call(request, context, next_middleware)
53
+ attempt = 0
54
+ last_error = nil
55
+
56
+ loop do
57
+ attempt += 1
58
+
59
+ begin
60
+ return next_middleware.call(request, context)
61
+ rescue StandardError => e
62
+ last_error = e
63
+
64
+ # Check if we should retry
65
+ raise e unless should_retry?(e, attempt)
66
+
67
+ delay = calculate_delay(attempt)
68
+ context[:retry_attempt] = attempt
69
+ context[:retry_delay] = delay
70
+
71
+ sleep(delay) if delay.positive?
72
+ next
73
+
74
+ # Re-raise the error if we shouldn't retry or max attempts reached
75
+ end
76
+ end
77
+ end
78
+
79
+ ##
80
+ # Check if an error should trigger a retry
81
+ #
82
+ # @param error [Exception] The error that occurred
83
+ # @param attempt [Integer] Current attempt number
84
+ # @return [Boolean] True if should retry
85
+ def should_retry?(error, attempt)
86
+ return false if attempt >= @max_attempts
87
+ return false unless retryable_error?(error)
88
+
89
+ # Check for specific HTTP status codes that shouldn't be retried
90
+ if error.respond_to?(:status_code)
91
+ case error.status_code
92
+ when 400, 401, 403, 404, 422 # Client errors - don't retry
93
+ return false
94
+ when 429 # Rate limited - should retry
95
+ return true
96
+ when 500..599 # Server errors - should retry
97
+ return true
98
+ end
99
+ end
100
+
101
+ true
102
+ end
103
+
104
+ ##
105
+ # Check if an error is retryable
106
+ #
107
+ # @param error [Exception] The error to check
108
+ # @return [Boolean] True if error is retryable
109
+ def retryable_error?(error)
110
+ @retryable_errors.any? { |error_class| error.is_a?(error_class) }
111
+ end
112
+
113
+ ##
114
+ # Calculate delay for the given attempt
115
+ #
116
+ # @param attempt [Integer] Current attempt number (1-based)
117
+ # @return [Float] Delay in seconds
118
+ def calculate_delay(attempt)
119
+ return 0 if attempt <= 1
120
+
121
+ # Exponential backoff: initial_delay * (backoff_multiplier ^ (attempt - 2))
122
+ delay = @initial_delay * (@backoff_multiplier**(attempt - 2))
123
+
124
+ # Add jitter to prevent thundering herd
125
+ jitter = delay * 0.1 * rand
126
+ delay += jitter
127
+
128
+ # Cap at max_delay
129
+ [delay, @max_delay].min
130
+ end
131
+
132
+ ##
133
+ # Get retry statistics
134
+ #
135
+ # @return [Hash] Retry configuration and statistics
136
+ def stats
137
+ {
138
+ max_attempts: @max_attempts,
139
+ initial_delay: @initial_delay,
140
+ max_delay: @max_delay,
141
+ backoff_multiplier: @backoff_multiplier,
142
+ retryable_errors: @retryable_errors.map(&:name)
143
+ }
144
+ end
145
+
146
+ private
147
+
148
+ ##
149
+ # Validate configuration parameters
150
+ def validate_configuration!
151
+ raise ArgumentError, "max_attempts must be positive" if @max_attempts <= 0
152
+ raise ArgumentError, "initial_delay must be non-negative" if @initial_delay.negative?
153
+ raise ArgumentError, "max_delay must be positive" if @max_delay <= 0
154
+ raise ArgumentError, "backoff_multiplier must be positive" if @backoff_multiplier <= 0
155
+
156
+ raise ArgumentError, "initial_delay cannot be greater than max_delay" if @initial_delay > @max_delay
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "middleware/retry_interceptor"
4
+ require_relative "middleware/logging_interceptor"
5
+ require_relative "middleware/rate_limit_interceptor"
6
+ require_relative "middleware/circuit_breaker_interceptor"
7
+
8
+ ##
9
+ # Client middleware system for A2A requests
10
+ #
11
+ # Provides a collection of middleware interceptors for handling
12
+ # cross-cutting concerns like retries, logging, rate limiting,
13
+ # and circuit breaking.
14
+ #
15
+ # @example Using middleware with a client
16
+ # retry_middleware = A2A::Client::Middleware::RetryInterceptor.new(max_attempts: 3)
17
+ # logging_middleware = A2A::Client::Middleware::LoggingInterceptor.new
18
+ #
19
+ # client = A2A::Client::HttpClient.new(
20
+ # 'https://agent.example.com',
21
+ # middleware: [retry_middleware, logging_middleware]
22
+ # )
23
+ #
24
+ module A2A
25
+ module Client
26
+ module Middleware
27
+ ##
28
+ # Base class for middleware interceptors
29
+ #
30
+ class Base
31
+ ##
32
+ # Call the middleware
33
+ #
34
+ # @param request [Object] The request object
35
+ # @param context [Hash] Request context
36
+ # @param next_middleware [Proc] Next middleware in chain
37
+ # @return [Object] Response from next middleware
38
+ def call(request, context, next_middleware)
39
+ raise NotImplementedError, "#{self.class}#call must be implemented"
40
+ end
41
+ end
42
+
43
+ ##
44
+ # Create middleware from configuration
45
+ #
46
+ # @param config [Hash] Middleware configuration
47
+ # @return [Base] Configured middleware instance
48
+ def self.from_config(config)
49
+ type = config["type"] || config[:type]
50
+
51
+ case type
52
+ when "retry"
53
+ RetryInterceptor.new(
54
+ max_attempts: config["max_attempts"] || config[:max_attempts] || 3,
55
+ initial_delay: config["initial_delay"] || config[:initial_delay] || 1.0,
56
+ max_delay: config["max_delay"] || config[:max_delay] || 60.0,
57
+ backoff_multiplier: config["backoff_multiplier"] || config[:backoff_multiplier] || 2.0,
58
+ retryable_errors: config["retryable_errors"] || config[:retryable_errors]
59
+ )
60
+ when "logging"
61
+ LoggingInterceptor.new(
62
+ logger: config["logger"] || config[:logger],
63
+ log_level: config["log_level"] || config[:log_level] || :info,
64
+ log_requests: config["log_requests"] || config[:log_requests] || true,
65
+ log_responses: config["log_responses"] || config[:log_responses] || true,
66
+ log_errors: config["log_errors"] || config[:log_errors] || true
67
+ )
68
+ when "rate_limit"
69
+ RateLimitInterceptor.new(
70
+ requests_per_second: config["requests_per_second"] || config[:requests_per_second] || 10,
71
+ burst_size: config["burst_size"] || config[:burst_size] || 20
72
+ )
73
+ when "circuit_breaker"
74
+ CircuitBreakerInterceptor.new(
75
+ failure_threshold: config["failure_threshold"] || config[:failure_threshold] || 5,
76
+ timeout: config["timeout"] || config[:timeout] || 60,
77
+ expected_errors: config["expected_errors"] || config[:expected_errors]
78
+ )
79
+ else
80
+ raise ArgumentError, "Unknown middleware type: #{type}"
81
+ end
82
+ end
83
+
84
+ ##
85
+ # Create default middleware stack
86
+ #
87
+ # @param config [Hash] Configuration options
88
+ # @return [Array<Base>] Default middleware stack
89
+ def self.default_stack(config = {})
90
+ stack = []
91
+
92
+ # Add retry middleware
93
+ stack << RetryInterceptor.new if config.fetch("retry", true)
94
+
95
+ # Add logging middleware
96
+ stack << LoggingInterceptor.new if config.fetch("logging", true)
97
+
98
+ # Add rate limiting if configured
99
+ if config["rate_limit"]
100
+ stack << RateLimitInterceptor.new(
101
+ requests_per_second: config["rate_limit"]["requests_per_second"] || 10
102
+ )
103
+ end
104
+
105
+ # Add circuit breaker if configured
106
+ if config["circuit_breaker"]
107
+ stack << CircuitBreakerInterceptor.new(
108
+ failure_threshold: config["circuit_breaker"]["failure_threshold"] || 5
109
+ )
110
+ end
111
+
112
+ stack
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ module Client
5
+ ##
6
+ # Performance tracking functionality for HTTP clients
7
+ #
8
+ module PerformanceTracker
9
+ ##
10
+ # Initialize performance tracking
11
+ #
12
+ def initialize_performance_tracking
13
+ @performance_stats = {
14
+ requests_count: 0,
15
+ total_time: 0.0,
16
+ avg_response_time: 0.0,
17
+ cache_hits: 0,
18
+ cache_misses: 0
19
+ }
20
+ @stats_mutex = Mutex.new
21
+ end
22
+
23
+ ##
24
+ # Get performance statistics
25
+ #
26
+ # @return [Hash] Performance statistics
27
+ def performance_stats
28
+ @stats_mutex.synchronize { @performance_stats.dup }
29
+ end
30
+
31
+ ##
32
+ # Reset performance statistics
33
+ #
34
+ def reset_performance_stats!
35
+ @stats_mutex.synchronize do
36
+ @performance_stats = {
37
+ requests_count: 0,
38
+ total_time: 0.0,
39
+ avg_response_time: 0.0,
40
+ cache_hits: 0,
41
+ cache_misses: 0
42
+ }
43
+ end
44
+ end
45
+
46
+ ##
47
+ # Record request performance metrics
48
+ #
49
+ # @param duration [Float] Request duration in seconds
50
+ def record_request_performance(duration)
51
+ @stats_mutex.synchronize do
52
+ @performance_stats[:requests_count] += 1
53
+ @performance_stats[:total_time] += duration
54
+ @performance_stats[:avg_response_time] =
55
+ @performance_stats[:total_time] / @performance_stats[:requests_count]
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ class Configuration
5
+ # Module for handling default configuration values
6
+ module Defaults
7
+ private
8
+
9
+ # Load default configuration values
10
+ def load_defaults
11
+ @default_timeout = 30
12
+ @log_level = :info
13
+ @protocol_version = "0.3.0"
14
+ @default_transport = "JSONRPC"
15
+ @streaming_enabled = true
16
+ @push_notifications_enabled = true
17
+ @default_input_modes = ["text/plain", "application/json"]
18
+ @default_output_modes = ["text/plain", "application/json"]
19
+ @redis_config = { url: "redis://localhost:6379/0" }
20
+ @rails_integration = defined?(Rails) ? true : false
21
+ @mount_path = "/a2a"
22
+ @auto_mount = true
23
+ @middleware_enabled = true
24
+ @authentication_required = false
25
+ @cors_enabled = true
26
+ @rate_limiting_enabled = false
27
+ @logging_enabled = true
28
+ @webhook_authentication_required = false
29
+ @logger = nil
30
+ @user_agent = "A2A-Ruby/#{A2A::VERSION}"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ class Configuration
5
+ # Module for loading configuration from environment variables
6
+ module EnvironmentLoader
7
+ private
8
+
9
+ # Load configuration from environment variables
10
+ def load_from_environment
11
+ @default_timeout = env_int("A2A_DEFAULT_TIMEOUT", @default_timeout)
12
+ @log_level = env_symbol("A2A_LOG_LEVEL", @log_level)
13
+ @protocol_version = env_string("A2A_PROTOCOL_VERSION", @protocol_version)
14
+ @default_transport = env_string("A2A_DEFAULT_TRANSPORT", @default_transport)
15
+ @streaming_enabled = env_bool("A2A_STREAMING_ENABLED", @streaming_enabled)
16
+ @push_notifications_enabled = env_bool("A2A_PUSH_NOTIFICATIONS_ENABLED", @push_notifications_enabled)
17
+ @default_input_modes = env_array("A2A_DEFAULT_INPUT_MODES", @default_input_modes)
18
+ @default_output_modes = env_array("A2A_DEFAULT_OUTPUT_MODES", @default_output_modes)
19
+ @rails_integration = env_bool("A2A_RAILS_INTEGRATION", @rails_integration)
20
+ @mount_path = env_string("A2A_MOUNT_PATH", @mount_path)
21
+ @auto_mount = env_bool("A2A_AUTO_MOUNT", @auto_mount)
22
+ @middleware_enabled = env_bool("A2A_MIDDLEWARE_ENABLED", @middleware_enabled)
23
+ @authentication_required = env_bool("A2A_AUTHENTICATION_REQUIRED", @authentication_required)
24
+ @cors_enabled = env_bool("A2A_CORS_ENABLED", @cors_enabled)
25
+ @rate_limiting_enabled = env_bool("A2A_RATE_LIMITING_ENABLED", @rate_limiting_enabled)
26
+ @logging_enabled = env_bool("A2A_LOGGING_ENABLED", @logging_enabled)
27
+ @webhook_authentication_required = env_bool("A2A_WEBHOOK_AUTHENTICATION_REQUIRED",
28
+ @webhook_authentication_required)
29
+ @user_agent = env_string("A2A_USER_AGENT", @user_agent)
30
+
31
+ # Redis configuration from environment
32
+ redis_url = ENV["REDIS_URL"] || ENV.fetch("A2A_REDIS_URL", nil)
33
+ return unless redis_url
34
+
35
+ @redis_config = { url: redis_url }
36
+ end
37
+
38
+ # Detect current environment
39
+ def detect_environment
40
+ return ENV["A2A_ENV"] if ENV["A2A_ENV"]
41
+ return ENV["RAILS_ENV"] if ENV["RAILS_ENV"]
42
+ return ENV["RACK_ENV"] if ENV["RACK_ENV"]
43
+ return rails_environment if rails_environment
44
+
45
+ "development"
46
+ end
47
+
48
+ # Environment variable helpers
49
+ def env_string(key, default)
50
+ ENV[key] || default
51
+ end
52
+
53
+ def env_int(key, default)
54
+ value = ENV.fetch(key, nil)
55
+ value ? value.to_i : default
56
+ end
57
+
58
+ def env_bool(key, default)
59
+ value = ENV.fetch(key, nil)
60
+ return default if value.nil?
61
+
62
+ %w[true yes 1 on].include?(value.downcase)
63
+ end
64
+
65
+ def env_symbol(key, default)
66
+ value = ENV.fetch(key, nil)
67
+ value ? value.to_sym : default
68
+ end
69
+
70
+ def env_array(key, default)
71
+ value = ENV.fetch(key, nil)
72
+ value ? value.split(",").map(&:strip) : default
73
+ end
74
+ end
75
+ end
76
+ end