
    Zǻi9                       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	m
Z
mZmZ e
rddlmZmZ  ej                   e      ZdZdZdZdZdZd	Zdd
ZddZ G d d      ZdZddZd Z G d d      Z G d d      Z y)a  TCP tunnel for accessing services running inside sandboxes.

Establishes a WebSocket connection to the daemon's ``/tunnel`` endpoint,
runs a yamux multiplexing session on top, and forwards local TCP connections
through yamux streams to the target port inside the sandbox.
    )annotationsN)TYPE_CHECKINGAnyOptional)YamuxSessionYamuxStream         z>BHc                `    | j                  t        j                  t        t        |             y)zAWrite the 3-byte connect header on a freshly opened yamux stream.N)writestructpack_CONNECT_HEADER_FMTPROTOCOL_VERSION)streamports     W/opt/lhia/marcimex/agent/venv/lib/python3.12/site-packages/langsmith/sandbox/_tunnel.py_write_connect_headerr   %   s    
LL02BDIJ    c                H    | j                  d      }|st        d      |d   S )z0Read the 1-byte status response from the daemon.r	   z'tunnel: connection closed before statusr   )readConnectionError)r   datas     r   _read_statusr   *   s'    ;;q>DGHH7Nr   c                  0    e Zd ZdZddZddZd	dZd
dZy)
_WSAdaptera
  Adapts the ``websockets`` message API to a byte-stream interface.

    yamux requires a plain read/write/close byte stream.  WebSocket is
    message-based, so this adapter buffers partially consumed messages on
    reads and sends one binary message per write.
    c                b    || _         t               | _        t        j                         | _        y N)_ws	bytearray_buf	threadingLock_write_lock)selfwss     r   __init__z_WSAdapter.__init__?   s!    K	$>>+r   c                `   t        | j                        |k  rn| j                  j                         }t	        |t
              r|j                         }| j                  j                  |       t        | j                        |k  rnt        | j                  d |       }| j                  d |= |S r   )	lenr"   r    recv
isinstancestrencodeextendbytes)r&   nmsgresults       r   r   z_WSAdapter.readD   s    $))nq ((--/C#s#jjlIIS!	 $))nq  tyy!}%IIbqbMr   c                    | j                   5  | j                  j                  |       d d d        t        |      S # 1 sw Y   t        |      S xY wr   )r%   r    sendr*   )r&   r   s     r   r   z_WSAdapter.writeO   s?     	 HHMM$	 4y	 4ys	   <Ac                X    	 | j                   j                          y # t        $ r Y y w xY wr   )r    close	Exceptionr&   s    r   r7   z_WSAdapter.closeT   s'    	HHNN 		s    	))N)r'   r   returnNone)r1   intr:   r0   )r   r0   r:   r<   r:   r;   )__name__
__module____qualname____doc__r(   r   r   r7    r   r   r   r   7   s    ,
	
r   r   i @  c                4    t        j                         d fd}d fd}t        j                  |d      }t        j                  |d      }|j                          |j                          j	                          	  j                          	 j                  t        j                         	 j                          |j                  d       |j                  d       y# t        $ r Y aw xY w# t        $ r Y Pw xY w# t        $ r Y Nw xY w)	z:Copy data bidirectionally until one side closes or errors.c                     	 	 j                  t              } | snj                  |        *	 j	                          y # t        $ r Y w xY w# j	                          w xY wr   )r   _BRIDGE_BUF_SIZEsendallr8   setr   doner   tcp_conns    r   _stream_to_tcpz_bridge.<locals>._stream_to_tcpf   sc    		{{#34  &	  
 HHJ  		 HHJ(   +A  A  	A	A AA A!c                     	 	 j                  t              } | snj                  |        *	 j	                          y # t        $ r Y w xY w# j	                          w xY wr   )r+   rE   r   r8   rG   rH   s    r   _tcp_to_streamz_bridge.<locals>._tcp_to_streamr   sa    		}}%56T"	  
 HHJ  		 HHJrL   T)targetdaemon   )timeoutNr=   )r#   EventThreadstartwaitr7   r8   shutdownsocket	SHUT_RDWROSErrorjoin)r   rJ   rK   rN   t1t2rI   s   ``    @r   _bridger^   b   s    ??D

 
			=B				=BHHJHHJIIK&**+ GGAGGGAG      s6   C- C< 8D -	C98C9<	DD	DDc                 F    	 ddl m}  | S # t        $ r t        d      dw xY w)z5Import websockets sync client or raise a clear error.r   )connectz_TCP tunnel requires the 'websockets' package. Install it with: pip install 'langsmith[sandbox]'N)websockets.sync.clientr`   ImportError)
ws_connects    r   _ensure_websocketsrd      s7    @ @
 	s   
  c                      e Zd ZdZdZdZddd	 	 	 	 	 	 	 	 	 	 	 ddZedd       Zedd	       Z	dd
