
    `gj8                    j   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mZ ddl	m
Z
mZmZmZmZ  ej                  e      ZdZdZdZ G d	 d
e      ZddZddZddZddZdddd	 	 	 	 	 	 	 	 	 	 	 ddZddZdZedd	 	 	 	 	 	 	 	 	 	 	 d dZ	 d!	 	 	 d"dZddddeddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d#dZ y)$u  Upload a Hermes session transcript to Hugging Face as an agent trace.

Hermes stores sessions in its own SQLite store (``hermes_state.SessionDB``),
so we reconstruct the conversation and emit it in the **Claude Code JSONL**
shape — one of the three formats the Hugging Face Agent Trace Viewer
auto-detects (Claude Code / Codex / Pi). No dataset-side preprocessing is
needed; the Hub tags the dataset ``agent-traces`` and opens it in the viewer.

Docs: https://huggingface.co/docs/hub/agent-traces

Design notes
------------
* **Zero LLM turn.** This is a deterministic export — it never spends a
  model call. The ``hermes trace upload`` subcommand calls
  :func:`upload_session_trace` directly.
* **Private by default.** Traces can contain prompts, tool output, local
  paths, and secrets. The dataset is created private and every text body
  is passed through Hermes' secret redactor (``force=True``) unless the
  caller explicitly opts out with ``redact=False``.
* **Never raises.** Returns a user-facing status string so command
  handlers can echo it straight back to the user. Programmatic callers
  that need the URL can use :func:`build_trace_jsonl` + :func:`_do_upload`
  directly.
    )annotationsN)datetimetimezone)AnyDictListOptionalTuplezhermes-traceszhermes-agentzTrace upload blocked: secret redaction failed, so the transcript may still contain credentials or other sensitive data. Fix the redactor or rerun with --no-redact only after manually reviewing the transcript.c                      e Zd ZdZy)TraceRedactionErrorz<Raised when a trace cannot be safely redacted before upload.N)__name__
__module____qualname____doc__     E/root/.hermes/venv/lib/python3.12/site-packages/agent/trace_upload.pyr   r   .   s    Fr   r   c                 r    t        j                  t        j                        j	                  d      d d dz   S )Nz%Y-%m-%dT%H:%M:%S.%fZ)r   nowr   utcstrftimer   r   r   _now_isor   6   s-    <<%../EFsKcQQr   c                    |rt        | t              r| s| S 	 ddlm}  || d      S # t        $ r,}t
        j                  dd       t        t              |d}~ww xY w)a  Redact secrets from a string body when redaction is enabled.

    Non-strings pass through untouched. Uses Hermes' shared redactor with
    ``force=True`` so an upload always scrubs known secret shapes even if
    the user disabled log redaction globally.
    r   )redact_sensitive_textT)forcez.Trace upload redaction failed; refusing upload)exc_infoN)	
isinstancestragent.redactr   	Exceptionloggerwarningr   _REDACTION_BLOCKED_MESSAGE)textenabledr   excs       r   _redactr)   :   s_     *T3/tG6$T66 GGRVW!"<=3FGs   ( 	A'AAc           
     T   | g S t        | t              rdt        | |      dgS t        | t              rg }| D ]  }t        |t              r|j                  d      }|dk(  r/|j                  dt        |j                  dd      |      d       X|dv r|j                  ddd       q|j                  dt        t        j                  |      |      d       |j                  dt        t        |      |      d        |S dt        t        j                  |       |      dgS )zDNormalize a message ``content`` field into Anthropic content blocks.r&   typer&   r,    )	image_urlimagez[image omitted])	r   r    r)   listdictgetappendjsondumps)contentredactblockspartptypes        r   _content_to_blocksr;   K   s   	'3&)ABCC'4 ') 	TD$%(F?MM67488FTVCWY_;`"ab44 MM6;L"MNMM674::dCSU[;\"]^vws4y&7QRS	T WTZZ-@&%IJKKr   c                b   g }t        | t              s|S | D ]0  }t        |t              s|j                  d      xs i }|j                  d      xs |j                  d      xs d}|j                  d      }t        |t              r)	 |j                         rt        j                  |      ni }nt        |t              r|}ni }|r3	 t        j                  t        t        j                  |      |            }|j!                  d|j                  d      xs$ d	t#        j$                         j&                  d
d  ||d       3 |S # t        j                  t        f$ r d|i}Y w xY w# t        j                  t        f$ r% t        j                  d       t        t              w xY w)zEConvert OpenAI tool_calls into Anthropic ``tool_use`` content blocks.functionnametool	arguments_rawzHTrace upload redacted tool arguments are not valid JSON; refusing uploadtool_useidtoolu_N   )r,   rC   r>   input)r   r0   r1   r2   r    stripr4   loadsJSONDecodeError
ValueErrorr)   r5   r#   r$   r   r%   r3   uuiduuid4hex)
tool_callsr7   r8   tcfnr>   raw_argsparseds           r   _tool_calls_to_blocksrS   d   s   #%Fj$' "d#VVJ%2vvf~99666+&h$,191AH-r $'FFFGDJJv,>$GH 	&&,BF4::<+;+;CR+@*A"B	
 	+6 M% ((*5 , (+, ((*5 Fij)*DEEFs   'E2E0E-,E-0>F.r-   T)modelcwdr7   c               X   g }dt               d	 ddl}rA|j                  g dddd      }|j                  dk(  r|j                  j                         dfd}| D ]  }	|	j                  d	      }
|
d
k(  rt        t        j                               }|
dk(  rt        |	j                  d      |      }|j                  t        |	j                  d      |             |sdddg} ||      }d|d<   d|xs d|d|d<   |j                  t        j                   |d             ||
dk(  r|	j                  d      xs |	j                  d      xs d}t#        t%        |	j                  d      t              r|	j                  d      n#t        j                   |	j                  d            |      } ||      }d|d<   dd||dgd|d<   |j                  t        j                   |d             ||	j                  d      }t%        |t              rt#        ||      }nt        ||      } ||      }d|d<   d|d|d<   |j                  t        j                   |d             | dj'                  |      |rdz   S dz   S # t        $ r dY Jw xY w)uT  Render Hermes conversation messages as Claude Code JSONL text.

    Each non-system message becomes one JSONL line in the Claude Code
    transcript shape the HF Agent Trace Viewer auto-detects:

    * ``user`` / ``tool`` -> ``{"type": "user", "message": {...}}``
    * ``assistant``       -> ``{"type": "assistant", "message": {...}}``
      with ``content`` blocks (text + ``tool_use``).

    Tool results are emitted as user turns carrying a ``tool_result``
    block keyed by ``tool_call_id`` — the same way Claude Code records
    them. Turns are linked via ``uuid`` / ``parentUuid``.
    Nr-   r   )gitz	rev-parsez--abbrev-refHEADT   )capture_outputr&   timeoutrU   c           
     P    ddxs t        j                         t        | d	S )NFexternal)	
