+
    „WÝjæg  ã                  óâ   € R t ^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	H
t
HtHtHtHt ]P                  ! R4      tRtR R lt]! RR7      t]! R	R7      tR
t]! R4      t ! R R4      tR# )z¯Token/usage accounting mixin for SessionDB: the coalescing background token writer,
per-model usage rows, and billing-route columns. Writer thread state lives on the instance.)ÚannotationsN)ÚAnyÚDictÚListÚOptionalÚTupleÚhermes_statec               ó    € V ^8„  d   QhRRRR/# )é   ÚdeltaÚboolÚreturnÚstr© )Úformats   "ÚFC:\Users\johnn\AppData\Local\hermes\hermes-agent\hermes_state_usage.pyÚ__annotate__r      s   € ÷ ñ ˜Tð  cñ ó    c           	     óÌ   a a€ R V 3R lloR V 3R llpRP                  V3R l\         4       4      pS '       d   RMRpR	V,           R
V RV! R4       RV! R4       R2,           # )zò``UPDATE sessions`` for one usage report: *delta* adds to the stored counters (CLI
per-call path), otherwise sets them (gateway cumulative path). Cost/route columns
COALESCE-fill either way (statement text is pinned by the SQL trace harness).c               ó    € V ^8„  d   QhRRRR/# ©r
   Úcolr   r   r   )r   s   "r   r   Ú'_token_update_sql.<locals>.__annotate__   s   € ÷ .ñ .�ð .˜ñ .r   c                ó"   <€ S'       d   V  R 2# R# )z + ?Ú?r   ©r   r   s   &€r   ÚaddÚ_token_update_sql.<locals>.add   s   ø€ ß$�#��dˆ|Ð-¨#Ð-r   c               ó    € V ^8„  d   QhRRRR/# r   r   )r   s   "r   r   r      s   € ÷ ;ñ ;�#ð ;˜#ñ ;r   c                ó$   <€ S'       d   R V  R2# R# )z	COALESCE(z, 0) + ?r   r   r   s   &€r   Úadd0Ú_token_update_sql.<locals>.add0   s   ø€ ß,1�˜3˜%˜xÐ(Ð:°sÐ:r   Ú c              3  óB   <"  € T F  pR V RS! V4       R2x € K  	  R# 5i)z                   z = z,
Nr   )Ú.0Úcr   s   & €r   Ú	<genexpr>Ú$_token_update_sql.<locals>.<genexpr>   s%   øé € ÐXÉÀ1Ð,¨Q¨C¨s±3°q³6°(¸#Õ>Ëùs   ƒz0COALESCE(estimated_cost_usd, 0) + COALESCE(?, 0)zCOALESCE(?, 0)zUPDATE sessions SET
z(                   estimated_cost_usd = zƒ,
                   actual_cost_usd = CASE
                       WHEN ? IS NULL THEN actual_cost_usd
                       ELSE Úactual_cost_usdaì  
                   END,
                   cost_status = COALESCE(?, cost_status),
                   cost_source = COALESCE(?, cost_source),
                   pricing_version = COALESCE(?, pricing_version),
                   billing_provider = COALESCE(billing_provider, ?),
                   billing_base_url = COALESCE(billing_base_url, ?),
                   billing_mode = COALESCE(billing_mode, ?),
                   model = COALESCE(model, ?),
                   api_call_count = Úapi_call_countz 
                   WHERE id = ?)ÚjoinÚ_TOKEN_COUNTERS)r   r    ÚcountersÚ	estimatedr   s   f   @r   Ú_token_update_sqlr.      s€   ù€ ÷.ð .÷;ð ;à�w‰wÔXÍÓXÓX€HßFKÑBÐQa€Ià (Õ*Ø6°y°kð Bñ "Ð"3Ó4Ð5ð 	6%ñ &*Ð*:Ó%;Ð$<ð = ð#õ	#ðr   F)r   Ta·  INSERT INTO session_model_usage (
                   session_id, model, billing_provider, billing_base_url, billing_mode,
                   task, api_call_count, input_tokens, output_tokens,
                   cache_read_tokens, cache_write_tokens, reasoning_tokens,
                   estimated_cost_usd, actual_cost_usd, cost_status, cost_source,
                   first_seen, last_seen
               ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
               ON CONFLICT(session_id, model, billing_provider, billing_base_url, billing_mode, task)
               DO UPDATE SET
                   api_call_count = api_call_count + excluded.api_call_count,
                   input_tokens = input_tokens + excluded.input_tokens,
                   output_tokens = output_tokens + excluded.output_tokens,
                   cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,
                   cache_write_tokens = cache_write_tokens + excluded.cache_write_tokens,
                   reasoning_tokens = reasoning_tokens + excluded.reasoning_tokens,
                   estimated_cost_usd = estimated_cost_usd + excluded.estimated_cost_usd,
                   actual_cost_usd = actual_cost_usd + excluded.actual_cost_usd,
                   cost_status = COALESCE(excluded.cost_status, cost_status),
                   cost_source = COALESCE(excluded.cost_source, cost_source),
                   last_seen = excluded.last_seenc                  ó^  € ] tR t^RtRtRR/R R lltR R ltR R	 ltR5R
 R lltR R lt	R R lt
