From aafdc4d4c6e2fda59194fec2832d91bf268cdb55 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Mon, 10 Aug 2026 11:18:14 -0300 Subject: [PATCH] fix(images): normalize image endpoint error format (#9981) (#10020) Co-authored-by: diegosouzapw --- .../fixes/9981-image-error-normalization.md | 1 + src/app/api/v1/images/generations/route.ts | 9 +-- .../[provider]/images/generations/route.ts | 9 +-- tests/unit/image-generation-route.test.ts | 61 +++++++++++++++++++ 4 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 changelog.d/fixes/9981-image-error-normalization.md diff --git a/changelog.d/fixes/9981-image-error-normalization.md b/changelog.d/fixes/9981-image-error-normalization.md new file mode 100644 index 0000000000..022d808ce2 --- /dev/null +++ b/changelog.d/fixes/9981-image-error-normalization.md @@ -0,0 +1 @@ +- fix(images): normalize terminal upstream errors via OpenAI-standard type/code (#9981) diff --git a/src/app/api/v1/images/generations/route.ts b/src/app/api/v1/images/generations/route.ts index 66d29aab19..7f32969d99 100644 --- a/src/app/api/v1/images/generations/route.ts +++ b/src/app/api/v1/images/generations/route.ts @@ -308,10 +308,11 @@ async function postHandler(request, context) { } const errorPayload = toJsonErrorPayload((result as any).error, "Image generation provider error"); - return new Response(JSON.stringify(errorPayload), { - status: (result as any).status, - headers: { "Content-Type": "application/json" }, - }); + const message = + typeof errorPayload?.error?.message === "string" + ? errorPayload.error.message + : "Image generation provider error"; + return errorResponse((result as any).status, message); } export const POST = withInjectionGuard(postHandler); diff --git a/src/app/api/v1/providers/[provider]/images/generations/route.ts b/src/app/api/v1/providers/[provider]/images/generations/route.ts index 5dcd9dbcee..1016e08bd0 100644 --- a/src/app/api/v1/providers/[provider]/images/generations/route.ts +++ b/src/app/api/v1/providers/[provider]/images/generations/route.ts @@ -119,8 +119,9 @@ export async function POST(request, { params }) { } const errorPayload = toJsonErrorPayload((result as any).error, "Image generation provider error"); - return new Response(JSON.stringify(errorPayload), { - status: (result as any).status, - headers: { "Content-Type": "application/json" }, - }); + const message = + typeof errorPayload?.error?.message === "string" + ? errorPayload.error.message + : "Image generation provider error"; + return errorResponse((result as any).status, message); } diff --git a/tests/unit/image-generation-route.test.ts b/tests/unit/image-generation-route.test.ts index 192a13dec0..f136078704 100644 --- a/tests/unit/image-generation-route.test.ts +++ b/tests/unit/image-generation-route.test.ts @@ -701,6 +701,67 @@ test("provider-scoped image generation POST uses the shared 401 account fallback ]); }); +test("v1 image generation POST normalizes a terminal upstream 401 to the OpenAI-standard error shape", async () => { + await seedConnection("openai", { apiKey: "single-expired-image-key" }); + + globalThis.fetch = async (url, options: RequestInit = {}) => { + assert.equal(String(url), "https://api.openai.com/v1/images/generations"); + const authorization = new Headers(options.headers).get("authorization") ?? ""; + assert.equal(authorization, "Bearer single-expired-image-key"); + return new Response(JSON.stringify({ error: { message: "expired access token" } }), { + status: 401, + headers: { "content-type": "application/json" }, + }); + }; + + const response = await imageRoute.POST( + new Request("http://localhost/api/v1/images/generations", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ model: "openai/gpt-image-2", prompt: "normalize terminal 401" }), + }) + ); + const body = (await response.json()) as ErrorResponseBody; + + assert.equal(response.status, 401); + assert.deepEqual(body.error, { + message: "expired access token", + type: "authentication_error", + code: "invalid_api_key", + }); +}); + +test("provider-scoped image generation POST normalizes a terminal upstream 401 to the OpenAI-standard error shape", async () => { + await seedConnection("openai", { apiKey: "provider-single-expired-key" }); + + globalThis.fetch = async (url, options: RequestInit = {}) => { + assert.equal(String(url), "https://api.openai.com/v1/images/generations"); + const authorization = new Headers(options.headers).get("authorization") ?? ""; + assert.equal(authorization, "Bearer provider-single-expired-key"); + return new Response(JSON.stringify({ error: { message: "expired provider token" } }), { + status: 401, + headers: { "content-type": "application/json" }, + }); + }; + + const response = await providerImageRoute.POST( + new Request("http://localhost/api/v1/providers/openai/images/generations", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ model: "gpt-image-2", prompt: "normalize provider terminal 401" }), + }), + { params: Promise.resolve({ provider: "openai" }) } + ); + const body = (await response.json()) as ErrorResponseBody; + + assert.equal(response.status, 401); + assert.deepEqual(body.error, { + message: "expired provider token", + type: "authentication_error", + code: "invalid_api_key", + }); +}); + test("v1 image generation POST refreshes an expired Antigravity token before dispatch", async () => { await seedConnection("antigravity", { authType: "oauth",