mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-14 19:22:32 +03:00
feat(sse): restate agentrouter quota 403/400 to 429 before classification
This commit is contained in:
@@ -184,6 +184,7 @@ import {
|
||||
DEFAULT_MAX_TOKENS,
|
||||
STREAM_DISCONNECT_GRACE_PERIOD_MS,
|
||||
} from "../config/constants.ts";
|
||||
import { applyStatusRestatement } from "../config/upstreamStatusRestatement.ts";
|
||||
import { createRecoverableStream, makeContinuationBody } from "../services/streamRecovery.ts";
|
||||
import {
|
||||
resolveResilienceSettings,
|
||||
@@ -3650,6 +3651,27 @@ export async function handleChatCore({
|
||||
upstreamErrorType = details.errorType as string | undefined;
|
||||
}
|
||||
|
||||
// Gateways like agentrouter misstate temporary quota exhaustion as 403/400,
|
||||
// which downstream classification treats as AUTH_ERROR and clients like
|
||||
// Claude Code treat as permanent. Restate to 429 (+ synthetic Retry-After)
|
||||
// BEFORE any classification so both the fallback engine and the surfaced
|
||||
// client status see a retryable error. Registry-scoped per provider.
|
||||
const restatement = applyStatusRestatement({
|
||||
provider,
|
||||
status: statusCode,
|
||||
message,
|
||||
body: upstreamErrorBody,
|
||||
retryAfterMs,
|
||||
});
|
||||
if (restatement.ruleId) {
|
||||
statusCode = restatement.status;
|
||||
retryAfterMs = restatement.retryAfterMs;
|
||||
log?.info?.(
|
||||
"STATUS_RESTATE",
|
||||
`${provider} ${restatement.fromStatus}→${statusCode} (${restatement.ruleId})`
|
||||
);
|
||||
}
|
||||
|
||||
const signatureRecovery = await recoverAnthropicThinkingSignature({
|
||||
provider,
|
||||
statusCode,
|
||||
|
||||
@@ -107,3 +107,20 @@ test("R9: registry exposes agentrouter so future gateways copy the one-line reci
|
||||
const rules = statusRestatementRegistry.get("agentrouter");
|
||||
assert.ok(rules && rules.length > 0);
|
||||
});
|
||||
|
||||
test("R10: chatCore wires applyStatusRestatement into the providerFailure block", async () => {
|
||||
// chatCore is a god-file that cannot be imported standalone in unit tests
|
||||
// (side-effectful DB/env wiring), so the wiring contract is asserted at the
|
||||
// source level: the hook must exist, run against the parsed error, and
|
||||
// reassign both statusCode and retryAfterMs BEFORE classification.
|
||||
const { readFile } = await import("node:fs/promises");
|
||||
const src = await readFile(
|
||||
new URL("../../open-sse/handlers/chatCore.ts", import.meta.url),
|
||||
"utf8"
|
||||
);
|
||||
assert.match(src, /applyStatusRestatement\(/, "chatCore must call applyStatusRestatement");
|
||||
const hookIndex = src.indexOf("applyStatusRestatement(");
|
||||
const classifyIndex = src.indexOf("classifyProviderError(statusCode");
|
||||
assert.ok(hookIndex > -1 && classifyIndex > -1 && hookIndex < classifyIndex,
|
||||
"restatement must run BEFORE classifyProviderError so fallback sees the corrected status");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user