a2a-ruby 1.0.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/.rspec +3 -0
- data/.rubocop.yml +137 -0
- data/.simplecov +46 -0
- data/.yardopts +10 -0
- data/CHANGELOG.md +33 -0
- data/CODE_OF_CONDUCT.md +128 -0
- data/CONTRIBUTING.md +165 -0
- data/Gemfile +43 -0
- data/Guardfile +34 -0
- data/LICENSE.txt +21 -0
- data/PUBLISHING_CHECKLIST.md +214 -0
- data/README.md +171 -0
- data/Rakefile +165 -0
- data/docs/agent_execution.md +309 -0
- data/docs/api_reference.md +792 -0
- data/docs/configuration.md +780 -0
- data/docs/events.md +475 -0
- data/docs/getting_started.md +668 -0
- data/docs/integration.md +262 -0
- data/docs/server_apps.md +621 -0
- data/docs/troubleshooting.md +765 -0
- data/lib/a2a/client/api_methods.rb +263 -0
- data/lib/a2a/client/auth/api_key.rb +161 -0
- data/lib/a2a/client/auth/interceptor.rb +288 -0
- data/lib/a2a/client/auth/jwt.rb +189 -0
- data/lib/a2a/client/auth/oauth2.rb +146 -0
- data/lib/a2a/client/auth.rb +137 -0
- data/lib/a2a/client/base.rb +316 -0
- data/lib/a2a/client/config.rb +210 -0
- data/lib/a2a/client/connection_pool.rb +233 -0
- data/lib/a2a/client/http_client.rb +524 -0
- data/lib/a2a/client/json_rpc_handler.rb +136 -0
- data/lib/a2a/client/middleware/circuit_breaker_interceptor.rb +245 -0
- data/lib/a2a/client/middleware/logging_interceptor.rb +371 -0
- data/lib/a2a/client/middleware/rate_limit_interceptor.rb +142 -0
- data/lib/a2a/client/middleware/retry_interceptor.rb +161 -0
- data/lib/a2a/client/middleware.rb +116 -0
- data/lib/a2a/client/performance_tracker.rb +60 -0
- data/lib/a2a/configuration/defaults.rb +34 -0
- data/lib/a2a/configuration/environment_loader.rb +76 -0
- data/lib/a2a/configuration/file_loader.rb +115 -0
- data/lib/a2a/configuration/inheritance.rb +101 -0
- data/lib/a2a/configuration/validator.rb +180 -0
- data/lib/a2a/configuration.rb +201 -0
- data/lib/a2a/errors.rb +291 -0
- data/lib/a2a/modules.rb +50 -0
- data/lib/a2a/monitoring/alerting.rb +490 -0
- data/lib/a2a/monitoring/distributed_tracing.rb +398 -0
- data/lib/a2a/monitoring/health_endpoints.rb +204 -0
- data/lib/a2a/monitoring/metrics_collector.rb +438 -0
- data/lib/a2a/monitoring.rb +463 -0
- data/lib/a2a/plugin.rb +358 -0
- data/lib/a2a/plugin_manager.rb +159 -0
- data/lib/a2a/plugins/example_auth.rb +81 -0
- data/lib/a2a/plugins/example_middleware.rb +118 -0
- data/lib/a2a/plugins/example_transport.rb +76 -0
- data/lib/a2a/protocol/agent_card.rb +8 -0
- data/lib/a2a/protocol/agent_card_server.rb +584 -0
- data/lib/a2a/protocol/capability.rb +496 -0
- data/lib/a2a/protocol/json_rpc.rb +254 -0
- data/lib/a2a/protocol/message.rb +8 -0
- data/lib/a2a/protocol/task.rb +8 -0
- data/lib/a2a/rails/a2a_controller.rb +258 -0
- data/lib/a2a/rails/controller_helpers.rb +499 -0
- data/lib/a2a/rails/engine.rb +167 -0
- data/lib/a2a/rails/generators/agent_generator.rb +311 -0
- data/lib/a2a/rails/generators/install_generator.rb +209 -0
- data/lib/a2a/rails/generators/migration_generator.rb +232 -0
- data/lib/a2a/rails/generators/templates/add_a2a_indexes.rb +57 -0
- data/lib/a2a/rails/generators/templates/agent_controller.rb +122 -0
- data/lib/a2a/rails/generators/templates/agent_controller_spec.rb +160 -0
- data/lib/a2a/rails/generators/templates/agent_readme.md +200 -0
- data/lib/a2a/rails/generators/templates/create_a2a_push_notification_configs.rb +68 -0
- data/lib/a2a/rails/generators/templates/create_a2a_tasks.rb +83 -0
- data/lib/a2a/rails/generators/templates/example_agent_controller.rb +228 -0
- data/lib/a2a/rails/generators/templates/initializer.rb +108 -0
- data/lib/a2a/rails/generators/templates/push_notification_config_model.rb +228 -0
- data/lib/a2a/rails/generators/templates/task_model.rb +200 -0
- data/lib/a2a/rails/tasks/a2a.rake +228 -0
- data/lib/a2a/server/a2a_methods.rb +520 -0
- data/lib/a2a/server/agent.rb +537 -0
- data/lib/a2a/server/agent_execution/agent_executor.rb +279 -0
- data/lib/a2a/server/agent_execution/request_context.rb +219 -0
- data/lib/a2a/server/apps/rack_app.rb +311 -0
- data/lib/a2a/server/apps/sinatra_app.rb +261 -0
- data/lib/a2a/server/default_request_handler.rb +350 -0
- data/lib/a2a/server/events/event_consumer.rb +116 -0
- data/lib/a2a/server/events/event_queue.rb +226 -0
- data/lib/a2a/server/example_agent.rb +248 -0
- data/lib/a2a/server/handler.rb +281 -0
- data/lib/a2a/server/middleware/authentication_middleware.rb +212 -0
- data/lib/a2a/server/middleware/cors_middleware.rb +171 -0
- data/lib/a2a/server/middleware/logging_middleware.rb +362 -0
- data/lib/a2a/server/middleware/rate_limit_middleware.rb +382 -0
- data/lib/a2a/server/middleware.rb +213 -0
- data/lib/a2a/server/push_notification_manager.rb +327 -0
- data/lib/a2a/server/request_handler.rb +136 -0
- data/lib/a2a/server/storage/base.rb +141 -0
- data/lib/a2a/server/storage/database.rb +266 -0
- data/lib/a2a/server/storage/memory.rb +274 -0
- data/lib/a2a/server/storage/redis.rb +320 -0
- data/lib/a2a/server/storage.rb +38 -0
- data/lib/a2a/server/task_manager.rb +534 -0
- data/lib/a2a/transport/grpc.rb +481 -0
- data/lib/a2a/transport/http.rb +415 -0
- data/lib/a2a/transport/sse.rb +499 -0
- data/lib/a2a/types/agent_card.rb +540 -0
- data/lib/a2a/types/artifact.rb +99 -0
- data/lib/a2a/types/base_model.rb +223 -0
- data/lib/a2a/types/events.rb +117 -0
- data/lib/a2a/types/message.rb +106 -0
- data/lib/a2a/types/part.rb +288 -0
- data/lib/a2a/types/push_notification.rb +139 -0
- data/lib/a2a/types/security.rb +167 -0
- data/lib/a2a/types/task.rb +154 -0
- data/lib/a2a/types.rb +88 -0
- data/lib/a2a/utils/helpers.rb +245 -0
- data/lib/a2a/utils/message_buffer.rb +278 -0
- data/lib/a2a/utils/performance.rb +247 -0
- data/lib/a2a/utils/rails_detection.rb +97 -0
- data/lib/a2a/utils/structured_logger.rb +306 -0
- data/lib/a2a/utils/time_helpers.rb +167 -0
- data/lib/a2a/utils/validation.rb +8 -0
- data/lib/a2a/version.rb +6 -0
- data/lib/a2a-rails.rb +58 -0
- data/lib/a2a.rb +198 -0
- metadata +437 -0
@@ -0,0 +1,288 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module A2A
|
4
|
+
module Types
|
5
|
+
##
|
6
|
+
# Base class for message parts (discriminated union)
|
7
|
+
#
|
8
|
+
# Parts represent different types of content within a message.
|
9
|
+
# This is an abstract base class - use TextPart, FilePart, or DataPart.
|
10
|
+
#
|
11
|
+
class Part < A2A::Types::BaseModel
|
12
|
+
attr_reader :kind, :metadata
|
13
|
+
|
14
|
+
##
|
15
|
+
# Create a part from a hash (factory method)
|
16
|
+
#
|
17
|
+
# @param hash [Hash] The hash representation
|
18
|
+
# @return [Part] The appropriate part subclass instance
|
19
|
+
def self.from_h(hash)
|
20
|
+
return hash if hash.is_a?(Part) # Already a Part instance
|
21
|
+
return nil if hash.nil?
|
22
|
+
|
23
|
+
kind = hash[:kind] || hash["kind"]
|
24
|
+
case kind
|
25
|
+
when PART_KIND_TEXT
|
26
|
+
TextPart.from_h(hash)
|
27
|
+
when PART_KIND_FILE
|
28
|
+
FilePart.from_h(hash)
|
29
|
+
when PART_KIND_DATA
|
30
|
+
DataPart.from_h(hash)
|
31
|
+
else
|
32
|
+
raise ArgumentError, "Unknown part kind: #{kind}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def initialize(kind:, metadata: nil)
|
39
|
+
@kind = kind
|
40
|
+
@metadata = metadata
|
41
|
+
validate!
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def validate!
|
47
|
+
validate_required(:kind)
|
48
|
+
validate_inclusion(:kind, VALID_PART_KINDS)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Represents a text part in a message
|
54
|
+
#
|
55
|
+
class TextPart < Part
|
56
|
+
attr_reader :text
|
57
|
+
|
58
|
+
##
|
59
|
+
# Initialize a new text part
|
60
|
+
#
|
61
|
+
# @param text [String] The text content
|
62
|
+
# @param metadata [Hash, nil] Additional metadata
|
63
|
+
def initialize(text:, metadata: nil)
|
64
|
+
@text = text
|
65
|
+
super(kind: PART_KIND_TEXT, metadata: metadata)
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Create a TextPart from a hash
|
70
|
+
#
|
71
|
+
# @param hash [Hash] The hash representation
|
72
|
+
# @return [TextPart] The new instance
|
73
|
+
def self.from_h(hash)
|
74
|
+
return hash if hash.is_a?(TextPart)
|
75
|
+
return nil if hash.nil?
|
76
|
+
|
77
|
+
# Convert string keys to symbols and snake_case camelCase keys
|
78
|
+
normalized_hash = {}
|
79
|
+
hash.each do |key, value|
|
80
|
+
snake_key = BaseModel.underscore(key.to_s).to_sym
|
81
|
+
normalized_hash[snake_key] = value unless snake_key == :kind
|
82
|
+
end
|
83
|
+
|
84
|
+
new(**normalized_hash)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def validate!
|
90
|
+
super
|
91
|
+
validate_required(:text)
|
92
|
+
validate_type(:text, String)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Represents a file part in a message
|
98
|
+
#
|
99
|
+
class FilePart < Part
|
100
|
+
attr_reader :file
|
101
|
+
|
102
|
+
##
|
103
|
+
# Initialize a new file part
|
104
|
+
#
|
105
|
+
# @param file [FileBase] The file object
|
106
|
+
# @param metadata [Hash, nil] Additional metadata
|
107
|
+
def initialize(file:, metadata: nil)
|
108
|
+
@file = file.is_a?(Hash) ? FileBase.from_h(file) : file
|
109
|
+
super(kind: PART_KIND_FILE, metadata: metadata)
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Create a FilePart from a hash
|
114
|
+
#
|
115
|
+
# @param hash [Hash] The hash representation
|
116
|
+
# @return [FilePart] The new instance
|
117
|
+
def self.from_h(hash)
|
118
|
+
return hash if hash.is_a?(FilePart)
|
119
|
+
return nil if hash.nil?
|
120
|
+
|
121
|
+
# Convert string keys to symbols and snake_case camelCase keys
|
122
|
+
normalized_hash = {}
|
123
|
+
hash.each do |key, value|
|
124
|
+
snake_key = BaseModel.underscore(key.to_s).to_sym
|
125
|
+
normalized_hash[snake_key] = value unless snake_key == :kind
|
126
|
+
end
|
127
|
+
|
128
|
+
new(**normalized_hash)
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def validate!
|
134
|
+
super
|
135
|
+
validate_required(:file)
|
136
|
+
validate_type(:file, FileBase)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# Represents a data part in a message
|
142
|
+
#
|
143
|
+
class DataPart < Part
|
144
|
+
attr_reader :data
|
145
|
+
|
146
|
+
##
|
147
|
+
# Initialize a new data part
|
148
|
+
#
|
149
|
+
# @param data [Object] The data content (any JSON-serializable object)
|
150
|
+
# @param metadata [Hash, nil] Additional metadata
|
151
|
+
def initialize(data:, metadata: nil)
|
152
|
+
@data = data
|
153
|
+
super(kind: PART_KIND_DATA, metadata: metadata)
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Create a DataPart from a hash
|
158
|
+
#
|
159
|
+
# @param hash [Hash] The hash representation
|
160
|
+
# @return [DataPart] The new instance
|
161
|
+
def self.from_h(hash)
|
162
|
+
return hash if hash.is_a?(DataPart)
|
163
|
+
return nil if hash.nil?
|
164
|
+
|
165
|
+
# Convert string keys to symbols and snake_case camelCase keys
|
166
|
+
normalized_hash = {}
|
167
|
+
hash.each do |key, value|
|
168
|
+
snake_key = BaseModel.underscore(key.to_s).to_sym
|
169
|
+
normalized_hash[snake_key] = value unless snake_key == :kind
|
170
|
+
end
|
171
|
+
|
172
|
+
new(**normalized_hash)
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def validate!
|
178
|
+
super
|
179
|
+
validate_required(:data)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
##
|
184
|
+
# Base class for file representations
|
185
|
+
#
|
186
|
+
class FileBase < A2A::Types::BaseModel
|
187
|
+
##
|
188
|
+
# Create a file from a hash (factory method)
|
189
|
+
#
|
190
|
+
# @param hash [Hash] The hash representation
|
191
|
+
# @return [FileBase] The appropriate file subclass instance
|
192
|
+
def self.from_h(hash)
|
193
|
+
return nil if hash.nil?
|
194
|
+
|
195
|
+
if hash.key?(:bytes) || hash.key?("bytes")
|
196
|
+
FileWithBytes.from_h(hash)
|
197
|
+
elsif hash.key?(:uri) || hash.key?("uri")
|
198
|
+
FileWithUri.from_h(hash)
|
199
|
+
else
|
200
|
+
raise ArgumentError, "File must have either 'bytes' or 'uri'"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
##
|
206
|
+
# Represents a file with base64-encoded bytes
|
207
|
+
#
|
208
|
+
class FileWithBytes < FileBase
|
209
|
+
attr_reader :name, :mime_type, :bytes
|
210
|
+
|
211
|
+
##
|
212
|
+
# Initialize a new file with bytes
|
213
|
+
#
|
214
|
+
# @param name [String] The file name
|
215
|
+
# @param mime_type [String] The MIME type
|
216
|
+
# @param bytes [String] Base64-encoded file content
|
217
|
+
def initialize(name:, mime_type:, bytes:)
|
218
|
+
@name = name
|
219
|
+
@mime_type = mime_type
|
220
|
+
@bytes = bytes
|
221
|
+
validate!
|
222
|
+
end
|
223
|
+
|
224
|
+
##
|
225
|
+
# Get the decoded file content
|
226
|
+
#
|
227
|
+
# @return [String] The decoded binary content
|
228
|
+
def content
|
229
|
+
require "base64"
|
230
|
+
Base64.decode64(@bytes)
|
231
|
+
end
|
232
|
+
|
233
|
+
##
|
234
|
+
# Get the file size in bytes
|
235
|
+
#
|
236
|
+
# @return [Integer] The file size
|
237
|
+
def size
|
238
|
+
content.bytesize
|
239
|
+
end
|
240
|
+
|
241
|
+
private
|
242
|
+
|
243
|
+
def validate!
|
244
|
+
validate_required(:name, :mime_type, :bytes)
|
245
|
+
validate_type(:name, String)
|
246
|
+
validate_type(:mime_type, String)
|
247
|
+
validate_type(:bytes, String)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
##
|
252
|
+
# Represents a file with a URI reference
|
253
|
+
#
|
254
|
+
class FileWithUri < FileBase
|
255
|
+
attr_reader :name, :mime_type, :uri
|
256
|
+
|
257
|
+
##
|
258
|
+
# Initialize a new file with URI
|
259
|
+
#
|
260
|
+
# @param name [String] The file name
|
261
|
+
# @param mime_type [String] The MIME type
|
262
|
+
# @param uri [String] The file URI
|
263
|
+
def initialize(name:, mime_type:, uri:)
|
264
|
+
@name = name
|
265
|
+
@mime_type = mime_type
|
266
|
+
@uri = uri
|
267
|
+
validate!
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
def validate!
|
273
|
+
validate_required(:name, :mime_type, :uri)
|
274
|
+
validate_type(:name, String)
|
275
|
+
validate_type(:mime_type, String)
|
276
|
+
validate_type(:uri, String)
|
277
|
+
|
278
|
+
# Basic URI validation
|
279
|
+
begin
|
280
|
+
require "uri"
|
281
|
+
URI.parse(@uri)
|
282
|
+
rescue URI::InvalidURIError
|
283
|
+
raise ArgumentError, "Invalid URI: #{@uri}"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module A2A
|
4
|
+
module Types
|
5
|
+
##
|
6
|
+
# Represents a push notification configuration
|
7
|
+
#
|
8
|
+
# Push notification configs define how and where to send notifications
|
9
|
+
# about task updates and other events.
|
10
|
+
#
|
11
|
+
class PushNotificationConfig < A2A::Types::BaseModel
|
12
|
+
attr_reader :id, :url, :token, :authentication
|
13
|
+
|
14
|
+
##
|
15
|
+
# Initialize a new push notification config
|
16
|
+
#
|
17
|
+
# @param url [String] The webhook URL
|
18
|
+
# @param id [String, nil] Optional config identifier
|
19
|
+
# @param token [String, nil] Optional authentication token
|
20
|
+
# @param authentication [Hash, nil] Authentication configuration
|
21
|
+
def initialize(url:, id: nil, token: nil, authentication: nil)
|
22
|
+
@url = url
|
23
|
+
@id = id
|
24
|
+
@token = token
|
25
|
+
@authentication = authentication
|
26
|
+
|
27
|
+
validate!
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Check if authentication is configured
|
32
|
+
#
|
33
|
+
# @return [Boolean] True if authentication is present
|
34
|
+
def authenticated?
|
35
|
+
!@token.nil? || !@authentication.nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Get authentication headers for webhook requests
|
40
|
+
#
|
41
|
+
# @return [Hash] Headers to include in webhook requests
|
42
|
+
def auth_headers
|
43
|
+
headers = {}
|
44
|
+
|
45
|
+
headers["Authorization"] = "Bearer #{@token}" if @token
|
46
|
+
|
47
|
+
if @authentication.is_a?(Hash)
|
48
|
+
case @authentication["type"]
|
49
|
+
when "bearer"
|
50
|
+
headers["Authorization"] = "Bearer #{@authentication['token']}"
|
51
|
+
when "basic"
|
52
|
+
require "base64"
|
53
|
+
credentials = Base64.strict_encode64("#{@authentication['username']}:#{@authentication['password']}")
|
54
|
+
headers["Authorization"] = "Basic #{credentials}"
|
55
|
+
when "api_key"
|
56
|
+
key_name = @authentication["key_name"] || "X-API-Key"
|
57
|
+
headers[key_name] = @authentication["api_key"]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
headers
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def validate!
|
67
|
+
validate_required(:url)
|
68
|
+
validate_type(:url, String)
|
69
|
+
|
70
|
+
# Basic URL validation
|
71
|
+
begin
|
72
|
+
require "uri"
|
73
|
+
uri = URI.parse(@url)
|
74
|
+
raise ArgumentError, "URL must be HTTP or HTTPS" unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
75
|
+
rescue URI::InvalidURIError
|
76
|
+
raise ArgumentError, "Invalid URL: #{@url}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Represents a task-specific push notification configuration
|
83
|
+
#
|
84
|
+
# Links a task to a push notification configuration for receiving
|
85
|
+
# updates about that specific task.
|
86
|
+
#
|
87
|
+
class TaskPushNotificationConfig < A2A::Types::BaseModel
|
88
|
+
attr_reader :task_id, :push_notification_config
|
89
|
+
|
90
|
+
##
|
91
|
+
# Initialize a new task push notification config
|
92
|
+
#
|
93
|
+
# @param task_id [String] The task identifier
|
94
|
+
# @param push_notification_config [PushNotificationConfig, Hash] The notification config
|
95
|
+
def initialize(task_id:, push_notification_config:)
|
96
|
+
@task_id = task_id
|
97
|
+
@push_notification_config = if push_notification_config.is_a?(PushNotificationConfig)
|
98
|
+
push_notification_config
|
99
|
+
else
|
100
|
+
PushNotificationConfig.from_h(push_notification_config)
|
101
|
+
end
|
102
|
+
|
103
|
+
validate!
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Get the webhook URL
|
108
|
+
#
|
109
|
+
# @return [String] The webhook URL
|
110
|
+
def webhook_url
|
111
|
+
@push_notification_config.url
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Get authentication headers
|
116
|
+
#
|
117
|
+
# @return [Hash] Authentication headers
|
118
|
+
def auth_headers
|
119
|
+
@push_notification_config.auth_headers
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# Check if authentication is configured
|
124
|
+
#
|
125
|
+
# @return [Boolean] True if authentication is present
|
126
|
+
def authenticated?
|
127
|
+
@push_notification_config.authenticated?
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def validate!
|
133
|
+
validate_required(:task_id, :push_notification_config)
|
134
|
+
validate_type(:task_id, String)
|
135
|
+
validate_type(:push_notification_config, PushNotificationConfig)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module A2A
|
4
|
+
module Types
|
5
|
+
##
|
6
|
+
# Base class for security schemes (discriminated union)
|
7
|
+
#
|
8
|
+
# Security schemes define how authentication and authorization
|
9
|
+
# should be handled for agent interactions.
|
10
|
+
#
|
11
|
+
class SecurityScheme < A2A::Types::BaseModel
|
12
|
+
attr_reader :type
|
13
|
+
|
14
|
+
##
|
15
|
+
# Create a security scheme from a hash (factory method)
|
16
|
+
#
|
17
|
+
# @param hash [Hash] The hash representation
|
18
|
+
# @return [SecurityScheme] The appropriate security scheme subclass instance
|
19
|
+
def self.from_h(hash)
|
20
|
+
return nil if hash.nil?
|
21
|
+
|
22
|
+
type = hash[:type] || hash["type"]
|
23
|
+
case type
|
24
|
+
when SECURITY_TYPE_API_KEY
|
25
|
+
ApiKeySecurityScheme.from_h(hash)
|
26
|
+
when SECURITY_TYPE_HTTP
|
27
|
+
HttpSecurityScheme.from_h(hash)
|
28
|
+
when SECURITY_TYPE_OAUTH2
|
29
|
+
OAuth2SecurityScheme.from_h(hash)
|
30
|
+
when SECURITY_TYPE_OPENID_CONNECT
|
31
|
+
OpenIdConnectSecurityScheme.from_h(hash)
|
32
|
+
when SECURITY_TYPE_MUTUAL_TLS
|
33
|
+
MutualTlsSecurityScheme.from_h(hash)
|
34
|
+
else
|
35
|
+
raise ArgumentError, "Unknown security scheme type: #{type}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def initialize(type:)
|
42
|
+
@type = type
|
43
|
+
validate!
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def validate!
|
49
|
+
validate_required(:type)
|
50
|
+
validate_inclusion(:type, VALID_SECURITY_TYPES)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# API Key security scheme
|
56
|
+
#
|
57
|
+
class ApiKeySecurityScheme < SecurityScheme
|
58
|
+
attr_reader :name, :location
|
59
|
+
|
60
|
+
##
|
61
|
+
# Initialize a new API key security scheme
|
62
|
+
#
|
63
|
+
# @param name [String] The name of the API key parameter
|
64
|
+
# @param location [String] Where the API key is sent ("query", "header", "cookie")
|
65
|
+
def initialize(name:, location:)
|
66
|
+
@name = name
|
67
|
+
@location = location
|
68
|
+
super(type: SECURITY_TYPE_API_KEY)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def validate!
|
74
|
+
super
|
75
|
+
validate_required(:name, :location)
|
76
|
+
validate_inclusion(:location, %w[query header cookie])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# HTTP security scheme (Basic, Bearer, etc.)
|
82
|
+
#
|
83
|
+
class HttpSecurityScheme < SecurityScheme
|
84
|
+
attr_reader :scheme, :bearer_format
|
85
|
+
|
86
|
+
##
|
87
|
+
# Initialize a new HTTP security scheme
|
88
|
+
#
|
89
|
+
# @param scheme [String] The HTTP authentication scheme ("basic", "bearer", etc.)
|
90
|
+
# @param bearer_format [String, nil] Format of bearer token (e.g., "JWT")
|
91
|
+
def initialize(scheme:, bearer_format: nil)
|
92
|
+
@scheme = scheme
|
93
|
+
@bearer_format = bearer_format
|
94
|
+
super(type: SECURITY_TYPE_HTTP)
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def validate!
|
100
|
+
super
|
101
|
+
validate_required(:scheme)
|
102
|
+
validate_inclusion(:scheme, %w[basic bearer digest])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# OAuth 2.0 security scheme
|
108
|
+
#
|
109
|
+
class OAuth2SecurityScheme < SecurityScheme
|
110
|
+
attr_reader :flows, :scopes
|
111
|
+
|
112
|
+
##
|
113
|
+
# Initialize a new OAuth 2.0 security scheme
|
114
|
+
#
|
115
|
+
# @param flows [Hash] OAuth 2.0 flows configuration
|
116
|
+
# @param scopes [Hash, nil] Available scopes
|
117
|
+
def initialize(flows:, scopes: nil)
|
118
|
+
@flows = flows
|
119
|
+
@scopes = scopes
|
120
|
+
super(type: SECURITY_TYPE_OAUTH2)
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def validate!
|
126
|
+
super
|
127
|
+
validate_required(:flows)
|
128
|
+
validate_type(:flows, Hash)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
##
|
133
|
+
# OpenID Connect security scheme
|
134
|
+
#
|
135
|
+
class OpenIdConnectSecurityScheme < SecurityScheme
|
136
|
+
attr_reader :open_id_connect_url
|
137
|
+
|
138
|
+
##
|
139
|
+
# Initialize a new OpenID Connect security scheme
|
140
|
+
#
|
141
|
+
# @param open_id_connect_url [String] The OpenID Connect discovery URL
|
142
|
+
def initialize(open_id_connect_url:)
|
143
|
+
@open_id_connect_url = open_id_connect_url
|
144
|
+
super(type: SECURITY_TYPE_OPENID_CONNECT)
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def validate!
|
150
|
+
super
|
151
|
+
validate_required(:open_id_connect_url)
|
152
|
+
validate_type(:open_id_connect_url, String)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Mutual TLS security scheme
|
158
|
+
#
|
159
|
+
class MutualTlsSecurityScheme < SecurityScheme
|
160
|
+
##
|
161
|
+
# Initialize a new mutual TLS security scheme
|
162
|
+
def initialize
|
163
|
+
super(type: SECURITY_TYPE_MUTUAL_TLS)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|