fix(agents): release provider error body readers

This commit is contained in:
Vincent Koc
2026-06-19 08:35:09 +02:00
parent 94b710ac00
commit e802fb8a9f
2 changed files with 45 additions and 1 deletions

View File

@@ -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<ProviderHttpError>);
});
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<ReadableStreamReadResult<Uint8Array>> = [
{ 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(

View File

@@ -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;