
    i8O                       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ZddlZddl	Z	ddl
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 ddlmZmZ ddlmZ ddlmZ d	d
lmZmZmZmZ ddl m!Z! ddl"m#Z#m$Z$m%Z% ddl&m'Z'm(Z(m)Z)  ejT                  e+      Z,dZ-e G d d             Z.	 	 	 	 	 	 d#dZ/	 d$	 	 	 	 	 	 	 	 	 	 	 d%dZ0	 	 	 	 d&dZ1 e2ejf                  ejh                  ejj                  ejl                  ejn                  ejp                  h      Z9ddd	 	 	 	 	 	 	 d'dZ:	 	 	 	 	 	 	 	 	 	 d(dZ;	 	 	 	 	 	 	 	 d)dZ<d*dZ=d+dZ>d,dZ?d-dZ@d.dZAd/d ZB	 	 	 	 	 	 	 	 	 	 	 	 	 	 d0d!ZCd1d"ZDy)2uw  Materialize a :class:`SessionStore`-backed resume into a temp ``CLAUDE_CONFIG_DIR``.

When ``options.resume`` (or ``options.continue_conversation``) is paired with
``options.session_store``, the session JSONL almost certainly does not exist on
local disk — it lives in the external store. The CLI subprocess only knows how
to resume from a local file. This module bridges the gap: it loads the session
from the store, writes it to a temporary directory laid out exactly like
``~/.claude/``, and returns the path so the caller can point the subprocess at
it via ``CLAUDE_CONFIG_DIR``.

Mirrors the behavior of the TypeScript SDK.
    )annotationsN)	AwaitableCallable)suppress)	dataclassreplace)Path)Any   )ClaudeAgentOptions
SessionKeySessionStoreSessionStoreFlushMode   )_store_implements)_get_projects_dir_validate_uuidproject_key_for_directory)MAX_PENDING_BYTESMAX_PENDING_ENTRIESTranscriptMirrorBatcherzClaude Code-credentialsc                  0    e Zd ZU dZded<   ded<   ded<   y)	MaterializedResumeu  Result of :func:`materialize_resume_session`.

    Attributes:
        config_dir: Temporary directory laid out like ``~/.claude/`` —
            point the subprocess at it via ``CLAUDE_CONFIG_DIR``.
        resume_session_id: Session ID to pass as ``--resume``. When the
            input was ``continue_conversation``, this is the most-recent
            session resolved via :meth:`SessionStore.list_sessions`.
        cleanup: Coroutine that removes ``config_dir`` (best-effort).
            Call it after the subprocess exits.
    r	   
config_dirstrresume_session_idzCallable[[], Awaitable[None]]cleanupN)__name__
__module____qualname____doc____annotations__     p/volume1/homes/robertsu/coba/app/.venv/lib/python3.12/site-packages/claude_agent_sdk/_internal/session_resume.pyr   r   2   s    
 **r$   r   c                z    t        | i | j                  dt        |j                        i|j                  d      S )a  Return a copy of ``options`` repointed at a materialized temp config dir.

    Sets ``CLAUDE_CONFIG_DIR`` in ``env``, ``resume`` to the materialized
    session id, and clears ``continue_conversation`` (already resolved to a
    concrete session id during materialization).
    CLAUDE_CONFIG_DIRF)envresumecontinue_conversation)r   r(   r   r   r   )optionsmaterializeds     r%   apply_materialized_optionsr-   E   sH     
