From 517d3ddb8563b238359e46c07fa9fba84e3f301f Mon Sep 17 00:00:00 2001 From: PratikDhanave Date: Sun, 6 Sep 2026 10:26:55 +0530 Subject: [PATCH] Byte-account hosted tool-call/result content in compaction sizing computeContentByteCount (used by group ByteCount and, via /4, by the token estimate for non-text content) counted FunctionResultContent and DataContent payloads, but MCPServerToolResult/CodeInterpreterToolResult (and the call variants) fell through to default: return 0. A 10k-byte hosted tool result therefore contributed ~2 bytes, so byte/token-based triggers (TokensExceed, ContextWindowStrategy) under-counted history dominated by hosted code- interpreter / MCP outputs - exactly the large content that most needs compaction - and could fail to fire. Account these types by recursing into Outputs/Inputs and counting the identity strings, mirroring the function-call/result cases. --- agent/compaction/index.go | 20 ++++++++++++++++++++ agent/compaction/index_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) 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) + } +}