From d9de5db5d9ce7b0b91c34b24c00c9a2f71f127db Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Wed, 11 Mar 2026 14:14:10 -0300 Subject: [PATCH] fix(errors): improve check for when to retry requests (#164) * Follow OpenAI's Go SDK. * Check status code >= 500. * Check `x-should-retry` header. Co-authored-by: yuguorui --- errors.go | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/errors.go b/errors.go index f9228de08e182020dcadf04e9d498655917ece1f..a925d4c43dce6d668bc1cac17b9a2a7e96b447fb 100644 --- a/errors.go +++ b/errors.go @@ -3,7 +3,9 @@ package fantasy import ( "errors" "fmt" + "io" "net/http" + "strconv" "strings" "github.com/charmbracelet/x/exp/slice" @@ -51,9 +53,36 @@ func (m *ProviderError) Error() string { return fmt.Sprintf("%s: %s", m.Title, m.Message) } -// IsRetryable checks if the error is retryable based on the status code. +// IsRetryable reports whether the error should be retried. +// It returns true if the underlying cause is io.ErrUnexpectedEOF, if the +// "x-should-retry" response header evaluates to true, or if the HTTP status +// code indicates a retryable condition (408, 409, 429, or any 5xx). func (m *ProviderError) IsRetryable() bool { - return m.StatusCode == http.StatusRequestTimeout || m.StatusCode == http.StatusConflict || m.StatusCode == http.StatusTooManyRequests + // We're mostly mimicking OpenAI's Go SDK here: + // https://github.com/openai/openai-go/blob/b9d280a37149430982e9dfeed16c41d27d45cfc5/internal/requestconfig/requestconfig.go#L244 + if errors.Is(m.Cause, io.ErrUnexpectedEOF) { + return true + } + if m.shouldRetryHeader() { + return true + } + return m.StatusCode == http.StatusRequestTimeout || + m.StatusCode == http.StatusConflict || + m.StatusCode == http.StatusTooManyRequests || + m.StatusCode >= http.StatusInternalServerError +} + +func (m *ProviderError) shouldRetryHeader() bool { + if m.ResponseHeaders == nil { + return false + } + for k, v := range m.ResponseHeaders { + if strings.EqualFold(k, "x-should-retry") { + b, _ := strconv.ParseBool(v) + return b + } + } + return false } // IsContextTooLarge checks if the error is due to the context exceeding the model's limit.