fix(image): add response-header timeout and idempotent retry
Give the image client a configurable ResponseHeaderTimeout (default 4m, capped at the overall timeout) so a stalled upstream fails fast, and retry such timeouts when an idempotency key is set so a slow-but-safe request can recover instead of terminating. Raise the Next.js API proxy maxDuration to 1200s to accommodate long generations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -347,3 +347,41 @@ func TestImageClientDoesNotRetryRequestTimeout(t *testing.T) {
|
||||
t.Fatalf("expected timeout to fail fast without retry, got %d calls", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageClientRetriesIdempotentResponseHeaderTimeout(t *testing.T) {
|
||||
calls := 0
|
||||
var keys []string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
calls++
|
||||
keys = append(keys, r.Header.Get("Idempotency-Key"))
|
||||
if calls == 1 {
|
||||
time.Sleep(80 * time.Millisecond)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"data":[{"url":"https://example.com/recovered.png"}]}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewImageClient(ImageClientOptions{BaseURL: server.URL, APIKey: "test-key"})
|
||||
transport, ok := client.client.Transport.(*http.Transport)
|
||||
if !ok {
|
||||
t.Fatalf("expected http transport, got %T", client.client.Transport)
|
||||
}
|
||||
transport.ResponseHeaderTimeout = 20 * time.Millisecond
|
||||
client.client.Timeout = time.Second
|
||||
|
||||
image, err := client.Generate(context.Background(), "create an image", ImageRequestOptions{Model: "gpt-image-2", IdempotencyKey: "same-generation"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if image.URL != "https://example.com/recovered.png" {
|
||||
t.Fatalf("expected recovered image, got %#v", image)
|
||||
}
|
||||
if calls != 2 {
|
||||
t.Fatalf("expected one retry after timeout, got %d calls", calls)
|
||||
}
|
||||
if len(keys) != 2 || keys[0] != "same-generation" || keys[1] != "same-generation" {
|
||||
t.Fatalf("expected retry to keep idempotency key, got %#v", keys)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user