
    `gjji                       U d Z ddlmZ ddlZddlZddlZddlmZmZm	Z	 ddl
mZmZ  ej                  e      ZdZh dZd-dZ	 ddlZdd	lmZ d
Zd Zd.dZd/dZ	 	 	 	 	 	 	 	 d0dZ	 	 	 	 	 	 	 	 	 	 	 	 d1dZ	 	 	 	 	 	 	 	 	 	 	 	 d2dZ	 	 	 	 	 d3	 	 	 	 	 	 	 	 	 	 	 	 	 d4dZdde dddddddi d
dddddddd d!d"d#d$dgd%d&Z de!d'<   d5d(Z" ejF                  dd)e d* e"d+,       y# e$ r	 dZeZdZY w xY w)6u  
Raw Chrome DevTools Protocol (CDP) passthrough tool.

Exposes a single tool, ``browser_cdp``, that sends arbitrary CDP commands to
the browser's DevTools WebSocket endpoint.  Works when a CDP URL is
configured — either via ``/browser connect`` (sets ``BROWSER_CDP_URL``) or
``browser.cdp_url`` in ``config.yaml`` — or when a CDP-backed cloud provider
session is active.

This is the escape hatch for browser operations not covered by the main
browser tool surface (``browser_navigate``, ``browser_click``,
``browser_console``, etc.) — handling native dialogs, iframe-scoped
evaluation, cookie/network control, low-level tab management, etc.

Method reference: https://chromedevtools.github.io/devtools-protocol/
    )annotationsN)AnyDictOptional)registry
tool_errorz3https://chromedevtools.github.io/devtools-protocol/>   Page.reloadPage.stopLoadingTarget.getTargetsBrowser.getVersionTarget.detachFromTargetPage.navigateTarget.attachToTargetc                l   ddl m} t        | t              r
 || d      S t        | t              r| D cg c]  }t        |       c}S t        | t              rt        d | D              S t        | t              r-| j                         D ci c]  \  }}|t        |       c}}S | S c c}w c c}}w )z>Redact browser-originated CDP result data before returning it.r   )redact_sensitive_textT)forcec              3  2   K   | ]  }t        |        y w)N)_redact_cdp_output).0items     I/root/.hermes/venv/lib/python3.12/site-packages/tools/browser_cdp_tool.py	<genexpr>z%_redact_cdp_output.<locals>.<genexpr>6   s     @$'-@s   )	agent.redactr   
isinstancestrlistr   tupledictitems)valuer   r   keys       r   r   r   -   s    2%$U$77%5:;T"4(;;%@%@@@%?D{{}M)#t'--MML < Ns   B+B0)WebSocketExceptionTFc                h   	 t        j                         }|rj|j                         rZddl}|j
                  j                  d      5 }|j                  t         j                  |       }|j                         cddd       S t        j                  |       S # t        $ r d}Y w xY w# 1 sw Y   /xY w)zJRun an async coroutine from a sync handler, safe inside or outside a loop.Nr      )max_workers)
asyncioget_running_loopRuntimeError
is_runningconcurrent.futuresfuturesThreadPoolExecutorsubmitrunresult)coroloop
concurrentpoolfutures        r   
_run_asyncr5   N   s    '') !!22q2A 	#T[[d3F==?	# 	# ;;t  	# 	#s   B 0B(B%$B%(B1c                     	 ddl m}   |        xs dj                         S # t        $ r }t        j                  d|       Y d}~yd}~ww xY w)aR  Return the normalized CDP WebSocket URL, or empty string if unavailable.

    Delegates to ``tools.browser_tool._get_cdp_override`` so precedence stays
    consistent with the rest of the browser tool surface:

    1. ``BROWSER_CDP_URL`` env var (live override from ``/browser connect``)
    2. ``browser.cdp_url`` in ``config.yaml``
    r   )_get_cdp_override z/browser_cdp: failed to resolve CDP endpoint: %sN)tools.browser_toolr7   strip	Exceptionloggerdebug)r7   excs     r   _resolve_cdp_endpointr?   c   sC    8!#)r0022 FLs   ! 	A
AA
methodc                4    t        d|  d|d|t              S )Nz9Blocked: page URL targets a private or internal address (z). Raw CDP method z, could expose private page content or state.r@   cdp_docs)r   CDP_DOCS_URL)blocked_urlr@   s     r   _private_page_guard_errorrF   u   s2    	=*6* 5!	!      Dict[str, Any]c                Z   	 ddl m} |j                  |       sy|dk(  rjt        |xs i j	                  d      xs d      j                         }|r8|j                  |      s|j                  |      st        d| d|t        	      S |d
k(  rKt        |xs i j	                  d      xs d      }|j                  |      }|rt        d| d|t        	      S |t        vr|j                  |       }|rt        ||      S y# t        $ r }t        j!                  d|       Y d}~yd}~ww xY w)a  Apply the browser SSRF/private-page guard to raw CDP calls.

    ``browser_cdp`` is intentionally an escape hatch, but it still shares the
    same cloud/private-network boundary as ``browser_snapshot``,
    ``browser_console`` and ``browser_eval``.  If a cloud browser has landed on
    a private/internal URL (for example via a prior eval navigation), raw CDP
    calls like ``Runtime.evaluate`` or ``DOM.getDocument`` must not become the
    sibling bypass for the guarded browser tools.
    r   )browser_toolNr   urlr8   zDBlocked: CDP Page.navigate target is a private or internal address (z).rB   zRuntime.evaluate
expressionzPBlocked: CDP Runtime.evaluate expression targets a private or internal address (z0browser_cdp: private-page guard probe failed: %s)toolsrJ   _eval_ssrf_guard_activer   getr:   _is_always_blocked_url_is_safe_urlr   rD   _expression_targets_private_url!_CDP_PRIVATE_PAGE_ALLOWED_METHODS_current_page_private_urlrF   r;   r<   r=   )	task_idr@   paramsbt
target_urlrL   blocked_literalrE   r>   s	            r   _browser_cdp_private_guardrZ      sI   %N,))'2_$fl//6<"=CCEJ))*5z2!))3B8!)	  ''fl//=CDJ @@LO!44C3DBH!)	  ::66w?K0fEE
 	  N 	GMM	Ns*   D A.D 	AD &D 	D*
D%%D*c           
     ~  K   t         J t        j                  | d|dd      4 d{   }d}d}|r3|}|dz  }|j                  t        j                  |d|ddd             d{    t        j                         j                         |z   }		 |	t        j                         j                         z
  }
|
d	k  rt        d
|       t        j                  |j                         |
       d{   }t        j                  |      }|j                  d      |k(  rDd|v rt        d|d          |j                  di       j                  d      }|st        d      n|}|dz  }|||xs i d}|r||d<   |j                  t        j                  |             d{    t        j                         j                         |z   }		 |	t        j                         j                         z
  }
|
d	k  rt        d|       t        j                  |j                         |
       d{   }t        j                  |      }|j                  d      |k(  r8d|v rt        d|d          |j                  di       cddd      d{    S 7 p7 27 7 7 q7 # 1 d{  7  sw Y   yxY ww)u  Make a single CDP call, optionally attaching to a target first.

    When ``target_id`` is provided, we call ``Target.attachToTarget`` with
    ``flatten=True`` to multiplex a page-level session over the same
    browser-level WebSocket, then send ``method`` with that ``sessionId``.
    When ``target_id`` is None, ``method`` is sent at browser level — which
    works for ``Target.*``, ``Browser.*``, ``Storage.*`` and a few other
    globally-scoped domains.
    N   )max_sizeopen_timeoutclose_timeoutping_intervalr$   r   T)targetIdflatten)idr@   rV   r   zTimed out attaching to target timeoutrc   errorzTarget.attachToTarget failed: r/   	sessionIdz0Target.attachToTarget did not return a sessionIdz"Timed out waiting for response to zCDP error: )
websocketsconnectsendjsondumpsr&   r'   timeTimeoutErrorwait_forrecvloadsrO   r(   )ws_urlr@   rV   	target_idre   wsnext_id
session_id	attach_iddeadline	remainingrawmsgcall_idreqs                  r   	_cdp_callr~      s      !!!!! E- E- 
$(
 IqLG''

'"9/8T"J   //16687BH$w'?'?'A'F'F'HH	>&8D  $,,RWWY	JJjjo774=I-#~*<S\NK  "%2!6!:!:;!GJ%*N  % , 1l

 )Cggdjjo&&&++-224w> 7#;#;#=#B#B#DDIA~"8A   ((IFFC**S/Cwwt}'c>&S\N'CDDwwx,KE- E- E-t uE-  K2 	' GAE- E- E- E-s   'J=JJ==J(*J+B
J(5J6B1J('J"(B
J(2J$3AJ(J=J&J=J(J=J(J("J($J(&J=(J:.J1/J:6J=c                r   	 ddl m} |j	                  |       t        d| d      S j                         }|j                  j	                  d      }d}	|r|j	                  d	      |k(  r|}	n=|j                  j	                  d
g       xs g D ]  }
|
j	                  d	      |k(  s|
}	 n |	Bj                  5  j                  j	                  |      }ddd       |j                         }	|	t        d|d      S |	j	                  d      st        d|d      S j                  }||j                         st        d      S fd}	 ddlm}  | |       |      }|t        dt              S |j                  dz         }d||j	                  di       d}t%        j&                  |d      S # t        $ r}t        d| d      cY d}~S d}~ww xY w# 1 sw Y   xY w# t        $ r4}t        dt!        |      j"                   d| t              cY d}~S d}~ww xY w)a\  Route a CDP call through the live supervisor session for an OOPIF frame.

    Looks up the frame in the supervisor's snapshot, extracts its child
    ``cdp_session_id``, and dispatches ``method`` with that sessionId via
    the supervisor's already-connected WebSocket (using
    ``asyncio.run_coroutine_threadsafe`` onto the supervisor loop).
    r   )SUPERVISOR_REGISTRYz!CDP supervisor is not available: zp. frame_id routing requires a running supervisor attached via /browser connect or an active Browserbase session.Nz'No CDP supervisor is attached for task=z. Call browser_navigate or /browser connect first so the supervisor can attach. Once attached, browser_snapshot will populate frame_tree with frame_ids you can pass here.topframe_idchildrenz	frame_id zP not found in supervisor state. Call browser_snapshot to see current frame_tree.rv   z is not an out-of-process iframe (no dedicated CDP session). For same-origin iframes, use `browser_cdp(method='Runtime.evaluate', params={'expression': "document.querySelector('iframe').contentDocument.title"})` at the top-level page instead.zKCDP supervisor loop is not running. Try reconnecting with /browser connect.c                 R   K   j                  xs i         d {   S 7 w)N)rv   re   )_cdp)	child_sidr@   rV   
supervisorre   s   r   _do_cdpz,_browser_cdp_via_supervisor.<locals>._do_cdpi  s7     __Lb 	 % 
 
 	
 
s   '%')safe_schedule_threadsafez0CDP call via supervisor failed: loop unavailablerC      rd   z CDP call via supervisor failed: : Tr/   )successr@   r   rv   r/   Fensure_ascii)tools.browser_supervisorr   r;   r   rO   snapshot
frame_tree_state_lock_framesto_dict_loopr)   agent.async_utilsr   rD   r/   type__name__rk   rl   )rU   r   r@   rV   re   r   r>   snapr   
frame_infochildrz   r1   r   r   fut
result_msgpayloadr   r   s     ```             @@r   _browser_cdp_via_supervisorr     s   
@ %((1J5g[ A; <
 	
  D
//

e
$C+/J
swwz"h.
__((R8>B 	Eyy$0"
	 ## 	3$$((2C	3?J| $? @
 	

 |,I | $- .
 	
 D|4??, 
 	


 

>&wy$7;B%  ZZ!Z4
 ..2.G ::gE22w  
/u 5# $
 	

:	3 	3b  
.tCy/A/A.B"SEJ!
 	

sM   G  G,&G9 G9 	G)G$G)$G),G69	H6)H1+H61H6c           	        |xs d}|r&t        || |xs i       }|r|S t        ||| ||      S | rt        | t              st	        dt
              S t        st	        d      S t               }|st	        dt
              S |j                  d      st	        d	|d
      S |xs i }	t        |	t              s!t	        dt        |	      j                         S t        || |	      }|r|S 	 |rt        |      nd}
