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,306 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "json"
5
+ require "securerandom"
6
+
7
+ ##
8
+ # Structured logger for A2A operations
9
+ #
10
+ # Provides structured logging with correlation IDs, performance metrics,
11
+ # and integration with monitoring systems.
12
+ #
13
+ module A2A
14
+ module Utils
15
+ class StructuredLogger
16
+ # Log levels
17
+ LEVELS = {
18
+ debug: Logger::DEBUG,
19
+ info: Logger::INFO,
20
+ warn: Logger::WARN,
21
+ error: Logger::ERROR,
22
+ fatal: Logger::FATAL
23
+ }.freeze
24
+
25
+ attr_accessor :correlation_id
26
+ attr_reader :logger, :service_name, :version
27
+
28
+ ##
29
+ # Initialize structured logger
30
+ #
31
+ # @param output [IO, String] Output destination (IO object or file path)
32
+ # @param level [Symbol, Integer] Log level
33
+ # @param service_name [String] Name of the service
34
+ # @param version [String] Version of the service
35
+ # @param correlation_id [String, nil] Correlation ID for request tracking
36
+ def initialize(output: $stdout, level: :info, service_name: "a2a-ruby", version: A2A::VERSION,
37
+ correlation_id: nil)
38
+ @logger = Logger.new(output)
39
+ @logger.level = LEVELS[level] || level
40
+ @logger.formatter = method(:format_log_entry)
41
+ @service_name = service_name
42
+ @version = version
43
+ @correlation_id = correlation_id || generate_correlation_id
44
+ @start_time = Time.now
45
+ end
46
+
47
+ ##
48
+ # Log a debug message
49
+ #
50
+ # @param message [String] Log message
51
+ # @param context [Hash] Additional context
52
+ def debug(message, **context)
53
+ log(:debug, message, **context)
54
+ end
55
+
56
+ ##
57
+ # Log an info message
58
+ #
59
+ # @param message [String] Log message
60
+ # @param context [Hash] Additional context
61
+ def info(message, **context)
62
+ log(:info, message, **context)
63
+ end
64
+
65
+ ##
66
+ # Log a warning message
67
+ #
68
+ # @param message [String] Log message
69
+ # @param context [Hash] Additional context
70
+ def warn(message, **context)
71
+ log(:warn, message, **context)
72
+ end
73
+
74
+ ##
75
+ # Log an error message
76
+ #
77
+ # @param message [String] Log message
78
+ # @param error [Exception, nil] Exception object
79
+ # @param context [Hash] Additional context
80
+ def error(message, error: nil, **context)
81
+ context[:error] = format_error(error) if error
82
+ log(:error, message, **context)
83
+ end
84
+
85
+ ##
86
+ # Log a fatal message
87
+ #
88
+ # @param message [String] Log message
89
+ # @param error [Exception, nil] Exception object
90
+ # @param context [Hash] Additional context
91
+ def fatal(message, error: nil, **context)
92
+ context[:error] = format_error(error) if error
93
+ log(:fatal, message, **context)
94
+ end
95
+
96
+ ##
97
+ # Log with timing information
98
+ #
99
+ # @param message [String] Log message
100
+ # @param level [Symbol] Log level
101
+ # @param context [Hash] Additional context
102
+ # @yield Block to time
103
+ # @return [Object] Result of the block
104
+ def timed(message, level: :info, **context)
105
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
106
+
107
+ begin
108
+ result = yield
109
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
110
+
111
+ log(level, "#{message} completed", duration: duration, **context)
112
+ result
113
+ rescue StandardError => e
114
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
115
+
116
+ error("#{message} failed",
117
+ error: e,
118
+ duration: duration,
119
+ **context)
120
+ raise
121
+ end
122
+ end
123
+
124
+ ##
125
+ # Log HTTP request/response
126
+ #
127
+ # @param method [String] HTTP method
128
+ # @param url [String] Request URL
129
+ # @param status [Integer] Response status
130
+ # @param duration [Float] Request duration
131
+ # @param context [Hash] Additional context
132
+ def http_request(method:, url:, status:, duration:, **context)
133
+ log_data = {
134
+ http_method: method.to_s.upcase,
135
+ url: url,
136
+ status_code: status,
137
+ duration: duration,
138
+ **context
139
+ }
140
+
141
+ level = case status
142
+ when 200..299 then :info
143
+ when 300..399 then :info
144
+ when 400..499 then :warn
145
+ when 500..599 then :error
146
+ else :info
147
+ end
148
+
149
+ log(level, "HTTP #{method.upcase} #{url}", **log_data)
150
+ end
151
+
152
+ ##
153
+ # Log task operation
154
+ #
155
+ # @param operation [String] Task operation (create, update, cancel, etc.)
156
+ # @param task_id [String] Task ID
157
+ # @param context_id [String, nil] Context ID
158
+ # @param status [String, nil] Task status
159
+ # @param context [Hash] Additional context
160
+ def task_operation(operation:, task_id:, context_id: nil, status: nil, **context)
161
+ log_data = {
162
+ operation: operation,
163
+ task_id: task_id,
164
+ context_id: context_id,
165
+ task_status: status,
166
+ **context
167
+ }.compact
168
+
169
+ log(:info, "Task #{operation}", **log_data)
170
+ end
171
+
172
+ ##
173
+ # Log performance metrics
174
+ #
175
+ # @param metric_name [String] Name of the metric
176
+ # @param value [Numeric] Metric value
177
+ # @param unit [String] Unit of measurement
178
+ # @param tags [Hash] Metric tags
179
+ def metric(metric_name, value, unit: nil, **tags)
180
+ log_data = {
181
+ metric_name: metric_name,
182
+ metric_value: value,
183
+ metric_unit: unit,
184
+ metric_tags: tags
185
+ }.compact
186
+
187
+ log(:info, "Metric: #{metric_name}", **log_data)
188
+ end
189
+
190
+ ##
191
+ # Create a child logger with additional context
192
+ #
193
+ # @param context [Hash] Additional context to include in all log entries
194
+ # @return [StructuredLogger] Child logger
195
+ def child(**context)
196
+ child_logger = self.class.new(
197
+ output: @logger.instance_variable_get(:@logdev).dev,
198
+ level: @logger.level,
199
+ service_name: @service_name,
200
+ version: @version,
201
+ correlation_id: @correlation_id
202
+ )
203
+
204
+ child_logger.instance_variable_set(:@additional_context, context)
205
+ child_logger
206
+ end
207
+
208
+ ##
209
+ # Set correlation ID for request tracking
210
+ #
211
+ # @param correlation_id [String] Correlation ID
212
+
213
+ ##
214
+ # Generate a new correlation ID
215
+ #
216
+ # @return [String] New correlation ID
217
+ def generate_correlation_id
218
+ SecureRandom.hex(8)
219
+ end
220
+
221
+ ##
222
+ # Get logger statistics
223
+ #
224
+ # @return [Hash] Logger statistics
225
+ def stats
226
+ {
227
+ service_name: @service_name,
228
+ version: @version,
229
+ correlation_id: @correlation_id,
230
+ uptime: Time.now - @start_time,
231
+ log_level: @logger.level
232
+ }
233
+ end
234
+
235
+ private
236
+
237
+ ##
238
+ # Log a message with structured data
239
+ #
240
+ # @param level [Symbol] Log level
241
+ # @param message [String] Log message
242
+ # @param context [Hash] Additional context
243
+ def log(level, message, **context)
244
+ return unless @logger.public_send("#{level}?")
245
+
246
+ # Merge additional context from child loggers
247
+ additional_context = instance_variable_get(:@additional_context) || {}
248
+ context = additional_context.merge(context)
249
+
250
+ log_entry = build_log_entry(level, message, **context)
251
+ @logger.public_send(level, log_entry)
252
+ end
253
+
254
+ ##
255
+ # Build structured log entry
256
+ #
257
+ # @param level [Symbol] Log level
258
+ # @param message [String] Log message
259
+ # @param context [Hash] Additional context
260
+ # @return [Hash] Structured log entry
261
+ def build_log_entry(level, message, **context)
262
+ {
263
+ timestamp: Time.now.utc.iso8601(3),
264
+ level: level.to_s.upcase,
265
+ message: message,
266
+ service: @service_name,
267
+ version: @version,
268
+ correlation_id: @correlation_id,
269
+ thread_id: Thread.current.object_id,
270
+ **context
271
+ }
272
+ end
273
+
274
+ ##
275
+ # Format log entry for output
276
+ #
277
+ # @param severity [String] Log severity
278
+ # @param datetime [Time] Log timestamp
279
+ # @param progname [String] Program name
280
+ # @param msg [Hash] Log message data
281
+ # @return [String] Formatted log entry
282
+ def format_log_entry(severity, _datetime, _progname, msg)
283
+ if msg.is_a?(Hash)
284
+ "#{JSON.generate(msg)}\n"
285
+ else
286
+ # Fallback for non-structured messages
287
+ entry = build_log_entry(severity.downcase.to_sym, msg.to_s)
288
+ "#{JSON.generate(entry)}\n"
289
+ end
290
+ end
291
+
292
+ ##
293
+ # Format error information
294
+ #
295
+ # @param error [Exception] Exception object
296
+ # @return [Hash] Formatted error information
297
+ def format_error(error)
298
+ {
299
+ class: error.class.name,
300
+ message: error.message,
301
+ backtrace: error.backtrace&.first(10) # Limit backtrace length
302
+ }
303
+ end
304
+ end
305
+ end
306
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Time handling utilities for consistent timestamp generation
5
+ #
6
+ # Provides compatibility layer for Time.current vs Time.now and
7
+ # consistent timestamp formatting across different Ruby versions and environments.
8
+ #
9
+ module A2A
10
+ module Utils
11
+ module TimeHelpers
12
+ class << self
13
+ ##
14
+ # Get current time with Rails compatibility
15
+ #
16
+ # Uses Time.current if available (Rails), otherwise falls back to Time.now
17
+ #
18
+ # @return [Time] Current time
19
+ def current_time
20
+ if defined?(Time.current)
21
+ Time.current
22
+ else
23
+ Time.now
24
+ end
25
+ end
26
+
27
+ ##
28
+ # Get current timestamp in ISO8601 format
29
+ #
30
+ # @return [String] ISO8601 formatted timestamp
31
+ def current_timestamp
32
+ current_time.utc.iso8601
33
+ end
34
+
35
+ ##
36
+ # Get current timestamp for tests (always uses Time.now for consistency)
37
+ #
38
+ # @return [String] ISO8601 formatted timestamp
39
+ def test_timestamp
40
+ Time.now.utc.iso8601
41
+ end
42
+
43
+ ##
44
+ # Get current time as integer (Unix timestamp)
45
+ #
46
+ # @return [Integer] Unix timestamp
47
+ def current_time_i
48
+ current_time.to_i
49
+ end
50
+
51
+ ##
52
+ # Parse ISO8601 timestamp string
53
+ #
54
+ # @param timestamp [String] ISO8601 timestamp string
55
+ # @return [Time] Parsed time object
56
+ def parse_timestamp(timestamp)
57
+ Time.parse(timestamp)
58
+ rescue ArgumentError => e
59
+ raise A2A::Errors::InvalidTimestamp, "Invalid timestamp format: #{timestamp} - #{e.message}"
60
+ end
61
+
62
+ ##
63
+ # Format time as ISO8601 string
64
+ #
65
+ # @param time [Time] Time object to format
66
+ # @return [String] ISO8601 formatted string
67
+ def format_timestamp(time)
68
+ time.utc.iso8601
69
+ end
70
+
71
+ ##
72
+ # Add time duration to current time
73
+ #
74
+ # @param duration [Numeric] Duration in seconds
75
+ # @return [Time] Future time
76
+ def time_from_now(duration)
77
+ current_time + duration
78
+ end
79
+
80
+ ##
81
+ # Add time duration to current time and format as ISO8601
82
+ #
83
+ # @param duration [Numeric] Duration in seconds
84
+ # @return [String] ISO8601 formatted future timestamp
85
+ def timestamp_from_now(duration)
86
+ format_timestamp(time_from_now(duration))
87
+ end
88
+
89
+ ##
90
+ # Check if timestamp is in the past
91
+ #
92
+ # @param timestamp [String, Time] Timestamp to check
93
+ # @return [Boolean] True if timestamp is in the past
94
+ def past?(timestamp)
95
+ time = timestamp.is_a?(String) ? parse_timestamp(timestamp) : timestamp
96
+ time < current_time
97
+ end
98
+
99
+ ##
100
+ # Check if timestamp is in the future
101
+ #
102
+ # @param timestamp [String, Time] Timestamp to check
103
+ # @return [Boolean] True if timestamp is in the future
104
+ def future?(timestamp)
105
+ time = timestamp.is_a?(String) ? parse_timestamp(timestamp) : timestamp
106
+ time > current_time
107
+ end
108
+
109
+ ##
110
+ # Calculate duration between two timestamps
111
+ #
112
+ # @param start_time [String, Time] Start timestamp
113
+ # @param end_time [String, Time] End timestamp (defaults to current time)
114
+ # @return [Float] Duration in seconds
115
+ def duration_between(start_time, end_time = nil)
116
+ start_t = start_time.is_a?(String) ? parse_timestamp(start_time) : start_time
117
+ end_t = if end_time
118
+ end_time.is_a?(String) ? parse_timestamp(end_time) : end_time
119
+ else
120
+ current_time
121
+ end
122
+
123
+ end_t - start_t
124
+ end
125
+
126
+ ##
127
+ # Format duration in human-readable format
128
+ #
129
+ # @param duration [Numeric] Duration in seconds
130
+ # @return [String] Human-readable duration
131
+ def format_duration(duration)
132
+ return "0s" if duration.zero?
133
+
134
+ parts = []
135
+
136
+ if duration >= 86_400 # days
137
+ days = (duration / 86_400).floor
138
+ parts << "#{days}d"
139
+ duration %= 86_400
140
+ end
141
+
142
+ if duration >= 3600 # hours
143
+ hours = (duration / 3600).floor
144
+ parts << "#{hours}h"
145
+ duration %= 3600
146
+ end
147
+
148
+ if duration >= 60 # minutes
149
+ minutes = (duration / 60).floor
150
+ parts << "#{minutes}m"
151
+ duration %= 60
152
+ end
153
+
154
+ if duration.positive? || parts.empty?
155
+ parts << if duration == duration.to_i
156
+ "#{duration.to_i}s"
157
+ else
158
+ "#{duration.round(2)}s"
159
+ end
160
+ end
161
+
162
+ parts.join(" ")
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Placeholder - will be implemented in future tasks
4
+ module A2A
5
+ module Utils
6
+ # Placeholder - will be implemented in future tasks
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ # The version of the A2A Ruby SDK
5
+ VERSION = "1.0.0"
6
+ end
data/lib/a2a-rails.rb ADDED
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Rails-specific entry point for A2A Ruby SDK
4
+ #
5
+ # This file should be required in Rails applications that want to use
6
+ # A2A integration features.
7
+ #
8
+ # @example In Gemfile
9
+ # gem 'a2a-ruby', require: 'a2a-rails'
10
+ #
11
+ # @example In config/application.rb
12
+ # require 'a2a-rails'
13
+ #
14
+ # class Application < Rails::Application
15
+ # config.a2a.enabled = true
16
+ # end
17
+
18
+ # Load the main A2A library first
19
+ require_relative "a2a"
20
+
21
+ # Ensure Rails is available
22
+ begin
23
+ require "rails"
24
+ rescue LoadError
25
+ raise LoadError, "Rails is required for A2A Rails integration. Add 'rails' to your Gemfile."
26
+ end
27
+
28
+ # Load Rails-specific components
29
+ require_relative "a2a/rails/engine"
30
+ require_relative "a2a/rails/controller_helpers"
31
+ require_relative "a2a/rails/a2a_controller"
32
+
33
+ # Extend A2A configuration for Rails-specific options
34
+ module A2A
35
+ class Configuration
36
+ # Rails integration settings
37
+ attr_accessor :rails_integration, :mount_path, :auto_mount,
38
+ :middleware_enabled, :webhook_authentication_required
39
+
40
+ def initialize
41
+ super
42
+
43
+ # Rails-specific defaults
44
+ @rails_integration = false
45
+ @mount_path = "/a2a"
46
+ @auto_mount = true
47
+ @middleware_enabled = true
48
+ @webhook_authentication_required = false
49
+ end
50
+ end
51
+ end
52
+
53
+ # Auto-configure Rails integration if Rails is detected
54
+ if defined?(Rails) && Rails.respond_to?(:application) && Rails.application
55
+ A2A.configure do |config|
56
+ config.rails_integration = true
57
+ end
58
+ end