Class: SimpleAcp::Models::Message

Inherits:
Base
  • Object
show all
Defined in:
lib/simple_acp/models/message.rb

Overview

Fundamental communication structure in ACP.

Messages represent units of communication between users and agents, containing one or more parts (text, JSON, images, etc.).

Examples:

Creating a simple text message

msg = Message.user("Hello, world!")

Creating a message with multiple parts

msg = Message.agent(
  MessagePart.text("Here's the data:"),
  MessagePart.json({ count: 42 })
)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#==, attribute, attributes, #hash, own_attributes, #to_h, #to_json

Constructor Details

#initialize(**kwargs) ⇒ Message

Returns a new instance of Message.



35
36
37
38
39
# File 'lib/simple_acp/models/message.rb', line 35

def initialize(**kwargs)
  super
  @parts ||= []
  @created_at ||= Time.now
end

Instance Attribute Details

#completed_at ⇒ Time? (readonly)

Returns when the message was completed.

Returns:

  • (Time, nil) —

    when the message was completed



33
# File 'lib/simple_acp/models/message.rb', line 33

attribute :completed_at

#created_at ⇒ Time? (readonly)

Returns when the message was created.

Returns:

  • (Time, nil) —

    when the message was created



29
# File 'lib/simple_acp/models/message.rb', line 29

attribute :created_at

#parts ⇒ Array<MessagePart> (readonly)

Returns content parts.

Returns:



25
# File 'lib/simple_acp/models/message.rb', line 25

attribute :parts, default: -> { [] }

#role ⇒ String (readonly)

Returns "user" or "agent" (or "agent/name").

Returns:

  • (String) —

    "user" or "agent" (or "agent/name")



21
# File 'lib/simple_acp/models/message.rb', line 21

attribute :role, required: true

Class Method Details

.agent(*contents) ⇒ Message

Create an agent message from content.

Examples:

Message.agent("Hello, I'm your assistant!")

Parameters:

  • contents (Array<String, MessagePart, Hash>) —

    message content

Returns:



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/simple_acp/models/message.rb', line 85

def self.agent(*contents)
  parts = contents.map do |content|
    case content
    when MessagePart
      content
    when String
      MessagePart.text(content)
    when Hash
      MessagePart.from_hash(content)
    else
      MessagePart.json(content)
    end
  end

  new(role: Types::Role::AGENT, parts: parts)
end

.from_hash(hash) ⇒ Message?

Create from a hash (JSON deserialization).

Parameters:

  • hash (Hash, nil) —

    message data

Returns:

  • (Message, nil) —

    the message or nil



45
46
47
48
49
50
51
# File 'lib/simple_acp/models/message.rb', line 45

def self.from_hash(hash)
  return nil if hash.nil?

  instance = allocate
  instance.send(:initialize_from_hash, hash)
  instance
end

.user(*contents) ⇒ Message

Create a user message from content.

Examples:

Message.user("Hello!")
Message.user(MessagePart.text("Hello"), MessagePart.json({key: "value"}))

Parameters:

  • contents (Array<String, MessagePart, Hash>) —

    message content

Returns:



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/simple_acp/models/message.rb', line 61

def self.user(*contents)
  parts = contents.map do |content|
    case content
    when MessagePart
      content
    when String
      MessagePart.text(content)
    when Hash
      MessagePart.from_hash(content)
    else
      MessagePart.json(content)
    end
  end

  new(role: Types::Role::USER, parts: parts)
end

Instance Method Details

#+(other) ⇒ Message

Combine two messages by appending parts.

Parameters:

  • other (Message) —

    message to append

Returns:

  • (Message) —

    new combined message



155
156
157
158
159
# File 'lib/simple_acp/models/message.rb', line 155

def +(other)
  combined = self.class.new(role: @role, parts: @parts.dup)
  other.parts.each { |p| combined.add_part(p) }
  combined
end

#add_part(part) ⇒ self

Add a part to this message.

Parameters:

Returns:

  • (self) —

    for chaining



131
132
133
134
# File 'lib/simple_acp/models/message.rb', line 131

def add_part(part)
  @parts << (part.is_a?(MessagePart) ? part : MessagePart.from_hash(part))
  self
end

#agent? ⇒ Boolean

Check if this is an agent message.

Returns:

  • (Boolean) —

    true if role is "agent" or starts with "agent/"



112
113
114
# File 'lib/simple_acp/models/message.rb', line 112

def agent?
  @role == Types::Role::AGENT || @role.to_s.start_with?("agent/")
end

#agent_name ⇒ String?

Get the agent name if this is a named agent message.

Returns:

  • (String, nil) —

    agent name extracted from "agent/name" role



119
120
121
122
123
124
125
# File 'lib/simple_acp/models/message.rb', line 119

def agent_name
  return nil unless agent?

  if @role.to_s.start_with?("agent/")
    @role.to_s.sub("agent/", "")
  end
end

#complete! ⇒ self

Mark the message as completed.

Returns:

  • (self) —

    for chaining



139
140
141
142
# File 'lib/simple_acp/models/message.rb', line 139

def complete!
  @completed_at = Time.now
  self
end

#completed? ⇒ Boolean

Check if the message is completed.

Returns:

  • (Boolean) —

    true if completed_at is set



147
148
149
# File 'lib/simple_acp/models/message.rb', line 147

def completed?
  !@completed_at.nil?
end

#compress ⇒ Message

Create a new message with adjacent text parts combined.

Returns:



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/simple_acp/models/message.rb', line 171

def compress
  return self if @parts.length <= 1

  compressed_parts = []
  current_text = nil

  @parts.each do |part|
    if part.text? && !part.base64_encoded?
      if current_text
        current_text = MessagePart.text("#{current_text.content}\n#{part.content}")
      else
        current_text = part.dup
      end
    else
      compressed_parts << current_text if current_text
      current_text = nil
      compressed_parts << part
    end
  end

  compressed_parts << current_text if current_text

  self.class.new(role: @role, parts: compressed_parts, created_at: @created_at)
end

#text_content ⇒ String

Get combined text content from all text parts.

Returns:

  • (String) —

    concatenated text content



164
165
166
# File 'lib/simple_acp/models/message.rb', line 164

def text_content
  @parts.select(&:text?).map(&:content).join("\n")
end

#to_s ⇒ String

Convert to string representation.

Returns:

  • (String) —

    concatenated string representation of all parts



210
211
212
# File 'lib/simple_acp/models/message.rb', line 210

def to_s
  @parts.map(&:to_s).join("\n")
end

#user? ⇒ Boolean

Check if this is a user message.

Returns:

  • (Boolean) —

    true if role is "user"



105
106
107
# File 'lib/simple_acp/models/message.rb', line 105

def user?
  @role == Types::Role::USER
end

#valid? ⇒ Boolean

Validate the message.

Returns:

  • (Boolean) —

    true if role is valid, has parts, and all parts are valid



199
200
201
202
203
204
205
# File 'lib/simple_acp/models/message.rb', line 199

def valid?
  return false unless Types::Role.valid?(@role)
  return false if @parts.empty?
  return false unless @parts.all?(&:valid?)

  true
end