/** * Read the NDJSON progress stream the generation routes return. * * Lines arrive as {"type":"status"|"result"|"error"}. A chunk can split a line * anywhere, so the tail is held back until its newline shows up. */ export async function readJobStream(body:ReadableStream, on:{status?:(text:string)=>void;result?:(value:unknown)=>void}){ const reader=body.getReader(),decoder=new TextDecoder(); let buffer='',result:unknown=null,failure=''; const take=(line:string)=>{ if(!line.trim())return; let event:{type?:string;data?:unknown}; try{event=JSON.parse(line);}catch{return;} if(event.type==='status'&&typeof event.data==='string')on.status?.(event.data); if(event.type==='error')failure=String(event.data||'Generation failed.'); if(event.type==='result'){result=event.data;on.result?.(event.data);} }; for(;;){ const {value,done}=await reader.read(); if(done)break; buffer+=decoder.decode(value,{stream:true}); const lines=buffer.split('\n'); buffer=lines.pop()||''; for(const line of lines)take(line); } take(buffer); if(failure)throw new Error(failure); return result; }