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.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +137 -0
  4. data/.simplecov +46 -0
  5. data/.yardopts +10 -0
  6. data/CHANGELOG.md +33 -0
  7. data/CODE_OF_CONDUCT.md +128 -0
  8. data/CONTRIBUTING.md +165 -0
  9. data/Gemfile +43 -0
  10. data/Guardfile +34 -0
  11. data/LICENSE.txt +21 -0
  12. data/PUBLISHING_CHECKLIST.md +214 -0
  13. data/README.md +171 -0
  14. data/Rakefile +165 -0
  15. data/docs/agent_execution.md +309 -0
  16. data/docs/api_reference.md +792 -0
  17. data/docs/configuration.md +780 -0
  18. data/docs/events.md +475 -0
  19. data/docs/getting_started.md +668 -0
  20. data/docs/integration.md +262 -0
  21. data/docs/server_apps.md +621 -0
  22. data/docs/troubleshooting.md +765 -0
  23. data/lib/a2a/client/api_methods.rb +263 -0
  24. data/lib/a2a/client/auth/api_key.rb +161 -0
  25. data/lib/a2a/client/auth/interceptor.rb +288 -0
  26. data/lib/a2a/client/auth/jwt.rb +189 -0
  27. data/lib/a2a/client/auth/oauth2.rb +146 -0
  28. data/lib/a2a/client/auth.rb +137 -0
  29. data/lib/a2a/client/base.rb +316 -0
  30. data/lib/a2a/client/config.rb +210 -0
  31. data/lib/a2a/client/connection_pool.rb +233 -0
  32. data/lib/a2a/client/http_client.rb +524 -0
  33. data/lib/a2a/client/json_rpc_handler.rb +136 -0
  34. data/lib/a2a/client/middleware/circuit_breaker_interceptor.rb +245 -0
  35. data/lib/a2a/client/middleware/logging_interceptor.rb +371 -0
  36. data/lib/a2a/client/middleware/rate_limit_interceptor.rb +142 -0
  37. data/lib/a2a/client/middleware/retry_interceptor.rb +161 -0
  38. data/lib/a2a/client/middleware.rb +116 -0
  39. data/lib/a2a/client/performance_tracker.rb +60 -0
  40. data/lib/a2a/configuration/defaults.rb +34 -0
  41. data/lib/a2a/configuration/environment_loader.rb +76 -0
  42. data/lib/a2a/configuration/file_loader.rb +115 -0
  43. data/lib/a2a/configuration/inheritance.rb +101 -0
  44. data/lib/a2a/configuration/validator.rb +180 -0
  45. data/lib/a2a/configuration.rb +201 -0
  46. data/lib/a2a/errors.rb +291 -0
  47. data/lib/a2a/modules.rb +50 -0
  48. data/lib/a2a/monitoring/alerting.rb +490 -0
  49. data/lib/a2a/monitoring/distributed_tracing.rb +398 -0
  50. data/lib/a2a/monitoring/health_endpoints.rb +204 -0
  51. data/lib/a2a/monitoring/metrics_collector.rb +438 -0
  52. data/lib/a2a/monitoring.rb +463 -0
  53. data/lib/a2a/plugin.rb +358 -0
  54. data/lib/a2a/plugin_manager.rb +159 -0
  55. data/lib/a2a/plugins/example_auth.rb +81 -0
  56. data/lib/a2a/plugins/example_middleware.rb +118 -0
  57. data/lib/a2a/plugins/example_transport.rb +76 -0
  58. data/lib/a2a/protocol/agent_card.rb +8 -0
  59. data/lib/a2a/protocol/agent_card_server.rb +584 -0
  60. data/lib/a2a/protocol/capability.rb +496 -0
  61. data/lib/a2a/protocol/json_rpc.rb +254 -0
  62. data/lib/a2a/protocol/message.rb +8 -0
  63. data/lib/a2a/protocol/task.rb +8 -0
  64. data/lib/a2a/rails/a2a_controller.rb +258 -0
  65. data/lib/a2a/rails/controller_helpers.rb +499 -0
  66. data/lib/a2a/rails/engine.rb +167 -0
  67. data/lib/a2a/rails/generators/agent_generator.rb +311 -0
  68. data/lib/a2a/rails/generators/install_generator.rb +209 -0
  69. data/lib/a2a/rails/generators/migration_generator.rb +232 -0
  70. data/lib/a2a/rails/generators/templates/add_a2a_indexes.rb +57 -0
  71. data/lib/a2a/rails/generators/templates/agent_controller.rb +122 -0
  72. data/lib/a2a/rails/generators/templates/agent_controller_spec.rb +160 -0
  73. data/lib/a2a/rails/generators/templates/agent_readme.md +200 -0
  74. data/lib/a2a/rails/generators/templates/create_a2a_push_notification_configs.rb +68 -0
  75. data/lib/a2a/rails/generators/templates/create_a2a_tasks.rb +83 -0
  76. data/lib/a2a/rails/generators/templates/example_agent_controller.rb +228 -0
  77. data/lib/a2a/rails/generators/templates/initializer.rb +108 -0
  78. data/lib/a2a/rails/generators/templates/push_notification_config_model.rb +228 -0
  79. data/lib/a2a/rails/generators/templates/task_model.rb +200 -0
  80. data/lib/a2a/rails/tasks/a2a.rake +228 -0
  81. data/lib/a2a/server/a2a_methods.rb +520 -0
  82. data/lib/a2a/server/agent.rb +537 -0
  83. data/lib/a2a/server/agent_execution/agent_executor.rb +279 -0
  84. data/lib/a2a/server/agent_execution/request_context.rb +219 -0
  85. data/lib/a2a/server/apps/rack_app.rb +311 -0
  86. data/lib/a2a/server/apps/sinatra_app.rb +261 -0
  87. data/lib/a2a/server/default_request_handler.rb +350 -0
  88. data/lib/a2a/server/events/event_consumer.rb +116 -0
  89. data/lib/a2a/server/events/event_queue.rb +226 -0
  90. data/lib/a2a/server/example_agent.rb +248 -0
  91. data/lib/a2a/server/handler.rb +281 -0
  92. data/lib/a2a/server/middleware/authentication_middleware.rb +212 -0
  93. data/lib/a2a/server/middleware/cors_middleware.rb +171 -0
  94. data/lib/a2a/server/middleware/logging_middleware.rb +362 -0
  95. data/lib/a2a/server/middleware/rate_limit_middleware.rb +382 -0
  96. data/lib/a2a/server/middleware.rb +213 -0
  97. data/lib/a2a/server/push_notification_manager.rb +327 -0
  98. data/lib/a2a/server/request_handler.rb +136 -0
  99. data/lib/a2a/server/storage/base.rb +141 -0
  100. data/lib/a2a/server/storage/database.rb +266 -0
  101. data/lib/a2a/server/storage/memory.rb +274 -0
  102. data/lib/a2a/server/storage/redis.rb +320 -0
  103. data/lib/a2a/server/storage.rb +38 -0
  104. data/lib/a2a/server/task_manager.rb +534 -0
  105. data/lib/a2a/transport/grpc.rb +481 -0
  106. data/lib/a2a/transport/http.rb +415 -0
  107. data/lib/a2a/transport/sse.rb +499 -0
  108. data/lib/a2a/types/agent_card.rb +540 -0
  109. data/lib/a2a/types/artifact.rb +99 -0
  110. data/lib/a2a/types/base_model.rb +223 -0
  111. data/lib/a2a/types/events.rb +117 -0
  112. data/lib/a2a/types/message.rb +106 -0
  113. data/lib/a2a/types/part.rb +288 -0
  114. data/lib/a2a/types/push_notification.rb +139 -0
  115. data/lib/a2a/types/security.rb +167 -0
  116. data/lib/a2a/types/task.rb +154 -0
  117. data/lib/a2a/types.rb +88 -0
  118. data/lib/a2a/utils/helpers.rb +245 -0
  119. data/lib/a2a/utils/message_buffer.rb +278 -0
  120. data/lib/a2a/utils/performance.rb +247 -0
  121. data/lib/a2a/utils/rails_detection.rb +97 -0
  122. data/lib/a2a/utils/structured_logger.rb +306 -0
  123. data/lib/a2a/utils/time_helpers.rb +167 -0
  124. data/lib/a2a/utils/validation.rb +8 -0
  125. data/lib/a2a/version.rb +6 -0
  126. data/lib/a2a-rails.rb +58 -0
  127. data/lib/a2a.rb +198 -0
  128. metadata +437 -0
