( prompt []fantasy.Message, toolNameByCallID map[string]string, )
| 1034 | } |
| 1035 | |
| 1036 | func injectMissingToolUses( |
| 1037 | prompt []fantasy.Message, |
| 1038 | toolNameByCallID map[string]string, |
| 1039 | ) []fantasy.Message { |
| 1040 | result := make([]fantasy.Message, 0, len(prompt)) |
| 1041 | for _, msg := range prompt { |
| 1042 | if msg.Role != fantasy.MessageRoleTool { |
| 1043 | result = append(result, msg) |
| 1044 | continue |
| 1045 | } |
| 1046 | |
| 1047 | allToolResults := make([]fantasy.ToolResultPart, 0, len(msg.Content)) |
| 1048 | for _, part := range msg.Content { |
| 1049 | toolResult, ok := safeAsToolResultPart(part) |
| 1050 | if !ok { |
| 1051 | continue |
| 1052 | } |
| 1053 | allToolResults = append(allToolResults, toolResult) |
| 1054 | } |
| 1055 | if len(allToolResults) == 0 { |
| 1056 | result = append(result, msg) |
| 1057 | continue |
| 1058 | } |
| 1059 | |
| 1060 | // Provider-executed tool results may be persisted in a |
| 1061 | // later step than the assistant message that initiated the |
| 1062 | // tool call. When that happens they appear as orphans after |
| 1063 | // the wrong assistant message. Filter them out before |
| 1064 | // matching because they cannot be converted into local |
| 1065 | // tool-use pairs safely. |
| 1066 | toolResults := make([]fantasy.ToolResultPart, 0, len(allToolResults)) |
| 1067 | for _, tr := range allToolResults { |
| 1068 | if !tr.ProviderExecuted { |
| 1069 | toolResults = append(toolResults, tr) |
| 1070 | } |
| 1071 | } |
| 1072 | if len(toolResults) == 0 { |
| 1073 | // All results were provider-executed; drop the message. |
| 1074 | continue |
| 1075 | } |
| 1076 | |
| 1077 | // Walk backwards through the result to find the nearest |
| 1078 | // preceding assistant message (skipping over other tool |
| 1079 | // messages that belong to the same batch of results). |
| 1080 | answeredByPrevious := make(map[string]struct{}) |
| 1081 | for k := len(result) - 1; k >= 0; k-- { |
| 1082 | if result[k].Role == fantasy.MessageRoleAssistant { |
| 1083 | for _, toolCall := range ExtractToolCalls(result[k].Content) { |
| 1084 | toolCallID := sanitizeToolCallID(toolCall.ToolCallID) |
| 1085 | if toolCallID == "" { |
| 1086 | continue |
| 1087 | } |
| 1088 | answeredByPrevious[toolCallID] = struct{}{} |
| 1089 | } |
| 1090 | break |
| 1091 | } |
| 1092 | if result[k].Role != fantasy.MessageRoleTool { |
| 1093 | break |
no test coverage detected