
    (<i}"              	         U d Z ddlmZ ddlZddlmZ ddlmZ ddlm	Z	 ddl
mZmZ  ej                  d      Zd	Zd
ed<   dZd
ed<    G d dee	      Ze G d d             Zej*                  ej*                  ej*                  ej,                  ej,                  ej,                  ej,                  ej,                  dZded<   edf	 	 	 	 	 ddZeed	 	 	 	 	 	 	 ddZeeed	 	 	 	 	 	 	 dd       Zy)u  브라우저 선택 라우터 — purpose에 따라 Lightpanda/Chrome 자동 선택

Usage:
    from tools.browser_router import get_browser, BrowserChoice, BrowserEngine

    # 브라우저 선택 (메타데이터만)
    choice = await get_browser("crawl")
    # choice.engine == BrowserEngine.LIGHTPANDA
    # choice.endpoint == "ws://127.0.0.1:9333"

    # 실제 Playwright BrowserContext 생성
    async with create_browser_context("crawl") as (browser, context):
        page = await context.new_page()
        ...
    )annotationsN)asynccontextmanager)	dataclass)Enum)AsyncGeneratorTuplebrowser_routerzws://127.0.0.1:9333strLIGHTPANDA_ENDPOINTzws://127.0.0.1:9222CHROME_ENDPOINTc                      e Zd ZdZdZdZy)BrowserEngineu!   지원하는 브라우저 엔진.
lightpandachromeN)__name__
__module____qualname____doc__
LIGHTPANDACHROME     E/home/jay/workspace/.worktrees/task-2057-dev2/tools/browser_router.pyr   r   '   s    +JFr   r   c                  0    e Zd ZU dZded<   ded<   ded<   y)BrowserChoiceuZ   브라우저 선택 결과 — 엔진, 접속 엔드포인트, 선택 사유를 담는다.r   enginer
   endpointreasonN)r   r   r   r   __annotations__r   r   r   r   r   .   s    dMKr   r   )crawlbulktextlogin
screenshotpdfspastealthzdict[str, BrowserEngine]PURPOSE_MAPg       @c                J  K   ddl m} 	  ||       }|j                  xs d}|j                  xs d}d| d| d}	 dd	l}	 |j                         4 d	{   }|j                  ||j                  |
            4 d	{   }	|	j                  dk(  }
t        j                  d|	j                  |
       |
