
    iDT                       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Zddl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?dZd@dZd@dZdAdZ d@dZ!i Z"ded<    ej#                    Z$dBdZ%dCdDdZ&dEdZ'dFdGdZ(efdHd%Z)defdId'Z*defdJd)Z+defdHd*Z,dFdKd+Z-	 	 dLdMd.Z.dFdNd0Z/dFdOd2Z0dFdPd4Z1dFdQd5Z2dFdRd7Z3dFdQd8Z4dFdQd9Z5	 	 dSdTd<Z6dSdUd>Z7dS )VuA  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``.
    )annotationsN)AnyDictOptional)cfg_getload_config)get_camofox_identity)
tool_error   i8 Optional[str]_vnc_urlFreturnstrc                 R    t          j        dd                              d          S )z:Return the configured Camofox server URL, or empty string.CAMOFOX_URL /)osgetenvrstrip     :/home/ubuntu/.hermes/hermes-agent/tools/browser_camofox.pyget_camofox_urlr   3   s"    9]B''..s333r   boolc                     t          j        dd                                          rdS t          t	                                S )aq  True when Camofox backend is configured and no CDP override is active.

    When the user has explicitly connected to a live Chrome instance via
    ``/browser connect`` (which sets ``BROWSER_CDP_URL``), the CDP connection
    takes priority over Camofox so the browser tools operate on the real
    browser instead of being silently routed to the Camofox backend.
    BROWSER_CDP_URLr   F)r   r   stripr   r   r   r   r   is_camofox_moder   8   s>     
y"B''--// u!!"""r   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/health   timeout   vncPort   i  r   )urlparse	localhostzhttp://:T)r   requestsgetstatus_code_vnc_url_checkedjson
isinstanceinturllib.parser'   hostnamer   
ValueErrorKeyError	Exception)urlrespdatavnc_portr'   parsedhosts          r   check_camofox_availabler<   E   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.)r-   r<   r   r   r   r   get_vnc_urlr>   ^   s     "!!!Or   c                    	 t                                          di                               di           } n3# t          $ r&}t                              d|           Y d}~dS d}~ww xY w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.
    browsercamofoxz<managed_persistence check failed, defaulting to disabled: %sNFmanaged_persistence)r   r+   r5   loggerwarningr   )camofox_cfgexcs     r   _managed_persistence_enabledrG   e   s    !mm''	266::9bII   UWZ[[[uuuuu  566777s   69 
A)A$$A)zDict[str, Dict[str, Any]]	_sessionstask_idDict[str, Any]c                p   | pd} t           5  | t          v rt          |          cddd           S t                      r#t          |           }|d         d|d         dd}n3dt	          j                    j        dd          dd	| dd
          dd}|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.
    defaultNuser_idsession_keyT)rM   tab_idrN   managedhermes_
   task_   F)_sessions_lockrH   rG   r	   uuiduuid4hex)rI   identitysessions      r   _get_sessionr[   ~   sG    "G	  iW%        ()) 	+G44H#I.'6	 GG =TZ\\%5crc%:<<5wss|55 	 G %	''                 s   B+A/B++B/2B/about:blankr6   c                2   t          |           }|d         r|S t                      }t          j        | d|d         |d         |dt                    }|                                 |                                }|                    d          |d<   |S )z<Ensure a tab exists for the session, creating one if needed.rO   z/tabsrM   rN   )userId
sessionKeyr6   r.   r#   tabId)r[   r   r*   post_DEFAULT_TIMEOUTraise_for_statusr.   r+   )rI   r6   rZ   baser7   r8   s         r   _ensure_tabrf      s    7##Gx D=i(!-0
 

 !  D 	99;;D))GHNr   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.rL   N)rU   rH   poprI   s    r   _drop_sessionrk      s    "G	 , ,}}Wd++, , , , , , , , , , , , , , , , , ,s   488c                z    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`.
    z6Camofox soft cleanup for task %s (managed persistence)TF)rG   rk   rC   debugrj   s    r   camofox_soft_cleanuprn      s?     $%% gMwWWWt5r   pathbodydictr#   r0   c                    t                       |  }t          j        |||          }|                                 |                                S )z0POST JSON to camofox and return parsed response.r`   )r   r*   rb   rd   r.   ro   rp   r#   r6   r7   s        r   _postrt      sN    
