
    Jjɉ                    n   U 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 ddlmZmZmZ ddlZddlmZmZmZ ddlmZ ddlmZ  ej        e          Zd	Zd
Zdade d<   da!da"de d<   da#dXdZ$dYdZ%dZdZ&dZd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'Z0dad(Z1dbd*Z2dcd-Z3i Z4d.e d/<    ej5                    Z6ddd1Z7ded2Z8dfdgd4Z9dhd6Z:didjd7Z;didkd<Z<dldmd>Z=dldnd@Z>dldkdAZ?didodBZ@dpdDZA	 	 dqdrdGZBdidsdIZCdidtdKZDdidudMZEdidvdNZFdidwdPZGdidvdQZHdidvdRZI	 	 dxdydUZJdxdzdWZKdS ){u   Camofox browser backend — local anti-detection browser via REST API.

Camofox-browser is a self-hosted Node.js server wrapping Camoufox (Firefox
fork with C++ fingerprint spoofing).  It exposes a REST API that maps 1:1
to our browser tool interface: accessibility snapshots with element refs,
click/type/scroll by ref, screenshots, etc.

When ``CAMOFOX_URL`` is set (e.g. ``http://localhost:9377``), the browser
tools route through this module instead of the ``agent-browser`` CLI.

Setup::

    # Option 1: npm
    git clone https://github.com/jo-inc/camofox-browser && cd camofox-browser
    npm install && npm start   # downloads Camoufox (~300MB) on first run

    # Option 2: Docker
    docker run -p 9377:9377 -e CAMOFOX_PORT=9377 jo-inc/camofox-browser

Then set ``CAMOFOX_URL=http://localhost:9377`` in ``~/.hermes/.env``.
For Docker Camofox, optionally set ``CAMOFOX_REWRITE_LOOPBACK_URLS=true``
so page URLs like ``http://127.0.0.1:3000`` are opened inside the
container as ``http://host.docker.internal:3000``.
    )annotationsN)AnyDictOptional)SplitResulturlsplit
urlunsplit)cfg_getload_configread_raw_config)get_camofox_identity)
tool_error   i8 Optional[str]_vnc_urlFOptional[int]_cached_cmd_timeoutreturnintc                    t           rt          S da t          } 	 t                      }t	          |dd          }|t          t          |          d          } n2# t          $ r%}t          	                    d|           Y d}~nd}~ww xY w| a| S )a  Return ``browser.command_timeout`` from config, falling back to 30s.

    Mirrors :func:`tools.browser_tool._get_command_timeout` so both the
    local browser path and the Camofox path honour the same config knob.
    Result is cached after the first call.
    Tbrowsercommand_timeoutN   z*Could not read browser.command_timeout: %s)
_cmd_timeout_resolvedr   _DEFAULT_TIMEOUTr   r
   maxr   	Exceptionloggerdebug)resultcfgvalexcs       ;/home/rurouni/.hermes/hermes-agent/tools/browser_camofox.py_get_command_timeoutr%   ;   s      #"" FHc9&788?S1%%F H H HA3GGGGGGGGH Ms   >A 
B"BBDict[str, str]c                 f    t          j        dd                                          } | rdd|  iS i S )z8Return Authorization header when CAMOFOX_API_KEY is set.CAMOFOX_API_KEY AuthorizationzBearer )osgetenvstrip)keys    r$   _auth_headersr/   S   s>    
)%r
*
*
0
0
2
2C
 2311I    strc                 R    t          j        dd                              d          S )z:Return the configured Camofox server URL, or empty string.CAMOFOX_URLr)   /)r+   r,   rstrip r0   r$   get_camofox_urlr7   [   s"    9]B''..s333r0   c                 
   	 ddl m}   |                                 di           }t          |t                    r7t          |                    dd          pd                                          S n# t          $ r Y nw xY wdS )a.  Persistent ``browser.cdp_url`` from config.yaml, or empty string.

    Read here (instead of importing ``browser_tool._get_cdp_override`` to avoid
    a circular import) so Camofox can yield to a config-based CDP override the
    same way it already yields to the ``BROWSER_CDP_URL`` env override.
    r   )r   r   cdp_urlr)   )hermes_cli.configr   get
