a2a-rb 0.1.1

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.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +38 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +311 -0
  5. data/lib/a2a/agent_capabilities.rb +32 -0
  6. data/lib/a2a/agent_card/builder.rb +135 -0
  7. data/lib/a2a/agent_card/signature.rb +35 -0
  8. data/lib/a2a/agent_card/verifier.rb +19 -0
  9. data/lib/a2a/agent_card.rb +118 -0
  10. data/lib/a2a/agent_extension.rb +32 -0
  11. data/lib/a2a/agent_interface.rb +54 -0
  12. data/lib/a2a/agent_provider.rb +20 -0
  13. data/lib/a2a/agent_skill.rb +46 -0
  14. data/lib/a2a/artifact.rb +40 -0
  15. data/lib/a2a/client.rb +109 -0
  16. data/lib/a2a/discovery.rb +49 -0
  17. data/lib/a2a/json_rpc_envelope.rb +32 -0
  18. data/lib/a2a/message.rb +54 -0
  19. data/lib/a2a/oauth_flow/authorization_code.rb +37 -0
  20. data/lib/a2a/oauth_flow/client_credentials.rb +31 -0
  21. data/lib/a2a/oauth_flow/device_code.rb +34 -0
  22. data/lib/a2a/oauth_flow.rb +37 -0
  23. data/lib/a2a/operation/cancel_task.rb +39 -0
  24. data/lib/a2a/operation/create_task_push_notification_config.rb +38 -0
  25. data/lib/a2a/operation/delete_task_push_notification_config.rb +38 -0
  26. data/lib/a2a/operation/executable.rb +27 -0
  27. data/lib/a2a/operation/get_extended_agent_card.rb +32 -0
  28. data/lib/a2a/operation/get_task.rb +39 -0
  29. data/lib/a2a/operation/get_task_push_notification_config.rb +38 -0
  30. data/lib/a2a/operation/list_task_push_notification_configs.rb +64 -0
  31. data/lib/a2a/operation/list_tasks.rb +78 -0
  32. data/lib/a2a/operation/send_message/configuration.rb +39 -0
  33. data/lib/a2a/operation/send_message.rb +58 -0
  34. data/lib/a2a/operation/send_message_request.rb +37 -0
  35. data/lib/a2a/operation/send_streaming_message.rb +53 -0
  36. data/lib/a2a/operation/subscribe_to_task.rb +40 -0
  37. data/lib/a2a/operation.rb +19 -0
  38. data/lib/a2a/part/data.rb +34 -0
  39. data/lib/a2a/part/file.rb +45 -0
  40. data/lib/a2a/part/text.rb +34 -0
  41. data/lib/a2a/part.rb +21 -0
  42. data/lib/a2a/protocol/http_json/transport.rb +82 -0
  43. data/lib/a2a/protocol/http_json.rb +53 -0
  44. data/lib/a2a/protocol/json_rpc/transport.rb +54 -0
  45. data/lib/a2a/protocol/json_rpc.rb +55 -0
  46. data/lib/a2a/push_notification/authentication_info.rb +29 -0
  47. data/lib/a2a/push_notification/config.rb +40 -0
  48. data/lib/a2a/push_notification/dispatcher.rb +52 -0
  49. data/lib/a2a/push_notification/receiver.rb +54 -0
  50. data/lib/a2a/push_notification.rb +11 -0
  51. data/lib/a2a/role.rb +13 -0
  52. data/lib/a2a/security_requirement.rb +19 -0
  53. data/lib/a2a/security_scheme/api_key.rb +33 -0
  54. data/lib/a2a/security_scheme/http_auth.rb +33 -0
  55. data/lib/a2a/security_scheme/mutual_tls.rb +25 -0
  56. data/lib/a2a/security_scheme/oauth2.rb +52 -0
  57. data/lib/a2a/security_scheme/open_id_connect.rb +30 -0
  58. data/lib/a2a/security_scheme.rb +26 -0
  59. data/lib/a2a/streaming/artifact_update_event.rb +40 -0
  60. data/lib/a2a/streaming/response.rb +65 -0
  61. data/lib/a2a/streaming/sse_parser.rb +43 -0
  62. data/lib/a2a/streaming/sse_writer.rb +25 -0
  63. data/lib/a2a/streaming/status_update_event.rb +43 -0
  64. data/lib/a2a/streaming/subscription.rb +56 -0
  65. data/lib/a2a/streaming.rb +12 -0
  66. data/lib/a2a/task/state.rb +31 -0
  67. data/lib/a2a/task/status.rb +35 -0
  68. data/lib/a2a/task.rb +66 -0
  69. data/lib/a2a/version.rb +6 -0
  70. data/lib/a2a/versioning.rb +28 -0
  71. data/lib/a2a.rb +90 -0
  72. metadata +116 -0
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ class Part
5
+ class Data
6
+ attr_reader :data, :media_type, :filename, :metadata
7
+
8
+ def initialize(data:, media_type: nil, filename: nil, metadata: nil)
9
+ @data = data
10
+ @media_type = media_type
11
+ @filename = filename
12
+ @metadata = metadata
13
+ end
14
+
15
+ def self.from_h(hash)
16
+ new(
17
+ data: hash.fetch("data"),
18
+ media_type: hash["mediaType"],
19
+ filename: hash["filename"],
20
+ metadata: hash["metadata"]
21
+ )
22
+ end
23
+
24
+ def to_h
25
+ {
26
+ "data" => data,
27
+ "mediaType" => media_type,
28
+ "filename" => filename,
29
+ "metadata" => metadata
30
+ }.compact
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ class Part
5
+ class File
6
+ attr_reader :raw, :url, :filename, :media_type, :metadata
7
+
8
+ def initialize(raw: nil, url: nil, filename: nil, media_type: nil, metadata: nil)
9
+ @raw = raw
10
+ @url = url
11
+ @filename = filename
12
+ @media_type = media_type
13
+ @metadata = metadata
14
+ end
15
+
16
+ def self.from_h(hash)
17
+ new(
18
+ raw: hash["raw"],
19
+ url: hash["url"],
20
+ filename: hash["filename"],
21
+ media_type: hash["mediaType"],
22
+ metadata: hash["metadata"]
23
+ )
24
+ end
25
+
26
+ def to_h
27
+ {
28
+ "raw" => raw,
29
+ "url" => url,
30
+ "filename" => filename,
31
+ "mediaType" => media_type,
32
+ "metadata" => metadata
33
+ }.compact
34
+ end
35
+
36
+ def inline?
37
+ !raw.nil?
38
+ end
39
+
40
+ def remote?
41
+ !url.nil?
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ class Part
5
+ class Text
6
+ attr_reader :text, :media_type, :filename, :metadata
7
+
8
+ def initialize(text:, media_type: nil, filename: nil, metadata: nil)
9
+ @text = text
10
+ @media_type = media_type
11
+ @filename = filename
12
+ @metadata = metadata
13
+ end
14
+
15
+ def self.from_h(hash)
16
+ new(
17
+ text: hash.fetch("text"),
18
+ media_type: hash["mediaType"],
19
+ filename: hash["filename"],
20
+ metadata: hash["metadata"]
21
+ )
22
+ end
23
+
24
+ def to_h
25
+ {
26
+ "text" => text,
27
+ "mediaType" => media_type,
28
+ "filename" => filename,
29
+ "metadata" => metadata
30
+ }.compact
31
+ end
32
+ end
33
+ end
34
+ end
data/lib/a2a/part.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "part/text"
4
+ require_relative "part/data"
5
+ require_relative "part/file"
6
+
7
+ module A2A
8
+ class Part
9
+ def self.from_h(hash)
10
+ if hash.key?("text")
11
+ Part::Text.from_h(hash)
12
+ elsif hash.key?("data")
13
+ Part::Data.from_h(hash)
14
+ elsif hash.key?("raw") || hash.key?("url")
15
+ Part::File.from_h(hash)
16
+ else
17
+ raise ArgumentError, "cannot detect Part type from keys: #{hash.keys.inspect}"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+
7
+ module A2A
8
+ module Protocol
9
+ class HttpJson
10
+ class Transport
11
+ def get(url, query:, headers:)
12
+ uri = URI.parse(url)
13
+ uri.query = URI.encode_www_form(query) unless query.empty?
14
+ http = Net::HTTP.new(uri.host, uri.port)
15
+ http.use_ssl = uri.scheme == "https"
16
+ request = Net::HTTP::Get.new(uri.request_uri, headers)
17
+ response = http.request(request)
18
+ handle_response(response)
19
+ end
20
+
21
+ def post(url, body:, headers:)
22
+ uri = URI.parse(url)
23
+ http = Net::HTTP.new(uri.host, uri.port)
24
+ http.use_ssl = uri.scheme == "https"
25
+ request = Net::HTTP::Post.new(uri.request_uri, headers)
26
+ request.body = JSON.generate(body)
27
+ response = http.request(request)
28
+ handle_response(response)
29
+ end
30
+
31
+ def delete(url, headers:)
32
+ uri = URI.parse(url)
33
+ http = Net::HTTP.new(uri.host, uri.port)
34
+ http.use_ssl = uri.scheme == "https"
35
+ response = http.request(Net::HTTP::Delete.new(uri.request_uri, headers))
36
+ handle_delete_response(response)
37
+ end
38
+
39
+ def stream(url, headers:, method: :post, body: {}, query: {}, &block)
40
+ uri = URI.parse(url)
41
+ http = Net::HTTP.new(uri.host, uri.port)
42
+ http.use_ssl = uri.scheme == "https"
43
+ http.request(build_stream_request(uri, method, headers, body, query), &block)
44
+ end
45
+
46
+ private
47
+
48
+ def build_stream_request(uri, method, headers, body, query)
49
+ case method
50
+ when :post
51
+ req = Net::HTTP::Post.new(uri.request_uri, headers)
52
+ req.body = JSON.generate(body)
53
+ req
54
+ when :get
55
+ uri.query = URI.encode_www_form(query) unless query.empty?
56
+ Net::HTTP::Get.new(uri.request_uri, headers)
57
+ end
58
+ end
59
+
60
+ def handle_response(response)
61
+ case response.code.to_i
62
+ when 200..299 then JSON.parse(response.body)
63
+ when 401 then raise AuthenticationError, "HTTP 401"
64
+ when 403 then raise AuthorizationError, "HTTP 403"
65
+ when 404 then raise TaskNotFoundError, "HTTP 404"
66
+ else raise TransportError, "HTTP #{response.code}"
67
+ end
68
+ end
69
+
70
+ def handle_delete_response(response)
71
+ case response.code.to_i
72
+ when 200..299 then nil
73
+ when 401 then raise AuthenticationError, "HTTP 401"
74
+ when 403 then raise AuthorizationError, "HTTP 403"
75
+ when 404 then raise TaskNotFoundError, "HTTP 404"
76
+ else raise TransportError, "HTTP #{response.code}"
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "http_json/transport"
4
+
5
+ module A2A
6
+ module Protocol
7
+ class HttpJson
8
+ attr_reader :url, :version, :headers, :extensions
9
+
10
+ def initialize(url:, version: Versioning::CURRENT, headers: {}, extensions: [], transport: Transport.new)
11
+ @url = url.chomp("/")
12
+ @version = version
13
+ @headers = headers
14
+ @extensions = extensions
15
+ @transport = transport
16
+ @built_headers = build_headers
17
+ end
18
+
19
+ def get(path, query: {})
20
+ @transport.get("#{@url}#{path}", query: query, headers: @built_headers)
21
+ end
22
+
23
+ def post(path, body: {})
24
+ @transport.post("#{@url}#{path}", body: body, headers: @built_headers)
25
+ end
26
+
27
+ def delete(path)
28
+ @transport.delete("#{@url}#{path}", headers: @built_headers)
29
+ end
30
+
31
+ def stream(path, method: :post, body: {}, query: {}, &)
32
+ sse_headers = @built_headers.merge("Accept" => "text/event-stream")
33
+ @transport.stream("#{@url}#{path}", headers: sse_headers, method: method, body: body, query: query, &)
34
+ end
35
+
36
+ private
37
+
38
+ def build_headers
39
+ h = default_headers.merge(@headers)
40
+ h["A2A-Extensions"] = @extensions.join(", ") unless @extensions.empty?
41
+ h
42
+ end
43
+
44
+ def default_headers
45
+ {
46
+ "Content-Type" => "application/json",
47
+ "Accept" => "application/json",
48
+ "A2A-Version" => @version
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+
7
+ module A2A
8
+ module Protocol
9
+ class JsonRpc
10
+ class Transport
11
+ def post(url, body:, headers:)
12
+ uri = URI.parse(url)
13
+ http = Net::HTTP.new(uri.host, uri.port)
14
+ http.use_ssl = uri.scheme == "https"
15
+ request = Net::HTTP::Post.new(uri.request_uri, headers)
16
+ request.body = JSON.generate(body)
17
+ response = http.request(request)
18
+ handle_response(response)
19
+ end
20
+
21
+ def stream(url, headers:, method: :post, body: {}, query: {}, &block)
22
+ uri = URI.parse(url)
23
+ http = Net::HTTP.new(uri.host, uri.port)
24
+ http.use_ssl = uri.scheme == "https"
25
+ http.request(build_stream_request(uri, method, headers, body, query), &block)
26
+ end
27
+
28
+ private
29
+
30
+ def build_stream_request(uri, method, headers, body, query)
31
+ case method
32
+ when :post
33
+ req = Net::HTTP::Post.new(uri.request_uri, headers)
34
+ req.body = JSON.generate(body)
35
+ req
36
+ when :get
37
+ uri.query = URI.encode_www_form(query) unless query.empty?
38
+ Net::HTTP::Get.new(uri.request_uri, headers)
39
+ end
40
+ end
41
+
42
+ def handle_response(response)
43
+ case response.code.to_i
44
+ when 200..299 then JSON.parse(response.body)
45
+ when 401 then raise AuthenticationError, "HTTP 401"
46
+ when 403 then raise AuthorizationError, "HTTP 403"
47
+ when 404 then raise TaskNotFoundError, "HTTP 404"
48
+ else raise TransportError, "HTTP #{response.code}"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require_relative "json_rpc/transport"
5
+
6
+ module A2A
7
+ module Protocol
8
+ class JsonRpc
9
+ attr_reader :url, :version, :headers, :extensions
10
+
11
+ def initialize(url:, version: Versioning::CURRENT, headers: {}, extensions: [], transport: Transport.new)
12
+ @url = url
13
+ @version = version
14
+ @headers = headers
15
+ @extensions = extensions
16
+ @transport = transport
17
+ @built_headers = build_headers
18
+ end
19
+
20
+ def post(method, params = {})
21
+ @transport.post(@url, body: build_envelope(method, params), headers: @built_headers)
22
+ end
23
+
24
+ def stream(method, params = {}, &)
25
+ sse_headers = @built_headers.merge("Accept" => "text/event-stream")
26
+ @transport.stream(@url, headers: sse_headers, method: :post, body: build_envelope(method, params), &)
27
+ end
28
+
29
+ private
30
+
31
+ def build_envelope(method, params)
32
+ {
33
+ "jsonrpc" => "2.0",
34
+ "method" => method,
35
+ "id" => SecureRandom.uuid,
36
+ "params" => params
37
+ }
38
+ end
39
+
40
+ def build_headers
41
+ h = default_headers.merge(@headers)
42
+ h["A2A-Extensions"] = @extensions.join(", ") unless @extensions.empty?
43
+ h
44
+ end
45
+
46
+ def default_headers
47
+ {
48
+ "Content-Type" => "application/json",
49
+ "Accept" => "application/json",
50
+ "A2A-Version" => @version
51
+ }
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ module PushNotification
5
+ class AuthenticationInfo
6
+ attr_reader :scheme, :credentials
7
+
8
+ def initialize(scheme:, credentials: nil)
9
+ @scheme = scheme
10
+ @credentials = credentials
11
+ end
12
+
13
+ def self.from_h(hash)
14
+ new(scheme: hash.fetch("scheme"), credentials: hash["credentials"])
15
+ end
16
+
17
+ def to_h
18
+ {
19
+ "scheme" => scheme,
20
+ "credentials" => credentials
21
+ }.compact
22
+ end
23
+
24
+ def authorization_header
25
+ credentials ? "#{scheme} #{credentials}" : scheme
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ module PushNotification
5
+ class Config
6
+ attr_reader :id, :url, :token, :tenant, :task_id, :authentication
7
+
8
+ def initialize(url:, **kwargs)
9
+ @url = url
10
+ @id = kwargs[:id]
11
+ @token = kwargs[:token]
12
+ @tenant = kwargs[:tenant]
13
+ @task_id = kwargs[:task_id]
14
+ @authentication = kwargs[:authentication]
15
+ end
16
+
17
+ def self.from_h(hash)
18
+ new(
19
+ url: hash.fetch("url"),
20
+ id: hash["id"],
21
+ token: hash["token"],
22
+ tenant: hash["tenant"],
23
+ task_id: hash["taskId"],
24
+ authentication: hash["authentication"] && AuthenticationInfo.from_h(hash["authentication"])
25
+ )
26
+ end
27
+
28
+ def to_h
29
+ {
30
+ "url" => url,
31
+ "id" => id,
32
+ "token" => token,
33
+ "tenant" => tenant,
34
+ "taskId" => task_id,
35
+ "authentication" => authentication
36
+ }.compact
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+
7
+ module A2A
8
+ module PushNotification
9
+ class Dispatcher
10
+ def initialize(transport: Transport.new)
11
+ @transport = transport
12
+ end
13
+
14
+ def dispatch(config, event)
15
+ headers = {
16
+ "Content-Type" => "application/json",
17
+ "Accept" => "application/json"
18
+ }
19
+ headers["Authorization"] = config.authentication.authorization_header if config.authentication
20
+
21
+ @transport.post(config.url, body: event.to_h, headers: headers)
22
+ end
23
+
24
+ class Transport
25
+ def post(url, body:, headers:)
26
+ uri = URI.parse(url)
27
+ handle(http_for(uri).request(build_request(uri, body, headers)))
28
+ end
29
+
30
+ private
31
+
32
+ def handle(response)
33
+ return if (200..299).cover?(response.code.to_i)
34
+
35
+ raise TransportError, "push notification delivery failed: HTTP #{response.code}"
36
+ end
37
+
38
+ def http_for(uri)
39
+ http = Net::HTTP.new(uri.host, uri.port)
40
+ http.use_ssl = uri.scheme == "https"
41
+ http
42
+ end
43
+
44
+ def build_request(uri, body, headers)
45
+ request = Net::HTTP::Post.new(uri.request_uri, headers)
46
+ request.body = JSON.generate(body)
47
+ request
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack"
4
+ require "json"
5
+
6
+ module A2A
7
+ module PushNotification
8
+ class Receiver
9
+ def initialize(app, path: "/a2a/webhook", scheme: "Bearer", credentials: nil, &handler)
10
+ @app = app
11
+ @path = path
12
+ @scheme = scheme
13
+ @credentials = credentials
14
+ @handler = handler
15
+ end
16
+
17
+ def call(env)
18
+ request = Rack::Request.new(env)
19
+ return @app.call(env) unless request.post? && request.path == @path
20
+ return unauthorized unless authorized?(request)
21
+
22
+ handle_event(request)
23
+ end
24
+
25
+ private
26
+
27
+ def handle_event(request)
28
+ event = Streaming::Response.from_h(JSON.parse(request.body.read))
29
+ @handler.call(event)
30
+ json(200, { "ok" => true })
31
+ rescue JSON::ParserError => e
32
+ json(400, { "error" => "invalid JSON: #{e.message}" })
33
+ rescue ArgumentError => e
34
+ json(400, { "error" => e.message })
35
+ end
36
+
37
+ def authorized?(request)
38
+ return true unless @credentials
39
+
40
+ expected = "#{@scheme} #{@credentials}"
41
+ actual = request.get_header("HTTP_AUTHORIZATION").to_s
42
+ actual.casecmp(expected).zero?
43
+ end
44
+
45
+ def unauthorized
46
+ json(401, { "error" => "unauthorized" })
47
+ end
48
+
49
+ def json(status, body)
50
+ [status, { "Content-Type" => "application/json" }, [JSON.generate(body)]]
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "push_notification/authentication_info"
4
+ require_relative "push_notification/config"
5
+ require_relative "push_notification/dispatcher"
6
+ require_relative "push_notification/receiver"
7
+
8
+ module A2A
9
+ module PushNotification
10
+ end
11
+ end
data/lib/a2a/role.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ module Role
5
+ UNSPECIFIED = "ROLE_UNSPECIFIED"
6
+ USER = "ROLE_USER"
7
+ AGENT = "ROLE_AGENT"
8
+
9
+ ALL = [UNSPECIFIED, USER, AGENT].freeze
10
+
11
+ def self.valid?(value) = ALL.include?(value)
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ class SecurityRequirement
5
+ attr_reader :schemes
6
+
7
+ def initialize(schemes:)
8
+ @schemes = schemes
9
+ end
10
+
11
+ def self.from_h(hash)
12
+ new(schemes: hash.transform_values { _1.is_a?(Array) ? _1 : _1["list"] })
13
+ end
14
+
15
+ def to_h
16
+ schemes
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ module SecurityScheme
5
+ class APIKey
6
+ attr_reader :name, :location, :description
7
+
8
+ def initialize(name:, location:, description: nil)
9
+ @name = name
10
+ @location = location
11
+ @description = description
12
+ end
13
+
14
+ def self.from_h(hash)
15
+ new(
16
+ name: hash.fetch("name"),
17
+ location: hash.fetch("location"),
18
+ description: hash["description"]
19
+ )
20
+ end
21
+
22
+ def to_h
23
+ {
24
+ "apiKeySecurityScheme" => {
25
+ "name" => name,
26
+ "location" => location,
27
+ "description" => description
28
+ }.compact
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ module SecurityScheme
5
+ class HTTPAuth
6
+ attr_reader :scheme, :bearer_format, :description
7
+
8
+ def initialize(scheme:, bearer_format: nil, description: nil)
9
+ @scheme = scheme
10
+ @bearer_format = bearer_format
11
+ @description = description
12
+ end
13
+
14
+ def self.from_h(hash)
15
+ new(
16
+ scheme: hash.fetch("scheme"),
17
+ bearer_format: hash["bearerFormat"],
18
+ description: hash["description"]
19
+ )
20
+ end
21
+
22
+ def to_h
23
+ {
24
+ "httpAuthSecurityScheme" => {
25
+ "scheme" => scheme,
26
+ "bearerFormat" => bearer_format,
27
+ "description" => description
28
+ }.compact
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end