Class: A2A::Protocol::JsonRpc

Inherits:
Object
  • Object
show all
Defined in:
lib/a2a/protocol/json_rpc.rb

Overview

JSON-RPC 2.0 implementation for A2A protocol

This class provides parsing and building functionality for JSON-RPC 2.0 requests and responses, including support for batch requests and proper error handling. Optimized for performance with optional Oj JSON parser support.

Constant Summary collapse

JSONRPC_VERSION =

JSON-RPC 2.0 version string

"2.0"
PARSE_ERROR =

Standard JSON-RPC error codes

-32_700
INVALID_REQUEST =
-32_600
METHOD_NOT_FOUND =
-32_601
INVALID_PARAMS =
-32_602
INTERNAL_ERROR =
-32_603
TASK_NOT_FOUND =

A2A-specific error codes

-32_001
TASK_NOT_CANCELABLE =
-32_002
INVALID_TASK_STATE =
-32_003
AUTHENTICATION_REQUIRED =
-32_004
AUTHORIZATION_FAILED =
-32_005
RATE_LIMIT_EXCEEDED =
-32_006
AGENT_UNAVAILABLE =
-32_007
PROTOCOL_VERSION_MISMATCH =
-32_008
CAPABILITY_NOT_SUPPORTED =
-32_009
RESOURCE_EXHAUSTED =
-32_010

Class Method Summary collapse

Class Method Details

.build_batch_response(responses) ⇒ Array<Hash>

Build a JSON-RPC 2.0 batch response



138
139
140
141
# File 'lib/a2a/protocol/json_rpc.rb', line 138

def self.build_batch_response(responses)
  # Filter out notification responses (id: nil)
  responses.reject { |resp| resp[:id].nil? }
end

.build_error_response(code:, message:, id:, data: nil) ⇒ Hash

Build an error response



151
152
153
154
155
156
# File 'lib/a2a/protocol/json_rpc.rb', line 151

def self.build_error_response(code:, message:, id:, data: nil)
  error = { code: code, message: message }
  error[:data] = data if data

  build_response(error: error, id: id)
end

.build_response(id:, **kwargs) ⇒ Hash

Build a JSON-RPC 2.0 response

Raises:



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/a2a/protocol/json_rpc.rb', line 112

def self.build_response(id:, **kwargs)
  result_provided = kwargs.key?(:result)
  error_provided = kwargs.key?(:error)

  raise ArgumentError, "Cannot specify both result and error" if result_provided && error_provided
  raise ArgumentError, "Must specify either result or error" unless result_provided || error_provided

  response = {
    jsonrpc: JSONRPC_VERSION,
    id: id
  }

  if error_provided
    response[:error] = normalize_error(kwargs[:error])
  else
    response[:result] = kwargs[:result]
  end

  response
end

.generate_json(object) ⇒ String

Generate JSON string using optimized generator if available



64
65
66
67
68
69
70
# File 'lib/a2a/protocol/json_rpc.rb', line 64

def self.generate_json(object)
  if OJ_AVAILABLE
    Oj.dump(object, mode: :compat)
  else
    JSON.generate(object)
  end
end

.normalize_error(error) ⇒ Object (private)



190
191
192
193
194
195
196
197
198
# File 'lib/a2a/protocol/json_rpc.rb', line 190

private_class_method def self.normalize_error(error)
  if error.is_a?(A2A::Errors::A2AError)
    error.to_json_rpc_error
  elsif error.is_a?(Hash)
    error
  else
    { code: INTERNAL_ERROR, message: error.to_s }
  end
end

.parse_json(json_string) ⇒ Hash, Array

Parse JSON string using optimized parser if available



51
52
53
54
55
56
57
# File 'lib/a2a/protocol/json_rpc.rb', line 51

def self.parse_json(json_string)
  if OJ_AVAILABLE
    Oj.load(json_string, mode: :strict, symbol_keys: false)
  else
    JSON.parse(json_string)
  end
end

.parse_request(json_string) ⇒ Request+

Parse a JSON-RPC 2.0 request from JSON string

Raises:

  • If JSON is invalid

  • If request format is invalid



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/a2a/protocol/json_rpc.rb', line 79

def self.parse_request(json_string)
  # Performance optimization: early return for empty strings
  return nil if json_string.nil? || (json_string.respond_to?(:empty?) && json_string.empty?)

  # Ensure we have a string
  json_string = json_string.to_s unless json_string.is_a?(String)

  begin
    # Use optimized JSON parser if available
    parsed = parse_json(json_string)
  rescue JSON::ParserError, Oj::ParseError => e
    raise A2A::Errors::ParseError, "Invalid JSON: #{e.message}"
  end

  if parsed.is_a?(Array)
    # Batch request
    raise A2A::Errors::InvalidRequest, "Empty batch request" if parsed.empty?

    # Performance optimization: use map! for in-place modification
    parsed.map! { |req| parse_single_request(req) }
  else
    # Single request
    parse_single_request(parsed)
  end
end

.parse_single_request(hash) ⇒ Object (private)

Raises:



179
180
181
182
183
184
185
186
187
188
# File 'lib/a2a/protocol/json_rpc.rb', line 179

private_class_method def self.parse_single_request(hash)
  raise A2A::Errors::InvalidRequest, "Invalid request format" unless valid_request?(hash)

  Request.new(
    jsonrpc: hash["jsonrpc"],
    method: hash["method"],
    params: hash["params"] || {},
    id: hash["id"]
  )
end

.valid_request?(hash) ⇒ Boolean

Check if a hash represents a valid JSON-RPC 2.0 request



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/a2a/protocol/json_rpc.rb', line 163

def self.valid_request?(hash)
  return false unless hash.is_a?(Hash)
  return false unless hash["jsonrpc"] == JSONRPC_VERSION
  return false unless hash["method"].is_a?(String)

  # id can be string, number, or null (for notifications)
  id = hash["id"]
  return false unless id.nil? || id.is_a?(String) || id.is_a?(Integer)

  # params is optional but must be object or array if present
  params = hash["params"]
  return false if params && !params.is_a?(Hash) && !params.is_a?(Array)

  true
end