Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1997,6 +1997,7 @@ public List<Msg> getOriginalMemoryMsgs() {
* <li>Tool-related messages ({@link MsgRole#TOOL})</li>
* <li>System messages ({@link MsgRole#SYSTEM})</li>
* <li>Intermediate ASSISTANT messages that contain tool calls (not final responses)</li>
* <li>Synthetic ASSISTANT messages generated by compression/offload flows</li>
* <li>Any other message types</li>
* </ul>
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,8 @@ public static boolean isToolResultMessage(Msg msg) {
*
* <p>A compressed message is one that has been processed by AutoContextMemory compression
* strategies. Compressed messages contain metadata with the {@code _compress_meta} key,
* which indicates that the message content has been compressed, summarized, or offloaded.
* which is the canonical marker for synthetic assistant summaries, offloaded payloads, and
* other replacement messages created by compression.
*
* <p>Compressed messages may have:
* <ul>
Expand Down Expand Up @@ -333,30 +334,23 @@ public static boolean isCompressedMessage(Msg msg) {
/**
* Check if an ASSISTANT message is a final response to the user (not a tool call).
*
* <p>A final assistant response should not contain ToolUseBlock, as those are intermediate
* tool invocation messages, not the final response returned to the user.
* <p>A final assistant response should not contain ToolUseBlock or ToolResultBlock, and it
* must not be a synthetic assistant message produced by compression. Synthetic assistant
* messages always carry {@code _compress_meta} metadata.
*
* @param msg the message to check
* @return true if the message is an ASSISTANT role message that does not contain tool calls
* @return true if the message is an ASSISTANT role message that is not synthetic and does not
* contain tool calls
*/
public static boolean isFinalAssistantResponse(Msg msg) {
if (msg == null || msg.getRole() != MsgRole.ASSISTANT) {
return false;
}

// Skip compressed current round messages - they are compression results, not real assistant
// responses
Map<String, Object> metadata = msg.getMetadata();
if (metadata != null) {
Object compressMeta = metadata.get("_compress_meta");
// compressMeta may be null if the key doesn't exist, but instanceof handles null safely
if (compressMeta != null && compressMeta instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> compressMetaMap = (Map<String, Object>) compressMeta;
if (Boolean.TRUE.equals(compressMetaMap.get("compressed_current_round"))) {
return false;
}
}
// Synthetic assistant messages are created by compression/offload flows and must not be
// treated as user-visible final replies.
if (isCompressedMessage(msg)) {
return false;
}

// A final response should not contain ToolUseBlock (tool calls)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,21 @@ void testAddMessage() {
assertEquals("Hello", originalMessages.get(0).getTextContent());
}

@Test
@DisplayName("Should exclude synthetic assistant messages from interaction messages")
void testGetInteractionMsgsSkipsSyntheticAssistant() {
memory.addMessage(createTextMessage("User message 1", MsgRole.USER));
memory.addMessage(createCompressedAssistantMessage("Compressed summary"));
memory.addMessage(createTextMessage("User message 2", MsgRole.USER));

List<Msg> interactionMsgs = memory.getInteractionMsgs();
assertEquals(2, interactionMsgs.size());
assertEquals(MsgRole.USER, interactionMsgs.get(0).getRole());
assertEquals("User message 1", interactionMsgs.get(0).getTextContent());
assertEquals(MsgRole.USER, interactionMsgs.get(1).getRole());
assertEquals("User message 2", interactionMsgs.get(1).getTextContent());
}

@Test
@DisplayName("Should return messages when below threshold")
void testGetMessagesBelowThreshold() {
Expand Down Expand Up @@ -1085,6 +1100,20 @@ private Msg createToolResultMessage(String toolName, String callId, String resul
.build();
}

private Msg createCompressedAssistantMessage(String text) {
Map<String, Object> metadata = new HashMap<>();
Map<String, Object> compressMeta = new HashMap<>();
compressMeta.put("offloaduuid", "uuid-1");
metadata.put("_compress_meta", compressMeta);

return Msg.builder()
.role(MsgRole.ASSISTANT)
.name("assistant")
.content(TextBlock.builder().text(text).build())
.metadata(metadata)
.build();
}

/**
* Simple Model implementation for testing.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,20 @@ private Msg createToolResultMessage(String toolName, String callId, String resul
.build();
}

private Msg createCompressedAssistantMessage(String text) {
Map<String, Object> metadata = new HashMap<>();
Map<String, Object> compressMeta = new HashMap<>();
compressMeta.put("offloaduuid", "uuid-1");
metadata.put("_compress_meta", compressMeta);

return Msg.builder()
.role(MsgRole.ASSISTANT)
.name("assistant")
.content(List.of(TextBlock.builder().text(text).build()))
.metadata(metadata)
.build();
}

// ============================================================================
// Tests for isToolMessage, isToolUseMessage, isToolResultMessage
// ============================================================================
Expand Down Expand Up @@ -601,6 +615,15 @@ void testIsFinalAssistantResponseWithCompressedMessage() {
assertFalse(MsgUtils.isFinalAssistantResponse(compressedMsg));
}

@Test
@DisplayName("Should return false for synthetic compressed assistant message")
void testIsFinalAssistantResponseWithSyntheticCompressedAssistant() {
Msg compressedMsg = createCompressedAssistantMessage("Compressed summary");

assertTrue(MsgUtils.isCompressedMessage(compressedMsg));
assertFalse(MsgUtils.isFinalAssistantResponse(compressedMsg));
}

@Test
@DisplayName("Should return true for assistant message with null metadata")
void testIsFinalAssistantResponseWithNullMetadata() {
Expand Down
Loading