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 #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 #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.
#7247 — Rate-limit header support (merged; nothing to read for Gemini)
#4842 — Custom retry callback (open, no agreed API)
Reproduction
Runnable with a stub fetch, no API key needed:
import { createGoogleGenerativeAI } from "@ai-sdk/google";
import { APICallError } from "@ai-sdk/provider";
import { generateText } from "ai";
const body = 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" },
],
},
});
const google = createGoogleGenerativeAI({
apiKey: "x",
fetch: async () =>
new Response(body, { status: 429, headers: { "content-type": "application/json" } }),
});
try {
await generateText({ model: google("gemini-2.5-flash"), prompt: "hi", maxRetries: 0 });
} catch (error) {
if (!APICallError.isInstance(error)) throw error;
console.log("retry-after header:", error.responseHeaders?.["retry-after"]); // undefined
console.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.