R R ltR6R R lltR R ltR7R R lltR8R R lltRRRRRRRRR^ R ^ R!^ R"^ R#^ R$RR%RR&RR'RR(^ R)R/R* R+ lltRRRRRRR^ R ^ R!^ R"^ R#^ R$RR(^/
R, R- lltR. R/ ltR0^R1R/R2 R3 lltR4tR# )9ÚSessionUsageMixinz<Coalesced token writer, per-model usage rows, billing route.Úbilling_modeNc          
     ó,   € V ^8„  d   QhRRRRRRRRRR/# )	r
   Ú
session_idr   ÚproviderÚbase_urlr1   úOptional[str]r   ÚNoner   )r   s   "r   r   ÚSessionUsageMixin.__annotate__U   s4   € ÷ !ñ !Øð!Ø,/ð!Ø;>ð!ØN[ð!à	ñ!r   c               ób   aaaa€ V P                  4        VVVV3R lpV P                  V4       R# )a  Unconditionally set the billing route (``update_token_counts`` only COALESCE-fills
NULLs) so the dashboard reflects the latest /model switch.

Route writers never touch the stored prompt; ``_stored_prompt_matches_runtime`` decides staleness.

See #48173, #48248.
c                ó4   <€ V P                  R SSSS34       R# )zÁUPDATE sessions SET
                   billing_provider = ?,
                   billing_base_url = ?,
                   billing_mode = COALESCE(?, billing_mode)
                   WHERE id = ?N)Úexecute)Úconnr5   r1   r4   r3   s   &€€€€r   Ú_doÚ;SessionUsageMixin.update_session_billing_route.<locals>._dob   s#   ø€ Ø�L‰Lð #ð &.¨x¸ÀzÐ$Rö	Tr   N)Úflush_token_countsÚ_execute_write)Úselfr3   r4   r5   r1   r=   s   &fddd r   Úupdate_session_billing_routeÚ.SessionUsageMixin.update_session_billing_routeU   s-   û€ ð 	×ÑÔ!÷	Tð 	Tð 	×Ñ˜CÖ r   c               ó    € V ^8„  d   QhRRRR/# )r
   r3   r   r   r7   r   )r   s   "r   r   r8   j   s   € ÷ ;ñ ;¨Sð ;¸tñ ;r   c                ó¸  a€ V P                   ;_uu_ 4        V P                  pVRJ;'       d    VP                  4       pV P                  ;'       d    V'       * pV'       gÁ   V P                  P                  W34       V'       gƒ   \        P                  ! V P                  RRR7      pW0n        VP                  4        V P                  f<   \        P                  ! V 4      oR V3R llpW`n
        \        P                  ! V4       V P                   P                  4        RRR4       X'       d   V P                   ! V3/ VB  R# R#   + '       g   i     L/; i)z¶Enqueue a token/cost delta for the background writer (same kwargs as
:meth:`update_token_counts`). After close() stopped the writer, falls back to the
synchronous path and may raise.Nzsession-db-token-writerT)ÚtargetÚnameÚdaemonc               ó   € V ^8„  d   QhRR/# ©r
   r   r7   r   )r   s   "r   r   Ú:SessionUsageMixin.queue_token_counts.<locals>.__annotate__~   s   € ÷ @ñ @°ñ @r   c                 óB   <€ S! 4       p V e   V P                  4        R # R # ©N)Ú_drain_token_queue_at_exit)ÚdbÚself_refs    €r   Ú_drain_at_exitÚ<SessionUsageMixin.queue_token_counts.<locals>._drain_at_exit~   s    ø€ Ù!)£˜BØ!š~Ø "× =Ñ =Ö ?ñ  .r   )Ú_token_queue_condÚ_token_writer_threadÚis_aliveÚ_token_writer_stopÚ_token_queueÚappendÚ	threadingÚThreadÚ_token_writer_loopÚstartÚ_token_atexit_hookÚweakrefÚrefÚatexitÚregisterÚ