t        dt!        |
d            }
	 t#        t%        || |	||
            }d| t5        |      d}|r||d<   t7        j8                  |d      S # t        t        f$ r d}
Y ow xY w# t&        j(                  $ r}t	        d|
 d| |       cY d}~S d}~wt(        $ r }t	        t        |      |       cY d}~S d}~wt*        $ r }t	        t        |      |       cY d}~S d}~wt,        $ r}t	        d| d| d|       cY d}~S d}~wt.        $ rE}t0        j3                  d       t	        dt        |      j                   d| |       cY d}~S d}~ww xY w)un  Send a raw CDP command.  See ``CDP_DOCS_URL`` for method documentation.

    Args:
        method: CDP method name, e.g. ``"Target.getTargets"``.
        params: Method-specific parameters; defaults to ``{}``.
        target_id: Optional target/tab ID for page-level methods.  When set,
            we first attach to the target (``flatten=True``) and send
            ``method`` with the resulting ``sessionId``.  Uses a fresh
            stateless CDP connection.
        frame_id: Optional cross-origin (OOPIF) iframe ``frame_id`` from
            ``browser_snapshot.frame_tree.children[]``.  When set (and the
            frame is an OOPIF with a live session tracked by the CDP
            supervisor), routes the call through the supervisor's existing
            WebSocket — which is how you Runtime.evaluate *inside* an
            iframe on backends where per-call fresh CDP connections would
            hit signed-URL expiry (Browserbase) or expensive reattach.
        timeout: Seconds to wait for the call to complete.
        task_id: Task identifier for supervisor lookup.  When ``frame_id``
            is set, this identifies which task's supervisor to use; the
            handler will default to ``"default"`` otherwise.

    Returns:
        JSON string ``{"success": True, "method": ..., "result": {...}}`` on
        success, or ``{"error": "..."}`` on failure.
    default)rU   r@   rV   )rU   r   r@   rV   re   z/'method' is required (e.g. 'Target.getTargets')r   zfThe 'websockets' Python package is required but not installed. Install it with: pip install websocketszNo CDP endpoint is available. Run '/browser connect' to attach to a running Chrome, Brave, Chromium, or Edge browser, or set 'browser.cdp_url' in config.yaml. The Camofox backend is REST-only and does not expose CDP.)zws://zwss://z%CDP endpoint is not a WebSocket URL: u   . Expected ws://... or wss://... — the /browser connect resolver should have rewritten this. Check that a Chromium-family browser is actually listening on the debug port.z%'params' must be an object/dict, got       >@g      ?g     r@zCDP call timed out after zs: )r@   Nz"WebSocket error talking to CDP at r   uE   . The browser may have disconnected — try '/browser connect' again.zbrowser_cdp unexpected errorzUnexpected error: T)r   r@   r/   rs   Fr   )rZ   r   r   r   r   rD   _WS_AVAILABLEr?   