cd	d	d	      d	{    cd	d	d	      d	{    S # t        $ r d\  }}Y w xY w7 7 n7 .7 # 1 d	{  7  sw Y   nxY wd	d	d	      d	{  7   n# 1 d	{  7  sw Y   nxY wy	y	# t        $ r }t        j                  d|       Y d	}~yd	}~ww xY w# t        $ r dd	l}	 |j                  j                  |      }|j                  j!                  ||      5 }	|	j                  dk(  }
t        j                  d|	j                  |
       |
cd	d	d	       cY S # 1 sw Y   nxY wY y	# t        $ r!}t        j                  d|       Y d	}~Y yd	}~ww xY ww xY ww)uU  Lightpanda 서비스 가용 여부를 확인한다.

    ``endpoint`` (ws://host:port 형식)에서 호스트와 포트를 파싱하여
    ``http://host:port/json/version`` 으로 GET 요청을 보낸다.
    200 응답이 오면 True, 그 외 모든 경우(타임아웃·연결 실패·비200)엔 False를 반환한다.
    예외는 절대 호출자에게 전파되지 않는다.

    Args:
        endpoint: Lightpanda WebSocket 엔드포인트 (ws://host:port 형식).
        timeout: HTTP 요청 타임아웃(초).

    Returns:
        서비스가 정상이면 True, 아니면 False.
    r   )urlparse	127.0.0.1u$  )r+   r,   zhttp://:z/json/versionN)total)timeout   u0   Lightpanda health check → status=%d healthy=%sz,Lightpanda health check failed (aiohttp): %sFz+Lightpanda health check failed (urllib): %s)urllib.parser*   hostnameport	ExceptionaiohttpClientSessiongetClientTimeoutstatusloggerdebugImportErrorurllib.requestrequestRequesturlopen)r   r/   r*   parsedhostr3   
health_urlr5   sessionresphealthyexcurllibreqs                 r   check_lightpanda_healthrJ   K   s    $ &'(#-+{{"d 4&$}5J	,,. # #'";;z7;P;PW^;P;_;` # #dh"kkS0GLL!SUYU`U`bij"# # ## # #  '&
d'## ### # ## # # # # #
  	LLGM	  	..((4C''W'= ++,OQUQ\Q\^ef     	LLFL	sd  H#(C 	H#E  D4 C.D4 (DC0D2C67DC2DD4 C4D4 H#C+(H#*C++H#.D4 0D2D4D4 6D	<C?=D	DD4 DD4 D/#D&$D/+D4 2H#4	E=EE  H#EE   H .8G2&2G$	G2!H "H#$G-	)G20H#2	H;HH H#HH  H#lightpanda_endpointchrome_endpointc                 K   t         j                  | t        j                        }t        j                  d| |j                         |t        j                  u rlt        |       d{   }|r t        t        j                  |d|  d      S t        j                  d| |       t        t        j                  |d|  d      S t        t        j                  |d|  d      S 7 }w)	u  purpose에 맞는 브라우저를 선택하여 BrowserChoice를 반환한다.

    PURPOSE_MAP에 정의된 purpose이면 해당 엔진을 우선 선택한다.
    알 수 없는 purpose는 Chrome으로 fallback한다(안전한 기본 선택).
    Lightpanda가 preferred이나 서비스가 불가하면 Chrome으로 자동 fallback한다.

    Args:
        purpose: 사용 목적 식별자 (예: "crawl", "screenshot", "pdf" 등).
        lightpanda_endpoint: Lightpanda CDP WebSocket 엔드포인트 (기본: ws://127.0.0.1:9333).
        chrome_endpoint: Chrome CDP WebSocket 엔드포인트 (기본: ws://127.0.0.1:9222).

    Returns:
        선택된 엔진 정보를 담은 BrowserChoice 인스턴스.
    u$   purpose='%s' → preferred engine=%sNz	purpose 'u.   ' → Lightpanda (텍스트 전용, 고성능))r   r   r   uA   purpose='%s': Lightpanda 불가 → Chrome fallback (endpoint=%s)u3   ' → Chrome fallback (Lightpanda 서비스 불가)u,   ' → Chrome (렌더링/상호작용 필요))r(   r7   r   r   r:   r;   valuer   rJ   r   warning)purposerL   rM   	preferredrF   s        r   get_browserrS      s     ( )=)=>I
LL7)//RM,,,/0CDD $//,"7)+YZ  NNS
 !$++("7)+^_  ## 7)#OP ) Es   A&C((C&)A>C(c              ,  K   ddl m} t        | ||       d{   }t        j	                  d|j
                  j                  |j                  |j                          |       4 d{   }|j                  j                  |j                         d{   }|j                  r|j                  d   n|j                          d{   }	 ||f |j                          d{    t        j                  d|j
                  j                         ddd      d{    y7 7 7 7 f7 H# |j                          d{  7   t        j                  d|j
                  j                         w xY w7 X# 1 d{  7  sw Y   yxY ww)u  purpose에 맞는 브라우저를 선택하고 Playwright BrowserContext를 생성한다.

    선택된 엔진에 관계없이 ``chromium.connect_over_cdp()`` 를 사용하여 CDP로 접속한다.
    컨텍스트 매니저 종료 시 browser.close()가 자동 호출된다.

    Args:
        purpose: 사용 목적 식별자 (get_browser()와 동일).
        lightpanda_endpoint: Lightpanda CDP WebSocket 엔드포인트.
        chrome_endpoint: Chrome CDP WebSocket 엔드포인트.

    Yields:
        (browser, context) 튜플.

    Example:
        async with create_browser_context("crawl") as (browser, context):
            page = await context.new_page()
            await page.goto("https://example.com")
    r   )async_playwrightrK   Nz7create_browser_context: engine=%s endpoint=%s reason=%su"   browser.close() 완료 (engine=%s))playwright.async_apirU   rS   r:   infor   rO   r   r   chromiumconnect_over_cdpcontextsnew_contextcloser;   )rQ   rL   rM   rU   choicepwbrowsercontexts           r   create_browser_contextra      sG    2 6w<OapqqF
KKA	  ! T TR44V__EE)0)9)9'""1%WEXEXEZ?Z	T7""--/!!LL=v}}?R?RST T T rTE?Z "'--/!!LL=v}}?R?RST T T Ts   FD*AF*D-+F.)E?D/2E?
D1E?D5E?*D3+.E?F$E=%F-F/E?1E?3E?5E:	E

0E::E?=F?FFFF)r   r
   r/   floatreturnbool)rQ   r
   rL   r
   rM   r
   rc   r   )rQ   r
   rL   r
   rM   r
   rc   zAsyncGenerator[Tuple, None])r   
__future__r   logging
contextlibr   dataclassesr   enumr   typingr   r   	getLoggerr:   r   r   r   r
   r   r   r   r   r(   rJ   rS   ra   r   r   r   <module>rl      sl    #  * !  (			+	, 1 S 0, ,C     %%$$$$!!&&##	)% 	" (777 
7D  3*	00 0 	0
 0p   3*	)T)T )T 	)T
 !)T )Tr   