Module: A2A::Rails::ControllerHelpers
Overview
Controller helpers for A2A Rails integration
This module provides helper methods for Rails controllers to handle A2A requests,
generate agent cards, and integrate with Rails authentication systems.
Instance Method Summary
collapse
#rails_application, #rails_available?, #rails_development?, #rails_environment, #rails_logger, #rails_production?, #rails_version, #rails_version_requires_validation?, #rails_version_supported?
Instance Method Details
#a2a_request? ⇒ Boolean
Check if current request is an A2A request
199
200
201
202
203
|
# File 'lib/a2a/rails/controller_helpers.rb', line 199
def a2a_request?
request.path.start_with?(A2A.config.mount_path) ||
request.["Content-Type"]&.include?("application/json-rpc") ||
params[:controller] == "a2a/rails/a2a"
end
|
#agent_card_url ⇒ Object
URL helpers for agent card generation
462
463
464
|
# File 'lib/a2a/rails/controller_helpers.rb', line 462
def agent_card_url
"#{request.base_url}#{A2A.config.mount_path}/agent-card"
end
|
#api_key_authenticated? ⇒ Boolean
452
453
454
455
456
457
458
459
|
# File 'lib/a2a/rails/controller_helpers.rb', line 452
def api_key_authenticated?
api_key = request.["X-API-Key"] || params[:api_key]
return false unless api_key.present?
api_key.length >= 32 end
|
#authenticate_a2a_request ⇒ Object
#build_a2a_error_response(error, id = nil) ⇒ Object
284
285
286
287
288
289
|
# File 'lib/a2a/rails/controller_helpers.rb', line 284
def build_a2a_error_response(error, id = nil)
A2A::Protocol::JsonRpc.build_response(
error: error.to_json_rpc_error,
id: id
)
end
|
#build_additional_interfaces ⇒ Object
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
|
# File 'lib/a2a/rails/controller_helpers.rb', line 333
def build_additional_interfaces
interfaces = []
if defined?(A2A::Transport::Grpc)
interfaces << {
transport: "GRPC",
url: grpc_endpoint_url
}
end
interfaces << {
transport: "HTTP+JSON",
url: http_json_endpoint_url
}
interfaces
end
|
412
413
414
415
416
417
418
419
420
421
|
# File 'lib/a2a/rails/controller_helpers.rb', line 412
def build_agent_metadata(config)
base_metadata = {
controller: controller_name,
action: action_name,
rails_version: rails_version || "unknown",
created_at: Time.now.iso8601
}
base_metadata.merge(config[:metadata] || {})
end
|
#build_provider_info ⇒ Object
384
385
386
387
388
389
390
391
392
393
394
395
|
# File 'lib/a2a/rails/controller_helpers.rb', line 384
def build_provider_info
app = rails_application
{
name: app&.class&.module_parent_name || "Unknown",
version: begin
app&.config&.version || "1.0.0"
rescue StandardError
"1.0.0"
end,
url: root_url
}
end
|
#build_security_config ⇒ Object
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
|
# File 'lib/a2a/rails/controller_helpers.rb', line 353
def build_security_config
auth_config = self.class.a2a_auth_config
case auth_config[:strategy]
when :jwt
{
security_schemes: {
jwt_auth: {
type: "http",
scheme: "bearer",
bearer_format: "JWT"
}
},
security: [{ jwt_auth: [] }]
}
when :api_key
{
security_schemes: {
api_key_auth: {
type: "apiKey",
in: "header",
name: "X-API-Key"
}
},
security: [{ api_key_auth: [] }]
}
else
{}
end
end
|
#collect_capabilities_hash ⇒ Object
#collect_skills ⇒ Object
309
310
311
312
313
314
315
316
317
318
319
320
321
322
|
# File 'lib/a2a/rails/controller_helpers.rb', line 309
def collect_skills
capabilities = self.class._a2a_capabilities || []
capabilities.map do |capability|
{
id: capability.name,
name: capability.name.humanize,
description: capability.description,
tags: capability.tags || [],
examples: capability.examples || [],
input_modes: capability.input_modes || A2A.config.default_input_modes,
output_modes: capability.output_modes || A2A.config.default_output_modes
}
end
end
|
#current_user_authenticated? ⇒ Boolean
Check if current user is authenticated for A2A requests
This method integrates with various Rails authentication systems.
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
|
# File 'lib/a2a/rails/controller_helpers.rb', line 212
def current_user_authenticated?
auth_config = self.class.a2a_auth_config
case auth_config[:strategy]
when :devise
respond_to?(:current_user) && current_user.present?
when :jwt
jwt_authenticated?
when :api_key
api_key_authenticated?
when :custom
auth_config[:block]&.call(request) || false
else
true end
end
|
#current_user_info ⇒ Hash
Get current user information for authenticated cards
234
235
236
237
238
239
240
241
242
243
244
245
|
# File 'lib/a2a/rails/controller_helpers.rb', line 234
def current_user_info
if respond_to?(:current_user) && current_user.present?
{
id: current_user.id,
email: current_user.email,
name: current_user.name || current_user.email,
roles: current_user_roles
}
else
{}
end
end
|
#current_user_permissions ⇒ Array<String>
Get current user permissions for authenticated cards
252
253
254
255
256
257
258
259
260
261
262
263
264
265
|
# File 'lib/a2a/rails/controller_helpers.rb', line 252
def current_user_permissions
if respond_to?(:current_user) && current_user.present?
if current_user.respond_to?(:permissions)
current_user.permissions
elsif current_user.respond_to?(:roles)
current_user.roles.map(&:name)
else
[]
end
else
[]
end
end
|
#current_user_roles ⇒ Object
423
424
425
426
427
428
429
430
431
432
433
434
435
|
# File 'lib/a2a/rails/controller_helpers.rb', line 423
def current_user_roles
if respond_to?(:current_user) && current_user.present?
if current_user.respond_to?(:roles)
current_user.roles.map(&:name)
elsif current_user.respond_to?(:role)
[current_user.role]
else
["user"]
end
else
[]
end
end
|
#documentation_url ⇒ Object
476
477
478
|
# File 'lib/a2a/rails/controller_helpers.rb', line 476
def documentation_url
"#{request.base_url}/docs/a2a"
end
|
#enhance_authenticated_card(card_data) ⇒ Object
401
402
403
404
405
406
407
408
409
410
|
# File 'lib/a2a/rails/controller_helpers.rb', line 401
def enhance_authenticated_card(card_data)
card_data.merge(
authenticated_user: current_user_info,
permissions: current_user_permissions,
authentication_context: {
strategy: self.class.a2a_auth_config[:strategy],
authenticated_at: Time.now.iso8601
}
)
end
|
#generate_agent_card(authenticated: false) ⇒ A2A::Types::AgentCard
Generate agent card for this controller
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
# File 'lib/a2a/rails/controller_helpers.rb', line 143
def generate_agent_card(authenticated: false)
config = self.class.a2a_agent_config
card_data = {
name: config[:name] || controller_name.humanize,
description: config[:description] || "A2A agent for #{controller_name}",
version: config[:version] || "1.0.0",
url: agent_card_url,
preferred_transport: "JSONRPC",
skills: collect_skills,
capabilities: collect_capabilities_hash,
default_input_modes: A2A.config.default_input_modes,
default_output_modes: A2A.config.default_output_modes,
additional_interfaces: build_additional_interfaces,
security: build_security_config,
provider: build_provider_info,
protocol_version: A2A.config.protocol_version,
supports_authenticated_extended_card: supports_authenticated_card?,
documentation_url: documentation_url,
metadata: build_agent_metadata(config)
}
card_data = enhance_authenticated_card(card_data) if authenticated && current_user_authenticated?
A2A::Types::AgentCard.new(**card_data)
end
|
#grpc_endpoint_url ⇒ Object
466
467
468
469
470
|
# File 'lib/a2a/rails/controller_helpers.rb', line 466
def grpc_endpoint_url
base_url = request.base_url.gsub(/:\d+/, ":#{grpc_port}")
"#{base_url}/a2a.grpc"
end
|
#grpc_port ⇒ Object
480
481
482
483
|
# File 'lib/a2a/rails/controller_helpers.rb', line 480
def grpc_port
rails_production? ? 443 : 50_051
end
|
#handle_a2a_error(error) ⇒ Object
486
487
488
|
# File 'lib/a2a/rails/controller_helpers.rb', line 486
def handle_a2a_error(error)
render json: build_a2a_error_response(error), status: :bad_request
end
|
#handle_a2a_rpc ⇒ Hash
Handle A2A JSON-RPC requests
This method processes incoming JSON-RPC requests and delegates them to
the appropriate A2A method handlers.
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
# File 'lib/a2a/rails/controller_helpers.rb', line 115
def handle_a2a_rpc
request_body = request.body.read
begin
json_rpc_request = A2A::Protocol::JsonRpc.parse_request(request_body)
if json_rpc_request.is_a?(Array)
responses = json_rpc_request.map { |req| handle_single_a2a_request(req) }
render json: responses
else
response = handle_single_a2a_request(json_rpc_request)
render json: response
end
rescue A2A::Errors::A2AError => e
render json: build_a2a_error_response(e), status: :bad_request
rescue StandardError => e
error = A2A::Errors::InternalError.new(e.message)
render json: build_a2a_error_response(error), status: :internal_server_error
end
end
|
#handle_authentication_error(error) ⇒ Object
494
495
496
|
# File 'lib/a2a/rails/controller_helpers.rb', line 494
def handle_authentication_error(error)
render json: build_a2a_error_response(error), status: :unauthorized
end
|
#handle_single_a2a_request(json_rpc_request) ⇒ Object
269
270
271
272
273
274
275
276
277
278
279
280
281
282
|
# File 'lib/a2a/rails/controller_helpers.rb', line 269
def handle_single_a2a_request(json_rpc_request)
if method_requires_authentication?(json_rpc_request.method) && !current_user_authenticated?
raise A2A::Errors::AuthenticationError, "Authentication required for method: #{json_rpc_request.method}"
end
handle_a2a_request(json_rpc_request)
rescue A2A::Errors::A2AError => e
build_a2a_error_response(e, json_rpc_request.id)
rescue StandardError => e
error = A2A::Errors::InternalError.new(e.message)
build_a2a_error_response(error, json_rpc_request.id)
end
|
#handle_task_not_found(error) ⇒ Object
490
491
492
|
# File 'lib/a2a/rails/controller_helpers.rb', line 490
def handle_task_not_found(error)
render json: build_a2a_error_response(error), status: :not_found
end
|
#http_json_endpoint_url ⇒ Object
472
473
474
|
# File 'lib/a2a/rails/controller_helpers.rb', line 472
def http_json_endpoint_url
"#{request.base_url}#{A2A.config.mount_path}/http"
end
|
#jwt_authenticated? ⇒ Boolean
437
438
439
440
441
442
443
444
445
446
447
448
449
450
|
# File 'lib/a2a/rails/controller_helpers.rb', line 437
def jwt_authenticated?
= request.["Authorization"]
return false unless &.start_with?("Bearer ")
token = .split.last
begin
JWT.decode(token, nil, false)
true
rescue JWT::DecodeError
false
end
end
|
#method_requires_authentication?(method_name) ⇒ Boolean
291
292
293
294
|
# File 'lib/a2a/rails/controller_helpers.rb', line 291
def method_requires_authentication?(method_name)
auth_config = self.class.a2a_auth_config
auth_config[:methods].include?(method_name.to_s)
end
|
#render_agent_card(authenticated: false, format: :json) ⇒ Object
Render agent card as JSON response
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
|
# File 'lib/a2a/rails/controller_helpers.rb', line 178
def render_agent_card(authenticated: false, format: :json)
card = generate_agent_card(authenticated: authenticated)
case format
when :json
render json: card.to_h
when :jws
render json: { error: "JWS format not yet implemented" }, status: :not_implemented
else
render json: card.to_h
end
rescue StandardError => e
render json: { error: e.message }, status: :internal_server_error
end
|
303
304
305
306
307
|
# File 'lib/a2a/rails/controller_helpers.rb', line 303
def
response.["X-A2A-Version"] = A2A::VERSION
response.["X-A2A-Protocol-Version"] = A2A.config.protocol_version
response.["Content-Type"] = "application/json"
end
|
#supports_authenticated_card? ⇒ Boolean
397
398
399
|
# File 'lib/a2a/rails/controller_helpers.rb', line 397
def supports_authenticated_card?
self.class.a2a_auth_config[:strategy] != :none
end
|