parentUuidisSidechainuserTyperU   	sessionIdversion	gitBranchrK   	timestamp)osgetcwd_HERMES_VERSION)	turn_uuidbase_tsrU   
git_branchparent
session_ids    r   _commonz"build_trace_jsonl.<locals>._common   s3      "%"))+#&# 

 
	
r   rolesystem	assistantr6   rN   r&   r+   r,   unknown)rn   rT   r6   messageF)ensure_asciir?   tool_call_id	tool_nameusertool_result)r,   tool_use_idr6   )rn   r6   
)rh   r    returnzDict[str, Any])r   
subprocessrun
returncodestdoutrG   r"   r2   r    rK   rL   r;   extendrS   r3   r4   r5   r)   r   join)messagesrl   rT   rU   r7   linesr{   rrm   msgrn   rh   r8   entryrx   result_contentr6   message_contentri   rj   rk   s    ` `              @@@r   build_trace_jsonlr      s   * E FjGJ
<#$s  A ||q XX^^-

 
  5wwv8

%	;'	(:FCFMM/0EvNO#)267I&E'E&M#+)! E)
 LLE>?F6>''.1SSWW[5ISVK$&01CS&I	"ZZ	 23N
 I&E"E&M)#.-  E) LLE>?F '')$gs##*7F#;O0&AO	"f$*GiTZZE:;k5n 99Uut55"55O  
s   AJ J)(J)c                     dD ]<  } t        j                  |       }|s|j                         s,|j                         c S  y)z=Return the user's Hugging Face token from the usual env vars.)HF_TOKENHUGGINGFACE_HUB_TOKENHUGGING_FACE_HUB_TOKENHUGGINGFACE_TOKENN)re   getenvrG   )varvals     r   _resolve_hf_tokenr      s:    c iin399;99; r   u]  Can't upload — no Hugging Face token is available. To set it up:

1. Create a token with WRITE access at https://huggingface.co/settings/tokens
   (New token -> type "Write" -> copy it).
2. Add it to your environment as HF_TOKEN (e.g. in ~/.hermes/.env):
     HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxx
