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,311 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "rack"
5
+ require_relative "../request_handler"
6
+ require_relative "../../protocol/json_rpc"
7
+
8
+ module A2A
9
+ module Server
10
+ module Apps
11
+ ##
12
+ # Rack application for serving A2A protocol endpoints
13
+ #
14
+ # This class provides a Rack-compatible application that can handle
15
+ # A2A JSON-RPC requests and serve agent cards. It's similar to the
16
+ # Python FastAPI implementation but uses Rack for Ruby web servers.
17
+ #
18
+ class RackApp
19
+ AGENT_CARD_PATH = "/.well-known/a2a/agent-card"
20
+ EXTENDED_AGENT_CARD_PATH = "/a2a/agent-card/extended"
21
+ RPC_PATH = "/a2a/rpc"
22
+
23
+ attr_reader :agent_card, :request_handler, :extended_agent_card
24
+
25
+ ##
26
+ # Initialize the Rack application
27
+ #
28
+ # @param agent_card [A2A::Types::AgentCard] The agent card describing capabilities
29
+ # @param request_handler [RequestHandler] The request handler for processing A2A requests
30
+ # @param extended_agent_card [A2A::Types::AgentCard, nil] Optional extended agent card
31
+ # @param card_modifier [Proc, nil] Optional callback to modify the public agent card
32
+ # @param extended_card_modifier [Proc, nil] Optional callback to modify the extended agent card
33
+ def initialize(agent_card:, request_handler:, extended_agent_card: nil, card_modifier: nil, extended_card_modifier: nil)
34
+ @agent_card = agent_card
35
+ @request_handler = request_handler
36
+ @extended_agent_card = extended_agent_card
37
+ @card_modifier = card_modifier
38
+ @extended_card_modifier = extended_card_modifier
39
+ end
40
+
41
+ ##
42
+ # Rack application call method
43
+ #
44
+ # @param env [Hash] Rack environment
45
+ # @return [Array] Rack response [status, headers, body]
46
+ def call(env)
47
+ request = Rack::Request.new(env)
48
+
49
+ case request.path_info
50
+ when AGENT_CARD_PATH
51
+ handle_agent_card(request)
52
+ when EXTENDED_AGENT_CARD_PATH
53
+ handle_extended_agent_card(request)
54
+ when RPC_PATH
55
+ handle_rpc_request(request)
56
+ else
57
+ not_found_response
58
+ end
59
+ rescue StandardError => e
60
+ error_response(500, "Internal Server Error: #{e.message}")
61
+ end
62
+
63
+ private
64
+
65
+ ##
66
+ # Handle agent card requests
67
+ #
68
+ # @param request [Rack::Request] The request object
69
+ # @return [Array] Rack response
70
+ def handle_agent_card(request)
71
+ return method_not_allowed_response unless request.get?
72
+
73
+ card_to_serve = @agent_card
74
+ card_to_serve = @card_modifier.call(card_to_serve) if @card_modifier
75
+
76
+ json_response(200, card_to_serve.to_h)
77
+ end
78
+
79
+ ##
80
+ # Handle extended agent card requests
81
+ #
82
+ # @param request [Rack::Request] The request object
83
+ # @return [Array] Rack response
84
+ def handle_extended_agent_card(request)
85
+ return method_not_allowed_response unless request.get?
86
+
87
+ return error_response(404, "Extended agent card not supported") unless @agent_card.supports_authenticated_extended_card
88
+
89
+ # Build server context from request
90
+ context = build_server_context(request)
91
+
92
+ card_to_serve = @extended_agent_card || @agent_card
93
+
94
+ card_to_serve = @extended_card_modifier.call(card_to_serve, context) if @extended_card_modifier
95
+
96
+ json_response(200, card_to_serve.to_h)
97
+ end
98
+
99
+ ##
100
+ # Handle JSON-RPC requests
101
+ #
102
+ # @param request [Rack::Request] The request object
103
+ # @return [Array] Rack response
104
+ def handle_rpc_request(request)
105
+ return method_not_allowed_response unless request.post?
106
+
107
+ # Check content type
108
+ content_type = request.content_type
109
+ return error_response(400, "Content-Type must be application/json") unless content_type&.include?("application/json")
110
+
111
+ # Parse request body
112
+ body = request.body.read
113
+ request.body.rewind
114
+
115
+ # Parse JSON-RPC request directly from string
116
+ begin
117
+ rpc_request = A2A::Protocol::JsonRpc.parse_request(body)
118
+ rescue A2A::Errors::A2AError => e
119
+ return json_rpc_error_response(
120
+ nil, # No ID available if parsing failed
121
+ e.code,
122
+ e.message,
123
+ e.data
124
+ )
125
+ end
126
+
127
+ # Build server context
128
+ context = build_server_context(request)
129
+
130
+ # Route to appropriate handler method
131
+ begin
132
+ result = route_request(rpc_request, context)
133
+
134
+ # Handle streaming responses
135
+ return streaming_response(result) if result.is_a?(Enumerator)
136
+
137
+ # Return regular JSON-RPC response
138
+ response_data = A2A::Protocol::JsonRpc.build_response(
139
+ result: result,
140
+ id: rpc_request.id
141
+ )
142
+ json_response(200, response_data)
143
+ rescue A2A::Errors::A2AError => e
144
+ json_rpc_error_response(rpc_request.id, e.code, e.message, e.data)
145
+ rescue StandardError => e
146
+ json_rpc_error_response(
147
+ rpc_request.id,
148
+ A2A::Protocol::JsonRpc::INTERNAL_ERROR,
149
+ "Internal error: #{e.message}"
150
+ )
151
+ end
152
+ end
153
+
154
+ ##
155
+ # Route JSON-RPC request to appropriate handler method
156
+ #
157
+ # @param request [A2A::Protocol::Request] The parsed JSON-RPC request
158
+ # @param context [A2A::Server::Context] The server context
159
+ # @return [Object] The result from the handler
160
+ def route_request(request, context)
161
+ case request.method
162
+ when "message/send"
163
+ @request_handler.on_message_send(request.params, context)
164
+ when "message/stream"
165
+ @request_handler.on_message_send_stream(request.params, context)
166
+ when "tasks/get"
167
+ @request_handler.on_get_task(request.params, context)
168
+ when "tasks/cancel"
169
+ @request_handler.on_cancel_task(request.params, context)
170
+ when "tasks/resubscribe"
171
+ @request_handler.on_resubscribe_to_task(request.params, context)
172
+ when "tasks/pushNotificationConfig/set"
173
+ @request_handler.on_set_task_push_notification_config(request.params, context)
174
+ when "tasks/pushNotificationConfig/get"
175
+ @request_handler.on_get_task_push_notification_config(request.params, context)
176
+ when "tasks/pushNotificationConfig/list"
177
+ @request_handler.on_list_task_push_notification_config(request.params, context)
178
+ when "tasks/pushNotificationConfig/delete"
179
+ @request_handler.on_delete_task_push_notification_config(request.params, context)
180
+ else
181
+ raise A2A::Errors::MethodNotFound, "Method '#{request.method}' not found"
182
+ end
183
+ end
184
+
185
+ ##
186
+ # Build server context from Rack request
187
+ #
188
+ # @param request [Rack::Request] The Rack request
189
+ # @return [A2A::Server::Context] The server context
190
+ def build_server_context(request)
191
+ context = A2A::Server::Context.new
192
+
193
+ # Extract user information if available (depends on authentication middleware)
194
+ if request.env["warden"]&.authenticated?
195
+ context.set_user(request.env["warden"].user)
196
+ context.set_authentication("warden", request.env["warden"])
197
+ elsif request.env["current_user"]
198
+ context.set_user(request.env["current_user"])
199
+ end
200
+
201
+ # Set request metadata
202
+ context.set_metadata(:remote_addr, request.ip)
203
+ context.set_metadata(:user_agent, request.user_agent)
204
+ context.set_metadata(:headers, request.env.select { |k, _| k.start_with?("HTTP_") })
205
+
206
+ context
207
+ end
208
+
209
+ ##
210
+ # Create a JSON response
211
+ #
212
+ # @param status [Integer] HTTP status code
213
+ # @param data [Object] Data to serialize as JSON
214
+ # @return [Array] Rack response
215
+ def json_response(status, data)
216
+ headers = {
217
+ "Content-Type" => "application/json",
218
+ "Cache-Control" => "no-cache"
219
+ }
220
+
221
+ body = JSON.generate(data)
222
+ [status, headers, [body]]
223
+ end
224
+
225
+ ##
226
+ # Create a JSON-RPC error response
227
+ #
228
+ # @param id [String, Integer, nil] Request ID
229
+ # @param code [Integer] Error code
230
+ # @param message [String] Error message
231
+ # @param data [Object, nil] Optional error data
232
+ # @return [Array] Rack response
233
+ def json_rpc_error_response(id, code, message, data = nil)
234
+ error_data = A2A::Protocol::JsonRpc.build_error_response(
235
+ code: code,
236
+ message: message,
237
+ data: data,
238
+ id: id
239
+ )
240
+ json_response(200, error_data)
241
+ end
242
+
243
+ ##
244
+ # Create a streaming response using Server-Sent Events
245
+ #
246
+ # @param enumerator [Enumerator] The enumerator yielding events
247
+ # @return [Array] Rack response
248
+ def streaming_response(enumerator)
249
+ headers = {
250
+ "Content-Type" => "text/event-stream",
251
+ "Cache-Control" => "no-cache",
252
+ "Connection" => "keep-alive"
253
+ }
254
+
255
+ # Create streaming body
256
+ body = Enumerator.new do |yielder|
257
+ enumerator.each do |event|
258
+ event_data = if event.respond_to?(:to_h)
259
+ event.to_h
260
+ else
261
+ event
262
+ end
263
+
264
+ yielder << "data: #{JSON.generate(event_data)}\n\n"
265
+ end
266
+ rescue StandardError => e
267
+ error_event = {
268
+ error: {
269
+ code: A2A::Protocol::JsonRpc::INTERNAL_ERROR,
270
+ message: e.message
271
+ }
272
+ }
273
+ yielder << "data: #{JSON.generate(error_event)}\n\n"
274
+ ensure
275
+ yielder << "data: [DONE]\n\n"
276
+ end
277
+
278
+ [200, headers, body]
279
+ end
280
+
281
+ ##
282
+ # Create a 404 Not Found response
283
+ #
284
+ # @return [Array] Rack response
285
+ def not_found_response
286
+ error_response(404, "Not Found")
287
+ end
288
+
289
+ ##
290
+ # Create a 405 Method Not Allowed response
291
+ #
292
+ # @return [Array] Rack response
293
+ def method_not_allowed_response
294
+ error_response(405, "Method Not Allowed")
295
+ end
296
+
297
+ ##
298
+ # Create an error response
299
+ #
300
+ # @param status [Integer] HTTP status code
301
+ # @param message [String] Error message
302
+ # @return [Array] Rack response
303
+ def error_response(status, message)
304
+ headers = { "Content-Type" => "application/json" }
305
+ body = JSON.generate({ error: message })
306
+ [status, headers, [body]]
307
+ end
308
+ end
309
+ end
310
+ end
311
+ end
@@ -0,0 +1,261 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "sinatra/base"
5
+ rescue LoadError
6
+ raise LoadError, "Sinatra is required for A2A::Server::Apps::SinatraApp. Install with: gem install sinatra"
7
+ end
8
+
9
+ require "json"
10
+ require_relative "../request_handler"
11
+ require_relative "../../protocol/json_rpc"
12
+
13
+ module A2A
14
+ module Server
15
+ module Apps
16
+ ##
17
+ # Sinatra application for serving A2A protocol endpoints
18
+ #
19
+ # This class provides a Sinatra-based application that can handle
20
+ # A2A JSON-RPC requests and serve agent cards. It's a more Ruby-idiomatic
21
+ # alternative to the Rack app.
22
+ #
23
+ class SinatraApp < Sinatra::Base
24
+ set :show_exceptions, false
25
+ set :raise_errors, false
26
+
27
+ class << self
28
+ attr_accessor :agent_card, :request_handler, :extended_agent_card, :card_modifier, :extended_card_modifier
29
+
30
+ ##
31
+ # Configure the Sinatra app with A2A components
32
+ #
33
+ # @param agent_card [A2A::Types::AgentCard] The agent card
34
+ # @param request_handler [RequestHandler] The request handler
35
+ # @param extended_agent_card [A2A::Types::AgentCard, nil] Optional extended agent card
36
+ # @param card_modifier [Proc, nil] Optional card modifier
37
+ # @param extended_card_modifier [Proc, nil] Optional extended card modifier
38
+ def configure_a2a(agent_card:, request_handler:, extended_agent_card: nil, card_modifier: nil,
39
+ extended_card_modifier: nil)
40
+ self.agent_card = agent_card
41
+ self.request_handler = request_handler
42
+ self.extended_agent_card = extended_agent_card
43
+ self.card_modifier = card_modifier
44
+ self.extended_card_modifier = extended_card_modifier
45
+ end
46
+ end
47
+
48
+ # Agent card endpoint
49
+ get "/.well-known/a2a/agent-card" do
50
+ content_type :json
51
+
52
+ card_to_serve = self.class.agent_card
53
+ card_to_serve = self.class.card_modifier.call(card_to_serve) if self.class.card_modifier
54
+
55
+ JSON.generate(card_to_serve.to_h)
56
+ end
57
+
58
+ # Extended agent card endpoint
59
+ get "/a2a/agent-card/extended" do
60
+ content_type :json
61
+
62
+ unless self.class.agent_card.supports_authenticated_extended_card
63
+ halt 404, JSON.generate({ error: "Extended agent card not supported" })
64
+ end
65
+
66
+ # Build server context
67
+ context = build_server_context
68
+
69
+ card_to_serve = self.class.extended_agent_card || self.class.agent_card
70
+
71
+ card_to_serve = self.class.extended_card_modifier.call(card_to_serve, context) if self.class.extended_card_modifier
72
+
73
+ JSON.generate(card_to_serve.to_h)
74
+ end
75
+
76
+ # JSON-RPC endpoint
77
+ post "/a2a/rpc" do
78
+ content_type :json
79
+
80
+ # Validate content type
81
+ unless request.content_type&.include?("application/json")
82
+ halt 400, JSON.generate({ error: "Content-Type must be application/json" })
83
+ end
84
+
85
+ # Parse request body
86
+ begin
87
+ request.body.rewind
88
+ json_data = JSON.parse(request.body.read)
89
+ rescue JSON::ParserError => e
90
+ return json_rpc_error_response(nil, A2A::Protocol::JsonRpc::PARSE_ERROR, "Parse error: #{e.message}")
91
+ end
92
+
93
+ # Validate JSON-RPC structure
94
+ begin
95
+ rpc_request = A2A::Protocol::JsonRpc.parse_request(json_data)
96
+ rescue A2A::Errors::A2AError => e
97
+ return json_rpc_error_response(json_data["id"], e.code, e.message, e.data)
98
+ end
99
+
100
+ # Build server context
101
+ context = build_server_context
102
+
103
+ # Route to appropriate handler method
104
+ begin
105
+ result = route_request(rpc_request, context)
106
+
107
+ # Handle streaming responses
108
+ return handle_streaming_response(result) if result.is_a?(Enumerator)
109
+
110
+ # Return regular JSON-RPC response
111
+ response_data = A2A::Protocol::JsonRpc.build_response(
112
+ result: result,
113
+ id: rpc_request.id
114
+ )
115
+ JSON.generate(response_data)
116
+ rescue A2A::Errors::A2AError => e
117
+ json_rpc_error_response(rpc_request.id, e.code, e.message, e.data)
118
+ rescue StandardError => e
119
+ json_rpc_error_response(
120
+ rpc_request.id,
121
+ A2A::Protocol::JsonRpc::INTERNAL_ERROR,
122
+ "Internal error: #{e.message}"
123
+ )
124
+ end
125
+ end
126
+
127
+ # Error handlers
128
+ error A2A::Errors::A2AError do
129
+ content_type :json
130
+ error = env["sinatra.error"]
131
+ status 400
132
+ JSON.generate({
133
+ error: {
134
+ code: error.code,
135
+ message: error.message,
136
+ data: error.data
137
+ }
138
+ })
139
+ end
140
+
141
+ error StandardError do
142
+ content_type :json
143
+ error = env["sinatra.error"]
144
+ status 500
145
+ JSON.generate({
146
+ error: {
147
+ code: A2A::Protocol::JsonRpc::INTERNAL_ERROR,
148
+ message: "Internal Server Error: #{error.message}"
149
+ }
150
+ })
151
+ end
152
+
153
+ private
154
+
155
+ ##
156
+ # Route JSON-RPC request to appropriate handler method
157
+ #
158
+ # @param request [A2A::Protocol::Request] The parsed JSON-RPC request
159
+ # @param context [A2A::Server::Context] The server context
160
+ # @return [Object] The result from the handler
161
+ def route_request(request, context)
162
+ case request.method
163
+ when "message/send"
164
+ self.class.request_handler.on_message_send(request.params, context)
165
+ when "message/stream"
166
+ self.class.request_handler.on_message_send_stream(request.params, context)
167
+ when "tasks/get"
168
+ self.class.request_handler.on_get_task(request.params, context)
169
+ when "tasks/cancel"
170
+ self.class.request_handler.on_cancel_task(request.params, context)
171
+ when "tasks/resubscribe"
172
+ self.class.request_handler.on_resubscribe_to_task(request.params, context)
173
+ when "tasks/pushNotificationConfig/set"
174
+ self.class.request_handler.on_set_task_push_notification_config(request.params, context)
175
+ when "tasks/pushNotificationConfig/get"
176
+ self.class.request_handler.on_get_task_push_notification_config(request.params, context)
177
+ when "tasks/pushNotificationConfig/list"
178
+ self.class.request_handler.on_list_task_push_notification_config(request.params, context)
179
+ when "tasks/pushNotificationConfig/delete"
180
+ self.class.request_handler.on_delete_task_push_notification_config(request.params, context)
181
+ else
182
+ raise A2A::Errors::MethodNotFound, "Method '#{request.method}' not found"
183
+ end
184
+ end
185
+
186
+ ##
187
+ # Build server context from Sinatra request
188
+ #
189
+ # @return [A2A::Server::Context] The server context
190
+ def build_server_context
191
+ context = A2A::Server::Context.new
192
+
193
+ # Extract user information if available
194
+ if respond_to?(:current_user) && current_user
195
+ context.set_user(current_user)
196
+ elsif env["warden"]&.authenticated?
197
+ context.set_user(env["warden"].user)
198
+ context.set_authentication("warden", env["warden"])
199
+ end
200
+
201
+ # Set request metadata
202
+ context.set_metadata(:remote_addr, request.ip)
203
+ context.set_metadata(:user_agent, request.user_agent)
204
+ context.set_metadata(:headers, request.env.select { |k, _| k.start_with?("HTTP_") })
205
+
206
+ context
207
+ end
208
+
209
+ ##
210
+ # Create a JSON-RPC error response
211
+ #
212
+ # @param id [String, Integer, nil] Request ID
213
+ # @param code [Integer] Error code
214
+ # @param message [String] Error message
215
+ # @param data [Object, nil] Optional error data
216
+ # @return [String] JSON response
217
+ def json_rpc_error_response(id, code, message, data = nil)
218
+ error_data = A2A::Protocol::JsonRpc.build_error_response(
219
+ code: code,
220
+ message: message,
221
+ data: data,
222
+ id: id
223
+ )
224
+ JSON.generate(error_data)
225
+ end
226
+
227
+ ##
228
+ # Handle streaming response using Server-Sent Events
229
+ #
230
+ # @param enumerator [Enumerator] The enumerator yielding events
231
+ # @return [String] SSE response
232
+ def handle_streaming_response(enumerator)
233
+ content_type "text/event-stream"
234
+ headers "Cache-Control" => "no-cache", "Connection" => "keep-alive"
235
+
236
+ stream do |out|
237
+ enumerator.each do |event|
238
+ event_data = if event.respond_to?(:to_h)
239
+ event.to_h
240
+ else
241
+ event
242
+ end
243
+
244
+ out << "data: #{JSON.generate(event_data)}\n\n"
245
+ end
246
+ rescue StandardError => e
247
+ error_event = {
248
+ error: {
249
+ code: A2A::Protocol::JsonRpc::INTERNAL_ERROR,
250
+ message: e.message
251
+ }
252
+ }
253
+ out << "data: #{JSON.generate(error_event)}\n\n"
254
+ ensure
255
+ out << "data: [DONE]\n\n"
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end