kk
\%<%<!=
 --# r$   c                    |t        |j                  dz        nt        t        |            }|dk(  }t        | |||rdnt        |rd      S t
              S )u  Construct the :class:`TranscriptMirrorBatcher` for a session.

    Resolves ``projects_dir`` to the materialized temp dir when present
    (so file_path → key resolution matches what the subprocess writes),
    otherwise to the standard projects directory under the effective
    ``CLAUDE_CONFIG_DIR``.

    ``flush_mode="eager"`` zeroes the batcher's pending thresholds so every
    enqueued frame schedules a background flush; ``"batched"`` keeps the
    defaults (flush on ``result`` or 500-entry / 1 MiB overflow).
    projectseagerr   )storeprojects_diron_errormax_pending_entriesmax_pending_bytes)r   r   r   r   r   r   )r1   r,   r(   r3   
flush_moder2   r0   s          r%   build_mirror_batcherr7   Y   sp    ( # 	L##j01"3'( 
 '!E"!!&A,?$! 
 +< r$   c                  	K   | j                   }|y| j                  | j                  sy| j                  dz  }t	        | j
                        }| j                  7t        | j                        yt        ||| j                  |       d{   }nt        |||       d{   }|y|\  }}t        t        j                  d            		 	dz  |z  }|j                  dd       t        || dz  |       t        	| j                         t!        |d	      rt#        |	||||       d{    d	fd
}t)        	||      S 7 7 7 # t$        $ r t'        	       d{  7    w xY ww)u  Load a session from ``options.session_store`` and write it to a temp dir.

    Returns ``None`` when no materialization is needed (no store, no
    resume/continue, store has no entries, or the resolved session ID is not a
    valid UUID) — caller falls through to the normal (no-store) resume/spawn
    path. For ``continue_conversation`` this means a fresh session; for an
    explicit ``resume`` value the CLI receives it unchanged.

    Raises ``RuntimeError`` if a store call fails or times out.
    N  zclaude-resume-)prefixr/   Tparentsexist_ok.jsonllist_subkeysc                 8   K   t                d {    y 7 wN)_rmtree_with_retry)tmp_bases   r%   r   z+materialize_resume_session.<locals>.cleanup   s      ***s   )r   r   r   )returnNone)session_storer)   r*   load_timeout_msr   cwdr   _load_candidate_resolve_continue_candidater	   tempfilemkdtempmkdir_write_jsonl_copy_auth_filesr(   r   _materialize_subkeysBaseExceptionrB   r   )
r+   r1   	timeout_sproject_keyresolved
session_identriesproject_dirr   rC   s
            @r%   materialize_resume_sessionrX   z   s     !!E}~~g&C&C''$.I+GKK8K
 ~~! '..)1(W^^YWW4UKSS"JH$$,<=>H+k9$6[j\#88'B
 	7;;/ UN3&xk:y  + $ I XS$   !***s[   B
E3EE3#E$+E3A"E 2E3E 7E3E3E E0(E+)E00E3   g?)retriesdelayc                 K   | j                         syt        |      D ]  }	 t        j                  |         y t        j                  | d       y# t        $ r2}|j
                  t        vrt        |t              sY d}~ IY d}~nd}~ww xY w	 t        j                  |       d{  7   # t        j                  $ r t        j                  | d        w xY ww)a  Best-effort ``shutil.rmtree`` with retries on transient lock errors.

    On Windows, AV/indexer can briefly hold a handle on freshly-written
    files (notably ``.credentials.json``), causing rmtree to fail with
    EBUSY/EPERM. Retry a few times with a short backoff; after exhausting
    retries, fall back to ``ignore_errors=True`` (matches the previous
    behavior, but gives the handle a chance to release first so the access
    token doesn't leak in temp). Never raises.
    NT)ignore_errors)existsrangeshutilrmtreeOSErrorerrno_RETRYABLE_RMTREE_ERRNOS
isinstancePermissionErrorasynciosleepCancelledError)pathrZ   r[   _es        r%   rB   rB      s      ;;=7^	MM$   MM$d+  	ww66z?@ 		
	--&&&%% 	 MM$d3		sQ    C AC 	B"B	>C 	BC B1*B-+B10C 1,CC c                p   K   t        | j                  ||d      |d|        d{   }|sy||fS 7 w)zBLoad entries for ``session_id``; return ``None`` if empty/missing.rS   rU    SessionStore.load() for session N)_with_timeoutload)r1   rS   rU   rR   rV   s        r%   rI   rI      sM      "

;jIJ
*:,7 G
 ws   &646c                <  K   t        | j                  |      |d       d{   }|syt        |d d      D ][  }|d   }t        |      t	        | |||       d{   }|-|d   d   }t        |t              r|j                  d	      du rY|c S  y7 u7 ;w)
u  Pick the most-recently-modified non-sidechain session.

    Sidechain transcripts are mirrored as ordinary top-level keys and often
    have the highest mtime (their append lands after the main session's in
    the same flush). Walk newest→oldest, loading each candidate (the load is
    needed anyway) and skipping sidechains so ``--continue`` resumes the
    user's conversation, not a subagent's. Matches the CLI's own
    ``--continue`` filter and ``list_sessions_from_store()``.
    zSessionStore.list_sessions()Nc                    | d   S )Nmtimer#   )ss    r%   <lambda>z-_resolve_continue_candidate.<locals>.<lambda>  s    qzr$   T)keyreverserU   r   r   isSidechain)rp   list_sessionssortedr   rI   re   dictget)r1   rS   rR   sessionscandsidloadedfirsts           r%   rJ   rJ     s      #K(& H
 x%94H< #&&uk3	JJ>q	!eT"uyy'?4'G I % Ks!    BB;BB:BBc           	        K   	 t        j                  | |       d{   S 7 # t         j                  $ r#}t        | dt	        |dz         d      |d}~wt
        $ r}t        | d|       |d}~ww xY ww)zKAwait ``coro`` with a timeout, re-raising as ``RuntimeError`` with context.)timeoutNz timed out after r9   z ms during resume materializationz' failed during resume materialization: )rg   wait_forTimeoutErrorRuntimeErrorint	Exception)cororR   whatrl   s       r%   rp   rp   %  s     W%%dI>>>> f%c)d*:&;%< = 
 	  WdV#J1#NOUVVWs<   A;% #% A;% A8AA8"A33A88A;c                v   | j                   j                  dd       | j                  dd      5 }|D ]9  }|j                  t	        j
                  |d             |j                  d       ; 	 d	d	d	       t        t              5  | j                  d
       d	d	d	       y	# 1 sw Y   3xY w# 1 sw Y   y	xY w)zHStream-write ``entries`` as one JSON line each to ``path`` (mode 0o600).Tr;   wutf-8encoding),:)
