mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
feat(sse): selective upstream 4xx error passthrough util
This commit is contained in:
38
open-sse/utils/upstreamErrorPassthrough.ts
Normal file
38
open-sse/utils/upstreamErrorPassthrough.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Selective upstream 4xx error passthrough (Claude Code auto-recover contract).
|
||||
*
|
||||
* Claude Code matches the upstream error WORDING to auto-disable capabilities
|
||||
* (thinking / output_config) for the rest of the conversation. Wrapping the body
|
||||
* via buildErrorBody() truncates the message and breaks that recovery. For
|
||||
* upstream-originated 4xx errors the body is the provider's public API message —
|
||||
* not our internals — so it is safe and required to relay it verbatim.
|
||||
* OmniRoute-generated errors MUST keep using buildErrorBody() (Hard Rule #12).
|
||||
*/
|
||||
const PASSTHROUGH_MIN = 400;
|
||||
const PASSTHROUGH_MAX = 499;
|
||||
// 401/403/407: auth-adjacent — our own credential context may leak via provider
|
||||
// echoes; keep those sanitized. 400/404/408/413/422/429 carry the capability and
|
||||
// quota wording the client needs.
|
||||
const EXCLUDED_STATUSES = new Set([401, 403, 407]);
|
||||
const INTERNAL_LEAK_RE = /\sat\s\/|node_modules|omniroute\//i;
|
||||
|
||||
export function shouldPassthroughUpstreamError(statusCode: number, upstreamBody: unknown): boolean {
|
||||
if (statusCode < PASSTHROUGH_MIN || statusCode > PASSTHROUGH_MAX) return false;
|
||||
if (EXCLUDED_STATUSES.has(statusCode)) return false;
|
||||
if (!upstreamBody || typeof upstreamBody !== "object") return false;
|
||||
const text = JSON.stringify(upstreamBody);
|
||||
if (INTERNAL_LEAK_RE.test(text)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function buildPassthroughErrorResponse(
|
||||
statusCode: number,
|
||||
upstreamBody: unknown,
|
||||
headers?: Record<string, string>
|
||||
): Response | null {
|
||||
if (!shouldPassthroughUpstreamError(statusCode, upstreamBody)) return null;
|
||||
return new Response(JSON.stringify(upstreamBody), {
|
||||
status: statusCode,
|
||||
headers: { "Content-Type": "application/json", ...(headers || {}) },
|
||||
});
|
||||
}
|
||||
46
tests/unit/upstream-error-passthrough.test.ts
Normal file
46
tests/unit/upstream-error-passthrough.test.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
shouldPassthroughUpstreamError,
|
||||
buildPassthroughErrorResponse,
|
||||
} from "../../open-sse/utils/upstreamErrorPassthrough.ts";
|
||||
|
||||
test("upstream error passthrough", async (t) => {
|
||||
await t.test("4xx com corpo JSON de erro do provider é elegível", () => {
|
||||
const body = {
|
||||
type: "error",
|
||||
error: { type: "invalid_request_error", message: "thinking.type: adaptive is not supported" },
|
||||
};
|
||||
assert.equal(shouldPassthroughUpstreamError(400, body), true);
|
||||
});
|
||||
await t.test("5xx NÃO é elegível (segue sanitizado)", () => {
|
||||
assert.equal(shouldPassthroughUpstreamError(500, { error: { message: "x" } }), false);
|
||||
});
|
||||
await t.test("corpo com cara de vazamento interno (stack trace) NÃO é elegível", () => {
|
||||
assert.equal(
|
||||
shouldPassthroughUpstreamError(400, {
|
||||
error: { message: "Error\n at /usr/lib/node_modules/omniroute/x.js:1" },
|
||||
}),
|
||||
false
|
||||
);
|
||||
});
|
||||
await t.test(
|
||||
"401/407 NÃO são elegíveis (credencial nossa pode vazar em www-authenticate)",
|
||||
() => {
|
||||
assert.equal(shouldPassthroughUpstreamError(401, { error: { message: "bad key" } }), false);
|
||||
}
|
||||
);
|
||||
await t.test("buildPassthroughErrorResponse preserva corpo byte-a-byte", async () => {
|
||||
const body = {
|
||||
type: "error",
|
||||
error: { type: "invalid_request_error", message: "thinking.type: nope" },
|
||||
};
|
||||
const res = buildPassthroughErrorResponse(400, body);
|
||||
assert.ok(res);
|
||||
assert.equal(res.status, 400);
|
||||
assert.deepEqual(await res.json(), body);
|
||||
});
|
||||
await t.test("retorna null quando inelegível", () => {
|
||||
assert.equal(buildPassthroughErrorResponse(500, {}), null);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user