notify_allÚupdate_token_counts)rA   r3   ÚkwargsÚthreadÚwriter_aliveÚwriter_stoppedrQ   rP   s   &&,    @r   Úqueue_token_countsÚ$SessionUsageMixin.queue_token_countsj   s  ø€ ð ×#×#Ó#Ø×.Ñ.ˆFØ!¨Ð-×CÐC°&·/±/Ó2CˆLØ!×4Ñ4×IÐI¸\Ô9IˆNß!Ø×!Ñ!×(Ñ(¨*Ð)=Ô>ß#ô '×-Ò-Ø#×6Ñ6Ð=VÐ_côe�Fà06Ô-Ø—L‘L”NØ×.Ñ.Ò6Ü#*§;¢;¨tÓ#4˜÷@ð @ð
 3AÔ/ÜŸš¨Ô7Ø×&Ñ&×1Ñ1Ô3÷/ $÷0 à×$Ò$ ZÑ:°6Ô:ñ ÷1 $×#ús   –;E	ÁE	Á"C E	Å	E	c               ó   € V ^8„  d   QhRR/# rJ   r   )r   s   "r   r   r8   Š   s   € ÷ 4ñ 4¨Tñ 4r   c           	     óz  €  V P                  V4       V P                  ;_uu_ 4        RV n        V P                  P                  4        RRR4       R#   + '       g   i     R# ; i  T P                  ;_uu_ 4        RT n        T P                  P                  4        RRR4       i   + '       g   i     i ; i; i)zKApply a batch whose ``busy`` flag the caller already claimed, then release.FN)Ú_apply_token_batchrS   Ú_token_writer_busyrb   )rA   Úbatchs   &&r   Ú_apply_claimed_batchÚ&SessionUsageMixin._apply_claimed_batchŠ   s~   € ð	4Ø×#Ñ# EÔ*à×'×'Ó'Ø*/�Ô'Ø×&Ñ&×1Ñ1Ô3÷ (×'×'Ò'û�×'×'Ó'Ø*/�Ô'Ø×&Ñ&×1Ñ1Ô3÷ (×'×'Ð'ýs.   ‚A' §"AÁA$	Á'B:Á<"B'	Â	B:Â'B7Â2B:c               ó    € V ^8„  d   QhRRRR/# )r
   ÚtimeoutÚfloatr   r   r   )r   s   "r   r   r8   “   s   € ÷ ñ ¨%ð ¸$ñ r   c                óÜ  € V P                   '       g   V P                  '       g   R# RpV P                  ;_uu_ 4        \        P                  ! 4       V,           pV P                   '       g   V P                  '       d»   V P
                  pVe   VP                  4       '       gJ   V P                  '       g8   RV n        \        V P                   4      pV P                   P                  4        MKV\        P                  ! 4       ,
          pV^ 8:  d    RRR4       R# V P                  P                  V4       KÞ  RRR4       V'       d   V P                  V4       R#   + '       g   i     L+; i)zŒBlock until every queued token delta has been applied. False on timeout (callers
then read totals stale by the queued deltas). Never raises.TNF)rW   rm   rS   ÚtimeÚ	monotonicrT   rU   ÚlistÚclearÚwaitro   )rA   rr   rn   Údeadlinere   Ú	remainings   &&    r   r?   Ú$SessionUsageMixin.flush_token_counts“   s
  € ð × × Ð ¨×)@×)@Ð)@ÙØˆØ×#×#Ó#Ü—~’~Ó'¨'Õ1ˆHØ×#×#Ð# t×'>×'>Ð'>ð
 ×2Ñ2�Ø’N¨&¯/©/×*;Ò*;ÀT×E\×E\ÐE\Ø.2�DÔ+Ü  ×!2Ñ!2Ó3�EØ×%Ñ%×+Ñ+Ô-ØØ$¤t§~¢~Ó'7Õ7�	Ø ”>Ø ÷ $Ñ#ð  ×&Ñ&×+Ñ+¨IÖ6÷! $÷" Ø×%Ñ% eÔ,Ù÷' $×#ús*   ½.EÁ,EÁ>%EÂ$EÂ6AEÄEÅE+	c               ó   € V ^8„  d   QhRR/# rJ   r   )r   s   "r   r   r8   ¯   s   € ÷ -ñ - Dñ -r   c                	óx  €  V P                   ;_uu_ 4        \        P                  ! 4       V P                  ,           pV P                  '       ge   V P
                  '       gS   V\        P                  ! 4       ,
          pV^ 8:  d   RV n         RRR4       R# V P                   P                  V4       Kv  V P                  '       g   RV n         RRR4       R# R V n        \        V P                  4      pV P                  P                  4        RRR4       V P                  X4       EK(    + '       g   i     L$; i)TN)rS   ru   rv   Ú_TOKEN_WRITER_IDLE_SECONDSrW   rV   rT   ry   rm   rw   rx   ro   )rA   Úidle_deadliner{   rn   s   &   r   r[   Ú$SessionUsageMixin._token_writer_loop¯   së   € ØØ×'×'Ó'Ü $§¢Ó 0°4×3RÑ3RÕ R�Ø×+×+Ð+°D×4K×4KÐ4KØ -´·²Ó0@Õ @�IØ  A”~ð 59˜Ô1Ø÷ (Ñ'ð ×*Ñ*×/Ñ/°	Ö:Ø×(×(Ð(Ø04�DÔ-Ø÷ (Ñ'ð +/�Ô'Ü˜T×.Ñ.Ó/�Ø×!Ñ!×'Ñ'Ô)÷# (ð$ ×%Ñ% e×,÷% (×'ús)   –8D)ÁD)Á!+D)Â.D)ÃD)Ã6D)Ä)D9	c               ó    € V ^8„  d   QhRRRR/# )r
   rn   ú list[tuple[str, dict[str, Any]]]r   r7   r   )r   s   "r   r   r8   Å   s   € ÷ iñ iÐ(Hð iÈTñ ir   c                ó&  €  V P                  V4      pV F  w  rE V P                  ! V3/ VB  K  	  R#   \         d#   p\        P                  RT4       Tp Rp?LIRp?ii ; i  \         d"   p\        P                  RYC4        Rp?Kv  Rp?ii ; i)zBApply queued deltas in order, coalescing where safe. Never raises.z?async token accounting: coalesce failed, applying raw batch: %sNz5async token accounting: apply failed (session=%s): %s)Ú_coalesce_token_deltasÚ	ExceptionÚloggerÚwarningrc   )rA   rn   Ú	coalescedÚexcr3   rd   s   &&    r   rl   Ú$SessionUsageMixin._apply_token_batchÅ   sŒ   € ð	Ø×3Ñ3°EÓ:ˆIó
 #,ÑˆJðiØ×(Ò(¨Ñ>°vÔ>ó #,øô	 ô 	ä�N‰NÐ\Ð^aÔbØ�Iûð	ûô ô iä—‘ÐVÐXb×hÒhûðiús+   ‚4 ›A$´A!¿AÁA!Á$BÁ/BÂBc               ó    € V ^8„  d   QhRRRR/# )r
   rn   rƒ   r   r   )r   s   "r   r   r8   Ô   s   € ÷ 4ñ 4Ð,Lð 4ÐQqñ 4r   c                ó‚  a€ . pV EF  w  poRpSP                  R4      '       g   V.V3R lV P                   4       O5pV'       d¸   Ve´   VR,          ^ ,          V8X  dŸ   VR,          ^,          pV P                   F.  pVP                  V^ 4      SP                  V^ 4      ,           WV&   K0  	  V P                   F>  pSP                  V4      pVf   K  VP                  V4      ;'       g    RV,           WV&   K@  	  Kù  VP	                  WC\        S4      34       EK  	  V UU	U