&
&
&C=4999D99;;r   paramsc                    t                       |  }t          j        |||          }|                                 |                                S )z,GET from camofox and return parsed response.ru   r#   )r   r*   r+   rd   r.   ro   ru   r#   r6   r7   s        r   _getry      sN    
&
&
&C<FG<<<D99;;r   requests.Responsec                    t                       |  }t          j        |||          }|                                 |S )z;GET from camofox and return raw response (for binary data).rw   )r   r*   r+   rd   rx   s        r   _get_rawr|      sE    
&
&
&C<FG<<<DKr   c                    t                       |  }t          j        |||          }|                                 |                                S )z-DELETE to camofox and return parsed response.r`   )r   r*   deleterd   r.   rs   s        r   _deleter      sN    
&
&
&C?3T7;;;D99;;r   c                   	 t          |          }|d         st          ||           }d| d}n%t          d|d          d|d         | 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.rO   T)okr6   /tabs/z	/navigaterM   )r^   r6   <   r"   r6   titler   )successr6   r   vnc_urlz]Browser is visible via VNC. Share this link with the user so they can watch the browser live.vnc_hint	/snapshotr^   ru   snapshotr   )SNAPSHOT_SUMMARIZE_THRESHOLD_truncate_snapshot	refsCountelement_countzNavigation failed: Fr   Nz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[   rf   rt   r+   r>   ry   tools.browser_toolr   r   lenr5   r.   dumpsr*   	HTTPErrorr
   ConnectionErrorr   r   )r6   rI   rZ   r8   resultvnc	snap_datasnapshot_textr   r   es              r   camofox_navigater      s   71w''x  
	!'3//Gs++DD 5*555"9-c::  D 88E3''XXgr**
 

 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[   BD? A=D D? 
D(%D? 'D((D? ?GE'!G'9G"	G+GGGfull	user_taskc                   	 t          |          }|d         st          dd          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.rO   0No browser session. Call browser_navigate first.Fr   r   r   r^   rM   r   r   r   r   r   )r   _extract_relevant_contentr   T)r   r   r   N)r[   r
   ry   r+   r   r   r   r   r   r.   r   r5   r   )r   rI   r   rZ   r8   r   
refs_countr   r   r   r   s              r   camofox_snapshotr   ,  su    1w''x  	aPZ_````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 BC 
C:C5/C:5C:refc                   	 t          |          }|d         st          dd          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.rO   r   Fr   @r   z/clickrM   )r^   r   Tr6   r   )r   clickedr6   N)	r[   r
   lstriprt   r.   r   r+   r5   r   )r   rI   rZ   	clean_refr8   r   s         r   camofox_clickr   R  s    1w''x  	aPZ_```` JJsOO	.WX&...y))<<
 
 z 88E2&&
 
   	
  1 1 1#a&&%0000000001s#   'B A#B 
C B;5C ;C textc                `   	 t          |          }|d         st          dd          S |                     d          }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-Type text into an element by ref via Camofox.rO   r   Fr   r   r   z/typerM   )r^   r   r   T)r   typedelementN)r[   r
   r   rt   r.   r   r5   r   )r   r   rI   rZ   r   r   s         r   camofox_typer   i  s    1w''x  	aPZ_````JJsOO	-WX&---y))TJJ	
 	
 	
 z 
 
   	
  1 1 1#a&&%0000000001s#   'A; AA; ;