isinstancedictr1   r-   r   )r   browser_cfgs     r$   _config_cdp_urlr?   `   s    555555%o''++Ir::k4(( 	E{y"55;<<BBDDD	E   2s   A/A3 3
B ?B boolc                     t          j        dd                                          rdS t                      rdS t	          t                                S )u  True when Camofox backend is configured and no CDP override is active.

    A CDP override takes priority over Camofox so the browser tools operate on
    the real CDP browser (and a CDP backend is treated as non-local for SSRF
    checks) instead of being silently routed to Camofox. The override may come
    from the ``BROWSER_CDP_URL`` env var (set by ``/browser connect``) OR a
    persistent ``browser.cdp_url`` in config.yaml — both are honored, matching
    ``browser_tool._get_cdp_override()``'s precedence. (Previously only the env
    var suppressed Camofox, so ``CAMOFOX_URL`` + a config CDP override still
    routed navigation through Camofox.)
    BROWSER_CDP_URLr)   F)r+   r,   r-   r?   r@   r7   r6   r0   r$   is_camofox_moderC   r   sQ     
y"B''--// u u!!"""r0   c                    t                      } | sdS 	 t          j        |  dd          }|j        dk    rt          s	 |                                }|                    d          }t          |t                    r2d|cxk    rdk    r%n n"d	d
lm	}  ||           }|j
        pd}d| d| an# t          t          f$ r Y nw xY wda|j        dk    S # t          $ r Y dS w xY w)z'Verify the Camofox server is reachable.Fz/healthr   timeout   vncPort   i  r   )urlparse	localhostzhttp://:T)r7   requestsr;   status_code_vnc_url_checkedjsonr<   r   urllib.parserJ   hostnamer   
ValueErrorKeyErrorr   )urlrespdatavnc_portrJ   parsedhosts          r$   check_camofox_availabler[      sA    

C u|sOOOQ777s""+;"	yy{{88I..h,, ;h1G1G1G1G%1G1G1G1G1G555555%Xc]]F!?9kD:::::H)   #3&&   uus6   +C  A0B1 0C 1CC CC 
C#"C#c                 :    t           st                       t          S )z>Return the VNC URL if the Camofox server exposes one, or None.)rO   r[   r   r6   r0   r$   get_vnc_urlr]      s     "!!!Or0   Dict[str, Any]c                    	 t                                          di                               di           } n4# t          $ r'}t                              d|           i cY d}~S d}~ww xY wt          | t                    r| ni S )z>Return the ``browser.camofox`` config block, or an empty dict.r   camofoxz7camofox config check failed, defaulting to disabled: %sN)r   r;   r   r   warningr<   r=   )camofox_cfgr#   s     r$   _get_camofox_configrc      s    !mm''	266::9bII   PRUVVV						 %[$77?;;R?s   69 
A*A%A*%A*c                 ^    t          t                                          d                    S )al  Return whether Hermes-managed persistence is enabled for Camofox.

    When enabled, sessions use a stable profile-scoped userId so the
    Camofox server can map it to a persistent browser profile directory.
    When disabled (default), each session gets a random userId (ephemeral).

    Controlled by ``browser.camofox.managed_persistence`` in config.yaml.
    managed_persistence)r@   rc   r;   r6   r0   r$   _managed_persistence_enabledrf      s(     #%%))*?@@AAAr0   task_idrb   Optional[Dict[str, str]]c                   t          j        dd                                          p5t          |                    d          pd                                          }|sdS t          j        dd                                          pDt          |                    d          pd                                          pd| pddd	          }||d
S )zReturn an externally configured Camofox identity, if one is set.

    Integrations that own the visible Camofox browser can set a shared user ID
    so Hermes operates in the same browser profile instead of creating a
    separate private session.
    CAMOFOX_USER_IDr)   user_idNCAMOFOX_SESSION_KEYsession_keytask_default   )rk   rm   r+   r,   r-   r1   r;   )rg   rb   rk   rm   s       r$   _camofox_identity_overriderr      s     i)2..4466g#kooi>X>X>^\^:_:_:e:e:g:gG t 		',,2244 	1{}--344::<<	10G(y#2#.00 
 {;;;r0   nameOptional[bool]c                    t          j        | d                                                                          }|sd S |dv rdS |dv rdS t                              d| |           d S )Nr)   >   1onyestrueT>   0noofffalseFz"Ignoring invalid boolean env %s=%r)r+   r,   r-   lowerr   r   )rs   raws     r$   	_env_flagr      sv    
)D"


#
#
%
%
+
+
-
-C t
(((t
)))u
LL5tSAAA4r0   c                l    t          d          }||S t          |                     d                    S )z@Return whether Hermes should recover an existing Camofox tab ID.CAMOFOX_ADOPT_EXISTING_TABNadopt_existing_tabr   r@   r;   rb   	env_values     r$   _adopt_existing_tab_enabledr      s7    677I 455666r0   c                l    t          d          }||S t          |                     d                    S )aL  Return whether loopback navigation URLs should be rewritten for Docker.

    ``CAMOFOX_URL`` itself often points at a host-published Docker port such as
    ``http://127.0.0.1:9377``.  That is correct for Hermes talking to the
    Camofox control API, but a page URL like ``http://127.0.0.1:3000`` is opened
    by the browser *inside* the Docker container.  In that context loopback
    points at the container, not the host running the web app.

    The rewrite is opt-in because non-Docker Camofox installs run the browser on
    the host, where loopback URLs are already correct.
    CAMOFOX_REWRITE_LOOPBACK_URLSNrewrite_loopback_urlsr   r   s     r$   _loopback_rewrite_enabledr      s9     9::I 788999r0   c                    t          j        dd                                          p7t          |                     d          pd                                          pdS )z=Return the host alias used when rewriting loopback page URLs.CAMOFOX_LOOPBACK_HOST_ALIASr)   loopback_host_aliaszhost.docker.internalrq   )rb   s    r$   _loopback_rewrite_hostr      s[     		/44::<< 	"{455;<<BBDD	"!r0   rR   c                    | sdS |                                                       d                                          }|dv rdS 	 ddl}|                    |          j        S # t
          $ r Y dS w xY w)z:Return True for localhost/127.0.0.0/8/::1-style hostnames.Fz[]>   localhost.localdomainrK   Tr   N)r-   r~   	ipaddress
ip_addressis_loopbackrS   )rR   rZ   r   s      r$   _is_loopback_hostnamer      s     u>>!!$''--//D555t##D))55   uus   A# #
A10A1rU   $tuple[str, Optional[Dict[str, str]]]c           	     H   t                      }t          |          s| dfS 	 t          |           }n# t          $ r | dfcY S w xY w|j        dvst          |j                  s| dfS t          |          }|s| dfS d}|j        r |j        }|j	        r|d|j	         z  }|dz  }d|v r|
                    d          sd| dn|}|j        r
d|j         nd}t          t          |j        | | | |j        |j        |j                            }||j        pd|| |dfS )	zRewrite loopback page URLs for Docker-hosted Camofox, if configured.

    Returns ``(rewritten_url, metadata)``.  ``metadata`` is present only when a
    rewrite happened so the tool result can disclose the change to the model.
    N>   httphttpsr)   rL   @[])fromtooriginal_urlrewritten_url)rc   r   r   rS   schemer   rR   r   usernamepassword
startswithportr	   r   pathqueryfragment)rU   rb   rY   aliasuserinfo	host_part	port_part	rewrittens           r$   !_rewrite_loopback_url_for_camofoxr     s    &''K$[11 Dy#   Dy }---5J6?5[5[-Dy";//E DyH ?? 	.-FO---HC #uU5E5Ec5J5JEPUI%+[8!FK!!!bIFMh#F	#F9#F#FU[Uacicrss I %2"	   s   3 AAzDict[str, Dict[str, Any]]	_sessionssessionc                
   |                      d          s|                      d          s| S t                      s| S 	 t          dd| d         id                               dg           }nH# t          $ r;}t                              d	|                      d          |           | cY d
}~S d
}~ww xY wt          |t                    r|s| S |                      d          fd|D             }|pd |D             }|r|d         nd
}t          |t                    r|                     d          nd
}t          |t                    r6|r4|| d<   t                              d||                      d                     | S )a0  Attach process-local state to an already-open managed Camofox tab.

    Some integrations own the visible Camofox tab outside Hermes. Gateway
    restarts can leave this module's in-memory session cache empty even though
    Camofox still has that tab, so rehydrate tab_id before creating a new tab.
    tab_idr   /tabsuserIdrk   r   )paramsrF   tabsz&Camofox tab adoption failed for %s: %sNrm   c                p    g | ]2}t          |t                    r|                    d           k    0|3S )
listItemId)r<   r=   r;   ).0tabrm   s     r$   
<listcomp>z'_adopt_existing_tab.<locals>.<listcomp>T  sN       c4   &)WW\%:%:k%I%I 	%I%I%Ir0   c                <    g | ]}t          |t                    |S r6   )r<   r=   )r   r   s     r$   r   z'_adopt_existing_tab.<locals>.<listcomp>Y  s'    "P"P"P3*S$:O:O"P3"P"P"Pr0   tabIdz&Adopted existing Camofox tab %s for %s)
r;   r7   _getr   r   r   r<   listr=   r1   )r   r   r#   matching_tabs
candidateslatestr   rm   s          @r$   _adopt_existing_tabr   =  s    {{8 GKK0D$E$E  GXwy/A$BANNNRRSY[]^^   =w{{9?U?UWZ[[[ dD!!  ++m,,K     M
 P"P"P$"P"P"PJ)3Z^^tF$.vt$<$<FVZZ   $F&# _6 _"=vw{{S\G]G]^^^Ns   .A. .
B380B.(B3.B3c                n   | pd} t           5  | t          v r&t          t          |                    cddd           S t                      }t	          | |          }|r"|d         d|d         dt          |          d}nt          |                    d                    r1t          |           }|d         d|d         dt          |          d}n4dt          j
                    j        dd	          dd
| dd          ddd}|t          | <   t          |          cddd           S # 1 swxY w Y   dS )zGet or create a camofox session for the given task.

    When managed persistence is enabled, uses a deterministic userId
    derived from the Hermes profile so the Camofox server can map it
    to the same persistent browser profile across restarts.
    ro   Nrk   rm   T)rk   r   rm   managedr   re   hermes_
   rn   rp   F)_sessions_lockr   r   rc   rr   r   r@   r;   r   uuiduuid4hex)rg   rb   identity_overrider   identitys        r$   _get_sessionr   c  s    "G	  ,  ,i&y'9:: ,  ,  ,  ,  ,  ,  ,  , *++6wLL 	,Y70?&A+&N&N GG +//"78899 	+G44H#I.'6&A+&N&N GG =TZ\\%5crc%:<<5wss|55 &+ G %	'"7++A ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,s   #D*C!D**D.1D.about:blankc                Z   t          |           }|d         r|S t                      }t          j        | d|d         |d         |dt	                      t                                }|                                 |                                }|                    d          |d<   |S )z<Ensure a tab exists for the session, creating one if needed.r   r   rk   rm   )r   r   rU   rP   rF   headersr   )	r   r7   rM   postr%   r/   raise_for_statusrP   r;   )rg   rU   r   baserV   rW   s         r$   _ensure_tabr     s    7##Gx D=i(!-0
 

 %&&	 	 	D 	99;;D))GHNr0   Optional[Dict[str, Any]]c                    | pd} t           5  t                              | d          cddd           S # 1 swxY w Y   dS )zRemove and return session info.ro   N)r   r   pop)rg   s    r$   _drop_sessionr     s    "G	 , ,}}Wd++, , , , , , , , , , , , , , , , , ,s   488c                    t                      }t          |                    d                    st          | |          r,t	          |            t
                              d|            dS dS )a  Release the in-memory session without destroying the server-side context.

    When managed persistence is enabled the browser profile (and its cookies)
    must survive across agent tasks.  This helper drops only the local tracking
    entry and returns ``True``.  When managed persistence is *not* enabled it
    does nothing and returns ``False`` so the caller can fall back to
    :func:`camofox_close`.
    re   z6Camofox soft cleanup for task %s (managed persistence)TF)rc   r@   r;   rr   r   r   r   )rg   rb   s     r$   camofox_soft_cleanupr     sm     &''KKOO12233 7QRY[f7g7g gMwWWWt5r0   r   bodyr=   rF   c                    |t                      }t                       |  }t          j        |||t	                                }|                                 |                                S )z0POST JSON to camofox and return parsed response.Nr   )r%   r7   rM   r   r/   r   rP   r   r   rF   rU   rV   s        r$   _postr     sd    &((
&
&
&C=4-//RRRD99;;r0   r   c                    |t                      }t                       |  }t          j        |||t	                                }|                                 |                                S )z,GET from camofox and return parsed response.Nr   rF   r   )r%   r7   rM   r;   r/   r   rP   r   r   rF   rU   rV   s        r$   r   r     sd    &((
&
&
&C<FG]__UUUD99;;r0   requests.Responsec                    |t                      }t                       |  }t          j        |||t	                                }|                                 |S )z;GET from camofox and return raw response (for binary data).Nr   )r%   r7   rM   r;   r/   r   r   s        r$   _get_rawr     s[    &((
&
&
&C<FG]__UUUDKr0   c                    |t                      }t                       |  }t          j        |||t	                                }|                                 |                                S )z-DELETE to camofox and return parsed response.Nr   )r%   r7   rM   deleter/   r   rP   r   s        r$   _deleter     sd    &((
&
&
&C?3T7MOOTTTD99;;r0   c                   	 t          |           \  }}t          |          }|d         st          ||          }d|d}n	 t          d|d          d|d         |dd	          }np# t          j        $ r^}|j        L|j        j        dk    r<t          	                    d|d                    d
|d<   t          ||          }d|d}n Y d
}~nd
}~ww xY wd|
                    d|          |
                    dd          d}|r!| |d<   ||d<   d|d          d|d          |d<   t                      }|r
||d<   d|d<   	 t          d|d          dd|d         i          }	|	
                    dd          }
dd lm}m} t!          |
          |k    r ||
          }
|
|d<   |	
                    d!d          |d"<   n# t"          $ r Y nw xY wt%          j        |          S # t          j        $ r}t)          d#| d$%          cY d
}~S d
}~wt          j        $ r* t%          j        d$d&t-                       d'd(          cY S t"          $ r(}t)          t/          |          d$%          cY d
}~S d
}~ww xY w))zNavigate to a URL via Camofox.r   T)okrU   /tabs/z	/navigaterk   )r   rU   <   rE   Ni  uP   Camofox tab %s returned 404 — tab was garbage collected. Creating a fresh tab.rU   titler)   )successrU   r   requested_urlurl_rewritez0Rewrote loopback URL for Docker-hosted Camofox: r   z -> r   ra   vnc_urlz]Browser is visible via VNC. Share this link with the user so they can watch the browser live.vnc_hint	/snapshotr   r   snapshotr   )SNAPSHOT_SUMMARIZE_THRESHOLD_truncate_snapshot	refsCountelement_countzNavigation failed: Fr   zCannot connect to Camofox at z. Is the server running? Start with: npm start (in camofox-browser dir) or: docker run -p 9377:9377 -e CAMOFOX_PORT=9377 jo-inc/camofox-browserr   error)r   r   r   r   rM   	HTTPErrorresponserN   r   ra   r;   r]   r   tools.browser_toolr   r   lenr   rP   dumpsr   ConnectionErrorr7   r1   )rU   rg   browser_urlrewrite_infor   rW   er    vnc	snap_datasnapshot_textr   r   s                r$   camofox_navigater    s   L1$Ec$J$J!\w''x  	!';77G{33DD9WX.999&y1+FF  
 %   :)aj.D.K.KNN0)  
 )-GH%)';??G"&{;;DD DDDD 88E;//XXgr**
 

  	&)F?#$0F=!B'B B-9$-?B B 9 mm 	 #F9T :	5*555 ')"45  I &MM*b99M        =!!$@@@ 2 2= A A!.F:&/mmK&C&CF?## 	 	 	D	 z&!!! D D D333UCCCCCCCCC#   z__5F5F _ _ _
 
   	 	 	  1 1 1#a&&%0000000001s   ?G& %A( 'G& (C7ACG& CA.G& A=G G& 
GG& GG& &I:5HI:9I:		I:I5/I:5I:actionc                    ddl m}m}  ||pd          sdS  || d         | d                   }|sdS t          j        dd| d	| d
dd          S )u  Return a blocked payload when the current Camofox page is private/internal.

    Mirrors the eval-path guard added for ``_camofox_eval`` (browser_tool.py):
    Camofox snapshot / vision / image-extraction all read current page state, so
    on a non-local backend they can leak the content of an intranet/metadata
    page the terminal itself can't reach.  The gate matches ``browser_snapshot``
    / ``browser_vision`` — only active when the SSRF guard applies (non-local
    backend, not a local sidecar, ``allow_private_urls`` unset).  Fail-open on
    probe failure, matching the sibling guards.

    Imports are deferred to call time because ``browser_tool`` imports this
    module; importing it at module load would create a circular import.
    r   )!_camofox_current_page_private_url_eval_ssrf_guard_activero   Nr   rk   Fz9Blocked: page URL targets a private or internal address (z). Refusing to z# on this page in this browser mode.r   )ensure_ascii)r  r  r  rP   r  )r   rg   r  r  r  blocked_urls         r$   _camofox_private_page_blockr  =  s           
 #"7#7i88 t33GH4EwyGYZZK t: ,2       r0   full	user_taskc                $   	 t          |          }|d         st          dd          S t          ||d          }|r|S t          d|d          dd|d	         i
          }|                    dd          }|                    dd          }ddlm}m}	m}
 t          |          |k    r|r |	||          }n |
|          }t          j        d||d          S # t          $ r(}t          t          |          d          cY d}~S d}~ww xY w)z-Get accessibility tree snapshot from Camofox.r   0No browser session. Call browser_navigate first.Fr   zread a page snapshotr   r   r   rk   r   r   r)   r   r   )r   _extract_relevant_contentr   T)r   r   r   N)r   r   r  r   r;   r  r   r  r   r  rP   r  r   r1   )r  rg   r  r   blockedrW   r   
refs_countr   r  r   r  s               r$   camofox_snapshotr  _  s   $1w''x  	aPZ_````-gw@VWW 	N1WX&111gi01
 
 

 88J++XXk1--
	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 x==777 844XyII--h77z '
 
   	
  1 1 1#a&&%0000000001s(   'C C BC 
D'D
D
Drefc                   	 t          |          }|d         st          dd          S t          ||d          }|r|S |                     d          }t	          d|d          d|d	         |d
          }t          j        d||                    dd          d          S # t          $ r(}t          t          |          d          cY d}~S d}~ww xY w)z$Click an element by ref via Camofox.r   r  Fr   clickr   r   z/clickrk   )r   r  TrU   r)   )r   clickedrU   N)
r   r   r  lstripr   rP   r  r;   r   r1   )r  rg   r   r  	clean_refrW   r  s          r$   camofox_clickr!    s   1w''x  	aPZ_````-gwHH 	N JJsOO	.WX&...y))<<
 
 z 88E2&&
 
   	
  1 1 1#a&&%0000000001s(   'B# B# A#B# #
C-C
CCtextc                   	 t          |          }|d         st          dd          S t          ||d          }|r|S |                     d          }t	          d|d          d|d	         ||d
           ddlm}m}  |dd|i          pi d         }d||d}	 ||	|          }	t          j	        |	          S # t          $ r8}
ddlm} t           |t          |
          |          d          cY d}
~
S d}
~
ww xY w)z-Type text into an element by ref via Camofox.r   r  Fr   typer   r   z/typerk   )r   r  r"  r   )%redact_browser_typed_text_for_displayredact_tool_args_for_displaybrowser_typer"  T)r   typedelement)r%  N)r   r   r  r  r   agent.displayr%  r&  rP   r  r   r1   )r  r"  rg   r   r  r   r%  r&  display_textr   r  s              r$   camofox_typer,    s   $^w''x  	aPZ_````-gwGG 	NJJsOO	-WX&---y))TJJ	
 	
 	
	
 	
 	
 	
 	
 	
 	
 	

 54^fd^TTZXZ\bc 
 " 
 
 984HHz(### ^ ^ ^GGGGGG??AMMW\]]]]]]]]]^s(   'B< B< A<B< <
C>-C93C>9C>	directionc                2   	 t          |          }|d         st          dd          S t          d|d          d|d         | d           t          j        d	| d
          S # t
          $ r(}t          t          |          d          cY d}~S d}~ww xY w)zScroll the page via Camofox.r   r  Fr   r   z/scrollrk   )r   r-  T)r   scrolledN)r   r   r   rP   r  r   r1   )r-  rg   r   r  s       r$   camofox_scrollr0    s    1w''x  	aPZ_````/WX&///y)	BB	
 	
 	
 zd	BBCCC 1 1 1#a&&%0000000001s"   'A$ 9A$ $
B.BBBc                X   	 t          |           }|d         st          dd          S t          d|d          dd|d         i          }t          j        d	|                    d
d          d          S # t          $ r(}t          t          |          d          cY d}~S d}~ww xY w)zNavigate back via Camofox.r   r  Fr   r   z/backr   rk   TrU   r)   )r   rU   N)r   r   r   rP   r  r;   r   r1   )rg   r   rW   r  s       r$   camofox_backr2    s    1w''x  	aPZ_````-WX&---wy)*
 
 zd488E23F3FGGHHH 1 1 1#a&&%0000000001s#   'A7 AA7 7
B)B$B)$B)r.   c                \   	 t          |          }|d         st          dd          S t          ||d          }|r|S t          d|d          d|d         | d	           t	          j        d
| d          S # t          $ r(}t          t          |          d          cY d}~S d}~ww xY w)z!Press a keyboard key via Camofox.r   r  Fr   pressr   z/pressrk   )r   r.   T)r   pressedN)r   r   r  r   rP   r  r   r1   )r.   rg   r   r  r  s        r$   camofox_pressr6    s    1w''x  	aPZ_````-gwHH 	N.WX&...y)#66	
 	
 	
 zds;;<<< 1 1 1#a&&%0000000001s'   'A9 A9 9A9 9
B+B& B+&B+c                *   	 t          |           }|st          j        ddd          S t          d|d                     t          j        ddd          S # t          $ r/}t          j        ddt          |          d          cY d}~S d}~ww xY w)z&Close the browser session via Camofox.T)r   closedz
/sessions/rk   )r   r8  ra   N)r   rP   r  r   r   r1   )rg   r   r  s      r$   camofox_closer9    s    
P(( 	A:$$??@@@-+--	
 	
 	
 zdd;;<<< P P Pzdds1vvNNOOOOOOOOPs"   'A .A 
B#$BBBc                   	 t          |           }|d         st          dd          S t          || d          }|r|S ddl}t	          d|d          d	d
|d         i          }|                    dd          }g }|                    d          }t          |          D ]\  }}	|	                                }
|
	                    d          r|
                    d|
          }|r|                    d          nd}d}|dz   t          |          k     rH|
                    d||dz                                                      }|r|                    d          }|s|r|                    ||d           t          j        d|t          |          d          S # t           $ r(}t          t#          |          d          cY d}~S d}~ww xY w)zGet images on the current page via Camofox.

    Extracts image information from the accessibility tree snapshot,
    since Camofox does not expose a dedicated /images endpoint.
    r   r  Fr   zextract page imagesr   Nr   r   r   rk   r   r   r)   
)z- img zimg zimg\s+"([^"]*)"rI   z/url:\s*(\S+))srcaltT)r   imagescount)r   r   r  rer   r;   split	enumerater-   r   searchgroupr  appendrP   r  r   r1   )rg   r   r  r@  rW   r   r>  linesilinestripped	alt_matchr=  r<  	url_matchr  s                   r$   camofox_get_imagesrL    s)   *1w''x  	aPZ_````-gw@UVV 	N			1WX&111gi01
 
 
 88J++
 t$$ '' 	< 	<GAtzz||H""#566 
<II&8(CC	,5=iooa(((2q53u::%% "		*:E!a%L<N<N<P<P Q QI  1'ooa00 <# <MM#c":":;;;z[[
 
   	
  1 1 1#a&&%0000000001s(   'F! F! E!F! !
G+GGGquestionannotatec                   	 t          |          }|d         st          dd          S t          ||d          }|r|S t          d|d          dd|d	         i
          }ddlm}  |            dz  }|                    dd           t          |dt          j	                    j
        dd          dz            }t          |d          5 }	|	                    |j                   ddd           n# 1 swxY w Y   t          j        |j                                      d          }
d}|rV	 t#          d|d          dd|d	         i
          }d|                    dd          dd          }n# t&          $ r Y nw xY wddlm}  ||          }ddlm} d|  | }	 t1                      }t3          |ddi            }t5          |                    d!d"                    }t5          |                    d#d$                    }n# t&          $ r d%}d$}Y nw xY w |d&d'|d(d)d*d+|
 id,gd-gd||.          }|j        r+|j        d         j        j        pd                                nd}ddlm}  ||          }t=          j        d||d/          S # t&          $ r(}t          t          |          d          cY d}~S d}~ww xY w)0z<Take a screenshot and analyze it with vision AI via Camofox.r   r  Fr   zcapture a screenshotr   z/screenshotr   rk   r   r   )get_hermes_homebrowser_screenshotsT)parentsexist_okbrowser_screenshot_N   z.pngwbzutf-8r)   r   z5

Accessibility tree (element refs for interaction):
r   i  )redact_sensitive_text)call_llmz,Analyze this browser screenshot and answer: 	auxiliaryvision)ro   rF   x   temperatureg?g      ^@userr"  )r$  r"  	image_urlrU   zdata:image/png;base64,)r$  r^  )rolecontent)messagestaskr\  rF   )r   analysisscreenshot_path) r   r   r  r   hermes_constantsrP  mkdirr1   r   r   r   openwriter`  base64	b64encodedecoder   r;   r   agent.redactrW  agent.auxiliary_clientrX  r   r
   floatchoicesmessager-   rP   r  )rM  rN  rg   r   r  rV   rP  screenshots_dirrd  fimg_b64annotation_contextr	  rW  rX  vision_prompt_cfg_vision_cfg_vision_timeout_vision_temperaturer   rc  r  s                          r$   camofox_visionrz  C  s[   [1w''x  	aPZ_````-gw@VWW 	N 3WX&333gi01
 
 
 	544444)/++.CCdT:::o0`djllFVWYXYWYFZ0`0`0``aa/4(( 	"AGGDL!!!	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" "4<0077@@   	 9WX.999$gi&89  	 &G`i`m`mnxz|`}`}  D  @D  D  aE  &G  &G""    	766666223EFF 	433333$8 $!$ $ 	
	&==D!$XrJJJK#KOOIs$C$CDDO"'s(K(K"L"L 	& 	& 	&#O"%	& 8#];; +!#EG#E#E&    +#
 
 
