diff --git a/.sdk-source.json b/.sdk-source.json index 5584dfb..cb58940 100644 --- a/.sdk-source.json +++ b/.sdk-source.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceCommit": "dcb64b2f48febb318d116d7abe5e12d51cb2913c", + "sourceCommit": "60be0cdcc0ccb8cbc28c3840c0a3980370b41f5b", "contract": "contracts/2026.07/public-sdk-contract.json", "packages": [ "typescript" diff --git a/typescript/src/streaming.ts b/typescript/src/streaming.ts index d3e33f3..ead5ba8 100644 --- a/typescript/src/streaming.ts +++ b/typescript/src/streaming.ts @@ -69,7 +69,7 @@ async function* parseSSE(response: Response): AsyncGenerator { `SSE frame exceeds the ${MAX_SSE_FRAME_BYTES.toLocaleString("en-US")}-byte limit.` ); } - const lines = buffer.split("\n"); + const lines = buffer.split(/\r?\n/); // Keep the last potentially incomplete line in the buffer buffer = lines.pop() ?? ""; @@ -80,7 +80,7 @@ async function* parseSSE(response: Response): AsyncGenerator { `SSE frame exceeds the ${MAX_SSE_FRAME_BYTES.toLocaleString("en-US")}-byte limit.` ); } - if (line === "") { + if (line === "" || line === "\r") { // Blank line = end of event if (currentData.length > 0) { yield { diff --git a/typescript/tests/streaming.test.ts b/typescript/tests/streaming.test.ts index e41e0fa..49d02ca 100644 --- a/typescript/tests/streaming.test.ts +++ b/typescript/tests/streaming.test.ts @@ -153,6 +153,21 @@ describe("ChatStream", () => { ]); }); + test("handles CRLF split between network chunks", async () => { + const response = chunkedSSEResponse([ + "event: chunk\r", + "\ndata: \"Hello\"\r", + "\n\r", + "\nevent: done\r\ndata: \r\n\r\n", + ]); + const events = []; + for await (const event of new ChatStream(response)) events.push(event); + expect(events).toEqual([ + { type: "chunk", content: "Hello" }, + { type: "done" }, + ]); + }); + test("ignores comment lines", async () => { const response = sseResponse( `: this is a comment\nevent: chunk\ndata: "Hi"\n\nevent: done\ndata: \n\n`