@@ -5,15 +5,36 @@ defmodule CodexPooler.Gateway.Transports.Streaming.StreamProtocol.PublicResponse
55 alias CodexPooler.Gateway.Runtime.Streaming.BufferTelemetry
66 alias CodexPooler.Gateway.Transports.Streaming.StreamProtocol
77
8+ @ type passthrough_terminal_state :: % {
9+ required ( :event_type ) => String . t ( ) ,
10+ required ( :json_started? ) => boolean ( ) ,
11+ required ( :depth ) => non_neg_integer ( ) ,
12+ required ( :in_string? ) => boolean ( ) ,
13+ required ( :escaped? ) => boolean ( ) ,
14+ required ( :complete? ) => boolean ( )
15+ }
816 @ type state :: % {
917 required ( :buffer ) => binary ( ) ,
1018 required ( :created? ) => boolean ( ) ,
1119 required ( :text_delta? ) => boolean ( ) ,
12- required ( :passthrough? ) => boolean ( )
20+ required ( :passthrough? ) => boolean ( ) ,
21+ required ( :passthrough_terminal ) => passthrough_terminal_state ( ) | nil ,
22+ required ( :passthrough_terminal_kind ) => atom ( ) | nil ,
23+ required ( :passthrough_terminal_seen? ) => boolean ( )
1324 }
1425
1526 @ spec new_state ( ) :: state ( )
16- def new_state , do: % { buffer: "" , created?: false , text_delta?: false , passthrough?: false }
27+ def new_state do
28+ % {
29+ buffer: "" ,
30+ created?: false ,
31+ text_delta?: false ,
32+ passthrough?: false ,
33+ passthrough_terminal: nil ,
34+ passthrough_terminal_kind: nil ,
35+ passthrough_terminal_seen?: false
36+ }
37+ end
1738
1839 @ terminal_buffer_markers [
1940 "data: [DONE]" ,
@@ -40,12 +61,12 @@ defmodule CodexPooler.Gateway.Transports.Streaming.StreamProtocol.PublicResponse
4061
4162 blocks == [ ] and StreamProtocol . oversized_incomplete_sse_block? ( buffer ) ->
4263 record_oversized_incomplete ( byte_size ( buffered_data ) )
43- { buffered_data , % { state | buffer: "" , passthrough?: true } }
64+ { buffered_data , enter_passthrough ( buffered_data , state ) }
4465
4566 StreamProtocol . oversized_incomplete_sse_block? ( buffer ) ->
4667 record_oversized_incomplete ( byte_size ( buffered_data ) )
4768 { iodata , state } = normalize_complete_blocks ( blocks , state )
48- { [ iodata , buffer ] |> IO . iodata_to_binary ( ) , % { state | buffer: "" , passthrough?: true } }
69+ { [ iodata , buffer ] |> IO . iodata_to_binary ( ) , enter_passthrough ( buffer , state ) }
4970
5071 true ->
5172 normalize_blocks ( blocks , buffer , state )
@@ -61,13 +82,165 @@ defmodule CodexPooler.Gateway.Transports.Streaming.StreamProtocol.PublicResponse
6182 passthrough = binary_part ( data , 0 , passthrough_size )
6283 rest = binary_part ( data , passthrough_size , byte_size ( data ) - passthrough_size )
6384
64- state = % { state | passthrough?: false , buffer: "" }
85+ state =
86+ state
87+ |> track_passthrough_terminal_data ( passthrough )
88+ |> then ( & % { & 1 | passthrough?: false , buffer: "" , passthrough_terminal: nil } )
89+
6590 { normalized_rest , state } = normalize_data ( rest , state )
6691
6792 { [ passthrough , normalized_rest ] |> IO . iodata_to_binary ( ) , state }
6893
6994 nil ->
70- { data , state }
95+ { data , track_passthrough_terminal_data ( state , data ) }
96+ end
97+ end
98+
99+ @ spec passthrough_terminal_kind ( state ( ) ) :: atom ( ) | nil
100+ def passthrough_terminal_kind ( % { passthrough_terminal_kind: kind } ) when is_atom ( kind ) , do: kind
101+ def passthrough_terminal_kind ( _state ) , do: nil
102+
103+ defp enter_passthrough ( data , state ) do
104+ data
105+ |> passthrough_terminal_event_type ( )
106+ |> case do
107+ event_type when is_binary ( event_type ) ->
108+ state
109+ |> Map . put ( :passthrough_terminal , new_passthrough_terminal ( event_type ) )
110+ |> track_passthrough_terminal_data ( data )
111+
112+ nil ->
113+ % { state | passthrough_terminal: nil }
114+ end
115+ |> then ( & % { & 1 | buffer: "" , passthrough?: true } )
116+ end
117+
118+ defp new_passthrough_terminal ( event_type ) do
119+ % {
120+ event_type: event_type ,
121+ json_started?: false ,
122+ depth: 0 ,
123+ in_string?: false ,
124+ escaped?: false ,
125+ complete?: false
126+ }
127+ end
128+
129+ defp passthrough_terminal_event_type ( data ) do
130+ { event_type , decoded } = stream_block_event ( data )
131+ type = event_type || decoded_string ( decoded , "type" ) || terminal_marker_type ( data )
132+
133+ if terminal_event? ( type ) , do: type
134+ end
135+
136+ defp terminal_marker_type ( data ) when is_binary ( data ) do
137+ cond do
138+ terminal_marker? ( data , "response.completed" ) -> "response.completed"
139+ terminal_marker? ( data , "response.failed" ) -> "response.failed"
140+ terminal_marker? ( data , "response.incomplete" ) -> "response.incomplete"
141+ terminal_marker? ( data , "error" ) -> "error"
142+ true -> nil
143+ end
144+ end
145+
146+ defp terminal_marker? ( data , type ) do
147+ String . contains? ( data , "event: #{ type } " ) or
148+ String . contains? ( data , "event:#{ type } " ) or
149+ String . contains? ( data , ~s( "type":"#{ type } ") ) or
150+ String . contains? ( data , ~s( "type": "#{ type } ") )
151+ end
152+
153+ defp track_passthrough_terminal_data (
154+ % { passthrough_terminal: % { complete?: false } = terminal } = state ,
155+ data
156+ )
157+ when is_binary ( data ) do
158+ terminal = scan_passthrough_terminal_json ( data , terminal )
159+
160+ state
161+ |> Map . put ( :passthrough_terminal , terminal )
162+ |> maybe_put_passthrough_terminal_kind ( terminal )
163+ |> Map . put (
164+ :passthrough_terminal_seen? ,
165+ state . passthrough_terminal_seen? or terminal . complete?
166+ )
167+ end
168+
169+ defp track_passthrough_terminal_data ( state , _data ) , do: state
170+
171+ defp maybe_put_passthrough_terminal_kind ( state , % { complete?: true , event_type: event_type } ) do
172+ Map . put ( state , :passthrough_terminal_kind , terminal_kind ( event_type ) )
173+ end
174+
175+ defp maybe_put_passthrough_terminal_kind ( state , _terminal ) , do: state
176+
177+ defp terminal_kind ( "response.completed" ) , do: :completed
178+ defp terminal_kind ( "response.incomplete" ) , do: :incomplete
179+ defp terminal_kind ( _event_type ) , do: :failed
180+
181+ defp scan_passthrough_terminal_json ( data , terminal ) ,
182+ do: scan_passthrough_terminal_json ( data , 0 , terminal )
183+
184+ defp scan_passthrough_terminal_json ( data , offset , terminal )
185+ when offset >= byte_size ( data ) or terminal . complete? ,
186+ do: terminal
187+
188+ defp scan_passthrough_terminal_json ( data , offset , % { json_started?: false } = terminal ) do
189+ case :binary . at ( data , offset ) do
190+ ?{ ->
191+ scan_passthrough_terminal_json ( data , offset + 1 , % {
192+ terminal
193+ | json_started?: true ,
194+ depth: 1
195+ } )
196+
197+ _byte ->
198+ scan_passthrough_terminal_json ( data , offset + 1 , terminal )
199+ end
200+ end
201+
202+ defp scan_passthrough_terminal_json (
203+ data ,
204+ offset ,
205+ % { in_string?: true , escaped?: true } = terminal
206+ ) do
207+ scan_passthrough_terminal_json ( data , offset + 1 , % { terminal | escaped?: false } )
208+ end
209+
210+ defp scan_passthrough_terminal_json (
211+ data ,
212+ offset ,
213+ % { in_string?: true , escaped?: false } = terminal
214+ ) do
215+ case :binary . at ( data , offset ) do
216+ ?\\ -> scan_passthrough_terminal_json ( data , offset + 1 , % { terminal | escaped?: true } )
217+ ?" -> scan_passthrough_terminal_json ( data , offset + 1 , % { terminal | in_string?: false } )
218+ _byte -> scan_passthrough_terminal_json ( data , offset + 1 , terminal )
219+ end
220+ end
221+
222+ defp scan_passthrough_terminal_json ( data , offset , terminal ) do
223+ case :binary . at ( data , offset ) do
224+ ?" ->
225+ scan_passthrough_terminal_json ( data , offset + 1 , % { terminal | in_string?: true } )
226+
227+ ?{ ->
228+ scan_passthrough_terminal_json ( data , offset + 1 , % {
229+ terminal
230+ | depth: terminal . depth + 1
231+ } )
232+
233+ ?} when terminal . depth <= 1 ->
234+ % { terminal | depth: 0 , complete?: true }
235+
236+ ?} ->
237+ scan_passthrough_terminal_json ( data , offset + 1 , % {
238+ terminal
239+ | depth: terminal . depth - 1
240+ } )
241+
242+ _byte ->
243+ scan_passthrough_terminal_json ( data , offset + 1 , terminal )
71244 end
72245 end
73246
0 commit comments