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
data/lib/a2a/plugin.rb ADDED
@@ -0,0 +1,358 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Plugin architecture for extending A2A functionality
5
+ #
6
+ # Provides extension points for custom functionality including:
7
+ # - Custom transport protocols
8
+ # - Authentication strategies
9
+ # - Request/response processing hooks
10
+ # - Middleware components
11
+ #
12
+ # @example Register a plugin
13
+ # A2A::Plugin.register(:my_plugin, MyPlugin)
14
+ #
15
+ # @example Create a transport plugin
16
+ # class MyTransport < A2A::Plugin::Base
17
+ # plugin_type :transport
18
+ #
19
+ # def send_request(request)
20
+ # # Custom transport implementation
21
+ # end
22
+ # end
23
+ #
24
+ module A2A
25
+ module Plugin
26
+ class << self
27
+ # Registry of all plugins
28
+ # @return [Hash<Symbol, Hash>]
29
+ attr_reader :registry
30
+
31
+ # Registered hooks
32
+ # @return [Hash<Symbol, Array>]
33
+ attr_reader :hooks
34
+
35
+ # Initialize plugin system
36
+ def initialize!
37
+ @registry = {}
38
+ @hooks = Hash.new { |h, k| h[k] = [] }
39
+ @loaded_plugins = Set.new
40
+ end
41
+
42
+ # Register a plugin
43
+ # @param name [Symbol] Plugin name
44
+ # @param plugin_class [Class] Plugin class
45
+ # @param **options [Hash] Plugin options
46
+ def register(name, plugin_class, **options)
47
+ initialize! unless @registry
48
+
49
+ unless plugin_class.respond_to?(:plugin_type)
50
+ raise A2A::Errors::PluginError, "Plugin class must include A2A::Plugin::Base"
51
+ end
52
+
53
+ @registry[name] = {
54
+ class: plugin_class,
55
+ type: plugin_class.plugin_type,
56
+ options: options,
57
+ loaded: false
58
+ }
59
+
60
+ A2A.config.logger&.info("Registered plugin: #{name} (#{plugin_class.plugin_type})")
61
+ end
62
+
63
+ # Load a plugin
64
+ # @param name [Symbol] Plugin name
65
+ # @param **config [Hash] Plugin configuration
66
+ # @return [Object] Plugin instance
67
+ def load(name, **config)
68
+ initialize! unless @registry
69
+
70
+ plugin_info = @registry[name]
71
+ raise A2A::Errors::PluginError, "Plugin not found: #{name}" unless plugin_info
72
+
73
+ return plugin_info[:instance] if plugin_info[:loaded]
74
+
75
+ begin
76
+ instance = plugin_info[:class].new(**config)
77
+ plugin_info[:instance] = instance
78
+ plugin_info[:loaded] = true
79
+ @loaded_plugins << name
80
+
81
+ # Register plugin hooks
82
+ instance.register_hooks(self) if instance.respond_to?(:register_hooks)
83
+
84
+ A2A.config.logger&.info("Loaded plugin: #{name}")
85
+ instance
86
+ rescue StandardError => e
87
+ raise A2A::Errors::PluginError, "Failed to load plugin #{name}: #{e.message}"
88
+ end
89
+ end
90
+
91
+ # Unload a plugin
92
+ # @param name [Symbol] Plugin name
93
+ def unload(name)
94
+ initialize! unless @registry
95
+
96
+ plugin_info = @registry[name]
97
+ return unless plugin_info && plugin_info[:loaded]
98
+
99
+ instance = plugin_info[:instance]
100
+
101
+ # Call cleanup if available
102
+ instance.cleanup if instance.respond_to?(:cleanup)
103
+
104
+ plugin_info[:loaded] = false
105
+ plugin_info[:instance] = nil
106
+ @loaded_plugins.delete(name)
107
+
108
+ A2A.config.logger&.info("Unloaded plugin: #{name}")
109
+ end
110
+
111
+ # Get loaded plugins by type
112
+ # @param type [Symbol] Plugin type
113
+ # @return [Array<Object>] Plugin instances
114
+ def loaded_plugins(type: nil)
115
+ initialize! unless @registry
116
+
117
+ plugins = @loaded_plugins.filter_map { |name| @registry[name][:instance] }
118
+
119
+ if type
120
+ plugins.select { |plugin| plugin.class.plugin_type == type }
121
+ else
122
+ plugins
123
+ end
124
+ end
125
+
126
+ # Register a hook
127
+ # @param event [Symbol] Hook event name
128
+ # @param callable [Proc, Method] Hook handler (optional if block given)
129
+ # @param priority [Integer] Hook priority (lower = higher priority)
130
+ def add_hook(event, callable = nil, priority: 50, &block)
131
+ initialize! unless @hooks
132
+
133
+ handler = callable || block
134
+ raise ArgumentError, "Must provide callable or block" unless handler
135
+
136
+ @hooks[event] << { callable: handler, priority: priority }
137
+ @hooks[event].sort_by! { |hook| hook[:priority] }
138
+ end
139
+
140
+ # Execute hooks for an event
141
+ # @param event [Symbol] Hook event name
142
+ # @param *args [Array] Arguments to pass to hooks
143
+ # @return [Array] Results from all hooks
144
+ def execute_hooks(event, *args)
145
+ initialize! unless @hooks
146
+
147
+ results = []
148
+ @hooks[event].each do |hook|
149
+ result = hook[:callable].call(*args)
150
+ results << result
151
+ rescue StandardError => e
152
+ A2A.config.logger&.error("Hook execution failed for #{event}: #{e.message}")
153
+ raise A2A::Errors::PluginError, "Hook execution failed: #{e.message}"
154
+ end
155
+ results
156
+ end
157
+
158
+ # Check if a plugin is loaded
159
+ # @param name [Symbol] Plugin name
160
+ # @return [Boolean]
161
+ def loaded?(name)
162
+ initialize! unless @registry
163
+ @loaded_plugins.include?(name)
164
+ end
165
+
166
+ # List all registered plugins
167
+ # @return [Hash] Plugin registry information
168
+ def list
169
+ initialize! unless @registry
170
+
171
+ @registry.transform_values do |info|
172
+ {
173
+ type: info[:type],
174
+ loaded: info[:loaded],
175
+ options: info[:options]
176
+ }
177
+ end
178
+ end
179
+
180
+ # Clear all plugins (for testing)
181
+ def clear!
182
+ @loaded_plugins&.each { |name| unload(name) }
183
+ @registry&.clear
184
+ @hooks&.clear
185
+ initialize!
186
+ end
187
+ end
188
+
189
+ ##
190
+ # Base class for all A2A plugins
191
+ #
192
+ class Base
193
+ class << self
194
+ # Define plugin type
195
+ # @param type [Symbol] Plugin type
196
+ def plugin_type(type = nil)
197
+ if type
198
+ @plugin_type = type
199
+ else
200
+ @plugin_type || (superclass.respond_to?(:plugin_type) ? superclass.plugin_type : nil)
201
+ end
202
+ end
203
+
204
+ # Define plugin dependencies
205
+ # @param *deps [Array<Symbol>] Plugin dependencies
206
+ def depends_on(*deps)
207
+ @dependencies = deps
208
+ end
209
+
210
+ # Get plugin dependencies
211
+ # @return [Array<Symbol>]
212
+ def dependencies
213
+ @dependencies || []
214
+ end
215
+
216
+ # Inherited hook to ensure plugin types are properly inherited
217
+ def inherited(subclass)
218
+ super
219
+ # Ensure subclass inherits plugin type if not explicitly set
220
+ return unless @plugin_type && !subclass.instance_variable_defined?(:@plugin_type)
221
+
222
+ subclass.instance_variable_set(:@plugin_type, @plugin_type)
223
+ end
224
+ end
225
+
226
+ # Initialize plugin
227
+ # @param **config [Hash] Plugin configuration
228
+ def initialize(**config)
229
+ @config = config
230
+ @logger = A2A.config.logger
231
+ setup if respond_to?(:setup, true)
232
+ end
233
+
234
+ # Get plugin configuration
235
+ # @return [Hash]
236
+ attr_reader :config
237
+
238
+ # Get logger instance
239
+ # @return [Logger]
240
+ attr_reader :logger
241
+
242
+ # Register hooks (override in subclasses)
243
+ # @param plugin_manager [A2A::Plugin] Plugin manager
244
+ def register_hooks(plugin_manager)
245
+ # Override in subclasses to register hooks
246
+ end
247
+
248
+ # Cleanup resources (override in subclasses)
249
+ def cleanup
250
+ # Override in subclasses for cleanup
251
+ end
252
+ end
253
+
254
+ ##
255
+ # Transport plugin interface
256
+ #
257
+ class TransportPlugin < A2A::Plugin::Base
258
+ plugin_type :transport
259
+
260
+ # Send a request (must be implemented by subclasses)
261
+ # @param request [Hash] Request data
262
+ # @param **options [Hash] Transport options
263
+ # @return [Object] Response
264
+ def send_request(request, **options)
265
+ raise NotImplementedError, "Transport plugins must implement #send_request"
266
+ end
267
+
268
+ # Check if transport supports streaming
269
+ # @return [Boolean]
270
+ def supports_streaming?
271
+ false
272
+ end
273
+
274
+ # Create streaming connection (optional)
275
+ # @param **options [Hash] Connection options
276
+ # @return [Object] Stream connection
277
+ def create_stream(**options)
278
+ raise NotImplementedError, "Streaming not supported by this transport"
279
+ end
280
+ end
281
+
282
+ ##
283
+ # Authentication plugin interface
284
+ #
285
+ class AuthPlugin < A2A::Plugin::Base
286
+ plugin_type :auth
287
+
288
+ # Authenticate a request (must be implemented by subclasses)
289
+ # @param request [Hash] Request data
290
+ # @param **options [Hash] Authentication options
291
+ # @return [Hash] Authenticated request
292
+ def authenticate_request(request, **options)
293
+ raise NotImplementedError, "Auth plugins must implement #authenticate_request"
294
+ end
295
+
296
+ # Validate authentication (optional)
297
+ # @param credentials [Object] Credentials to validate
298
+ # @return [Boolean] Whether credentials are valid
299
+ def validate_credentials(_credentials)
300
+ true
301
+ end
302
+ end
303
+
304
+ ##
305
+ # Middleware plugin interface
306
+ #
307
+ class MiddlewarePlugin < A2A::Plugin::Base
308
+ plugin_type :middleware
309
+
310
+ # Process request (must be implemented by subclasses)
311
+ # @param request [Hash] Request data
312
+ # @param next_middleware [Proc] Next middleware in chain
313
+ # @return [Object] Response
314
+ def call(request, next_middleware)
315
+ raise NotImplementedError, "Middleware plugins must implement #call"
316
+ end
317
+ end
318
+
319
+ ##
320
+ # Hook events for plugin system
321
+ #
322
+ module Events
323
+ # Request processing hooks
324
+ BEFORE_REQUEST = :before_request
325
+ AFTER_REQUEST = :after_request
326
+ REQUEST_ERROR = :request_error
327
+
328
+ # Response processing hooks
329
+ BEFORE_RESPONSE = :before_response
330
+ AFTER_RESPONSE = :after_response
331
+ RESPONSE_ERROR = :response_error
332
+
333
+ # Task lifecycle hooks
334
+ TASK_CREATED = :task_created
335
+ TASK_UPDATED = :task_updated
336
+ TASK_COMPLETED = :task_completed
337
+ TASK_FAILED = :task_failed
338
+
339
+ # Agent card hooks
340
+ AGENT_CARD_GENERATED = :agent_card_generated
341
+ AGENT_CARD_REQUESTED = :agent_card_requested
342
+
343
+ # All available events
344
+ ALL = constants.map { |const| const_get(const) }.freeze
345
+ end
346
+ end
347
+ end
348
+
349
+ # Add plugin error to errors module
350
+ ##
351
+ # Plugin-related errors
352
+ #
353
+ module A2A
354
+ module Errors
355
+ class PluginError < A2A::Errors::A2AError
356
+ end
357
+ end
358
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "plugin"
4
+
5
+ ##
6
+ # Plugin manager for loading and managing A2A plugins
7
+ #
8
+ # Handles plugin lifecycle, dependency resolution, and integration
9
+ # with the A2A framework components.
10
+ #
11
+ module A2A
12
+ class PluginManager
13
+ # Initialize plugin manager
14
+ # @param config [A2A::Configuration] Configuration instance
15
+ def initialize(config = A2A.config)
16
+ @config = config
17
+ @logger = config.logger
18
+ @auto_load_plugins = []
19
+ @plugin_configs = {}
20
+ end
21
+
22
+ # Configure auto-loading plugins
23
+ # @param plugins [Hash<Symbol, Hash>] Plugin configurations
24
+ def configure_plugins(plugins)
25
+ @plugin_configs = plugins
26
+
27
+ plugins.each do |name, plugin_config|
28
+ @auto_load_plugins << name if plugin_config[:auto_load]
29
+ end
30
+ end
31
+
32
+ # Load all configured plugins
33
+ def load_all_plugins
34
+ @auto_load_plugins.each do |plugin_name|
35
+ load_plugin(plugin_name)
36
+ end
37
+ end
38
+
39
+ # Load a specific plugin
40
+ # @param name [Symbol] Plugin name
41
+ # @param **config [Hash] Plugin configuration override
42
+ def load_plugin(name, **config)
43
+ plugin_config = @plugin_configs[name] || {}
44
+ merged_config = plugin_config.merge(config)
45
+
46
+ # Load dependencies first
47
+ if A2A::Plugin.registry[name]
48
+ dependencies = A2A::Plugin.registry[name][:class].dependencies
49
+ dependencies.each do |dep|
50
+ load_plugin(dep) unless A2A::Plugin.loaded?(dep)
51
+ end
52
+ end
53
+
54
+ A2A::Plugin.load(name, **merged_config)
55
+ rescue A2A::Errors::PluginError => e
56
+ @logger&.error("Failed to load plugin #{name}: #{e.message}")
57
+ raise
58
+ end
59
+
60
+ # Unload a plugin
61
+ # @param name [Symbol] Plugin name
62
+ def unload_plugin(name)
63
+ A2A::Plugin.unload(name)
64
+ end
65
+
66
+ # Get transport plugins
67
+ # @return [Array<A2A::Plugin::TransportPlugin>]
68
+ def transport_plugins
69
+ A2A::Plugin.loaded_plugins(type: :transport)
70
+ end
71
+
72
+ # Get authentication plugins
73
+ # @return [Array<A2A::Plugin::AuthPlugin>]
74
+ def auth_plugins
75
+ A2A::Plugin.loaded_plugins(type: :auth)
76
+ end
77
+
78
+ # Get middleware plugins
79
+ # @return [Array<A2A::Plugin::MiddlewarePlugin>]
80
+ def middleware_plugins
81
+ A2A::Plugin.loaded_plugins(type: :middleware)
82
+ end
83
+
84
+ # Find transport plugin by name
85
+ # @param transport_name [String] Transport protocol name
86
+ # @return [A2A::Plugin::TransportPlugin, nil]
87
+ def find_transport(transport_name)
88
+ transport_plugins.find do |plugin|
89
+ plugin.respond_to?(:transport_name) &&
90
+ plugin.transport_name == transport_name
91
+ end
92
+ end
93
+
94
+ # Find auth plugin by name
95
+ # @param auth_name [String] Authentication strategy name
96
+ # @return [A2A::Plugin::AuthPlugin, nil]
97
+ def find_auth_strategy(auth_name)
98
+ auth_plugins.find do |plugin|
99
+ plugin.respond_to?(:strategy_name) &&
100
+ plugin.strategy_name == auth_name
101
+ end
102
+ end
103
+
104
+ # Execute request hooks
105
+ # @param event [Symbol] Hook event
106
+ # @param request [Hash] Request data
107
+ # @return [Hash] Modified request
108
+ def execute_request_hooks(event, request)
109
+ results = A2A::Plugin.execute_hooks(event, request)
110
+
111
+ # Apply modifications from hooks
112
+ results.each do |result|
113
+ request.merge!(result) if result.is_a?(Hash)
114
+ end
115
+
116
+ request
117
+ end
118
+
119
+ # Execute response hooks
120
+ # @param event [Symbol] Hook event
121
+ # @param response [Object] Response data
122
+ # @param request [Hash] Original request
123
+ # @return [Object] Modified response
124
+ def execute_response_hooks(event, response, request = nil)
125
+ results = A2A::Plugin.execute_hooks(event, response, request)
126
+
127
+ # Return last non-nil result or original response
128
+ results.reverse.find { |result| !result.nil? } || response
129
+ end
130
+
131
+ # Create middleware chain from plugins
132
+ # @return [Array<Proc>] Middleware chain
133
+ def build_middleware_chain
134
+ middleware_plugins.map do |plugin|
135
+ proc { |request, next_middleware| plugin.call(request, next_middleware) }
136
+ end
137
+ end
138
+
139
+ # Get plugin status
140
+ # @return [Hash] Plugin status information
141
+ def status
142
+ {
143
+ loaded_plugins: A2A::Plugin.loaded_plugins.size,
144
+ registered_plugins: A2A::Plugin.registry.size,
145
+ plugins_by_type: {
146
+ transport: transport_plugins.size,
147
+ auth: auth_plugins.size,
148
+ middleware: middleware_plugins.size
149
+ },
150
+ auto_load_plugins: @auto_load_plugins,
151
+ plugin_list: A2A::Plugin.list
152
+ }
153
+ end
154
+
155
+ private
156
+
157
+ attr_reader :config, :logger
158
+ end
159
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Example custom authentication plugin
5
+ #
6
+ # Demonstrates how to create a custom authentication plugin
7
+ # for the A2A plugin architecture.
8
+ #
9
+ module A2A
10
+ module Plugins
11
+ class ExampleAuth < A2A::Plugin::AuthPlugin
12
+ # Authentication strategy name
13
+ def strategy_name
14
+ "example"
15
+ end
16
+
17
+ # Authenticate request
18
+ # @param request [Hash] Request data
19
+ # @param **options [Hash] Authentication options
20
+ # @return [Hash] Authenticated request
21
+ def authenticate_request(request, **options)
22
+ logger&.info("Authenticating request with Example Auth")
23
+
24
+ # Add custom authentication header
25
+ request[:headers] ||= {}
26
+ request[:headers]["X-Example-Auth"] = generate_token(options)
27
+
28
+ request
29
+ end
30
+
31
+ # Validate credentials
32
+ # @param credentials [Hash] Credentials to validate
33
+ # @return [Boolean] Whether credentials are valid
34
+ def validate_credentials(credentials)
35
+ return false unless credentials.is_a?(Hash)
36
+ return false unless credentials[:api_key]
37
+
38
+ # Simple validation - in real implementation, validate against backend
39
+ credentials[:api_key].start_with?("example_")
40
+ end
41
+
42
+ # Register hooks for this plugin
43
+ def register_hooks(plugin_manager)
44
+ plugin_manager.add_hook(A2A::Plugin::Events::BEFORE_REQUEST) do |request|
45
+ logger&.debug("Example Auth: Validating request authentication")
46
+
47
+ # Add request timestamp for security
48
+ request[:auth_timestamp] = Time.now.to_i
49
+ end
50
+
51
+ plugin_manager.add_hook(A2A::Plugin::Events::REQUEST_ERROR) do |error, request|
52
+ if error.is_a?(A2A::Errors::AuthenticationError)
53
+ logger&.warn("Example Auth: Authentication failed for request #{request[:id]}")
54
+ end
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def setup
61
+ @secret_key = config[:secret_key] || "default_secret"
62
+ logger&.info("Example Auth plugin initialized")
63
+ end
64
+
65
+ def cleanup
66
+ @secret_key = nil
67
+ logger&.info("Example Auth plugin cleaned up")
68
+ end
69
+
70
+ def generate_token(options)
71
+ # Simple token generation - in real implementation, use proper JWT or similar
72
+ payload = {
73
+ timestamp: Time.now.to_i,
74
+ client_id: options[:client_id] || "unknown"
75
+ }
76
+
77
+ Base64.encode64("#{payload.to_json}:#{@secret_key}").strip
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Example custom middleware plugin
5
+ #
6
+ # Demonstrates how to create a custom middleware plugin
7
+ # for the A2A plugin architecture.
8
+ #
9
+ module A2A
10
+ module Plugins
11
+ class ExampleMiddleware < A2A::Plugin::MiddlewarePlugin
12
+ # Middleware name for identification
13
+ def middleware_name
14
+ "example"
15
+ end
16
+
17
+ # Process request through middleware
18
+ # @param request [Hash] Request data
19
+ # @param next_middleware [Proc] Next middleware in chain
20
+ # @return [Object] Response
21
+ def call(request, next_middleware)
22
+ start_time = Time.now
23
+
24
+ logger&.info("Example Middleware: Processing request #{request[:id]}")
25
+
26
+ # Pre-processing
27
+ request = preprocess_request(request)
28
+
29
+ begin
30
+ # Call next middleware in chain
31
+ response = next_middleware.call(request)
32
+
33
+ # Post-processing
34
+ response = postprocess_response(response, request)
35
+
36
+ # Log success
37
+ duration = Time.now - start_time
38
+ logger&.info("Example Middleware: Request completed in #{duration.round(3)}s")
39
+
40
+ response
41
+ rescue StandardError => e
42
+ # Error handling
43
+ duration = Time.now - start_time
44
+ logger&.error("Example Middleware: Request failed after #{duration.round(3)}s: #{e.message}")
45
+
46
+ # Execute error hooks
47
+ A2A::Plugin.execute_hooks(A2A::Plugin::Events::REQUEST_ERROR, e, request)
48
+
49
+ raise
50
+ end
51
+ end
52
+
53
+ # Register hooks for this plugin
54
+ def register_hooks(plugin_manager)
55
+ plugin_manager.add_hook(A2A::Plugin::Events::BEFORE_REQUEST, priority: 10) do |request|
56
+ logger&.debug("Example Middleware: Adding request metadata")
57
+ request[:middleware_metadata] ||= {}
58
+ request[:middleware_metadata][:example] = {
59
+ processed_at: Time.now.iso8601,
60
+ version: "1.0.0"
61
+ }
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def setup
68
+ @request_counter = 0
69
+ @error_counter = 0
70
+ logger&.info("Example Middleware plugin initialized")
71
+ end
72
+
73
+ def cleanup
74
+ logger&.info("Example Middleware plugin cleaned up (processed #{@request_counter} requests, #{@error_counter} errors)")
75
+ end
76
+
77
+ def preprocess_request(request)
78
+ @request_counter += 1
79
+
80
+ # Add request ID if not present
81
+ request[:id] ||= SecureRandom.uuid
82
+
83
+ # Add processing metadata
84
+ request[:processing] ||= {}
85
+ request[:processing][:middleware_start] = Time.now.to_f
86
+
87
+ # Validate request structure
88
+ validate_request_structure(request)
89
+
90
+ request
91
+ end
92
+
93
+ def postprocess_response(response, request)
94
+ # Add processing time to response metadata
95
+ if request[:processing] && request[:processing][:middleware_start]
96
+ processing_time = Time.now.to_f - request[:processing][:middleware_start]
97
+
98
+ if response.is_a?(Hash)
99
+ response[:metadata] ||= {}
100
+ response[:metadata][:processing_time] = processing_time
101
+ end
102
+ end
103
+
104
+ response
105
+ end
106
+
107
+ def validate_request_structure(request)
108
+ raise A2A::Errors::InvalidRequest, "Request must be a hash" unless request.is_a?(Hash)
109
+
110
+ return if request[:method]
111
+
112
+ raise A2A::Errors::InvalidRequest, "Request must have a method"
113
+
114
+ # Additional validation can be added here
115
+ end
116
+ end
117
+ end
118
+ end