Class: A2A::Server::Handler

Inherits:
Object
  • Object
show all
Defined in:
lib/a2a/server/handler.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(agent, middleware: []) ⇒ Handler

Initialize a new request handler

Parameters:

  • agent (Object)

    The agent instance that includes A2A::Server::Agent

  • middleware (Array) (defaults to: [])

    Array of middleware to apply



29
30
31
32
33
34
# File 'lib/a2a/server/handler.rb', line 29

def initialize(agent, middleware: [])
  @agent = agent
  @middleware_stack = middleware.dup

  validate_agent!
end

Instance Attribute Details

#agentObject (readonly)

Returns the value of attribute agent.



22
23
24
# File 'lib/a2a/server/handler.rb', line 22

def agent
  @agent
end

#middleware_stackObject (readonly)

Returns the value of attribute middleware_stack.



22
23
24
# File 'lib/a2a/server/handler.rb', line 22

def middleware_stack
  @middleware_stack
end

Instance Method Details

#add_middleware(middleware) ⇒ Object

Add middleware to the handler

Parameters:

  • middleware (Object)

    Middleware instance that responds to #call



153
154
155
# File 'lib/a2a/server/handler.rb', line 153

def add_middleware(middleware)
  @middleware_stack << middleware
end

#apply_middleware_stack(request, context) { ... } ⇒ Object (private)

Apply the middleware stack to a request

Parameters:

Yields:

  • Block to execute after middleware

Returns:

  • (Object)

    The result from the block



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/a2a/server/handler.rb', line 260

def apply_middleware_stack(request, context, &block)
  # Build the middleware chain from the outside in
  chain = block

  @middleware_stack.reverse_each do |middleware|
    current_chain = chain
    chain = lambda do
      if middleware.respond_to?(:call)
        middleware.call(request, context) { current_chain.call }
      else
        # Fallback for middleware that don't implement call
        current_chain.call
      end
    end
  end

  # Execute the chain
  chain.call
end

#capabilitiesArray<A2A::Protocol::Capability>

Get all capabilities from the agent

Returns:



195
196
197
# File 'lib/a2a/server/handler.rb', line 195

def capabilities
  @agent.class.a2a_capability_registry.all
end

#find_capability_by_method(method_name) ⇒ A2A::Protocol::Capability?

Find capability by method name

Parameters:

  • method_name (String)

    The method name

Returns:



204
205
206
# File 'lib/a2a/server/handler.rb', line 204

def find_capability_by_method(method_name)
  @agent.class.a2a_capability_registry.find_by_method(method_name).first
end

#handle_batch_request(requests, context: nil) ⇒ String

Handle a batch JSON-RPC request

Parameters:

Returns:

  • (String)

    The JSON-RPC batch response as a string



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/a2a/server/handler.rb', line 101

def handle_batch_request(requests, context: nil)
  # Process each request in the batch
  responses = requests.map do |request|
    # Apply middleware stack for each request
    apply_middleware_stack(request, context) do
      route_request(request, context: context)
    end
  rescue StandardError => e
    # Convert errors to error responses
    A2A::Errors::ErrorUtils.exception_to_json_rpc_error(e, request_id: request.id)
  end

  # Filter out nil responses (from notifications)
  batch_response = A2A::Protocol::JsonRpc.build_batch_response(responses.compact)

  # Return empty array if no responses (all notifications)
  batch_response.to_json
end

#handle_request(request_body, context: nil) ⇒ String

Handle a JSON-RPC request string

Parameters:

  • request_body (String)

    The JSON-RPC request as a string

  • context (A2A::Server::Context, nil) (defaults to: nil)

    Optional request context

Returns:

  • (String)

    The JSON-RPC response as a string



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/a2a/server/handler.rb', line 42

def handle_request(request_body, context: nil)
  A2A::Monitoring::Instrumentation.instrument_request({ method: "parse_request" }) do
    # Parse the JSON-RPC request
    parsed_request = A2A::Protocol::JsonRpc.parse_request(request_body)

    # Handle single or batch requests
    if parsed_request.is_a?(Array)
      handle_batch_request(parsed_request, context: context)
    else
      handle_single_request(parsed_request, context: context)
    end
  rescue A2A::Errors::A2AError => e
    # Return error response for A2A errors
    error_response = A2A::Protocol::JsonRpc.build_error_response(
      code: e.code,
      message: e.message,
      data: e.data,
      id: nil # Unknown ID for parse errors
    )
    error_response.to_json
  rescue StandardError => e
    # Return internal error for unexpected errors
    error_response = A2A::Protocol::JsonRpc.build_error_response(
      code: A2A::Protocol::JsonRpc::INTERNAL_ERROR,
      message: "Internal server error: #{e.message}",
      id: nil
    )
    error_response.to_json
  end
