fix(agents): normalize abort-wrapped RESOURCE_EXHAUSTED into failover errors (#11972)

This commit is contained in:
Catalin Lupuleti
2026-03-08 12:21:41 +00:00
committed by Darshil
parent 2f5d3b6574
commit dac220bd88
4 changed files with 179 additions and 15 deletions

View File

@@ -72,9 +72,16 @@ function getStatusCode(err: unknown): number | undefined {
if (!err || typeof err !== "object") {
return undefined;
}
// Dig into nested `err.error` shapes (e.g. Google Vertex abort wrappers)
const nestedError =
"error" in err && err.error && typeof err.error === "object"
? (err.error as { status?: unknown; code?: unknown })
: undefined;
const candidate =
(err as { status?: unknown; statusCode?: unknown }).status ??
(err as { statusCode?: unknown }).statusCode;
(err as { statusCode?: unknown }).statusCode ??
nestedError?.code ??
nestedError?.status;
if (typeof candidate === "number") {
return candidate;
}
@@ -88,7 +95,11 @@ function getErrorCode(err: unknown): string | undefined {
if (!err || typeof err !== "object") {
return undefined;
}
const candidate = (err as { code?: unknown }).code;
const nestedError =
"error" in err && err.error && typeof err.error === "object"
? (err.error as { code?: unknown; status?: unknown })
: undefined;
const candidate = (err as { code?: unknown }).code ?? nestedError?.status ?? nestedError?.code;
if (typeof candidate !== "string") {
return undefined;
}
@@ -114,10 +125,53 @@ function getErrorMessage(err: unknown): string {
if (typeof message === "string") {
return message;
}
// Extract message from nested `err.error.message` (e.g. Google Vertex wrappers)
const nestedMessage =
"error" in err &&
err.error &&
typeof err.error === "object" &&
typeof (err.error as { message?: unknown }).message === "string"
? ((err.error as { message: string }).message ?? "")
: "";
if (nestedMessage) {
return nestedMessage;
}
}
return "";
}
function getErrorCause(err: unknown): unknown {
if (!err || typeof err !== "object" || !("cause" in err)) {
return undefined;
}
return (err as { cause?: unknown }).cause;
}
/** Classify rate-limit / overloaded from symbolic error codes like RESOURCE_EXHAUSTED. */
function classifyFailoverReasonFromSymbolicCode(raw: string | undefined): FailoverReason | null {
const normalized = raw?.trim().toUpperCase();
if (!normalized) {
return null;
}
switch (normalized) {
case "RESOURCE_EXHAUSTED":
case "RATE_LIMIT":
case "RATE_LIMITED":
case "RATE_LIMIT_EXCEEDED":
case "TOO_MANY_REQUESTS":
case "THROTTLED":
case "THROTTLING":
case "THROTTLINGEXCEPTION":
case "THROTTLING_EXCEPTION":
return "rate_limit";
case "OVERLOADED":
case "OVERLOADED_ERROR":
return "overloaded";
default:
return null;
}
}
function hasTimeoutHint(err: unknown): boolean {
if (!err) {
return false;
@@ -160,6 +214,12 @@ export function resolveFailoverReasonFromError(err: unknown): FailoverReason | n
return statusReason;
}
// Check symbolic error codes (e.g. RESOURCE_EXHAUSTED from Google APIs)
const symbolicCodeReason = classifyFailoverReasonFromSymbolicCode(getErrorCode(err));
if (symbolicCodeReason) {
return symbolicCodeReason;
}
const code = (getErrorCode(err) ?? "").toUpperCase();
if (
[
@@ -181,6 +241,14 @@ export function resolveFailoverReasonFromError(err: unknown): FailoverReason | n
if (isTimeoutError(err)) {
return "timeout";
}
// Walk into error cause chain (e.g. AbortError wrapping a rate-limit cause)
const cause = getErrorCause(err);
if (cause && cause !== err) {
const causeReason = resolveFailoverReasonFromError(cause);
if (causeReason) {
return causeReason;
}
}
if (!message) {
return null;
}