diff --git a/agent/compaction/index.go b/agent/compaction/index.go index 279018a1..cf063fae 100644 --- a/agent/compaction/index.go +++ b/agent/compaction/index.go @@ -399,6 +399,26 @@ func computeContentByteCount(content message.Content) int { return stringByteCount(typed.Message) + stringByteCount(typed.ErrorCode) + stringByteCount(typed.Details) case *message.HostedFileContent: return stringByteCount(typed.FileID) + stringByteCount(typed.MediaType) + stringByteCount(typed.Name) + case *message.MCPServerToolCallContent: + return stringByteCount(typed.CallID) + stringByteCount(typed.Name) + stringByteCount(typed.ServerName) + stringByteCount(typed.Arguments) + case *message.MCPServerToolResultContent: + total := stringByteCount(typed.CallID) + stringByteCount(typed.Name) + stringByteCount(typed.ServerName) + stringByteCount(typed.Error) + for _, output := range typed.Outputs { + total += computeContentByteCount(output) + } + return total + case *message.CodeInterpreterToolCallContent: + total := stringByteCount(typed.CallID) + for _, input := range typed.Inputs { + total += computeContentByteCount(input) + } + return total + case *message.CodeInterpreterToolResultContent: + total := stringByteCount(typed.CallID) + for _, output := range typed.Outputs { + total += computeContentByteCount(output) + } + return total default: return 0 } diff --git a/agent/compaction/index_test.go b/agent/compaction/index_test.go index c0c557ea..4b96bedd 100644 --- a/agent/compaction/index_test.go +++ b/agent/compaction/index_test.go @@ -5,6 +5,7 @@ package compaction_test import ( "context" "slices" + "strings" "testing" "github.com/microsoft/agent-framework-go/agent/compaction" @@ -370,3 +371,31 @@ func TestMessageIndex_Update_RepeatedContentDoesNotDropMessages(t *testing.T) { t.Fatalf("after turn 2 (repeated \"continue\"): got %v, want %v — appended turns were dropped", got, want) } } + +func TestMessageIndex_CountsHostedToolResultBytes(t *testing.T) { + big := strings.Repeat("x", 10000) + // A hosted tool result carrying 10k bytes of Outputs must be byte-accounted + // like the equivalent FunctionResultContent, so token/byte-based compaction + // triggers see the payload that most needs compacting. + fnMsg := &message.Message{Role: message.RoleTool, Contents: message.Contents{ + &message.FunctionResultContent{CallID: "c1", Result: big}, + }} + mcpMsg := &message.Message{Role: message.RoleTool, Contents: message.Contents{ + &message.MCPServerToolResultContent{CallID: "c1", Outputs: message.Contents{&message.TextContent{Text: big}}}, + }} + ciMsg := &message.Message{Role: message.RoleTool, Contents: message.Contents{ + &message.CodeInterpreterToolResultContent{CallID: "c1", Outputs: message.Contents{&message.TextContent{Text: big}}}, + }} + prefix := textMessage(message.RoleUser, "hi") + + fnBytes := compaction.CreateMessageIndex([]*message.Message{prefix, fnMsg}, nil).TotalByteCount() + mcpBytes := compaction.CreateMessageIndex([]*message.Message{prefix, mcpMsg}, nil).TotalByteCount() + ciBytes := compaction.CreateMessageIndex([]*message.Message{prefix, ciMsg}, nil).TotalByteCount() + + if mcpBytes < fnBytes { + t.Errorf("MCP tool-result bytes = %d, want >= FunctionResult bytes %d (Outputs undercounted)", mcpBytes, fnBytes) + } + if ciBytes < fnBytes { + t.Errorf("code-interpreter result bytes = %d, want >= FunctionResult bytes %d (Outputs undercounted)", ciBytes, fnBytes) + } +}