Class: Keycardai::A2A::DelegationClient

Inherits:
Object
  • Object
show all
Defined in:
lib/keycardai/a2a/delegation_client.rb

Overview

Calls another agent on the user's behalf, carrying the user's identity through the hop: discover the target's agent card, exchange the inbound user token for one scoped to the target (RFC 8693; the user stays the subject and the authorization server records this agent in the act chain), and invoke the target's JSON-RPC endpoint with the exchanged token as the bearer credential.

Instance Method Summary collapse

Constructor Details

#initialize(issuer:, credential: nil, client_id: nil, client_secret: nil, http_client: OAuth::HTTP::NetHTTPClient.new, discovery: nil, invoke_timeout: nil, protocol_version: PROTOCOL_VERSION) ⇒ DelegationClient

Returns a new instance of DelegationClient.

Parameters:

  • issuer (String) —

    the zone where exchanges are performed

  • credential (Object, nil) (defaults to: nil) —

    this agent's application credential

  • client_id (String, nil) (defaults to: nil) —

    shared-secret pair alternative

  • client_secret (String, nil) (defaults to: nil)
  • http_client (#get, #post_form, #post_json) (defaults to: OAuth::HTTP::NetHTTPClient.new) —

    pluggable transport

  • discovery (ServiceDiscovery, nil) (defaults to: nil) —

    card resolution override

  • invoke_timeout (Numeric, nil) (defaults to: nil) —

    JSON-RPC call timeout

  • protocol_version (String) (defaults to: PROTOCOL_VERSION) —

    the A2A generation to speak: PROTOCOL_VERSION (1.0: SendMessage, A2A-Version header, ROLE_USER roles, untagged text parts) or LEGACY_PROTOCOL_VERSION (0.3: message/send, X-A2A-Protocol-Version header, "user" roles, kind-tagged parts). Build the message with A2A.text_message either way; the client translates it for 0.3.

Raises:

  • (ArgumentError) —

    protocol_version is neither supported generation



33
34
35
36
37
38
39
40
41
42
43
# File 'lib/keycardai/a2a/delegation_client.rb', line 33

def initialize(issuer:, credential: nil, client_id: nil, client_secret: nil,
               http_client: OAuth::HTTP::NetHTTPClient.new, discovery: nil,
               invoke_timeout: nil, protocol_version: PROTOCOL_VERSION)
  @exchange = OAuth::TokenExchangeClient.new(issuer: issuer, credential: credential,
                                             client_id: client_id, client_secret: client_secret,
                                             http_client: http_client)
  @discovery = discovery || ServiceDiscovery.new(http_client: http_client)
  @http_client = http_client
  @invoke_timeout = invoke_timeout
  @wire = Wire.for(protocol_version)
end

Instance Method Details

#invoke(target:, subject_token:, message:) ⇒ Result

Delegate a call to another agent: discover, exchange, invoke.

Parameters:

  • target (String) —

    the downstream agent's base URL

  • subject_token (String) —

    the inbound user's verified access token

  • message (Hash) —

    the A2A SendMessage params (see A2A.text_message)

Returns:

  • (Result) —

    message is the JSON-RPC result as the agent returned it: for a 1.0 agent an object with a message or task key

Raises:

  • (ArgumentError) —

    protocol_version is unsupported

  • (DiscoveryError) —

    the agent card cannot be resolved

  • (Keycardai::OAuth::OAuthError) —

    the exchange was rejected

  • (InvocationError) —

    the JSON-RPC call failed



56
57
58
59
60
61
# File 'lib/keycardai/a2a/delegation_client.rb', line 56

def invoke(target:, subject_token:, message:)
  card = @discovery.get_card(target)
  token = @exchange.exchange_token(subject_token: subject_token, resource: target.chomp("/"))
  result = post_jsonrpc(jsonrpc_url(target, card), token.access_token, message)
  Result.new(message: result, agent_card: card)
end