Skip to content

Commit 2ca478e

Browse files
committed
fix(api): detach anthropic and minimax external abort listeners after each stream
1 parent 3d92d06 commit 2ca478e

4 files changed

Lines changed: 405 additions & 335 deletions

File tree

src/api/providers/__tests__/anthropic.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,29 @@ describe("AnthropicHandler", () => {
599599
expect(addEventListenerSpy.mock.calls[0][2]).toEqual({ once: true })
600600
})
601601

602+
it("should remove the external abort listener when the stream completes", async () => {
603+
const controller = new AbortController()
604+
const addEventListenerSpy = vitest.spyOn(controller.signal, "addEventListener")
605+
const removeEventListenerSpy = vitest.spyOn(controller.signal, "removeEventListener")
606+
607+
mockCreate.mockImplementationOnce(async () => asyncStreamFrom([]))
608+
609+
const stream = handler.createMessage(
610+
systemPrompt,
611+
[{ role: "user", content: "Hello" }],
612+
makeCreateMessageMetadata({ abortSignal: controller.signal }),
613+
)
614+
615+
await collectStream(stream)
616+
617+
expect(addEventListenerSpy).toHaveBeenCalledTimes(1)
618+
const [event, listener] = addEventListenerSpy.mock.calls[0]
619+
expect(event).toBe("abort")
620+
// The same retained callback must be detached once the stream is done.
621+
expect(removeEventListenerSpy).toHaveBeenCalledTimes(1)
622+
expect(removeEventListenerSpy).toHaveBeenCalledWith("abort", listener)
623+
})
624+
602625
it("should join beta headers with a comma for the default model", async () => {
603626
mockCreate.mockImplementationOnce(async () => asyncStreamFrom([]))
604627

src/api/providers/__tests__/minimax.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,29 @@ describe("MiniMaxHandler", () => {
557557
// The listener is registered once so it detaches itself when the signal aborts.
558558
expect(addEventListenerSpy.mock.calls[0][2]).toEqual({ once: true })
559559
})
560+
561+
it("should remove the external abort listener when the stream completes", async () => {
562+
const controller = new AbortController()
563+
const addEventListenerSpy = vitest.spyOn(controller.signal, "addEventListener")
564+
const removeEventListenerSpy = vitest.spyOn(controller.signal, "removeEventListener")
565+
566+
mockCreate.mockResolvedValueOnce(asyncStreamFrom([]))
567+
568+
const stream = handler.createMessage(
569+
"system prompt",
570+
[{ role: "user", content: "Hello" }],
571+
makeCreateMessageMetadata({ abortSignal: controller.signal }),
572+
)
573+
574+
await expect(collectStream(stream)).resolves.toEqual([])
575+
576+
expect(addEventListenerSpy).toHaveBeenCalledTimes(1)
577+
const [event, listener] = addEventListenerSpy.mock.calls[0]
578+
expect(event).toBe("abort")
579+
// The same retained callback must be detached once the stream is done.
580+
expect(removeEventListenerSpy).toHaveBeenCalledTimes(1)
581+
expect(removeEventListenerSpy).toHaveBeenCalledWith("abort", listener)
582+
})
560583
})
561584

562585
describe("Model Configuration", () => {

0 commit comments

Comments
 (0)