mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 23:32:12 +03:00
feat: enhance error handling for semaphore capacity and implement fallback logic in chat processing
This commit is contained in:
@@ -595,14 +595,39 @@ function toFiniteNumberOrNull(value: unknown): number | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function isSemaphoreTimeoutError(error: unknown): error is Error & { code: string } {
|
||||
function isSemaphoreCapacityError(error: unknown): error is Error & { code: string } {
|
||||
return (
|
||||
!!error &&
|
||||
typeof error === "object" &&
|
||||
(error as { code?: unknown }).code === "SEMAPHORE_TIMEOUT"
|
||||
((error as { code?: unknown }).code === "SEMAPHORE_TIMEOUT" ||
|
||||
(error as { code?: unknown }).code === "SEMAPHORE_QUEUE_FULL")
|
||||
);
|
||||
}
|
||||
|
||||
function createStreamingErrorResult(statusCode: number, message: string, code?: string) {
|
||||
const errorBody = buildErrorBody(statusCode, message);
|
||||
if (code) {
|
||||
errorBody.error.code = code;
|
||||
}
|
||||
|
||||
const body = `data: ${JSON.stringify(errorBody)}\n\ndata: [DONE]\n\n`;
|
||||
|
||||
return {
|
||||
success: false as const,
|
||||
status: statusCode,
|
||||
error: message,
|
||||
response: new Response(body, {
|
||||
status: statusCode,
|
||||
headers: {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache, no-transform",
|
||||
Connection: "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function wrapReadableStreamWithFinalize<T>(
|
||||
readable: ReadableStream<T>,
|
||||
finalize: () => void
|
||||
@@ -2998,16 +3023,13 @@ export async function handleChatCore({
|
||||
);
|
||||
} catch (error) {
|
||||
trackPendingRequest(model, provider, connectionId, false);
|
||||
if (isSemaphoreTimeoutError(error)) {
|
||||
if (isSemaphoreCapacityError(error)) {
|
||||
appendRequestLog({
|
||||
model,
|
||||
provider,
|
||||
connectionId,
|
||||
status: `FAILED ${error.code}`,
|
||||
}).catch(() => {});
|
||||
if (isCombo) {
|
||||
throw error;
|
||||
}
|
||||
const failureMessage = error.message || "Semaphore timeout";
|
||||
persistAttemptLogs({
|
||||
status: HTTP_STATUS.RATE_LIMITED,
|
||||
@@ -3018,7 +3040,14 @@ export async function handleChatCore({
|
||||
cacheSource: "upstream",
|
||||
});
|
||||
persistFailureUsage(HTTP_STATUS.RATE_LIMITED, error.code);
|
||||
return createErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage);
|
||||
const result = stream
|
||||
? createStreamingErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage, error.code)
|
||||
: createErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage);
|
||||
return {
|
||||
...result,
|
||||
errorType: "account_semaphore_capacity",
|
||||
errorCode: error.code,
|
||||
};
|
||||
}
|
||||
const failureStatus =
|
||||
error.name === "AbortError"
|
||||
|
||||
@@ -791,6 +791,25 @@ async function handleSingleModelChat(
|
||||
return result.response;
|
||||
}
|
||||
|
||||
if (result.errorType === "account_semaphore_capacity") {
|
||||
// Local concurrency pressure is not an upstream quota failure. Prefer another
|
||||
// account when possible; pinned combo steps fall through to combo orchestration.
|
||||
if (hasForcedConnection) {
|
||||
return result.response;
|
||||
}
|
||||
|
||||
log.warn(
|
||||
"AUTH",
|
||||
`Account ${accountId}... at local concurrency cap, trying fallback account`
|
||||
);
|
||||
excludedConnectionIds.add(credentials.connectionId);
|
||||
lastError = result.error;
|
||||
lastStatus = result.status;
|
||||
requestRetryLastError = result.error;
|
||||
requestRetryLastStatus = result.status;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Emergency fallback for budget exhaustion (402 / billing / quota keywords):
|
||||
// reroute to a free model (default provider/model: nvidia + openai/gpt-oss-120b) exactly once.
|
||||
if (!runtimeOptions.emergencyFallbackTried) {
|
||||
|
||||
Reference in New Issue
Block a user