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,223 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "time"
|
4
|
+
|
5
|
+
module A2A
|
6
|
+
module Types
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Base class for all A2A protocol types
|
12
|
+
#
|
13
|
+
# Provides common functionality for validation, serialization, and
|
14
|
+
# camelCase/snake_case conversion for A2A protocol compatibility.
|
15
|
+
#
|
16
|
+
module A2A
|
17
|
+
module Types
|
18
|
+
class BaseModel
|
19
|
+
##
|
20
|
+
# Initialize a new model instance
|
21
|
+
#
|
22
|
+
# @param attributes [Hash] The attributes to set
|
23
|
+
def initialize(**attributes)
|
24
|
+
# Set instance variables for all provided attributes
|
25
|
+
attributes.each do |key, value|
|
26
|
+
instance_variable_set("@#{key}", value)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Validate the instance after initialization
|
30
|
+
validate! if respond_to?(:validate!, true)
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Convert the model to a hash representation
|
35
|
+
#
|
36
|
+
# @param camel_case [Boolean] Whether to convert keys to camelCase
|
37
|
+
# @return [Hash] The model as a hash
|
38
|
+
def to_h(camel_case: true)
|
39
|
+
hash = {}
|
40
|
+
|
41
|
+
instance_variables.each do |var|
|
42
|
+
key = var.to_s.delete("@")
|
43
|
+
value = instance_variable_get(var)
|
44
|
+
|
45
|
+
# Convert nested models
|
46
|
+
case value
|
47
|
+
when BaseModel
|
48
|
+
value = value.to_h(camel_case: camel_case)
|
49
|
+
when Array
|
50
|
+
value = value.map do |item|
|
51
|
+
item.is_a?(BaseModel) ? item.to_h(camel_case: camel_case) : item
|
52
|
+
end
|
53
|
+
when Hash
|
54
|
+
value = value.transform_values do |v|
|
55
|
+
v.is_a?(BaseModel) ? v.to_h(camel_case: camel_case) : v
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Convert key to camelCase if requested
|
60
|
+
key = camelize(key) if camel_case
|
61
|
+
hash[key] = value unless value.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
hash
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Create an instance from a hash
|
69
|
+
#
|
70
|
+
# @param hash [Hash] The hash to create from
|
71
|
+
# @return [BaseModel] The new instance
|
72
|
+
def self.from_h(hash)
|
73
|
+
return hash if hash.is_a?(self) # Already an instance of this class
|
74
|
+
return nil if hash.nil?
|
75
|
+
|
76
|
+
# Convert string keys to symbols and snake_case camelCase keys
|
77
|
+
normalized_hash = {}
|
78
|
+
hash.each do |key, value|
|
79
|
+
snake_key = underscore(key.to_s).to_sym
|
80
|
+
normalized_hash[snake_key] = value
|
81
|
+
end
|
82
|
+
|
83
|
+
new(**normalized_hash)
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# Convert to JSON string
|
88
|
+
#
|
89
|
+
# @param options [Hash] JSON generation options
|
90
|
+
# @return [String] The JSON representation
|
91
|
+
def to_json(**options)
|
92
|
+
require "json"
|
93
|
+
to_h.to_json(**options)
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Create an instance from JSON string
|
98
|
+
#
|
99
|
+
# @param json_string [String] The JSON string
|
100
|
+
# @return [BaseModel] The new instance
|
101
|
+
def self.from_json(json_string)
|
102
|
+
require "json"
|
103
|
+
hash = JSON.parse(json_string)
|
104
|
+
from_h(hash)
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# Check equality with another model
|
109
|
+
#
|
110
|
+
# @param other [Object] The other object to compare
|
111
|
+
# @return [Boolean] True if equal
|
112
|
+
def ==(other)
|
113
|
+
return false unless other.is_a?(self.class)
|
114
|
+
|
115
|
+
to_h == other.to_h
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# Generate hash code for the model
|
120
|
+
#
|
121
|
+
# @return [Integer] The hash code
|
122
|
+
def hash
|
123
|
+
to_h.hash
|
124
|
+
end
|
125
|
+
|
126
|
+
##
|
127
|
+
# Check if the model is valid
|
128
|
+
#
|
129
|
+
# @return [Boolean] True if valid
|
130
|
+
def valid?
|
131
|
+
validate!
|
132
|
+
true
|
133
|
+
rescue StandardError
|
134
|
+
false
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
##
|
140
|
+
# Convert snake_case to camelCase
|
141
|
+
#
|
142
|
+
# @param string [String] The string to convert
|
143
|
+
# @return [String] The camelCase string
|
144
|
+
def camelize(string)
|
145
|
+
string.to_s.gsub(/_([a-z])/) { ::Regexp.last_match(1).upcase }
|
146
|
+
end
|
147
|
+
|
148
|
+
##
|
149
|
+
# Convert camelCase to snake_case
|
150
|
+
#
|
151
|
+
# @param string [String] The string to convert
|
152
|
+
# @return [String] The snake_case string
|
153
|
+
def self.underscore(string)
|
154
|
+
string.to_s
|
155
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
156
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
157
|
+
.downcase
|
158
|
+
end
|
159
|
+
|
160
|
+
##
|
161
|
+
# Validate required fields
|
162
|
+
#
|
163
|
+
# @param fields [Array<Symbol>] The required field names
|
164
|
+
# @raise [ArgumentError] If any required field is missing
|
165
|
+
def validate_required(*fields)
|
166
|
+
fields.each do |field|
|
167
|
+
value = instance_variable_get("@#{field}")
|
168
|
+
raise ArgumentError, "#{field} is required" if value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
##
|
173
|
+
# Validate that a field is one of the allowed values
|
174
|
+
#
|
175
|
+
# @param field [Symbol] The field name
|
176
|
+
# @param allowed_values [Array] The allowed values
|
177
|
+
# @raise [ArgumentError] If the field value is not allowed
|
178
|
+
def validate_inclusion(field, allowed_values)
|
179
|
+
value = instance_variable_get("@#{field}")
|
180
|
+
return if value.nil?
|
181
|
+
|
182
|
+
return if allowed_values.include?(value)
|
183
|
+
|
184
|
+
raise ArgumentError, "#{field} must be one of: #{allowed_values.join(', ')}"
|
185
|
+
end
|
186
|
+
|
187
|
+
##
|
188
|
+
# Validate that a field is of the expected type
|
189
|
+
#
|
190
|
+
# @param field [Symbol] The field name
|
191
|
+
# @param expected_type [Class, Array<Class>] The expected type(s)
|
192
|
+
# @raise [ArgumentError] If the field is not of the expected type
|
193
|
+
def validate_type(field, expected_type)
|
194
|
+
value = instance_variable_get("@#{field}")
|
195
|
+
return if value.nil?
|
196
|
+
|
197
|
+
types = expected_type.is_a?(Array) ? expected_type : [expected_type]
|
198
|
+
|
199
|
+
return if types.any? { |type| value.is_a?(type) }
|
200
|
+
|
201
|
+
type_names = types.map(&:to_s).join(" or ")
|
202
|
+
raise ArgumentError, "#{field} must be a #{type_names}"
|
203
|
+
end
|
204
|
+
|
205
|
+
##
|
206
|
+
# Validate that an array field contains only items of the expected type
|
207
|
+
#
|
208
|
+
# @param field [Symbol] The field name
|
209
|
+
# @param expected_type [Class] The expected item type
|
210
|
+
# @raise [ArgumentError] If any item is not of the expected type
|
211
|
+
def validate_array_type(field, expected_type)
|
212
|
+
value = instance_variable_get("@#{field}")
|
213
|
+
return if value.nil?
|
214
|
+
|
215
|
+
validate_type(field, Array)
|
216
|
+
|
217
|
+
value.each_with_index do |item, index|
|
218
|
+
raise ArgumentError, "#{field}[#{index}] must be a #{expected_type}" unless item.is_a?(expected_type)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module A2A
|
4
|
+
module Types
|
5
|
+
##
|
6
|
+
# Represents a task status update event
|
7
|
+
#
|
8
|
+
# These events are sent when a task's status changes, allowing clients
|
9
|
+
# to track task progress in real-time.
|
10
|
+
#
|
11
|
+
class TaskStatusUpdateEvent < A2A::Types::BaseModel
|
12
|
+
attr_reader :task_id, :context_id, :status, :metadata
|
13
|
+
|
14
|
+
##
|
15
|
+
# Initialize a new task status update event
|
16
|
+
#
|
17
|
+
# @param task_id [String] The task identifier
|
18
|
+
# @param context_id [String] The context identifier
|
19
|
+
# @param status [TaskStatus, Hash] The new task status
|
20
|
+
# @param metadata [Hash, nil] Additional event metadata
|
21
|
+
def initialize(task_id:, context_id:, status:, metadata: nil)
|
22
|
+
@task_id = task_id
|
23
|
+
@context_id = context_id
|
24
|
+
@status = status.is_a?(TaskStatus) ? status : TaskStatus.from_h(status)
|
25
|
+
@metadata = metadata
|
26
|
+
|
27
|
+
validate!
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Get the event type
|
32
|
+
#
|
33
|
+
# @return [String] The event type
|
34
|
+
def event_type
|
35
|
+
"task_status_update"
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Check if this is a terminal status update
|
40
|
+
#
|
41
|
+
# @return [Boolean] True if the status is terminal
|
42
|
+
def terminal?
|
43
|
+
@status.state.in?(%w[completed canceled failed rejected])
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def validate!
|
49
|
+
validate_required(:task_id, :context_id, :status)
|
50
|
+
validate_type(:task_id, String)
|
51
|
+
validate_type(:context_id, String)
|
52
|
+
validate_type(:status, TaskStatus)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Represents a task artifact update event
|
58
|
+
#
|
59
|
+
# These events are sent when artifacts are added or updated for a task,
|
60
|
+
# supporting streaming artifact delivery.
|
61
|
+
#
|
62
|
+
class TaskArtifactUpdateEvent < A2A::Types::BaseModel
|
63
|
+
attr_reader :task_id, :context_id, :artifact, :append, :metadata
|
64
|
+
|
65
|
+
##
|
66
|
+
# Initialize a new task artifact update event
|
67
|
+
#
|
68
|
+
# @param task_id [String] The task identifier
|
69
|
+
# @param context_id [String] The context identifier
|
70
|
+
# @param artifact [Artifact, Hash] The artifact being updated
|
71
|
+
# @param append [Boolean] Whether to append to existing artifact
|
72
|
+
# @param metadata [Hash, nil] Additional event metadata
|
73
|
+
def initialize(task_id:, context_id:, artifact:, append: false, metadata: nil)
|
74
|
+
@task_id = task_id
|
75
|
+
@context_id = context_id
|
76
|
+
@artifact = artifact.is_a?(Artifact) ? artifact : Artifact.from_h(artifact)
|
77
|
+
@append = append
|
78
|
+
@metadata = metadata
|
79
|
+
|
80
|
+
validate!
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Get the event type
|
85
|
+
#
|
86
|
+
# @return [String] The event type
|
87
|
+
def event_type
|
88
|
+
"task_artifact_update"
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# Check if this is an append operation
|
93
|
+
#
|
94
|
+
# @return [Boolean] True if appending to existing artifact
|
95
|
+
def append?
|
96
|
+
@append
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Check if this is a replace operation
|
101
|
+
#
|
102
|
+
# @return [Boolean] True if replacing existing artifact
|
103
|
+
def replace?
|
104
|
+
!@append
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def validate!
|
110
|
+
validate_required(:task_id, :context_id, :artifact)
|
111
|
+
validate_type(:task_id, String)
|
112
|
+
validate_type(:context_id, String)
|
113
|
+
validate_type(:artifact, Artifact)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# Represents a message in the A2A protocol
|
5
|
+
#
|
6
|
+
# Messages are the primary means of communication between agents and users.
|
7
|
+
# They contain one or more parts (text, files, or data) and metadata.
|
8
|
+
#
|
9
|
+
module A2A
|
10
|
+
module Types
|
11
|
+
class Message < A2A::Types::BaseModel
|
12
|
+
attr_reader :message_id, :role, :parts, :context_id, :task_id, :kind,
|
13
|
+
:metadata, :extensions, :reference_task_ids
|
14
|
+
|
15
|
+
##
|
16
|
+
# Initialize a new message
|
17
|
+
#
|
18
|
+
# @param message_id [String] Unique message identifier
|
19
|
+
# @param role [String] Message role ("user" or "agent")
|
20
|
+
# @param parts [Array<Part>] Message parts
|
21
|
+
# @param kind [String] Message kind (always "message")
|
22
|
+
# @param context_id [String, nil] Context identifier
|
23
|
+
# @param task_id [String, nil] Associated task identifier
|
24
|
+
# @param metadata [Hash, nil] Additional metadata
|
25
|
+
# @param extensions [Array<Hash>, nil] Protocol extensions
|
26
|
+
# @param reference_task_ids [Array<String>, nil] Referenced task IDs
|
27
|
+
def initialize(message_id:, role:, parts:, kind: KIND_MESSAGE, context_id: nil,
|
28
|
+
task_id: nil, metadata: nil, extensions: nil, reference_task_ids: nil)
|
29
|
+
@message_id = message_id
|
30
|
+
@role = role
|
31
|
+
@parts = parts.map { |p| p.is_a?(Part) ? p : Part.from_h(p) }
|
32
|
+
@kind = kind
|
33
|
+
@context_id = context_id
|
34
|
+
@task_id = task_id
|
35
|
+
@metadata = metadata
|
36
|
+
@extensions = extensions
|
37
|
+
@reference_task_ids = reference_task_ids
|
38
|
+
|
39
|
+
validate!
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Get all text content from the message
|
44
|
+
#
|
45
|
+
# @return [String] Combined text from all text parts
|
46
|
+
def text_content
|
47
|
+
@parts.select { |p| p.is_a?(TextPart) }
|
48
|
+
.map(&:text)
|
49
|
+
.join("\n")
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Get all file parts from the message
|
54
|
+
#
|
55
|
+
# @return [Array<FilePart>] All file parts
|
56
|
+
def file_parts
|
57
|
+
@parts.select { |p| p.is_a?(FilePart) }
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Get all data parts from the message
|
62
|
+
#
|
63
|
+
# @return [Array<DataPart>] All data parts
|
64
|
+
def data_parts
|
65
|
+
@parts.select { |p| p.is_a?(DataPart) }
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Add a part to the message
|
70
|
+
#
|
71
|
+
# @param part [Part] The part to add
|
72
|
+
def add_part(part)
|
73
|
+
@parts << part
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# Check if the message is from a user
|
78
|
+
#
|
79
|
+
# @return [Boolean] True if the message is from a user
|
80
|
+
def from_user?
|
81
|
+
@role == ROLE_USER
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Check if the message is from an agent
|
86
|
+
#
|
87
|
+
# @return [Boolean] True if the message is from an agent
|
88
|
+
def from_agent?
|
89
|
+
@role == ROLE_AGENT
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def validate!
|
95
|
+
validate_required(:message_id, :role, :parts, :kind)
|
96
|
+
validate_inclusion(:role, VALID_ROLES)
|
97
|
+
validate_inclusion(:kind, [KIND_MESSAGE])
|
98
|
+
validate_array_type(:parts, Part)
|
99
|
+
|
100
|
+
return unless @parts.empty?
|
101
|
+
|
102
|
+
raise ArgumentError, "Message must have at least one part"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|