fix(embeddings): cool down account on hard errors (402/401/5xx) (#10529)

Merged — locally validated (focused embedding-cooldown tests green, gates green). Good parity with the chat path's existing pattern. Thanks!
This commit is contained in:
Bob.Hou
2026-08-21 00:21:11 +08:00
committed by GitHub
parent 424b950856
commit f52fa9dc85
4 changed files with 314 additions and 12 deletions

View File

@@ -10,13 +10,14 @@ import { errorResponse, unavailableResponse } from "@omniroute/open-sse/utils/er
import { HTTP_STATUS } from "@omniroute/open-sse/config/constants.ts";
import * as log from "@/sse/utils/logger";
import { toJsonErrorPayload } from "@/shared/utils/upstreamError";
import { getProviderCredentials, clearRecoveredProviderState } from "@/sse/services/auth";
import {
getCachedProviderNodes,
getComboByName,
getCombos,
getDatabaseSettings,
} from "@/lib/localDb";
getProviderCredentials,
clearRecoveredProviderState,
markAccountUnavailable,
} from "@/sse/services/auth";
import { getCachedProviderNodes } from "@/lib/db/readCache";
import { getComboByName, getCombos } from "@/lib/db/combos";
import { getDatabaseSettings } from "@/lib/db/databaseSettings";
import { resolveProxyForConnection } from "@/lib/db/settings";
import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts";
import { handleComboChat } from "@omniroute/open-sse/services/combo.ts";
@@ -309,7 +310,7 @@ export async function createEmbeddingResponse(
// #10347 — thread the selected connection id so handleEmbedding can cool the
// account on a hard upstream failure (previously always null on /v1/embeddings).
connectionId:
((credentials as { connectionId?: string } | null)?.connectionId) ||
(credentials as { connectionId?: string } | null)?.connectionId ||
options.connectionId ||
connectionIdForProxy ||
null,
@@ -340,6 +341,33 @@ export async function createEmbeddingResponse(
});
}
// #10347: cool down the account on hard errors (402 subscription expired,
// 401 revoked, 403 forbidden, 404 model gone, 429 rate limit, 5xx server
// errors) so the next embedding request skips this account. Mirrors chat.ts
// behavior.
// Skip for 400 (bad request) — the account is fine, the request was wrong.
// Best-effort: don't block the error response on the DB write.
const HARD_ERROR_STATUSES = new Set([401, 402, 403, 404, 429, 500, 502, 503, 504]);
if (
credentials &&
"connectionId" in credentials &&
typeof credentials.connectionId === "string" &&
HARD_ERROR_STATUSES.has(result.status)
) {
markAccountUnavailable(
credentials.connectionId,
result.status,
result.error || "Embedding provider error",
provider,
resolvedModel || null
).catch((err) => {
log.debug(
"EMBED",
`Cooldown write failed for ${provider}/${credentials.connectionId?.slice(0, 8)}: ${err}`
);
});
}
responseHeaders.set("Content-Type", "application/json");
const errorPayload = toJsonErrorPayload(result.error, "Embedding provider error");
return new Response(JSON.stringify(errorPayload), {