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
@@ -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
|
data/lib/a2a/version.rb
ADDED
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
|