end

#handle_single_request(request, context: nil) ⇒ String

Handle a single JSON-RPC request

Parameters:

Returns:

  • (String)

    The JSON-RPC response as a string



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/a2a/server/handler.rb', line 79

def handle_single_request(request, context: nil)
  # Apply middleware stack
  response = apply_middleware_stack(request, context) do
    # Route the request to the agent
    route_request(request, context: context)
  end

  # Convert response to JSON
  if response
    response.to_json
  else
    # No response for notifications
    nil
  end
end

#method_definition(method_name) ⇒ Hash?

Get method definition

Parameters:

  • method_name (String)

    The method name

Returns:

  • (Hash, nil)

    The method definition or nil if not found



187
188
189
# File 'lib/a2a/server/handler.rb', line 187

def method_definition(method_name)
  @agent.class.a2a_method_definition(method_name)
end

#method_registered?(method_name) ⇒ Boolean

Check if a method is registered

Parameters:

  • method_name (String)

    The method name to check

Returns:

  • (Boolean)

    True if the method is registered



178
179
180
# File 'lib/a2a/server/handler.rb', line 178

def method_registered?(method_name)
  @agent.class.a2a_method_registered?(method_name)
end

#registered_methodsArray<String>

Get all registered methods from the agent

Returns:

  • (Array<String>)

    Array of method names



169
170
171
# File 'lib/a2a/server/handler.rb', line 169

def registered_methods
  @agent.class.a2a_method_registry.keys
end

#remove_middleware(middleware) ⇒ Object

Remove middleware from the handler

Parameters:

  • middleware (Object)

    Middleware instance to remove



161
162
163
# File 'lib/a2a/server/handler.rb', line 161

def remove_middleware(middleware)
  @middleware_stack.delete(middleware)
end

#route_request(request, context: nil) ⇒ Hash?

Route a request to the appropriate agent method

Parameters:

Returns:

  • (Hash, nil)

    The JSON-RPC response hash or nil for notifications



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/a2a/server/handler.rb', line 126

def route_request(request, context: nil)
  # Validate the request
  validate_request(request)

  # Create or enhance context
  request_context = context || A2A::Server::Context.new(request: request)
  request_context.instance_variable_set(:@request, request) if context

  # Check if the method exists
  unless @agent.class.a2a_method_registered?(request.method)
    raise A2A::Errors::MethodNotFound, "Method '#{request.method}' not found"
  end

  # Get method definition for validation
  @agent.class.a2a_method_definition(request.method)

  # Validate parameters against capability schema if available
  validate_method_parameters(request.method, request.params)

  # Delegate to the agent
  @agent.handle_a2a_request(request, context: request_context)
end

#validate_agent!Object (private)

Validate that the agent includes the Agent module

Raises:

  • (ArgumentError)


212
213
214
215
216
# File 'lib/a2a/server/handler.rb', line 212

def validate_agent!
  return if @agent.class.included_modules.include?(A2A::Server::Agent)

  raise ArgumentError, "Agent must include A2A::Server::Agent module"
end

#validate_method_parameters(method_name, params) ⇒ Object (private)

Validate method parameters against capability schema

Parameters:

  • method_name (String)

    The method name

  • params (Hash, Array)

    The method parameters

Raises:



242
243
244
245
246
247
248
249
250
251
# File 'lib/a2a/server/handler.rb', line 242

def validate_method_parameters(method_name, params)
  capability = find_capability_by_method(method_name)
  return unless capability # Skip validation if no capability defined

  begin
    capability.validate_input(params)
  rescue ArgumentError => e
    raise A2A::Errors::InvalidParams, "Parameter validation failed: #{e.message}"
  end
end

#validate_request(request) ⇒ Object (private)

Validate a JSON-RPC request

Parameters:

Raises:



223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/a2a/server/handler.rb', line 223

def validate_request(request)
  raise A2A::Errors::InvalidRequest, "Invalid request object" unless request.is_a?(A2A::Protocol::Request)

  if request.method.nil? || (respond_to?(:empty?) && empty?) || (is_a?(String) && strip.empty?)
    raise A2A::Errors::InvalidRequest,
          "Method name is required"
  end

  return if request.params.is_a?(Hash) || request.params.is_a?(Array)

  raise A2A::Errors::InvalidParams, "Parameters must be an object or array"
end