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,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
require "erb"
|
5
|
+
|
6
|
+
module A2A
|
7
|
+
class Configuration
|
8
|
+
# Module for loading configuration from YAML files
|
9
|
+
module FileLoader
|
10
|
+
# Load configuration from YAML file
|
11
|
+
# @param file_path [String] Path to YAML configuration file
|
12
|
+
# @param environment [String, nil] Environment section to load
|
13
|
+
# @return [self]
|
14
|
+
def load_from_file(file_path, environment: nil)
|
15
|
+
@config_file = file_path
|
16
|
+
environment ||= @environment
|
17
|
+
|
18
|
+
# Validate file exists and is readable
|
19
|
+
raise A2A::Errors::ConfigurationError, "Configuration file not found: #{file_path}" unless File.exist?(file_path)
|
20
|
+
|
21
|
+
raise A2A::Errors::ConfigurationError, "Configuration file not readable: #{file_path}" unless File.readable?(file_path)
|
22
|
+
|
23
|
+
begin
|
24
|
+
content = File.read(file_path)
|
25
|
+
|
26
|
+
# Process ERB if the file contains ERB syntax
|
27
|
+
erb_content = if content.include?("<%") || content.include?("%>")
|
28
|
+
ERB.new(content).result
|
29
|
+
else
|
30
|
+
content
|
31
|
+
end
|
32
|
+
|
33
|
+
# Parse YAML with proper error handling
|
34
|
+
config_data = YAML.safe_load(erb_content, aliases: true, symbolize_names: false) || {}
|
35
|
+
|
36
|
+
unless config_data.is_a?(Hash)
|
37
|
+
raise A2A::Errors::ConfigurationError,
|
38
|
+
"Configuration file must contain a hash/dictionary at root level"
|
39
|
+
end
|
40
|
+
rescue Psych::SyntaxError => e
|
41
|
+
raise A2A::Errors::ConfigurationError,
|
42
|
+
"Invalid YAML syntax in configuration file #{file_path}: #{e.message}"
|
43
|
+
rescue StandardError => e
|
44
|
+
raise A2A::Errors::ConfigurationError,
|
45
|
+
"Failed to load configuration file #{file_path}: #{e.message}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Load environment-specific configuration
|
49
|
+
env_config = config_data[environment.to_s] || config_data[environment.to_sym] || {}
|
50
|
+
|
51
|
+
unless env_config.is_a?(Hash)
|
52
|
+
raise A2A::Errors::ConfigurationError,
|
53
|
+
"Environment configuration for '#{environment}' must be a hash/dictionary"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Apply configuration from file with validation
|
57
|
+
begin
|
58
|
+
apply_hash_config(env_config)
|
59
|
+
rescue StandardError => e
|
60
|
+
raise A2A::Errors::ConfigurationError,
|
61
|
+
"Failed to apply configuration from file #{file_path}: #{e.message}"
|
62
|
+
end
|
63
|
+
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# Apply configuration from hash
|
70
|
+
def apply_hash_config(config_hash)
|
71
|
+
unknown_keys = []
|
72
|
+
|
73
|
+
config_hash.each do |key, value|
|
74
|
+
key_str = key.to_s
|
75
|
+
setter = "#{key_str}="
|
76
|
+
|
77
|
+
if respond_to?(setter)
|
78
|
+
begin
|
79
|
+
# Special handling for log_level to normalize from string to symbol
|
80
|
+
if key_str == "log_level" && value.is_a?(String)
|
81
|
+
normalized_value = value.downcase
|
82
|
+
valid_levels = %w[debug info warn error fatal]
|
83
|
+
value = normalized_value.to_sym if valid_levels.include?(normalized_value)
|
84
|
+
end
|
85
|
+
send(setter, value)
|
86
|
+
rescue StandardError => e
|
87
|
+
raise A2A::Errors::ConfigurationError,
|
88
|
+
"Failed to set #{key_str}: #{e.message}"
|
89
|
+
end
|
90
|
+
else
|
91
|
+
# Handle nested configurations
|
92
|
+
case key_str
|
93
|
+
when "redis_config", "redis"
|
94
|
+
if value.is_a?(Hash)
|
95
|
+
@redis_config = value.transform_keys(&:to_sym)
|
96
|
+
elsif value.is_a?(String)
|
97
|
+
@redis_config = { url: value }
|
98
|
+
else
|
99
|
+
raise A2A::Errors::ConfigurationError,
|
100
|
+
"redis_config must be a hash or URL string, got: #{value.class}"
|
101
|
+
end
|
102
|
+
else
|
103
|
+
unknown_keys << key_str
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Warn about unknown keys but don't fail
|
109
|
+
return if unknown_keys.empty?
|
110
|
+
|
111
|
+
logger&.warn("Unknown configuration keys: #{unknown_keys.join(', ')}")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module A2A
|
4
|
+
class Configuration
|
5
|
+
# Module for configuration inheritance support
|
6
|
+
module Inheritance
|
7
|
+
# Create a child configuration with inheritance
|
8
|
+
# @param **overrides [Hash] Configuration overrides
|
9
|
+
# @return [Configuration] New configuration instance
|
10
|
+
def child(**overrides)
|
11
|
+
# Create new instance without loading defaults to avoid overwriting parent values
|
12
|
+
child_config = self.class.allocate
|
13
|
+
child_config.instance_variable_set(:@environment, @environment)
|
14
|
+
child_config.instance_variable_set(:@parent_config, self)
|
15
|
+
child_config.instance_variable_set(:@overrides, overrides)
|
16
|
+
child_config.instance_variable_set(:@config_file, nil)
|
17
|
+
|
18
|
+
# Copy all instance variables from parent to child (deep copy for complex objects)
|
19
|
+
instance_variables.each do |var|
|
20
|
+
next if %i[@parent_config @overrides @config_file].include?(var)
|
21
|
+
|
22
|
+
value = instance_variable_get(var)
|
23
|
+
# Deep copy arrays and hashes to prevent shared references
|
24
|
+
copied_value = case value
|
25
|
+
when Array
|
26
|
+
value.dup
|
27
|
+
when Hash
|
28
|
+
value.dup
|
29
|
+
else
|
30
|
+
value
|
31
|
+
end
|
32
|
+
child_config.instance_variable_set(var, copied_value)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Apply overrides after copying parent values
|
36
|
+
child_config.send(:apply_overrides, overrides)
|
37
|
+
|
38
|
+
child_config
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get configuration value with inheritance support
|
42
|
+
# @param key [Symbol, String] Configuration key
|
43
|
+
# @return [Object] Configuration value
|
44
|
+
def get(key)
|
45
|
+
# Convert key to instance variable name
|
46
|
+
ivar_name = "@#{key}"
|
47
|
+
|
48
|
+
# Check if we have the instance variable set
|
49
|
+
if instance_variable_defined?(ivar_name)
|
50
|
+
value = instance_variable_get(ivar_name)
|
51
|
+
# Return value if it's not nil, or if we don't have a parent
|
52
|
+
return value unless value.nil? && @parent_config
|
53
|
+
end
|
54
|
+
|
55
|
+
# Fall back to parent configuration if value is nil and parent exists
|
56
|
+
@parent_config&.get(key)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Validate that child configuration properly inherits from parent
|
60
|
+
# @return [Boolean] true if inheritance is working correctly
|
61
|
+
def validate_inheritance!
|
62
|
+
return true unless @parent_config
|
63
|
+
|
64
|
+
# Check that we can access parent values when child values are nil
|
65
|
+
parent_attrs = @parent_config.to_h
|
66
|
+
parent_attrs.each do |key, parent_value|
|
67
|
+
next if parent_value.nil?
|
68
|
+
|
69
|
+
# Get child value directly (not through inheritance)
|
70
|
+
child_ivar = "@#{key}"
|
71
|
+
child_value = instance_variable_defined?(child_ivar) ? instance_variable_get(child_ivar) : nil
|
72
|
+
|
73
|
+
# If child value is nil, get should return parent value
|
74
|
+
next unless child_value.nil?
|
75
|
+
|
76
|
+
inherited_value = get(key)
|
77
|
+
unless inherited_value == parent_value
|
78
|
+
raise A2A::Errors::ConfigurationError,
|
79
|
+
"Inheritance failed for #{key}: expected #{parent_value.inspect}, got #{inherited_value.inspect}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
# Merge configuration from another configuration object
|
87
|
+
# @param other [Configuration] Configuration to merge from
|
88
|
+
# @return [self]
|
89
|
+
def merge!(other)
|
90
|
+
return self unless other.is_a?(A2A::Configuration)
|
91
|
+
|
92
|
+
other.to_h.each do |key, value|
|
93
|
+
setter = "#{key}="
|
94
|
+
send(setter, value) if respond_to?(setter)
|
95
|
+
end
|
96
|
+
|
97
|
+
self
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module A2A
|
4
|
+
class Configuration
|
5
|
+
# Module for configuration validation
|
6
|
+
module Validator
|
7
|
+
# Validate the configuration
|
8
|
+
# @raise [A2A::Errors::ConfigurationError] if configuration is invalid
|
9
|
+
def validate!
|
10
|
+
errors = []
|
11
|
+
|
12
|
+
begin
|
13
|
+
validate_basic_config
|
14
|
+
rescue A2A::Errors::ConfigurationError => e
|
15
|
+
errors << e.message
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
validate_transport_config
|
20
|
+
rescue A2A::Errors::ConfigurationError => e
|
21
|
+
errors << e.message
|
22
|
+
end
|
23
|
+
|
24
|
+
if rails_integration
|
25
|
+
begin
|
26
|
+
validate_rails_config
|
27
|
+
rescue A2A::Errors::ConfigurationError => e
|
28
|
+
errors << e.message
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
begin
|
33
|
+
validate_redis_config
|
34
|
+
rescue A2A::Errors::ConfigurationError => e
|
35
|
+
errors << e.message
|
36
|
+
end
|
37
|
+
|
38
|
+
begin
|
39
|
+
validate_environment_config
|
40
|
+
rescue A2A::Errors::ConfigurationError => e
|
41
|
+
errors << e.message
|
42
|
+
end
|
43
|
+
|
44
|
+
begin
|
45
|
+
validate_timeout_values
|
46
|
+
rescue A2A::Errors::ConfigurationError => e
|
47
|
+
errors << e.message
|
48
|
+
end
|
49
|
+
|
50
|
+
begin
|
51
|
+
validate_boolean_options
|
52
|
+
rescue A2A::Errors::ConfigurationError => e
|
53
|
+
errors << e.message
|
54
|
+
end
|
55
|
+
|
56
|
+
unless errors.empty?
|
57
|
+
raise A2A::Errors::ConfigurationError,
|
58
|
+
"Configuration validation failed:\n - #{errors.join("\n - ")}"
|
59
|
+
end
|
60
|
+
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# Validation methods
|
67
|
+
def validate_basic_config
|
68
|
+
raise A2A::Errors::ConfigurationError, "default_timeout must be positive" if default_timeout <= 0
|
69
|
+
|
70
|
+
validate_log_level
|
71
|
+
validate_protocol_version
|
72
|
+
end
|
73
|
+
|
74
|
+
# Validate log level with proper string/symbol handling
|
75
|
+
def validate_log_level
|
76
|
+
return if log_level.nil?
|
77
|
+
|
78
|
+
# Accept both string and symbol log levels, normalize to symbol
|
79
|
+
normalized_level = log_level.to_s.downcase
|
80
|
+
valid_levels = %w[debug info warn error fatal]
|
81
|
+
|
82
|
+
unless valid_levels.include?(normalized_level)
|
83
|
+
raise A2A::Errors::ConfigurationError,
|
84
|
+
"log_level must be one of: #{valid_levels.join(', ')}. Got: #{log_level.inspect}"
|
85
|
+
end
|
86
|
+
|
87
|
+
# Normalize to symbol for internal use
|
88
|
+
@log_level = normalized_level.to_sym
|
89
|
+
end
|
90
|
+
|
91
|
+
# Validate protocol version with proper nil/blank handling
|
92
|
+
def validate_protocol_version
|
93
|
+
return if protocol_version.nil?
|
94
|
+
|
95
|
+
if protocol_version.respond_to?(:strip) && protocol_version.strip.empty?
|
96
|
+
raise A2A::Errors::ConfigurationError,
|
97
|
+
"protocol_version cannot be blank"
|
98
|
+
elsif protocol_version.respond_to?(:empty?) && protocol_version.empty?
|
99
|
+
raise A2A::Errors::ConfigurationError,
|
100
|
+
"protocol_version cannot be empty"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def validate_transport_config
|
105
|
+
valid_transports = %w[JSONRPC GRPC HTTP+JSON]
|
106
|
+
unless valid_transports.include?(default_transport)
|
107
|
+
raise A2A::Errors::ConfigurationError, "default_transport must be one of: #{valid_transports.join(', ')}"
|
108
|
+
end
|
109
|
+
|
110
|
+
unless default_input_modes.is_a?(Array) && default_input_modes.all?(String)
|
111
|
+
raise A2A::Errors::ConfigurationError, "default_input_modes must be an array of strings"
|
112
|
+
end
|
113
|
+
|
114
|
+
return if default_output_modes.is_a?(Array) && default_output_modes.all?(String)
|
115
|
+
|
116
|
+
raise A2A::Errors::ConfigurationError, "default_output_modes must be an array of strings"
|
117
|
+
end
|
118
|
+
|
119
|
+
def validate_rails_config
|
120
|
+
raise A2A::Errors::ConfigurationError, "mount_path must start with '/'" if mount_path && !mount_path.start_with?("/")
|
121
|
+
|
122
|
+
# Only validate Rails version if Rails is available and version checking is needed
|
123
|
+
return unless rails_available? && rails_version_requires_validation?
|
124
|
+
|
125
|
+
current_version = rails_version
|
126
|
+
raise A2A::Errors::ConfigurationError,
|
127
|
+
"Rails integration requires Rails 6.0 or higher. Current version: #{current_version}"
|
128
|
+
end
|
129
|
+
|
130
|
+
def validate_redis_config
|
131
|
+
raise A2A::Errors::ConfigurationError, "redis_config must be a hash" if redis_config && !redis_config.is_a?(Hash)
|
132
|
+
|
133
|
+
return unless redis_config && redis_config[:url] && !redis_config[:url].is_a?(String)
|
134
|
+
|
135
|
+
raise A2A::Errors::ConfigurationError, "redis_config[:url] must be a string"
|
136
|
+
end
|
137
|
+
|
138
|
+
def validate_environment_config
|
139
|
+
return if environment.nil?
|
140
|
+
|
141
|
+
raise A2A::Errors::ConfigurationError, "environment must be a string" unless environment.is_a?(String)
|
142
|
+
|
143
|
+
valid_environments = %w[development test production staging]
|
144
|
+
return if valid_environments.include?(environment)
|
145
|
+
|
146
|
+
logger&.warn("Unknown environment: #{environment}. Valid environments: #{valid_environments.join(', ')}")
|
147
|
+
end
|
148
|
+
|
149
|
+
def validate_timeout_values
|
150
|
+
return unless default_timeout && (!default_timeout.is_a?(Numeric) || default_timeout <= 0)
|
151
|
+
|
152
|
+
raise A2A::Errors::ConfigurationError,
|
153
|
+
"default_timeout must be a positive number, got: #{default_timeout.inspect}"
|
154
|
+
end
|
155
|
+
|
156
|
+
def validate_boolean_options
|
157
|
+
boolean_options = {
|
158
|
+
streaming_enabled: streaming_enabled,
|
159
|
+
push_notifications_enabled: push_notifications_enabled,
|
160
|
+
rails_integration: rails_integration,
|
161
|
+
auto_mount: auto_mount,
|
162
|
+
middleware_enabled: middleware_enabled,
|
163
|
+
authentication_required: authentication_required,
|
164
|
+
cors_enabled: cors_enabled,
|
165
|
+
rate_limiting_enabled: rate_limiting_enabled,
|
166
|
+
logging_enabled: logging_enabled,
|
167
|
+
webhook_authentication_required: webhook_authentication_required
|
168
|
+
}
|
169
|
+
|
170
|
+
boolean_options.each do |option, value|
|
171
|
+
next if value.nil?
|
172
|
+
next if [true, false].include?(value)
|
173
|
+
|
174
|
+
raise A2A::Errors::ConfigurationError,
|
175
|
+
"#{option} must be true or false, got: #{value.inspect}"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "utils/rails_detection"
|
4
|
+
require_relative "configuration/defaults"
|
5
|
+
require_relative "configuration/environment_loader"
|
6
|
+
require_relative "configuration/file_loader"
|
7
|
+
require_relative "configuration/validator"
|
8
|
+
require_relative "configuration/inheritance"
|
9
|
+
|
10
|
+
##
|
11
|
+
# Global configuration for the A2A Ruby SDK
|
12
|
+
#
|
13
|
+
# Supports environment variables, per-environment configuration,
|
14
|
+
# configuration inheritance, and validation.
|
15
|
+
#
|
16
|
+
# @example Configure the SDK programmatically
|
17
|
+
# A2A.configure do |config|
|
18
|
+
# config.default_timeout = 30
|
19
|
+
# config.log_level = :debug
|
20
|
+
# config.protocol_version = "0.3.0"
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @example Load from environment variables
|
24
|
+
# # Set A2A_DEFAULT_TIMEOUT=60 in environment
|
25
|
+
# config = A2A::Configuration.new
|
26
|
+
# config.default_timeout # => 60
|
27
|
+
#
|
28
|
+
# @example Load from YAML file
|
29
|
+
# A2A.configure_from_file('config/a2a.yml')
|
30
|
+
#
|
31
|
+
module A2A
|
32
|
+
class Configuration
|
33
|
+
include A2A::Utils::RailsDetection
|
34
|
+
include Defaults
|
35
|
+
include EnvironmentLoader
|
36
|
+
include FileLoader
|
37
|
+
include Validator
|
38
|
+
include Inheritance
|
39
|
+
|
40
|
+
# Default timeout for HTTP requests in seconds
|
41
|
+
# @return [Integer]
|
42
|
+
attr_accessor :default_timeout
|
43
|
+
|
44
|
+
# Default log level
|
45
|
+
# @return [Symbol]
|
46
|
+
attr_accessor :log_level
|
47
|
+
|
48
|
+
# A2A protocol version to use
|
49
|
+
# @return [String]
|
50
|
+
attr_accessor :protocol_version
|
51
|
+
|
52
|
+
# Default transport protocol
|
53
|
+
# @return [String]
|
54
|
+
attr_accessor :default_transport
|
55
|
+
|
56
|
+
# Enable/disable streaming support
|
57
|
+
# @return [Boolean]
|
58
|
+
attr_accessor :streaming_enabled
|
59
|
+
|
60
|
+
# Enable/disable push notifications
|
61
|
+
# @return [Boolean]
|
62
|
+
attr_accessor :push_notifications_enabled
|
63
|
+
|
64
|
+
# Default input MIME types
|
65
|
+
# @return [Array<String>]
|
66
|
+
attr_accessor :default_input_modes
|
67
|
+
|
68
|
+
# Default output MIME types
|
69
|
+
# @return [Array<String>]
|
70
|
+
attr_accessor :default_output_modes
|
71
|
+
|
72
|
+
# Redis configuration for task storage and rate limiting
|
73
|
+
# @return [Hash]
|
74
|
+
attr_accessor :redis_config
|
75
|
+
|
76
|
+
# Enable Rails integration
|
77
|
+
# @return [Boolean]
|
78
|
+
attr_accessor :rails_integration
|
79
|
+
|
80
|
+
# Rails mount path for A2A endpoints
|
81
|
+
# @return [String]
|
82
|
+
attr_accessor :mount_path
|
83
|
+
|
84
|
+
# Enable automatic mounting of A2A routes
|
85
|
+
# @return [Boolean]
|
86
|
+
attr_accessor :auto_mount
|
87
|
+
|
88
|
+
# Enable A2A middleware stack
|
89
|
+
# @return [Boolean]
|
90
|
+
attr_accessor :middleware_enabled
|
91
|
+
|
92
|
+
# Require authentication for A2A endpoints
|
93
|
+
# @return [Boolean]
|
94
|
+
attr_accessor :authentication_required
|
95
|
+
|
96
|
+
# Enable CORS middleware
|
97
|
+
# @return [Boolean]
|
98
|
+
attr_accessor :cors_enabled
|
99
|
+
|
100
|
+
# Enable rate limiting middleware
|
101
|
+
# @return [Boolean]
|
102
|
+
attr_accessor :rate_limiting_enabled
|
103
|
+
|
104
|
+
# Enable logging middleware
|
105
|
+
# @return [Boolean]
|
106
|
+
attr_accessor :logging_enabled
|
107
|
+
|
108
|
+
# Require authentication for webhook endpoints
|
109
|
+
# @return [Boolean]
|
110
|
+
attr_accessor :webhook_authentication_required
|
111
|
+
|
112
|
+
# Custom logger instance
|
113
|
+
# @return [Logger, nil]
|
114
|
+
attr_accessor :logger
|
115
|
+
|
116
|
+
# User agent string for HTTP requests
|
117
|
+
# @return [String]
|
118
|
+
attr_accessor :user_agent
|
119
|
+
|
120
|
+
# Environment for configuration (development, test, production)
|
121
|
+
# @return [String]
|
122
|
+
attr_accessor :environment
|
123
|
+
|
124
|
+
# Configuration file path
|
125
|
+
# @return [String, nil]
|
126
|
+
attr_accessor :config_file
|
127
|
+
|
128
|
+
# Parent configuration for inheritance
|
129
|
+
# @return [Configuration, nil]
|
130
|
+
attr_accessor :parent_config
|
131
|
+
|
132
|
+
# Configuration overrides
|
133
|
+
# @return [Hash]
|
134
|
+
attr_accessor :overrides
|
135
|
+
|
136
|
+
def initialize(environment: nil, parent: nil, **overrides)
|
137
|
+
@environment = environment || detect_environment
|
138
|
+
@parent_config = parent
|
139
|
+
@overrides = overrides
|
140
|
+
@config_file = nil
|
141
|
+
|
142
|
+
load_defaults
|
143
|
+
load_from_environment
|
144
|
+
apply_overrides(overrides)
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
# Apply configuration overrides
|
150
|
+
def apply_overrides(overrides)
|
151
|
+
overrides.each do |key, value|
|
152
|
+
setter = "#{key}="
|
153
|
+
unless respond_to?(setter, true) # Check for both public and private methods
|
154
|
+
raise A2A::Errors::ConfigurationError, "Unknown configuration option: #{key}"
|
155
|
+
end
|
156
|
+
|
157
|
+
send(setter, value)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
public
|
162
|
+
|
163
|
+
# Get the logger instance
|
164
|
+
# @return [Logger]
|
165
|
+
def logger
|
166
|
+
@logger ||= if rails_available? && rails_logger
|
167
|
+
rails_logger
|
168
|
+
else
|
169
|
+
require "logger"
|
170
|
+
Logger.new($stdout, level: log_level)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Convert configuration to hash
|
175
|
+
# @return [Hash] Configuration as hash
|
176
|
+
def to_h
|
177
|
+
{
|
178
|
+
environment: @environment,
|
179
|
+
default_timeout: @default_timeout,
|
180
|
+
log_level: @log_level,
|
181
|
+
protocol_version: @protocol_version,
|
182
|
+
default_transport: @default_transport,
|
183
|
+
streaming_enabled: @streaming_enabled,
|
184
|
+
push_notifications_enabled: @push_notifications_enabled,
|
185
|
+
default_input_modes: @default_input_modes,
|
186
|
+
default_output_modes: @default_output_modes,
|
187
|
+
redis_config: @redis_config,
|
188
|
+
rails_integration: @rails_integration,
|
189
|
+
mount_path: @mount_path,
|
190
|
+
auto_mount: @auto_mount,
|
191
|
+
middleware_enabled: @middleware_enabled,
|
192
|
+
authentication_required: @authentication_required,
|
193
|
+
cors_enabled: @cors_enabled,
|
194
|
+
rate_limiting_enabled: @rate_limiting_enabled,
|
195
|
+
logging_enabled: @logging_enabled,
|
196
|
+
webhook_authentication_required: @webhook_authentication_required,
|
197
|
+
user_agent: @user_agent
|
198
|
+
}
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|