dropOrphanToolMessages removes tool-role messages whose tool-call references have been truncated out of the recent window. Providers reject prompts with tool_result blocks that do not have a matching tool_use, so a truncation cut that lands between an assistant tool-call message and its tool-result
(recent []fantasy.Message)
| 127 | // recent window. A single forward pass tracking known tool-call IDs is |
| 128 | // sufficient to drop them. |
| 129 | func dropOrphanToolMessages(recent []fantasy.Message) []fantasy.Message { |
| 130 | if len(recent) == 0 { |
| 131 | return recent |
| 132 | } |
| 133 | known := make(map[string]struct{}) |
| 134 | result := make([]fantasy.Message, 0, len(recent)) |
| 135 | for _, msg := range recent { |
| 136 | if msg.Role == fantasy.MessageRoleAssistant { |
| 137 | for _, part := range msg.Content { |
| 138 | call, ok := fantasy.AsMessagePart[fantasy.ToolCallPart](part) |
| 139 | if !ok { |
| 140 | continue |
| 141 | } |
| 142 | known[call.ToolCallID] = struct{}{} |
| 143 | } |
| 144 | result = append(result, msg) |
| 145 | continue |
| 146 | } |
| 147 | if msg.Role != fantasy.MessageRoleTool { |
| 148 | result = append(result, msg) |
| 149 | continue |
| 150 | } |
| 151 | |
| 152 | kept := make([]fantasy.MessagePart, 0, len(msg.Content)) |
| 153 | for _, part := range msg.Content { |
| 154 | tr, ok := fantasy.AsMessagePart[fantasy.ToolResultPart](part) |
| 155 | if !ok { |
| 156 | kept = append(kept, part) |
| 157 | continue |
| 158 | } |
| 159 | if _, matched := known[tr.ToolCallID]; matched { |
| 160 | kept = append(kept, part) |
| 161 | } |
| 162 | } |
| 163 | if len(kept) == 0 { |
| 164 | continue |
| 165 | } |
| 166 | trimmed := msg |
| 167 | trimmed.Content = kept |
| 168 | result = append(result, trimmed) |
| 169 | } |
| 170 | return result |
| 171 | } |
| 172 | |
| 173 | func textMessage(role fantasy.MessageRole, text string) fantasy.Message { |
| 174 | return fantasy.Message{ |
no outgoing calls
no test coverage detected