u. uF	  w  r‰q©V
3NK  	  up
p	p# u up
p	pi )zœMerge adjacent incremental deltas with an identical route, so ordering across
sessions and /model switches is preserved exactly. absolute=True never merges.NÚabsolutec              3  óF   <"  € T F  pSP                  V4      x € K  	  R # 5irM   )Úget)r$   Úfrd   s   & €r   r&   Ú;SessionUsageMixin._coalesce_token_deltas.<locals>.<genexpr>Û   s   øé € Ð$[Ñ<Z°q V§Z¡Z°§] ]Ó<Zùs   ƒ!ç        éÿÿÿÿ)r�   Ú_TOKEN_DELTA_ROUTE_FIELDSÚ_TOKEN_DELTA_SUM_FIELDSÚ_TOKEN_DELTA_COST_FIELDSrX   Údict)rA   rn   Úgroupsr3   ÚkeyÚmergedr‘   ÚvalueÚ_ÚsidÚkwrd   s   &&         @r   r…   Ú(SessionUsageMixin._coalesce_token_deltasÔ   s  ø€ ð EGˆÜ"'ÑˆJ˜ØˆCØ—:‘:˜j×)Ò)Ø!Ð\Ô$[¸D×<ZÒ<ZÓ$[Ñ\�ß˜#š/¨f°R­j¸­m¸sÔ.BØ � A��Ø×5Ô5�AØ &§
¡
¨1¨aÓ 0°6·:±:¸aÀÓ3CÕ C�F“Iñ 6à×6Ô6�AØ"ŸJ™J q›M�EØÔ(à%+§Z¡Z°£]×%9Ð%9°c¸UÕ$B˜›	ó	 7ð —‘˜s´°V³Ð=×>ñ #(ñ -3Õ3©F™j˜a b�b“	©FÓ3Ð3ùÔ3s   Ä&D:c               ó    € V ^8„  d   QhRRRR/# )r
   Újoin_timeoutrs   r   r7   r   )r   s   "r   r   r8   é   s   € ÷ "-ñ "-¨uð "-Àñ "-r   c           	     óö  € V P                   ;_uu_ 4        RV n        V P                   P                  4        V P                  pRRR4       Xek   VP	                  4       '       dU   VP                  VR7       VP	                  4       '       d-   \        P                  RV\        V P                  4      4       R# V P                   ;_uu_ 4        \        P                  ! 4       V,           pV P                  '       dv   V\        P                  ! 4       ,
          pV^ 8:  d6   \        P                  RV\        V P                  4      4        RRR4       R# V P                   P                  V4       K‡  \        V P                  4      pV'       d"   RV n        V P                  P                  4        RRR4       X'       d   V P!                  V4       R# R#   + '       g   i     ELš; i  + '       g   i     LA; i)z@Stop the writer thread and drain remaining deltas. Never raises.TN)rr   zZasync token accounting: writer did not stop within %.0fs; %d queued delta(s) not persistedzfasync token accounting: concurrent drain did not finish within %.0fs; %d queued delta(s) not persisted)rS   rV   rb   rT   rU   r*   r‡   rˆ   ÚlenrW   ru   rv   rm   ry   rw   rx   ro   )rA   r¢   re   rz   r{   rn   s   &&    r   Ú_stop_token_writerÚ$SessionUsageMixin._stop_token_writeré   su  € à×#×#Ó#Ø&*ˆDÔ#Ø×"Ñ"×-Ñ-Ô/Ø×.Ñ.ˆF÷ $ð Ò &§/¡/×"3Ò"3Ø�K‰K ˆKÔ-Ø�‰× Ò ä—‘ð7Ø8DÄcÈ$×J[ÑJ[ÓF\ô^ñ ð ×#×#Ó#Ü—~’~Ó'¨,Õ6ˆHØ×)×)Ð)Ø$¤t§~¢~Ó'7Õ7�	Ø ”>Ü—N‘NðPà$¤c¨$×*;Ñ*;Ó&<ô>ð ÷ $Ñ#ð ×&Ñ&×+Ñ+¨IÖ6ä˜×*Ñ*Ó+ˆEßØ*.�Ô'Ø×!Ñ!×'Ñ'Ô)÷ $÷  Ø×%Ñ% eÖ,ñ ÷? $×#Ð#ú÷ $×#ús%   •.GÃ.G(Ã<AG(ÅAG(ÇG%	Ç(G8	c               ó   € V ^8„  d   QhRR/# rJ   r   )r   s   "r   r   r8     s   € ÷ &ñ &¨Dñ &r   c                	ó¤   € \         P                  ! \        4      ;_uu_ 4        V P                  4        R R R 4       R #   + '       g   i     R # ; irM   )Ú