startswithr   r   r   float	TypeError
ValueErrormaxminr5   r~   r&   rn   r(   r"   r;   r<   	exceptionr   rk   rl   )r@   rV   rs   r   re   rU   effective_task_idblockedendpointcall_paramssafe_timeoutr/   r>   r   s                 r   browser_cdpr     s   B  ,9  -%<R

 N*%
 	
 FC0=!
 	

 6
 	

 %&H' "
 	
 233H< @? ?
 	
 #),BKk4(3D4E4N4N3OP
 	
 )!G
 )0uW~d sCe45L
hYM
4 $V,G
 (::gE22K z"   
'~S>
 	
  3#c(622 3#c(622 
0
"SE BN N
 	

  
78 c!3!3 4Bse<
 	

sx   *E E, E)(E),I?FII#F>8I>I
G%I%I1H
I
I:IIIr   u  Send a raw Chrome DevTools Protocol (CDP) command. Escape hatch for browser operations not covered by browser_navigate, browser_click, browser_console, etc.

**Requires a reachable CDP endpoint.** Available when the user has run '/browser connect' to attach to a running Chrome, Brave, Chromium, or Edge browser, or when 'browser.cdp_url' is set in config.yaml. Not currently wired up for cloud backends (Browserbase, Browser Use, Firecrawl) — those expose CDP per session but live-session routing is a follow-up. Camofox is REST-only and will never support CDP. If the tool is in your toolset at all, a CDP endpoint is already reachable.