@@ -0,0 +1,263 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2A
4
+ module Client
5
+ ##
6
+ # API methods for A2A HTTP client
7
+ #
8
+ module ApiMethods
9
+ ##
10
+ # Send a message to the agent
11
+ #
12
+ # @param message [Message, Hash] The message to send
13
+ # @param context [Hash, nil] Optional context information
14
+ # @return [Enumerator, Message] Stream of responses or single response
15
+ def send_message(message, context: nil)
16
+ message = ensure_message(message)
17
+
18
+ if @config.streaming?
19
+ send_streaming_message(message, context)
20
+ else
21
+ send_sync_message(message, context)
22
+ end
23
+ end
24
+
25
+ ##
26
+ # Get a task by ID
27
+ #
28
+ # @param task_id [String] The task ID
29
+ # @param context [Hash, nil] Optional context information
30
+ # @param history_length [Integer, nil] Maximum number of history messages to include
31
+ # @return [Task] The task
32
+ def get_task(task_id, context: nil, history_length: nil)
33
+ params = { id: task_id }
34
+ params[:historyLength] = history_length if history_length
35
+
36
+ request = build_json_rpc_request("tasks/get", params)
37
+ response = execute_with_middleware(request, context || {}) do |req, _ctx|
38
+ send_json_rpc_request(req)
39
+ end
40
+
41
+ ensure_task(response["result"])
42
+ end
43
+
44
+ ##
45
+ # Cancel a task
46
+ #
47
+ # @param task_id [String] The task ID to cancel
48
+ # @param context [Hash, nil] Optional context information
49
+ # @return [Task] The updated task
50
+ def cancel_task(task_id, context: nil)
51
+ request = build_json_rpc_request("tasks/cancel", { id: task_id })
52
+ response = execute_with_middleware(request, context || {}) do |req, _ctx|
53
+ send_json_rpc_request(req)
54
+ end
55
+
56
+ ensure_task(response["result"])
57
+ end
58
+
59
+ ##
60
+ # Get the agent card
61
+ #
62
+ # @param context [Hash, nil] Optional context information
63
+ # @param authenticated [Boolean] Whether to get authenticated extended card
64
+ # @return [AgentCard] The agent card
65
+ def get_card(context: nil, authenticated: false)
66
+ if authenticated
67
+ request = build_json_rpc_request("agent/getAuthenticatedExtendedCard", {})
68
+ response = execute_with_middleware(request, context || {}) do |req, _ctx|
69
+ send_json_rpc_request(req)
70
+ end
71
+ ensure_agent_card(response["result"])
72
+ else
73
+ # Use HTTP GET for basic agent card
74
+ response = execute_with_middleware({}, context || {}) do |_req, _ctx|
75
+ @connection.get("/agent-card") do |request|
76
+ request.headers.merge!(@config.all_headers)
77
+ end
78
+ end
79
+
80
+ raise A2A::Errors::HTTPError, "HTTP #{response.status}: #{response.body}" unless response.success?
81
+
82
+ ensure_agent_card(JSON.parse(response.body))
83
+
84
+ end
85
+ end
86
+
87
+ ##
88
+ # Resubscribe to a task
89
+ #
90
+ # @param task_id [String] The task ID to resubscribe to
91
+ # @param context [Hash, nil] Optional context information
92
+ # @return [Task] The task
93
+ def resubscribe(task_id, context: nil)
94
+ request = build_json_rpc_request("tasks/resubscribe", { id: task_id })
95
+ response = execute_with_middleware(request, context || {}) do |req, _ctx|
96
+ send_json_rpc_request(req)
97
+ end
98
+
99
+ ensure_task(response["result"])
100
+ end
101
+
102
+ ##
103
+ # Set a task callback (deprecated - use set_task_push_notification_config)
104
+ #
105
+ # @param task_id [String] The task ID
106
+ # @param push_notification_config [Hash] The push notification configuration
107
+ # @param context [Hash, nil] Optional context information
108
+ # @return [Hash] The response
109
+ def set_task_callback(task_id, push_notification_config, context: nil)
110
+ params = {
111
+ taskId: task_id,
112
+ pushNotificationConfig: push_notification_config
113
+ }
114
+
115
+ request = build_json_rpc_request("tasks/pushNotificationConfig/set", params)
116
+ response = execute_with_middleware(request, context || {}) do |req, _ctx|
117
+ send_json_rpc_request(req)
118
+ end
119
+
120
+ response["result"]
121
+ end
122
+
123
+ ##
124
+ # Get a task callback (deprecated - use get_task_push_notification_config)
125
+ #
126
+ # @param task_id [String] The task ID
127
+ # @param push_notification_config_id [String] The config ID
128
+ # @param context [Hash, nil] Optional context information
129
+ # @return [Hash] The configuration
130
+ def get_task_callback(task_id, push_notification_config_id, context: nil)
131
+ params = {
132
+ taskId: task_id,
133
+ pushNotificationConfigId: push_notification_config_id
134
+ }
135
+
136
+ request = build_json_rpc_request("tasks/pushNotificationConfig/get", params)
137
+ response = execute_with_middleware(request, context || {}) do |req, _ctx|
138
+ send_json_rpc_request(req)
139
+ end
140
+
141
+ response["result"]
142
+ end
143
+
144
+ ##
145
+ # List task callbacks (deprecated - use list_task_push_notification_configs)
146
+ #
147
+ # @param task_id [String] The task ID
148
+ # @param context [Hash, nil] Optional context information
149
+ # @return [Array] List of configurations
150
+ def list_task_callbacks(task_id, context: nil)
151
+ params = { taskId: task_id }
152
+
153
+ request = build_json_rpc_request("tasks/pushNotificationConfig/list", params)
154
+ response = execute_with_middleware(request, context || {}) do |req, _ctx|
155
+ send_json_rpc_request(req)
156
+ end
157
+
158
+ response["result"]
159
+ end
160
+
161
+ ##
162
+ # Delete a task callback (deprecated - use delete_task_push_notification_config)
163
+ #
164
+ # @param task_id [String] The task ID
165
+ # @param push_notification_config_id [String] The config ID
166
+ # @param context [Hash, nil] Optional context information
167
+ # @return [Boolean] Success status
168
+ def delete_task_callback(task_id, push_notification_config_id, context: nil)
169
+ params = {
170
+ taskId: task_id,
171
+ pushNotificationConfigId: push_notification_config_id
172
+ }
173
+
174
+ request = build_json_rpc_request("tasks/pushNotificationConfig/delete", params)
175
+ response = execute_with_middleware(request, context || {}) do |req, _ctx|
176
+ send_json_rpc_request(req)
177
+ end
178
+
179
+ response["result"]
180
+ end
181
+
182
+ ##
183
+ # Set a push notification config for a task
184
+ #
185
+ # @param task_id [String] The task ID
186
+ # @param config [Hash] The push notification configuration
187
+ # @param context [Hash, nil] Optional context information
188
+ # @return [Hash] The response
189
+ def set_task_push_notification_config(task_id, config, context: nil)
190
+ params = {
191
+ taskId: task_id,
192
+ pushNotificationConfig: config
193
+ }
194
+
195
+ request = build_json_rpc_request("tasks/pushNotificationConfig/set", params)
196
+ response = execute_with_middleware(request, context || {}) do |req, _ctx|
197
+ send_json_rpc_request(req)
198
+ end
199
+
200
+ response["result"]
201
+ end
202
+
203
+ ##
204
+ # Get a push notification config for a task
205
+ #
206
+ # @param task_id [String] The task ID
207
+ # @param config_id [String] The config ID
208
+ # @param context [Hash, nil] Optional context information
209
+ # @return [Hash] The configuration
210
+ def get_task_push_notification_config(task_id, config_id, context: nil)
211
+ params = {
212
+ taskId: task_id,
213
+ pushNotificationConfigId: config_id
214
+ }
215
+
216
+ request = build_json_rpc_request("tasks/pushNotificationConfig/get", params)
217
+ response = execute_with_middleware(request, context || {}) do |req, _ctx|
218
+ send_json_rpc_request(req)
219
+ end
220
+
221
+ response["result"]
222
+ end
223
+
224
+ ##
225
+ # List push notification configs for a task
226
+ #
227
+ # @param task_id [String] The task ID
228
+ # @param context [Hash, nil] Optional context information
229
+ # @return [Array] List of configurations
230
+ def list_task_push_notification_configs(task_id, context: nil)
231
+ params = { taskId: task_id }
232
+
233
+ request = build_json_rpc_request("tasks/pushNotificationConfig/list", params)
234
+ response = execute_with_middleware(request, context || {}) do |req, _ctx|
235
+ send_json_rpc_request(req)
236
+ end
237
+
238
+ response["result"]
239
+ end
240
+
241
+ ##
242
+ # Delete a push notification config for a task
243
+ #
244
+ # @param task_id [String] The task ID
245
+ # @param config_id [String] The config ID
246
+ # @param context [Hash, nil] Optional context information
247
+ # @return [Boolean] Success status
248
+ def delete_task_push_notification_config(task_id, config_id, context: nil)
249
+ params = {
250
+ taskId: task_id,
251
+ pushNotificationConfigId: config_id
252
+ }
253
+
254
+ request = build_json_rpc_request("tasks/pushNotificationConfig/delete", params)
255
+ response = execute_with_middleware(request, context || {}) do |req, _ctx|
256
+ send_json_rpc_request(req)
257
+ end
258
+
259
+ response["result"]
260
+ end
261
+ end
262
+ end
263
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # API Key authentication strategy
5
+ #
6
+ # Supports API key authentication via headers, query parameters,
7
+ # or custom locations as specified by the agent's security scheme.
8
+ #
9
+ module A2A
10
+ module Client
11
+ module Auth
12
+ class ApiKey
13
+ attr_reader :key, :value, :location, :name
14
+
15
+ # Valid locations for API key
16
+ VALID_LOCATIONS = %w[header query cookie].freeze
17
+
18
+ ##
19
+ # Initialize API key authentication
20
+ #
21
+ # @param key [String] The API key value
22
+ # @param name [String] The parameter/header name for the API key (default: 'X-API-Key')
23
+ # @param location [String] Where to place the API key: 'header', 'query', or 'cookie' (default: 'header')
24
+ def initialize(key:, name: "X-API-Key", location: "header")
25
+ @key = key
26
+ @name = name
27
+ @location = location.downcase
28
+ @value = key # Alias for consistency
29
+
30
+ validate_configuration!
31
+ end
32
+
33
+ ##
34
+ # Apply authentication to a Faraday request
35
+ #
36
+ # @param request [Faraday::Request] The request to authenticate
37
+ def apply_to_request(request)
38
+ case @location
39
+ when "header"
40
+ request.headers[@name] = @key
41
+ when "query"
42
+ # Add to query parameters
43
+ request.params[@name] = @key
44
+ when "cookie"
45
+ # Add to cookie header
46
+ existing_cookies = request.headers["Cookie"]
47
+ cookie_value = "#{@name}=#{@key}"
48
+
49
+ request.headers["Cookie"] = if existing_cookies
50
+ "#{existing_cookies}; #{cookie_value}"
51
+ else
52
+ cookie_value
53
+ end
54
+ end
55
+ end
56
+
57
+ ##
58
+ # Get the authentication header (for header-based API keys)
59
+ #
60
+ # @return [Hash] Header name and value
61
+ def authentication_header
62
+ return {} unless @location == "header"
63
+
64
+ { @name => @key }
65
+ end
66
+
67
+ ##
68
+ # Get the authentication query parameter (for query-based API keys)
69
+ #
70
+ # @return [Hash] Parameter name and value
71
+ def authentication_params
72
+ return {} unless @location == "query"
73
+
74
+ { @name => @key }
75
+ end
76
+
77
+ ##
78
+ # Check if the API key is valid (basic validation)
79
+ #
80
+ # @return [Boolean] True if key appears valid
81
+ def valid?
82
+ @key.present? && @key.is_a?(String)
83
+ end
84
+
85
+ ##
86
+ # Mask the API key for logging (shows only first and last 4 characters)
87
+ #
88
+ # @return [String] Masked API key
89
+ def masked_key
90
+ return "[empty]" if @key.nil? || (respond_to?(:empty?) && empty?) || (is_a?(String) && strip.empty?)
91
+ return @key if @key.length <= 8
92
+
93
+ "#{@key[0..3]}#{'*' * (@key.length - 8)}#{@key[-4..]}"
94
+ end
95
+
96
+ ##
97
+ # Create API key authentication from security scheme
98
+ #
99
+ # @param scheme [Hash] Security scheme definition
100
+ # @param key_value [String] The API key value
101
+ # @return [ApiKey] Configured API key authentication
102
+ def self.from_security_scheme(scheme, key_value)
103
+ location = scheme["in"] || "header"
104
+ name = scheme["name"] || "X-API-Key"
105
+
106
+ new(key: key_value, name: name, location: location)
107
+ end
108
+
109
+ ##
110
+ # Convert to hash representation
111
+ #
112
+ # @return [Hash] Configuration as hash
113
+ def to_h
114
+ {
115
+ type: "api_key",
116
+ key: masked_key,
117
+ name: @name,
118
+ location: @location
119
+ }
120
+ end
121
+
122
+ ##
123
+ # String representation (with masked key)
124
+ #
125
+ # @return [String] String representation
126
+ def to_s
127
+ "ApiKey(name=#{@name}, location=#{@location}, key=#{masked_key})"
128
+ end
129
+
130
+ ##
131
+ # Inspect representation (with masked key)
132
+ #
133
+ # @return [String] Inspect representation
134
+ def inspect
135
+ "#<A2A::Client::Auth::ApiKey:0x#{object_id.to_s(16)} #{self}>"
136
+ end
137
+
138
+ private
139
+
140
+ ##
141
+ # Validate the authentication configuration
142
+ def validate_configuration!
143
+ if @key.nil? || (respond_to?(:empty?) && empty?) || (is_a?(String) && strip.empty?)
144
+ raise ArgumentError,
145
+ "API key cannot be nil or empty"
146
+ end
147
+ raise ArgumentError, "API key must be a string" unless @key.is_a?(String)
148
+
149
+ if @name.nil? || (respond_to?(:empty?) && empty?) || (is_a?(String) && strip.empty?)
150
+ raise ArgumentError,
151
+ "Name cannot be nil or empty"
152
+ end
153
+
154
+ return if VALID_LOCATIONS.include?(@location)
155
+
156
+ raise ArgumentError, "Invalid location '#{@location}'. Must be one of: #{VALID_LOCATIONS.join(', ')}"
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end