contextlibÚsuppressr†   r¥   )rA   s   &r   rN   Ú,SessionUsageMixin._drain_token_queue_at_exit  s.   € Ü× Ò ¤×+Õ+Ø×#Ñ#Ô%÷ ,×+×+Ò+ús	   £>¾A	Fr"   c          (     óh   € V ^8„  d   QhRRRRRRRRRRR	RR
RRRRRRRRRRRRRRRRRRRRRRRRRRR/# )r
   r3   r   Úinput_tokensÚintÚoutput_tokensÚmodelú
str | NoneÚcache_read_tokensÚcache_write_tokensÚreasoning_tokensÚestimated_cost_usdúOptional[float]r(   Úcost_statusr6   Úcost_sourceÚpricing_versionÚbilling_providerÚbilling_base_urlr1   r)   rŽ   r   ÚsourceÚtaskr   r7   r   )r   s   "r   r   r8     sã   € ÷ oñ oØðoØ-0ðoØCFðoØQ[ðoØuxðoàðoà58ðoàP_ðoð )ðoð =Jðoð ^kðoð 'ð	oð @Mð	oð fsð	oð
 $ðoð
 ;>ðoð
 LPðoð ðoð +.ðoð 
ñor   c                ó  a aa€ \        \        4       4      oSP                  R4       SP                  R4       SP                  R4       S P                  ST;'       g    RVR7       S P	                  V VV3R l4       R# )a  Update totals and route attribution, ensuring the legacy missing-row backfill.
*absolute*=False increments (per-API-call deltas, CLI path); *absolute*=True sets directly
(gateway path, where the cached agent holds cumulative totals). ``source`` is the session's
real surface for the row-existence guard; callers that don't know it leave the placeholder.
``task`` names a per-turn route (``voice_chat``): its calls count in the session totals and
land in ``session_model_usage`` under that task, but never become the session's recorded route.rA   r3   r¼   Úunknown)r°   c                ó,   <€ SP                   ! V S3/ SB # rM   )Ú#_update_token_counts_in_transaction)r<   rA   r3   Úvaluess   &€€€r   Ú<lambda>Ú7SessionUsageMixin.update_token_counts.<locals>.<lambda>)  s   ø€ ¨×)QÒ)QÐRVÐXbÑ)mÐflÒ)mr   N)r˜   ÚlocalsÚpopÚ_insert_session_rowr@   )rA   r3   r­   r¯   r°   r²   r³   r´   rµ   r(   r·   r¸   r¹   rº   r»   r1   r)   rŽ   r¼   r½   rÂ   s   ff&&&&&&&&&&&&&&&&&&@r   rc   Ú%SessionUsageMixin.update_token_counts  sb   ú€ ô ”f“h“ˆØ�
‰
�6ÔØ�
‰
�<Ô Ø�
‰
�8Ôð 	× Ñ  ¨V×-@Ð-@°yÈÐ ÔNØ×ÑÖmÖnr   c          &     ód   € V ^8„  d   QhRRRRRRRRRRR	RR
RRRRRRRRRRRRRRRRRRRRRRRRR/# )r
   r3   r   r­   r®   r¯   r°   r±   r²   r³   r´   rµ   r¶   r(   r·   r6   r¸   r¹   rº   r»   r1   r)   rŽ   r   r½   r   r7   r   )r   s   "r   r   r8   +  sÚ   € ÷ 1Kñ 1KØ #ð1KØ36ð1KØILð1KØWað1KØ{~ð1Kàð1Kà58ð1KàP_ð1Kð )ð1Kð =Jð1Kð ^kð1Kð 'ð	1Kð @Mð	1Kð fsð	1Kð
 $ð1Kð
 ;>ð1Kð
 LPð1Kð
 ^að1Kð 