3. Run /upload-trace again (or `hermes trace upload`).)dataset_nameprivatec                  	 ddl m} |j                  dd       	 ddlm}  ||      }	 |j                         }t        |t              r|j                  d	      nd
}	|	sy|	 d| }	 |j                  |d|d       d| d}	 |j                  | j                  d      ||dd|        d| d| d| S # t        $ r Y w xY w# t        $ r Y yw xY w# t        $ r }
t        j                  d|
       Y d
}
~
yd
}
~
ww xY w# t        $ r)}
t        j                  d||
       d| d|
 cY d
}
~
S d
}
~
ww xY w# t        $ r&}
t        j                  d||
       d|
 cY d
}
~
S d
}
~
ww xY w)zCreate (idempotently) the private dataset and push the trace file.

    Returns a user-facing status string. Never raises.
    r   )	lazy_depsztool.trace_uploadF)prompt)HfApizXHugging Face upload needs the `huggingface_hub` package (`pip install huggingface_hub`).)tokenr>   NzHF whoami failed: %szfYour Hugging Face token was rejected (whoami failed). Make sure it has WRITE access and isn't expired.z<Could not resolve your Hugging Face username from the token./datasetT)repo_id	repo_typer   exist_okz HF create_repo failed for %s: %sz Could not create/access dataset : z	sessions/z.jsonlzutf-8zadd session trace )path_or_fileobjpath_in_repor   r   commit_messagez HF upload_file failed for %s: %szUpload to Hugging Face failed: z,Uploaded -> https://huggingface.co/datasets/z/blob/main/z;
View in the trace viewer: https://huggingface.co/datasets/)toolsr   ensurer"   huggingface_hubr   ImportErrorwhoamir   r1   r2   r#   r$   create_repoupload_fileencode)jsonlr   rl   r   r   r   r   apiwhorv   er   r   s                r   
_do_uploadr     s   #,U;
4)
 e
CDjjl",S$"7swwvT
 Ma~&GAy'D 	 	
 zl&1L
5!LL1%/
|< 	 	
 ;7);|n ]IIP	S TW   	  434  D-q1CD  A97AF1'"QC@@A  597AF0445su   B9 C 3C *D (D8 9	CC	CC	D  C;;D 	D5D0*D50D58	E'E"E'"E'c                    ddl m} |r	 ||      n |       }|j                  |       xs | }|j                  |      xs i }|j	                  |      }||fS )zLoad a session's conversation + metadata from the SQLite store.

    Returns ``(messages, meta)``. ``meta`` is ``{}`` when the session row is
    missing (messages may still be present for a live, untitled session).
    r   )	SessionDBdb_path)hermes_stater   resolve_session_idget_sessionget_messages_as_conversation)rl   r   r   dbresolvedmetar   s          r   load_session_messagesr   I  sY     ''.7	#IKB$$Z0>JH>>(#)rD..x8HT>r   )rT   rU   r7   r   r   r   r   c                  | sy|xs
 t               }|st        S 	 t        | |      \  }}	|sy|xs |	j                  d      xs d	}	 t        || |||
      }|j                         syt        ||| ||      S # t        $ r)}
t        j                  d| |
       d|  d|
 cY d}
~
S d}
~
ww xY w# t        $ r	 t        cY S w xY w)zTop-level entry point used by the CLI/gateway/subcommand.

    Loads the session, converts it to Claude Code JSONL, and uploads it to
    the user's private ``{user}/hermes-traces`` dataset. Returns a
    user-facing status string and never raises.
    zNo active session to upload.r   z.Failed to load session %s for trace upload: %szCould not load session r   Nz-No transcript to upload for this session yet.rT   r-   )rl   rT   rU   r7   z1No transcript content to upload for this session.)r   rl   r   r   )r   _NO_TOKEN_MESSAGEr   r"   r#   r$   r2   r   r   r%   rG   r   )rl   rT   rU   r7   r   r   r   r   r   r   r   resolved_modelr   s                r   upload_session_tracer   Y  s    " -(&(E  ;.z7K$
 >5dhhw/52N	*!! 
 ;;=B! +  ;GUVW(Bqc::;   *))*s/   A9 B. 9	B+B& B+&B+.C ?C )rz   r    )r&   r   r'   boolrz   r   )r6   r   r7   r   rz   List[Dict[str, Any]])rN   r   r7   r   rz   r   )r   r   rl   r    rT   r    rU   r    r7   r   rz   r    )rz   Optional[str])r   r    r   r    rl   r    r   r    r   r   rz   r    )N)rl   r    rz   z+Tuple[List[Dict[str, Any]], Dict[str, Any]])rl   r    rT   r    rU   r    r7   r   r   r   r   r    r   r   rz   r    )!r   
__future__r   r4   loggingre   rK   r   r   typingr   r   r   r	   r
   	getLoggerr   r#   DEFAULT_DATASET_NAMErg   r%   RuntimeErrorr   r   r)   r;   rS   r   r   r   r   r   r   r   r   r   <module>r      s  2 #   	  ' 3 3			8	$&  K G, GRG"L2 N i6"i6 i6 	i6
 
i6 i6 	i6`=   -;T;T ;T 	;T
 ;T ;T 	;T~ "0& ,55 5 
	5
 5 5 5 5 	5r   