
    `gjɉ                       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j4                  e      Zd	Zd
Zdade d<   da!da"de d<   da#d7dZ$d8dZ%d9dZ&d9d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Z0d@dZ1dAdZ2dBdZ3i Z4d e d!<    ejj                         Z6dCd"Z7dDd#Z8dEdFd$Z9dGd%Z:dHdId&Z;dHdJd'Z<dKdLd(Z=dKdMd)Z>dKdJd*Z?dHdNd+Z@dOd,ZA	 	 dP	 	 	 dQd-ZBdHdRd.ZCdHdSd/ZDdHdTd0ZEdHdUd1ZFdHdVd2ZGdHdUd3ZHdHdUd4ZI	 	 dW	 	 	 dXd5ZJdWdYd6ZKy)Zu   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_timeoutc                     t         rt        S da t        } 	 t               }t	        |dd      }|t        t        |      d      } | a| S # t        $ r#}t        j                  d|       Y d}~| a| S d}~ww xY w)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
   maxint	Exceptionloggerdebug)resultcfgvalexcs       H/root/.hermes/venv/lib/python3.12/site-packages/tools/browser_camofox.py_get_command_timeoutr$   ;   s     "" FHc9&78?S1%F !M  HA3GG MHs   .A 	A4A//A4c                 `    t        j                  dd      j                         } | 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   s5    
))%r
*
0
0
2C
73%11I    c                 L    t        j                  dd      j                  d      S )z:Return the configured Camofox server URL, or empty string.CAMOFOX_URLr'   /)r)   r*   rstrip r.   r#   get_camofox_urlr4   [   s    99]B'..s33r.   c                     	 ddl m}   |        j                  di       }t        |t              r-t        |j                  dd      xs d      j                         S 	 y# t        $ r Y yw xY w)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dictstrr+   r   )r   browser_cfgs     r#   _config_cdp_urlr=   `   sn    5%'++Ir:k4({y"5;<BBDD )   s   AA 	A*)A*c                     t        j                  dd      j                         ryt               ry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=   boolr4   r3   r.   r#   is_camofox_moderA   r   s6     
yy"B'--/!""r.   c                    t               } | sy	 t        j                  |  dd      }|j                  dk(  rnt        sh	 |j                         }|j                  d      }t        |t              r4d|cxk  rdk  r)n n&d	d
lm	}  ||       }|j                  xs d}d| d| ada|j                  dk(  S # t        t        f$ r Y "w xY w# t        $ r Y yw xY w)z'Verify the Camofox server is reachable.Fz/healthr   timeout   vncPort   i  r   )urlparse	localhostzhttp://:T)r4   requestsr8   status_code_vnc_url_checkedjsonr9   r   urllib.parserH   hostnamer   
ValueErrorKeyErrorr   )urlrespdatavnc_portrH   parsedhosts          r#   check_camofox_availablerY      s     
C||se7OQ7s"+;	yy{88I.h,h1G%1G5%c]F!??9kD!(az:H  $3&& )   s5   /C
 A%B5 $C
 5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.)rM   rY   r   r3   r.   r#   get_vnc_urlr[      s    !Or.   c                     	 t               j                  di       j                  di       } t        | t              r| S i S # t        $ r"}t        j	                  d|       i cY d}~S d}~ww xY w)z>Return the ``browser.camofox`` config block, or an empty dict.r   camofoxz7camofox config check failed, defaulting to disabled: %sN)r   r8   r   r   warningr9   r:   )camofox_cfgr"   s     r#   _get_camofox_configr`      se    !m''	26::9bI %[$7;?R?  PRUV	s   *A   	A+	A& A+&A+c                 F    t        t               j                  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@   r`   r8   r3   r.   r#   _managed_persistence_enabledrc      s     #%))*?@AAr.   c                v   t        j                  dd      j                         xs, t        |j	                  d      xs d      j                         }|syt        j                  dd      j                         xs: t        |j	                  d      xs d      j                         xs d| xs 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   )rf   rh   r)   r*   r+   r;   r8   )task_idr_   rf   rh   s       r#   _camofox_identity_overridern      s     ii)2.446g#kooi>X>^\^:_:e:e:gG 			',224 	1{}-34::<	1G(y#2./0 
 {;;r.   c                    t        j                  | d      j                         j                         }|sy |dv ry|dv ryt        j                  d| |       y )Nr'   >   1onyestrueT>   0noofffalseFz"Ignoring invalid boolean env %s=%r)r)   r*   r+   lowerr   r   )nameraws     r#   	_env_flagr{      sV    
))D"