ñ1Kr   c                	ó2  € \        4       P                  4        UUu/ uF  w  ppV\        9   g   K  VVbK  	  pppV'       d   \        M\        p\        T;'       g5    T;'       g+    T;'       g!    T;'       g    T;'       g    T;'       g    T	4      p\        T;'       g    T
4      pY4YgTYšY«YÍV'       d   TMR V'       d   TMR V'       d   TMR V'       d   TMR VV3pV'       * ;'       d    TpVP                  RV34      P                  4       pVe   \        V4      M/ pV'       * ;'       d�    \        VP                  R4      ;'       g    ^ 4      ^ 8H  ;'       da    T;'       dW    \        V4      ;'       dD    \        V4      ;'       d1    VP                  R4      V8g  ;'       g    VP                  R4      V8g  pV'       d   VP                  RW^VVV34       VP                  VV4       V'       d   V P                  ! W3RV/VB  R # R # u uppi )NzISELECT model, billing_provider, api_call_count FROM sessions WHERE id = ?r)   r°   rº   z¡UPDATE sessions
                   SET model = ?, billing_provider = ?,
                   billing_base_url = ?, billing_mode = ?
                   WHERE id = ?r½   )rÅ   ÚitemsÚ_MODEL_USAGE_FIELDSÚ_TOKEN_UPDATE_ABSOLUTE_SQLÚ_TOKEN_UPDATE_DELTA_SQLr   r;   Úfetchoner˜   r®   r�   Ú_record_model_usage)rA   r<   r3   r­   r¯   r°   r²   r³   r´   rµ   r(   r·   r¸   r¹   rº   r»   r1   r)   rŽ   r½   ÚkÚvÚusageÚsqlÚ	has_usageÚhas_accounted_usageÚparamsÚrecord_model_usageÚrowÚexistingÚfirst_accounted_routes   &&&&&&&&&&&&&&&&&&&&           r   rÁ   Ú5SessionUsageMixin._update_token_counts_in_transaction+  s;  € ô #)£(§.¡.Ô"2ÔOÑ"2™$˜!˜Q°aÔ;NÑ6N”��A’Ñ"2ˆÑOß,4Õ(Ô:QˆÜ˜÷ Að A¨÷ Að AÐ:K÷ Að AÐOa÷ Að AÐeu÷ Að AØ*÷Að AØ.@óBˆ	ä" 9×#?Ð#?°Ó@ÐàÐ):ÐP`Ø°È{ß 3Ñ¸ß 3Ñ¸ß/‰L°T×DW¹5Ð]aØ˜Jð(ˆð  #+œl×9Ð9°	Ðà�l‰lØWÐZdÐYfó
