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,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
|