(response, onEvent)
| 31 | } |
| 32 | |
| 33 | const processRunSseResponse = async (response, onEvent) => { |
| 34 | if (!response || !response.body) return |
| 35 | const reader = response.body.getReader() |
| 36 | const decoder = new TextDecoder() |
| 37 | let buffer = '' |
| 38 | let eventType = 'message' |
| 39 | let eventId = null |
| 40 | let dataLines = [] |
| 41 | |
| 42 | const dispatch = () => { |
| 43 | if (dataLines.length === 0) return |
| 44 | const dataText = dataLines.join('\n') |
| 45 | try { |
| 46 | const parsed = JSON.parse(dataText) |
| 47 | onEvent(eventType, parsed, eventId) |
| 48 | } catch (e) { |
| 49 | console.warn('Failed to parse run SSE data:', e, dataText) |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | try { |
| 54 | while (true) { |
| 55 | const { done, value } = await reader.read() |
| 56 | if (done) break |
| 57 | buffer += decoder.decode(value, { stream: true }) |
| 58 | const lines = buffer.split('\n') |
| 59 | buffer = lines.pop() || '' |
| 60 | |
| 61 | for (const rawLine of lines) { |
| 62 | const line = rawLine.replace(/\r$/, '') |
| 63 | if (!line) { |
| 64 | dispatch() |
| 65 | eventType = 'message' |
| 66 | eventId = null |
| 67 | dataLines = [] |
| 68 | continue |
| 69 | } |
| 70 | |
| 71 | if (line.startsWith(':')) { |
| 72 | continue |
| 73 | } |
| 74 | if (line.startsWith('event:')) { |
| 75 | eventType = line.slice(6).trim() || 'message' |
| 76 | } else if (line.startsWith('data:')) { |
| 77 | dataLines.push(line.slice(5).trimStart()) |
| 78 | } else if (line.startsWith('id:')) { |
| 79 | eventId = line.slice(3).trim() |
| 80 | } |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | dispatch() |
| 85 | } finally { |
| 86 | try { |
| 87 | reader.releaseLock() |
| 88 | } catch { |
| 89 | // ignore |
| 90 | } |
no test coverage detected