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,382 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../errors"
4
+
5
+ module A2A
6
+ module Server
7
+ module Middleware
8
+ ##
9
+ # Rate limiting middleware for A2A requests
10
+ #
11
+ # Implements rate limiting using various strategies including
12
+ # in-memory, Redis-backed, and sliding window algorithms.
13
+ #
14
+ # @example Basic usage
15
+ # middleware = RateLimitMiddleware.new(
16
+ # limit: 100,
17
+ # window: 3600, # 1 hour
18
+ # strategy: :sliding_window
19
+ # )
20
+ #
21
+ class RateLimitMiddleware
22
+ attr_reader :limit, :window, :strategy, :store
23
+
24
+ # Rate limiting strategies
25
+ STRATEGIES = %i[fixed_window sliding_window token_bucket].freeze
26
+
27
+ ##
28
+ # Initialize rate limiting middleware
29
+ #
30
+ # @param limit [Integer] Maximum number of requests per window
31
+ # @param window [Integer] Time window in seconds
32
+ # @param strategy [Symbol] Rate limiting strategy
33
+ # @param store [Object] Storage backend (defaults to in-memory)
34
+ # @param key_generator [Proc] Custom key generator for rate limiting
35
+ def initialize(limit: 100, window: 3600, strategy: :sliding_window,
36
+ store: nil, key_generator: nil)
37
+ @limit = limit
38
+ @window = window
39
+ @strategy = strategy
40
+ @store = store || InMemoryStore.new
41
+ @key_generator = key_generator || method(:default_key_generator)
42
+
43
+ validate_strategy!
44
+ end
45
+
46
+ ##
47
+ # Process rate limiting for a request
48
+ #
49
+ # @param request [A2A::Protocol::Request] The JSON-RPC request
50
+ # @param context [A2A::Server::Context] The request context
51
+ # @yield Block to continue the middleware chain
52
+ # @return [Object] The result from the next middleware or handler
53
+ # @raise [A2A::Errors::RateLimitExceeded] If rate limit is exceeded
54
+ def call(request, context)
55
+ # Generate rate limiting key
56
+ key = @key_generator.call(request, context)
57
+
58
+ # Check rate limit
59
+ unless check_rate_limit(key)
60
+ raise A2A::Errors::RateLimitExceeded, "Rate limit exceeded: #{@limit} requests per #{@window} seconds"
61
+ end
62
+
63
+ # Continue to next middleware
64
+ yield
65
+ end
66
+
67
+ ##
68
+ # Check if request is within rate limit
69
+ #
70
+ # @param key [String] The rate limiting key
71
+ # @return [Boolean] True if within limit, false otherwise
72
+ def check_rate_limit(key)
73
+ case @strategy
74
+ when :fixed_window
75
+ check_fixed_window(key)
76
+ when :sliding_window
77
+ check_sliding_window(key)
78
+ when :token_bucket
79
+ check_token_bucket(key)
80
+ else
81
+ true # Fallback to allow request
82
+ end
83
+ end
84
+
85
+ ##
86
+ # Get current rate limit status for a key
87
+ #
88
+ # @param key [String] The rate limiting key
89
+ # @return [Hash] Status information
90
+ def status(key)
91
+ case @strategy
92
+ when :fixed_window
93
+ fixed_window_status(key)
94
+ when :sliding_window
95
+ sliding_window_status(key)
96
+ when :token_bucket
97
+ token_bucket_status(key)
98
+ else
99
+ { limit: @limit, remaining: @limit, reset_time: nil }
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ ##
106
+ # Validate the rate limiting strategy
107
+ def validate_strategy!
108
+ return if STRATEGIES.include?(@strategy)
109
+
110
+ raise ArgumentError, "Invalid strategy: #{@strategy}. Must be one of: #{STRATEGIES.join(', ')}"
111
+ end
112
+
113
+ ##
114
+ # Default key generator based on authentication or IP
115
+ #
116
+ # @param request [A2A::Protocol::Request] The request
117
+ # @param context [A2A::Server::Context] The context
118
+ # @return [String] The rate limiting key
119
+ def default_key_generator(_request, context)
120
+ # Try to use authenticated user/API key
121
+ if context.authenticated?
122
+ auth_data = context.instance_variable_get(:@auth_schemes)&.values&.first
123
+ return "user:#{auth_data[:username] || auth_data[:api_key] || auth_data[:token]}" if auth_data.is_a?(Hash)
124
+ end
125
+
126
+ # Fall back to IP address if available
127
+ ip = context.get_metadata(:remote_ip) || context.get_metadata("REMOTE_ADDR")
128
+ return "ip:#{ip}" if ip
129
+
130
+ # Default fallback
131
+ "anonymous"
132
+ end
133
+
134
+ ##
135
+ # Fixed window rate limiting
136
+ #
137
+ # @param key [String] The rate limiting key
138
+ # @return [Boolean] True if within limit
139
+ def check_fixed_window(key)
140
+ now = Time.now.to_i
141
+ window_start = (now / @window) * @window
142
+ window_key = "#{key}:#{window_start}"
143
+
144
+ current_count = @store.get(window_key) || 0
145
+
146
+ if current_count >= @limit
147
+ false
148
+ else
149
+ @store.increment(window_key, expires_at: window_start + @window)
150
+ true
151
+ end
152
+ end
153
+
154
+ ##
155
+ # Sliding window rate limiting
156
+ #
157
+ # @param key [String] The rate limiting key
158
+ # @return [Boolean] True if within limit
159
+ def check_sliding_window(key)
160
+ now = Time.now.to_f
161
+ window_start = now - @window
162
+
163
+ # Get timestamps of requests in the current window
164
+ timestamps = @store.get_list("#{key}:timestamps") || []
165
+
166
+ # Remove old timestamps
167
+ timestamps = timestamps.select { |ts| ts > window_start }
168
+
169
+ if timestamps.length >= @limit
170
+ false
171
+ else
172
+ # Add current timestamp
173
+ timestamps << now
174
+ @store.set_list("#{key}:timestamps", timestamps, expires_at: now + @window)
175
+ true
176
+ end
177
+ end
178
+
179
+ ##
180
+ # Token bucket rate limiting
181
+ #
182
+ # @param key [String] The rate limiting key
183
+ # @return [Boolean] True if within limit
184
+ def check_token_bucket(key)
185
+ now = Time.now.to_f
186
+ bucket_key = "#{key}:bucket"
187
+
188
+ # Get current bucket state
189
+ bucket = @store.get(bucket_key) || { tokens: @limit, last_refill: now }
190
+
191
+ # Calculate tokens to add based on time elapsed
192
+ time_elapsed = now - bucket[:last_refill]
193
+ tokens_to_add = (time_elapsed / @window) * @limit
194
+
195
+ # Refill bucket
196
+ bucket[:tokens] = [@limit, bucket[:tokens] + tokens_to_add].min
197
+ bucket[:last_refill] = now
198
+
199
+ if bucket[:tokens] >= 1
200
+ bucket[:tokens] -= 1
201
+ @store.set(bucket_key, bucket, expires_at: now + (@window * 2))
202
+ true
203
+ else
204
+ @store.set(bucket_key, bucket, expires_at: now + (@window * 2))
205
+ false
206
+ end
207
+ end
208
+
209
+ ##
210
+ # Get fixed window status
211
+ def fixed_window_status(key)
212
+ now = Time.now.to_i
213
+ window_start = (now / @window) * @window
214
+ window_key = "#{key}:#{window_start}"
215
+
216
+ current_count = @store.get(window_key) || 0
217
+ reset_time = window_start + @window
218
+
219
+ {
220
+ limit: @limit,
221
+ remaining: [@limit - current_count, 0].max,
222
+ reset_time: Time.zone.at(reset_time),
223
+ window_start: Time.zone.at(window_start)
224
+ }
225
+ end
226
+
227
+ ##
228
+ # Get sliding window status
229
+ def sliding_window_status(key)
230
+ now = Time.now.to_f
231
+ window_start = now - @window
232
+
233
+ timestamps = @store.get_list("#{key}:timestamps") || []
234
+ current_count = timestamps.count { |ts| ts > window_start }
235
+
236
+ {
237
+ limit: @limit,
238
+ remaining: [@limit - current_count, 0].max,
239
+ reset_time: nil, # No fixed reset time for sliding window
240
+ window_start: Time.zone.at(window_start)
241
+ }
242
+ end
243
+
244
+ ##
245
+ # Get token bucket status
246
+ def token_bucket_status(key)
247
+ now = Time.now.to_f
248
+ bucket_key = "#{key}:bucket"
249
+
250
+ bucket = @store.get(bucket_key) || { tokens: @limit, last_refill: now }
251
+
252
+ # Calculate current tokens
253
+ time_elapsed = now - bucket[:last_refill]
254
+ tokens_to_add = (time_elapsed / @window) * @limit
255
+ current_tokens = [@limit, bucket[:tokens] + tokens_to_add].min
256
+
257
+ {
258
+ limit: @limit,
259
+ remaining: current_tokens.floor,
260
+ reset_time: nil, # Continuous refill
261
+ tokens: current_tokens
262
+ }
263
+ end
264
+ end
265
+
266
+ ##
267
+ # In-memory storage for rate limiting
268
+ #
269
+ class InMemoryStore
270
+ def initialize
271
+ @data = {}
272
+ @mutex = Mutex.new
273
+ end
274
+
275
+ def get(key)
276
+ @mutex.synchronize do
277
+ entry = @data[key]
278
+ return nil unless entry
279
+
280
+ # Check expiration
281
+ if entry[:expires_at] && Time.now.to_f > entry[:expires_at]
282
+ @data.delete(key)
283
+ return nil
284
+ end
285
+
286
+ entry[:value]
287
+ end
288
+ end
289
+
290
+ def set(key, value, expires_at: nil)
291
+ @mutex.synchronize do
292
+ @data[key] = {
293
+ value: value,
294
+ expires_at: expires_at
295
+ }
296
+ end
297
+ end
298
+
299
+ def increment(key, expires_at: nil)
300
+ @mutex.synchronize do
301
+ entry = @data[key] || { value: 0, expires_at: expires_at }
302
+
303
+ # Check expiration
304
+ entry = { value: 0, expires_at: expires_at } if entry[:expires_at] && Time.now.to_f > entry[:expires_at]
305
+
306
+ entry[:value] += 1
307
+ entry[:expires_at] = expires_at if expires_at
308
+ @data[key] = entry
309
+
310
+ entry[:value]
311
+ end
312
+ end
313
+
314
+ def get_list(key)
315
+ get(key)
316
+ end
317
+
318
+ def set_list(key, list, expires_at: nil)
319
+ set(key, list, expires_at: expires_at)
320
+ end
321
+
322
+ def clear
323
+ @mutex.synchronize do
324
+ @data.clear
325
+ end
326
+ end
327
+
328
+ def size
329
+ @mutex.synchronize do
330
+ @data.size
331
+ end
332
+ end
333
+ end
334
+
335
+ ##
336
+ # Redis-backed storage for rate limiting
337
+ #
338
+ class RedisStore
339
+ def initialize(redis_client)
340
+ @redis = redis_client
341
+ end
342
+
343
+ def get(key)
344
+ value = @redis.get(key)
345
+ value ? JSON.parse(value) : nil
346
+ rescue JSON::ParserError
347
+ nil
348
+ end
349
+
350
+ def set(key, value, expires_at: nil)
351
+ json_value = JSON.generate(value)
352
+
353
+ if expires_at
354
+ ttl = (expires_at - Time.now.to_f).ceil
355
+ @redis.setex(key, ttl, json_value) if ttl.positive?
356
+ else
357
+ @redis.set(key, json_value)
358
+ end
359
+ end
360
+
361
+ def increment(key, expires_at: nil)
362
+ result = @redis.incr(key)
363
+
364
+ if expires_at && result == 1
365
+ ttl = (expires_at - Time.now.to_f).ceil
366
+ @redis.expire(key, ttl) if ttl.positive?
367
+ end
368
+
369
+ result
370
+ end
371
+
372
+ def get_list(key)
373
+ get(key)
374
+ end
375
+
376
+ def set_list(key, list, expires_at: nil)
377
+ set(key, list, expires_at: expires_at)
378
+ end
379
+ end
380
+ end
381
+ end
382
+ end
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "middleware/authentication_middleware"
4
+ require_relative "middleware/rate_limit_middleware"
5
+ require_relative "middleware/logging_middleware"
6
+ require_relative "middleware/cors_middleware"
7
+
8
+ ##
9
+ # Server middleware for A2A request processing
10
+ #
11
+ # This module provides various middleware components for A2A servers,
12
+ # including authentication, rate limiting, logging, and CORS support.
13
+ #
14
+ # @example Using middleware with a handler
15
+ # handler = A2A::Server::Handler.new(agent)
16
+ # handler.add_middleware(A2A::Server::Middleware::AuthenticationMiddleware.new)
17
+ # handler.add_middleware(A2A::Server::Middleware::LoggingMiddleware.new)
18
+ #
19
+ module A2A
20
+ module Server
21
+ module Middleware
22
+ ##
23
+ # Middleware registry for managing middleware instances
24
+ #
25
+ class Registry
26
+ def initialize
27
+ @middleware = []
28
+ end
29
+
30
+ ##
31
+ # Add middleware to the registry
32
+ #
33
+ # @param middleware [Object] Middleware instance
34
+ # @param options [Hash] Middleware options
35
+ def add(middleware, **options)
36
+ @middleware << { instance: middleware, options: options }
37
+ end
38
+
39
+ ##
40
+ # Remove middleware from the registry
41
+ #
42
+ # @param middleware [Object] Middleware instance to remove
43
+ def remove(middleware)
44
+ @middleware.reject! { |m| m[:instance] == middleware }
45
+ end
46
+
47
+ ##
48
+ # Get all middleware instances
49
+ #
50
+ # @return [Array] Array of middleware instances
51
+ def all
52
+ @middleware.pluck(:instance)
53
+ end
54
+
55
+ ##
56
+ # Clear all middleware
57
+ def clear
58
+ @middleware.clear
59
+ end
60
+
61
+ ##
62
+ # Get middleware count
63
+ #
64
+ # @return [Integer] Number of registered middleware
65
+ def count
66
+ @middleware.size
67
+ end
68
+
69
+ ##
70
+ # Check if middleware is registered
71
+ #
72
+ # @param middleware [Object] Middleware instance to check
73
+ # @return [Boolean] True if registered
74
+ def include?(middleware)
75
+ @middleware.any? { |m| m[:instance] == middleware }
76
+ end
77
+
78
+ ##
79
+ # Execute middleware chain
80
+ #
81
+ # @param request [A2A::Protocol::Request] The request
82
+ # @param context [A2A::Server::Context] The request context
83
+ # @yield Block to execute after all middleware
84
+ # @return [Object] Result from the block
85
+ def call(request, context, &block)
86
+ chain = block
87
+
88
+ # Build middleware chain from the end backwards
89
+ @middleware.reverse_each do |middleware_def|
90
+ middleware = middleware_def[:instance]
91
+ current_chain = chain
92
+
93
+ chain = lambda do
94
+ if middleware.respond_to?(:call)
95
+ middleware.call(request, context) { current_chain.call }
96
+ else
97
+ current_chain.call
98
+ end
99
+ end
100
+ end
101
+
102
+ # Execute the chain
103
+ chain.call
104
+ end
105
+ end
106
+
107
+ ##
108
+ # Middleware builder for creating middleware stacks
109
+ #
110
+ class Builder
111
+ def initialize
112
+ @registry = Registry.new
113
+ end
114
+
115
+ ##
116
+ # Add authentication middleware
117
+ #
118
+ # @param options [Hash] Authentication options
119
+ def use_authentication(**options)
120
+ @registry.add(AuthenticationMiddleware.new(**options))
121
+ self
122
+ end
123
+
124
+ ##
125
+ # Add rate limiting middleware
126
+ #
127
+ # @param options [Hash] Rate limiting options
128
+ def use_rate_limiting(**options)
129
+ @registry.add(RateLimitMiddleware.new(**options))
130
+ self
131
+ end
132
+
133
+ ##
134
+ # Add logging middleware
135
+ #
136
+ # @param options [Hash] Logging options
137
+ def use_logging(**options)
138
+ @registry.add(LoggingMiddleware.new(**options))
139
+ self
140
+ end
141
+
142
+ ##
143
+ # Add CORS middleware
144
+ #
145
+ # @param options [Hash] CORS options
146
+ def use_cors(**options)
147
+ @registry.add(CorsMiddleware.new(**options))
148
+ self
149
+ end
150
+
151
+ ##
152
+ # Add custom middleware
153
+ #
154
+ # @param middleware [Object] Middleware instance
155
+ # @param options [Hash] Middleware options
156
+ def use(middleware, **options)
157
+ @registry.add(middleware, **options)
158
+ self
159
+ end
160
+
161
+ ##
162
+ # Build the middleware registry
163
+ #
164
+ # @return [Registry] The built middleware registry
165
+ def build
166
+ @registry
167
+ end
168
+
169
+ ##
170
+ # Execute the middleware stack
171
+ #
172
+ # @param request [A2A::Protocol::Request] The request
173
+ # @param context [A2A::Server::Context] The request context
174
+ # @yield Block to execute after all middleware
175
+ # @return [Object] Result from the block
176
+ def call(request, context, &block)
177
+ @registry.call(request, context, &block)
178
+ end
179
+ end
180
+
181
+ ##
182
+ # Create a new middleware builder
183
+ #
184
+ # @return [Builder] New middleware builder instance
185
+ def self.build
186
+ Builder.new
187
+ end
188
+
189
+ ##
190
+ # Create a middleware stack with common middleware
191
+ #
192
+ # @param options [Hash] Configuration options
193
+ # @return [Registry] Configured middleware registry
194
+ def self.default_stack(**options)
195
+ builder = build
196
+
197
+ # Add logging by default
198
+ builder.use_logging(options[:logging] || {})
199
+
200
+ # Add authentication if configured
201
+ builder.use_authentication(options[:authentication]) if options[:authentication]
202
+
203
+ # Add rate limiting if configured
204
+ builder.use_rate_limiting(options[:rate_limiting]) if options[:rate_limiting]
205
+
206
+ # Add CORS if configured
207
+ builder.use_cors(options[:cors]) if options[:cors]
208
+
209
+ builder.build
210
+ end
211
+ end
212
+ end
213
+ end