@@ -27,19 +27,17 @@ defmodule CodexPooler.Gateway.Runtime.Finalization.ResponseUsage do
2727
2828 @ spec from_websocket_body ( binary ( ) ) :: usage ( )
2929 def from_websocket_body ( body ) when is_binary ( body ) do
30- case usage_from_sse_lines ( body ) do
31- % { status: "usage_known" } = usage ->
32- usage
33-
34- _missing ->
35- from_delimited_json_messages ( body ) ||
36- usage_from_retained_usage_fragment ( body ) ||
37- % { status: "usage_unknown" , source: "websocket_usage_missing" }
30+ line_or_message_usage =
31+ best_usage ( usage_from_sse_lines ( body ) , from_delimited_json_messages ( body ) )
32+
33+ case best_usage ( line_or_message_usage , usage_from_retained_usage_fragment ( body ) ) do
34+ % { status: "usage_known" } = usage -> usage
35+ _missing -> % { status: "usage_unknown" , source: "websocket_usage_missing" }
3836 end
3937 end
4038
4139 defp from_sse_body ( body , missing_source ) do
42- usage_from_sse_lines ( body ) || usage_from_retained_usage_fragment ( body ) ||
40+ best_usage ( usage_from_sse_lines ( body ) , usage_from_retained_usage_fragment ( body ) ) ||
4341 % { status: "usage_unknown" , source: missing_source }
4442 end
4543
@@ -54,14 +52,33 @@ defmodule CodexPooler.Gateway.Runtime.Finalization.ResponseUsage do
5452 end
5553
5654 defp usage_from_retained_usage_fragment ( body ) do
57- with { :ok , input_tokens } <- retained_int ( body , ~r/ "input_tokens"\s *:\s *(\d +)/ ) ,
58- { :ok , output_tokens } <- retained_int ( body , ~r/ "output_tokens"\s *:\s *(\d +)/ ) ,
59- { :ok , total_tokens } <- retained_int ( body , ~r/ "total_tokens"\s *:\s *(\d +)/ ) do
55+ body
56+ |> retained_usage_candidates ( )
57+ |> Enum . reduce ( nil , fn candidate , acc ->
58+ case usage_from_retained_usage_candidate ( candidate ) do
59+ % { status: "usage_known" } = usage -> usage
60+ _missing -> acc
61+ end
62+ end )
63+ end
64+
65+ defp retained_usage_candidates ( body ) do
66+ ~r/ "usage"\s *:/
67+ |> Regex . scan ( body , return: :index )
68+ |> Enum . map ( fn [ { offset , _length } | _captures ] ->
69+ binary_part ( body , offset , byte_size ( body ) - offset )
70+ end )
71+ end
72+
73+ defp usage_from_retained_usage_candidate ( candidate ) do
74+ with { :ok , input_tokens } <- retained_int ( candidate , ~r/ "input_tokens"\s *:\s *(\d +)/ ) ,
75+ { :ok , output_tokens } <- retained_int ( candidate , ~r/ "output_tokens"\s *:\s *(\d +)/ ) ,
76+ { :ok , total_tokens } <- retained_int ( candidate , ~r/ "total_tokens"\s *:\s *(\d +)/ ) do
6077 % {
6178 "input_tokens" => input_tokens ,
62- "cached_input_tokens" => retained_cached_input_tokens ( body ) ,
79+ "cached_input_tokens" => retained_cached_input_tokens ( candidate ) ,
6380 "output_tokens" => output_tokens ,
64- "reasoning_tokens" => retained_int_or_zero ( body , ~r/ "reasoning_tokens"\s *:\s *(\d +)/ ) ,
81+ "reasoning_tokens" => retained_int_or_zero ( candidate , ~r/ "reasoning_tokens"\s *:\s *(\d +)/ ) ,
6582 "total_tokens" => total_tokens
6683 }
6784 |> normalize_usage ( % { } )
@@ -99,10 +116,10 @@ defmodule CodexPooler.Gateway.Runtime.Finalization.ResponseUsage do
99116 end
100117
101118 defp usage_from_json_lines ( lines ) do
102- Enum . find_value ( lines , fn line ->
119+ Enum . reduce ( lines , nil , fn line , acc ->
103120 case Jason . decode ( line ) do
104- { :ok , decoded } -> usage_from_decoded ( decoded , false )
105- { :error , _reason } -> nil
121+ { :ok , decoded } -> latest_usage_candidate ( decoded , acc )
122+ { :error , _reason } -> acc
106123 end
107124 end )
108125 end
@@ -117,17 +134,62 @@ defmodule CodexPooler.Gateway.Runtime.Finalization.ResponseUsage do
117134 do: normalize_usage ( usage , response )
118135
119136 defp usage_from_decoded ( % { "output" => output } , default ) when is_list ( output ) do
120- Enum . find_value ( output , & usage_from_decoded ( & 1 , false ) ) || maybe_default_usage ( default )
137+ latest_usage ( output ) || maybe_default_usage ( default )
121138 end
122139
123140 defp usage_from_decoded ( _decoded , default ) , do: maybe_default_usage ( default )
124141
142+ defp latest_usage ( items ) do
143+ Enum . reduce ( items , nil , fn item , acc ->
144+ case usage_from_decoded ( item , false ) do
145+ % { status: "usage_known" } = usage -> usage
146+ % { status: "usage_unknown" } = usage -> acc || usage
147+ nil -> acc
148+ end
149+ end )
150+ end
151+
152+ defp latest_usage_candidate ( decoded , acc ) do
153+ case usage_from_decoded ( decoded , false ) do
154+ % { status: "usage_known" } = usage -> usage
155+ % { status: "usage_unknown" } = usage -> acc || usage
156+ nil -> acc
157+ end
158+ end
159+
160+ defp best_usage ( nil , nil ) , do: nil
161+
162+ defp best_usage ( % { status: "usage_known" } = usage , nil ) , do: usage
163+ defp best_usage ( nil , % { status: "usage_known" } = usage ) , do: usage
164+
165+ defp best_usage (
166+ % { status: "usage_known" } = line_usage ,
167+ % { status: "usage_known" } = fragment_usage
168+ ) do
169+ if total_tokens ( fragment_usage ) > total_tokens ( line_usage ) ,
170+ do: fragment_usage ,
171+ else: line_usage
172+ end
173+
174+ defp best_usage ( % { status: "usage_unknown" } , % { status: "usage_known" } = usage ) , do: usage
175+ defp best_usage ( % { status: "usage_known" } = usage , % { status: "usage_unknown" } ) , do: usage
176+ defp best_usage ( % { status: "usage_unknown" } = usage , nil ) , do: usage
177+ defp best_usage ( nil , % { status: "usage_unknown" } = usage ) , do: usage
178+ defp best_usage ( % { status: "usage_unknown" } = usage , _fragment_usage ) , do: usage
179+ defp best_usage ( _line_usage , % { status: "usage_unknown" } = usage ) , do: usage
180+
181+ defp total_tokens ( % { total_tokens: total_tokens } ) when is_integer ( total_tokens ) , do: total_tokens
182+ defp total_tokens ( _usage ) , do: 0
183+
125184 defp normalize_usage ( usage , envelope ) do
126- with { :ok , input_tokens } <- int_value ( usage [ "input_tokens" ] || usage [ "prompt_tokens" ] ) ,
127- { :ok , cached_input_tokens } <- int_value ( cached_input_tokens ( usage ) ) ,
128- { :ok , output_tokens } <- int_value ( usage [ "output_tokens" ] || usage [ "completion_tokens" ] ) ,
129- { :ok , reasoning_tokens } <- int_value ( usage [ "reasoning_tokens" ] ) ,
130- { :ok , total_tokens } <- int_value ( usage [ "total_tokens" ] ) do
185+ with { :ok , input_tokens } <-
186+ required_int_value ( usage [ "input_tokens" ] || usage [ "prompt_tokens" ] ) ,
187+ { :ok , cached_input_tokens } <- optional_int_value ( cached_input_tokens ( usage ) ) ,
188+ { :ok , output_tokens } <-
189+ required_int_value ( usage [ "output_tokens" ] || usage [ "completion_tokens" ] ) ,
190+ { :ok , reasoning_tokens } <- optional_int_value ( usage [ "reasoning_tokens" ] ) ,
191+ { :ok , total_tokens } <-
192+ total_tokens_value ( usage [ "total_tokens" ] , input_tokens , output_tokens ) do
131193 % {
132194 status: "usage_known" ,
133195 source: "upstream_usage" ,
@@ -160,7 +222,18 @@ defmodule CodexPooler.Gateway.Runtime.Finalization.ResponseUsage do
160222 defp maybe_default_usage ( true ) , do: % { status: "usage_unknown" , source: "usage_missing" }
161223 defp maybe_default_usage ( false ) , do: nil
162224
163- defp int_value ( nil ) , do: { :ok , 0 }
225+ defp total_tokens_value ( nil , input_tokens , output_tokens ) ,
226+ do: { :ok , input_tokens + output_tokens }
227+
228+ defp total_tokens_value ( value , _input_tokens , _output_tokens ) , do: required_int_value ( value )
229+
230+ defp required_int_value ( nil ) , do: :error
231+ defp required_int_value ( value ) , do: int_value ( value )
232+
233+ defp optional_int_value ( nil ) , do: { :ok , 0 }
234+ defp optional_int_value ( value ) , do: int_value ( value )
235+
236+ defp int_value ( nil ) , do: :error
164237 defp int_value ( value ) when is_integer ( value ) , do: { :ok , value }
165238 defp int_value ( value ) when is_float ( value ) , do: :error
166239
0 commit comments