
























@@ -1130,6 +1130,37 @@ describe("buildGuardedModelFetch", () => {
11301130expect(items).toEqual([{ ok: true }]);
11311131});
113211321133+it("handles a large transport chunk containing many valid small SSE events", async () => {
1134+// Regression: one TCP read can deliver >64 KiB of already-delimited SSE
1135+// events; the cap must apply only to the unterminated tail, not the full chunk.
1136+const eventCount = 5_000;
1137+const manyEvents = `data: ${JSON.stringify({ ok: true })}\n\n`.repeat(eventCount);
1138+fetchWithSsrFGuardMock.mockResolvedValue({
1139+response: new Response(manyEvents, {
1140+headers: { "content-type": "text/event-stream" },
1141+}),
1142+finalUrl: "https://openrouter.ai/api/v1/chat/completions",
1143+release: vi.fn(async () => undefined),
1144+});
1145+const model = {
1146+id: "gpt-5.4",
1147+provider: "openrouter",
1148+api: "openai-completions",
1149+baseUrl: "https://openrouter.ai/api/v1",
1150+} as unknown as Model<"openai-completions">;
1151+1152+const response = await buildGuardedModelFetch(model)(
1153+"https://openrouter.ai/api/v1/chat/completions",
1154+{ method: "POST" },
1155+);
1156+const items: unknown[] = [];
1157+for await (const item of Stream.fromSSEResponse(response, new AbortController())) {
1158+items.push(item);
1159+}
1160+expect(items.length).toBe(eventCount);
1161+expect(items[0]).toEqual({ ok: true });
1162+});
1163+11331164it("synthesizes SSE frames for JSON bodies returned to streaming OpenAI SDK requests", async () => {
11341165fetchWithSsrFGuardMock.mockResolvedValue({
11351166response: new Response(' {"ok": true} ', {
@@ -1338,6 +1369,102 @@ describe("buildGuardedModelFetch", () => {
13381369expect(refreshTimeout).toHaveBeenCalledTimes(2);
13391370});
134013711372+it("errors on oversized SSE body without event boundary in sanitizer", async () => {
1373+const oversized = "x".repeat(65 * 1024);
1374+const encoder = new TextEncoder();
1375+fetchWithSsrFGuardMock.mockResolvedValue({
1376+response: new Response(
1377+new ReadableStream({
1378+start(controller) {
1379+controller.enqueue(encoder.encode(oversized));
1380+controller.close();
1381+},
1382+}),
1383+{ headers: { "content-type": "text/event-stream" } },
1384+),
1385+finalUrl: "https://openrouter.ai/api/v1/chat/completions",
1386+release: vi.fn(async () => undefined),
1387+});
1388+const model = {
1389+id: "gpt-5.4",
1390+provider: "openrouter",
1391+api: "openai-completions",
1392+baseUrl: "https://openrouter.ai/api/v1",
1393+} as unknown as Model<"openai-completions">;
1394+1395+const response = await buildGuardedModelFetch(model)(
1396+"https://openrouter.ai/api/v1/chat/completions",
1397+{ method: "POST" },
1398+);
1399+1400+const reader = response.body?.getReader();
1401+let caught: unknown = null;
1402+try {
1403+while (true) {
1404+const { done } = await reader!.read();
1405+if (done) {
1406+break;
1407+}
1408+}
1409+} catch (e) {
1410+caught = e;
1411+}
1412+expect(caught).toBeTruthy();
1413+expect(String(caught)).toMatch(/exceeded max buffer size/i);
1414+});
1415+1416+it("errors on oversized streaming JSON body without content-length in SSE synthesis", async () => {
1417+const CHUNK = 1024 * 1024;
1418+let sends = 0;
1419+fetchWithSsrFGuardMock.mockResolvedValue({
1420+response: new Response(
1421+new ReadableStream({
1422+pull(controller) {
1423+if (sends < 17) {
1424+sends++;
1425+controller.enqueue(new Uint8Array(CHUNK));
1426+} else {
1427+controller.close();
1428+}
1429+},
1430+}),
1431+{ headers: { "content-type": "application/json" } },
1432+),
1433+finalUrl: "https://openrouter.ai/api/v1/chat/completions",
1434+release: vi.fn(async () => undefined),
1435+});
1436+const model = {
1437+id: "moonshotai/kimi-k2.6",
1438+provider: "openrouter",
1439+api: "openai-completions",
1440+baseUrl: "https://openrouter.ai/api/v1",
1441+} as unknown as Model<"openai-completions">;
1442+1443+const response = await buildGuardedModelFetch(model)(
1444+"https://openrouter.ai/api/v1/chat/completions",
1445+{
1446+method: "POST",
1447+headers: { "content-type": "application/json" },
1448+body: JSON.stringify({ model: "moonshotai/kimi-k2.6", stream: true }),
1449+},
1450+);
1451+1452+const reader = response.body?.getReader();
1453+let caught: unknown = null;
1454+try {
1455+while (true) {
1456+const { done } = await reader!.read();
1457+if (done) {
1458+break;
1459+}
1460+}
1461+} catch (e) {
1462+caught = e;
1463+}
1464+expect(caught).toBeTruthy();
1465+expect(String(caught)).toMatch(/exceeded.*bytes while synthesizing SSE/i);
1466+});
1467+13411468describe("long retry-after handling", () => {
13421469const anthropicModel = {
13431470id: "sonnet-4.6",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。