You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Summary:@ai-sdk/google's error schema discards error.details, which is the only place Gemini reports its retry delay. Callers who schedule their own retries therefore cannot read the provider's suggested delay off the typed APICallError, and the SDK's own retry layer has nothing to read either.
Background
Gemini returns 429 without a Retry-After header. It reports the delay in the JSON body as a google.rpc.RetryInfo entry under error.details:
{
"error": {
"code": 429,
"message": "You exceeded your current quota, please check your plan and billing details.",
"status": "RESOURCE_EXHAUSTED",
"details": [
{ "@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [...] },
{ "@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "34.4s" }
]
}
}
The problem
googleErrorDataSchema is a plain (non-loose) Zod object covering only code, message, and status:
Zod strips unknown keys, so details is dropped before createJsonErrorResponseHandler attaches the parsed value as APICallError.data. Two consequences:
The retry-header support added in SDK does not respect rate limit headers from API providers #7247 reads retry-after / retry-after-ms. Gemini sends neither, so maxRetries always falls back to exponential backoff even when the API said exactly how long to wait.
Callers who do their own scheduling — durable workflow engines, job queues, anything that must hand a delay to an external scheduler rather than block a process — cannot obtain the value from the typed error. This is the same gap raised in Respect retry-after header for API (Anthropic at least) #5018 for headers ("the error types themselves don't expose the header value to callers implementing custom retry logic"), but for Google it is worse: there is no header to re-parse in the first place.
Current behaviour
APICallError.data never contains details, regardless of what the API returned:
Going further (optional, and a larger change): @ai-sdk/google could surface the RetryInfo delay to the retry layer the way retry-after is surfaced for other providers, so maxRetries honours it automatically. Google's own Python SDK has the same request open — googleapis/python-genai#1875.
Preserving details alone would already unblock every caller doing custom scheduling, without waiting on the retry-API design discussion in #4842.
import{createGoogleGenerativeAI}from"@ai-sdk/google";import{APICallError}from"@ai-sdk/provider";import{generateText}from"ai";constbody=JSON.stringify({error: {code: 429,message: "You exceeded your current quota, please check your plan.",status: "RESOURCE_EXHAUSTED",details: [{"@type": "type.googleapis.com/google.rpc.QuotaFailure",violations: [{quotaId: "GenerateRequestsPerMinutePerProjectPerModel-FreeTier"}],},{"@type": "type.googleapis.com/google.rpc.RetryInfo",retryDelay: "34.4s"},],},});constgoogle=createGoogleGenerativeAI({apiKey: "x",fetch: async()=>newResponse(body,{status: 429,headers: {"content-type": "application/json"}}),});try{awaitgenerateText({model: google("gemini-2.5-flash"),prompt: "hi",maxRetries: 0});}catch(error){if(!APICallError.isInstance(error))throwerror;console.log("retry-after header:",error.responseHeaders?.["retry-after"]);// undefinedconsole.log("error.data:",JSON.stringify(error.data));// no `details`console.log("responseBody has RetryInfo:",error.responseBody?.includes("RetryInfo"));// true}
Output:
retry-after header: undefined
error.data: {"error":{"code":429,"message":"You exceeded your current quota, please check your plan.","status":"RESOURCE_EXHAUSTED"}}
responseBody has RetryInfo: true
AI SDK Version
ai: 5.0.118
@ai-sdk/google: 2.0.70
@ai-sdk/provider: 2.0.1
@ai-sdk/provider-utils: 3.0.23
packages/google/src/google-error.ts on main is unchanged, so current versions are affected too.
Description
Summary:
@ai-sdk/google's error schema discardserror.details, which is the only place Gemini reports its retry delay. Callers who schedule their own retries therefore cannot read the provider's suggested delay off the typedAPICallError, and the SDK's own retry layer has nothing to read either.Background
Gemini returns 429 without a
Retry-Afterheader. It reports the delay in the JSON body as agoogle.rpc.RetryInfoentry undererror.details:{ "error": { "code": 429, "message": "You exceeded your current quota, please check your plan and billing details.", "status": "RESOURCE_EXHAUSTED", "details": [ { "@type": "type.googleapis.com/google.rpc.QuotaFailure", "violations": [...] }, { "@type": "type.googleapis.com/google.rpc.RetryInfo", "retryDelay": "34.4s" } ] } }The problem
googleErrorDataSchemais a plain (non-loose) Zod object covering onlycode,message, andstatus:Zod strips unknown keys, so
detailsis dropped beforecreateJsonErrorResponseHandlerattaches the parsed value asAPICallError.data. Two consequences:retry-after/retry-after-ms. Gemini sends neither, somaxRetriesalways falls back to exponential backoff even when the API said exactly how long to wait.retry-afterheader for API (Anthropic at least) #5018 for headers ("the error types themselves don't expose the header value to callers implementing custom retry logic"), but for Google it is worse: there is no header to re-parse in the first place.Current behaviour
APICallError.datanever containsdetails, regardless of what the API returned:Workaround (works today, but shouldn't be necessary)
APICallError.responseBodyretains the full raw body, so the delay is recoverable by re-parsing a payload the SDK already parsed:That means going around the typed
datafield and hand-parsing an untyped body, which is easy to get subtly wrong.Expected behaviour
Preserve
error.detailsso it reachesAPICallError.data. Minimal, backward-compatible change:Going further (optional, and a larger change):
@ai-sdk/googlecould surface theRetryInfodelay to the retry layer the wayretry-afteris surfaced for other providers, somaxRetrieshonours it automatically. Google's own Python SDK has the same request open — googleapis/python-genai#1875.Preserving
detailsalone would already unblock every caller doing custom scheduling, without waiting on the retry-API design discussion in #4842.Related
retry-afterheader for API (Anthropic at least) #5018 — Respectretry-afterheader (header-only; Gemini sends none)Reproduction
Runnable with a stub
fetch, no API key needed:Output:
AI SDK Version
packages/google/src/google-error.tsonmainis unchanged, so current versions are affected too.