Skip to content

Commit be7e3de

Browse files
committed
fix: preserve stream abort cancellation
1 parent ec0c403 commit be7e3de

2 files changed

Lines changed: 35 additions & 3 deletions

File tree

src/client.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -741,7 +741,9 @@ export class OpenAI {
741741

742742
const security = options.__security ?? { bearerAuth: true };
743743
const controller = new AbortController();
744-
const response = await this.fetchWithAuth(url, req, timeout, controller, security).catch(castToError);
744+
const response = await this.fetchWithAuth(url, req, timeout, controller, security, options.stream).catch(
745+
castToError,
746+
);
745747
const headersTime = Date.now();
746748

747749
if (response instanceof globalThis.Error) {
@@ -924,6 +926,7 @@ export class OpenAI {
924926
bearerAuth: true,
925927
adminAPIKeyAuth: true,
926928
},
929+
keepAbortListener = false,
927930
): Promise<Response> {
928931
if (this._workloadIdentityAuth && schemes.bearerAuth) {
929932
const headers = init.headers as Headers;
@@ -934,7 +937,7 @@ export class OpenAI {
934937
}
935938
}
936939

937-
const response = await this.fetchWithTimeout(url, init, timeout, controller);
940+
const response = await this.fetchWithTimeout(url, init, timeout, controller, keepAbortListener);
938941

939942
return response;
940943
}
@@ -944,6 +947,7 @@ export class OpenAI {
944947
init: RequestInit | undefined,
945948
ms: number,
946949
controller: AbortController,
950+
keepAbortListener = false,
947951
): Promise<Response> {
948952
const { signal, method, ...options } = init || {};
949953
const abort = this._makeAbort(controller);
@@ -972,7 +976,7 @@ export class OpenAI {
972976
return await this.fetch.call(undefined, url, fetchOptions);
973977
} finally {
974978
clearTimeout(timeout);
975-
if (signal) signal.removeEventListener('abort', abort);
979+
if (signal && !keepAbortListener) signal.removeEventListener('abort', abort);
976980
}
977981
}
978982

tests/index.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,34 @@ describe('retries', () => {
676676
expect(removeEventListener).toHaveBeenCalledWith('abort', addEventListener.mock.calls[0][1]);
677677
});
678678

679+
test('keeps abort listener after fetch resolves for streaming responses', async () => {
680+
const client = new OpenAI({
681+
apiKey: 'My API Key',
682+
adminAPIKey: 'My Admin API Key',
683+
fetch: async () => new Response('data: {"a":1}\n\n'),
684+
});
685+
686+
const externalController = new AbortController();
687+
const requestController = new AbortController();
688+
const addEventListener = jest.spyOn(externalController.signal, 'addEventListener');
689+
const removeEventListener = jest.spyOn(externalController.signal, 'removeEventListener');
690+
691+
await client.fetchWithTimeout(
692+
'http://localhost/foo',
693+
{ signal: externalController.signal },
694+
1000,
695+
requestController,
696+
true,
697+
);
698+
699+
expect(addEventListener).toHaveBeenCalledWith('abort', expect.any(Function), { once: true });
700+
expect(removeEventListener).not.toHaveBeenCalled();
701+
702+
externalController.abort();
703+
704+
expect(requestController.signal.aborted).toBe(true);
705+
});
706+
679707
test('retry on timeout', async () => {
680708
let count = 0;
681709
const testFetch = async (

0 commit comments

Comments
 (0)