" KSJZbH$Q'/7=2DDFFF`b 	766666((22z .
 
   	
  1 1 1#a&&%0000000001s   'J  J  BJ  C7+J  7C;;J  >C;?3J  3AE8 7J  8
FJ  F!J  'A'H J  H J  H  A?J   
K*KKKclearc           	     8    t          j        dg g dddd          S )u   Get console output — limited support in Camofox.

    Camofox does not expose browser console logs via its REST API.
    Returns an empty result with a note.
    Tr   z|Console log capture is not available with the Camofox backend. Use browser_snapshot or browser_vision to inspect page state.)r   console_messages	js_errorstotal_messagestotal_errorsnote)rP   r  )r{  rg   s     r$   camofox_consoler    s7     :P    r0   )r   r   )r   r&   )r   r1   )r   r@   )r   r   )r   r^   )rg   r   rb   r^   r   rh   )rs   r1   r   rt   )rb   r^   r   r@   )rb   r^   r   r1   )rR   r   r   r@   )rU   r1   r   r   )r   r^   r   r^   )rg   r   r   r^   )r   )rg   r   rU   r1   r   r^   )rg   r   r   r   )N)rg   r   r   r@   )r   r1   r   r=   rF   r   r   r=   )NN)r   r1   r   r=   rF   r   r   r=   )r   r1   r   r=   rF   r   r   r   )rU   r1   rg   r   r   r1   )r   r^   rg   r   r  r1   r   r   )FNN)r  r@   rg   r   r  r   r   r1   )r  r1   rg   r   r   r1   )r  r1   r"  r1   rg   r   r   r1   )r-  r1   rg   r   r   r1   )rg   r   r   r1   )r.   r1   rg   r   r   r1   )FN)rM  r1   rN  r@   rg   r   r   r1   )r{  r@   rg   r   r   r1   )L__doc__