Z
ddZddZddZddZddZddZddZddZddZy)Tunnela  TCP tunnel to a port inside a sandbox.

    Opens a local TCP listener and forwards each accepted connection through
    a yamux-multiplexed WebSocket to the daemon, which dials the target port
    inside the sandbox.

    Typically used as a context manager::

        with sandbox.tunnel(remote_port=5432) as t:
            conn = psycopg2.connect(host="127.0.0.1", port=t.local_port)

    Or with explicit lifecycle::

        t = sandbox.tunnel(remote_port=5432)
        # ... use tunnel ...
        t.close()
          ?g       @r   r   
local_portmax_reconnectsc                   || _         || _        || _        |xs || _        | j                  | _        || _        d | _        d | _        d | _        d | _	        t        j                         | _        d| _        d| _        y )NF)_dataplane_url_api_key_remote_port_requested_local_port_local_port_max_reconnectsr    _yamux_server_socket_accept_threadr#   r$   _reconnect_lock_closed_startedr&   dataplane_urlapi_keyremote_portri   rj   s         r   r(   zTunnel.__init__   sy     ,'%/%>;"55-.27;:>(~~/r   c                    | j                   S )z&Local port the tunnel is listening on.)rp   r9   s    r   ri   zTunnel.local_port   s     r   c                    | j                   S )z4Port inside the sandbox that the tunnel connects to.)rn   r9   s    r   r{   zTunnel.remote_port   s        r   c                    | S r   rB   r9   s    r   	__enter__zTunnel.__enter__   s    r   c                $    | j                          y r   )r7   )r&   argss     r   __exit__zTunnel.__exit__   s    

