Skip to content

Commit f621443

Browse files
committed
Address review: cover LM Studio early consumer termination
createMessage's finally block cancels the request-local SDK signal and detaches the external abort listener, but no test pinned the early consumer exit (break/return while the SDK stream is still pending): the existing coverage only exercised normal completion and external-signal aborts. New test consumes one chunk from a stream that then stays pending, calls stream.return(), and asserts the request-local SDK signal becomes aborted and the exact registered external listener is removed (by reference, per the established pattern). Negative control: removing the finally-block abort fails exactly this test (isolated run). Verification at this head: 27/27 green in lm-studio.spec.ts; tsc --noEmit 0 errors; eslint --max-warnings=0 clean with suppression counts unchanged; prettier clean.
1 parent dc09b59 commit f621443

1 file changed

Lines changed: 30 additions & 0 deletions

File tree

‎src/api/providers/__tests__/lm-studio.spec.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,36 @@ describe("LmStudioHandler abort wiring", () => {
446446
expect(removeSpy).toHaveBeenCalledWith("abort", listener)
447447
})
448448

449+
it("cancels the request and detaches the external listener when the consumer stops early", async () => {
450+
const handler = new LmStudioHandler(options)
451+
vitest.spyOn(handler, "countTokens").mockResolvedValue(1)
452+
const create = lastCreate()
453+
const external = new AbortController()
454+
const addSpy = vitest.spyOn(external.signal, "addEventListener")
455+
const removeSpy = vitest.spyOn(external.signal, "removeEventListener")
456+
// The SDK stream yields one chunk and then stays pending: only the
457+
// consumer's early exit can end this request, so this pins the
458+
// finally-block cleanup on the early-termination path.
459+
create.mockResolvedValue(
460+
(async function* () {
461+
yield { choices: [{ delta: { content: "first" } }] }
462+
await new Promise<never>(() => {})
463+
})(),
464+
)
465+
466+
const stream = handler.createMessage("system", [], { taskId: "t1", abortSignal: external.signal })
467+
const first = await stream.next()
468+
expect(first.value).toEqual({ type: "text", text: "first" })
469+
470+
await stream.return(undefined) // consumer stops early; finally must cancel the request
471+
472+
const opts = create.mock.calls[0][1]
473+
expect(opts?.signal.aborted).toBe(true) // the request-local SDK signal is aborted
474+
const listener = addSpy.mock.calls.find(([type]) => type === "abort")?.[1]
475+
expect(typeof listener).toBe("function")
476+
expect(removeSpy).toHaveBeenCalledWith("abort", listener) // the bridge is detached by reference
477+
})
478+
449479
it("counts the input tokens for the system prompt plus every message content block", async () => {
450480
const handler = new LmStudioHandler(options)
451481
const countSpy = vitest.spyOn(handler, "countTokens").mockResolvedValue(1)

0 commit comments

Comments
 (0)