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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +137 -0
- data/.simplecov +46 -0
- data/.yardopts +10 -0
- data/CHANGELOG.md +33 -0
- data/CODE_OF_CONDUCT.md +128 -0
- data/CONTRIBUTING.md +165 -0
- data/Gemfile +43 -0
- data/Guardfile +34 -0
- data/LICENSE.txt +21 -0
- data/PUBLISHING_CHECKLIST.md +214 -0
- data/README.md +171 -0
- data/Rakefile +165 -0
- data/docs/agent_execution.md +309 -0
- data/docs/api_reference.md +792 -0
- data/docs/configuration.md +780 -0
- data/docs/events.md +475 -0
- data/docs/getting_started.md +668 -0
- data/docs/integration.md +262 -0
- data/docs/server_apps.md +621 -0
- data/docs/troubleshooting.md +765 -0
- data/lib/a2a/client/api_methods.rb +263 -0
- data/lib/a2a/client/auth/api_key.rb +161 -0
- data/lib/a2a/client/auth/interceptor.rb +288 -0
- data/lib/a2a/client/auth/jwt.rb +189 -0
- data/lib/a2a/client/auth/oauth2.rb +146 -0
- data/lib/a2a/client/auth.rb +137 -0
- data/lib/a2a/client/base.rb +316 -0
- data/lib/a2a/client/config.rb +210 -0
- data/lib/a2a/client/connection_pool.rb +233 -0
- data/lib/a2a/client/http_client.rb +524 -0
- data/lib/a2a/client/json_rpc_handler.rb +136 -0
- data/lib/a2a/client/middleware/circuit_breaker_interceptor.rb +245 -0
- data/lib/a2a/client/middleware/logging_interceptor.rb +371 -0
- data/lib/a2a/client/middleware/rate_limit_interceptor.rb +142 -0
- data/lib/a2a/client/middleware/retry_interceptor.rb +161 -0
- data/lib/a2a/client/middleware.rb +116 -0
- data/lib/a2a/client/performance_tracker.rb +60 -0
- data/lib/a2a/configuration/defaults.rb +34 -0
- data/lib/a2a/configuration/environment_loader.rb +76 -0
- data/lib/a2a/configuration/file_loader.rb +115 -0
- data/lib/a2a/configuration/inheritance.rb +101 -0
- data/lib/a2a/configuration/validator.rb +180 -0
- data/lib/a2a/configuration.rb +201 -0
- data/lib/a2a/errors.rb +291 -0
- data/lib/a2a/modules.rb +50 -0
- data/lib/a2a/monitoring/alerting.rb +490 -0
- data/lib/a2a/monitoring/distributed_tracing.rb +398 -0
- data/lib/a2a/monitoring/health_endpoints.rb +204 -0
- data/lib/a2a/monitoring/metrics_collector.rb +438 -0
- data/lib/a2a/monitoring.rb +463 -0
- data/lib/a2a/plugin.rb +358 -0
- data/lib/a2a/plugin_manager.rb +159 -0
- data/lib/a2a/plugins/example_auth.rb +81 -0
- data/lib/a2a/plugins/example_middleware.rb +118 -0
- data/lib/a2a/plugins/example_transport.rb +76 -0
- data/lib/a2a/protocol/agent_card.rb +8 -0
- data/lib/a2a/protocol/agent_card_server.rb +584 -0
- data/lib/a2a/protocol/capability.rb +496 -0
- data/lib/a2a/protocol/json_rpc.rb +254 -0
- data/lib/a2a/protocol/message.rb +8 -0
- data/lib/a2a/protocol/task.rb +8 -0
- data/lib/a2a/rails/a2a_controller.rb +258 -0
- data/lib/a2a/rails/controller_helpers.rb +499 -0
- data/lib/a2a/rails/engine.rb +167 -0
- data/lib/a2a/rails/generators/agent_generator.rb +311 -0
- data/lib/a2a/rails/generators/install_generator.rb +209 -0
- data/lib/a2a/rails/generators/migration_generator.rb +232 -0
- data/lib/a2a/rails/generators/templates/add_a2a_indexes.rb +57 -0
- data/lib/a2a/rails/generators/templates/agent_controller.rb +122 -0
- data/lib/a2a/rails/generators/templates/agent_controller_spec.rb +160 -0
- data/lib/a2a/rails/generators/templates/agent_readme.md +200 -0
- data/lib/a2a/rails/generators/templates/create_a2a_push_notification_configs.rb +68 -0
- data/lib/a2a/rails/generators/templates/create_a2a_tasks.rb +83 -0
- data/lib/a2a/rails/generators/templates/example_agent_controller.rb +228 -0
- data/lib/a2a/rails/generators/templates/initializer.rb +108 -0
- data/lib/a2a/rails/generators/templates/push_notification_config_model.rb +228 -0
- data/lib/a2a/rails/generators/templates/task_model.rb +200 -0
- data/lib/a2a/rails/tasks/a2a.rake +228 -0
- data/lib/a2a/server/a2a_methods.rb +520 -0
- data/lib/a2a/server/agent.rb +537 -0
- data/lib/a2a/server/agent_execution/agent_executor.rb +279 -0
- data/lib/a2a/server/agent_execution/request_context.rb +219 -0
- data/lib/a2a/server/apps/rack_app.rb +311 -0
- data/lib/a2a/server/apps/sinatra_app.rb +261 -0
- data/lib/a2a/server/default_request_handler.rb +350 -0
- data/lib/a2a/server/events/event_consumer.rb +116 -0
- data/lib/a2a/server/events/event_queue.rb +226 -0
- data/lib/a2a/server/example_agent.rb +248 -0
- data/lib/a2a/server/handler.rb +281 -0
- data/lib/a2a/server/middleware/authentication_middleware.rb +212 -0
- data/lib/a2a/server/middleware/cors_middleware.rb +171 -0
- data/lib/a2a/server/middleware/logging_middleware.rb +362 -0
- data/lib/a2a/server/middleware/rate_limit_middleware.rb +382 -0
- data/lib/a2a/server/middleware.rb +213 -0
- data/lib/a2a/server/push_notification_manager.rb +327 -0
- data/lib/a2a/server/request_handler.rb +136 -0
- data/lib/a2a/server/storage/base.rb +141 -0
- data/lib/a2a/server/storage/database.rb +266 -0
- data/lib/a2a/server/storage/memory.rb +274 -0
- data/lib/a2a/server/storage/redis.rb +320 -0
- data/lib/a2a/server/storage.rb +38 -0
- data/lib/a2a/server/task_manager.rb +534 -0
- data/lib/a2a/transport/grpc.rb +481 -0
- data/lib/a2a/transport/http.rb +415 -0
- data/lib/a2a/transport/sse.rb +499 -0
- data/lib/a2a/types/agent_card.rb +540 -0
- data/lib/a2a/types/artifact.rb +99 -0
- data/lib/a2a/types/base_model.rb +223 -0
- data/lib/a2a/types/events.rb +117 -0
- data/lib/a2a/types/message.rb +106 -0
- data/lib/a2a/types/part.rb +288 -0
- data/lib/a2a/types/push_notification.rb +139 -0
- data/lib/a2a/types/security.rb +167 -0
- data/lib/a2a/types/task.rb +154 -0
- data/lib/a2a/types.rb +88 -0
- data/lib/a2a/utils/helpers.rb +245 -0
- data/lib/a2a/utils/message_buffer.rb +278 -0
- data/lib/a2a/utils/performance.rb +247 -0
- data/lib/a2a/utils/rails_detection.rb +97 -0
- data/lib/a2a/utils/structured_logger.rb +306 -0
- data/lib/a2a/utils/time_helpers.rb +167 -0
- data/lib/a2a/utils/validation.rb +8 -0
- data/lib/a2a/version.rb +6 -0
- data/lib/a2a-rails.rb +58 -0
- data/lib/a2a.rb +198 -0
- 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
|