diff --git a/pkg/providers/http_provider.go b/pkg/providers/http_provider.go index ddc549a..0342c71 100644 --- a/pkg/providers/http_provider.go +++ b/pkg/providers/http_provider.go @@ -65,7 +65,21 @@ func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []Too return nil, err } if statusCode != http.StatusOK { - return nil, fmt.Errorf("API error (status %d, content-type %q): %s", statusCode, contentType, previewResponseBody(body)) + preview := previewResponseBody(body) + if statusCode == http.StatusBadRequest && strings.Contains(strings.ToLower(preview), "no tool call found for function call output") { + // Retry once with sanitized history to avoid orphaned tool outputs causing hard-fail. + safeMessages := sanitizeResponsesRetryMessages(messages) + body2, status2, ctype2, err2 := p.callResponses(ctx, safeMessages, tools, model, options) + if err2 == nil && status2 == http.StatusOK && json.Valid(body2) { + logger.InfoCF("provider", "Recovered responses 400 by sanitizing tool outputs", map[string]interface{}{"messages_before": len(messages), "messages_after": len(safeMessages)}) + return parseResponsesAPIResponse(body2) + } + if err2 != nil { + return nil, err2 + } + return nil, fmt.Errorf("API error (status %d, content-type %q): %s", status2, ctype2, previewResponseBody(body2)) + } + return nil, fmt.Errorf("API error (status %d, content-type %q): %s", statusCode, contentType, preview) } if !json.Valid(body) { return nil, fmt.Errorf("API error (status %d, content-type %q): non-JSON response: %s", statusCode, contentType, previewResponseBody(body)) @@ -207,6 +221,22 @@ func toChatCompletionsContent(msg Message) []map[string]interface{} { return content } +func sanitizeResponsesRetryMessages(messages []Message) []Message { + out := make([]Message, 0, len(messages)) + for _, m := range messages { + if strings.EqualFold(strings.TrimSpace(m.Role), "tool") { + text := strings.TrimSpace(m.Content) + if text == "" { + continue + } + out = append(out, Message{Role: "user", Content: "[tool_result_fallback] " + text}) + continue + } + out = append(out, m) + } + return out +} + func toResponsesInputItems(msg Message) []map[string]interface{} { return toResponsesInputItemsWithState(msg, nil) }