r   c                    | j                   ry d| _         	 | j                          y # t        $ r | j                           w xY w)NT)rw   	_do_startr8   r7   r9   s    r   _startzTunnel._start   s=    ==	NN 	JJL	s	   ' Ac                   | j                          t        j                  t        j                  t        j                        | _        | j                  j                  t        j                  t        j                  d       | j                  }|dk7  rvt        j                  t        j                  t        j                        }	 |j                  d       |j                  d|f       |j                          t        d| d      | j                  j!                  d| j                  f       | j                  j#                  d	       | j                  j%                         d   | _        t)        j*                  | j,                  d
d      | _        | j.                  j1                          y # t        $ r Y n@t        $ r5}dt        |      v rndt        |      j                         v r 	 Y d }~nd }~ww xY w	 |j                          # t        $ r Y w xY w# 	 |j                          w # t        $ r Y w w xY wxY w)Nr	   r   rg   z	127.0.0.1zPort zE is already in use by another service. Choose a different local_port.zConnection refusedzalready in use   Tztunnel-accept)rO   rP   name)_connectrX   AF_INETSOCK_STREAMrs   
setsockopt
SOL_SOCKETSO_REUSEADDRro   
settimeoutr`   r7   rZ   ConnectionRefusedErrorr-   lowerbindlistengetsocknamerp   r#   rT   _accept_looprt   rU   )r&   r   probees       r   r   zTunnel._do_start   s   $mmFNNF<N<NO&&v'8'8&:M:MqQ
 ))19MM&..&2D2DEE  %{D12D6 "5 6 & 	  +t/I/I!JK""3'..::<Q?'..$$T
 	!!#- *  '3q61%Q7KKM KKM sm   AF. .	G67H 9G6+G1,H 1G66H :H 	HHH>H/.H>/	H;8H>:H;;H>c                B   ddl m} | j                  }|r	 |j                          t               }| j                         }i }| j                  r| j                  |d<    |||ddd      | _        t        | j                        } ||      | _        y# t        $ r Y ww xY w)z:Establish (or re-establish) the WebSocket + yamux session.r   )r   z	X-Api-Key   rQ   N)additional_headersopen_timeoutclose_timeoutping_interval)
langsmith.sandbox._yamuxr   rr   r7   r8   rd   _build_ws_urlrm   r    r   )r&   r   	old_yamuxrc   ws_urlheadersadapters          r   r   zTunnel._connect!  s    9KK	! ()
##%"$==#'==GK &
 TXX&"7+%  s   B 	BBc                   ddl m} | j                  r"| j                  j                  s| j                  S | j                  5  | j                  r+| j                  j                  s| j                  cddd       S d}t        | j                        D ]B  }	 | j                          t        j                  d|dz          | j                  c cddd       S   |d| j                   d      |# t        $ rY}|}|| j                  dz
  k  r;t        | j                  d|z  z  | j                        }t        j                  |       Y d}~d}~ww xY w# 1 sw Y   yxY w)	z4Return a live yamux session, reconnecting if needed.r   )TunnelErrorNz tunnel: reconnected (attempt %d)r	   r
   ztunnel: reconnect failed after z	 attempts)langsmith.sandbox._exceptionsr   rr   	is_closedru   rangerq   r   loggerdebugr8   min_BACKOFF_BASE_BACKOFF_MAXtimesleep)r&   r   last_errattemptexcdelays         r   _ensure_sessionzTunnel._ensure_session=  s8   =;;t{{44;;!! 	{{4;;#8#8{{	 	 -1H !5!56 **MMOLL!CWq[Q;;&	 	
* 1$2F2F1GyQ ! *"H!5!5!99 # ..!W*= --! 

5)*	 	sC   .E9E4C,EE,	E5AE	E	EEEc                    | j                   ryd| _         | j                  r	 | j                  j                          | j                  r| j                  j                          yy# t        $ r Y 3w xY w)z.Shut down the tunnel, closing all connections.NT)rv   rs   r7   rZ   rr   r9   s    r   r7   zTunnel.close[  se    <<##))+ ;;KK   s   A$ $	A0/A0c                    | j                   s^	 | j                  j                         \  }}t	        j
                  | j                  |fdd      j                          | j                   s]y y # t        $ r Y y w xY w)NTztunnel-bridge)rO   r   rP   r   )rv   rs   acceptrZ   r#   rT   _handle_connrU   )r&   conn_s      r   r   zTunnel._accept_loopl  sp    ,,--446a ((W$	
 eg ,,  s   A, ,	A87A8c                   	 | j                         }|j                         }t        || j                         t	        |      }|t
        k(  rt        ||       y |j                          |j                          |t        k(  r!t        j                  d| j                         y |t        k(  r!t        j                  d| j                         y |t        k(  rt        j                  dt               y t        j                  d|       y # t        $ rF}t        j                  d|       	 |j                          n# t         $ r Y nw xY wY d }~y Y d }~y d }~ww xY w)Nz%tunnel: port %d not allowed by daemonz3tunnel: nothing listening on port %d inside sandboxz.tunnel: protocol version mismatch (client v%d)ztunnel: unknown status %dz$tunnel: connection handler error: %s)r   open_streamr   rn   r   	STATUS_OKr^   r7   STATUS_PORT_NOT_ALLOWEDr   warningSTATUS_DIAL_FAILEDSTATUS_UNSUPPORTED_VERSIONr   r8   r   rZ   )r&   rJ   sessionr   statusr   s         r   r   zTunnel._handle_conny  s   $	**,G((*F!&$*;*;<!&)F")LLNNN00;%% --I%% 55D$
 :FC 	LL?E   !	sU   AD A	D #)D #D 1D 	EE(D98E9	EEEEEc                    | j                   j                  d      }|j                  dd      j                  dd      }| dS )N/zhttps://zwss://zhttp://zws://z/tunnel)rl   rstripreplace)r&   urls     r   r   zTunnel._build_ws_url  sA    !!((-kk*h/77	7Kgr   Nry   r-   rz   zOptional[str]r{   r<   ri   r<   rj   r<   r:   r;   r:   r<   )r:   rf   r   objectr:   r;   r=   )r:   r   )rJ   socket.socketr:   r;   )r:   r-   )r>   r?   r@   rA   r   r   r(   propertyri   r{   r   r   r   r   r   r   r7   r   r   r   rB   r   r   rf   rf      s    $ ML   	   
0     ! !
	*$X,8< "%Nr   rf   c                  r    e Zd ZdZddd	 	 	 	 	 	 	 	 	 	 	 ddZedd       Zedd       ZddZdd	Z	dd
Z
y)AsyncTunnelaw  Async wrapper around :class:`Tunnel`.

    The underlying tunnel runs in background threads (TCP listener + bridges);
    async context-manager methods delegate to the sync tunnel via the event
    loop's executor.

    Usage::

        async with await sandbox.tunnel(remote_port=5432) as t:
            conn = await asyncpg.connect(host="127.0.0.1", port=t.local_port)
    r   r   rh   c               .    t        |||||      | _        y )Nrh   )rf   _tunnelrx   s         r   r(   zAsyncTunnel.__init__  s     !)
r   c                .    | j                   j                  S r   )r   ri   r9   s    r   ri   zAsyncTunnel.local_port  s    ||&&&r   c                .    | j                   j                  S r   )r   r{   r9   s    r   r{   zAsyncTunnel.remote_port  s    ||'''r   c                   K   t        j                         }|j                  d | j                  j                         d {    | S 7 wr   )asyncioget_running_looprun_in_executorr   r   )r&   loops     r   
__aenter__zAsyncTunnel.__aenter__  s>     '')""4)<)<=== 	>s   >A	 AA	c                   K   t        j                         }|j                  d | j                  j                         d {    y 7 wr   )r   r   r   r   r7   )r&   r   r   s      r   	__aexit__zAsyncTunnel.__aexit__  s4     '')""4););<<<s   >A AAc                8    | j                   j                          y)z;Shut down the tunnel (sync, safe to call from any context).N)r   r7   r9   s    r   r7   zAsyncTunnel.close  s    r   Nr   r   )r:   r   r   r=   )r>   r?   r@   rA   r(   r   ri   r{   r   r   r7   rB   r   r   r   r     s    
$ 

 
 	
 
 
 

" ' ' ( (
=r   r   )r   r   r   r<   r:   r;   )r   r   r:   r<   )r   r   rJ   r   r:   r;   )!rA   
__future__r   r   loggingrX   r   r#   r   typingr   r   r   r   r   r   	getLoggerr>   r   r   r   r   r   r   r   r   r   r   rE   r^   rd   rf   r   rB   r   r   <module>r      s    #       / /B			8	$  	  !  K
! !P  1r
{ {F1 1r   