Source file sse.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
type event = { event : string option; data : string list }
type state = { buffer : string }

let empty = { buffer = "" }

let normalize_newlines input =
  input |> String.split_on_char '\r' |> String.concat ""

let split_complete_frames buffer =
  let normalized = normalize_newlines buffer in
  let rec scan acc start =
    let rec find_separator index =
      if index + 1 >= String.length normalized then None
      else if normalized.[index] = '\n' && normalized.[index + 1] = '\n' then
        Some index
      else find_separator (index + 1)
    in
    match find_separator start with
    | None ->
        ( List.rev acc,
          String.sub normalized start (String.length normalized - start) )
    | Some index ->
        let frame = String.sub normalized start (index - start) in
        scan (frame :: acc) (index + 2)
  in
  scan [] 0

let parse_line (event, data) line =
  if line = "" || line.[0] = ':' then (event, data)
  else
    match String.index_opt line ':' with
    | None -> if line = "event" then (Some "", data) else (event, data)
    | Some index ->
        let name = String.sub line 0 index in
        let value_start =
          if index + 1 < String.length line && line.[index + 1] = ' ' then
            index + 2
          else index + 1
        in
        let value =
          String.sub line value_start (String.length line - value_start)
        in
        if name = "event" then (Some value, data)
        else if name = "data" then (event, data @ [ value ])
        else (event, data)

let parse_frame frame =
  let event, data =
    frame |> String.split_on_char '\n' |> List.fold_left parse_line (None, [])
  in
  { event; data }

let feed state chunk =
  let frames, remaining = split_complete_frames (state.buffer ^ chunk) in
  ({ buffer = remaining }, List.map parse_frame frames)

let finish state =
  if state.buffer = "" then [] else [ parse_frame state.buffer ]

let data_string event = String.concat "\n" event.data
let is_done event = String.trim (data_string event) = "[DONE]"