@@ -14,6 +14,10 @@ defmodule CodexPooler.Gateway.Runtime.Finalization.ResponseUsage do
1414 optional ( :service_tier ) => String . t ( ) | nil
1515 }
1616
17+ @ retained_usage_context_bytes 4_096
18+ @ usage_field_pattern ~r/ (?<!\\ )"usage"\s *:/
19+ @ service_tier_pattern ~r/ (?<!\\ )"service_tier"\s *:\s *"([^"\\ ]+)"/
20+
1721 @ spec from_json ( binary ( ) ) :: usage ( )
1822 def from_json ( body ) when is_binary ( body ) do
1923 case Jason . decode ( body ) do
@@ -63,30 +67,134 @@ defmodule CodexPooler.Gateway.Runtime.Finalization.ResponseUsage do
6367 end
6468
6569 defp retained_usage_candidates ( body ) do
66- ~r / "usage" \s *: /
70+ @ usage_field_pattern
6771 |> Regex . scan ( body , return: :index )
6872 |> Enum . map ( fn [ { offset , _length } | _captures ] ->
69- binary_part ( body , offset , byte_size ( body ) - offset )
73+ record_offset = retained_record_start ( body , offset )
74+ context_offset = max ( offset - @ retained_usage_context_bytes , record_offset )
75+
76+ % {
77+ context_prefix: binary_part ( body , context_offset , offset - context_offset ) ,
78+ usage_fragment: binary_part ( body , offset , byte_size ( body ) - offset )
79+ }
7080 end )
7181 end
7282
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
83+ defp retained_record_start ( body , offset ) do
84+ body
85+ |> binary_part ( 0 , offset )
86+ |> :binary . matches ( "\n " )
87+ |> List . last ( )
88+ |> case do
89+ { newline_offset , _length } -> newline_offset + 1
90+ nil -> 0
91+ end
92+ end
93+
94+ defp usage_from_retained_usage_candidate ( % {
95+ context_prefix: context_prefix ,
96+ usage_fragment: usage_fragment
97+ } ) do
98+ with { :ok , input_tokens } <- retained_int ( usage_fragment , ~r/ "input_tokens"\s *:\s *(\d +)/ ) ,
99+ { :ok , output_tokens } <- retained_int ( usage_fragment , ~r/ "output_tokens"\s *:\s *(\d +)/ ) ,
100+ { :ok , total_tokens } <- retained_int ( usage_fragment , ~r/ "total_tokens"\s *:\s *(\d +)/ ) do
77101 % {
78102 "input_tokens" => input_tokens ,
79- "cached_input_tokens" => retained_cached_input_tokens ( candidate ) ,
103+ "cached_input_tokens" => retained_cached_input_tokens ( usage_fragment ) ,
80104 "output_tokens" => output_tokens ,
81- "reasoning_tokens" => retained_int_or_zero ( candidate , ~r/ "reasoning_tokens"\s *:\s *(\d +)/ ) ,
105+ "reasoning_tokens" =>
106+ retained_int_or_zero ( usage_fragment , ~r/ "reasoning_tokens"\s *:\s *(\d +)/ ) ,
82107 "total_tokens" => total_tokens
83108 }
84- |> normalize_usage ( % { } )
109+ |> normalize_usage ( % {
110+ "service_tier" => retained_service_tier ( context_prefix , usage_fragment )
111+ } )
85112 else
86113 :error -> nil
87114 end
88115 end
89116
117+ defp retained_service_tier ( context_prefix , usage_fragment ) do
118+ retained_service_tier_after_usage ( usage_fragment ) ||
119+ retained_service_tier_before_usage ( context_prefix )
120+ end
121+
122+ defp retained_service_tier_after_usage ( usage_fragment ) do
123+ usage_fragment
124+ |> retained_suffix_after_usage_object ( )
125+ |> retained_line_suffix ( )
126+ |> retained_service_tier_matches ( )
127+ |> List . first ( )
128+ end
129+
130+ defp retained_service_tier_before_usage ( context_prefix ) do
131+ context_prefix
132+ |> retained_service_tier_matches ( )
133+ |> List . last ( )
134+ end
135+
136+ defp retained_suffix_after_usage_object ( usage_fragment ) do
137+ with { object_offset , _length } <- :binary . match ( usage_fragment , "{" ) ,
138+ { :ok , object_end } <- json_object_end ( usage_fragment , object_offset ) do
139+ binary_part ( usage_fragment , object_end , byte_size ( usage_fragment ) - object_end )
140+ else
141+ _missing_or_incomplete -> ""
142+ end
143+ end
144+
145+ defp json_object_end ( binary , object_offset ) ,
146+ do: scan_json_object ( binary , object_offset , 0 , false , false )
147+
148+ defp scan_json_object ( binary , offset , _depth , _in_string? , _escaped? )
149+ when offset >= byte_size ( binary ) ,
150+ do: :error
151+
152+ defp scan_json_object ( binary , offset , depth , true , true ) ,
153+ do: scan_json_object ( binary , offset + 1 , depth , true , false )
154+
155+ defp scan_json_object ( binary , offset , depth , true , false ) do
156+ case :binary . at ( binary , offset ) do
157+ ?\\ -> scan_json_object ( binary , offset + 1 , depth , true , true )
158+ ?" -> scan_json_object ( binary , offset + 1 , depth , false , false )
159+ _other -> scan_json_object ( binary , offset + 1 , depth , true , false )
160+ end
161+ end
162+
163+ defp scan_json_object ( binary , offset , depth , false , false ) do
164+ case :binary . at ( binary , offset ) do
165+ ?" ->
166+ scan_json_object ( binary , offset + 1 , depth , true , false )
167+
168+ ?{ ->
169+ scan_json_object ( binary , offset + 1 , depth + 1 , false , false )
170+
171+ ?} when depth == 1 ->
172+ { :ok , offset + 1 }
173+
174+ ?} when depth > 1 ->
175+ scan_json_object ( binary , offset + 1 , depth - 1 , false , false )
176+
177+ ?} ->
178+ :error
179+
180+ _other ->
181+ scan_json_object ( binary , offset + 1 , depth , false , false )
182+ end
183+ end
184+
185+ defp retained_line_suffix ( body ) do
186+ case :binary . match ( body , "\n " ) do
187+ { newline_offset , _length } -> binary_part ( body , 0 , newline_offset )
188+ :nomatch -> body
189+ end
190+ end
191+
192+ defp retained_service_tier_matches ( body ) do
193+ @ service_tier_pattern
194+ |> Regex . scan ( body , capture: :all_but_first )
195+ |> Enum . map ( fn [ tier ] -> tier end )
196+ end
197+
90198 defp retained_cached_input_tokens ( body ) do
91199 case retained_int ( body , ~r/ "cached_input_tokens"\s *:\s *(\d +)/ ) do
92200 { :ok , tokens } -> tokens
@@ -167,7 +275,7 @@ defmodule CodexPooler.Gateway.Runtime.Finalization.ResponseUsage do
167275 % { status: "usage_known" } = fragment_usage
168276 ) do
169277 if total_tokens ( fragment_usage ) > total_tokens ( line_usage ) ,
170- do: fragment_usage ,
278+ do: inherit_missing_service_tier ( fragment_usage , line_usage ) ,
171279 else: line_usage
172280 end
173281
@@ -181,6 +289,15 @@ defmodule CodexPooler.Gateway.Runtime.Finalization.ResponseUsage do
181289 defp total_tokens ( % { total_tokens: total_tokens } ) when is_integer ( total_tokens ) , do: total_tokens
182290 defp total_tokens ( _usage ) , do: 0
183291
292+ defp inherit_missing_service_tier ( % { service_tier: tier } = usage , _fallback )
293+ when is_binary ( tier ) ,
294+ do: usage
295+
296+ defp inherit_missing_service_tier ( usage , % { service_tier: tier } ) when is_binary ( tier ) ,
297+ do: Map . put ( usage , :service_tier , tier )
298+
299+ defp inherit_missing_service_tier ( usage , _fallback ) , do: usage
300+
184301 defp normalize_usage ( usage , envelope ) do
185302 with { :ok , input_tokens } <-
186303 required_int_value ( usage [ "input_tokens" ] || usage [ "prompt_tokens" ] ) ,
0 commit comments