Class AbstractMessage

java.lang.Object
uno.anahata.asi.agi.event.BasicPropertyChangeSource
uno.anahata.asi.agi.message.AbstractMessage
All Implemented Interfaces:
PropertyChangeSource, Rebindable
Direct Known Subclasses:
AbstractModelMessage, UserMessage

public abstract class AbstractMessage extends BasicPropertyChangeSource
The abstract base class for all messages in a conversation, providing common metadata and functionality for the rich, hierarchical V2 domain model.

This class ensures each message has a unique identity, timestamp, and full access to the Agi context. It supports thread-safe iteration of its components via a CopyOnWriteArrayList to facilitate background persistence during active generation.

Author:
anahata
  • Field Details

    • timestamp

      private final long timestamp
      The timestamp when this message was created, in milliseconds since the epoch.
    • sequentialId

      private long sequentialId
      A monotonically increasing number assigned to the message when it is added to a agi, representing its order in the conversation.
    • parts

      private final List<AbstractPart> parts
      The list of parts that make up the message content. Uses CopyOnWriteArrayList to allow thread-safe iteration during background serialization (e.g., auto-save) while the model is adding parts or appending text to existing parts.
    • agi

      private final Agi agi
      A backward reference to the Agi session that owns this message. This is the root of the V2 context management system, allowing any domain object to access the full application state. It is intentionally not transient to support full serialization with Kryo.
  • Constructor Details

    • AbstractMessage

      public AbstractMessage()
  • Method Details

    • getRole

      public abstract Role getRole()
      Gets the role of the entity that created this message. This is implemented by subclasses to provide compile-time type safety.
      Returns:
      The role of the message creator.
    • getFrom

      public abstract String getFrom()
      Gets the identity of the sender of this message. For user messages, this is typically the user's name. For model messages, it's the model ID. For tool messages, it's the identity of the execution context.
      Returns:
      The sender's identity.
    • getDevice

      public abstract String getDevice()
      Gets the ID of the device where this message was created or processed. This could be a hostname, a JVM ID, or a cloud identifier.
      Returns:
      The device ID.
    • isPrunnableOrRemovable

      public boolean isPrunnableOrRemovable()
      Checks if this message is eligible for pruning or removal. A message is prunnable if it is attached to a agi and has been assigned a sequential ID (i.e., it's not the system message or a transient message).
      Returns:
      true if the message is prunnable.
    • addPart

      public void addPart(@NonNull @NonNull AbstractPart part)
      Safely adds a single part to this message, establishing the bidirectional relationship and ensuring the part is fully initialized before firing property change events.
      Parameters:
      part - The part to add.
      Throws:
      IllegalArgumentException - if the part is already attached to this or another message.
    • removePart

      public void removePart(AbstractPart part)
      Removes a part from this message and severs the bidirectional link.
      Parameters:
      part - The part to remove.
      Throws:
      IllegalArgumentException - if the part is not attached to this message.
    • onPartPruningStateChanged

      protected void onPartPruningStateChanged(AbstractPart part, PruningState oldState, PruningState newState)
      Lifecycle callback invoked whenever a child part's pruning state changes. Subclasses can override to propagate reactive updates.
      Parameters:
      part - The part whose pruning state changed.
      oldState - The previous pruning state.
      newState - The new pruning state.
    • remove

      public void remove()
      Removes this message from the agi history.
    • getDepth

      public int getDepth()
      Calculates the "depth" of this message, defined as its distance from the most recent message in the agi history. The head message has a depth of 0.
      Returns:
      The depth of the message.
    • asText

      public String asText(boolean includePruned)
      Convenience method to get the message content as a single string, concatenating the text representation of all its parts.
      Parameters:
      includePruned - whether to include pruned parts
      Returns:
      The concatenated text content.
    • isAnyPinned

      public boolean isAnyPinned()
      Checks if any part in this message is explicitly pinned.
      Returns:
      true if at least one part is pinned.
    • isAllPinned

      public boolean isAllPinned()
      Checks if all parts in this message are explicitly pinned.
      Returns:
      true if all parts are pinned.
    • isAllPruned

      public boolean isAllPruned()
      Checks if all parts in this message are explicitly pruned.
      Returns:
      true if all parts are PRUNED.
    • isEffectivelyPruned

      public boolean isEffectivelyPruned()
      Calculates the EFFECTIVE pruned state of this message. A message is effectively pruned ONLY if it contains no visible (un-pruned) parts.
      Returns:
      true if the message is effectively pruned.
    • isGarbageCollectable

      public boolean isGarbageCollectable()
      Determines if this message is eligible for "hard pruning" (permanent removal from history). In the atomic model, a message is collectable if it is effectively pruned.
      Returns:
      true if the message can be safely removed from history.
    • pinAllParts

      public void pinAllParts()
      Pins all parts in this message.
    • setAutoAllParts

      public void setAutoAllParts()
      Sets all parts in this message to AUTO.
    • pruneAllParts

      public void pruneAllParts()
      Explicitly prunes all parts in this message.
    • resetTokenCounts

      public void resetTokenCounts()
      Resets the cached token counts on all parts contained within this message, forcing a lazy recalculation under the new selected model's tokenizer.
    • getTokenCount

      public int getTokenCount(boolean includePruned)
      Calculates the total number of tokens in this message, summing the token counts of its visible parts.
      Parameters:
      includePruned - whether to include pruned parts
      Returns:
      The total token count.
    • getEffectiveTokenCount

      public int getEffectiveTokenCount()
      Calculates the total effective tokens consumed by this message, including the in-band metadata header and all of its parts.
      Returns:
      The total effective token count.
    • getParts

      public List<AbstractPart> getParts(boolean includePruned)
      The definitive, encapsulated method for retrieving the parts of a message that should be sent to the model, respecting the pruning policy.
      Parameters:
      includePruned - If true, all parts are returned, bypassing the pruning check.
      Returns:
      A new list of the visible parts.
    • getParts

      public List<AbstractPart> getParts()
      Gets an unmodifiable list of all parts in this message.
      Returns:
      The list of parts.
    • getRemainingDepth

      public int getRemainingDepth()
      Calculates the remaining depth of the message as the maximum remaining depth of all its parts.
      Returns:
      The maximum remaining depth.
    • addTextPart

      public abstract TextPart addTextPart(String text)
      Creates and adds a new text part to this message.
      Parameters:
      text - The text content.
      Returns:
      The created text part.
    • addBlobPart

      public abstract BlobPart addBlobPart(String mimeType, byte[] data)
      Creates and adds a new binary data part to this message.
      Parameters:
      mimeType - The MIME type.
      data - The binary data.
      Returns:
      The created blob part.
    • addBlobPart

      public abstract BlobPart addBlobPart(Path path) throws Exception
      Creates and adds a new binary data part from a local file path.
      Parameters:
      path - The path to the file.
      Returns:
      The created blob part.
      Throws:
      Exception - if the file cannot be read or the MIME type cannot be detected.
    • createMetadataHeader

      public String createMetadataHeader()
      Creates a standardized text header containing metadata for this message. This is used for in-band metadata injection to improve model self-awareness.
      Returns:
      A formatted metadata header string.
    • getIdentityLabel

      protected String getIdentityLabel()
      Returns the identity label for the metadata header (e.g., "Message ID: 12"). Subclasses can override this to hide or customize the identity.
      Returns:
      The identity label.
    • appendMetadata

      protected void appendMetadata(StringBuilder sb)
      Hook for subclasses to inject specialized metadata into the message header.
      Parameters:
      sb - The StringBuilder building the header.
    • shouldCreateMetadata

      public boolean shouldCreateMetadata()
      Hook for subclasses to declare if they should generate in-band metadata headers.
      Returns:
      true if metadata headers should be generated.
    • isEmpty

      public boolean isEmpty()
      Checks if the message is empty. A message is considered empty if it contains no parts.
      Returns:
      true if the message is empty, false otherwise.