test(provider-transport-fetch): cover split large SSE event

(cherry picked from commit 3f147ae5ca)
This commit is contained in:
Ayaan Zaidi
2026-06-30 10:40:07 -07:00
committed by Dallin Romney
parent e15a93ab34
commit b6516e4d3d
2 changed files with 40 additions and 8 deletions
@@ -1369,6 +1369,42 @@ describe("buildGuardedModelFetch", () => {
expect(refreshTimeout).toHaveBeenCalledTimes(2);
});
it("handles a valid large SSE event split before its boundary", async () => {
const payload = { text: "x".repeat(70 * 1024) };
const encoder = new TextEncoder();
fetchWithSsrFGuardMock.mockResolvedValue({
response: new Response(
new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify(payload)}`));
controller.enqueue(encoder.encode("\n\n"));
controller.close();
},
}),
{ headers: { "content-type": "text/event-stream" } },
),
finalUrl: "https://openrouter.ai/api/v1/chat/completions",
release: vi.fn(async () => undefined),
});
const model = {
id: "gpt-5.4",
provider: "openrouter",
api: "openai-completions",
baseUrl: "https://openrouter.ai/api/v1",
} as unknown as Model<"openai-completions">;
const response = await buildGuardedModelFetch(model)(
"https://openrouter.ai/api/v1/chat/completions",
{ method: "POST" },
);
const items = [];
for await (const item of Stream.fromSSEResponse(response, new AbortController())) {
items.push(item);
}
expect(items).toEqual([payload]);
});
it("errors on oversized SSE body without event boundary in sanitizer", async () => {
const oversized = "x".repeat(16 * 1024 * 1024 + 1024);
const encoder = new TextEncoder();
+4 -8
View File
@@ -51,12 +51,8 @@ const log = createSubsystemLogger("provider-transport-fetch");
* without Content-Length. */
const SSE_SYNTHESIZE_JSON_MAX_BYTES = 16 * 1024 * 1024;
/** Max bytes for the internal SSE sanitization buffer between event boundaries.
* A single legitimate event (e.g. a large reasoning summary on the chatgpt-responses
* API) can far exceed 64 KiB, so bound this at the same 16 MiB ceiling as the
* JSON-synthesis path: only a genuinely boundary-less (hostile/broken) stream trips
* the guard, not a real large event. */
const SSE_SANITIZE_BUFFER_MAX_BYTES = 16 * 1024 * 1024;
/** Max decoded characters buffered while waiting for the next SSE event boundary. */
const SSE_SANITIZE_BUFFER_MAX_CHARS = 16 * 1024 * 1024;
const BLOCKED_EXACT_ORIGIN_TRUST_HOSTNAME_LABELS = new Set(["instance-data"]);
const PLAIN_DECIMAL_NUMBER_RE = /^\d+(?:\.\d+)?$/;
@@ -179,9 +175,9 @@ function sanitizeOpenAISdkSseResponse(
for (;;) {
const boundary = findSseEventBoundary(buffer);
if (!boundary) {
if (buffer.length > SSE_SANITIZE_BUFFER_MAX_BYTES) {
if (buffer.length > SSE_SANITIZE_BUFFER_MAX_CHARS) {
throw new Error(
`SSE response exceeded max buffer size (${SSE_SANITIZE_BUFFER_MAX_BYTES} bytes) without event boundary`,
`SSE response exceeded max buffer size (${SSE_SANITIZE_BUFFER_MAX_CHARS} chars) without event boundary`,
);
}
return enqueued;