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,248 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "agent"
4
+ require_relative "a2a_methods"
5
+ require_relative "../types"
6
+
7
+ ##
8
+ # Example agent implementation demonstrating A2A protocol methods
9
+ #
10
+ # This class shows how to create an A2A agent that includes the standard
11
+ # protocol methods and implements custom message processing logic.
12
+ #
13
+ module A2A
14
+ module Server
15
+ class ExampleAgent
16
+ include A2A::Server::Agent
17
+ include A2A::Server::A2AMethods
18
+
19
+ # Configure the agent
20
+ a2a_config name: "Example A2A Agent",
21
+ description: "A demonstration agent for the A2A protocol",
22
+ version: "1.0.0",
23
+ default_input_modes: ["text"],
24
+ default_output_modes: ["text"]
25
+
26
+ # Define a simple capability
27
+ a2a_capability "echo" do
28
+ method :echo
29
+ description "Echo back the input message"
30
+ input_schema type: "object",
31
+ properties: { message: { type: "string" } },
32
+ required: ["message"]
33
+ output_schema type: "object",
34
+ properties: { echo: { type: "string" } }
35
+ tags %w[utility test]
36
+ end
37
+
38
+ # Define a custom method
39
+ a2a_method "echo" do |params, _context|
40
+ message = params["message"]
41
+ { echo: "You said: #{message}" }
42
+ end
43
+
44
+ protected
45
+
46
+ ##
47
+ # Process message synchronously
48
+ #
49
+ # @param message [A2A::Types::Message] The message to process
50
+ # @param task [A2A::Types::Task] The associated task
51
+ # @param context [A2A::Server::Context] Request context
52
+ # @return [Object] Processing result
53
+ def process_message_sync(message, task, _context)
54
+ # Extract text from message parts
55
+ text_parts = message.parts.select { |part| part.is_a?(A2A::Types::TextPart) }
56
+ text_content = text_parts.map(&:text).join(" ")
57
+
58
+ # Simple echo response
59
+ response_message = A2A::Types::Message.new(
60
+ message_id: SecureRandom.uuid,
61
+ role: A2A::Types::ROLE_AGENT,
62
+ parts: [
63
+ A2A::Types::TextPart.new(text: "Echo: #{text_content}")
64
+ ],
65
+ context_id: message.context_id,
66
+ task_id: task.id
67
+ )
68
+
69
+ # Add message to task history
70
+ task_manager.add_message(task.id, message)
71
+ task_manager.add_message(task.id, response_message)
72
+
73
+ {
74
+ message: response_message.to_h,
75
+ processed_at: Time.now.utc.iso8601
76
+ }
77
+ end
78
+
79
+ ##
80
+ # Process message asynchronously
81
+ #
82
+ # @param message [A2A::Types::Message] The message to process
83
+ # @param task [A2A::Types::Task] The associated task
84
+ # @param context [A2A::Server::Context] Request context
85
+ # @return [void]
86
+ def process_message_async(message, task, context)
87
+ # Start background processing
88
+ Thread.new do
89
+ # Simulate some processing time
90
+ sleep 1
91
+
92
+ # Process the message
93
+ result = process_message_sync(message, task, context)
94
+
95
+ # Update task with result
96
+ task_manager.update_task_status(
97
+ task.id,
98
+ A2A::Types::TaskStatus.new(
99
+ state: A2A::Types::TASK_STATE_COMPLETED,
100
+ result: result,
101
+ updated_at: Time.now.utc.iso8601
102
+ )
103
+ )
104
+ rescue StandardError => e
105
+ # Handle errors
106
+ task_manager.update_task_status(
107
+ task.id,
108
+ A2A::Types::TaskStatus.new(
109
+ state: A2A::Types::TASK_STATE_FAILED,
110
+ error: { message: e.message, type: e.class.name },
111
+ updated_at: Time.now.utc.iso8601
112
+ )
113
+ )
114
+ end
115
+ end
116
+
117
+ ##
118
+ # Process message stream
119
+ #
120
+ # @param message [A2A::Types::Message] The message to process
121
+ # @param task [A2A::Types::Task] The associated task
122
+ # @param context [A2A::Server::Context] Request context
123
+ # @yield [response] Yields each response in the stream
124
+ # @return [void]
125
+ def process_message_stream(message, task, _context)
126
+ # Extract text from message parts
127
+ text_parts = message.parts.select { |part| part.is_a?(A2A::Types::TextPart) }
128
+ text_content = text_parts.map(&:text).join(" ")
129
+
130
+ # Stream back the message word by word
131
+ words = text_content.split(/\s+/)
132
+
133
+ words.each_with_index do |word, index|
134
+ response_message = A2A::Types::Message.new(
135
+ message_id: SecureRandom.uuid,
136
+ role: A2A::Types::ROLE_AGENT,
137
+ parts: [
138
+ A2A::Types::TextPart.new(text: "Word #{index + 1}: #{word}")
139
+ ],
140
+ context_id: message.context_id,
141
+ task_id: task.id
142
+ )
143
+
144
+ yield response_message.to_h
145
+
146
+ # Small delay between words
147
+ sleep 0.5
148
+ end
149
+
150
+ # Final message
151
+ final_message = A2A::Types::Message.new(
152
+ message_id: SecureRandom.uuid,
153
+ role: A2A::Types::ROLE_AGENT,
154
+ parts: [
155
+ A2A::Types::TextPart.new(text: "Streaming complete. Processed #{words.length} words.")
156
+ ],
157
+ context_id: message.context_id,
158
+ task_id: task.id
159
+ )
160
+
161
+ yield final_message.to_h
162
+ end
163
+
164
+ ##
165
+ # Generate agent card
166
+ #
167
+ # @param context [A2A::Server::Context] Request context
168
+ # @return [A2A::Types::AgentCard] The agent card
169
+ def generate_agent_card(_context)
170
+ A2A::Types::AgentCard.new(
171
+ name: self.class._a2a_config[:name] || "Example Agent",
172
+ description: self.class._a2a_config[:description] || "An example A2A agent",
173
+ version: self.class._a2a_config[:version] || "1.0.0",
174
+ url: "https://example.com/agent",
175
+ preferred_transport: A2A::Types::TRANSPORT_JSONRPC,
176
+ skills: generate_skills_from_capabilities,
177
+ capabilities: generate_capabilities_info,
178
+ default_input_modes: self.class._a2a_config[:default_input_modes] || ["text"],
179
+ default_output_modes: self.class._a2a_config[:default_output_modes] || ["text"],
180
+ additional_interfaces: [
181
+ A2A::Types::AgentInterface.new(
182
+ transport: A2A::Types::TRANSPORT_JSONRPC,
183
+ url: "https://example.com/agent/rpc"
184
+ )
185
+ ],
186
+ supports_authenticated_extended_card: true,
187
+ protocol_version: "1.0"
188
+ )
189
+ end
190
+
191
+ ##
192
+ # Generate extended agent card with authentication context
193
+ #
194
+ # @param context [A2A::Server::Context] Request context
195
+ # @return [A2A::Types::AgentCard] The extended agent card
196
+ def generate_extended_agent_card(context)
197
+ # Get base card
198
+ card = generate_agent_card(context)
199
+
200
+ # Add authenticated user information if available
201
+ if context.user
202
+ # Modify card based on user context
203
+ # This is where you could add user-specific capabilities or information
204
+ card.instance_variable_set(:@metadata, {
205
+ authenticated_user: context.user.to_s,
206
+ authentication_time: Time.now.utc.iso8601,
207
+ extended_features: %w[user_context personalized_responses]
208
+ })
209
+ end
210
+
211
+ card
212
+ end
213
+
214
+ private
215
+
216
+ ##
217
+ # Generate skills from registered capabilities
218
+ #
219
+ # @return [Array<A2A::Types::AgentSkill>] List of skills
220
+ def generate_skills_from_capabilities
221
+ self.class.a2a_capability_registry.all.map do |capability|
222
+ A2A::Types::AgentSkill.new(
223
+ id: capability.name,
224
+ name: capability.name.humanize,
225
+ description: capability.description || "No description available",
226
+ tags: capability.tags || [],
227
+ examples: capability.examples || [],
228
+ input_modes: ["text"],
229
+ output_modes: ["text"]
230
+ )
231
+ end
232
+ end
233
+
234
+ ##
235
+ # Generate capabilities information
236
+ #
237
+ # @return [A2A::Types::AgentCapabilities] Capabilities info
238
+ def generate_capabilities_info
239
+ A2A::Types::AgentCapabilities.new(
240
+ streaming: true,
241
+ push_notifications: true,
242
+ state_transition_history: true,
243
+ extensions: []
244
+ )
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,281 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../protocol/json_rpc"
4
+ require_relative "../errors"
5
+ require_relative "agent"
6
+ require_relative "middleware"
7
+
8
+ ##
9
+ # Request handler for processing A2A JSON-RPC requests
10
+ #
11
+ # This class handles the processing of JSON-RPC requests, including
12
+ # method routing, parameter validation, response generation, and
13
+ # batch request processing.
14
+ #
15
+ # @example Basic usage
16
+ # handler = A2A::Server::Handler.new(agent_instance)
17
+ # response = handler.handle_request(json_request_string)
18
+ #
19
+ module A2A
20
+ module Server
21
+ class Handler
22
+ attr_reader :agent, :middleware_stack
23
+
24
+ ##
25
+ # Initialize a new request handler
26
+ #
27
+ # @param agent [Object] The agent instance that includes A2A::Server::Agent
28
+ # @param middleware [Array] Array of middleware to apply
29
+ def initialize(agent, middleware: [])
30
+ @agent = agent
31
+ @middleware_stack = middleware.dup
32
+
33
+ validate_agent!
34
+ end
35
+
36
+ ##
37
+ # Handle a JSON-RPC request string
38
+ #
39
+ # @param request_body [String] The JSON-RPC request as a string
40
+ # @param context [A2A::Server::Context, nil] Optional request context
41
+ # @return [String] The JSON-RPC response as a string
42
+ def handle_request(request_body, context: nil)
43
+ A2A::Monitoring::Instrumentation.instrument_request({ method: "parse_request" }) do
44
+ # Parse the JSON-RPC request
45
+ parsed_request = A2A::Protocol::JsonRpc.parse_request(request_body)
46
+
47
+ # Handle single or batch requests
48
+ if parsed_request.is_a?(Array)
49
+ handle_batch_request(parsed_request, context: context)
50
+ else
51
+ handle_single_request(parsed_request, context: context)
52
+ end
53
+ rescue A2A::Errors::A2AError => e
54
+ # Return error response for A2A errors
55
+ error_response = A2A::Protocol::JsonRpc.build_error_response(
56
+ code: e.code,
57
+ message: e.message,
58
+ data: e.data,
59
+ id: nil # Unknown ID for parse errors
60
+ )
61
+ error_response.to_json
62
+ rescue StandardError => e
63
+ # Return internal error for unexpected errors
64
+ error_response = A2A::Protocol::JsonRpc.build_error_response(
65
+ code: A2A::Protocol::JsonRpc::INTERNAL_ERROR,
66
+ message: "Internal server error: #{e.message}",
67
+ id: nil
68
+ )
69
+ error_response.to_json
70
+ end
71
+ end
72
+
73
+ ##
74
+ # Handle a single JSON-RPC request
75
+ #
76
+ # @param request [A2A::Protocol::Request] The parsed request
77
+ # @param context [A2A::Server::Context, nil] Optional request context
78
+ # @return [String] The JSON-RPC response as a string
79
+ def handle_single_request(request, context: nil)
80
+ # Apply middleware stack
81
+ response = apply_middleware_stack(request, context) do
82
+ # Route the request to the agent
83
+ route_request(request, context: context)
84
+ end
85
+
86
+ # Convert response to JSON
87
+ if response
88
+ response.to_json
89
+ else
90
+ # No response for notifications
91
+ nil
92
+ end
93
+ end
94
+
95
+ ##
96
+ # Handle a batch JSON-RPC request
97
+ #
98
+ # @param requests [Array<A2A::Protocol::Request>] Array of parsed requests
99
+ # @param context [A2A::Server::Context, nil] Optional request context
100
+ # @return [String] The JSON-RPC batch response as a string
101
+ def handle_batch_request(requests, context: nil)
102
+ # Process each request in the batch
103
+ responses = requests.map do |request|
104
+ # Apply middleware stack for each request
105
+ apply_middleware_stack(request, context) do
106
+ route_request(request, context: context)
107
+ end
108
+ rescue StandardError => e
109
+ # Convert errors to error responses
110
+ A2A::Errors::ErrorUtils.exception_to_json_rpc_error(e, request_id: request.id)
111
+ end
112
+
113
+ # Filter out nil responses (from notifications)
114
+ batch_response = A2A::Protocol::JsonRpc.build_batch_response(responses.compact)
115
+
116
+ # Return empty array if no responses (all notifications)
117
+ batch_response.to_json
118
+ end
119
+
120
+ ##
121
+ # Route a request to the appropriate agent method
122
+ #
123
+ # @param request [A2A::Protocol::Request] The JSON-RPC request
124
+ # @param context [A2A::Server::Context, nil] Optional request context
125
+ # @return [Hash, nil] The JSON-RPC response hash or nil for notifications
126
+ def route_request(request, context: nil)
127
+ # Validate the request
128
+ validate_request(request)
129
+
130
+ # Create or enhance context
131
+ request_context = context || A2A::Server::Context.new(request: request)
132
+ request_context.instance_variable_set(:@request, request) if context
133
+
134
+ # Check if the method exists
135
+ unless @agent.class.a2a_method_registered?(request.method)
136
+ raise A2A::Errors::MethodNotFound, "Method '#{request.method}' not found"
137
+ end
138
+
139
+ # Get method definition for validation
140
+ @agent.class.a2a_method_definition(request.method)
141
+
142
+ # Validate parameters against capability schema if available
143
+ validate_method_parameters(request.method, request.params)
144
+
145
+ # Delegate to the agent
146
+ @agent.handle_a2a_request(request, context: request_context)
147
+ end
148
+
149
+ ##
150
+ # Add middleware to the handler
151
+ #
152
+ # @param middleware [Object] Middleware instance that responds to #call
153
+ def add_middleware(middleware)
154
+ @middleware_stack << middleware
155
+ end
156
+
157
+ ##
158
+ # Remove middleware from the handler
159
+ #
160
+ # @param middleware [Object] Middleware instance to remove
161
+ def remove_middleware(middleware)
162
+ @middleware_stack.delete(middleware)
163
+ end
164
+
165
+ ##
166
+ # Get all registered methods from the agent
167
+ #
168
+ # @return [Array<String>] Array of method names
169
+ def registered_methods
170
+ @agent.class.a2a_method_registry.keys
171
+ end
172
+
173
+ ##
174
+ # Check if a method is registered
175
+ #
176
+ # @param method_name [String] The method name to check
177
+ # @return [Boolean] True if the method is registered
178
+ def method_registered?(method_name)
179
+ @agent.class.a2a_method_registered?(method_name)
180
+ end
181
+
182
+ ##
183
+ # Get method definition
184
+ #
185
+ # @param method_name [String] The method name
186
+ # @return [Hash, nil] The method definition or nil if not found
187
+ def method_definition(method_name)
188
+ @agent.class.a2a_method_definition(method_name)
189
+ end
190
+
191
+ ##
192
+ # Get all capabilities from the agent
193
+ #
194
+ # @return [Array<A2A::Protocol::Capability>] Array of capabilities
195
+ def capabilities
196
+ @agent.class.a2a_capability_registry.all
197
+ end
198
+
199
+ ##
200
+ # Find capability by method name
201
+ #
202
+ # @param method_name [String] The method name
203
+ # @return [A2A::Protocol::Capability, nil] The capability or nil if not found
204
+ def find_capability_by_method(method_name)
205
+ @agent.class.a2a_capability_registry.find_by_method(method_name).first
206
+ end
207
+
208
+ private
209
+
210
+ ##
211
+ # Validate that the agent includes the Agent module
212
+ def validate_agent!
213
+ return if @agent.class.included_modules.include?(A2A::Server::Agent)
214
+
215
+ raise ArgumentError, "Agent must include A2A::Server::Agent module"
216
+ end
217
+
218
+ ##
219
+ # Validate a JSON-RPC request
220
+ #
221
+ # @param request [A2A::Protocol::Request] The request to validate
222
+ # @raise [A2A::Errors::InvalidRequest] If the request is invalid
223
+ def validate_request(request)
224
+ raise A2A::Errors::InvalidRequest, "Invalid request object" unless request.is_a?(A2A::Protocol::Request)
225
+
226
+ if request.method.nil? || (respond_to?(:empty?) && empty?) || (is_a?(String) && strip.empty?)
227
+ raise A2A::Errors::InvalidRequest,
228
+ "Method name is required"
229
+ end
230
+
231
+ return if request.params.is_a?(Hash) || request.params.is_a?(Array)
232
+
233
+ raise A2A::Errors::InvalidParams, "Parameters must be an object or array"
234
+ end
235
+
236
+ ##
237
+ # Validate method parameters against capability schema
238
+ #
239
+ # @param method_name [String] The method name
240
+ # @param params [Hash, Array] The method parameters
241
+ # @raise [A2A::Errors::InvalidParams] If parameters are invalid
242
+ def validate_method_parameters(method_name, params)
243
+ capability = find_capability_by_method(method_name)
244
+ return unless capability # Skip validation if no capability defined
245
+
246
+ begin
247
+ capability.validate_input(params)
248
+ rescue ArgumentError => e
249
+ raise A2A::Errors::InvalidParams, "Parameter validation failed: #{e.message}"
250
+ end
251
+ end
252
+
253
+ ##
254
+ # Apply the middleware stack to a request
255
+ #
256
+ # @param request [A2A::Protocol::Request] The request
257
+ # @param context [A2A::Server::Context, nil] The request context
258
+ # @yield Block to execute after middleware
259
+ # @return [Object] The result from the block
260
+ def apply_middleware_stack(request, context, &block)
261
+ # Build the middleware chain from the outside in
262
+ chain = block
263
+
264
+ @middleware_stack.reverse_each do |middleware|
265
+ current_chain = chain
266
+ chain = lambda do
267
+ if middleware.respond_to?(:call)
268
+ middleware.call(request, context) { current_chain.call }
269
+ else
270
+ # Fallback for middleware that don't implement call
271
+ current_chain.call
272
+ end
273
+ end
274
+ end
275
+
276
+ # Execute the chain
277
+ chain.call
278
+ end
279
+ end
280
+ end
281
+ end