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,362 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "json"
5
+ require_relative "../../utils/rails_detection"
6
+
7
+ ##
8
+ # Logging middleware for A2A requests
9
+ #
10
+ # Provides comprehensive logging for A2A requests and responses,
11
+ # including request/response tracking, performance metrics, and
12
+ # structured logging support.
13
+ #
14
+ # @example Basic usage
15
+ # middleware = LoggingMiddleware.new(
16
+ # logger: Rails.logger,
17
+ # level: :info,
18
+ # format: :structured
19
+ # )
20
+ #
21
+ module A2A
22
+ module Server
23
+ module Middleware
24
+ class LoggingMiddleware
25
+ include A2A::Utils::RailsDetection
26
+
27
+ attr_accessor :logger
28
+ attr_reader :level, :format, :options
29
+
30
+ # Logging formats
31
+ FORMATS = %i[simple detailed structured json].freeze
32
+
33
+ ##
34
+ # Initialize logging middleware
35
+ #
36
+ # @param logger [Logger] The logger instance to use
37
+ # @param level [Symbol] The log level (:debug, :info, :warn, :error)
38
+ # @param format [Symbol] The log format (:simple, :detailed, :structured, :json)
39
+ # @param options [Hash] Additional logging options
40
+ # @option options [Boolean] :log_params Whether to log request parameters
41
+ # @option options [Boolean] :log_response Whether to log response data
42
+ # @option options [Boolean] :log_errors Whether to log error details
43
+ # @option options [Array<String>] :filtered_params Parameters to filter from logs
44
+ def initialize(logger: nil, level: :info, format: :detailed, **options)
45
+ @logger = logger || default_logger
46
+ @level = level
47
+ @format = format
48
+ @options = {
49
+ log_params: true,
50
+ log_response: false,
51
+ log_errors: true,
52
+ filtered_params: %w[password token api_key secret],
53
+ include_context: true,
54
+ include_timing: true
55
+ }.merge(options)
56
+
57
+ validate_format!
58
+ end
59
+
60
+ ##
61
+ # Process logging for a request
62
+ #
63
+ # @param request [A2A::Protocol::Request] The JSON-RPC request
64
+ # @param context [A2A::Server::Context] The request context
65
+ # @yield Block to continue the middleware chain
66
+ # @return [Object] The result from the next middleware or handler
67
+ def call(request, context)
68
+ start_time = Time.now
69
+ request_id = generate_request_id(request, context)
70
+
71
+ # Log request start
72
+ log_request_start(request, context, request_id)
73
+
74
+ begin
75
+ # Execute next middleware/handler
76
+ result = yield
77
+
78
+ # Log successful completion
79
+ duration = Time.now - start_time
80
+ log_request_success(request, context, result, duration, request_id)
81
+
82
+ result
83
+ rescue StandardError => e
84
+ # Log error
85
+ duration = Time.now - start_time
86
+ log_request_error(request, context, e, duration, request_id)
87
+
88
+ # Re-raise the error
89
+ raise
90
+ end
91
+ end
92
+
93
+ ##
94
+ # Set custom logger
95
+ #
96
+ # @param new_logger [Logger] The new logger instance
97
+
98
+ ##
99
+ # Add filtered parameter
100
+ #
101
+ # @param param [String] Parameter name to filter
102
+ def add_filtered_param(param)
103
+ @options[:filtered_params] << param.to_s
104
+ end
105
+
106
+ ##
107
+ # Remove filtered parameter
108
+ #
109
+ # @param param [String] Parameter name to unfilter
110
+ def remove_filtered_param(param)
111
+ @options[:filtered_params].delete(param.to_s)
112
+ end
113
+
114
+ private
115
+
116
+ ##
117
+ # Validate the logging format
118
+ def validate_format!
119
+ return if FORMATS.include?(@format)
120
+
121
+ raise ArgumentError, "Invalid format: #{@format}. Must be one of: #{FORMATS.join(', ')}"
122
+ end
123
+
124
+ ##
125
+ # Get default logger
126
+ def default_logger
127
+ if rails_logger
128
+ rails_logger
129
+ else
130
+ Logger.new($stdout).tap do |logger|
131
+ logger.level = Logger::INFO
132
+ end
133
+ end
134
+ end
135
+
136
+ ##
137
+ # Generate a unique request ID
138
+ #
139
+ # @param request [A2A::Protocol::Request] The request
140
+ # @param context [A2A::Server::Context] The context
141
+ # @return [String] The request ID
142
+ def generate_request_id(request, _context)
143
+ # Use existing request ID if available
144
+ return request.id.to_s if request.id
145
+
146
+ # Generate a new ID
147
+ "req_#{Time.now.to_f}_#{rand(10_000)}"
148
+ end
149
+
150
+ ##
151
+ # Log request start
152
+ #
153
+ # @param request [A2A::Protocol::Request] The request
154
+ # @param context [A2A::Server::Context] The context
155
+ # @param request_id [String] The request ID
156
+ def log_request_start(request, context, request_id)
157
+ case @format
158
+ when :simple
159
+ @logger.send(@level, "A2A Request: #{request.method}")
160
+ when :detailed
161
+ log_detailed_start(request, context, request_id)
162
+ when :structured
163
+ log_structured_start(request, context, request_id)
164
+ when :json
165
+ log_json_start(request, context, request_id)
166
+ end
167
+ end
168
+
169
+ ##
170
+ # Log successful request completion
171
+ #
172
+ # @param request [A2A::Protocol::Request] The request
173
+ # @param context [A2A::Server::Context] The context
174
+ # @param result [Object] The response result
175
+ # @param duration [Float] Request duration in seconds
176
+ # @param request_id [String] The request ID
177
+ def log_request_success(request, context, result, duration, request_id)
178
+ case @format
179
+ when :simple
180
+ @logger.send(@level, "A2A Response: #{request.method} (#{duration.round(3)}s)")
181
+ when :detailed
182
+ log_detailed_success(request, context, result, duration, request_id)
183
+ when :structured
184
+ log_structured_success(request, context, result, duration, request_id)
185
+ when :json
186
+ log_json_success(request, context, result, duration, request_id)
187
+ end
188
+ end
189
+
190
+ ##
191
+ # Log request error
192
+ #
193
+ # @param request [A2A::Protocol::Request] The request
194
+ # @param context [A2A::Server::Context] The context
195
+ # @param error [StandardError] The error
196
+ # @param duration [Float] Request duration in seconds
197
+ # @param request_id [String] The request ID
198
+ def log_request_error(request, context, error, duration, request_id)
199
+ case @format
200
+ when :simple
201
+ @logger.error("A2A Error: #{request.method} - #{error.class.name}: #{error.message}")
202
+ when :detailed
203
+ log_detailed_error(request, context, error, duration, request_id)
204
+ when :structured
205
+ log_structured_error(request, context, error, duration, request_id)
206
+ when :json
207
+ log_json_error(request, context, error, duration, request_id)
208
+ end
209
+ end
210
+
211
+ ##
212
+ # Log detailed request start
213
+ def log_detailed_start(request, context, request_id)
214
+ message = "A2A Request Started - Method: #{request.method}, ID: #{request_id}"
215
+
216
+ if @options[:log_params] && !request.params.empty?
217
+ filtered_params = filter_sensitive_data(request.params)
218
+ message += ", Params: #{filtered_params.inspect}"
219
+ end
220
+
221
+ message += ", Authenticated: true" if @options[:include_context] && context.authenticated?
222
+
223
+ @logger.send(@level, message)
224
+ end
225
+
226
+ ##
227
+ # Log detailed request success
228
+ def log_detailed_success(request, _context, result, duration, request_id)
229
+ message = "A2A Request Completed - Method: #{request.method}, ID: #{request_id}, Duration: #{duration.round(3)}s"
230
+
231
+ if @options[:log_response] && result
232
+ filtered_result = filter_sensitive_data(result)
233
+ message += ", Response: #{filtered_result.inspect}"
234
+ end
235
+
236
+ @logger.send(@level, message)
237
+ end
238
+
239
+ ##
240
+ # Log detailed request error
241
+ def log_detailed_error(request, _context, error, duration, request_id)
242
+ message = "A2A Request Failed - Method: #{request.method}, ID: #{request_id}, Duration: #{duration.round(3)}s"
243
+ message += ", Error: #{error.class.name}: #{error.message}"
244
+
245
+ if @options[:log_errors] && error.respond_to?(:backtrace) && error.backtrace
246
+ message += ", Backtrace: #{error.backtrace.first(3).join(' | ')}"
247
+ end
248
+
249
+ @logger.error(message)
250
+ end
251
+
252
+ ##
253
+ # Log structured request start
254
+ def log_structured_start(request, context, request_id)
255
+ data = {
256
+ event: "a2a_request_start",
257
+ method: request.method,
258
+ request_id: request_id,
259
+ timestamp: Time.now.iso8601
260
+ }
261
+
262
+ data[:params] = filter_sensitive_data(request.params) if @options[:log_params] && !request.params.empty?
263
+
264
+ if @options[:include_context]
265
+ data[:authenticated] = context.authenticated?
266
+ data[:user] = context.user if context.user
267
+ end
268
+
269
+ @logger.send(@level, format_structured_log(data))
270
+ end
271
+
272
+ ##
273
+ # Log structured request success
274
+ def log_structured_success(request, _context, result, duration, request_id)
275
+ data = {
276
+ event: "a2a_request_success",
277
+ method: request.method,
278
+ request_id: request_id,
279
+ duration: duration.round(3),
280
+ timestamp: Time.now.iso8601
281
+ }
282
+
283
+ data[:response] = filter_sensitive_data(result) if @options[:log_response] && result
284
+
285
+ @logger.send(@level, format_structured_log(data))
286
+ end
287
+
288
+ ##
289
+ # Log structured request error
290
+ def log_structured_error(request, _context, error, duration, request_id)
291
+ data = {
292
+ event: "a2a_request_error",
293
+ method: request.method,
294
+ request_id: request_id,
295
+ duration: duration.round(3),
296
+ error_class: error.class.name,
297
+ error_message: error.message,
298
+ timestamp: Time.now.iso8601
299
+ }
300
+
301
+ data[:backtrace] = error.backtrace.first(5) if @options[:log_errors] && error.respond_to?(:backtrace) && error.backtrace
302
+
303
+ data[:error_code] = error.code if error.respond_to?(:code)
304
+
305
+ @logger.error(format_structured_log(data))
306
+ end
307
+
308
+ ##
309
+ # Log JSON format (same as structured but always JSON)
310
+ def log_json_start(request, context, request_id)
311
+ log_structured_start(request, context, request_id)
312
+ end
313
+
314
+ def log_json_success(request, context, result, duration, request_id)
315
+ log_structured_success(request, context, result, duration, request_id)
316
+ end
317
+
318
+ def log_json_error(request, context, error, duration, request_id)
319
+ log_structured_error(request, context, error, duration, request_id)
320
+ end
321
+
322
+ ##
323
+ # Format structured log data
324
+ #
325
+ # @param data [Hash] The log data
326
+ # @return [String] Formatted log message
327
+ def format_structured_log(data)
328
+ if @format == :json
329
+ JSON.generate(data)
330
+ else
331
+ # Key=value format for structured logs
332
+ data.map { |k, v| "#{k}=#{v.inspect}" }.join(" ")
333
+ end
334
+ end
335
+
336
+ ##
337
+ # Filter sensitive data from parameters/responses
338
+ #
339
+ # @param data [Object] The data to filter
340
+ # @return [Object] Filtered data
341
+ def filter_sensitive_data(data)
342
+ case data
343
+ when Hash
344
+ filtered = {}
345
+ data.each do |key, value|
346
+ filtered[key] = if @options[:filtered_params].include?(key.to_s.downcase)
347
+ "[FILTERED]"
348
+ else
349
+ filter_sensitive_data(value)
350
+ end
351
+ end
352
+ filtered
353
+ when Array
354
+ data.map { |item| filter_sensitive_data(item) }
355
+ else
356
+ data
357
+ end
358
+ end
359
+ end
360
+ end
361
+ end
362
+ end