mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
abort(reason) rejects the upstream fetch with the raw reason, which is
often a bare string ("request_signal_aborted", "Client disconnected: ...")
carrying no `name` or `status`. The chatCore catch block only recognized
`error.name === "AbortError"`, so those aborts fell through to the 502
provider-failure default and were surfaced as `FAILED 502 / Bad Gateway`
in the client response, request logs, and usage records.
Classify the caught error with the existing isLocalStreamLifecycleError
helper (expanded by #7908 to cover AbortError plus the known abort reason
strings) so every client-abort shape maps to `499 Request aborted`.
Status-normalization follow-up to #7908, which already excluded these
aborts from provider circuit-breaker and cooldown accounting.
Co-authored-by: xiaolong.835 <xiaolong.835@bytedance.com>
This commit is contained in:
1
changelog.d/fixes/7907-chatcore-499-string-abort.md
Normal file
1
changelog.d/fixes/7907-chatcore-499-string-abort.md
Normal file
@@ -0,0 +1 @@
|
||||
- **Chat Core**: client aborts that reject the upstream fetch with a raw string reason (e.g. `request_signal_aborted`, `Client disconnected`) are now reported as `499 Request aborted` in the response, request logs, and usage records instead of `FAILED 502 / Bad Gateway`. Follow-up to #7908 for #7907.
|
||||
@@ -319,6 +319,7 @@ import {
|
||||
stripMarkdownCodeFence,
|
||||
} from "../utils/aiSdkCompat.ts";
|
||||
import { generateRequestId } from "@/shared/utils/requestId";
|
||||
import { isLocalStreamLifecycleError } from "@/shared/utils/circuitBreaker";
|
||||
import { extractFacts } from "@/lib/memory/extraction";
|
||||
import { handleToolCallExecution } from "@/lib/skills/interception";
|
||||
import { OMNIROUTE_RESPONSE_HEADERS } from "@/shared/constants/headers";
|
||||
@@ -3112,18 +3113,21 @@ export async function handleChatCore({
|
||||
errorCode: error.code,
|
||||
};
|
||||
}
|
||||
const failureStatus =
|
||||
error.name === "AbortError"
|
||||
? 499
|
||||
: error.name === "TimeoutError" || error.name === "BodyTimeoutError"
|
||||
? HTTP_STATUS.GATEWAY_TIMEOUT
|
||||
: error.status && typeof error.status === "number"
|
||||
? error.status
|
||||
: HTTP_STATUS.BAD_GATEWAY;
|
||||
const failureMessage =
|
||||
error.name === "AbortError"
|
||||
? "Request aborted"
|
||||
: formatProviderError(error, provider, model, failureStatus);
|
||||
// abort(reason) can reject the upstream fetch with a raw string reason
|
||||
// (e.g. "request_signal_aborted") that has no `name`/`status`; classify
|
||||
// via isLocalStreamLifecycleError so those map to 499 instead of falling
|
||||
// through to the 502 provider-failure default.
|
||||
const isRequestAborted = isLocalStreamLifecycleError(error);
|
||||
const failureStatus = isRequestAborted
|
||||
? 499
|
||||
: error.name === "TimeoutError" || error.name === "BodyTimeoutError"
|
||||
? HTTP_STATUS.GATEWAY_TIMEOUT
|
||||
: error.status && typeof error.status === "number"
|
||||
? error.status
|
||||
: HTTP_STATUS.BAD_GATEWAY;
|
||||
const failureMessage = isRequestAborted
|
||||
? "Request aborted"
|
||||
: formatProviderError(error, provider, model, failureStatus);
|
||||
const upstreamErrorCode = getUpstreamErrorIdentifier(error);
|
||||
// Tag our own deadline timeouts (fetch-start TimeoutError / body BodyTimeoutError,
|
||||
// both surfaced as a 504) as "upstream_timeout" so the cooldown layer can tell a
|
||||
@@ -3152,7 +3156,7 @@ export async function handleChatCore({
|
||||
claudeCacheMeta: claudePromptCacheLogMeta,
|
||||
cacheSource: "upstream",
|
||||
});
|
||||
if (error.name === "AbortError") {
|
||||
if (isRequestAborted) {
|
||||
streamController.handleError(error);
|
||||
return createErrorResult(499, "Request aborted");
|
||||
}
|
||||
|
||||
@@ -2367,6 +2367,28 @@ test("chatCore maps upstream aborts to request-aborted errors", async () => {
|
||||
assert.equal(result.error, "Request aborted");
|
||||
});
|
||||
|
||||
test("chatCore maps raw string abort reasons to 499, not 502 (#7907)", async () => {
|
||||
// abort(reason) rejects the upstream fetch with the raw reason — often a
|
||||
// bare string with no `name`/`status`. It must map to 499 like a named
|
||||
// AbortError, not fall through to the 502 provider-failure default.
|
||||
const { result } = await invokeChatCore({
|
||||
provider: "openai",
|
||||
model: "gpt-4o-mini",
|
||||
body: {
|
||||
model: "gpt-4o-mini",
|
||||
stream: false,
|
||||
messages: [{ role: "user", content: "abort me with a string reason" }],
|
||||
},
|
||||
responseFactory() {
|
||||
throw "request_signal_aborted";
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(result.success, false);
|
||||
assert.equal(result.status, 499);
|
||||
assert.equal(result.error, "Request aborted");
|
||||
});
|
||||
|
||||
test("chatCore returns streaming responses without waiting for upstream completion", async () => {
|
||||
const encoder = new TextEncoder();
|
||||
let closeUpstream: (() => void) | null = null;
|
||||
|
||||
Reference in New Issue
Block a user