diff --git a/src/agents/provider-http-errors.test.ts b/src/agents/provider-http-errors.test.ts index 4f8de348596..4c3ab7b8454 100644 --- a/src/agents/provider-http-errors.test.ts +++ b/src/agents/provider-http-errors.test.ts @@ -1,5 +1,5 @@ // Verifies provider HTTP error parsing, redaction, and response-size limits. -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { assertOkOrThrowProviderError, assertOkOrThrowHttpError, @@ -9,6 +9,7 @@ import { ProviderHttpError, readProviderBinaryResponse, readProviderJsonResponse, + readResponseTextLimited, } from "./provider-http-errors.js"; function createStreamingBinaryResponse(params: { @@ -108,6 +109,46 @@ describe("provider error utils", () => { } satisfies Partial); }); + it("releases provider error body reader locks after bounded reads complete", async () => { + const releaseLock = vi.fn(); + const cancel = vi.fn(async () => undefined); + const chunks: Array> = [ + { done: false, value: new TextEncoder().encode("provider error") }, + { done: true, value: undefined }, + ]; + const response = { + body: { + getReader: () => ({ + read: async () => chunks.shift() ?? { done: true, value: undefined }, + cancel, + releaseLock, + }), + }, + } as unknown as Response; + + await expect(readResponseTextLimited(response, 64)).resolves.toBe("provider error"); + expect(cancel).not.toHaveBeenCalled(); + expect(releaseLock).toHaveBeenCalledTimes(1); + }); + + it("cancels and releases provider error body readers after diagnostic truncation", async () => { + const releaseLock = vi.fn(); + const cancel = vi.fn(async () => undefined); + const response = { + body: { + getReader: () => ({ + read: async () => ({ done: false, value: new TextEncoder().encode("provider error") }), + cancel, + releaseLock, + }), + }, + } as unknown as Response; + + await expect(readResponseTextLimited(response, 8)).resolves.toBe("provider"); + expect(cancel).toHaveBeenCalledTimes(1); + expect(releaseLock).toHaveBeenCalledTimes(1); + }); + it("attaches structured provider error metadata", async () => { // API-key-like substrings must be redacted from stored error bodies. const response = new Response( diff --git a/src/agents/provider-http-errors.ts b/src/agents/provider-http-errors.ts index f33c0d80dee..7099b3fd49e 100644 --- a/src/agents/provider-http-errors.ts +++ b/src/agents/provider-http-errors.ts @@ -77,6 +77,9 @@ export async function readResponseTextLimited( // Stop the upstream body once the diagnostic budget is full. await reader.cancel().catch(() => {}); } + try { + reader.releaseLock(); + } catch {} } return text;