#
#
%
+
+
-C
((
))
LL5tSAr.   c                T    t        d      }||S t        | j                  d            S )z@Return whether Hermes should recover an existing Camofox tab ID.CAMOFOX_ADOPT_EXISTING_TABadopt_existing_tabr{   r@   r8   r_   	env_values     r#   _adopt_existing_tab_enabledr      s.    67I 4566r.   c                T    t        d      }||S t        | j                  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_URLSrewrite_loopback_urlsr   r   s     r#   _loopback_rewrite_enabledr      s0     9:I 7899r.   c                    t        j                  dd      j                         xs0 t        | j	                  d      xs d      j                         xs dS )z=Return the host alias used when rewriting loopback page URLs.CAMOFOX_LOOPBACK_HOST_ALIASr'   loopback_host_aliaszhost.docker.internalrl   )r_   s    r#   _loopback_rewrite_hostr      sN     			/4::< 	"{45;<BBD	"!r.   c                    | sy| j                         j                  d      j                         }|dv ry	 ddl}|j                  |      j                  S # t
        $ r Y yw xY w)z:Return True for localhost/127.0.0.0/8/::1-style hostnames.Fz[]>   localhost.localdomainrI   Tr   N)r+   rx   	ipaddress
ip_addressis_loopbackrQ   )rP   rX   r   s      r#   _is_loopback_hostnamer      se    >>!!$'--/D55##D)555 s   A 	A"!A"c           	        t               }t        |      s| dfS 	 t        |       }|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|j                  d      sd| dn|}|j                  rd|j                   nd}t        t        |j                  | | | |j                  |j                  |j                               }||j                  xs d|| |dfS # t        $ r | dfcY S w xY w)	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'   rJ   @[])fromtooriginal_urlrewritten_url)r`   r   r   rQ   schemer   rP   r   usernamepassword
startswithportr	   r   pathqueryfragment)rS   r_   rW   aliasuserinfo	host_part	port_part	rewrittens           r#   !_rewrite_loopback_url_for_camofoxr     s_    &'K$[1Dy# }}--5J6??5[Dy";/EDyH????!FOO,--HC #uU5E5Ec5J!E7!PUI%+[[!FKK=!bIFMMhZ	{9+#FU[UaUacicrcrsI %2"	  +  Dys   D2 2EEzDict[str, Dict[str, Any]]	_sessionsc                
   | j                  d      s| j                  d      s| S t               s| S 	 t        dd| d   id      j                  dg       }t        |t              r|s| S | j                  d      }|D cg c](  }t        |t              r|j                  d      |k(  r|* }}|xs! |D cg c]  }t        |t              s| c}}|r|d   nd
}t        |t              r|j                  d      nd
}t        |t              r-|r+|| d<   t        j                  d|| j                  d             | S # t        $ r2}t        j                  d	| j                  d      |       | cY d
}~S d
}~ww xY wc c}w c c}w )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userIdrf   r   )paramsrD   tabsz&Camofox tab adoption failed for %s: %sNrh   
listItemIdtabIdz&Adopted existing Camofox tab %s for %s)
r8   r4   _getr   r   r   r9   listr:   r;   )	sessionr   r"   rh   tabmatching_tabs
candidateslatestr   s	            r#   _adopt_existing_tabr   =  sj    {{8GKK0D$EGXwy/A$BANRRSY[]^
 dD!++m,K c4 SWW\%:k%I 	M 
 P$"P3*S$:O3"PJ)Z^tF$.vt$<VZZ $F&#6"=vw{{S\G]^N)  =w{{9?UWZ[
 #Qs/   #D= >-E;5F F =	E8'E3-E83E8c                   | xs d} t         5  | t        v rt        t        |          cddd       S t               }t	        | |      }|r|d   d|d   dt        |      d}nmt        |j                  d            r#t        |       }|d   d|d   dt        |      d}n0dt        j                         j                  dd	  dd
