mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-19 06:45:50 +00:00
fix: support CRLF SSE frame parsing (#223)
This commit is contained in:
@@ -63,11 +63,15 @@ export function parseSSEFrames(buffer: string): {
|
|||||||
const frames: SSEFrame[] = []
|
const frames: SSEFrame[] = []
|
||||||
let pos = 0
|
let pos = 0
|
||||||
|
|
||||||
// SSE frames are delimited by double newlines
|
// SSE frames are delimited by an empty line. Support LF and CRLF streams.
|
||||||
let idx: number
|
const frameDelimiter = /\r?\n\r?\n/g
|
||||||
while ((idx = buffer.indexOf('\n\n', pos)) !== -1) {
|
frameDelimiter.lastIndex = pos
|
||||||
const rawFrame = buffer.slice(pos, idx)
|
|
||||||
pos = idx + 2
|
let delimiterMatch: RegExpExecArray | null
|
||||||
|
while ((delimiterMatch = frameDelimiter.exec(buffer)) !== null) {
|
||||||
|
const frameEnd = delimiterMatch.index
|
||||||
|
const rawFrame = buffer.slice(pos, frameEnd)
|
||||||
|
pos = frameEnd + delimiterMatch[0].length
|
||||||
|
|
||||||
// Skip empty frames
|
// Skip empty frames
|
||||||
if (!rawFrame.trim()) continue
|
if (!rawFrame.trim()) continue
|
||||||
@@ -75,7 +79,13 @@ export function parseSSEFrames(buffer: string): {
|
|||||||
const frame: SSEFrame = {}
|
const frame: SSEFrame = {}
|
||||||
let isComment = false
|
let isComment = false
|
||||||
|
|
||||||
for (const line of rawFrame.split('\n')) {
|
for (const rawLine of rawFrame.split('\n')) {
|
||||||
|
// Normalize CRLF lines in mixed-line-ending streams.
|
||||||
|
const line =
|
||||||
|
rawLine[rawLine.length - 1] === '\r'
|
||||||
|
? rawLine.slice(0, -1)
|
||||||
|
: rawLine
|
||||||
|
|
||||||
if (line.startsWith(':')) {
|
if (line.startsWith(':')) {
|
||||||
// SSE comment (e.g., `:keepalive`)
|
// SSE comment (e.g., `:keepalive`)
|
||||||
isComment = true
|
isComment = true
|
||||||
|
|||||||
49
src/cli/transports/__tests__/SSETransport.test.ts
Normal file
49
src/cli/transports/__tests__/SSETransport.test.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test'
|
||||||
|
import { parseSSEFrames } from '../SSETransport.js'
|
||||||
|
|
||||||
|
describe('parseSSEFrames', () => {
|
||||||
|
test('parses LF-delimited frames', () => {
|
||||||
|
const input = 'event: client_event\ndata: {"ok":true}\n\n'
|
||||||
|
const { frames, remaining } = parseSSEFrames(input)
|
||||||
|
|
||||||
|
expect(remaining).toBe('')
|
||||||
|
expect(frames).toEqual([
|
||||||
|
{
|
||||||
|
event: 'client_event',
|
||||||
|
data: '{"ok":true}',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('parses CRLF-delimited frames and strips trailing carriage returns', () => {
|
||||||
|
const input =
|
||||||
|
'event: client_event\r\ndata: {"ok":true}\r\nid: 7\r\n\r\nevent: keepalive\r\ndata: ping\r\n\r\n'
|
||||||
|
const { frames, remaining } = parseSSEFrames(input)
|
||||||
|
|
||||||
|
expect(remaining).toBe('')
|
||||||
|
expect(frames).toEqual([
|
||||||
|
{
|
||||||
|
event: 'client_event',
|
||||||
|
data: '{"ok":true}',
|
||||||
|
id: '7',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: 'keepalive',
|
||||||
|
data: 'ping',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('keeps incomplete trailing frame in remaining buffer for CRLF streams', () => {
|
||||||
|
const input = 'event: client_event\r\ndata: {"ok":true}\r\n\r\ndata: {"tail":1}\r\n'
|
||||||
|
const { frames, remaining } = parseSSEFrames(input)
|
||||||
|
|
||||||
|
expect(frames).toEqual([
|
||||||
|
{
|
||||||
|
event: 'client_event',
|
||||||
|
data: '{"ok":true}',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
expect(remaining).toBe('data: {"tail":1}\r\n')
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user