separators
N  )	parentrM   openwritejsondumpsr   rb   chmod)rj   rV   frl   s       r%   rN   rN   2  s    KKdT2	3	)QAGGDJJqZ89GGDM  
* 
'	

5 
		 
*	) 
	s   ?B#B/#B,/B8c                   |j                  d      xs t        j                  j                  d      }|rt        |      nt        j                         dz  }d}|dz  }t        t              5  |j                  d      }ddd       |n|j                  d      s]t        j                  j                  d      s>|j                  d      s-t        j                  j                  d      st               }||}t        || dz         |rt        |      d	z  nt        j                         d	z  }t        || d	z         y# 1 sw Y   xY w)
aR  Copy ``.credentials.json`` (refreshToken redacted) and ``.claude.json``.

    Source resolution mirrors the CLI:
    - ``.credentials.json`` lives under the config dir (default ``~/.claude/``)
    - ``.claude.json`` lives at ``$CLAUDE_CONFIG_DIR/.claude.json`` when set,
      else ``~/.claude.json`` (NOT ``~/.claude/.claude.json``)
    r'   z.claudeNz.credentials.jsonr   r   ANTHROPIC_API_KEYCLAUDE_CODE_OAUTH_TOKENz.claude.json)r}   osenvironr	   homer   FileNotFoundError	read_text_read_keychain_credentials_write_redacted_credentials_copy_if_present)rC   opt_envcaller_config_dirsource_config_dir
creds_json
creds_pathkeychainclaude_json_srcs           r%   rO   rO   =  s)     $78 BJJNN= $5$))+	:Q  "J"%88J	#	$))7);
 
% 	!KK+,

?R0S KK12zz~~78 ./!J
H7J,JK  	.0YY[>) 
 _h&?@= 
%	$s   -D>>Ec                   | y| }	 t        j                  |       }t        |t              r|j	                  d      nd}t        |t              rd|v r|d= t        j
                  |      }|j                  |d       t        t              5  |j                  d       ddd       y# t         j                  t        f$ r Y Xw xY w# 1 sw Y   yxY w)u  Write ``creds_json`` with ``claudeAiOauth.refreshToken`` removed.

    The resumed subprocess runs under a redirected ``CLAUDE_CONFIG_DIR``; if it
    refreshed, the single-use refresh token would be consumed server-side and
    the new tokens written to a location the parent never reads back — leaving
    the parent's stored creds revoked. With no ``refreshToken``, the
    subprocess's refresh check short-circuits.
    NclaudeAiOauthrefreshTokenr   r   r   )r   loadsre   r|   r}   r   JSONDecodeError
ValueError
write_textr   rb   r   )r   dstoutdataoauths        r%   r   r   o  s     
Czz*%-7d-C)eT"~'>n%**T"C NN3N)	'			% 
		   *-  
	s   A$B( C(CCCc                x    t        t              5  t        j                  | |       d d d        y # 1 sw Y   y xY wrA   )r   r   r`   copyfile)srcr   s     r%   r   r     s%    	#	$S! 
%	$	$s   09c            	        t        j                         dk7  ry	 t        j                  j	                  d      xs t        j                         } 	 t        j                  ddd| dd	t        gd
d
dd      }|j                  dk7  ry|j                  j                         }|xs dS # t        $ r d} Y cw xY w# t        $ r Y yw xY w)u   Read OAuth credentials JSON from the macOS Keychain (default service name).

    Best-effort — returns ``None`` on any error or non-macOS platforms.
    DarwinNUSERzclaude-code-usersecurityzfind-generic-passwordz-az-wz-sT   F)capture_outputtextr   checkr   )platformsystemr   r   r}   getpassgetuserr   
