Skip to content

fix(gateway): preserve retry semantics when APICallError is converted to GatewayError#14254

Open
giulio-leone wants to merge 1 commit intovercel:mainfrom
giulio-leone:fix/gateway-retry-14216
Open

fix(gateway): preserve retry semantics when APICallError is converted to GatewayError#14254
giulio-leone wants to merge 1 commit intovercel:mainfrom
giulio-leone:fix/gateway-retry-14216

Conversation

@giulio-leone
Copy link
Copy Markdown

Summary

Fixes #14216

When @ai-sdk/gateway converts an APICallError into a GatewayError subclass (via asGatewayError()), the SDK retry logic no longer recognizes the error as retryable because APICallError.isInstance() returns false for GatewayError. This causes maxRetries to be silently ignored for transient 503, 429, and 408 gateway failures.

Root Cause

The retry logic in retry-with-exponential-backoff.ts only checked APICallError.isInstance(error) && error.isRetryable. After asGatewayError() wraps the original APICallError into a GatewayError subclass, APICallError.isInstance() returns false, so the error is never retried.

Changes

@ai-sdk/gateway

  • Added isRetryable field to GatewayError base class with the same default logic as APICallError (408/429/500+ are retryable)
  • Updated all 7 GatewayError subclasses to accept and forward isRetryable
  • Updated createGatewayErrorFromResponse() to accept and forward isRetryable
  • Updated asGatewayError() to propagate isRetryable from the original APICallError

ai (core)

  • Updated retry logic to recognize both APICallError and GatewayError as retryable error types
  • Added getResponseHeaders() helper to extract retry-after headers from GatewayError's cause chain (the original APICallError is preserved as cause)

Test Coverage

  • 10 new tests for isRetryable defaults across all GatewayError subclasses
  • 3 new tests for isRetryable propagation from APICallError through asGatewayError()
  • 8 new tests for gateway error retry behavior in the retry logic (retryable errors, non-retryable errors, retry-after header extraction, maxRetries limit)
  • All 392 existing gateway tests pass
  • All 20 retry tests pass (11 existing + 8 new + 1 updated)

… to GatewayError

When `@ai-sdk/gateway` converts an `APICallError` into a `GatewayError`
subclass (via `asGatewayError`), the SDK retry logic no longer recognizes
the error as retryable because `APICallError.isInstance()` returns false
for `GatewayError`. This causes `maxRetries` to be silently ignored for
transient 503, 429, and 408 gateway failures.

Changes:
- Add `isRetryable` field to `GatewayError` base class with the same
  default logic as `APICallError` (408/429/500+ are retryable)
- Propagate `isRetryable` from the original `APICallError` through
  `asGatewayError()` → `createGatewayErrorFromResponse()`
- Update retry logic to recognize both `APICallError` and `GatewayError`
  as retryable error types
- Extract retry-after headers from GatewayError's cause chain so
  provider rate-limit headers still work after error conversion

Fixes vercel#14216

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@tigent tigent bot added ai/core core functions like generateText, streamText, etc. Provider utils, and provider spec. ai/provider related to a provider package. Must be assigned together with at least one `provider/*` label bug Something isn't working as documented provider/gateway Issues related to the @ai-sdk/gateway provider labels Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai/core core functions like generateText, streamText, etc. Provider utils, and provider spec. ai/provider related to a provider package. Must be assigned together with at least one `provider/*` label bug Something isn't working as documented provider/gateway Issues related to the @ai-sdk/gateway provider

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AI Gateway breaks SDK retry: GatewayInternalServerError not recognized by maxRetries

1 participant