__future__r   ri  rP   loggingr+   	threadingr   typingr   r   r   rQ   r   r   r	   rM   r:   r
   r   r   tools.browser_camofox_stater   tools.registryr   	getLogger__name__r   r   _SNAPSHOT_MAX_CHARSr   __annotations__rO   r   r   r%   r/   r7   r?   rC   r[   r]   rc   rf   rr   r   r   r   r   r   r   r   Lockr   r   r   r   r   r   r   r   r   r   r  r  r  r!  r,  r0  r2  r6  r9  rL  rz  r  r6   r0   r$   <module>r     s    2 # " " " " "    				      & & & & & & & & & & : : : : : : : : : :  C C C C C C C C C C < < < < < < % % % % % %		8	$	$         &*  ) ) ) )    0   4 4 4 4
   $# # # #&   2   @ @ @ @	B 	B 	B 	B< < < <&	 	 	 	7 7 7 7: : : :$      & & & &Z (*	 ) ) ) )!!# # # #L(, (, (, (,V    ,, , , ,    *                N1 N1 N1 N1 N1b   D CG04'1 '1 '1 '1 '1T1 1 1 1 16&^ &^ &^ &^ &^R1 1 1 1 1 1 1 1 1 1 1 1 1 1 1(P P P P P01 01 01 01 01f 49,0^1 ^1 ^1 ^1 ^1B      r0   