ç
‰(‹*ð 	ð !$¢”4˜”9°Rˆð
 ŒH÷ ið iœ˜XŸ\™\Ð*:Ó;×@Ð@¸qÓAÀQÑF÷ ið iÐK^÷ ið iÔcgÐhmÓcn÷ ið iÜÐ%Ó&÷ið ià—‘˜gÓ&¨%Ñ/×gÐg°8·<±<Ð@RÓ3SÐWgÑ3gð 	÷
 !Ø�L‰Lð #ð &+Ð>NÐP\Ð^hÐ$iôkð 	�‰�S˜&Ô!ßØ×$Ò$ TÑJ¸DÐJÀEÔJñ ùóS Ps
   œH±Hr°   rº   r»   r­   r¯   r²   r³   r´   rµ   r(   r·   r¸   r)   r½   c          "     ó\   € V ^8„  d   QhRRRRRRRRRRRR	R
R	RR	RR	RR	RRRRRRRRRR	RRRR/# )r
   r3   r   r°   r6   rº   r»   r1   r­   r®   r¯   r²   r³   r´   rµ   r¶   r(   r·   r¸   r)   r½   r   r7   r   )r   s   "r   r   r8   ^  sÈ   € ÷ rñ rØ #ðrØ/<ðrØUbðrà'ðrà<Iðrà^aðrð ðrð 25ðrð MPðrð fiðrð ,ð	rð DSð	rð
 #ðrð
 7Dðrð
 [^ðrð
 hkðrð 
ñrr   c               óÌ  € VP                  RV34      P                  4       pVe   V'       g   \        V4      M/ pWxWšV3 Uu. uF  pT;'       g    ^ NK  	  pp\        P                  ! 4       pTP                  \        Y#;'       g    VP                  R4      ;'       g    RT;'       g    VP                  R4      ;'       g    RT;'       g    VP                  R4      ;'       g    RT;'       g    VP                  R4      ;'       g    RT;'       g    RT;'       g    ^ .VO\        T;'       g    R	4      N\        T;'       g    R	4      NVNVNVNVN54       R# u upi )
u[  Accumulate a per-API-call usage delta into session_model_usage, inside the caller's
write txn after the ``sessions`` UPDATE. A missing model/provider falls back to
the session row â€” except for aux rows (``task`` set), which must NOT inherit the
main-loop route (vision on gemini while the main loop runs anthropic): missing
info stays 'unknown'/empty.

``task`` distinguishes what kind of work consumed the tokens: ``''`` (empty) is the main agent loop;
auxiliary calls record their task name (``vision``, ``compression``, ``title_generation``, ...) via
:meth:`record_auxiliary_usage` (issue #23270).
zYSELECT model, billing_provider, billing_base_url, billing_mode FROM sessions WHERE id = ?Nr°   r¿   rº   r"   r»   r1   r“   )r;   rÏ   r˜   ru   Ú_MODEL_USAGE_UPSERT_SQLr�   rs   )rA   r<   r3   r°   rº   r»   r1   r­   r¯   r²   r³   r´   rµ   r(   r·   r¸   r)   r½   rÙ   ÚsessrÒ   ÚcountsÚnows   &&&$$$$$$$$$$$$$$$     r   rÐ   Ú%SessionUsageMixin._record_model_usage^  s}  € ð" �l‰lØgÐjtÐivó
ç
‰(‹*ð 	ð !š_·TŒt�CŒyÀˆØ#/Ð@QÐgwÑ"xÓyÑ"x˜Q�!—&�&�q’&Ñ"xˆÐyÜ�iŠi‹kˆØ�‰Ô,Ø×?Ð? §¡¨'Ó!2×?Ð?°iØ×BÐB §¡Ð);Ó <×BÐBÀØ×BÐB §¡Ð);Ó <×BÐBÀØ×:Ð:˜DŸH™H ^Ó4×:Ð:¸¸D¿J¸JÀBÈ×H[ÐH[ÐZ[ð	/qð _eð	/qô
 Ð$×+Ð+¨Ó,ð/qô
 /4°O×4JÐ4JÀsÓ.Kð/qð
 NYð/qð
 [fð/qð
 hkð/qð
 mpñ/qö 	rùò zs   ÁE!ÁE!c               óL   € V ^8„  d   QhRRRRRRRRRRRR	R
R	RR	RR	RR	RRRR	RR/# )r
   r3   r   r½   r°   r6   rº   r»   r­   r®   r¯   r²   r³   r´   rµ   r¶   r)   r   r7   r   )r   s   "r   r   r8   |  s�   € ÷ !Rñ !RØð!RØ%(ð!RØ4Að!RØZgð!Rà'ð!Rà<?ð!RàRUð!Ràloð!Rð  ð!Rð 69ð!Rð Q`ð!Rð ð	!Rð
 
ñ!Rr   c       
        ó4  a aaaa€ \        4       P                  4        UUu/ uF  w  rÞV\        9   g   K  WÞbK  	  uppoS'       d	   S'       g   R# Vf   ^M
\        V4      SR&   ^ RIHo VV VVV3R lpS P                  VS P                  R7       R# u uppi )aÍ  Record an auxiliary LLM call's usage (vision, compression, title generation, ...)
as a per-(model, provider, task) delta in ``session_model_usage`` WITHOUT touching
the ``sessions`` summary row (the gateway overwrites those counters with absolute
main-loop totals). ``api_call_count`` may aggregate N calls. Best-effort.

See #23270.
Background-review forks record an aggregate of N fork API calls in one write with
``task='background_review'`` (issue #87250).
Nr)   )Ú!insert_session_row_in_transactionc                óê   <€ V P                  R S34      P                  4       f   S! SV SR4       M,SP                  4       ;p'       d   V P                  RVS34       SP                  ! V S3RS/SB  R# )z!SELECT 1 FROM sessions WHERE id=?Nr¿   zFUPDATE sessions SET profile_name=? WHERE id=? AND profile_name IS NULLr½   )r;   rÏ   Ú_own_profile_namerÐ   )r<   Úown_profileræ   rA   r3   r½   rÓ   s   & €€€€€r   Ú_writeÚ8SessionUsageMixin.record_auxiliary_usage.<locals>._write•  su   ø€ Ø�|‰|Ð?À*ÀÓO×XÑXÓZÒbÙ1°$¸¸jÈ)ÕTØ $× 6Ñ 6Ó 8Ð8�Ö8Ø—‘Ø\Ø  *Ð-ô/ð ×$Ò$ T¨:ÑJ¸DÐJÀEÔJr   )Ú
patience_s)rÅ   rË   rÌ   r®   Úhermes_state_worker_lifecycleræ   r@   Ú_TRANSCRIPT_WRITE_PATIENCE_S)rA   r3   r½   r°   rº   r»   r­   r¯   r²   r³   r´   rµ   r)   rÑ   rÒ   rê   ræ   rÓ   s   fff$$$$$$$$$$   @@r   Úrecord_auxiliary_usageÚ(SessionUsageMixin.record_auxiliary_usage|  s„   ü€ ô #)£(§.¡.Ô"2ÔOÑ"2™$˜!°aÔ;NÑ6N”�’Ñ"2ÒOˆß§ÙØ'5Ò'=¡!Ä3À~ÓCVˆÐÑõ
 	T÷	Kñ 	Kð 	×Ñ˜F¨t×/PÑ/PÐÖQùó% Ps
   ¡BµBc               ó    € V ^8„  d   QhRRRR/# )r
   r3   r   r   zdict[str, dict[str, float]]r   )r   s   "r   r   r8   Ÿ  s   € ÷ ]ñ ]°#ð ]Ð:Uñ ]r   c                óÖ  € V'       g   / # V P                  V4      p\        \        V4      ^,
          RR4       F&  pV P                  W#,          4      '       g   K"  W#R p M	  V P	                  RRP                  R\        V4      ,          4       R2V4      pV UUu/ uF9  qUR,          VP                  4        Uu/ uF  qfR8w  g   K  WeV,          bK  	  upbK;  	  upp# u upi u uppi )a
  Per-task auxiliary usage (``task != ''``: vision, compression, title_generation, ...) summed
