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,520 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../types"
4
+ require_relative "../errors"
5
+ require_relative "task_manager"
6
+ require_relative "push_notification_manager"
7
+
8
+ ##
9
+ # Standard A2A protocol method implementations
10
+ #
11
+ # This module provides implementations for all core A2A JSON-RPC methods
12
+ # including message handling, task management, and push notifications.
13
+ #
14
+ module A2A
15
+ module Server
16
+ module A2AMethods
17
+ def self.included(base)
18
+ base.extend(ClassMethods)
19
+
20
+ # Register all standard A2A methods when included
21
+ base.register_a2a_methods
22
+ end
23
+
24
+ module ClassMethods
25
+ ##
26
+ # Register all standard A2A protocol methods
27
+ def register_a2a_methods
28
+ # Core messaging methods
29
+ a2a_method "message/send" do |params, context|
30
+ handle_message_send(params, context)
31
+ end
32
+
33
+ a2a_method "message/stream", streaming: true do |params, context|
34
+ handle_message_stream(params, context)
35
+ end
36
+
37
+ # Task management methods
38
+ a2a_method "tasks/get" do |params, context|
39
+ handle_tasks_get(params, context)
40
+ end
41
+
42
+ a2a_method "tasks/cancel" do |params, context|
43
+ handle_tasks_cancel(params, context)
44
+ end
45
+
46
+ a2a_method "tasks/resubscribe", streaming: true do |params, context|
47
+ handle_tasks_resubscribe(params, context)
48
+ end
49
+
50
+ # Push notification methods
51
+ a2a_method "tasks/pushNotificationConfig/set" do |params, context|
52
+ handle_push_notification_config_set(params, context)
53
+ end
54
+
55
+ a2a_method "tasks/pushNotificationConfig/get" do |params, context|
56
+ handle_push_notification_config_get(params, context)
57
+ end
58
+
59
+ a2a_method "tasks/pushNotificationConfig/list" do |params, context|
60
+ handle_push_notification_config_list(params, context)
61
+ end
62
+
63
+ a2a_method "tasks/pushNotificationConfig/delete" do |params, context|
64
+ handle_push_notification_config_delete(params, context)
65
+ end
66
+
67
+ # Agent card methods
68
+ a2a_method "agent/getCard" do |params, context|
69
+ handle_agent_get_card(params, context)
70
+ end
71
+
72
+ a2a_method "agent/getAuthenticatedExtendedCard" do |params, context|
73
+ handle_agent_get_authenticated_extended_card(params, context)
74
+ end
75
+ end
76
+ end
77
+
78
+ ##
79
+ # Handle message/send method
80
+ #
81
+ # @param params [Hash] Method parameters
82
+ # @param context [A2A::Server::Context] Request context
83
+ # @return [Hash] Response with task information
84
+ def handle_message_send(params, context)
85
+ validate_required_params(params, %w[message])
86
+
87
+ message_data = params["message"]
88
+ blocking = params.fetch("blocking", true)
89
+
90
+ # Parse message
91
+ message = A2A::Types::Message.from_h(message_data)
92
+
93
+ # Create task for message processing
94
+ task = task_manager.create_task(
95
+ type: "message_processing",
96
+ params: { message: message.to_h, blocking: blocking },
97
+ context_id: message.context_id,
98
+ metadata: {
99
+ message_id: message.message_id,
100
+ role: message.role
101
+ }
102
+ )
103
+
104
+ # Process message (delegate to subclass implementation)
105
+ if blocking
106
+ # Update task to working state first
107
+ task_manager.update_task_status(
108
+ task.id,
109
+ A2A::Types::TaskStatus.new(
110
+ state: A2A::Types::TASK_STATE_WORKING,
111
+ message: "Processing message",
112
+ updated_at: Time.now.utc.iso8601
113
+ )
114
+ )
115
+
116
+ # Synchronous processing
117
+ result = process_message_sync(message, task, context)
118
+
119
+ # Update task with result
120
+ task_manager.update_task_status(
121
+ task.id,
122
+ A2A::Types::TaskStatus.new(
123
+ state: A2A::Types::TASK_STATE_COMPLETED,
124
+ result: result,
125
+ updated_at: Time.now.utc.iso8601
126
+ )
127
+ )
128
+
129
+ {
130
+ task_id: task.id,
131
+ context_id: task.context_id,
132
+ result: result
133
+ }
134
+ else
135
+ # Asynchronous processing
136
+ process_message_async(message, task, context)
137
+
138
+ {
139
+ task_id: task.id,
140
+ context_id: task.context_id,
141
+ status: task.status.to_h
142
+ }
143
+ end
144
+ end
145
+
146
+ ##
147
+ # Handle message/stream method
148
+ #
149
+ # @param params [Hash] Method parameters
150
+ # @param context [A2A::Server::Context] Request context
151
+ # @return [Enumerator] Stream of responses
152
+ def handle_message_stream(params, context)
153
+ validate_required_params(params, %w[message])
154
+
155
+ message_data = params["message"]
156
+ message = A2A::Types::Message.from_h(message_data)
157
+
158
+ # Create task for streaming message processing
159
+ task = task_manager.create_task(
160
+ type: "message_streaming",
161
+ params: { message: message.to_h },
162
+ context_id: message.context_id,
163
+ metadata: {
164
+ message_id: message.message_id,
165
+ role: message.role,
166
+ streaming: true
167
+ }
168
+ )
169
+
170
+ # Return enumerator for streaming responses
171
+ Enumerator.new do |yielder|
172
+ # Update task to working state
173
+ task_manager.update_task_status(
174
+ task.id,
175
+ A2A::Types::TaskStatus.new(
176
+ state: A2A::Types::TASK_STATE_WORKING,
177
+ message: "Processing message stream",
178
+ updated_at: Time.now.utc.iso8601
179
+ )
180
+ )
181
+
182
+ # Process message stream (delegate to subclass implementation)
183
+ process_message_stream(message, task, context) do |response|
184
+ yielder << {
185
+ task_id: task.id,
186
+ context_id: task.context_id,
187
+ response: response
188
+ }
189
+ end
190
+
191
+ # Mark task as completed
192
+ task_manager.update_task_status(
193
+ task.id,
194
+ A2A::Types::TaskStatus.new(
195
+ state: A2A::Types::TASK_STATE_COMPLETED,
196
+ message: "Stream completed",
197
+ updated_at: Time.now.utc.iso8601
198
+ )
199
+ )
200
+ rescue StandardError => e
201
+ # Mark task as failed
202
+ task_manager.update_task_status(
203
+ task.id,
204
+ A2A::Types::TaskStatus.new(
205
+ state: A2A::Types::TASK_STATE_FAILED,
206
+ error: { message: e.message, type: e.class.name },
207
+ updated_at: Time.now.utc.iso8601
208
+ )
209
+ )
210
+ raise
211
+ end
212
+ end
213
+
214
+ ##
215
+ # Handle tasks/get method
216
+ #
217
+ # @param params [Hash] Method parameters
218
+ # @param context [A2A::Server::Context] Request context
219
+ # @return [Hash] Task information
220
+ def handle_tasks_get(params, _context)
221
+ validate_required_params(params, %w[id])
222
+
223
+ task_id = params["id"]
224
+ history_length = params["historyLength"]
225
+
226
+ task = task_manager.get_task(task_id, history_length: history_length)
227
+
228
+ {
229
+ task: task.to_h
230
+ }
231
+ end
232
+
233
+ ##
234
+ # Handle tasks/cancel method
235
+ #
236
+ # @param params [Hash] Method parameters
237
+ # @param context [A2A::Server::Context] Request context
238
+ # @return [Hash] Cancellation result
239
+ def handle_tasks_cancel(params, _context)
240
+ validate_required_params(params, %w[id])
241
+
242
+ task_id = params["id"]
243
+ reason = params["reason"]
244
+
245
+ task = task_manager.cancel_task(task_id, reason: reason)
246
+
247
+ {
248
+ task_id: task.id,
249
+ status: task.status.to_h
250
+ }
251
+ end
252
+
253
+ ##
254
+ # Handle tasks/resubscribe method
255
+ #
256
+ # @param params [Hash] Method parameters
257
+ # @param context [A2A::Server::Context] Request context
258
+ # @return [Enumerator] Stream of task updates
259
+ def handle_tasks_resubscribe(params, _context)
260
+ validate_required_params(params, %w[id])
261
+
262
+ task_id = params["id"]
263
+
264
+ # Verify task exists
265
+ task = task_manager.get_task(task_id)
266
+
267
+ # Return enumerator for task update stream
268
+ Enumerator.new do |yielder|
269
+ # Register for task updates
270
+ client_id = push_notification_manager.register_sse_client(task_id, yielder)
271
+
272
+ begin
273
+ # Send current task state immediately
274
+ yielder << {
275
+ event_type: "task_status_update",
276
+ event_data: {
277
+ task_id: task.id,
278
+ context_id: task.context_id,
279
+ status: task.status.to_h
280
+ }
281
+ }
282
+
283
+ # Keep connection alive until client disconnects
284
+ # The actual updates will be sent via the push notification manager
285
+ loop do
286
+ sleep 1
287
+ # Check if client is still connected (implementation specific)
288
+ break unless client_connected?(yielder)
289
+ end
290
+ ensure
291
+ # Unregister client when done
292
+ push_notification_manager.unregister_sse_client(task_id, client_id)
293
+ end
294
+ end
295
+ end
296
+
297
+ ##
298
+ # Handle tasks/pushNotificationConfig/set method
299
+ #
300
+ # @param params [Hash] Method parameters
301
+ # @param context [A2A::Server::Context] Request context
302
+ # @return [Hash] Configuration result
303
+ def handle_push_notification_config_set(params, _context)
304
+ validate_required_params(params, %w[taskId config])
305
+
306
+ task_id = params["taskId"]
307
+ config_data = params["config"]
308
+
309
+ # Verify task exists
310
+ task_manager.get_task(task_id)
311
+
312
+ # Create push notification config
313
+ config = push_notification_manager.set_push_notification_config(task_id, config_data)
314
+
315
+ {
316
+ task_id: task_id,
317
+ config: config.push_notification_config.to_h
318
+ }
319
+ end
320
+
321
+ ##
322
+ # Handle tasks/pushNotificationConfig/get method
323
+ #
324
+ # @param params [Hash] Method parameters
325
+ # @param context [A2A::Server::Context] Request context
326
+ # @return [Hash] Configuration information
327
+ def handle_push_notification_config_get(params, _context)
328
+ validate_required_params(params, %w[taskId])
329
+
330
+ task_id = params["taskId"]
331
+ config_id = params["configId"]
332
+
333
+ config = push_notification_manager.get_push_notification_config(
334
+ task_id,
335
+ config_id: config_id
336
+ )
337
+
338
+ raise A2A::Errors::NotFound, "Push notification config not found" unless config
339
+
340
+ {
341
+ task_id: task_id,
342
+ config: config.push_notification_config.to_h
343
+ }
344
+ end
345
+
346
+ ##
347
+ # Handle tasks/pushNotificationConfig/list method
348
+ #
349
+ # @param params [Hash] Method parameters
350
+ # @param context [A2A::Server::Context] Request context
351
+ # @return [Hash] List of configurations
352
+ def handle_push_notification_config_list(params, _context)
353
+ validate_required_params(params, %w[taskId])
354
+
355
+ task_id = params["taskId"]
356
+
357
+ configs = push_notification_manager.list_push_notification_configs(task_id)
358
+
359
+ {
360
+ task_id: task_id,
361
+ configs: configs.map { |config| config.push_notification_config.to_h }
362
+ }
363
+ end
364
+
365
+ ##
366
+ # Handle tasks/pushNotificationConfig/delete method
367
+ #
368
+ # @param params [Hash] Method parameters
369
+ # @param context [A2A::Server::Context] Request context
370
+ # @return [Hash] Deletion result
371
+ def handle_push_notification_config_delete(params, _context)
372
+ validate_required_params(params, %w[taskId configId])
373
+
374
+ task_id = params["taskId"]
375
+ config_id = params["configId"]
376
+
377
+ deleted = push_notification_manager.delete_push_notification_config(task_id, config_id)
378
+
379
+ raise A2A::Errors::NotFound, "Push notification config not found" unless deleted
380
+
381
+ {
382
+ task_id: task_id,
383
+ config_id: config_id,
384
+ deleted: true
385
+ }
386
+ end
387
+
388
+ ##
389
+ # Handle agent/getCard method
390
+ #
391
+ # @param params [Hash] Method parameters
392
+ # @param context [A2A::Server::Context] Request context
393
+ # @return [Hash] Agent card
394
+ def handle_agent_get_card(_params, context)
395
+ # Generate agent card from registered capabilities
396
+ card = generate_agent_card(context)
397
+
398
+ {
399
+ agent_card: card.to_h
400
+ }
401
+ end
402
+
403
+ ##
404
+ # Handle agent/getAuthenticatedExtendedCard method
405
+ #
406
+ # @param params [Hash] Method parameters
407
+ # @param context [A2A::Server::Context] Request context
408
+ # @return [Hash] Extended agent card
409
+ def handle_agent_get_authenticated_extended_card(_params, context)
410
+ # Verify authentication
411
+ raise A2A::Errors::AuthenticationRequired, "Authentication required for extended agent card" unless context.authenticated?
412
+
413
+ # Generate extended agent card with authentication context
414
+ card = generate_extended_agent_card(context)
415
+
416
+ {
417
+ agent_card: card.to_h
418
+ }
419
+ end
420
+
421
+ ##
422
+ # Get task manager instance
423
+ #
424
+ # @return [A2A::Server::TaskManager] Task manager
425
+ def task_manager
426
+ @task_manager ||= A2A::Server::TaskManager.new
427
+ end
428
+
429
+ ##
430
+ # Get push notification manager instance
431
+ #
432
+ # @return [A2A::Server::PushNotificationManager] Push notification manager
433
+ def push_notification_manager
434
+ @push_notification_manager ||= A2A::Server::PushNotificationManager.new
435
+ end
436
+
437
+ protected
438
+
439
+ ##
440
+ # Process message synchronously (to be implemented by subclasses)
441
+ #
442
+ # @param message [A2A::Types::Message] The message to process
443
+ # @param task [A2A::Types::Task] The associated task
444
+ # @param context [A2A::Server::Context] Request context
445
+ # @return [Object] Processing result
446
+ def process_message_sync(message, task, context)
447
+ raise NotImplementedError, "Subclasses must implement process_message_sync"
448
+ end
449
+
450
+ ##
451
+ # Process message asynchronously (to be implemented by subclasses)
452
+ #
453
+ # @param message [A2A::Types::Message] The message to process
454
+ # @param task [A2A::Types::Task] The associated task
455
+ # @param context [A2A::Server::Context] Request context
456
+ # @return [void]
457
+ def process_message_async(message, task, context)
458
+ raise NotImplementedError, "Subclasses must implement process_message_async"
459
+ end
460
+
461
+ ##
462
+ # Process message stream (to be implemented by subclasses)
463
+ #
464
+ # @param message [A2A::Types::Message] The message to process
465
+ # @param task [A2A::Types::Task] The associated task
466
+ # @param context [A2A::Server::Context] Request context
467
+ # @yield [response] Yields each response in the stream
468
+ # @return [void]
469
+ def process_message_stream(message, task, context)
470
+ raise NotImplementedError, "Subclasses must implement process_message_stream"
471
+ end
472
+
473
+ ##
474
+ # Generate agent card (to be implemented by subclasses)
475
+ #
476
+ # @param context [A2A::Server::Context] Request context
477
+ # @return [A2A::Types::AgentCard] The agent card
478
+ def generate_agent_card(context)
479
+ raise NotImplementedError, "Subclasses must implement generate_agent_card"
480
+ end
481
+
482
+ ##
483
+ # Generate extended agent card (to be implemented by subclasses)
484
+ #
485
+ # @param context [A2A::Server::Context] Request context
486
+ # @return [A2A::Types::AgentCard] The extended agent card
487
+ def generate_extended_agent_card(context)
488
+ # Default implementation returns the same as regular card
489
+ generate_agent_card(context)
490
+ end
491
+
492
+ private
493
+
494
+ ##
495
+ # Validate required parameters
496
+ #
497
+ # @param params [Hash] Parameters to validate
498
+ # @param required [Array<String>] Required parameter names
499
+ # @raise [A2A::Errors::InvalidParams] If required parameters are missing
500
+ def validate_required_params(params, required)
501
+ missing = required.reject { |param| params.key?(param) }
502
+
503
+ return if missing.empty?
504
+
505
+ raise A2A::Errors::InvalidParams, "Missing required parameters: #{missing.join(', ')}"
506
+ end
507
+
508
+ ##
509
+ # Check if client is still connected (implementation specific)
510
+ #
511
+ # @param yielder [Object] The yielder object
512
+ # @return [Boolean] True if client is connected
513
+ def client_connected?(_yielder)
514
+ # This is a placeholder - actual implementation depends on the server framework
515
+ # For now, assume client is always connected
516
+ true
517
+ end
518
+ end
519
+ end
520
+ end