keycardai-a2a 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE +9 -0
- data/README.md +78 -0
- data/lib/keycardai/a2a/delegation_client.rb +109 -0
- data/lib/keycardai/a2a/errors.rb +21 -0
- data/lib/keycardai/a2a/service_discovery.rb +90 -0
- data/lib/keycardai/a2a/version.rb +7 -0
- data/lib/keycardai/a2a.rb +30 -0
- metadata +66 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 85d721a792f798ee77ee42405e0c4222f3bceeca3c0174dfe6301e75ebbef9ee
|
|
4
|
+
data.tar.gz: fae24b8dae14b2babe8a59c77633b43752d0b43e6312340bae8fcc135fdca822
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: eb034049a79763b365a4939bfa02035ba2917fd0314777c543c7a0b7cdeeb3c7496747ad80131c8b56cdb5733beaec09cb07ee0aa3f0cb7b5e3aa8e8fed4ed70
|
|
7
|
+
data.tar.gz: 8cae6a783cba822fd0cdeb62ec3ab848469dbdcaf856f300a687021cce488356690c1c3c15929fabc83204bd91edd2f8f7a20e9de658d70d6a96e84f186381ad
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Entries from 0.2.0 onward are generated by commitizen on each bump. This first
|
|
4
|
+
one is written by hand because 0.1.0 is published from a baseline tag rather
|
|
5
|
+
than from a bump.
|
|
6
|
+
|
|
7
|
+
## 0.1.0
|
|
8
|
+
|
|
9
|
+
Initial release.
|
|
10
|
+
|
|
11
|
+
Agent-to-agent delegation: agent card discovery with caching, per-hop RFC 8693
|
|
12
|
+
token exchange that keeps the user as the subject, and JSON-RPC `message/send`
|
|
13
|
+
invocation. Wraps no A2A SDK; hosting an agent inside a framework is out of
|
|
14
|
+
scope.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Keycard Labs, inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# keycardai-a2a
|
|
2
|
+
|
|
3
|
+
Agent-to-agent delegation for Ruby agents on the Keycard platform.
|
|
4
|
+
|
|
5
|
+
> **Preview.** APIs may change between minor versions while the surface settles.
|
|
6
|
+
> Conformance against the cross-SDK contract is tracked in the
|
|
7
|
+
> [conformance report](https://github.com/keycardai/ruby-sdk/blob/main/docs/conformance-report.md).
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
bundle add keycardai-a2a
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Implements the delegation contract from
|
|
14
|
+
[keycard-sdk-spec](https://github.com/keycardai/keycard-sdk-spec)
|
|
15
|
+
(`specs/a2a/a2a-delegation.md`):
|
|
16
|
+
|
|
17
|
+
1. **Discover**: fetch and cache the target agent's card from
|
|
18
|
+
`/.well-known/agent-card.json`
|
|
19
|
+
2. **Exchange**: RFC 8693 token exchange, subject = the inbound user token,
|
|
20
|
+
authenticated by the calling agent's credential; the user stays the subject
|
|
21
|
+
and the authorization server appends the caller to the `act` chain
|
|
22
|
+
3. **Invoke**: JSON-RPC `message/send` against the target with the exchanged
|
|
23
|
+
token as the bearer credential
|
|
24
|
+
|
|
25
|
+
Hosting an agent inside a specific agent framework is out of scope, matching
|
|
26
|
+
the Go SDK's boundary. This gem wraps no A2A SDK.
|
|
27
|
+
|
|
28
|
+
## Quickstart
|
|
29
|
+
|
|
30
|
+
### Call another agent on the user's behalf
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
require "keycardai/a2a"
|
|
34
|
+
|
|
35
|
+
delegation = Keycardai::A2A::DelegationClient.new(
|
|
36
|
+
issuer: ENV.fetch("KEYCARD_URL"),
|
|
37
|
+
client_id: ENV.fetch("KEYCARD_CLIENT_ID"),
|
|
38
|
+
client_secret: ENV.fetch("KEYCARD_CLIENT_SECRET"),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
result = delegation.invoke(
|
|
42
|
+
target: "https://agent-b.example.com",
|
|
43
|
+
subject_token: inbound_user_token,
|
|
44
|
+
message: Keycardai::A2A.text_message("summarize today's incidents"),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
result.message # the JSON-RPC result from agent B
|
|
48
|
+
result.agent_card # the card that was discovered on the way
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
One call covers all three steps: fetch and cache agent B's card, exchange the
|
|
52
|
+
inbound token for one scoped to B, then send JSON-RPC `message/send` with the
|
|
53
|
+
exchanged token as the bearer credential. The user stays the subject across the
|
|
54
|
+
hop; agent B verifies a token whose `sub` is the original user, not this agent.
|
|
55
|
+
|
|
56
|
+
Failures are typed by stage, so you can tell "B is unreachable" from "the zone
|
|
57
|
+
refused the exchange":
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
begin
|
|
61
|
+
delegation.invoke(...)
|
|
62
|
+
rescue Keycardai::A2A::DiscoveryError => e # no resolvable agent card
|
|
63
|
+
rescue Keycardai::OAuth::OAuthError => e # the zone rejected the exchange
|
|
64
|
+
rescue Keycardai::A2A::InvocationError => e # B was reached and failed
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Discover a card without invoking
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
discovery = Keycardai::A2A::ServiceDiscovery.new
|
|
72
|
+
card = discovery.get_card("https://agent-b.example.com")
|
|
73
|
+
card["url"]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Cards are cached for 15 minutes by default (`cache_ttl:`). `refresh` forces a
|
|
77
|
+
fetch, `clear_cache` drops everything. Passing the same `ServiceDiscovery` into
|
|
78
|
+
`DelegationClient.new(discovery:)` shares one cache across both paths.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
|
|
6
|
+
module Keycardai
|
|
7
|
+
# The A2A delegation client and its result type.
|
|
8
|
+
module A2A
|
|
9
|
+
# The result of a delegated invocation: the target's JSON-RPC result plus
|
|
10
|
+
# the agent card it was discovered through.
|
|
11
|
+
Result = Data.define(:message, :agent_card)
|
|
12
|
+
|
|
13
|
+
# Calls another agent on the user's behalf, carrying the user's identity
|
|
14
|
+
# through the hop: discover the target's agent card, exchange the inbound
|
|
15
|
+
# user token for one scoped to the target (RFC 8693; the user stays the
|
|
16
|
+
# subject and the authorization server records this agent in the act
|
|
17
|
+
# chain), and invoke the target's JSON-RPC endpoint with the exchanged
|
|
18
|
+
# token as the bearer credential.
|
|
19
|
+
class DelegationClient
|
|
20
|
+
# @param issuer [String] the zone where exchanges are performed
|
|
21
|
+
# @param credential [Object, nil] this agent's application credential
|
|
22
|
+
# @param client_id [String, nil] shared-secret pair alternative
|
|
23
|
+
# @param client_secret [String, nil]
|
|
24
|
+
# @param http_client [#get, #post_form, #post_json] pluggable transport
|
|
25
|
+
# @param discovery [ServiceDiscovery, nil] card resolution override
|
|
26
|
+
# @param invoke_timeout [Numeric, nil] JSON-RPC call timeout
|
|
27
|
+
# @param protocol_version [String] sent as X-A2A-Protocol-Version
|
|
28
|
+
def initialize(issuer:, credential: nil, client_id: nil, client_secret: nil,
|
|
29
|
+
http_client: OAuth::HTTP::NetHTTPClient.new, discovery: nil,
|
|
30
|
+
invoke_timeout: nil, protocol_version: PROTOCOL_VERSION)
|
|
31
|
+
@exchange = OAuth::TokenExchangeClient.new(issuer: issuer, credential: credential,
|
|
32
|
+
client_id: client_id, client_secret: client_secret,
|
|
33
|
+
http_client: http_client)
|
|
34
|
+
@discovery = discovery || ServiceDiscovery.new(http_client: http_client)
|
|
35
|
+
@http_client = http_client
|
|
36
|
+
@invoke_timeout = invoke_timeout
|
|
37
|
+
@protocol_version = protocol_version
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Delegate a call to another agent: discover, exchange, invoke.
|
|
41
|
+
#
|
|
42
|
+
# @param target [String] the downstream agent's base URL
|
|
43
|
+
# @param subject_token [String] the inbound user's verified access token
|
|
44
|
+
# @param message [Hash] the A2A message/send params
|
|
45
|
+
# @return [Result]
|
|
46
|
+
# @raise [DiscoveryError] the agent card cannot be resolved
|
|
47
|
+
# @raise [Keycardai::OAuth::OAuthError] the exchange was rejected
|
|
48
|
+
# @raise [InvocationError] the JSON-RPC call failed
|
|
49
|
+
def invoke(target:, subject_token:, message:)
|
|
50
|
+
card = @discovery.get_card(target)
|
|
51
|
+
token = @exchange.exchange_token(subject_token: subject_token, resource: target.chomp("/"))
|
|
52
|
+
result = post_jsonrpc(jsonrpc_url(target, card), token.access_token, message)
|
|
53
|
+
Result.new(message: result, agent_card: card)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# The invocation endpoint: read from the card when it names one,
|
|
59
|
+
# otherwise derived by convention from the target base URL.
|
|
60
|
+
def jsonrpc_url(target, card)
|
|
61
|
+
card["url"].is_a?(String) && !card["url"].empty? ? card["url"] : "#{target.chomp("/")}#{JSONRPC_PATH}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def post_jsonrpc(url, access_token, message)
|
|
65
|
+
payload = { "jsonrpc" => "2.0", "id" => SecureRandom.uuid, "method" => MESSAGE_SEND_METHOD,
|
|
66
|
+
"params" => message }
|
|
67
|
+
headers = { "Accept" => "application/json", "Authorization" => "Bearer #{access_token}",
|
|
68
|
+
"X-A2A-Protocol-Version" => @protocol_version }
|
|
69
|
+
response = begin
|
|
70
|
+
@http_client.post_json(url, payload, headers: headers, timeout: @invoke_timeout)
|
|
71
|
+
rescue OAuth::NetworkError => e
|
|
72
|
+
raise InvocationError, "invocation of #{url} failed: #{e.message}"
|
|
73
|
+
end
|
|
74
|
+
raise InvocationError, "invocation of #{url} returned HTTP #{response.status}" unless response.success?
|
|
75
|
+
|
|
76
|
+
parse_jsonrpc(url, response.body)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def parse_jsonrpc(url, body)
|
|
80
|
+
document = begin
|
|
81
|
+
JSON.parse(body)
|
|
82
|
+
rescue JSON::ParserError
|
|
83
|
+
raise InvocationError, "invocation of #{url} returned invalid JSON"
|
|
84
|
+
end
|
|
85
|
+
if document["error"]
|
|
86
|
+
raise InvocationError.new("invocation of #{url} returned a JSON-RPC error: #{document["error"]["message"]}",
|
|
87
|
+
rpc_error: document["error"])
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
document["result"]
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Build A2A message/send params carrying one text part, the common case
|
|
95
|
+
# for driving a downstream agent.
|
|
96
|
+
#
|
|
97
|
+
# @param text [String]
|
|
98
|
+
# @return [Hash]
|
|
99
|
+
def self.text_message(text)
|
|
100
|
+
{
|
|
101
|
+
"message" => {
|
|
102
|
+
"role" => "user",
|
|
103
|
+
"parts" => [{ "kind" => "text", "text" => text }],
|
|
104
|
+
"messageId" => SecureRandom.uuid
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Keycardai
|
|
4
|
+
module A2A
|
|
5
|
+
# The target agent's card cannot be fetched, is not valid JSON, or is
|
|
6
|
+
# missing the required name.
|
|
7
|
+
class DiscoveryError < Keycardai::Error; end
|
|
8
|
+
|
|
9
|
+
# The JSON-RPC invocation returned an HTTP error or a JSON-RPC error
|
|
10
|
+
# response. Carries the JSON-RPC error payload when one was returned.
|
|
11
|
+
class InvocationError < Keycardai::Error
|
|
12
|
+
# @return [Hash, nil] the JSON-RPC error object, when present
|
|
13
|
+
attr_reader :rpc_error
|
|
14
|
+
|
|
15
|
+
def initialize(message, rpc_error: nil)
|
|
16
|
+
super(message)
|
|
17
|
+
@rpc_error = rpc_error
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Keycardai
|
|
6
|
+
module A2A
|
|
7
|
+
# Resolves an agent's base URL to its agent card, fetched from
|
|
8
|
+
# /.well-known/agent-card.json and cached (default 15 minutes,
|
|
9
|
+
# refreshable). A card must carry a name to be valid.
|
|
10
|
+
class ServiceDiscovery
|
|
11
|
+
DEFAULT_CACHE_TTL = 900
|
|
12
|
+
|
|
13
|
+
# @param http_client [#get] pluggable transport
|
|
14
|
+
# @param cache_ttl [Numeric] card cache lifetime in seconds
|
|
15
|
+
# @param timeout [Numeric, nil] fetch timeout
|
|
16
|
+
# @param clock [#call] returns the current Time; override in tests
|
|
17
|
+
def initialize(http_client: OAuth::HTTP::NetHTTPClient.new, cache_ttl: DEFAULT_CACHE_TTL,
|
|
18
|
+
timeout: nil, clock: -> { Time.now })
|
|
19
|
+
@http_client = http_client
|
|
20
|
+
@cache_ttl = cache_ttl
|
|
21
|
+
@timeout = timeout
|
|
22
|
+
@clock = clock
|
|
23
|
+
@cards = {}
|
|
24
|
+
@mutex = Mutex.new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# The agent card for a base URL, served from cache while fresh.
|
|
28
|
+
#
|
|
29
|
+
# @param base_url [String] the agent's base URL
|
|
30
|
+
# @return [Hash] the agent card
|
|
31
|
+
# @raise [DiscoveryError]
|
|
32
|
+
def get_card(base_url)
|
|
33
|
+
key = base_url.chomp("/")
|
|
34
|
+
cached = @mutex.synchronize do
|
|
35
|
+
entry = @cards[key]
|
|
36
|
+
entry[:card] if entry && @clock.call - entry[:fetched_at] <= @cache_ttl
|
|
37
|
+
end
|
|
38
|
+
return cached if cached
|
|
39
|
+
|
|
40
|
+
refresh(key)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Fetch a fresh card, replacing any cached one.
|
|
44
|
+
#
|
|
45
|
+
# @param base_url [String]
|
|
46
|
+
# @return [Hash]
|
|
47
|
+
# @raise [DiscoveryError]
|
|
48
|
+
def refresh(base_url)
|
|
49
|
+
key = base_url.chomp("/")
|
|
50
|
+
card = fetch_card(key)
|
|
51
|
+
@mutex.synchronize { @cards[key] = { card: card, fetched_at: @clock.call } }
|
|
52
|
+
card
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Drop all cached cards.
|
|
56
|
+
#
|
|
57
|
+
# @return [void]
|
|
58
|
+
def clear_cache
|
|
59
|
+
@mutex.synchronize { @cards.clear }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def fetch_card(base_url)
|
|
65
|
+
url = "#{base_url}#{AGENT_CARD_PATH}"
|
|
66
|
+
response = begin
|
|
67
|
+
@http_client.get(url, headers: { "Accept" => "application/json" }, timeout: @timeout)
|
|
68
|
+
rescue OAuth::NetworkError => e
|
|
69
|
+
raise DiscoveryError, "agent card fetch from #{url} failed: #{e.message}"
|
|
70
|
+
end
|
|
71
|
+
raise DiscoveryError, "agent card fetch from #{url} returned HTTP #{response.status}" unless response.success?
|
|
72
|
+
|
|
73
|
+
parse_card(url, response.body)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def parse_card(url, body)
|
|
77
|
+
card = begin
|
|
78
|
+
JSON.parse(body)
|
|
79
|
+
rescue JSON::ParserError
|
|
80
|
+
raise DiscoveryError, "agent card from #{url} is not valid JSON"
|
|
81
|
+
end
|
|
82
|
+
unless card.is_a?(Hash) && card["name"].is_a?(String) && !card["name"].empty?
|
|
83
|
+
raise DiscoveryError, "agent card from #{url} is missing the required name"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
card
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "keycardai/oauth"
|
|
4
|
+
require_relative "a2a/version"
|
|
5
|
+
require_relative "a2a/errors"
|
|
6
|
+
require_relative "a2a/service_discovery"
|
|
7
|
+
require_relative "a2a/delegation_client"
|
|
8
|
+
|
|
9
|
+
module Keycardai
|
|
10
|
+
# Agent-to-agent delegation: one agent calls another on the user's behalf.
|
|
11
|
+
# Discover the target's agent card, exchange the user's token for one scoped
|
|
12
|
+
# to the target (RFC 8693; the user stays the subject, the authorization
|
|
13
|
+
# server records the caller in the token's act chain), and invoke the target
|
|
14
|
+
# over JSON-RPC.
|
|
15
|
+
#
|
|
16
|
+
# Contract: https://github.com/keycardai/keycard-sdk-spec
|
|
17
|
+
module A2A
|
|
18
|
+
# Well-known path of an agent's card, relative to its base URL.
|
|
19
|
+
AGENT_CARD_PATH = "/.well-known/agent-card.json"
|
|
20
|
+
|
|
21
|
+
# JSON-RPC invocation path, relative to an agent's base URL.
|
|
22
|
+
JSONRPC_PATH = "/a2a/jsonrpc"
|
|
23
|
+
|
|
24
|
+
# JSON-RPC method used to deliver a message to an agent.
|
|
25
|
+
MESSAGE_SEND_METHOD = "message/send"
|
|
26
|
+
|
|
27
|
+
# A2A protocol version sent with each invocation.
|
|
28
|
+
PROTOCOL_VERSION = "0.3"
|
|
29
|
+
end
|
|
30
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: keycardai-a2a
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Keycard
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: keycardai-oauth
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.1.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.1.0
|
|
26
|
+
description: 'The A2A delegation contract: agent card discovery, per-hop RFC 8693
|
|
27
|
+
token exchange carrying the user''s identity, and JSON-RPC invocation. Framework
|
|
28
|
+
glue for hosting agents is out of scope.'
|
|
29
|
+
email:
|
|
30
|
+
- support@keycard.ai
|
|
31
|
+
executables: []
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- CHANGELOG.md
|
|
36
|
+
- LICENSE
|
|
37
|
+
- README.md
|
|
38
|
+
- lib/keycardai/a2a.rb
|
|
39
|
+
- lib/keycardai/a2a/delegation_client.rb
|
|
40
|
+
- lib/keycardai/a2a/errors.rb
|
|
41
|
+
- lib/keycardai/a2a/service_discovery.rb
|
|
42
|
+
- lib/keycardai/a2a/version.rb
|
|
43
|
+
homepage: https://github.com/keycardai/ruby-sdk
|
|
44
|
+
licenses:
|
|
45
|
+
- MIT
|
|
46
|
+
metadata:
|
|
47
|
+
rubygems_mfa_required: 'true'
|
|
48
|
+
source_code_uri: https://github.com/keycardai/ruby-sdk/tree/main/a2a
|
|
49
|
+
rdoc_options: []
|
|
50
|
+
require_paths:
|
|
51
|
+
- lib
|
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '3.2'
|
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
requirements: []
|
|
63
|
+
rubygems_version: 3.6.9
|
|
64
|
+
specification_version: 4
|
|
65
|
+
summary: Agent-to-agent delegation with Keycard
|
|
66
|
+
test_files: []
|