mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 03:45:46 -06:00
test(provider-transport-fetch): cover split large SSE event
(cherry picked from commit 3f147ae5ca)
This commit is contained in:
committed by
Dallin Romney
parent
e15a93ab34
commit
b6516e4d3d
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user