anchorUserMessage guarantees the packed window carries a user-role message whenever history has one. The newest-first walk drops oldest-first, so a long single turn (one task message, then dozens of assistant+tool rounds that fill the budget) evicts the sole user task and hands the backend a userles
(kept, history []Message)
| 199 | } |
| 200 | |
| 201 | // anchorUserMessage guarantees the packed window carries a user-role message |
| 202 | // whenever history has one. The newest-first walk drops oldest-first, so a long |
| 203 | // single turn (one task message, then dozens of assistant+tool rounds that fill |
| 204 | // the budget) evicts the sole user task and hands the backend a userless window, |
| 205 | // which 400s every OpenAI-compatible server ("no user query found in messages"). |
| 206 | // When no user survived, recover the FIRST user message (the original task, the |
| 207 | // agent's anchor against drift), prepended chronologically over budget: the same |
| 208 | // deliberately-over-budget guarantee newestToolGroup and the always-keep-newest |
| 209 | // path already make. A lone user message carries no tool-call pairing, so this is |
| 210 | // safe after the dangling/orphan passes. No-op when a recent user already |
| 211 | // survived (normal multi-turn) or history has no user message at all. |
| 212 | func anchorUserMessage(kept, history []Message) []Message { |
| 213 | for _, m := range kept { |
| 214 | if m.Role == RoleUser { |
| 215 | return kept |
| 216 | } |