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,278 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+ require "zlib"
5
+ require_relative "performance"
6
+
7
+ ##
8
+ # Memory-efficient message buffer for handling large messages
9
+ #
10
+ # Provides streaming capabilities and compression for large message handling
11
+ # to optimize memory usage and performance.
12
+ #
13
+ module A2A
14
+ module Utils
15
+ class MessageBuffer
16
+ # Default buffer size (64KB)
17
+ DEFAULT_BUFFER_SIZE = 64 * 1024
18
+
19
+ # Compression threshold (1MB)
20
+ COMPRESSION_THRESHOLD = 1024 * 1024
21
+
22
+ attr_reader :size, :compressed, :encoding
23
+
24
+ ##
25
+ # Initialize a new message buffer
26
+ #
27
+ # @param initial_capacity [Integer] Initial buffer capacity
28
+ # @param compress [Boolean] Whether to use compression for large messages
29
+ # @param encoding [Encoding] Text encoding to use
30
+ def initialize(initial_capacity: DEFAULT_BUFFER_SIZE, compress: true, encoding: Encoding::UTF_8)
31
+ @buffer = StringIO.new
32
+ @buffer.set_encoding(encoding)
33
+ @initial_capacity = initial_capacity
34
+ @compress = compress
35
+ @compressed = false
36
+ @encoding = encoding
37
+ @chunks = []
38
+ @total_size = 0
39
+ end
40
+
41
+ ##
42
+ # Write data to the buffer
43
+ #
44
+ # @param data [String] Data to write
45
+ # @return [Integer] Number of bytes written
46
+ def write(data)
47
+ data = data.to_s.encode(@encoding) unless data.encoding == @encoding
48
+
49
+ if @compressed
50
+ # If already compressed, decompress first
51
+ decompress!
52
+ end
53
+
54
+ bytes_written = @buffer.write(data)
55
+ @total_size += bytes_written
56
+
57
+ # Compress if buffer gets too large
58
+ compress! if @compress && @total_size > COMPRESSION_THRESHOLD && !@compressed
59
+
60
+ bytes_written
61
+ end
62
+
63
+ ##
64
+ # Append data to the buffer (alias for write)
65
+ #
66
+ # @param data [String] Data to append
67
+ # @return [Integer] Number of bytes written
68
+ def <<(data)
69
+ write(data)
70
+ end
71
+
72
+ ##
73
+ # Read data from the buffer
74
+ #
75
+ # @param length [Integer, nil] Number of bytes to read (nil for all)
76
+ # @return [String] Data read from buffer
77
+ def read(length = nil)
78
+ decompress! if @compressed
79
+
80
+ @buffer.rewind
81
+ data = @buffer.read(length)
82
+ @buffer.rewind
83
+ data
84
+ end
85
+
86
+ ##
87
+ # Get the current size of the buffer
88
+ #
89
+ # @return [Integer] Size in bytes
90
+ def size
91
+ @compressed ? @compressed_size : @buffer.size
92
+ end
93
+
94
+ ##
95
+ # Check if buffer is empty
96
+ #
97
+ # @return [Boolean] True if buffer is empty
98
+ def empty?
99
+ size.zero?
100
+ end
101
+
102
+ ##
103
+ # Clear the buffer
104
+ #
105
+ def clear!
106
+ @buffer = StringIO.new
107
+ @buffer.set_encoding(@encoding)
108
+ @compressed = false
109
+ @compressed_size = 0
110
+ @total_size = 0
111
+ @chunks.clear
112
+ end
113
+
114
+ ##
115
+ # Get buffer contents as string
116
+ #
117
+ # @return [String] Buffer contents
118
+ def to_s
119
+ read
120
+ end
121
+
122
+ ##
123
+ # Get buffer contents as JSON
124
+ #
125
+ # @return [String] JSON representation
126
+ def to_json(*_args)
127
+ A2A::Utils::Performance.optimized_json_generate(to_s)
128
+ end
129
+
130
+ ##
131
+ # Create buffer from JSON string
132
+ #
133
+ # @param json_string [String] JSON string
134
+ # @return [MessageBuffer] New buffer with JSON data
135
+ def self.from_json(json_string)
136
+ data = A2A::Utils::Performance.optimized_json_parse(json_string)
137
+ buffer = new
138
+ buffer.write(data.to_s)
139
+ buffer
140
+ end
141
+
142
+ ##
143
+ # Stream data in chunks
144
+ #
145
+ # @param chunk_size [Integer] Size of each chunk
146
+ # @yield [String] Each chunk of data
147
+ def each_chunk(chunk_size = DEFAULT_BUFFER_SIZE)
148
+ return enum_for(:each_chunk, chunk_size) unless block_given?
149
+
150
+ decompress! if @compressed
151
+
152
+ @buffer.rewind
153
+
154
+ while (chunk = @buffer.read(chunk_size))
155
+ yield chunk
156
+ end
157
+
158
+ @buffer.rewind
159
+ end
160
+
161
+ ##
162
+ # Compress buffer contents
163
+ #
164
+ def compress!
165
+ return if @compressed || @buffer.size < COMPRESSION_THRESHOLD
166
+
167
+ @buffer.rewind
168
+ original_data = @buffer.read
169
+ @buffer.rewind
170
+
171
+ compressed_data = Zlib::Deflate.deflate(original_data)
172
+
173
+ # Only use compression if it actually saves space
174
+ return unless compressed_data.size < original_data.size
175
+
176
+ @buffer = StringIO.new(compressed_data)
177
+ @compressed = true
178
+ @compressed_size = compressed_data.size
179
+ @original_size = original_data.size
180
+ end
181
+
182
+ ##
183
+ # Decompress buffer contents
184
+ #
185
+ def decompress!
186
+ return unless @compressed
187
+
188
+ @buffer.rewind
189
+ compressed_data = @buffer.read
190
+ @buffer.rewind
191
+
192
+ original_data = Zlib::Inflate.inflate(compressed_data)
193
+
194
+ @buffer = StringIO.new
195
+ @buffer.set_encoding(@encoding)
196
+ @buffer.write(original_data)
197
+ @buffer.rewind
198
+
199
+ @compressed = false
200
+ @total_size = original_data.size
201
+ end
202
+
203
+ ##
204
+ # Get compression ratio
205
+ #
206
+ # @return [Float] Compression ratio (0.0 to 1.0)
207
+ def compression_ratio
208
+ return 0.0 unless @compressed && @original_size&.positive?
209
+
210
+ 1.0 - (@compressed_size.to_f / @original_size)
211
+ end
212
+
213
+ ##
214
+ # Get buffer statistics
215
+ #
216
+ # @return [Hash] Buffer statistics
217
+ def stats
218
+ {
219
+ size: size,
220
+ compressed: @compressed,
221
+ compression_ratio: compression_ratio,
222
+ encoding: @encoding.name,
223
+ chunks: @chunks.size
224
+ }
225
+ end
226
+
227
+ ##
228
+ # Optimize buffer for memory usage
229
+ #
230
+ def optimize!
231
+ # Force garbage collection on buffer
232
+ GC.start if defined?(GC)
233
+
234
+ # Compress if beneficial
235
+ compress! if @compress && !@compressed && size > COMPRESSION_THRESHOLD
236
+
237
+ # Compact string if possible (Ruby 2.7+)
238
+ return unless @buffer.string.respond_to?(:squeeze!)
239
+
240
+ @buffer.string.squeeze!
241
+ end
242
+
243
+ ##
244
+ # Create a memory-mapped buffer for very large data
245
+ #
246
+ # @param file_path [String] Path to temporary file
247
+ # @return [MessageBuffer] Memory-mapped buffer
248
+ def self.create_memory_mapped(file_path)
249
+ # This would require additional gems like 'mmap' for true memory mapping
250
+ # For now, we'll use a file-backed buffer
251
+ buffer = new
252
+ buffer.instance_variable_set(:@file_backed, true)
253
+ buffer.instance_variable_set(:@file_path, file_path)
254
+ buffer
255
+ end
256
+
257
+ private
258
+
259
+ ##
260
+ # Ensure buffer has minimum capacity
261
+ #
262
+ # @param capacity [Integer] Required capacity
263
+ def ensure_capacity(capacity)
264
+ return if @buffer.size >= capacity
265
+
266
+ # Expand buffer if needed
267
+ current_pos = @buffer.pos
268
+ @buffer.seek(0, IO::SEEK_END)
269
+
270
+ # Write null bytes to expand
271
+ padding_needed = capacity - @buffer.size
272
+ @buffer.write("\0" * padding_needed) if padding_needed.positive?
273
+
274
+ @buffer.pos = current_pos
275
+ end
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Performance optimization utilities
5
+ #
6
+ # Provides tools for profiling, memory optimization, and performance monitoring
7
+ # across the A2A Ruby gem.
8
+ #
9
+ module A2A
10
+ module Utils
11
+ class Performance
12
+ # Memory usage tracking
13
+ @memory_snapshots = []
14
+ @performance_data = {}
15
+
16
+ class << self
17
+ attr_reader :memory_snapshots, :performance_data
18
+
19
+ ##
20
+ # Profile a block of code and return execution time
21
+ #
22
+ # @param label [String] Label for the profiling session
23
+ # @yield Block to profile
24
+ # @return [Object] Result of the block
25
+ def profile(label = "operation")
26
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
27
+ start_memory = memory_usage
28
+
29
+ result = yield
30
+
31
+ end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
32
+ end_memory = memory_usage
33
+
34
+ duration = end_time - start_time
35
+ memory_delta = end_memory - start_memory
36
+
37
+ record_performance_data(label, duration, memory_delta)
38
+
39
+ if A2A.configuration.respond_to?(:performance_logging) && A2A.configuration.performance_logging
40
+ A2A.logger.info("Performance [#{label}]: #{duration.round(4)}s, Memory: #{format_bytes(memory_delta)}")
41
+ end
42
+
43
+ result
44
+ end
45
+
46
+ ##
47
+ # Get current memory usage in bytes
48
+ #
49
+ # @return [Integer] Memory usage in bytes
50
+ def memory_usage
51
+ if defined?(GC.stat)
52
+ # Use GC stats for more accurate memory tracking
53
+ GC.stat(:heap_allocated_pages) * GC.stat(:heap_page_size)
54
+ else
55
+ # Fallback to process memory (less accurate)
56
+ `ps -o rss= -p #{Process.pid}`.to_i * 1024
57
+ end
58
+ rescue StandardError
59
+ 0
60
+ end
61
+
62
+ ##
63
+ # Take a memory snapshot
64
+ #
65
+ # @param label [String] Label for the snapshot
66
+ def memory_snapshot(label = Time.now.to_s)
67
+ snapshot = {
68
+ label: label,
69
+ timestamp: Time.now,
70
+ memory: memory_usage,
71
+ gc_stats: defined?(GC.stat) ? GC.stat : {}
72
+ }
73
+
74
+ @memory_snapshots << snapshot
75
+
76
+ # Keep only last 100 snapshots
77
+ @memory_snapshots = @memory_snapshots.last(100) if @memory_snapshots.size > 100
78
+
79
+ snapshot
80
+ end
81
+
82
+ ##
83
+ # Optimize garbage collection
84
+ #
85
+ def optimize_gc!
86
+ return unless defined?(GC)
87
+
88
+ # Force garbage collection
89
+ GC.start
90
+
91
+ # Compact heap if available (Ruby 2.7+)
92
+ GC.compact if GC.respond_to?(:compact)
93
+
94
+ # Tune GC settings for better performance
95
+ return unless defined?(GC.tune)
96
+
97
+ GC.tune(
98
+ heap_growth_factor: 1.8,
99
+ heap_growth_max_slots: 0,
100
+ heap_init_slots: 10_000,
101
+ heap_free_slots: 4096,
102
+ heap_oldobject_limit_factor: 2.0
103
+ )
104
+ end
105
+
106
+ ##
107
+ # Optimize JSON parsing performance
108
+ #
109
+ # @param json_string [String] JSON string to parse
110
+ # @return [Hash, Array] Parsed JSON
111
+ def optimized_json_parse(json_string)
112
+ # Use Oj if available for better performance
113
+ if defined?(Oj)
114
+ Oj.load(json_string, mode: :strict)
115
+ else
116
+ JSON.parse(json_string)
117
+ end
118
+ end
119
+
120
+ ##
121
+ # Optimize JSON generation performance
122
+ #
123
+ # @param object [Object] Object to serialize
124
+ # @return [String] JSON string
125
+ def optimized_json_generate(object)
126
+ # Use Oj if available for better performance
127
+ if defined?(Oj)
128
+ Oj.dump(object, mode: :compat)
129
+ else
130
+ JSON.generate(object)
131
+ end
132
+ end
133
+
134
+ ##
135
+ # Create an optimized string buffer for large message handling
136
+ #
137
+ # @param initial_capacity [Integer] Initial buffer capacity
138
+ # @return [StringIO] Optimized string buffer
139
+ def create_string_buffer(initial_capacity = 8192)
140
+ buffer = StringIO.new
141
+ buffer.set_encoding(Encoding::UTF_8)
142
+
143
+ # Pre-allocate capacity if possible
144
+ buffer.capacity = initial_capacity if buffer.respond_to?(:capacity=)
145
+
146
+ buffer
147
+ end
148
+
149
+ ##
150
+ # Batch process items for better performance
151
+ #
152
+ # @param items [Array] Items to process
153
+ # @param batch_size [Integer] Size of each batch
154
+ # @yield [Array] Block to process each batch
155
+ # @return [Array] Results from all batches
156
+ def batch_process(items, batch_size = 100)
157
+ results = []
158
+
159
+ items.each_slice(batch_size) do |batch|
160
+ batch_result = yield(batch)
161
+ results.concat(Array(batch_result))
162
+ end
163
+
164
+ results
165
+ end
166
+
167
+ ##
168
+ # Get performance statistics
169
+ #
170
+ # @return [Hash] Performance statistics
171
+ def statistics
172
+ {
173
+ memory_snapshots: @memory_snapshots.size,
174
+ current_memory: memory_usage,
175
+ performance_data: @performance_data,
176
+ gc_stats: defined?(GC.stat) ? GC.stat : {}
177
+ }
178
+ end
179
+
180
+ ##
181
+ # Reset performance tracking data
182
+ #
183
+ def reset!
184
+ @memory_snapshots.clear
185
+ @performance_data.clear
186
+ end
187
+
188
+ ##
189
+ # Check if performance optimizations are available
190
+ #
191
+ # @return [Hash] Available optimizations
192
+ def available_optimizations
193
+ {
194
+ oj_json: defined?(Oj),
195
+ gc_compact: defined?(GC) && GC.respond_to?(:compact),
196
+ gc_tune: defined?(GC.tune),
197
+ net_http_persistent: defined?(Net::HTTP::Persistent)
198
+ }
199
+ end
200
+
201
+ private
202
+
203
+ ##
204
+ # Record performance data
205
+ #
206
+ # @param label [String] Operation label
207
+ # @param duration [Float] Duration in seconds
208
+ # @param memory_delta [Integer] Memory change in bytes
209
+ def record_performance_data(label, duration, memory_delta)
210
+ @performance_data[label] ||= {
211
+ count: 0,
212
+ total_time: 0.0,
213
+ avg_time: 0.0,
214
+ min_time: Float::INFINITY,
215
+ max_time: 0.0,
216
+ total_memory: 0,
217
+ avg_memory: 0.0
218
+ }
219
+
220
+ data = @performance_data[label]
221
+ data[:count] += 1
222
+ data[:total_time] += duration
223
+ data[:avg_time] = data[:total_time] / data[:count]
224
+ data[:min_time] = [data[:min_time], duration].min
225
+ data[:max_time] = [data[:max_time], duration].max
226
+ data[:total_memory] += memory_delta
227
+ data[:avg_memory] = data[:total_memory].to_f / data[:count]
228
+ end
229
+
230
+ ##
231
+ # Format bytes for human-readable output
232
+ #
233
+ # @param bytes [Integer] Number of bytes
234
+ # @return [String] Formatted string
235
+ def format_bytes(bytes)
236
+ return "0 B" if bytes.zero?
237
+
238
+ units = %w[B KB MB GB TB]
239
+ exp = (Math.log(bytes.abs) / Math.log(1024)).floor
240
+ exp = [exp, units.length - 1].min
241
+
242
+ "#{(bytes / (1024.0**exp)).round(2)} #{units[exp]}"
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Utility module for Rails detection and compatibility
5
+ #
6
+ # Provides consistent Rails detection methods that can be used throughout
7
+ # the A2A Ruby gem to handle Rails availability gracefully.
8
+ #
9
+ module A2A
10
+ module Utils
11
+ module RailsDetection
12
+ # Check if Rails is available and properly defined
13
+ # @return [Boolean] true if Rails is available
14
+ def rails_available?
15
+ defined?(::Rails) && ::Rails.respond_to?(:version)
16
+ end
17
+
18
+ # Check if Rails version requires validation
19
+ # @return [Boolean] true if Rails version is less than 6.0
20
+ def rails_version_requires_validation?
21
+ return false unless rails_available?
22
+
23
+ begin
24
+ ::Rails.version < "6.0"
25
+ rescue StandardError
26
+ # If we can't get the version, assume validation is not required
27
+ false
28
+ end
29
+ end
30
+
31
+ # Check if Rails version is supported (6.0+)
32
+ # @return [Boolean] true if Rails version is 6.0 or higher
33
+ def rails_version_supported?
34
+ return false unless rails_available?
35
+
36
+ begin
37
+ ::Rails.version >= "6.0"
38
+ rescue StandardError
39
+ false
40
+ end
41
+ end
42
+
43
+ # Get Rails logger if available
44
+ # @return [Logger, nil] Rails logger or nil if not available
45
+ def rails_logger
46
+ return nil unless rails_available?
47
+ return nil unless ::Rails.respond_to?(:logger)
48
+
49
+ ::Rails.logger
50
+ end
51
+
52
+ # Get Rails environment if available
53
+ # @return [String, nil] Rails environment or nil if not available
54
+ def rails_environment
55
+ return nil unless rails_available?
56
+ return nil unless ::Rails.respond_to?(:env)
57
+
58
+ ::Rails.env
59
+ end
60
+
61
+ # Get Rails application if available
62
+ # @return [Rails::Application, nil] Rails application or nil if not available
63
+ def rails_application
64
+ return nil unless rails_available?
65
+ return nil unless ::Rails.respond_to?(:application)
66
+
67
+ ::Rails.application
68
+ end
69
+
70
+ # Check if we're in a Rails production environment
71
+ # @return [Boolean] true if in Rails production environment
72
+ def rails_production?
73
+ env = rails_environment
74
+ env && env == "production"
75
+ end
76
+
77
+ # Check if we're in a Rails development environment
78
+ # @return [Boolean] true if in Rails development environment
79
+ def rails_development?
80
+ env = rails_environment
81
+ env && env == "development"
82
+ end
83
+
84
+ # Get Rails version string if available
85
+ # @return [String, nil] Rails version or nil if not available
86
+ def rails_version
87
+ return nil unless rails_available?
88
+
89
+ begin
90
+ ::Rails.version
91
+ rescue StandardError
92
+ nil
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end