
    Jjji                       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n# e$ r	 dZeZdZY nw xY wd Zd>dZd?dZd@dZdAdZdBd!Z	 	 	 	 	 dCdDd#Zd$d%e d&d'd(d)d*d'd+i dd,d(d-d*d(d.d*d/d0d1d2d3dgd4d5Z de!d6<   dEd8Z" ej#        d$d9e d: e"d;<           dS )Fu  
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.attachToTargetvaluer   returnc                Z   ddl m} t          | t                    r || d          S t          | t                    rd | D             S t          | t
                    rt          d | D                       S t          | t                    rd |                                 D             S | S )z>Redact browser-originated CDP result data before returning it.r   )redact_sensitive_textT)forcec                ,    g | ]}t          |          S  _redact_cdp_output.0items     </home/rurouni/.hermes/hermes-agent/tools/browser_cdp_tool.py
<listcomp>z&_redact_cdp_output.<locals>.<listcomp>4   s!    ;;;T"4((;;;    c              3  4   K   | ]}t          |          V  d S )Nr   r   s     r   	<genexpr>z%_redact_cdp_output.<locals>.<genexpr>6   s+      @@$'--@@@@@@r   c                4    i | ]\  }}|t          |          S r   r   )r   keyr   s      r   
<dictcomp>z&_redact_cdp_output.<locals>.<dictcomp>8   s'    MMM)#t'--MMMr   )agent.redactr   
isinstancestrlisttupledictitems)r   r   s     r   r   r   -   s    222222% 8$$U$7777% <;;U;;;;% A@@%@@@@@@% NMMu{{}}MMMMLr   )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        r   
_run_asyncr>   N   s   '))     #!! #!!!!22q2AA 	#T[[d33F==??	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# ;ts    %%4BB"%B"r&   c                     	 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_endpointrH   c   sz    888888!!##)r00222   FLLLrrrrrs   #& 
AAAblocked_urlmethodc                >    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.rJ   cdp_docs)r   CDP_DOCS_URL)rI   rJ   s     r   _private_page_guard_errorrO   u   sE    	!	! 	!+1	! 	! 	!    r   task_idparamsDict[str, Any]Optional[str]c                   	 ddl m} |                    |           sdS |dk    rt          |pi                     d          pd                                          }|rE|                    |          s|                    |          st          d| d|t          	          S |d
k    rXt          |pi                     d          pd          }|
                    |          }|rt          d| d|t          	          S |t          vr'|                    |           }|rt          ||          S n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wdS )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   urlrA   zDBlocked: CDP Page.navigate target is a private or internal address (z).rL   zRuntime.evaluate
expressionzPBlocked: CDP Runtime.evaluate expression targets a private or internal address (z0browser_cdp: private-page guard probe failed: %s)toolsrU   _eval_ssrf_guard_activer&   getrC   _is_always_blocked_url_is_safe_urlr   rN   _expression_targets_private_url!_CDP_PRIVATE_PAGE_ALLOWED_METHODS_current_page_private_urlrO   rD   rE   rF   )	rP   rJ   rQ   bt
target_urlrW   blocked_literalrI   rG   s	            r   _browser_cdp_private_guardrc      s   %N,,,,,,))'22 	4_$$fl//66<"==CCEEJ 	))*55	z22	 "8)38 8 8!)	    '''fl//==CDDJ @@LLO !H4CH H H!)	    :::66w??K F0fEEE N N N 	GMMMMMMMMN 4s*   D3 BD3 $AD3 /D3 3
E"=EE"ws_url	target_id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-   r   T)targetIdflatten)idrJ   rQ   r   zTimed out attaching to target rf   rp   errorzTarget.attachToTarget failed: r8   	sessionIdz0Target.attachToTarget did not return a sessionIdz"Timed out waiting for response to zCDP error: )
websocketsconnectsendjsondumpsr/   r0   timeTimeoutErrorwait_forrecvloadsrZ   r1   )rd   rJ   rQ   re   rf   wsnext_id
session_id	attach_iddeadline	remainingrawmsgcall_idreqs                  r   	_cdp_callr      s8       !!!!   E- E- E- E- E- E- E- E- 
$(
  	IqLG''
'"9/8T"J"J           /1166887BH$w'?'A'A'F'F'H'HH	>>&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&&&&&&&&&+--2244w>	- 7#;#=#=#B#B#D#DDI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!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 j
        }||                                st          d          S fd}	 ddlm}  | |            |          }|t          dt                    S |                    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.topr   childrenz	frame_id zP not found in supervisor state. Call browser_snapshot to see current frame_tree.r   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)r   rf   )_cdp)	child_sidrJ   rQ   
