
    i>W                    v   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	 ddlZddlmZ dZn# e$ r	 dZeZd	ZY nw xY wd
 Zd6dZd7dZd8dZ	 	 	 	 	 d9d:d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/<   d;d1Z ej        dd2ed3 ed45           dS )<u  
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/)WebSocketExceptionTFc                z   	 t          j                    }n# t          $ r d}Y nw xY w|r|                                rkddl}|j                            d          5 }|                    t           j        |           }|	                                cddd           S # 1 swxY w Y   t          j        |           S )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        ;/home/ubuntu/.hermes/hermes-agent/tools/browser_cdp_tool.py
_run_asyncr   2   s   '))     #!! #!!!!22q2AA 	#T[[d33F==??	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# ;ts    %%4BB"%B"returnstrc                     	 ddl m}   |             pd                                S # t          $ r&}t                              d|           Y d}~dS 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_toolr!   strip	Exceptionloggerdebug)r!   excs     r   _resolve_cdp_endpointr)   G   sz    888888!!##)r00222   FLLLrrrrrs   #& 
AAAws_urlmethodparamsDict[str, Any]	target_idOptional[str]timeoutfloatc           
       K   t           J t          j        | d|dd          4 d{V 	 }d}d}|rW|}|dz  }|                    t          j        |d|ddd                     d{V  t          j                                                    |z   }		 |	t          j                                                    z
  }
|
d	k    rt          d
|           t          j	        |
                                |
           d{V }t          j        |          }|                    d          |k    rWd|v rt          d|d                    |                    di                               d          }|st          d          n|}|dz  }|||pi d}|r||d<   |                    t          j        |                     d{V  t          j                                                    |z   }		 |	t          j                                                    z
  }
|
d	k    rt          d|           t          j	        |
                                |
           d{V }t          j        |          }|                    d          |k    rDd|v rt          d|d                    |                    di           cddd          d{V  S # 1 d{V swxY w Y   dS )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   zTarget.attachToTargetT)targetIdflatten)idr+   r,   r   zTimed out attaching to target r0   r:   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   get_event_looptimeTimeoutErrorwait_forrecvloadsgetr   )r*   r+   r,   r.   r0   wsnext_id
session_id	attach_iddeadline	remainingrawmsgcall_idreqs                  r   	_cdp_callrT   ^   s8       !!!!   E- E- E- E- E- E- E- E- 
$(
  	IqLG''
'"9/8T"J"J           -//4466@H$w'='?'?'D'D'F'FF	>>&DDD   $,RWWYY	JJJJJJJJJjoo774==I--#~~*KS\KK   "%2!6!6!:!:;!G!GJ% *N   %, 1l
 

  	*)Cggdjoo&&&&&&&&&)++0022W<	- 7#9#;#;#@#@#B#BBIA~~"AAA    (IFFFFFFFFFC*S//Cwwt}}''c>>&'CS\'C'CDDDwwx,,KE- E- E- E- E- E- E- E- E- E- E- E- E- E-t	-uE- E- E- E- E- E- E- E- E- E- E- E- E- E- E- E-s   JKK
K!$K!task_idframe_idOptional[Dict[str, Any]]c                   	 ddl m} n*# t          $ r}t          d| d          cY d}~S d}~ww xY w|                    |           t          d| d          S                                 }|j                            d          }d}	|r|                    d	          |k    r|}	n=|j                            d
g           pg D ]}
|
                    d	          |k    r|
}	 n |	Oj        5  j                            |          }ddd           n# 1 swxY w Y   ||	                                }	|	t          d|d          S |	                    d          st          d|d          S ddl
}j        }||                                st          d          S fd}	 |                     |            |          }|                    dz             }nE# t          $ r8}t          dt          |          j         d| t"                    cY d}~S d}~ww xY wd||                    di           d}t%          j        |d          S )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.toprV   childrenz	frame_id zP not found in supervisor state. Call browser_snapshot to see current frame_tree.rL   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                 J   K                        pi             d {V S )N)rL   r0   )_cdp)	child_sidr+   r,   
supervisorr0   s   r   _do_cdpz,_browser_cdp_via_supervisor.<locals>._do_cdp  sP      __Lb 	 % 
 
 
 
 
 
 
 
 	
       r;   z CDP call via supervisor failed: : cdp_docsTr   )successr+   rV   rL   r   Fensure_ascii)tools.browser_supervisorrY   r%   r   rI   snapshot