subprocessrun_KEYCHAIN_SERVICE_NAME
returncodestdoutstrip)userresultr   s      r%   r   r     s     H$"zz~~f%:):'&  
" A
--


C;$/  "!"$  s#   5B% %B6 %B32B36	CCc           	       K   ||z  }t        | j                  ||d      |d|        d{   }|D ]  }t        ||      st        j	                  d|       '|||d}	t        | j                  |	      |d| d|        d{   }
|
sZg }g }|
D ]I  }t        |t              r&|j                  d      d	k(  r|j                  |       9|j                  |       K ||z  j                  ||z  j                  d
z         }|rt        ||       |s|d   j                         D ci c]  \  }}|dk7  s|| }}}|j                  |j                  dt        d
        dz         }|j                  j!                  dd       |j#                  t%        j&                  |      d       t)        t*              5  |j-                  d       ddd        y7 7 Yc c}}w # 1 sw Y   xY ww)zFLoad and write all subagent transcripts/metadata under ``session_id``.rn   z(SessionStore.list_subkeys() for session Nz<[SessionStore] skipping unsafe subpath from list_subkeys: %r)rS   rU   subpathro   z	 subpath typeagent_metadatar>   z
.meta.jsonTr;   r   r   r   )rp   r?   _is_safe_subpathloggerwarningrq   re   r|   r}   append	with_namenamerN   itemslenr   rM   r   r   r   r   rb   r   )r1   rC   rW   rS   rU   rR   session_dirsubkeysr   sub_keysub_entriesmetadata
transcriptrl   sub_filekvmeta_content	meta_files                      r%   rP   rP     s     
*K!;jQR
2:,? G
 
  5NNNPW  '$

 *JJw.zl)G9M
 

  *, "
A!T"quuV}8H'H"!!!$	   ')447"((83
 :.-5b\-?-?-AQ-ATQQ&[AqD-ALQ **.X/,>I ""4$"?  L!9G L'"& #"a *
4 R #"sZ   +G5GAG5GBG5G5+G"9G">B G5>G(G5G5"G5(G2	-G5c                   | syt        |       j                         s| j                  d      ryt        j                  |       d   ryt        d t        j                  d|       D              ryd| v ry|| z  }	 |j                  |j                  dz         j                         }|j                  |j                                y# t        t        f$ r Y yw xY w)	ziReject subpaths that are empty, absolute, contain ``..``, or escape
    ``session_dir`` after resolution.F)/\r   c              3  $   K   | ]  }|d v  
 yw)).z..Nr#   ).0ps     r%   	<genexpr>z#_is_safe_subpath.<locals>.<genexpr>  s     
A%@1%@s   z[\\/] r>   T)r	   is_absolute
startswithntpath
splitdriveanyresplitr   r   resolverelative_tor   rb   )r   r   targetr   s       r%   r   r     s      G}  "g&8&8&E !!$

ARXXh%@
AA 7"F##FKK($:;CCE[0023    s   :AC CC)r+   r   r,   r   rD   r   )batched)r1   r   r,   MaterializedResume | Noner(   zdict[str, str] | Noner3   z3Callable[[SessionKey | None, str], Awaitable[None]]r6   r   rD   r   )r+   r   rD   r   )rj   r	   rZ   r   r[   floatrD   rE   )
r1   r   rS   r   rU   r   rR   r   rD   tuple[str, list[Any]] | None)r1   r   rS   r   rR   r   rD   r   )r   zAwaitable[Any]rR   r   r   r   rD   r
   )rj   r	   rV   z	list[Any]rD   rE   )rC   r	   r   zdict[str, str]rD   rE   )r   
str | Noner   r	   rD   rE   )r   r	   r   r	   rD   rE   )rD   r   )r1   r   rC   r	   rW   r	   rS   r   rU   r   rR   r   rD   rE   )r   r   r   r	   rD   bool)Er!   
__future__r   rg   rc   r   r   loggingr   r   r   r   r`   r   rK   collections.abcr   r   
contextlibr   dataclassesr   r   pathlibr	   typingr
   typesr   r   r   r   session_store_validationr   r~   r   r   r   transcript_mirror_batcherr   r   r   	getLoggerr   r   r   r   r-   r7   rX   	frozensetEBUSYEMFILEENFILE	ENOTEMPTYEPERMEACCESrd   rB   rI   rJ   rp   rN   rO   r   r   r   rP   r   r#   r$   r%   <module>r     s=   #       	  	    /  *   W W 7 R R  
		8	$ 3  + + +$/A2 )2+ 
 B	
 & BFFF^ %	  #$C,
,,,1,	,B&)7:GL!&)6;!B
W/Ad4"
"J@'@'@' @' 	@'
 @' @' 
@'F r$   