TestPackKeepsNewestToolPairOverBudget: when the newest message is a tool result and its owning assistant won't fit the budget, Pack must keep the pair whole rather than drop the lone orphan and collapse to nothing, otherwise the next request carries only the system prompt and silently loses the whol
(t *testing.T)
| 191 | // next request carries only the system prompt and silently loses the whole |
| 192 | // conversation. Reachable on small-ctx profiles after a big tool output. |
| 193 | func TestPackKeepsNewestToolPairOverBudget(t *testing.T) { |
| 194 | bigArgs := strings.Repeat("x", 4*5000) // ~5000-token write_file content |
| 195 | history := []Message{ |
| 196 | {Role: RoleUser, Content: "do the thing"}, |
| 197 | {Role: RoleAssistant, ToolCalls: []ToolCall{ |
| 198 | {ID: "c1", Name: "write_file", Arguments: map[string]any{"content": bigArgs}}, |
| 199 | }}, |
| 200 | {Role: RoleTool, ToolCallID: "c1", Content: "wrote 20000 bytes"}, |
| 201 | } |
| 202 | r := Pack(history, 500) // far below the assistant's cost |
| 203 | if len(r.Messages) == 0 { |
| 204 | t.Fatal("Pack collapsed to zero messages - newest tool pair was dropped") |
| 205 | } |
| 206 | var sawAssistant, sawTool bool |
| 207 | for _, m := range r.Messages { |
| 208 | if m.Role == RoleAssistant && len(m.ToolCalls) > 0 { |
| 209 | sawAssistant = true |
| 210 | } |
| 211 | if m.Role == RoleTool { |
| 212 | sawTool = true |
| 213 | } |
| 214 | } |
| 215 | if !sawAssistant || !sawTool { |
| 216 | t.Fatalf("expected the assistant+tool pair to survive, got %+v", r.Messages) |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | // TestPackKeepsNewestParallelToolGroupOverBudget: with parallel tool calls the |
| 221 | // tail is [assistant(c1,c2), tool(c1), tool(c2)], so the newest tool's owner is |