
    [j                        U d Z ddlmZ ddlZddlmZ ddlmZmZ  ej	        e
          Z edd          Zded	<    ed
dh          ZddZddZddZdddddZdS ) u  Ambient session-accounting context for auxiliary LLM calls.

Auxiliary calls (vision, compression, title generation, web_extract,
session_search, ...) funnel through ``agent.auxiliary_client`` which has no
session handle — so their token usage was historically discarded, leaving
dashboard analytics blind to aux model spend (issue #23270).

Instead of threading ``session_db``/``session_id`` parameters through every
aux call site, the agent loop publishes them here (mirroring the Nous Portal
conversation context in ``agent.portal_tags``) and the auxiliary client
records usage at its single response-validation chokepoint.

ContextVar semantics give us the right isolation for free:

* concurrent agents in one process (gateway sessions, delegate subagents)
  never see each other's accounting context;
* worker threads spawned via ``tools.thread_context.propagate_context_to_thread``
  (MoA fan-out, background review) inherit the parent turn's context;
* asyncio tasks inherit the context of the code that created them.

MoA reference/aggregator slots are explicitly EXCLUDED from recording:
``agent/conversation_loop.py`` already folds MoA advisor usage and cost into
the main loop's ``update_token_counts`` delta, so recording them here would
double-count (see ``_EXCLUDED_TASKS``).
    )annotationsN)
ContextVar)AnyOptionalaux_accounting_context)defaultzContextVar[Optional[tuple]]_accountingmoa_referencemoa_aggregator
session_dbr   
session_idOptional[str]c                v    | |st                               d          S t                               | |f          S )a(  Publish the active session's accounting handles for aux usage recording.

    Called by the agent loop at turn entry. Returns the ContextVar token so
    callers can ``reset_accounting_context(token)`` on turn exit. Publishing
    ``None`` handles (no DB / no session id) clears the context.
    N)r	   set)r   r   s     6/home/ice/.hermes/hermes-agent/agent/aux_accounting.pyset_accounting_contextr   .   s8     t$$$??J
3444    returnNonec                    	 t                               |            dS # t          $ r t                               d           Y dS w xY w)z@Restore the previous accounting context (pair with ``set_...``).N)r	   reset	Exceptionr   )tokens    r   reset_accounting_contextr   :   sW    %        s    $AAOptional[tuple]c                 4    t                                           S )zEReturn ``(session_db, session_id)`` for the active turn, or ``None``.)r	   get r   r   get_accounting_contextr   B   s    ??r   providerbase_urlresponsetaskr!   r"   c                  	 |r	|t           v rdS t                                          }|dS |\  }}t          | dd          }|dS ddlm}m}	  |	||          }
|
j        s|
j        s|
j	        s|
j
        s	|
j        sdS t          t          | dd          pd          pd}d}	  |||
||	          }|j        t          |j                  }n,# t          $ r t                               d
d           Y nw xY w|                    ||||||
j        |
j        |
j	        |
j
        |
j        |           dS # t          $ r  t                               dd           Y dS w xY w)u  Record an auxiliary response's token usage against the ambient session.

    Called from the auxiliary client's response-validation chokepoint. Strictly
    best-effort: any failure is swallowed (accounting must never break an aux
    call). No-ops when:

    * no accounting context is published (call is outside any agent turn),
    * the task is main-loop-accounted (MoA slots — see ``_EXCLUDED_TASKS``),
    * the response carries no usage object.

    The model is read from ``response.model`` (accurate even after the aux
    client's provider-fallback chains); *provider*/*base_url* reflect the
    originally-resolved route and are best-effort.
    Nusager   )estimate_usage_costnormalize_usage)r!   model unknownr    z Aux usage cost estimation failedT)exc_info)	r)   billing_providerbilling_base_urlinput_tokensoutput_tokenscache_read_tokenscache_write_tokensreasoning_tokensestimated_cost_usdz&Aux usage recording failed (non-fatal))_EXCLUDED_TASKSr	   r   getattragent.usage_pricingr'   r(   r/   r0   r1   r2   r3   str
amount_usdfloatr   loggerdebugrecord_auxiliary_usage)r#   r$   r!   r"   ctxr   r   	raw_usager'   r(   r&   r)   estimated_costcosts                 r   record_aux_usagerB   G   s   *.N 	t..Foo;F!$
JHgt44	FLLLLLLLL	H===	"'"5	&	*/*B	 %	
 FGHgr228b99FY	L&&ux(  D *!&t!7!7 	L 	L 	LLL;dLKKKKK	L 	))%%+-#5$7"3- 	* 	
 	
 	
 	
 	
  N N N=MMMMMMNsK   D7 D7 D7 8D7  $D7 %*C D7 &C96D7 8C99<D7 7&E! E!)r   r   r   r   )r   r   )r   r   )
r#   r   r$   r   r!   r   r"   r   r   r   )__doc__
__future__r   loggingcontextvarsr   typingr   r   	getLogger__name__r;   r	   __annotations__	frozensetr5   r   r   r   rB   r   r   r   <module>rL      s1    4 # " " " " "  " " " " " "                		8	$	$ ,6:d, , ,     )_.>?@@	5 	5 	5 	5       #"CN CN CN CN CN CN CN CNr   