**CDP method reference:** u   — use web_extract on a method's URL (e.g. '/tot/Page/#method-handleJavaScriptDialog') to look up parameters and return shape.

**Common patterns:**
- List tabs: method='Target.getTargets', params={}
- Handle a native JS dialog: method='Page.handleJavaScriptDialog', params={'accept': true, 'promptText': ''}, target_id=<tabId>
- Get all cookies: method='Network.getAllCookies', params={}
- Eval in a specific tab: method='Runtime.evaluate', params={'expression': '...', 'returnByValue': true}, target_id=<tabId>
- Set viewport for a tab: method='Emulation.setDeviceMetricsOverride', params={'width': 1280, 'height': 720, 'deviceScaleFactor': 1, 'mobile': false}, target_id=<tabId>

**Usage rules:**
- Browser-level methods (Target.*, Browser.*, Storage.*): omit target_id and frame_id.
- Page-level methods (Page.*, Runtime.*, DOM.*, Emulation.*, Network.* scoped to a tab): pass target_id from Target.getTargets.
- **Cross-origin iframe scope** (Runtime.evaluate inside an OOPIF, Page.* targeting a frame target, etc.): pass frame_id from the browser_snapshot frame_tree output. This routes through the CDP supervisor's live connection — the only reliable way on Browserbase where stateless CDP calls hit signed-URL expiry.
- Each stateless call (without frame_id) is independent — sessions and event subscriptions do not persist between calls. For stateful workflows, prefer the dedicated browser tools or use frame_id routing.objectstringz]CDP method name, e.g. 'Target.getTargets', 'Runtime.evaluate', 'Page.handleJavaScriptDialog'.)r   descriptionzaMethod-specific parameters as a JSON object. Omit or pass {} for methods that take no parameters.)r   r   
propertiesadditionalPropertieszOptional. Target/tab ID from Target.getTargets result (each entry's 'targetId'). Use for page-level methods at the top-level tab scope. Mutually exclusive with frame_id.a  Optional. Out-of-process iframe (OOPIF) frame_id from browser_snapshot.frame_tree.children[] where is_oopif=true. When set, routes the call through the CDP supervisor's live session for that iframe. Essential for Runtime.evaluate inside cross-origin iframes, especially on Browserbase where fresh per-call CDP connections can't keep up with signed URL rotation. For same-origin iframes, use parent contentWindow/contentDocument from Runtime.evaluate at the top-level page instead.numberz)Timeout in seconds (default 30, max 300).   )r   r   r   )r@   rV   rs   r   re   )r   r   required)namer   
parametersBROWSER_CDP_SCHEMAc                     	 ddl m} m}  |       syt         |              S # t        $ r }t        j                  d|       Y d}~yd}~ww xY w)u*  Availability check for browser_cdp.

    The tool is only offered when the Python side can actually reach a CDP
    endpoint right now — meaning a static URL is set via ``/browser connect``
    (``BROWSER_CDP_URL``) or ``browser.cdp_url`` in ``config.yaml``.

    Backends that do *not* currently expose CDP to us — Camofox (REST-only),
    the default local agent-browser mode (Playwright hides its internal CDP
    port), and cloud providers whose per-session ``cdp_url`` is not yet
    surfaced — are gated out so the model doesn't see a tool that would
    reliably fail.  Cloud-provider CDP routing is a follow-up.

    Kept in a thin wrapper so the registration statement stays at module top
    level (the tool-discovery AST scan only picks up top-level
    ``registry.register(...)`` calls).
    r   )r7   check_browser_requirementsz1browser_cdp check: browser_tool import failed: %sNF)r9   r7   r   ImportErrorr<   r=   bool)r7   r   r>   s      r   _browser_cdp_checkr   }  sJ    "	
 &'!#$$  H#Ns   " 	AAAzbrowser-cdpc           
         t        | j                  dd      | j                  d      | j                  d      | j                  d      | j                  dd      |j                  d      	      S )
