mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 00:21:23 +00:00
fix(failover): align abort timeout detection and regressions
This commit is contained in:
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
coerceToFailoverError,
|
||||
describeFailoverError,
|
||||
isTimeoutError,
|
||||
resolveFailoverReasonFromError,
|
||||
} from "./failover-error.js";
|
||||
|
||||
@@ -27,6 +28,22 @@ describe("failover-error", () => {
|
||||
expect(resolveFailoverReasonFromError({ code: "ECONNRESET" })).toBe("timeout");
|
||||
});
|
||||
|
||||
it("infers timeout from abort stop-reason messages", () => {
|
||||
expect(resolveFailoverReasonFromError({ message: "Unhandled stop reason: abort" })).toBe(
|
||||
"timeout",
|
||||
);
|
||||
expect(resolveFailoverReasonFromError({ message: "stop reason: abort" })).toBe("timeout");
|
||||
expect(resolveFailoverReasonFromError({ message: "reason: abort" })).toBe("timeout");
|
||||
});
|
||||
|
||||
it("treats AbortError reason=abort as timeout", () => {
|
||||
const err = Object.assign(new Error("aborted"), {
|
||||
name: "AbortError",
|
||||
reason: "reason: abort",
|
||||
});
|
||||
expect(isTimeoutError(err)).toBe(true);
|
||||
});
|
||||
|
||||
it("coerces failover-worthy errors into FailoverError with metadata", () => {
|
||||
const err = coerceToFailoverError("credit balance too low", {
|
||||
provider: "anthropic",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { classifyFailoverReason, type FailoverReason } from "./pi-embedded-helpers.js";
|
||||
|
||||
const TIMEOUT_HINT_RE =
|
||||
/timeout|timed out|deadline exceeded|context deadline exceeded|stop reason:\s*abort|unhandled stop reason:\s*abort/i;
|
||||
/timeout|timed out|deadline exceeded|context deadline exceeded|stop reason:\s*abort|reason:\s*abort|unhandled stop reason:\s*abort/i;
|
||||
const ABORT_TIMEOUT_RE = /request was aborted|request aborted/i;
|
||||
|
||||
export class FailoverError extends Error {
|
||||
|
||||
@@ -400,6 +400,17 @@ describe("runWithModelFallback", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back on abort errors with reason: abort", async () => {
|
||||
await expectFallsBackToHaiku({
|
||||
provider: "openai",
|
||||
model: "gpt-4.1-mini",
|
||||
firstError: Object.assign(new Error("aborted"), {
|
||||
name: "AbortError",
|
||||
reason: "reason: abort",
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back when message says aborted but error is a timeout", async () => {
|
||||
await expectFallsBackToHaiku({
|
||||
provider: "openai",
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
isFailoverErrorMessage,
|
||||
isImageDimensionErrorMessage,
|
||||
isLikelyContextOverflowError,
|
||||
isTimeoutErrorMessage,
|
||||
isTransientHttpError,
|
||||
parseImageDimensionError,
|
||||
parseImageSizeError,
|
||||
@@ -286,6 +287,15 @@ describe("isFailoverErrorMessage", () => {
|
||||
expect(isFailoverErrorMessage(sample)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("matches abort stop-reason timeout variants", () => {
|
||||
const samples = ["Unhandled stop reason: abort", "stop reason: abort", "reason: abort"];
|
||||
for (const sample of samples) {
|
||||
expect(isTimeoutErrorMessage(sample)).toBe(true);
|
||||
expect(classifyFailoverReason(sample)).toBe("timeout");
|
||||
expect(isFailoverErrorMessage(sample)).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseImageSizeError", () => {
|
||||
|
||||
Reference in New Issue
Block a user