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,254 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ # Try to use Oj for faster JSON parsing if available
6
+ begin
7
+ require "oj"
8
+ OJ_AVAILABLE = true
9
+ rescue LoadError
10
+ OJ_AVAILABLE = false
11
+ end
12
+
13
+ module A2A
14
+ module Protocol
15
+ ##
16
+ # JSON-RPC 2.0 implementation for A2A protocol
17
+ #
18
+ # This class provides parsing and building functionality for JSON-RPC 2.0 requests
19
+ # and responses, including support for batch requests and proper error handling.
20
+ # Optimized for performance with optional Oj JSON parser support.
21
+ #
22
+ # @see https://www.jsonrpc.org/specification JSON-RPC 2.0 Specification
23
+ class JsonRpc
24
+ # JSON-RPC 2.0 version string
25
+ JSONRPC_VERSION = "2.0"
26
+
27
+ # Standard JSON-RPC error codes
28
+ PARSE_ERROR = -32_700
29
+ INVALID_REQUEST = -32_600
30
+ METHOD_NOT_FOUND = -32_601
31
+ INVALID_PARAMS = -32_602
32
+ INTERNAL_ERROR = -32_603
33
+
34
+ # A2A-specific error codes
35
+ TASK_NOT_FOUND = -32_001
36
+ TASK_NOT_CANCELABLE = -32_002
37
+ INVALID_TASK_STATE = -32_003
38
+ AUTHENTICATION_REQUIRED = -32_004
39
+ AUTHORIZATION_FAILED = -32_005
40
+ RATE_LIMIT_EXCEEDED = -32_006
41
+ AGENT_UNAVAILABLE = -32_007
42
+ PROTOCOL_VERSION_MISMATCH = -32_008
43
+ CAPABILITY_NOT_SUPPORTED = -32_009
44
+ RESOURCE_EXHAUSTED = -32_010
45
+
46
+ ##
47
+ # Parse JSON string using optimized parser if available
48
+ #
49
+ # @param json_string [String] JSON string to parse
50
+ # @return [Hash, Array] Parsed JSON data
51
+ def self.parse_json(json_string)
52
+ if OJ_AVAILABLE
53
+ Oj.load(json_string, mode: :strict, symbol_keys: false)
54
+ else
55
+ JSON.parse(json_string)
56
+ end
57
+ end
58
+
59
+ ##
60
+ # Generate JSON string using optimized generator if available
61
+ #
62
+ # @param object [Object] Object to serialize
63
+ # @return [String] JSON string
64
+ def self.generate_json(object)
65
+ if OJ_AVAILABLE
66
+ Oj.dump(object, mode: :compat)
67
+ else
68
+ JSON.generate(object)
69
+ end
70
+ end
71
+
72
+ ##
73
+ # Parse a JSON-RPC 2.0 request from JSON string
74
+ #
75
+ # @param json_string [String] The JSON string to parse
76
+ # @return [Request, Array<Request>] Single request or array for batch requests
77
+ # @raise [A2A::Errors::ParseError] If JSON is invalid
78
+ # @raise [A2A::Errors::InvalidRequest] If request format is invalid
79
+ def self.parse_request(json_string)
80
+ # Performance optimization: early return for empty strings
81
+ return nil if json_string.nil? || (json_string.respond_to?(:empty?) && json_string.empty?)
82
+
83
+ # Ensure we have a string
84
+ json_string = json_string.to_s unless json_string.is_a?(String)
85
+
86
+ begin
87
+ # Use optimized JSON parser if available
88
+ parsed = parse_json(json_string)
89
+ rescue JSON::ParserError, Oj::ParseError => e
90
+ raise A2A::Errors::ParseError, "Invalid JSON: #{e.message}"
91
+ end
92
+
93
+ if parsed.is_a?(Array)
94
+ # Batch request
95
+ raise A2A::Errors::InvalidRequest, "Empty batch request" if parsed.empty?
96
+
97
+ # Performance optimization: use map! for in-place modification
98
+ parsed.map! { |req| parse_single_request(req) }
99
+ else
100
+ # Single request
101
+ parse_single_request(parsed)
102
+ end
103
+ end
104
+
105
+ ##
106
+ # Build a JSON-RPC 2.0 response
107
+ #
108
+ # @param result [Object, nil] The result value (mutually exclusive with error)
109
+ # @param error [Hash, nil] The error object (mutually exclusive with result)
110
+ # @param id [String, Integer, nil] The request ID
111
+ # @return [Hash] The response hash
112
+ def self.build_response(id:, **kwargs)
113
+ result_provided = kwargs.key?(:result)
114
+ error_provided = kwargs.key?(:error)
115
+
116
+ raise ArgumentError, "Cannot specify both result and error" if result_provided && error_provided
117
+ raise ArgumentError, "Must specify either result or error" unless result_provided || error_provided
118
+
119
+ response = {
120
+ jsonrpc: JSONRPC_VERSION,
121
+ id: id
122
+ }
123
+
124
+ if error_provided
125
+ response[:error] = normalize_error(kwargs[:error])
126
+ else
127
+ response[:result] = kwargs[:result]
128
+ end
129
+
130
+ response
131
+ end
132
+
133
+ ##
134
+ # Build a JSON-RPC 2.0 batch response
135
+ #
136
+ # @param responses [Array<Hash>] Array of individual responses
137
+ # @return [Array<Hash>] The batch response array
138
+ def self.build_batch_response(responses)
139
+ # Filter out notification responses (id: nil)
140
+ responses.reject { |resp| resp[:id].nil? }
141
+ end
142
+
143
+ ##
144
+ # Build an error response
145
+ #
146
+ # @param code [Integer] The error code
147
+ # @param message [String] The error message
148
+ # @param data [Object, nil] Additional error data
149
+ # @param id [String, Integer, nil] The request ID
150
+ # @return [Hash] The error response hash
151
+ def self.build_error_response(code:, message:, id:, data: nil)
152
+ error = { code: code, message: message }
153
+ error[:data] = data if data
154
+
155
+ build_response(error: error, id: id)
156
+ end
157
+
158
+ ##
159
+ # Check if a hash represents a valid JSON-RPC 2.0 request
160
+ #
161
+ # @param hash [Hash] The hash to validate
162
+ # @return [Boolean] True if valid request format
163
+ def self.valid_request?(hash)
164
+ return false unless hash.is_a?(Hash)
165
+ return false unless hash["jsonrpc"] == JSONRPC_VERSION
166
+ return false unless hash["method"].is_a?(String)
167
+
168
+ # id can be string, number, or null (for notifications)
169
+ id = hash["id"]
170
+ return false unless id.nil? || id.is_a?(String) || id.is_a?(Integer)
171
+
172
+ # params is optional but must be object or array if present
173
+ params = hash["params"]
174
+ return false if params && !params.is_a?(Hash) && !params.is_a?(Array)
175
+
176
+ true
177
+ end
178
+
179
+ private_class_method def self.parse_single_request(hash)
180
+ raise A2A::Errors::InvalidRequest, "Invalid request format" unless valid_request?(hash)
181
+
182
+ Request.new(
183
+ jsonrpc: hash["jsonrpc"],
184
+ method: hash["method"],
185
+ params: hash["params"] || {},
186
+ id: hash["id"]
187
+ )
188
+ end
189
+
190
+ private_class_method def self.normalize_error(error)
191
+ if error.is_a?(A2A::Errors::A2AError)
192
+ error.to_json_rpc_error
193
+ elsif error.is_a?(Hash)
194
+ error
195
+ else
196
+ { code: INTERNAL_ERROR, message: error.to_s }
197
+ end
198
+ end
199
+ end
200
+
201
+ ##
202
+ # Represents a JSON-RPC 2.0 request
203
+ class Request
204
+ attr_reader :jsonrpc, :method, :params, :id
205
+
206
+ ##
207
+ # Initialize a new request
208
+ #
209
+ # @param jsonrpc [String] The JSON-RPC version (should be "2.0")
210
+ # @param method [String] The method name
211
+ # @param params [Hash, Array] The method parameters
212
+ # @param id [String, Integer, nil] The request ID (nil for notifications)
213
+ def initialize(jsonrpc:, method:, params: {}, id: nil)
214
+ @jsonrpc = jsonrpc
215
+ @method = method
216
+ @params = params
217
+ @id = id
218
+ end
219
+
220
+ ##
221
+ # Check if this is a notification (no response expected)
222
+ #
223
+ # @return [Boolean] True if this is a notification
224
+ def notification?
225
+ @id.nil?
226
+ end
227
+
228
+ ##
229
+ # Convert to hash representation
230
+ #
231
+ # @return [Hash] The request as a hash
232
+ def to_h
233
+ hash = {
234
+ jsonrpc: @jsonrpc,
235
+ method: @method
236
+ }
237
+
238
+ hash[:params] = @params unless @params.nil? || (@params.respond_to?(:empty?) && @params.empty?)
239
+ hash[:id] = @id unless @id.nil?
240
+
241
+ hash
242
+ end
243
+
244
+ ##
245
+ # Convert to JSON string
246
+ #
247
+ # @return [String] The request as JSON
248
+ def to_json(*_args)
249
+ # Use optimized JSON generator if available
250
+ JsonRpc.generate_json(to_h)
251
+ end
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Placeholder - will be implemented in task 2.2
4
+ module A2A
5
+ module Protocol
6
+ # Placeholder - will be implemented in task 2.2
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Placeholder - will be implemented in task 6.1
4
+ module A2A
5
+ module Protocol
6
+ # Placeholder - will be implemented in task 6.1
7
+ end
8
+ end
@@ -0,0 +1,258 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../utils/rails_detection"
4
+
5
+ ##
6
+ # Main controller for A2A Rails engine
7
+ #
8
+ # This controller handles all A2A protocol endpoints including JSON-RPC requests,
9
+ # agent card serving, and streaming responses.
10
+ #
11
+ module A2A
12
+ module Rails
13
+ class A2aController < ApplicationController
14
+ include A2A::Rails::ControllerHelpers
15
+ include A2A::Utils::RailsDetection
16
+
17
+ # Skip CSRF protection for A2A endpoints
18
+ skip_before_action :verify_authenticity_token
19
+
20
+ # Handle JSON-RPC requests
21
+ def rpc
22
+ request_body = request.body.read
23
+
24
+ begin
25
+ json_rpc_request = A2A::Protocol::JsonRpc.parse_request(request_body)
26
+
27
+ # Handle batch requests
28
+ if json_rpc_request.is_a?(Array)
29
+ responses = json_rpc_request.map { |req| handle_single_request(req) }
30
+ render json: responses
31
+ else
32
+ response = handle_single_request(json_rpc_request)
33
+ render json: response
34
+ end
35
+ rescue A2A::Errors::A2AError => e
36
+ render json: build_error_response(e), status: :bad_request
37
+ rescue StandardError => e
38
+ error = A2A::Errors::InternalError.new(e.message)
39
+ render json: build_error_response(error), status: :internal_server_error
40
+ end
41
+ end
42
+
43
+ # Serve agent card
44
+ def agent_card
45
+ card = generate_agent_card
46
+
47
+ # Support different output formats
48
+ case request.format.symbol
49
+ when :json
50
+ render json: card.to_h
51
+ when :jws
52
+ # TODO: Implement JWS signing
53
+ render json: { error: "JWS format not yet implemented" }, status: :not_implemented
54
+ else
55
+ render json: card.to_h
56
+ end
57
+ rescue StandardError => e
58
+ render json: { error: e.message }, status: :internal_server_error
59
+ end
60
+
61
+ # Serve authenticated agent card
62
+ def authenticated_agent_card
63
+ # Ensure authentication is present
64
+ unless authenticated?
65
+ render json: { error: "Authentication required" }, status: :unauthorized
66
+ return
67
+ end
68
+
69
+ card = generate_authenticated_agent_card
70
+ render json: card.to_h
71
+ rescue StandardError => e
72
+ render json: { error: e.message }, status: :internal_server_error
73
+ end
74
+
75
+ # List capabilities
76
+ def capabilities
77
+ capabilities = collect_capabilities
78
+ render json: { capabilities: capabilities }
79
+ rescue StandardError => e
80
+ render json: { error: e.message }, status: :internal_server_error
81
+ end
82
+
83
+ # Health check endpoint
84
+ def health
85
+ render json: {
86
+ status: "healthy",
87
+ version: A2A::VERSION,
88
+ timestamp: Time.now.iso8601,
89
+ rails_version: rails_version || "unknown"
90
+ }
91
+ end
92
+
93
+ # Server-Sent Events streaming endpoint
94
+ def stream
95
+ task_id = params[:task_id]
96
+
97
+ begin
98
+ task = A2A::Server::TaskManager.instance.get_task(task_id)
99
+
100
+ response.headers["Content-Type"] = "text/event-stream"
101
+ response.headers["Cache-Control"] = "no-cache"
102
+ response.headers["Connection"] = "keep-alive"
103
+
104
+ # Set up SSE stream
105
+ sse = A2A::Transport::SSE.new(response.stream)
106
+
107
+ # Send initial task status
108
+ sse.write_event("task-status", task.to_h)
109
+
110
+ # Subscribe to task updates
111
+ subscription = A2A::Server::TaskManager.instance.subscribe_to_task(task_id) do |event|
112
+ case event
113
+ when A2A::Types::TaskStatusUpdateEvent
114
+ sse.write_event("task-status-update", event.to_h)
115
+ when A2A::Types::TaskArtifactUpdateEvent
116
+ sse.write_event("task-artifact-update", event.to_h)
117
+ end
118
+ end
119
+
120
+ # Keep connection alive until client disconnects
121
+ loop do
122
+ break if response.stream.closed?
123
+
124
+ sleep 1
125
+ sse.write_event("heartbeat", { timestamp: Time.now.iso8601 })
126
+ end
127
+ rescue A2A::Errors::TaskNotFound
128
+ render json: { error: "Task not found" }, status: :not_found
129
+ rescue StandardError => e
130
+ render json: { error: e.message }, status: :internal_server_error
131
+ ensure
132
+ subscription&.unsubscribe
133
+ response.stream.close
134
+ end
135
+ end
136
+
137
+ # Webhook endpoint for push notifications
138
+ def webhook
139
+ task_id = params[:task_id]
140
+
141
+ begin
142
+ # Verify webhook authentication if configured
143
+ verify_webhook_authentication! if webhook_authentication_required?
144
+
145
+ # Process webhook payload
146
+ payload = JSON.parse(request.body.read)
147
+
148
+ # Handle different webhook types
149
+ case payload["type"]
150
+ when "task_status_update"
151
+ handle_task_status_webhook(task_id, payload)
152
+ when "task_artifact_update"
153
+ handle_task_artifact_webhook(task_id, payload)
154
+ else
155
+ render json: { error: "Unknown webhook type" }, status: :bad_request
156
+ return
157
+ end
158
+
159
+ render json: { status: "processed" }
160
+ rescue JSON::ParserError
161
+ render json: { error: "Invalid JSON payload" }, status: :bad_request
162
+ rescue A2A::Errors::TaskNotFound
163
+ render json: { error: "Task not found" }, status: :not_found
164
+ rescue A2A::Errors::AuthenticationError
165
+ render json: { error: "Webhook authentication failed" }, status: :unauthorized
166
+ rescue StandardError => e
167
+ render json: { error: e.message }, status: :internal_server_error
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ def handle_single_request(json_rpc_request)
174
+ # Delegate to the A2A request handler
175
+ handle_a2a_request(json_rpc_request)
176
+ rescue A2A::Errors::A2AError => e
177
+ build_error_response(e, json_rpc_request.id)
178
+ rescue StandardError => e
179
+ error = A2A::Errors::InternalError.new(e.message)
180
+ build_error_response(error, json_rpc_request.id)
181
+ end
182
+
183
+ def build_error_response(error, id = nil)
184
+ A2A::Protocol::JsonRpc.build_response(
185
+ error: error.to_json_rpc_error,
186
+ id: id
187
+ )
188
+ end
189
+
190
+ def generate_authenticated_agent_card
191
+ # Generate extended agent card with authentication context
192
+ card = generate_agent_card
193
+
194
+ # Add authenticated-specific information
195
+ card_hash = card.to_h
196
+ card_hash[:authenticated_user] = current_user_info if respond_to?(:current_user_info)
197
+ card_hash[:permissions] = current_user_permissions if respond_to?(:current_user_permissions)
198
+
199
+ A2A::Types::AgentCard.from_h(card_hash)
200
+ end
201
+
202
+ def collect_capabilities
203
+ # Collect all registered A2A capabilities from controllers
204
+ capabilities = []
205
+
206
+ # Scan all controllers that include A2A::Server::Agent
207
+ ObjectSpace.each_object(Class) do |klass|
208
+ if klass < ActionController::Base && klass.included_modules.include?(A2A::Server::Agent)
209
+ capabilities.concat(klass._a2a_capabilities || [])
210
+ end
211
+ end
212
+
213
+ capabilities.map(&:to_h)
214
+ end
215
+
216
+ def authenticated?
217
+ # Check if request is authenticated
218
+ # This can be overridden by applications to integrate with their auth system
219
+ request.headers["Authorization"].present? ||
220
+ (respond_to?(:current_user) && current_user.present?)
221
+ end
222
+
223
+ def webhook_authentication_required?
224
+ A2A.config.webhook_authentication_required || false
225
+ end
226
+
227
+ def verify_webhook_authentication!
228
+ # Verify webhook signature or token
229
+ # This should be implemented based on the specific authentication method
230
+ auth_header = request.headers["Authorization"]
231
+
232
+ return if auth_header.present?
233
+
234
+ raise A2A::Errors::AuthenticationError, "Missing webhook authentication"
235
+
236
+ # TODO: Implement specific webhook authentication logic
237
+ # This could verify HMAC signatures, JWT tokens, etc.
238
+ end
239
+
240
+ def handle_task_status_webhook(task_id, payload)
241
+ # Process task status update webhook
242
+ status_data = payload["status"]
243
+ status = A2A::Types::TaskStatus.from_h(status_data)
244
+
245
+ A2A::Server::TaskManager.instance.update_task_status(task_id, status)
246
+ end
247
+
248
+ def handle_task_artifact_webhook(task_id, payload)
249
+ # Process task artifact update webhook
250
+ artifact_data = payload["artifact"]
251
+ artifact = A2A::Types::Artifact.from_h(artifact_data)
252
+ append = payload["append"] || false
253
+
254
+ A2A::Server::TaskManager.instance.update_task_artifact(task_id, artifact, append: append)
255
+ end
256
+ end
257
+ end
258
+ end