
    `gj                        d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m
Z
mZ ddlmZ  ej                  e      ZddZddZdd	Z	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dd
Zy)u  Full MoA turn trace persistence (opt-in via config ``moa.save_traces``).

When enabled, every Mixture-of-Agents turn that actually runs the reference
fan-out (a cache MISS in ``MoAChatCompletions.create``) appends one JSON line
to ``<hermes_home>/moa-traces/<session_id>.jsonl``. The record is the TRUE
FULL turn — the exact messages array each reference model received (system
prompt + advisory view, not the truncated display preview), each reference's
full output, and the exact messages array the aggregator received (including
the injected reference-context guidance block) plus its output when available
— so a run can be audited end-to-end offline: what every model saw, what every
model said, and what it cost.

This is a side-channel trace. It is NOT the conversation ``messages`` table and
never enters message history or replay — MoA references are advisory side-calls
with their own system prompt, not conversation turns, so persisting them as
message rows would corrupt role alternation / replay. Traces live in their own
files, keyed by session id, and are safe to delete.

Cost model note: gated OFF by default. When off, the only overhead is the
``_traces_enabled()`` config read (cheap) — no file I/O, no serialization.
    )annotationsN)Path)AnyOptional)get_hermes_homec            	     r   	 ddl m}   |        xs i j                  d      xs i }|j                  d      sy|j                  d      }|rPt	        t
        j                  j                  t
        j                  j                  t        |                        }|S t               dz  }|S # t        $ r Y yw xY w)a1  Return the trace directory if ``moa.save_traces`` is on, else None.

    Reads config lazily per call (config is cheap to load and this only runs on
    a cache-MISS MoA turn, i.e. once per user turn, not per tool iteration).
    ``moa.trace_dir`` overrides the default ``<hermes_home>/moa-traces/``.
    r   )load_configmoaNsave_traces	trace_dirz
moa-traces)hermes_cli.configr	   get	Exceptionr   ospath
expandvars
expanduserstrr   )r	   moa_cfgoverridebases       B/root/.hermes/venv/lib/python3.12/site-packages/agent/moa_trace.py_traces_enabled_and_dirr   %   s    1=&B++E28b ;;}%{{;'HBGG&&rww'9'9#h-'HIJ K  </K  s   $B* *	B65B6c                J    | sydj                  d t        |       D              S )z/Make a session id safe as a filename component.zunknown-session c              3  L   K   | ]  }|j                         s|d v r|nd  yw)z-_._N)isalnum).0cs     r   	<genexpr>z'_sanitize_session_id.<locals>.<genexpr>@   s#     VU
1<Vs   "$)joinr   )
session_ids    r   _sanitize_session_idr$   <   s!     77Vc*oVVV    c                l   t        | dd      }i }|?t        |dd      t        |dd      t        |dd      t        |dd      t        |dd      d	}|t        | d
d      t        | dd      t        | dd      t        | dd      t        | dd      |t        | dd      t        | dd      t        | dd      d
S )u   Render one reference's _RefAccounting into a full trace dict.

    Includes the FULL input messages the reference received and its FULL
    output — not the truncated display preview.
    usageNinput_tokensr   output_tokenscache_read_tokenscache_write_tokensreasoning_tokens)r(   r)   r*   r+   r,   modelprovidertemperaturemessagesoutputcost_usdcost_statuscost_source)
labelr-   r.   r/   input_messagesr1   r'   r2   r3   r4   )getattr)acctr5   r'   
usage_dicts       r   _slot_tracer:   C   s     D'4(E!#J#E>1=$UOQ?!(0CQ!G")%1Eq"I '/A1 E

 w-D*d3t]D9!$
D9$$/D*d3t]D9t]D9 r%   c        
        0   t               }
|
y	 |
j                  dd       |
t        |        dz  }t        |      }|	sd}n|rd}nd}t	        j                         | ||D cg c]  \  }}}t        ||       c}}}|||||||	|dd	}|j                  d
d      5 }|j                  t        j                  |dt              dz          ddd       yc c}}}w # 1 sw Y   yxY w# t        $ r!}t        j                  d| |       Y d}~yd}~ww xY w)u  Append one full MoA turn record to the session's trace JSONL, if enabled.

    Best-effort: any failure is logged at debug and swallowed — tracing must
    never break a live turn. Called once per turn on a reference cache MISS.

    ``aggregator_output`` is the aggregator's synthesized text. On the
    non-streaming path (eval / quiet-mode / subagents) it was captured inline
    at call time. On the streaming path it is captured after the fact from the
    caller's resolved assistant text (``aggregator_output_fallback`` in
    ``consume_and_save_trace``) so the trace is self-contained either way; if
    that resolved text was unavailable, it falls back to None and the record
    points at the session store via ``output_location``.
    NT)parentsexist_okz.jsonlinlineinline_from_streamassistant_message_in_session_db)r5   r-   r.   r/   r6   r1   streamedoutput_location)tsr#   preset
references
aggregatorazutf-8)encodingF)ensure_asciidefault
z'MoA trace write failed (session=%s): %s)r   mkdirr$   booltimer:   openwritejsondumpsr   r   loggerdebug)r#   preset_namereference_outputsaggregator_labelaggregator_modelaggregator_provideraggregator_temperatureaggregator_input_messagesaggregator_outputaggregator_streamedr   r   _have_output_output_locationr5   _textr8   recordfexcs                       r   save_moa_turnrd   a   sC   4 #$D|)Q

4$
/-j9:&AA -."'3@))+$! +< &E5$ D%(
 *)/5";+/ $4
2 YYsWY- 	PGGDJJvE3G$NO	P 	P+*	P 	P Q>
CPPQsH   AC+ %C<$C+  /CC+ C+ C($C+ (C+ +	D4DD)returnzOptional[Path])r#   Optional[str]re   r   )r8   r   r5   r   re   zdict[str, Any])r#   rf   rU   r   rV   zlist[tuple[str, str, Any]]rW   r   rX   rf   rY   rf   rZ   r   r[   r   r\   rf   r]   rM   re   None)__doc__
__future__r   rQ   loggingr   rN   pathlibr   typingr   r   hermes_constantsr   	getLogger__name__rS   r   r$   r:   rd    r%   r   <module>rq      s   , #   	     ,			8	$.W<FQFQ FQ 2	FQ
 FQ $FQ 'FQ  FQ  #FQ %FQ FQ 
FQr%   