(prompt []fantasy.Message)
| 967 | } |
| 968 | |
| 969 | func injectMissingToolResults(prompt []fantasy.Message) []fantasy.Message { |
| 970 | result := make([]fantasy.Message, 0, len(prompt)) |
| 971 | for i := 0; i < len(prompt); i++ { |
| 972 | msg := prompt[i] |
| 973 | result = append(result, msg) |
| 974 | |
| 975 | if msg.Role != fantasy.MessageRoleAssistant { |
| 976 | continue |
| 977 | } |
| 978 | toolCalls := ExtractToolCalls(msg.Content) |
| 979 | if len(toolCalls) == 0 { |
| 980 | continue |
| 981 | } |
| 982 | |
| 983 | // Collect the tool call IDs that have results in the |
| 984 | // following tool message(s). |
| 985 | answered := make(map[string]struct{}) |
| 986 | j := i + 1 |
| 987 | for ; j < len(prompt); j++ { |
| 988 | if prompt[j].Role != fantasy.MessageRoleTool { |
| 989 | break |
| 990 | } |
| 991 | for _, part := range prompt[j].Content { |
| 992 | tr, ok := safeAsToolResultPart(part) |
| 993 | if !ok { |
| 994 | continue |
| 995 | } |
| 996 | answered[tr.ToolCallID] = struct{}{} |
| 997 | } |
| 998 | } |
| 999 | if i+1 < j { |
| 1000 | // Preserve persisted tool result ordering and inject any |
| 1001 | // synthetic results after the existing contiguous tool messages. |
| 1002 | result = append(result, prompt[i+1:j]...) |
| 1003 | i = j - 1 |
| 1004 | } |
| 1005 | |
| 1006 | // Build synthetic results for any unanswered tool calls. |
| 1007 | // Provider-executed tool calls are handled server-side by |
| 1008 | // the LLM provider, and their result blocks contain |
| 1009 | // provider-owned metadata. We cannot synthesize a valid |
| 1010 | // provider result if one is missing, so provider-specific |
| 1011 | // sanitization removes unpaired calls before replay. |
| 1012 | var missing []fantasy.MessagePart |
| 1013 | for _, tc := range toolCalls { |
| 1014 | if tc.ProviderExecuted { |
| 1015 | continue |
| 1016 | } |
| 1017 | if _, ok := answered[tc.ToolCallID]; !ok { |
| 1018 | missing = append(missing, fantasy.ToolResultPart{ |
| 1019 | ToolCallID: tc.ToolCallID, |
| 1020 | Output: fantasy.ToolResultOutputContentError{ |
| 1021 | Error: xerrors.New("tool call was interrupted and did not receive a result"), |
| 1022 | }, |
| 1023 | }) |
| 1024 | } |
| 1025 | } |
| 1026 | if len(missing) > 0 { |
no test coverage detected