fix: improve streaming error propagation and forwarded response logging (#5317)

* Fix: Improve streaming error handling and forwarded response logging

* Fix: fix ESLint error  Strings must use singlequote  quotes

* fix: preserve and log forwarded stream errors

* chore: narrow forwarded stream error fix scope

* fix: make forwardFetchResponse awaitable and forward upstream error text

* Restore original happy path handling

* Remove redundant checks in forwardFetchResponse function

* Don't send anything on parsing error end

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
This commit is contained in:
KKTsN
2026-04-06 04:01:47 +08:00
committed by GitHub
parent d96d1451ab
commit c9c652eece
8 changed files with 96 additions and 25 deletions
+61 -2
View File
@@ -1,6 +1,31 @@
import { describe, test, expect } from '@jest/globals';
import { afterEach, describe, test, expect, jest } from '@jest/globals';
import { once } from 'node:events';
import { PassThrough } from 'node:stream';
import { Response } from 'node-fetch';
import { CHAT_COMPLETION_SOURCES } from '../src/constants';
import { flattenSchema } from '../src/util';
import { flattenSchema, forwardFetchResponse } from '../src/util';
function createMockExpressResponse() {
const response = new PassThrough();
response.statusCode = 200;
response.statusMessage = '';
return response;
}
async function collectResponseBody(response) {
const chunks = [];
response.on('data', chunk => chunks.push(Buffer.from(chunk)));
await once(response, 'finish');
return Buffer.concat(chunks).toString('utf8');
}
afterEach(() => {
jest.restoreAllMocks();
});
describe('flattenSchema', () => {
test('should return the schema if it is not an object', () => {
@@ -105,3 +130,37 @@ describe('flattenSchema', () => {
expect(flattenSchema(schema, 'some-other-api')).toEqual(expected);
});
});
describe('forwardFetchResponse', () => {
test('should log JSON error bodies and return the original body for non-2xx streaming responses', async () => {
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined);
const body = JSON.stringify({ error: { message: 'Forbidden by upstream policy' }, detail: 'policy_denied' });
const response = createMockExpressResponse();
const bodyPromise = collectResponseBody(response);
await forwardFetchResponse(new Response(body, {
status: 403,
statusText: 'Forbidden',
}), response);
expect(await bodyPromise).toBe(body);
expect(response.statusCode).toBe(403);
expect(warnSpy).toHaveBeenCalledWith(`Streaming request failed with status 403 Forbidden: ${body}`);
});
test('should log plain text error bodies and return the original body for non-2xx streaming responses', async () => {
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined);
const body = 'Plain text upstream failure';
const response = createMockExpressResponse();
const bodyPromise = collectResponseBody(response);
await forwardFetchResponse(new Response(body, {
status: 502,
statusText: 'Bad Gateway',
}), response);
expect(await bodyPromise).toBe(body);
expect(response.statusCode).toBe(502);
expect(warnSpy).toHaveBeenCalledWith(`Streaming request failed with status 502 Bad Gateway: ${body}`);
});
});