@@ -4,54 +4,43 @@ import { JSONRPCMessage, JSONRPCMessageSchema } from '../types.js';
44 * Buffers a continuous stdio stream into discrete JSON-RPC messages.
55 */
66export class ReadBuffer {
7- private _buffer ?: Buffer ;
7+ private _validLines : string [ ] = [ ] ;
8+ private _lastIncompleteLine : string = '' ;
89
910 append ( chunk : Buffer ) : void {
10- this . _buffer = filterNonJsonLines ( this . _buffer ? Buffer . concat ( [ this . _buffer , chunk ] ) : chunk ) ;
11+ this . _processChunk ( chunk ) ;
1112 }
1213
1314 readMessage ( ) : JSONRPCMessage | null {
14- if ( ! this . _buffer ) {
15+ if ( this . _validLines . length === 0 ) {
1516 return null ;
1617 }
17-
18- const index = this . _buffer . indexOf ( '\n' ) ;
19- if ( index === - 1 ) {
20- return null ;
21- }
22-
23- const line = this . _buffer . toString ( 'utf8' , 0 , index ) . replace ( / \r $ / , '' ) ;
24- this . _buffer = this . _buffer . subarray ( index + 1 ) ;
18+ const line = this . _validLines . shift ( ) ! ;
2519 return deserializeMessage ( line ) ;
2620 }
2721
2822 clear ( ) : void {
29- this . _buffer = undefined ;
23+ this . _validLines = [ ] ;
24+ this . _lastIncompleteLine = '' ;
25+ }
26+
27+ private _processChunk ( newChunk : Buffer ) : void {
28+ // Combine any previously incomplete line with the new chunk
29+ const combinedText = this . _lastIncompleteLine + newChunk . toString ( 'utf8' ) ;
30+ const newLines = combinedText . split ( '\n' ) ;
31+
32+ // The last element may be incomplete, so store it for the next chunk
33+ this . _lastIncompleteLine = newLines . pop ( ) ?? '' ;
34+ const completedLines = newLines . filter ( looksLikeJson ) ;
35+ this . _validLines . push ( ...completedLines ) ;
3036 }
3137}
3238
3339/**
34- * Filters out any lines that are not valid JSON objects from the given buffer.
35- * Retains the last line in case it is incomplete.
36- * @param buffer The buffer to filter.
37- * @returns A new buffer containing only valid JSON object lines and the last line.
40+ * Checks if a line looks like a JSON object.
41+ * @param line The line to check.
42+ * @returns True if the line looks like a JSON object, false otherwise.
3843 */
39- function filterNonJsonLines ( buffer : Buffer ) : Buffer {
40- const text = buffer . toString ( 'utf8' ) ;
41- const lines = text . split ( '\n' ) ;
42-
43- // Pop the last line - it may be incomplete (no trailing newline yet)
44- const incompleteLine = lines . pop ( ) ?? '' ;
45-
46- // Filter complete lines to only keep those that look like JSON objects
47- const validLines = lines . filter ( looksLikeJson ) ;
48-
49- // Reconstruct: valid JSON lines + incomplete line
50- const filteredText = validLines . length > 0 ? validLines . join ( '\n' ) + '\n' + incompleteLine : incompleteLine ;
51-
52- return Buffer . from ( filteredText , 'utf8' ) ;
53- }
54-
5544function looksLikeJson ( line : string ) : boolean {
5645 const trimmed = line . trim ( ) ;
5746 return trimmed . startsWith ( '{' ) && trimmed . endsWith ( '}' ) ;
0 commit comments