frame_tree_state_lock_framesto_dictr   _loopr   run_coroutine_threadsafer   type__name__CDP_DOCS_URLrA   rB   )rU   rV   r+   r,   r0   rY   r(   snaprZ   
frame_infochildrP   _asyncior   r`   fut
result_msgpayloadr^   r_   s     ```             @@r   _browser_cdp_via_supervisorr{      s   
@@@@@@@ 
 
 
$ $ $ $
 
 	
 	
 	
 	
 	
 	

 %((11J<g < < <
 
 	
   D
/

e
$
$C+/J
 swwz""h..

_((R88>B 	 	Eyy$$00"
 1 # 	3 	3$((22C	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3?J@ @ @ @
 
 	

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


 
 
 
 
 
 
 
 

//		4@@ZZ!Z44

 
 
 
JtCyy/AJJSJJ!
 
 
 	
 	
 	
 	
 	
 	

 ..2.. G :gE2222sD    
50556DD!$D!77G/ /
H19-H,&H1,H1      >@c           	        |rt          |pd|| ||          S ~| rt          | t                    st          dt                    S t
          st          d          S t                      }|st          dt                    S |                    d          st          d|d	          S |pi }t          |t                    s$t          d
t          |          j
                   S 	 |rt          |          nd}n# t          t          f$ r d}Y nw xY wt          dt          |d                    }	 t!          t#          || |||                    }	n# 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,          $ rM}
t.                              d           t          dt          |
          j
         d|
 |           cY d}
~
S d}
~
ww xY wd| |	d}|r||d<   t3          j        |d          S )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   rV   r+   r,   r0   z/'method' is required (e.g. 'Target.getTargets')rd   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, 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 Chrome is actually listening on the debug port.z%'params' must be an object/dict, got r|   g      ?g     r@zCDP call timed out after zs: )r+   Nz"WebSocket error talking to CDP at rc   uE   . The browser may have disconnected — try '/browser connect' again.zbrowser_cdp unexpected errorzUnexpected error: T)rf   r+   r   r.   Frg   )r{   
isinstancer   r   rs   _WS_AVAILABLEr)   
startswithdictrq   rr   r1   	TypeError
ValueErrormaxminr   rT   r   rE   r   r	   r%   r&   	exceptionrA   rB   )r+   r,   r.   rV   r0   rU   endpointcall_paramssafe_timeoutr   r(   rz   s               r   browser_cdpr   '  s   D  
*(y
 
 
 	
 	 
FC00 
=!
 
 
 	

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

 %&&H 
H "	
 
 
 	
 233 
4H 4 4 4
 
 	
 #),BKk4(( 
PD4E4E4NPP
 
 	
)0:uW~~~dz"   sCe4455L
hYMM
 
  
 
 
>>>>>
 
 
 	
 	
 	
 	
 	
 	
  3 3 3#c((6222222222 3 3 3#c((6222222222 
 
 
N N NS N N N
 
 
 	
 	
 	
 	
 	
 	

  
 
 
7888<c!3<<s<<
 
 
 	
 	
 	
 	
 	
 	

  G
  )(:gE2222sy   )C= =DD5 E I&&F<I&I&F2,I&2I&?G"I&"I&/HI&I&AI!I&!I&r   uz  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, 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'.)rq   descriptionzaMethod-specific parameters as a JSON object. Omit or pass {} for methods that take no parameters.)rq   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).   )rq   r   r~   )r+   r,   r.   rV   r0   )rq   r   required)namer   
parametersBROWSER_CDP_SCHEMAboolc                     	 ddl m} m} n3# t          $ r&}t                              d|           Y d}~dS d}~ww xY w |            sdS t           |                       S )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   )r!   check_browser_requirementsz1browser_cdp check: browser_tool import failed: %sNF)r#   r!   r   ImportErrorr&   r'   r   )r!   r   r(   s      r   _browser_cdp_checkr     s    "	
 	
 	
 	
 	
 	
 	
 	
 	
    H#NNNuuuuu &%'' u!!##$$$s    
;6;zbrowser-cdpc           
        t          |                     dd          |                     d          |                     d          |                     d          |                     dd          |                    d          	          S )
Nr+   r"   r,   r.   rV   r0   r|   rU   )r+   r,   r.   rV   r0   rU   )r   rI   )argskws     r   <lambda>r   )  ss    {xx"%%xx!!((;''*%%D))y!!      ra   u   🧪)r   toolsetschemahandlercheck_fnemoji)r   r   )r*   r   r+   r   r,   r-   r.   r/   r0   r1   r   r-   )rU   r   rV   r   r+   r   r,   rW   r0   r1   r   r   )NNNr|   N)r+   r   r,   rW   r.   r/   rV   r/   r0   r1   rU   r/   r   r   )r   r   ) __doc__
__future__r   r   rA   loggingtypingr   r   r   tools.registryr   r   	getLoggerrr   r&   rs   r>   websockets.exceptionsr	   r   r   r%   r   r)   rT   r{   r   r   __annotations__r   register ra   r   <module>r      s      # " " " " "    & & & & & & & & & & / / / / / / / /		8	$	$D
888888MM   J"MMM  *   .W- W- W- W-@f3 f3 f3 f3V (,#"!v3 v3 v3 v3 v3~ %	 &2%	 %	 %	P  !I  !C !(,  !   !	5   !? S0
 0
b Jg4 4U_& _&  _ _ _ _D% % % %<  	   
     s   A AA