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,327 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+ require "securerandom"
7
+
8
+ ##
9
+ # Manages push notifications for task updates
10
+ #
11
+ # The PushNotificationManager handles webhook delivery, Server-Sent Events,
12
+ # retry logic, and CRUD operations for push notification configurations.
13
+ #
14
+ module A2A
15
+ module Server
16
+ class PushNotificationManager
17
+ attr_reader :storage, :config
18
+
19
+ ##
20
+ # Initialize a new PushNotificationManager
21
+ #
22
+ # @param storage [Object] Storage backend for push notification configs
23
+ # @param config [Hash] Configuration options
24
+ def initialize(storage: nil, config: {})
25
+ @storage = storage || A2A::Server::Storage::Memory.new
26
+ @config = default_config.merge(config)
27
+ @sse_clients = {}
28
+ @retry_queue = []
29
+ @mutex = Mutex.new
30
+ end
31
+
32
+ ##
33
+ # Set a push notification config for a task
34
+ #
35
+ # @param task_id [String] The task ID
36
+ # @param config [A2A::Types::PushNotificationConfig, Hash] The notification config
37
+ # @return [A2A::Types::TaskPushNotificationConfig] The created config
38
+ def set_push_notification_config(task_id, config)
39
+ notification_config = if config.is_a?(A2A::Types::PushNotificationConfig)
40
+ config
41
+ else
42
+ A2A::Types::PushNotificationConfig.from_h(config)
43
+ end
44
+
45
+ # Generate ID if not provided
46
+ notification_config.instance_variable_set(:@id, SecureRandom.uuid) unless notification_config.id
47
+
48
+ task_config = A2A::Types::TaskPushNotificationConfig.new(
49
+ task_id: task_id,
50
+ push_notification_config: notification_config
51
+ )
52
+
53
+ @storage.save_push_notification_config(task_config)
54
+ task_config
55
+ end
56
+
57
+ ##
58
+ # Get push notification config for a task
59
+ #
60
+ # @param task_id [String] The task ID
61
+ # @param config_id [String, nil] Optional specific config ID
62
+ # @return [A2A::Types::TaskPushNotificationConfig, nil] The config or nil if not found
63
+ def get_push_notification_config(task_id, config_id: nil)
64
+ if config_id
65
+ @storage.get_push_notification_config_by_id(task_id, config_id)
66
+ else
67
+ configs = @storage.list_push_notification_configs(task_id)
68
+ configs.first # Return the first config if no specific ID requested
69
+ end
70
+ end
71
+
72
+ ##
73
+ # List all push notification configs for a task
74
+ #
75
+ # @param task_id [String] The task ID
76
+ # @return [Array<A2A::Types::TaskPushNotificationConfig>] List of configs
77
+ def list_push_notification_configs(*args)
78
+ @storage.list_push_notification_configs(*args)
79
+ end
80
+
81
+ ##
82
+ # Delete a push notification config
83
+ #
84
+ # @param task_id [String] The task ID
85
+ # @param config_id [String] The config ID
86
+ # @return [Boolean] True if deleted, false if not found
87
+ def delete_push_notification_config(*args)
88
+ @storage.delete_push_notification_config(*args)
89
+ end
90
+
91
+ ##
92
+ # Send a task status update notification
93
+ #
94
+ # @param event [A2A::Types::TaskStatusUpdateEvent] The status update event
95
+ # @return [void]
96
+ def notify_task_status_update(event)
97
+ configs = list_push_notification_configs(event.task_id)
98
+
99
+ configs.each do |config|
100
+ send_webhook_notification(config, "task_status_update", event)
101
+ end
102
+
103
+ # Send to SSE clients
104
+ send_sse_notification(event.task_id, "task_status_update", event)
105
+ end
106
+
107
+ ##
108
+ # Send a task artifact update notification
109
+ #
110
+ # @param event [A2A::Types::TaskArtifactUpdateEvent] The artifact update event
111
+ # @return [void]
112
+ def notify_task_artifact_update(event)
113
+ configs = list_push_notification_configs(event.task_id)
114
+
115
+ configs.each do |config|
116
+ send_webhook_notification(config, "task_artifact_update", event)
117
+ end
118
+
119
+ # Send to SSE clients
120
+ send_sse_notification(event.task_id, "task_artifact_update", event)
121
+ end
122
+
123
+ ##
124
+ # Register an SSE client for task updates
125
+ #
126
+ # @param task_id [String] The task ID
127
+ # @param client [Object] The SSE client (response object)
128
+ # @return [String] Client ID
129
+ def register_sse_client(task_id, client)
130
+ client_id = SecureRandom.uuid
131
+
132
+ @mutex.synchronize do
133
+ @sse_clients[task_id] ||= {}
134
+ @sse_clients[task_id][client_id] = client
135
+ end
136
+
137
+ client_id
138
+ end
139
+
140
+ ##
141
+ # Unregister an SSE client
142
+ #
143
+ # @param task_id [String] The task ID
144
+ # @param client_id [String] The client ID
145
+ # @return [void]
146
+ def unregister_sse_client(task_id, client_id)
147
+ @mutex.synchronize do
148
+ @sse_clients[task_id]&.delete(client_id)
149
+ @sse_clients.delete(task_id) if @sse_clients[task_id] && @sse_clients[task_id].empty?
150
+ end
151
+ end
152
+
153
+ ##
154
+ # Process retry queue
155
+ #
156
+ # This method should be called periodically to retry failed notifications
157
+ #
158
+ # @return [void]
159
+ def process_retry_queue
160
+ @mutex.synchronize do
161
+ current_time = Time.now
162
+
163
+ @retry_queue.select! do |retry_item|
164
+ if current_time >= retry_item[:next_retry_at]
165
+ # Attempt retry
166
+ success = send_webhook_notification_internal(
167
+ retry_item[:config],
168
+ retry_item[:event_type],
169
+ retry_item[:event_data],
170
+ retry_item[:attempt]
171
+ )
172
+
173
+ if success
174
+ false # Remove from queue
175
+ elsif retry_item[:attempt] < @config[:max_retry_attempts]
176
+ # Schedule next retry if not exceeded max attempts
177
+ retry_item[:attempt] += 1
178
+ retry_item[:next_retry_at] = calculate_next_retry_time(retry_item[:attempt])
179
+ true
180
+ else
181
+ false # Remove from queue (max attempts exceeded)
182
+ end
183
+ else
184
+ true # Keep in queue (not time for retry yet)
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ private
191
+
192
+ ##
193
+ # Send a webhook notification
194
+ #
195
+ # @param config [A2A::Types::TaskPushNotificationConfig] The notification config
196
+ # @param event_type [String] The event type
197
+ # @param event_data [Object] The event data
198
+ # @return [void]
199
+ def send_webhook_notification(config, event_type, event_data)
200
+ success = send_webhook_notification_internal(config, event_type, event_data, 1)
201
+
202
+ return if success
203
+
204
+ # Add to retry queue
205
+ @mutex.synchronize do
206
+ @retry_queue << {
207
+ config: config,
208
+ event_type: event_type,
209
+ event_data: event_data,
210
+ attempt: 1,
211
+ next_retry_at: calculate_next_retry_time(1)
212
+ }
213
+ end
214
+ end
215
+
216
+ ##
217
+ # Internal webhook notification sending
218
+ #
219
+ # @param config [A2A::Types::TaskPushNotificationConfig] The notification config
220
+ # @param event_type [String] The event type
221
+ # @param event_data [Object] The event data
222
+ # @param attempt [Integer] The attempt number
223
+ # @return [Boolean] True if successful
224
+ def send_webhook_notification_internal(config, event_type, event_data, attempt)
225
+ uri = URI.parse(config.webhook_url)
226
+
227
+ http = Net::HTTP.new(uri.host, uri.port)
228
+ http.use_ssl = uri.scheme == "https"
229
+ http.read_timeout = @config[:webhook_timeout]
230
+ http.open_timeout = @config[:webhook_timeout]
231
+
232
+ request = Net::HTTP::Post.new(uri.path)
233
+ request["Content-Type"] = "application/json"
234
+ request["User-Agent"] = "A2A-Ruby/#{A2A::VERSION}"
235
+
236
+ # Add authentication headers
237
+ config.auth_headers.each { |key, value| request[key] = value }
238
+
239
+ # Build payload
240
+ payload = {
241
+ event_type: event_type,
242
+ event_data: event_data.to_h,
243
+ timestamp: Time.now.utc.iso8601,
244
+ attempt: attempt
245
+ }
246
+
247
+ request.body = JSON.generate(payload)
248
+
249
+ response = http.request(request)
250
+
251
+ # Consider 2xx responses as successful
252
+ response.code.to_i.between?(200, 299)
253
+ rescue StandardError => e
254
+ warn "Webhook notification failed: #{e.message}" if @config[:log_errors]
255
+ false
256
+ end
257
+
258
+ ##
259
+ # Send SSE notification to registered clients
260
+ #
261
+ # @param task_id [String] The task ID
262
+ # @param event_type [String] The event type
263
+ # @param event_data [Object] The event data
264
+ # @return [void]
265
+ def send_sse_notification(task_id, event_type, event_data)
266
+ @mutex.synchronize do
267
+ clients = @sse_clients[task_id]
268
+ return unless clients
269
+
270
+ # Build SSE message
271
+ sse_data = {
272
+ event_type: event_type,
273
+ event_data: event_data.to_h,
274
+ timestamp: Time.now.utc.iso8601
275
+ }
276
+
277
+ sse_message = "event: #{event_type}\n"
278
+ sse_message += "data: #{JSON.generate(sse_data)}\n\n"
279
+
280
+ # Send to all clients for this task
281
+ clients.each do |client_id, client|
282
+ client.write(sse_message)
283
+ client.flush if client.respond_to?(:flush)
284
+ rescue StandardError => e
285
+ # Remove disconnected client
286
+ clients.delete(client_id)
287
+ warn "SSE client disconnected: #{e.message}" if @config[:log_errors]
288
+ end
289
+
290
+ # Clean up empty task entries
291
+ @sse_clients.delete(task_id) if clients.empty?
292
+ end
293
+ end
294
+
295
+ ##
296
+ # Calculate next retry time using exponential backoff
297
+ #
298
+ # @param attempt [Integer] The attempt number
299
+ # @return [Time] The next retry time
300
+ def calculate_next_retry_time(attempt)
301
+ base_delay = @config[:retry_base_delay]
302
+ max_delay = @config[:retry_max_delay]
303
+
304
+ delay = [base_delay * (2**(attempt - 1)), max_delay].min
305
+
306
+ # Add jitter to prevent thundering herd
307
+ jitter = rand(0.1 * delay)
308
+
309
+ Time.now + delay + jitter
310
+ end
311
+
312
+ ##
313
+ # Default configuration
314
+ #
315
+ # @return [Hash] Default configuration
316
+ def default_config
317
+ {
318
+ webhook_timeout: 30,
319
+ max_retry_attempts: 3,
320
+ retry_base_delay: 1.0, # seconds
321
+ retry_max_delay: 60.0, # seconds
322
+ log_errors: true
323
+ }
324
+ end
325
+ end
326
+ end
327
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../types"
4
+ require_relative "../errors"
5
+
6
+ module A2A
7
+ module Server
8
+ ##
9
+ # Abstract base class for A2A request handlers
10
+ #
11
+ # This interface defines the methods that an A2A server implementation must
12
+ # provide to handle incoming JSON-RPC requests. It mirrors the Python
13
+ # RequestHandler interface for consistency.
14
+ #
15
+ class RequestHandler
16
+ ##
17
+ # Handle the 'tasks/get' method
18
+ #
19
+ # Retrieves the state and history of a specific task.
20
+ #
21
+ # @param params [Hash] Parameters specifying the task ID and optionally history length
22
+ # @param context [A2A::Server::Context, nil] Context provided by the server
23
+ # @return [A2A::Types::Task, nil] The Task object if found, otherwise nil
24
+ # @abstract Subclasses must implement this method
25
+ def on_get_task(params, context = nil)
26
+ raise NotImplementedError, "Subclasses must implement on_get_task"
27
+ end
28
+
29
+ ##
30
+ # Handle the 'tasks/cancel' method
31
+ #
32
+ # Requests the agent to cancel an ongoing task.
33
+ #
34
+ # @param params [Hash] Parameters specifying the task ID
35
+ # @param context [A2A::Server::Context, nil] Context provided by the server
36
+ # @return [A2A::Types::Task, nil] The Task object with its status updated to canceled, or nil if not found
37
+ # @abstract Subclasses must implement this method
38
+ def on_cancel_task(params, context = nil)
39
+ raise NotImplementedError, "Subclasses must implement on_cancel_task"
40
+ end
41
+
42
+ ##
43
+ # Handle the 'message/send' method (non-streaming)
44
+ #
45
+ # Sends a message to the agent to create, continue, or restart a task,
46
+ # and waits for the final result (Task or Message).
47
+ #
48
+ # @param params [Hash] Parameters including the message and configuration
49
+ # @param context [A2A::Server::Context, nil] Context provided by the server
50
+ # @return [A2A::Types::Task, A2A::Types::Message] The final Task object or a final Message object
51
+ # @abstract Subclasses must implement this method
52
+ def on_message_send(params, context = nil)
53
+ raise NotImplementedError, "Subclasses must implement on_message_send"
54
+ end
55
+
56
+ ##
57
+ # Handle the 'message/stream' method (streaming)
58
+ #
59
+ # Sends a message to the agent and yields stream events as they are
60
+ # produced (Task updates, Message chunks, Artifact updates).
61
+ #
62
+ # @param params [Hash] Parameters including the message and configuration
63
+ # @param context [A2A::Server::Context, nil] Context provided by the server
64
+ # @return [Enumerator] Enumerator yielding Event objects from the agent's execution
65
+ # @abstract Subclasses must implement this method
66
+ def on_message_send_stream(_params, _context = nil)
67
+ raise A2A::Errors::UnsupportedOperation, "Streaming not supported by this handler"
68
+ end
69
+
70
+ ##
71
+ # Handle the 'tasks/pushNotificationConfig/set' method
72
+ #
73
+ # Sets or updates the push notification configuration for a task.
74
+ #
75
+ # @param params [Hash] Parameters including the task ID and push notification configuration
76
+ # @param context [A2A::Server::Context, nil] Context provided by the server
77
+ # @return [A2A::Types::TaskPushNotificationConfig] The provided TaskPushNotificationConfig upon success
78
+ # @abstract Subclasses must implement this method
79
+ def on_set_task_push_notification_config(params, context = nil)
80
+ raise NotImplementedError, "Subclasses must implement on_set_task_push_notification_config"
81
+ end
82
+
83
+ ##
84
+ # Handle the 'tasks/pushNotificationConfig/get' method
85
+ #
86
+ # Retrieves the current push notification configuration for a task.
87
+ #
88
+ # @param params [Hash] Parameters including the task ID
89
+ # @param context [A2A::Server::Context, nil] Context provided by the server
90
+ # @return [A2A::Types::TaskPushNotificationConfig] The TaskPushNotificationConfig for the task
91
+ # @abstract Subclasses must implement this method
92
+ def on_get_task_push_notification_config(params, context = nil)
93
+ raise NotImplementedError, "Subclasses must implement on_get_task_push_notification_config"
94
+ end
95
+
96
+ ##
97
+ # Handle the 'tasks/resubscribe' method
98
+ #
99
+ # Allows a client to re-subscribe to a running streaming task's event stream.
100
+ #
101
+ # @param params [Hash] Parameters including the task ID
102
+ # @param context [A2A::Server::Context, nil] Context provided by the server
103
+ # @return [Enumerator] Enumerator yielding Event objects from the agent's ongoing execution
104
+ # @abstract Subclasses must implement this method
105
+ def on_resubscribe_to_task(_params, _context = nil)
106
+ raise A2A::Errors::UnsupportedOperation, "Task resubscription not supported by this handler"
107
+ end
108
+
109
+ ##
110
+ # Handle the 'tasks/pushNotificationConfig/list' method
111
+ #
112
+ # Retrieves the current push notification configurations for a task.
113
+ #
114
+ # @param params [Hash] Parameters including the task ID
115
+ # @param context [A2A::Server::Context, nil] Context provided by the server
116
+ # @return [Array<A2A::Types::TaskPushNotificationConfig>] The list of TaskPushNotificationConfig for the task
117
+ # @abstract Subclasses must implement this method
118
+ def on_list_task_push_notification_config(params, context = nil)
119
+ raise NotImplementedError, "Subclasses must implement on_list_task_push_notification_config"
120
+ end
121
+
122
+ ##
123
+ # Handle the 'tasks/pushNotificationConfig/delete' method
124
+ #
125
+ # Deletes a push notification configuration associated with a task.
126
+ #
127
+ # @param params [Hash] Parameters including the task ID and config ID
128
+ # @param context [A2A::Server::Context, nil] Context provided by the server
129
+ # @return [void]
130
+ # @abstract Subclasses must implement this method
131
+ def on_delete_task_push_notification_config(params, context = nil)
132
+ raise NotImplementedError, "Subclasses must implement on_delete_task_push_notification_config"
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ module Server
5
+ module Storage
6
+ end
7
+ end
8
+ end
9
+
10
+ ##
11
+ # Abstract base class for task storage backends
12
+ #
13
+ # This class defines the interface that all storage backends must implement
14
+ # for task persistence and retrieval.
15
+ #
16
+ module A2A
17
+ module Server
18
+ module Storage
19
+ class Base
20
+ ##
21
+ # Save a task to storage
22
+ #
23
+ # @param task [A2A::Types::Task] The task to save
24
+ # @return [void]
25
+ # @raise [NotImplementedError] Must be implemented by subclasses
26
+ def save_task(task)
27
+ raise NotImplementedError, "#{self.class} must implement #save_task"
28
+ end
29
+
30
+ ##
31
+ # Get a task by ID
32
+ #
33
+ # @param task_id [String] The task ID
34
+ # @return [A2A::Types::Task, nil] The task or nil if not found
35
+ # @raise [NotImplementedError] Must be implemented by subclasses
36
+ def get_task(task_id)
37
+ raise NotImplementedError, "#{self.class} must implement #get_task"
38
+ end
39
+
40
+ ##
41
+ # Delete a task by ID
42
+ #
43
+ # @param task_id [String] The task ID
44
+ # @return [Boolean] True if task was deleted, false if not found
45
+ # @raise [NotImplementedError] Must be implemented by subclasses
46
+ def delete_task(task_id)
47
+ raise NotImplementedError, "#{self.class} must implement #delete_task"
48
+ end
49
+
50
+ ##
51
+ # List all tasks for a given context ID
52
+ #
53
+ # @param context_id [String] The context ID
54
+ # @return [Array<A2A::Types::Task>] Tasks in the context
55
+ # @raise [NotImplementedError] Must be implemented by subclasses
56
+ def list_tasks_by_context(context_id)
57
+ raise NotImplementedError, "#{self.class} must implement #list_tasks_by_context"
58
+ end
59
+
60
+ ##
61
+ # List all tasks
62
+ #
63
+ # @return [Array<A2A::Types::Task>] All tasks
64
+ # @raise [NotImplementedError] Must be implemented by subclasses
65
+ def list_all_tasks
66
+ raise NotImplementedError, "#{self.class} must implement #list_all_tasks"
67
+ end
68
+
69
+ ##
70
+ # List tasks with optional filtering
71
+ #
72
+ # @param filters [Hash] Optional filters (state, context_id, etc.)
73
+ # @return [Array<A2A::Types::Task>] Filtered tasks
74
+ # @raise [NotImplementedError] Must be implemented by subclasses
75
+ def list_tasks(**filters)
76
+ raise NotImplementedError, "#{self.class} must implement #list_tasks"
77
+ end
78
+
79
+ ##
80
+ # Check if a task exists
81
+ #
82
+ # @param task_id [String] The task ID
83
+ # @return [Boolean] True if task exists
84
+ def task_exists?(task_id)
85
+ !get_task(task_id).nil?
86
+ end
87
+
88
+ ##
89
+ # Clear all tasks (useful for testing)
90
+ #
91
+ # @return [void]
92
+ # @raise [NotImplementedError] Must be implemented by subclasses
93
+ def clear_all_tasks
94
+ raise NotImplementedError, "#{self.class} must implement #clear_all_tasks"
95
+ end
96
+
97
+ ##
98
+ # Save a push notification config
99
+ #
100
+ # @param config [A2A::Types::TaskPushNotificationConfig] The config to save
101
+ # @return [void]
102
+ # @raise [NotImplementedError] Must be implemented by subclasses
103
+ def save_push_notification_config(config)
104
+ raise NotImplementedError, "#{self.class} must implement #save_push_notification_config"
105
+ end
106
+
107
+ ##
108
+ # Get a push notification config by task and config ID
109
+ #
110
+ # @param task_id [String] The task ID
111
+ # @param config_id [String] The config ID
112
+ # @return [A2A::Types::TaskPushNotificationConfig, nil] The config or nil if not found
113
+ # @raise [NotImplementedError] Must be implemented by subclasses
114
+ def get_push_notification_config_by_id(task_id, config_id)
115
+ raise NotImplementedError, "#{self.class} must implement #get_push_notification_config_by_id"
116
+ end
117
+
118
+ ##
119
+ # List all push notification configs for a task
120
+ #
121
+ # @param task_id [String] The task ID
122
+ # @return [Array<A2A::Types::TaskPushNotificationConfig>] List of configs
123
+ # @raise [NotImplementedError] Must be implemented by subclasses
124
+ def list_push_notification_configs(task_id)
125
+ raise NotImplementedError, "#{self.class} must implement #list_push_notification_configs"
126
+ end
127
+
128
+ ##
129
+ # Delete a push notification config
130
+ #
131
+ # @param task_id [String] The task ID
132
+ # @param config_id [String] The config ID
133
+ # @return [Boolean] True if deleted, false if not found
134
+ # @raise [NotImplementedError] Must be implemented by subclasses
135
+ def delete_push_notification_config(task_id, config_id)
136
+ raise NotImplementedError, "#{self.class} must implement #delete_push_notification_config"
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end