| dd  ddd}|t        | <   t        |      cddd       S # 1 sw Y   yxY w)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.
    rj   Nrf   rh   T)rf   r   rh   managedr~   rb   hermes_
   ri   rk   F)_sessions_lockr   r   r`   rn   r   r@   r8   r   uuiduuid4hex)rm   r_   identity_overrider   identitys        r#   _get_sessionr   c  s    "G	  ,i&y'9: ,  , *+6wL,Y70?&A+&NG +//"789+G4H#I.'6&A+&NG %TZZ\%5%5cr%:$;<!&ws|n5 &+G %	'"7+A ,  ,  ,s   C+B0C++C4c                   t        |       }|d   r|S t               }t        j                  | d|d   |d   |dt	               t                     }|j                          |j                         }|j                  d      |d<   |S )z<Ensure a tab exists for the session, creating one if needed.r   r   rf   rh   )r   r   rS   rN   rD   headersr   )	r   r4   rK   postr$   r-   raise_for_statusrN   r8   )rm   rS   r   baserT   rU   s         r#   _ensure_tabr     s    7#GxD==&i(!-0

 %&	D 	99;D)GHNr.   c                r    | xs d} t         5  t        j                  | d      cddd       S # 1 sw Y   yxY w)zRemove and return session info.rj   N)r   r   pop)rm   s    r#   _drop_sessionr     s2    "G	 ,}}Wd+, , ,s   -6c                    t               }t        |j                  d            st        | |      r"t	        |        t
        j                  d|        yy)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`.
    rb   z6Camofox soft cleanup for task %s (managed persistence)TF)r`   r@   r8   rn   r   r   r   )rm   r_   s     r#   camofox_soft_cleanupr     sG     &'KKOO1237QRY[f7ggMwWr.   c                    |
t               }t                |  }t        j                  |||t	                     }|j                          |j                         S )z0POST JSON to camofox and return parsed response.r   )r$   r4   rK   r   r-   r   rN   r   bodyrD   rS   rT   s        r#   _postr     sO    &(v
&C==4-/RD99;r.   c                    |
t               }t                |  }t        j                  |||t	                     }|j                          |j                         S )z,GET from camofox and return parsed response.r   rD   r   )r$   r4   rK   r8   r-   r   rN   r   r   rD   rS   rT   s        r#   r   r     sO    &(v
&C<<FG]_UD99;r.   c                    |
t               }t                |  }t        j                  |||t	                     }|j                          |S )z;GET from camofox and return raw response (for binary data).r   )r$   r4   rK   r8   r-   r   r   s        r#   _get_rawr     sH    &(v
&C<<FG]_UDKr.   c                    |
t               }t                |  }t        j                  |||t	                     }|j                          |j                         S )z-DELETE to camofox and return parsed response.r   )r$   r4   rK   deleter-   r   rN   r   s        r#   _deleter     sO    &(v
&C??3T7MOTD99;r.   c                j   	 t        |       \  }}t        |      }|d   st        ||      }d|d}n	 t        d|d    d|d   |dd	      }d|j                  d|      |j                  dd      d}|r| |d<   ||d<   d|d    d|d    |d<   t               }|r
||d<   d|d<   	 t        d|d    dd|d   i      }	|	j                  dd      }
dd lm}m} t!        |
      |kD  r ||
      }
|
|d<   |	j                  d!d      |d"<   t%        j&                  |      S # t        j
                  $ ra}|j                  I|j                  j                  dk(  r0t        j                  d|d          d
|d<   t        ||      }d|d}n Y d
}~Ad
}~ww xY w# t"        $ r Y w xY w# 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)okrS   /tabs/z	/navigaterf   )r   rS   <   rC   Ni  uP   Camofox tab %s returned 404 — tab was garbage collected. Creating a fresh tab.rS   titler'   )successrS   r   requested_urlurl_rewritez0Rewrote loopback URL for Docker-hosted Camofox: r   z -> r   r^   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   rK   	HTTPErrorresponserL   r   r^   r8   r[   r   tools.browser_toolr   r   lenr   rN   dumpsr   ConnectionErrorr4   r;   )rS   rm   browser_urlrewrite_infor   rU   er   vnc	snap_datasnapshot_textr   r   s                r#   camofox_navigater     s   L1$Ec$J!\w'x !';7G{3DWX./y9&y1+F$ 88E;/XXgr*

 &)F?#$0F=!B'(\$-?,@B 9 m #F9T :	*+95 ')"45I &MM*b9M =!$@@ 2= A!.F:&/mmK&CF?# zz&!!e %% ::)ajj.D.D.KNN0)
 )-GH%)';?G"&{;D ^  		  D/s3UCC## zz4_5F4G H_ _
  	  1#a&%001s   0F& D  AF& (A#F F&  F3AF	F& FF& 	F# F& "F##F& &H29GH2:H2
H2H-'H2-H2c                    ddl m}m}  ||xs d      sy || d   | d         }|sy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_activerj   Nr   rf   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  rN   r   )r   rm   actionr   r  blocked_urls         r#   _camofox_private_page_blockr  =  sm    
 #7#7i83GH4EwyGYZK::}OF8 4  r.   c                   	 t        |      }|d   st        dd      S t        ||d      }|r|S t        d|d    dd|d	   i
      }|j	                  dd      }|j	                  dd      }ddlm}m}	m}
 t        |      |kD  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   rf   r   r   r'   r   r   )r   _extract_relevant_contentr   T)r   r   r   N)r   r   r  r   r8   r   r   r  r   r   rN   r   r   r;   )fullrm   	user_taskr   blockedrU   r   
refs_countr   r  r   r   s               r#   camofox_snapshotr  _  s    $1w'x PZ_``-gw@VWNWX&'y1gi01

 88J+XXk1-
	
 	
 x=774XyI-h7zz '
  	
  1#a&%001s(   B2 B2 BB2 2	C;CCCc                ^   	 t        |      }|d   st        dd      S t        ||d      }|r|S | j                  d      }t	        d|d    d|d	   |d
      }t        j                  d||j                  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/clickrf   )r   refTrS   r'   )r   clickedrS   N)
r   r   r  lstripr   rN   r   r8   r   r;   )r  rm   r   r  	clean_refrU   r   s          r#   camofox_clickr    s    1w'x PZ_``-gwHN JJsO	WX&'v.y))<
 zz 88E2&
  	
  1#a&%001s(   B B AB 	B,B'!B,'B,c                   	 t        |      }|d   st        dd      S t        ||d      }|r|S | j                  d      }t	        d|d    d|d	   ||d
       ddlm}m}  |dd|i      xs i d   }d||d}	 ||	|      }	t        j                  |	      S # t        $ r-}
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/typerf   )r   r  textr   )%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  rN   r   r   r;   )r  r  rm   r   r  r  r  r  display_textr   r   s              r#   camofox_typer    s    $^w'x PZ_``-gwGNJJsO	WX&'u-y))TJ	
	

 5^fd^TZXZ\bc 
 " 
 94Hzz(## ^G?AMW\]]^s(   B B A(B 	C""C
C
Cc                    	 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/scrollrf   )r   	directionT)r   scrolledN)r   r   r   rN   r   r   r;   )r!  rm   r   r   s       r#   camofox_scrollr#    s    1w'x PZ_``WX&'w/y)	B	
 zzd	BCC 1#a&%001s"   A 0A 	A9A4.A94A9c                   	 t        |       }|d   st        dd      S t        d|d    dd|d   i      }t        j                  d	|j                  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   rf   TrS   r'   )r   rS   N)r   r   r   rN   r   r8   r   r;   )rm   r   rU   r   s       r#   camofox_backr%    s    1w'x PZ_``WX&'u-wy)*
 zzd488E23FGHH 1#a&%001s"   A ?A 	B(B=BB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/pressrf   )r   r,   T)r   pressedN)r   r   r  r   rN   r   r   r;   )r,   rm   r   r  r   s        r#   camofox_pressr)    s    1w'x PZ_``-gwHNWX&'v.y)#6	
 zzds;<< 1#a&%001s'   A! A! 0A! !	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/rf   )r   r+  r^   N)r   rN   r   r   r   r;   )rm   r   r   s      r#   camofox_closer,    s    
P(::$$?@@+,-	
 zzdd;<< Pzzdds1vN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      }|j                  dd      }g }|j                  d      }t        |      D ]  \  }}	|	j                         }
|
j                  d      s(|j                  d|
      }|r|j                  d      nd}d}|dz   t        |      k  r9|j                  d||dz      j                               }|r|j                  d      }|s|s|j                  ||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   rf   r   r   r'   
)z- img zimg zimg\s+"([^"]*)"rG   z/url:\s*(\S+))srcaltT)r   imagescount)r   r   r  rer   r8   split	enumerater+   r   searchgroupr   appendrN   r   r   r;   )rm   r   r  r3  rU   r   r1  linesilinestripped	alt_matchr0  r/  	url_matchr   s                   r#   camofox_get_imagesr?    s   *1w'x PZ_``-gw@UVNWX&'y1gi01
 88J+
 t$ ' 	<GAtzz|H""#56II&8(C	,5iooa(2q53u:% "		*:E!a%L<N<N<P QI 'ooa0#MM#c":;	< zz[
  	
  1#a&%001s5   E E A4E %A7E 7E 	E>E93E>9E>c                   	 t        |      }|d   st        dd      S t        ||d      }|r|S t        d|d    dd|d	   i
      }ddlm}  |       dz  }|j                  dd       t        |dt        j                         j                  dd  dz        }t        |d      5 }	|	j                  |j                         ddd       t        j                  |j                        j!                  d      }
d}|r2	 t#        d|d    dd|d	   i
      }d|j%                  dd      dd  }ddlm}  ||      }ddlm} d|  | }	 t1               }t3        |ddi        }t5        |j%                  d!d"            }t5        |j%                  d#d$            } |d&d'|d(d)d*d+|
 id,gd-gd||.      }|j6                  r5|j6                  d   j8                  j                  xs dj;                         nd}ddlm}  ||      }t=        j>                  d||d/      S # 1 sw Y   `xY w# t&        $ r Y w xY w# t&        $ r d%}d$}Y w xY w# 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   rf   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)rj   rD   x   temperatureg?g      ^@userr  )r  r  	image_urlrS   zdata:image/png;base64,)r  rO  )rolecontent)messagestaskrM  rD   )r   analysisscreenshot_path) r   r   r  r   hermes_constantsrA  mkdirr;   r   r   r   openwriterQ  base64	b64encodedecoder   r8   r   agent.redactrH  agent.auxiliary_clientrI  r   r
   floatchoicesmessager+   rN   r   )questionannotaterm   r   r  rT   rA  screenshots_dirrU  fimg_b64annotation_contextr   rH  rI  vision_prompt_cfg_vision_cfg_vision_timeout_vision_temperaturer   rT  r   s                          r#   camofox_visionrm  C  s   [1w'x PZ_``-gw@VWN WX&'{3gi01
 	5)+.CCdT:o2EdjjlFVFVWYXYFZE[[_0``a/4( 	"AGGDLL!	" ""4<<077@   WX./y9$gi&89	 (``i`m`mnxz|`}  D  @D  aE  `F  &G" 	723EF 	4 ;8*!"$ 	
	&=D!$XrJK#KOOIs$CDO"'s(K"L
 #]; +!%;G9#E&  +#
" KSJZJZH$$Q'//77=2DDF`b 	7(2zz .
  	A	" 	"  ,  	&#O"%	&@  1#a&%001s   I I A9I )H#:I  1H0 1I AI  BI #H-(I 0	H=9I <H==I  II II 	I<I71I<7I<c           	     :    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)rN   r   )clearrm   s     r#   camofox_consoleru    s/     ::P  r.   )returnr   )rv  zDict[str, str])rv  r;   )rv  r@   )rv  r   )rv  Dict[str, Any])rm   r   r_   rw  rv  zOptional[Dict[str, str]])ry   r;   rv  zOptional[bool])r_   rw  rv  r@   )r_   rw  rv  r;   )rP   r   rv  r@   )rS   r;   rv  z$tuple[str, Optional[Dict[str, str]]])r   rw  rv  rw  )rm   r   rv  rw  )zabout:blank)rm   r   rS   r;   rv  rw  )rm   r   rv  zOptional[Dict[str, Any]])N)rm   r   rv  r@   )r   r;   r   r:   rD   r   rv  r:   )NN)r   r;   r   r:   rD   r   rv  r:   )r   r;   r   r:   rD   r   rv  zrequests.Response)rS   r;   rm   r   rv  r;   )r   rw  rm   r   r  r;   rv  r   )FNN)r	  r@   rm   r   r
  r   rv  r;   )r  r;   rm   r   rv  r;   )r  r;   r  r;   rm   r   rv  r;   )r!  r;   rm   r   rv  r;   )rm   r   rv  r;   )r,   r;   rm   r   rv  r;   )FN)rb  r;   rc  r@   rm   r   rv  r;   )rt  r@   rm   r   rv  r;   )L__doc__
__future__r   rZ  rN   loggingr)   	threadingr   typingr   r   r   rO   r   r   r	   rK   r7   r
   r   r   tools.browser_camofox_stater   tools.registryr   	getLogger__name__r   r   _SNAPSHOT_MAX_CHARSr   __annotations__rM   r   r   r$   r-   r4   r=   rA   rY   r[   r`   rc   rn   r{   r   r   r   r   r   r   Lockr   r   r   r   r   r   r   r   r   r   r   r  r  r  r  r#  r%  r)  r,  r?  rm  ru  r3   r.   r#   <module>r     s  2 #    	   & & : :  C C < %			8	$   -   &* ] ) 04
$#&2@	B<&	7:$&Z (*	$ )!#L(,V,,*N1bD CG04'1 -'19<'1T16&^R1 1 1(P01f 49,0^1)^158^1Br.   