Nr@   r8   rV   rs   r   re   r   rU   )r@   rV   rs   r   re   rU   )r   rO   )argskws     r   <lambda>r     sW    {xx"%xx!((;'*%D)y!  rG   u   🧪)r   toolsetschemahandlercheck_fnemoji)r    r   returnr   )r   r   )rE   r   r@   r   r   r   )rU   r   r@   r   rV   rH   r   Optional[str])rr   r   r@   r   rV   rH   rs   r   re   r   r   rH   )rU   r   r   r   r@   r   rV   Optional[Dict[str, Any]]re   r   r   r   )NNNr   N)r@   r   rV   r   rs   r   r   r   re   r   rU   r   r   r   )r   r   )$__doc__
__future__r   r&   rk   loggingtypingr   r   r   tools.registryr   r   	getLoggerr   r<   rD   rS   r   rh   websockets.exceptionsr"   r   r   r;   r5   r?   rF   rZ   r~   r   r   r   __annotations__r   register rG   r   <module>r      sh    #    & & /			8	$D% !"8M*$55 5 	5
 5zW-W-W- W- 	W-
 W- W-@k3k3k3 k3 %	k3
 k3 	k3` (,#"!I3I3$I3 I3 	I3
 I3 I3 	I3d 
	% &2N 3%	P  !I !C !(, !  !	5  !?S0
b Jg4U_& N _D%<   	  
q  J"Ms   C) )C76C7