mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-20 22:02:19 +03:00
fix(providers): surface real Antigravity upstream error detail (#13591)
Antigravity double-wraps its own errors: buildAntigravityUpstreamError() replaced error.message with the generic "Antigravity upstream error (400)" template and buried the real Gemini-dialect detail under upstream_details. chatCore's shared failure path re-parses that already-wrapped body with the generic parseUpstreamError(), which only reads the outer error.message, so the generic text is what ends up in createErrorResult, the persisted call logs, and the [ProxyEgress] antigravity status=error lines. buildAntigravityUpstreamError() now composes error.message from the real upstream detail when present, so both the HTTP response and every log/telemetry consumer that reads error.message get the actual detail. Regression test: tests/unit/antigravityUpstreamError.test.ts
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
- **fix(providers):** Antigravity error responses and logs now surface the real upstream
|
||||
message (e.g. Gemini field-path rejections) instead of the generic "Antigravity upstream
|
||||
error (400)" placeholder (#13591) — thanks @afonsoft
|
||||
@@ -21,6 +21,20 @@ const GEO_BLOCKED_HINT =
|
||||
"call the model API. Route antigravity/agy egress through a proxy in a " +
|
||||
"supported region (e.g. US/EU) or use a different provider.";
|
||||
|
||||
/**
|
||||
* Extract the real upstream error message (e.g. Google's Gemini-dialect field-path
|
||||
* rejection) from a parsed Antigravity `upstream_details`-shaped body, so callers can
|
||||
* surface it directly in `error.message` instead of only nesting it under
|
||||
* `upstream_details` — the generic `parseUpstreamError()` re-parser used by the shared
|
||||
* chatCore failure path only reads the outer `error.message` (#13591).
|
||||
*/
|
||||
function extractUpstreamMessage(details: unknown): string | null {
|
||||
if (!details || typeof details !== "object") return null;
|
||||
const err = (details as { error?: { message?: unknown } }).error;
|
||||
const msg = err && typeof err.message === "string" ? err.message : null;
|
||||
return msg && msg.trim() ? msg.trim() : null;
|
||||
}
|
||||
|
||||
export function buildAntigravityUpstreamError(status: number, statusText: string, rawBody: string) {
|
||||
let upstreamDetails: unknown;
|
||||
try {
|
||||
@@ -36,5 +50,9 @@ export function buildAntigravityUpstreamError(status: number, statusText: string
|
||||
upstreamDetails
|
||||
);
|
||||
}
|
||||
return buildErrorBody(status, `Antigravity upstream error (${status})${suffix}`, upstreamDetails);
|
||||
const upstreamMessage = extractUpstreamMessage(upstreamDetails);
|
||||
const message = upstreamMessage
|
||||
? `Antigravity upstream error (${status}): ${upstreamMessage}`
|
||||
: `Antigravity upstream error (${status})${suffix}`;
|
||||
return buildErrorBody(status, message, upstreamDetails);
|
||||
}
|
||||
|
||||
75
tests/unit/antigravityUpstreamError.test.ts
Normal file
75
tests/unit/antigravityUpstreamError.test.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
// Regression coverage for issue #13591: Antigravity double-wraps its own errors, so
|
||||
// parseUpstreamError() (the shared chatCore failure-classification path) only ever saw
|
||||
// the generic "Antigravity upstream error (400)" template instead of the real Gemini
|
||||
// upstream detail buried under `upstream_details`. buildAntigravityUpstreamError() must
|
||||
// surface the real upstream message as `error.message` directly.
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { buildAntigravityUpstreamError } from "../../open-sse/executors/antigravityUpstreamError.ts";
|
||||
import { parseUpstreamError } from "../../open-sse/utils/error.ts";
|
||||
|
||||
test("issue #13591: parseUpstreamError surfaces the real upstream detail for an Antigravity-wrapped 400", async () => {
|
||||
const rawUpstreamGeminiBody = JSON.stringify({
|
||||
error: {
|
||||
code: 400,
|
||||
message:
|
||||
"Invalid value at 'tools[0].function_declarations[0].parameters.properties[0].value' " +
|
||||
'(type.googleapis.com/google.ai.generativelanguage.v1beta.Schema), "string"',
|
||||
status: "INVALID_ARGUMENT",
|
||||
},
|
||||
});
|
||||
|
||||
const wrappedErrorBody = buildAntigravityUpstreamError(400, "", rawUpstreamGeminiBody);
|
||||
|
||||
assert.ok(
|
||||
JSON.stringify(wrappedErrorBody).includes("function_declarations"),
|
||||
"sanity: buildAntigravityUpstreamError should embed the real upstream detail somewhere in the body"
|
||||
);
|
||||
assert.ok(
|
||||
(wrappedErrorBody as { error: { message: string } }).error.message.includes(
|
||||
"function_declarations"
|
||||
),
|
||||
"buildAntigravityUpstreamError should surface the real upstream detail directly in error.message"
|
||||
);
|
||||
|
||||
const wrappedResponse = new Response(JSON.stringify(wrappedErrorBody), {
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
const parsed = await parseUpstreamError(wrappedResponse, "antigravity");
|
||||
|
||||
assert.ok(
|
||||
parsed.message.includes("function_declarations"),
|
||||
`expected parseUpstreamError to surface the real upstream detail, but got: ${JSON.stringify(parsed.message)}`
|
||||
);
|
||||
});
|
||||
|
||||
test("issue #13591: geo-blocked branch keeps its explicit hint message untouched", () => {
|
||||
const geoBlockedBody = JSON.stringify({
|
||||
error: {
|
||||
message: "User location is not supported for the API use.",
|
||||
},
|
||||
});
|
||||
|
||||
const wrappedErrorBody = buildAntigravityUpstreamError(400, "Bad Request", geoBlockedBody) as {
|
||||
error: { message: string };
|
||||
};
|
||||
|
||||
assert.ok(
|
||||
wrappedErrorBody.error.message.includes(
|
||||
"not offered from this server's current egress location"
|
||||
),
|
||||
"geo-blocked responses must keep the operator-facing hint, not the raw upstream text"
|
||||
);
|
||||
});
|
||||
|
||||
test("issue #13591: non-JSON upstream body falls back to the generic templated message without throwing", () => {
|
||||
const htmlErrorPage = "<html><body>502 Bad Gateway</body></html>";
|
||||
|
||||
const wrappedErrorBody = buildAntigravityUpstreamError(502, "Bad Gateway", htmlErrorPage) as {
|
||||
error: { message: string };
|
||||
};
|
||||
|
||||
assert.equal(wrappedErrorBody.error.message, "Antigravity upstream error (502): Bad Gateway");
|
||||
});
|
||||
Reference in New Issue
Block a user