B-B("B-(B-	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.rO   r   Fr   r   z/scrollrM   )r^   r   T)r   scrolledNr[   r
   rt   r.   r   r5   r   )r   rI   rZ   r   s       r   camofox_scrollr     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1"   '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.rO   r   Fr   r   z/backr^   rM   Tr6   r   )r   r6   N)r[   r
   rt   r.   r   r+   r5   r   )rI   rZ   r8   r   s       r   camofox_backr     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)keyc                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!Press a keyboard key via Camofox.rO   r   Fr   r   z/pressrM   )r^   r   T)r   pressedNr   )r   rI   rZ   r   s       r   camofox_pressr     s    1w''x  	aPZ_````.WX&...y)#66	
 	
 	
 zds;;<<< 1 1 1#a&&%0000000001r   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/rM   )r   r   rD   N)rk   r.   r   r   r5   r   )rI   rZ   r   s      r   camofox_closer     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 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.
    rO   r   Fr   r   Nr   r   r^   rM   r   r   r   
)z- img zimg zimg\s+"([^"]*)"r&   z/url:\s*(\S+))srcaltT)r   imagescount)r[   r
   rery   r+   split	enumerater   
startswithsearchgroupr   appendr.   r   r5   r   )rI   rZ   r   r8   r   r   linesilinestripped	alt_matchr   r   	url_matchr   s                  r   camofox_get_imagesr     s
   &1w''x  	aPZ_````			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 E!F 
F>F93F>9F>questionannotatec                   	 t          |          }|d         st          dd          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|  |
 }	 t/                      }t1          |ddi           }t3          |                    d d!                    }t3          |                    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)/z<Take a screenshot and analyze it with vision AI via Camofox.rO   r   Fr   r   z/screenshotr^   rM   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)rL   r#   x   temperatureg?g      ^@userr   )typer   	image_urlr6   zdata:image/png;base64,)r   r   )rolecontent)messagestaskr   r#   )r   analysisscreenshot_path)r[   r
   r|   hermes_constantsr   mkdirr   rV   rW   rX   openwriter   base64	b64encodedecodery   r+   r5   agent.redactr   agent.auxiliary_clientr   r   r   floatchoicesmessager   r.   r   )r   r   rI   rZ   r7   r   screenshots_dirr   fimg_b64annotation_contextr   r   r   vision_prompt_cfg_vision_cfg_vision_timeout_vision_temperatureresponser   r   s                         r   camofox_visionr    s<   W1w''x  	aPZ_```` 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 BJ ;C"J "C&&J )C&*3J AE# "J #
E0-J /E00!J A'G: 9J :HJ 
HA?J 
J=J82J=8J=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)r.   r   )r  rI   s     r   camofox_consoler	  J  s7     :P    r   )r   r   )r   r   )r   r   )rI   r   r   rJ   )r\   )rI   r   r6   r   r   rJ   )rI   r   r   rg   )N)rI   r   r   r   )ro   r   rp   rq   r#   r0   r   rq   )ro   r   ru   rq   r#   r0   r   rq   )ro   r   ru   rq   r#   r0   r   rz   )r6   r   rI   r   r   r   )FNN)r   r   rI   r   r   r   r   r   )r   r   rI   r   r   r   )r   r   r   r   rI   r   r   r   )r   r   rI   r   r   r   )rI   r   r   r   )r   r   rI   r   r   r   )FN)r   r   r   r   rI   r   r   r   )r  r   rI   r   r   r   )8__doc__
__future__r   r   r.   loggingr   	threadingrV   typingr   r   r   r*   hermes_cli.configr   r   tools.browser_camofox_stater	   tools.registryr
   	getLogger__name__rC   rc   _SNAPSHOT_MAX_CHARSr   __annotations__r-   r   r   r<   r>   rG   rH   LockrU   r[   rf   rk   rn   rt   ry   r|   r   r   r   r   r   r   r   r   r   r   r  r	  r   r   r   <module>r     s    , # " " " " "    				      & & & & & & & & & &  2 2 2 2 2 2 2 2 < < < < < < % % % % % %		8	$	$        4 4 4 4

# 
# 
# 
#   2   8 8 8 8* (*	 ) ) ) )!!   <    *, , , ,    ( 1A      $(8H      (,<L      %)9I     91 91 91 91 91x CG04#1 #1 #1 #1 #1L1 1 1 1 1.1 1 1 1 1,1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 P P P P P,1 ,1 ,1 ,1 ,1^ 49,0Z1 Z1 Z1 Z1 Z1z      r   