From edbdc3bcf106a60540348f01baa45d39a6627e00 Mon Sep 17 00:00:00 2001 From: xiaoen <2768753269@qq.com> Date: Fri, 13 Mar 2026 16:25:27 +0800 Subject: [PATCH] fix(agent): findSafeBoundary returns 0 for single-Turn history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the entire history is a single Turn (one user message followed by tool calls and responses, no subsequent user message), the only Turn boundary is at index 0. Previously the fallback returned targetIndex, which could land on a tool or assistant message — splitting the Turn. Return 0 instead, so callers (forceCompression, summarizeSession) see mid <= 0 and skip compression rather than cutting inside the Turn. --- pkg/agent/context_budget.go | 6 +++++- pkg/agent/context_budget_test.go | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/pkg/agent/context_budget.go b/pkg/agent/context_budget.go index 0b7f443e6..c87695c7a 100644 --- a/pkg/agent/context_budget.go +++ b/pkg/agent/context_budget.go @@ -79,7 +79,11 @@ func findSafeBoundary(history []providers.Message, targetIndex int) int { } } - return targetIndex + // No Turn boundary after targetIndex either. The only boundary is at + // index 0, meaning the entire history is a single Turn. Return 0 to + // signal that safe compression is not possible — callers check for + // mid <= 0 and skip compression in that case. + return 0 } // estimateMessageTokens estimates the token count for a single message, diff --git a/pkg/agent/context_budget_test.go b/pkg/agent/context_budget_test.go index 175e04885..30b3fe6a2 100644 --- a/pkg/agent/context_budget_test.go +++ b/pkg/agent/context_budget_test.go @@ -346,6 +346,23 @@ func TestFindSafeBoundary(t *testing.T) { } } +func TestFindSafeBoundary_SingleTurnReturnsZero(t *testing.T) { + // A single Turn with no subsequent user message. The only Turn boundary + // is at index 0; cutting anywhere else would split the Turn's tool + // sequence. findSafeBoundary must return 0 so callers skip compression. + history := []providers.Message{ + msgUser("do everything"), // 0 ← only Turn boundary + msgAssistantTC("tc1"), // 1 + msgTool("tc1", "result"), // 2 + msgAssistant("all done"), // 3 + } + + got := findSafeBoundary(history, 2) + if got != 0 { + t.Errorf("findSafeBoundary(single_turn, 2) = %d, want 0 (cannot split single Turn)", got) + } +} + func TestFindSafeBoundary_BackwardScanSkipsToolSequence(t *testing.T) { // A long tool-call chain: user → assistant+TC → tool → tool → ... → assistant → user // Target is inside the chain; boundary should skip the entire chain backward.