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
data/docs/events.md ADDED
@@ -0,0 +1,475 @@
1
+ # Event System
2
+
3
+ The A2A Ruby SDK includes a comprehensive event system that enables real-time, asynchronous processing of agent requests and responses. This event-driven architecture allows for streaming responses, task updates, and flexible agent implementations.
4
+
5
+ ## Overview
6
+
7
+ The event system consists of:
8
+
9
+ - **Event**: Represents a single event with type, data, and metadata
10
+ - **EventQueue**: Manages event publishing and subscription
11
+ - **EventConsumer**: Processes events with registered handlers
12
+ - **Event Types**: Predefined event types for different scenarios
13
+
14
+ ## Event Types
15
+
16
+ ### Core Event Types
17
+
18
+ - `task` - Complete task objects
19
+ - `message` - Message objects (user or agent messages)
20
+ - `task_status_update` - Task status change events
21
+ - `task_artifact_update` - Task artifact addition/modification events
22
+
23
+ ### Event Structure
24
+
25
+ ```ruby
26
+ event = A2A::Server::Events::Event.new(
27
+ type: "task_status_update",
28
+ data: status_update_object,
29
+ id: "optional-custom-id" # Auto-generated if not provided
30
+ )
31
+
32
+ # Event properties
33
+ event.type # => "task_status_update"
34
+ event.data # => The event data object
35
+ event.timestamp # => Time when event was created
36
+ event.id # => Unique event identifier
37
+ event.task_id # => Task ID if applicable
38
+ event.context_id # => Context ID if applicable
39
+ ```
40
+
41
+ ## Event Queue
42
+
43
+ ### In-Memory Event Queue
44
+
45
+ ```ruby
46
+ # Create event queue
47
+ queue = A2A::Server::Events::InMemoryEventQueue.new
48
+
49
+ # Publish events
50
+ event = A2A::Server::Events::Event.new(
51
+ type: "message",
52
+ data: message_object
53
+ )
54
+ queue.publish(event)
55
+
56
+ # Subscribe to events
57
+ queue.subscribe do |event|
58
+ puts "Received event: #{event.type}"
59
+ process_event(event)
60
+ end
61
+
62
+ # Subscribe with filtering
63
+ queue.subscribe(->(event) { event.task_event? }) do |event|
64
+ puts "Task-related event: #{event.type}"
65
+ end
66
+
67
+ # Clean up
68
+ queue.close
69
+ ```
70
+
71
+ ### Custom Event Queue Implementation
72
+
73
+ ```ruby
74
+ class RedisEventQueue < A2A::Server::Events::EventQueue
75
+ def initialize(redis_client)
76
+ @redis = redis_client
77
+ @subscribers = []
78
+ end
79
+
80
+ def publish(event)
81
+ @redis.publish("a2a_events", event.to_h.to_json)
82
+ end
83
+
84
+ def subscribe(filter = nil)
85
+ # Implementation for Redis pub/sub
86
+ @redis.subscribe("a2a_events") do |on|
87
+ on.message do |channel, message|
88
+ event_data = JSON.parse(message)
89
+ event = A2A::Server::Events::Event.new(**event_data.symbolize_keys)
90
+
91
+ next if filter && !filter.call(event)
92
+
93
+ yield event if block_given?
94
+ end
95
+ end
96
+ end
97
+
98
+ def close
99
+ @redis.unsubscribe("a2a_events")
100
+ end
101
+
102
+ def closed?
103
+ !@redis.connected?
104
+ end
105
+ end
106
+ ```
107
+
108
+ ## Event Consumer
109
+
110
+ The EventConsumer provides a higher-level interface for processing events:
111
+
112
+ ```ruby
113
+ # Create consumer
114
+ consumer = A2A::Server::Events::EventConsumer.new(event_queue)
115
+
116
+ # Register handlers for specific event types
117
+ consumer.register_handler("task") do |event|
118
+ task = event.data
119
+ puts "Task received: #{task.id}"
120
+
121
+ # Save to database
122
+ TaskStore.save(task)
123
+ end
124
+
125
+ consumer.register_handler("task_status_update") do |event|
126
+ status_update = event.data
127
+ puts "Task #{status_update.task_id} status: #{status_update.status.state}"
128
+
129
+ # Update task in database
130
+ TaskStore.update_status(status_update.task_id, status_update.status)
131
+
132
+ # Send notifications
133
+ NotificationService.notify_status_change(status_update)
134
+ end
135
+
136
+ consumer.register_handler("message") do |event|
137
+ message = event.data
138
+ puts "Message: #{message.parts.first.text}"
139
+
140
+ # Process message
141
+ MessageProcessor.process(message)
142
+ end
143
+
144
+ # Start consuming events
145
+ consumer.start
146
+
147
+ # Stop consuming
148
+ consumer.stop
149
+ ```
150
+
151
+ ## Event Filtering
152
+
153
+ ### Basic Filtering
154
+
155
+ ```ruby
156
+ # Filter by event type
157
+ task_filter = ->(event) { event.task_event? }
158
+ message_filter = ->(event) { event.message_event? }
159
+
160
+ # Filter by task ID
161
+ task_id_filter = ->(event) { event.task_id == "specific-task-id" }
162
+
163
+ # Filter by context ID
164
+ context_filter = ->(event) { event.context_id == "specific-context-id" }
165
+
166
+ # Combine filters
167
+ combined_filter = ->(event) {
168
+ event.task_event? && event.task_id == "task-123"
169
+ }
170
+
171
+ queue.subscribe(combined_filter) do |event|
172
+ # Only receives task events for task-123
173
+ end
174
+ ```
175
+
176
+ ### Advanced Filtering
177
+
178
+ ```ruby
179
+ class EventFilter
180
+ def initialize(criteria)
181
+ @criteria = criteria
182
+ end
183
+
184
+ def call(event)
185
+ @criteria.all? { |key, value| matches?(event, key, value) }
186
+ end
187
+
188
+ private
189
+
190
+ def matches?(event, key, value)
191
+ case key
192
+ when :type
193
+ event.type == value
194
+ when :task_id
195
+ event.task_id == value
196
+ when :context_id
197
+ event.context_id == value
198
+ when :after
199
+ event.timestamp > value
200
+ when :user_id
201
+ event.data.respond_to?(:user_id) && event.data.user_id == value
202
+ else
203
+ false
204
+ end
205
+ end
206
+ end
207
+
208
+ # Usage
209
+ filter = EventFilter.new(
210
+ type: "task_status_update",
211
+ task_id: "task-123",
212
+ after: 1.hour.ago
213
+ )
214
+
215
+ queue.subscribe(filter) do |event|
216
+ # Process filtered events
217
+ end
218
+ ```
219
+
220
+ ## Integration with Agent Executors
221
+
222
+ Agent executors use the event system to communicate results:
223
+
224
+ ```ruby
225
+ class StreamingAgentExecutor < A2A::Server::AgentExecution::AgentExecutor
226
+ def execute(context, event_queue)
227
+ # Create task
228
+ task = create_task(context)
229
+
230
+ # Publish initial task
231
+ publish_task(event_queue, task)
232
+
233
+ # Publish status updates
234
+ publish_task_status_update(
235
+ event_queue,
236
+ task.id,
237
+ task.context_id,
238
+ working_status
239
+ )
240
+
241
+ # Process and publish streaming results
242
+ process_streaming(context.message, event_queue, task)
243
+
244
+ # Publish completion
245
+ publish_task_status_update(
246
+ event_queue,
247
+ task.id,
248
+ task.context_id,
249
+ completed_status
250
+ )
251
+ end
252
+
253
+ private
254
+
255
+ def process_streaming(message, event_queue, task)
256
+ words = message.parts.first.text.split
257
+
258
+ words.each do |word|
259
+ # Create partial response
260
+ response = A2A::Types::Message.new(
261
+ message_id: SecureRandom.uuid,
262
+ context_id: task.context_id,
263
+ role: "assistant",
264
+ parts: [A2A::Types::TextPart.new(text: word)]
265
+ )
266
+
267
+ # Publish message event
268
+ publish_message(event_queue, response)
269
+
270
+ sleep 0.1 # Simulate processing delay
271
+ end
272
+ end
273
+ end
274
+ ```
275
+
276
+ ## Event Persistence
277
+
278
+ ### Database Event Store
279
+
280
+ ```ruby
281
+ class DatabaseEventStore
282
+ def initialize(db_connection)
283
+ @db = db_connection
284
+ end
285
+
286
+ def store_event(event)
287
+ @db.execute(
288
+ "INSERT INTO events (id, type, data, timestamp, task_id, context_id) VALUES (?, ?, ?, ?, ?, ?)",
289
+ event.id,
290
+ event.type,
291
+ event.data.to_json,
292
+ event.timestamp,
293
+ event.task_id,
294
+ event.context_id
295
+ )
296
+ end
297
+
298
+ def get_events(task_id: nil, context_id: nil, after: nil)
299
+ conditions = []
300
+ params = []
301
+
302
+ if task_id
303
+ conditions << "task_id = ?"
304
+ params << task_id
305
+ end
306
+
307
+ if context_id
308
+ conditions << "context_id = ?"
309
+ params << context_id
310
+ end
311
+
312
+ if after
313
+ conditions << "timestamp > ?"
314
+ params << after
315
+ end
316
+
317
+ where_clause = conditions.empty? ? "" : "WHERE #{conditions.join(' AND ')}"
318
+
319
+ @db.execute("SELECT * FROM events #{where_clause} ORDER BY timestamp", *params)
320
+ end
321
+ end
322
+
323
+ # Usage with event consumer
324
+ event_store = DatabaseEventStore.new(db_connection)
325
+
326
+ consumer.register_handler("task") do |event|
327
+ event_store.store_event(event)
328
+ end
329
+ ```
330
+
331
+ ## Real-time Notifications
332
+
333
+ ### WebSocket Integration
334
+
335
+ ```ruby
336
+ class WebSocketEventBroadcaster
337
+ def initialize(websocket_server)
338
+ @ws_server = websocket_server
339
+ @client_subscriptions = {}
340
+ end
341
+
342
+ def subscribe_client(client_id, websocket, filters = {})
343
+ @client_subscriptions[client_id] = {
344
+ websocket: websocket,
345
+ filters: filters
346
+ }
347
+ end
348
+
349
+ def broadcast_event(event)
350
+ @client_subscriptions.each do |client_id, subscription|
351
+ next unless event_matches_filters?(event, subscription[:filters])
352
+
353
+ begin
354
+ subscription[:websocket].send(event.to_h.to_json)
355
+ rescue => e
356
+ # Remove disconnected clients
357
+ @client_subscriptions.delete(client_id)
358
+ end
359
+ end
360
+ end
361
+
362
+ private
363
+
364
+ def event_matches_filters?(event, filters)
365
+ return true if filters.empty?
366
+
367
+ filters.all? do |key, value|
368
+ case key
369
+ when :task_id
370
+ event.task_id == value
371
+ when :context_id
372
+ event.context_id == value
373
+ when :event_types
374
+ value.include?(event.type)
375
+ else
376
+ true
377
+ end
378
+ end
379
+ end
380
+ end
381
+
382
+ # Integration
383
+ broadcaster = WebSocketEventBroadcaster.new(ws_server)
384
+
385
+ consumer.register_handler("task_status_update") do |event|
386
+ broadcaster.broadcast_event(event)
387
+ end
388
+ ```
389
+
390
+ ## Testing Events
391
+
392
+ ### Testing Event Publishing
393
+
394
+ ```ruby
395
+ RSpec.describe "Event Publishing" do
396
+ let(:event_queue) { A2A::Server::Events::InMemoryEventQueue.new }
397
+ let(:executor) { MyAgentExecutor.new }
398
+
399
+ it "publishes task events" do
400
+ events = []
401
+ event_queue.subscribe { |event| events << event }
402
+
403
+ context = create_test_context
404
+ executor.execute(context, event_queue)
405
+
406
+ task_events = events.select { |e| e.type == "task" }
407
+ expect(task_events).not_to be_empty
408
+
409
+ status_events = events.select { |e| e.type == "task_status_update" }
410
+ expect(status_events.map(&:data).map(&:status).map(&:state))
411
+ .to include("working", "completed")
412
+ end
413
+ end
414
+ ```
415
+
416
+ ### Testing Event Consumption
417
+
418
+ ```ruby
419
+ RSpec.describe "Event Consumption" do
420
+ let(:event_queue) { A2A::Server::Events::InMemoryEventQueue.new }
421
+ let(:consumer) { A2A::Server::Events::EventConsumer.new(event_queue) }
422
+
423
+ it "processes events with handlers" do
424
+ processed_events = []
425
+
426
+ consumer.register_handler("test") do |event|
427
+ processed_events << event
428
+ end
429
+
430
+ consumer.start
431
+
432
+ # Publish test event
433
+ test_event = A2A::Server::Events::Event.new(
434
+ type: "test",
435
+ data: { message: "test data" }
436
+ )
437
+ event_queue.publish(test_event)
438
+
439
+ # Wait for processing
440
+ sleep 0.1
441
+
442
+ expect(processed_events).to include(test_event)
443
+
444
+ consumer.stop
445
+ end
446
+ end
447
+ ```
448
+
449
+ ## Performance Considerations
450
+
451
+ 1. **Queue Size**: Monitor queue size to prevent memory issues
452
+ 2. **Event Filtering**: Use efficient filters to reduce processing overhead
453
+ 3. **Batch Processing**: Consider batching events for high-throughput scenarios
454
+ 4. **Persistence**: Choose appropriate persistence strategies based on requirements
455
+ 5. **Error Handling**: Implement robust error handling to prevent event loss
456
+
457
+ ## Best Practices
458
+
459
+ 1. **Event Granularity**: Balance between too many small events and too few large events
460
+ 2. **Event Ordering**: Consider event ordering requirements for your use case
461
+ 3. **Error Recovery**: Implement retry mechanisms for failed event processing
462
+ 4. **Monitoring**: Monitor event queue health and processing metrics
463
+ 5. **Testing**: Thoroughly test event flows in your application
464
+ 6. **Documentation**: Document your custom event types and their expected data structures
465
+
466
+ The event system provides a powerful foundation for building responsive, real-time A2A agents while maintaining clean separation of concerns and enabling sophisticated processing workflows.
467
+
468
+ ## Complete Examples
469
+
470
+ For complete working examples of event-driven agents, see the [A2A Ruby Samples Repository](https://github.com/a2aproject/a2a-ruby-samples), which includes:
471
+
472
+ - **Streaming Chat Agent** - Real-time event processing with Server-Sent Events
473
+ - **File Processing Agent** - Background job processing with progress events
474
+ - **Multi-Agent Client** - Event orchestration across multiple agents
475
+ - **Task Management Examples** - Complete task lifecycle event handling