char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
521a64cd0
char/aliceai-t5-moe
1import { 2SSE_DATA_PREFIX , 3SSE_DONE_MARKER , 4SSE_LINE_SEPARATOR , 5SSE_RECORD_SEPARATOR 6} from '$lib/constants' ; 7 8/** 9* Minimal SSE-with-JSON stream iterator. 10* 11* Yields one event per `\n\n`-separated record. Each event payload is the 12* decoded `data:` field after JSON-parsing. A `[DONE]` sentinel terminates 13* the stream early. Malformed records - any record whose `data:` payload 14* fails `JSON.parse` - are skipped silently: usually a transient mid-stream 15* fault that the caller should not have to special-case, and the noise of 16* logging every occurrence on long-running streams outweighs the diagnostic 17* value. 18* 19* Less ambitious than ChatService.handleStreamResponse (no resume, no byte 20* offset tracking) - suitable for one-shot streams like `/tools?stream=true` 21* where the consumer just reads chunks until done. 22*/ 23 24export interface SseJsonEvent < T = unknown > { 25data :T ; 26} 27 28/** 29* Splits a raw SSE byte buffer into complete records on the blank-line 30* boundary, returning the leftover partial record separately. Shared by the 31* record-based consumers (parseSseJsonStream, models.service). 32*/ 33export function splitSseRecords ( buffer :string ) :{ records :string []; rest :string } { 34const parts = buffer . split ( SSE_RECORD_SEPARATOR ); 35 36return { records :parts . slice ( 0 , - 1 ), rest :parts [ parts . length - 1 ] ?? '' }; 37} 38 39/** 40* Extracts the joined `data:` payload from one SSE record (the data lines 41* concatenated with a newline), or an empty string when the record carries 42* no data lines. Used by models.service to parse status envelopes. 43*/ 44export function extractSseDataPayload ( record :string ) :string { 45return record 46. split ( SSE_LINE_SEPARATOR ) 47. filter (( line ) => line . startsWith ( SSE_DATA_PREFIX )) 48. map (( line ) => line . slice ( SSE_DATA_PREFIX . length ). trim ()) 49. join ( SSE_LINE_SEPARATOR ); 50} 51 52export async function * parseSseJsonStream < T = unknown > ( 53response :Response , 54signal ?:AbortSignal 55) :AsyncGenerator < SseJsonEvent < T >> { 56const reader = response . body ?. getReader (); 57 58if ( ! reader ) return ; 59 60const decoder = new TextDecoder (); 61 62let buffer = '' ; 63 64try { 65while ( true ) { 66if ( signal ?. aborted ) return ; 67 68const { done, value} = await reader . read (); 69 70if ( done ) break ; 71 72buffer += decoder . decode ( value , { stream :true }); 73const { records, rest} = splitSseRecords ( buffer ); 74 75buffer = rest ; 76 77for ( const record of records ) { 78if ( ! record ) continue ; 79 80for ( const line of record . split ( SSE_LINE_SEPARATOR )) { 81if ( ! line . startsWith ( SSE_DATA_PREFIX )) continue ; 82 83const payload = line . slice ( SSE_DATA_PREFIX . length ). trim (); 84 85if ( payload === SSE_DONE_MARKER ) return ; 86 87if ( ! payload ) continue ; 88 89try { 90yield { data :JSON . parse ( payload ) as T }; 91} catch { 92// Skip silently per the function contract above. 93} 94} 95} 96} 97} finally { 98try { 99reader . releaseLock (); 100} catch ( error ) { 101console . error ( '[sse] failed to release reader lock:' , error ); 102} 103} 104}