supervisorrf   s   r   _do_cdpz,_browser_cdp_via_supervisor.<locals>._do_cdpi  sP      __Lb 	 % 
 
 
 
 
 
 
 
 	
r   )safe_schedule_threadsafez0CDP call via supervisor failed: loop unavailablerM      rq   z CDP call via supervisor failed: : Tr8   )successrJ   r   r   r8   Fensure_ascii)tools.browser_supervisorr   rD   r   rZ   snapshot
frame_tree_state_lock_framesto_dict_loopr2   agent.async_utilsr   rN   r8   type__name__rw   rx   )rP   r   rJ   rQ   rf   r   rG   snapr   
frame_infochildr   r:   r   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??,,| 
 
 	


 
 
 
 
 
 
 
 

>>>>>>&&wwyy$77;B%    ZZ!Z44

 
 
 
JtCyy/AJJSJJ!
 
 
 	
 	
 	
 	
 	
 	

 ..2.. G :gE2222sJ    
50556DD!$D!31G? %G? ?
I	-H<6I<I      >@c           	        |pd}|r,t          || |pi           }|r|S t          ||| ||          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 t          || |	          }|r|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}t0                              d           t	          dt          |          j         d| |           cY d}~S d}~ww xY wd| t5          |          d}|r||d<   t7          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)rP   rJ   rQ   )rP   r   rJ   rQ   rf   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 r   g      ?g     r@zCDP call timed out after zs: )rJ   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   rJ   r8   re   Fr   )rc   r   r%   r&   r   rN   _WS_AVAILABLErH   
startswithr)   r   r   rg   	TypeError
ValueErrormaxminr>   r   r/   rz   r1   r+   rD   rE   	exceptionr   rw   rx   )rJ   rQ   re   r   rf   rP   effective_task_idblockedendpointcall_paramssafe_timeoutr8   rG   r   s                 r   browser_cdpr     sX   B  ,9  
 -%<R
 
 

  	N*%
 
 
 	
  
FC00 
=!
 
 
 	

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

 %&&H 
' "
 
 
 	
 233 
?H ? ? ?
 
 	
 #),BKk4(( 
PD4E4E4NPP
 
 	
 )!  G
  )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<<
 
 
 	
 	
 	
 	
 	
 	

 $V,, G
  )(:gE2222sy   D, ,EE$ F JF1+J1J>G!J!J.HJJH;5J;JAJ
JJ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   )rJ   rQ   re   r   rf   )r   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)rB   r@   r   ImportErrorrE   rF   r   )r@   r   rG   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 )
NrJ   rA   rQ   re   r   rf   r   rP   )rJ   rQ   re   r   rf   rP   )r   rZ   )argskws     r   <lambda>r     ss    {xx"%%xx!!((;''*%%D))y!!      r   u   🧪)r   toolsetschemahandlercheck_fnemoji)r   r   r   r   )r   r&   )rI   r&   rJ   r&   r   r&   )rP   r&   rJ   r&   rQ   rR   r   rS   )rd   r&   rJ   r&   rQ   rR   re   rS   rf   rg   r   rR   )rP   r&   r   r&   rJ   r&   rQ   r   rf   rg   r   r&   )NNNr   N)rJ   r&   rQ   r   re   rS   r   rS   rf   rg   rP   rS   r   r&   )r   r   )$__doc__
__future__r   r/   rw   loggingtypingr   r   r   tools.registryr   r   	getLoggerr   rE   rN   r^   r   rt   websockets.exceptionsr+   r   r   rD   r>   rH   rO   rc   r   r   r   r   __annotations__r   registerr   r   r   <module>r      s      # " " " " "    & & & & & & & & & & / / / / / / / /		8	$	$D% % % !   "888888MM   J"MMM  *   $   5 5 5 5zW- W- W- W-@k3 k3 k3 k3` (,#"!I3 I3 I3 I3 I3d %	 &2%	 %	 %	P  !I  !C !(,  !   !	5   !? S0
 0
b Jg4 4U_& _&  _ _ _ _D% % % %<  	   
     s   A AA