over the session's compression lineage. Aux calls bill to the id the turn STARTED with while
compression mints child ids mid-turn, so a single-id read misses rows (#112848).Nay  SELECT task,
                       COALESCE(SUM(api_call_count), 0) AS api_calls,
                       COALESCE(SUM(input_tokens), 0) AS input_tokens,
                       COALESCE(SUM(output_tokens), 0) AS output_tokens,
                       COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens,
                       COALESCE(SUM(cache_write_tokens), 0) AS cache_write_tokens,
                       COALESCE(SUM(reasoning_tokens), 0) AS reasoning_tokens,
                       COALESCE(SUM(estimated_cost_usd), 0) AS estimated_cost_usd
                  FROM session_model_usage
                 WHERE session_id IN (Ú,r   z/) AND task != ''
                 GROUP BY taskr½   r”   )Ú_session_lineage_root_to_tipÚranger¤   Ú_is_explicit_branch_sessionÚ	_read_allr*   Úkeys)rA   r3   ÚchainÚiÚrowsrÙ   rÑ   s   &&     r   Úauxiliary_usage_by_taskÚ)SessionUsageMixin.auxiliary_usage_by_taskŸ  sá   € ÷ ØˆIØ×1Ñ1°*Ó=ˆô ”s˜5“z A•~ r¨2Ö.ˆAØ×/Ñ/°µ×9Ô9Ø˜b˜	�Ùñ /ð �~‰~ð	'ð (+§x¡x°´c¸%³jÕ0@Ó'AÐ&Bð Cð
"ð ó
ˆñ X\Ô\ÑW[ÐPS�F•°·±´ÓK±¨AÀF¹{œi˜a Q¥ši±ÑKÒKÑW[Ò\Ð\ùÒKùÓ\s   ÂC%Â;C ÃC ÃC%Ã C%Úmin_message_countÚinclude_archivedc               ó$   € V ^8„  d   QhRRRRRR/# )r
   rþ   r®   rÿ   r   r   zdict[str, float]r   )r   s   "r   r   r8   ¼  s'   € ÷ Nñ N°ð NÈDð NÐ]mñ Nr   c               ó   € RR.pV.pV'       g   VP                  R4       V P                  RRP                  V4       R2V4      pR\        V^ ,          ;'       g    ^ 4      R\	        V^,          ;'       g    R	4      /# )
z˜Tokens and spend across the whole store (one scan), so the sidebar total does not
shrink with paging. Spend prefers the billed figure over the estimate.zparent_session_id IS NULLzmessage_count >= ?zCOALESCE(archived, 0) = 0zã
            SELECT COALESCE(SUM(COALESCE(input_tokens, 0) + COALESCE(output_tokens, 0)), 0),
                   COALESCE(SUM(COALESCE(actual_cost_usd, estimated_cost_usd, 0)), 0)
              FROM sessions
             WHERE z AND z
            ÚtokensÚcost_usdr“   )rX   Ú	_read_oner*   r®   rs   )rA   rþ   rÿ   Úwherer×   rÙ   s   &$$   r   Úusage_totalsÚSessionUsageMixin.usage_totals¼  s‰   € ð -Ð.BÐCˆØ.Ð/ˆßØ�L‰LÐ4Ô5Ø�n‰nð "ð —L‘L Ó'Ð(ð )ð	ð
 óˆð œ#˜c !�fŸk˜k¨Ó*¨J¼¸cÀ!½f¿m¸mÈÓ8LÐMÐMr   )r]   rm   rV   rT   )g      @)g      $@)é    r  Nr  r  r  NNNNNNNNr  FNr"   )r  r  Nr  r  r  NNNNNNNNr  Fr"   )Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__rB   rh   ro   r?   r[   rl   r…   r¥   rN   rc   rÁ   rÐ   rï   rü   r  Ú__static_attributes__r   r   r   r0   r0   R   s…  † ÙFð!Ø^b÷!õ*;õ@4÷õ8-õ,iõ4÷*"-õH&÷o÷41KðfrØ=AðrØcgðrà(,ðràJNðràbcðrð ðrð 67ðrð QRðrð jkðrð -1ð	rð TXð	rð
 $(ðrð
 EIðrð
 _`ðrð
 ln÷rð<!RØBFð!RØhlð!Rà(,ð!Rà@Að!RàVWð!Ràpqð!Rð !"ð!Rð :;ð!Rð aeð!Rð ÷	!RõF]ð:N°qð NÐSX÷ Nó Nr   r0   )r­   r¯   r²   r³   r´   )r°   rº   r»   r1   r­   r¯   r²   r³   r´   rµ   r(   r·   r¸   r)   )r  Ú
__future__r   r`   r©   ÚloggingrY   ru   r^   Útypingr   r   r   r   r   Ú	getLoggerr‡   r+   r.   rÍ   rÎ   rß   Ú	frozensetrÌ   r0   r   r   r   Ú<module>r     s†   ðñ_õ #ã Û Û Û Û Û ß 3Õ 3ð 
×	Ò	˜>Ó	*€àr€õñ: /°UÔ;Ð Ù+°$Ô7Ð ð5Ð ñ0  ð !Gó HÐ ÷wNó wNr   