fix(agents): trigger model failover on connection-refused and network-unreachable errors

Previously, only ETIMEDOUT / ESOCKETTIMEDOUT / ECONNRESET / ECONNABORTED
were recognised as failover-worthy network errors. Connection-level
failures such as ECONNREFUSED (server down), ENETUNREACH / EHOSTUNREACH
(network disconnected), ENETRESET, and EAI_AGAIN (DNS failure) were
treated as unknown errors and did not advance the fallback chain.

This is particularly impactful when a local fallback model (e.g. Ollama)
is configured: if the remote provider is unreachable due to a network
outage, the gateway should fall back to the local model instead of
returning an error to the user.

Add the missing error codes to resolveFailoverReasonFromError() and
corresponding e2e tests.

Closes #18868
This commit is contained in:
Ayane
2026-02-17 18:09:05 +08:00
committed by Peter Steinberger
parent 3b2ed8fe6f
commit 76ed274aad
2 changed files with 49 additions and 1 deletions

View File

@@ -183,7 +183,19 @@ export function resolveFailoverReasonFromError(err: unknown): FailoverReason | n
}
const code = (getErrorCode(err) ?? "").toUpperCase();
if (["ETIMEDOUT", "ESOCKETTIMEDOUT", "ECONNRESET", "ECONNABORTED"].includes(code)) {
if (
[
"ETIMEDOUT",
"ESOCKETTIMEDOUT",
"ECONNRESET",
"ECONNABORTED",
"ECONNREFUSED",
"ENETUNREACH",
"EHOSTUNREACH",
"ENETRESET",
"EAI_AGAIN",
].includes(code)
) {
return "timeout";
}
if (isTimeoutError(err)) {

View File

@@ -751,6 +751,42 @@ describe("runWithModelFallback", () => {
});
});
it("falls back on ECONNREFUSED (local server down or remote unreachable)", async () => {
await expectFallsBackToHaiku({
provider: "openai",
model: "gpt-4.1-mini",
firstError: Object.assign(new Error("connect ECONNREFUSED 127.0.0.1:11434"), {
code: "ECONNREFUSED",
}),
});
});
it("falls back on ENETUNREACH (network disconnected)", async () => {
await expectFallsBackToHaiku({
provider: "openai",
model: "gpt-4.1-mini",
firstError: Object.assign(new Error("connect ENETUNREACH"), { code: "ENETUNREACH" }),
});
});
it("falls back on EHOSTUNREACH (host unreachable)", async () => {
await expectFallsBackToHaiku({
provider: "openai",
model: "gpt-4.1-mini",
firstError: Object.assign(new Error("connect EHOSTUNREACH"), { code: "EHOSTUNREACH" }),
});
});
it("falls back on EAI_AGAIN (DNS resolution failure)", async () => {
await expectFallsBackToHaiku({
provider: "openai",
model: "gpt-4.1-mini",
firstError: Object.assign(new Error("getaddrinfo EAI_AGAIN api.openai.com"), {
code: "EAI_AGAIN",
}),
});
});
it("falls back on provider abort errors with request-aborted messages", async () => {
await expectFallsBackToHaiku({
provider: "openai",