+
    i{V                     `   R t ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIHt ^ RIH	t	H
t
HtHt ^ RIHt ^ RIt^ RIHtHt ^ RIHt ^ RIHt ]P.                  ! ]4      t]! RR	R
7      tR R lt]! 4       tR R ltR R ltRRR R lltR R lt RSR R llt!RSR R llt"R R lt#R R lt$]R8X  Ed@    ]%! R4       ]%! RT4       ]#! 4       t&]&'       g   ]%! R4       ]%! R 4       ]'! ^4       M]%! R!4       ]%! R"4       ]PP                  '       d-   ]%! R#]PR                   24       ]%! R$]PR                   R%24       M]%! R&4       ]%! R'4       ]%! R(4       ]%! R)4       ]%! R*4       ]%! R+4       ]%! R,4       ]%! R-4       ]%! R.4       ]%! R/4       ]%! R04       ]%! R14       ]%! R24       ]%! R34       ]%! R44       ]%! R54       ]%! R64       ]%! R74       ]%! R84       ]%! R94       ]%! R:4       ]%! R;4       ]%! R<4       ^ R=I*H+t+ R>R?R@RARBRCRDRERFRCRGR@RH/RIRCRGR@RJ//RKRFRI.//t,RL RM lt-]+P\                  ! R?RN],]-]#RORPRQ7       R# )UaV  
Vision Tools Module

This module provides vision analysis tools that work with image URLs.
Uses the centralized auxiliary vision router, which can select OpenRouter,
Nous, Codex, native Anthropic, or a custom OpenAI-compatible endpoint.

Available tools:
- vision_analyze_tool: Analyze images from URLs with custom prompts

Features:
- Downloads images from URLs and converts to base64 for API compatibility
- Comprehensive image description
- Context-aware analysis based on user queries
- Automatic temporary file cleanup
- Proper error handling and validation
- Debug logging support

Usage:
    from vision_tools import vision_analyze_tool
    import asyncio
    
    # Analyze an image
    result = await vision_analyze_tool(
        image_url="https://example.com/image.jpg",
        user_prompt="What architectural style is this building?"
    )
N)Path)Any	AwaitableDictOptional)urlparse)async_call_llmextract_content_or_reasoning)DebugSession)check_website_accessvision_toolsVISION_TOOLS_DEBUG)env_varc                $    V ^8  d   QhR\         /#    return)float)formats   "//home/ubuntu/hermes-agent/tools/vision_tools.py__annotate__r   3   s      5     c                  \   \         P                  ! R R4      P                  4       p V '       d    \        V 4      #  ^ RIHp V! 4       pVP                  R/ 4      P                  R/ 4      P                  R4      pVe   \        V4      #  R#   \         d     L^i ; i  \         d     R# i ; i)HERMES_VISION_DOWNLOAD_TIMEOUT load_config	auxiliaryvisiondownload_timeoutg      >@)	osgetenvstripr   
ValueErrorhermes_cli.configr   get	Exception)env_valr   cfgvals       r   _resolve_download_timeoutr*   3   s    ii8"=CCEG	>!1mggk2&**8R8<<=OP?:    		  s#   
B AB BBB+*B+c                0    V ^8  d   QhR\         R\        /# )r   urlr   )strbool)r   s   "r   r   r   G   s      S T r   c                   V '       d   \        V \        4      '       g   R# V P                  R4      '       g   V P                  R4      '       g   R# \        V 4      pVP                  '       g   R# ^ RIHp V! V 4      '       g   R# R# )z
Basic validation of image URL format.

Args:
    url (str): The URL to validate
    
Returns:
    bool: True if URL appears to be valid, False otherwise
Fzhttp://zhttps://is_safe_urlT)
isinstancer-   
startswithr   netloctools.url_safetyr1   )r,   parsedr1   s   &  r   _validate_image_urlr7   G   se     jc** NN9%%
)C)C c]F=== -sr   c                F    V ^8  d   QhR\         R\        \        ,          /# r   
image_pathr   r   r   r-   )r   s   "r   r   r   f   s       # r   c                V   V P                  R4      ;_uu_ 4       pVP                  ^@4      pRRR4       XP                  R4      '       d   R# VP                  R4      '       d   R# VP                  R4      '       d   R# VP                  R4      '       d   R	# \        V4      ^8  d   VR
,          R8X  d   VR,          R8X  d   R# V P                  P                  4       R8X  d2   V P                  RRR7      R,          P                  4       pRV9   d   R# R#   + '       g   i     L; i)z>Return a MIME type when the file looks like a supported image.rbNs   PNG

	image/pngs   
image/jpeg	image/gifs   BM	image/bmp:N   Ns   RIFF:      Ns   WEBP
image/webp.svgzutf-8ignore)encodingerrors:Ni   Nz<svgimage/svg+xml)s   GIF87as   GIF89a)openreadr3   lensuffixlower	read_text)r:   fheaderheads   &   r   _detect_image_mime_typerT   f   s    			! 
 -..))/00
6{bVBZ72vd|w7N F*##WX#FuMSSUT>"# 
	s   DD(	c                H    V ^8  d   QhR\         R\        R\        R\        /# )r   	image_urldestinationmax_retriesr   )r-   r   int)r   s   "r   r   r   |   s/     X XS Xt X# XVZ Xr   c           
     R  "   ^ RI pVP                  P                  RRR7       R pRp\        V4       F  p \	        V 4      pV'       d   \        VR,          4      h\        P                  ! \        RRV./R7      ;_uu_4       GRj  xL
 pVP                  V RR	R
R/R7      G Rj  xL
 p	V	P                  4        \        V	P                  4      p
\	        V
4      pV'       d   \        VR,          4      hVP                  V	P                  4       RRR4      GRj  xL
  Vu # 	  Vf   \)        RV R24      hVh L L L#  + GRj  xL 
 '       g   i     L:; i  \         d   pTpYb^,
          8  dw   ^T^,           ,          p\         P#                  RT^,           T\        T4      R,          4       \         P#                  RT4       TP%                  T4      G Rj  xL 
   Rp?EK  \         P'                  RT\        T4      R,          RR7        Rp?EK  Rp?ii ; i5i)a  
Download an image from a URL to a local destination (async) with retry logic.

Args:
    image_url (str): The URL of the image to download
    destination (Path): The path where the image should be saved
    max_retries (int): Maximum number of retry attempts (default: 3)
    
Returns:
    Path: The path to the downloaded image
    
Raises:
    Exception: If download fails after all retries
NT)parentsexist_okc                   "   V P                   '       dX   V P                  '       dD   \        V P                  P                  4      p^ RIHp V! V4      '       g   \        RV 24      hR# R# R# 5i)a
  Re-validate each redirect target to prevent redirect-based SSRF.

Without this, an attacker can host a public URL that 302-redirects
to http://169.254.169.254/ and bypass the pre-flight is_safe_url check.

Must be async because httpx.AsyncClient awaits event hooks.
r0   z.Blocked redirect to private/internal address: N)is_redirectnext_requestr-   r,   r5   r1   r#   )responseredirect_urlr1   s   &  r   _ssrf_redirect_guard-_download_image.<locals>._ssrf_redirect_guard   sf      H$9$9$9x44889L4|,, D\NS  - %:s   $A.AA.messager`   )timeoutfollow_redirectsevent_hooksz
User-AgentzoMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36Acceptzimage/*,*/*;q=0.8)headersz)Image download failed (attempt %s/%s): %s:N2   NzRetrying in %ss...z+Image download failed after %s attempts: %sNd   Nexc_infozB_download_image exited retry loop without attempting (max_retries=))asyncioparentmkdirranger   PermissionErrorhttpxAsyncClient_VISION_DOWNLOAD_TIMEOUTr%   raise_for_statusr-   r,   write_bytescontentr&   loggerwarningsleeperrorRuntimeError)rV   rW   rX   rp   rb   
last_errorattemptblockedclientr`   	final_urle	wait_times   &&&          r   _download_imager   |   s      TD9  J%,	*95G%gi&899
 ((0!%'*>)?@   !'$  'X "5 ", "  ))+-	.y9)')*<== ''(8(89) , ? &^ PQ\P]]^_
 	
 U
   .  	Jq('A+.	JGVWKYdfijkflmpfqr3Y?mmI....AF4L!	   	s   5H'AE	D=

EE(D?
)A)EEEE#H'=E?EEE	E

EEEH$*A8H"G%#H(H'/)HH'H$$H'c                0    V ^8  d   QhR\         R\        /# r9   )r   r-   )r   s   "r   r   r      s     3 3T 3c 3r   c                z    V P                   P                  4       pRRRRRRRRRR	R
RRR/pVP                  VR4      # )z
Determine the MIME type of an image based on its file extension.

Args:
    image_path (Path): Path to the image file
    
Returns:
    str: The MIME type (defaults to image/jpeg if unknown)
.jpgr?   z.jpegz.pngr>   z.gifr@   z.bmprA   z.webprE   rF   rJ   )rN   rO   r%   )r:   	extension
mime_typess   &  r   _determine_mime_typer      sU     !!'')IJ >>)\22r   c                R    V ^8  d   QhR\         R\        \        ,          R\        /# )r   r:   	mime_typer   r;   )r   s   "r   r   r      s&      $ 8C= TW r   c                    V P                  4       p\        P                  ! V4      P                  R4      pT;'       g    \	        V 4      pRV RV 2pV# )a  
Convert an image file to a base64-encoded data URL.

Args:
    image_path (Path): Path to the image file
    mime_type (Optional[str]): MIME type of the image (auto-detected if None)
    
Returns:
    str: Base64-encoded data URL (e.g., "data:image/jpeg;base64,...")
asciizdata:z;base64,)
read_bytesbase64	b64encodedecoder   )r:   r   dataencodedmimedata_urls   &&    r   _image_to_base64_data_urlr      s]       "D t$++G4G 88,Z8D tfHWI.HOr   c                H    V ^8  d   QhR\         R\         R\         R\         /# )r   rV   user_promptmodelr   )r-   )r   s   "r   r   r     s6     f fff f 		fr   c                  a"   RRT R\        V4      ^8  d   VR,          R,           MTRV/RRR	R
R^ RVR^ /pRpRpRp ^ RIHp V! 4       '       dj   \        P                  ! R	R
RR/4      V'       dH   V'       d?   VP                  4       '       d(    VP                  4        \        P                  R4       # # # # \        P                  RV R,          4       \        P                  RVR,          4       \        \        P                  P                  V 4      4      p	V	P!                  4       '       d   \        P                  RV 4       T	pR
pM\#        V 4      '       d|   \%        V 4      p
V
'       d   \'        V
R,          4      h\        P                  R4       \        R4      pVR\(        P*                  ! 4        R2,          p\-        W4      G Rj  xL
  RpM\/        R4      hVP1                  4       P2                  pVR,          p\        P                  R V4       \5        V4      pV'       g   \/        R!4      h\        P                  R"4       \7        WFR#7      p\        V4      R,          p\        P                  R$V4       WR&   TpR%R&R'R(R)R)V/R(RRR*V//./.p\        P                  R+4       R,p ^ R-IHp V! 4       pVP=                  R./ 4      P=                  R// 4      P=                  R04      pVe   \?        V4      pR1R/R2TR3R4R5R6R0T/pT'       d   TTR&   \A        RD/ TB G Rj  xL
 p\C        T4      pT'       g4   \        P                  R74       \A        RD/ TB G Rj  xL
 p\C        T4      p\        T4      p\        P                  R8T4       R	RR9T;'       g    R:/pRTR	&   TTR&   \D        PG                  R;T4       \D        PI                  4        \        P                  ! T^R
R<7      T'       dH   T'       d?   TP                  4       '       d(    TP                  4        \        P                  R4       # # # #   \         d#   p\        P                  RTRR7        Rp?# Rp?ii ; i EL  \         d     ELi ; i EL] EL%  \         d#   p\        P                  RTRR7        Rp?# Rp?ii ; i  \         Ed   pR=\K        T4       2p\        PM                  R>TRR7       \K        T4      PO                  4       o\P        ;QJ d    T3R? lRE 4       F  '       g   K   RM	  R
M! T3R? lRE 4       4      '       d   R@T 2pMM\P        ;QJ d    T3RA lRF 4       F  '       g   K   RM	  R
M! T3RA lRF 4       4      '       d	   T RBT 2pMRCT 2pR	R
RTR9T/pTTR&   \D        PG                  R;T4       \D        PI                  4        \        P                  ! T^R
R<7      u Rp?T'       dx   T'       do   TP                  4       '       dX    TP                  4        \        P                  R4       #   \         d#   p\        P                  RTRR7        Rp?# Rp?ii ; i# # # Rp?ii ; i  T'       dx   T'       do   TP                  4       '       dX    TP                  4        \        P                  R4       i   \         d#   p\        P                  RTRR7        Rp?i Rp?ii ; ii i i ; i5i)Ga  
Analyze an image from a URL or local file path using vision AI.

This tool accepts either an HTTP/HTTPS URL or a local file path. For URLs,
it downloads the image first. In both cases, the image is converted to base64
and processed using Gemini 3 Flash Preview via OpenRouter API.

The user_prompt parameter is expected to be pre-formatted by the calling
function (typically model_tools.py) to include both full description
requests and specific questions.

Args:
    image_url (str): The URL or local file path of the image to analyze.
                     Accepts http://, https:// URLs or absolute/relative file paths.
    user_prompt (str): The pre-formatted prompt for the vision model
    model (str): The vision model to use (default: google/gemini-3-flash-preview)

Returns:
    str: JSON string containing the analysis results with the following structure:
         {
             "success": bool,
             "analysis": str (defaults to error message if None)
         }

Raises:
    Exception: If download fails, analysis fails, or API key is not set
    
Note:
    - For URLs, temporary images are stored in ./temp_vision_images/ and cleaned up
    - For local file paths, the file is used directly and NOT deleted
    - Supports common image formats (JPEG, PNG, GIF, WebP, etc.)

parametersrV   r   :N   Nz...r   r~   NsuccessFanalysis_length
model_usedimage_size_bytesT)is_interruptedInterruptedzCleaned up temporary image filez#Could not delete temporary file: %srm   zAnalyzing image: %s:N<   NzUser prompt: %srk   zUsing local image file: %srd   zDownloading image from URL...z./temp_vision_imagestemp_image_r   zKInvalid image source. Provide an HTTP/HTTPS URL or a valid local file path.i   zImage ready (%.1f KB)z8Only real image files are supported for vision analysis.zConverting image to base64...)r   z#Image converted to base64 (%.1f KB)roleuserrz   typetextr,   z%Processing image with vision model...g      ^@r   r   r   re   taskmessagestemperatureg?
max_tokensi  z0Vision LLM returned empty content, retrying oncez(Image analysis completed (%s characters)analysiszIThere was a problem with the request and the image could not be analyzed.vision_analyze_tool)indentensure_asciizError analyzing image: z%sc              3   ,   <"   T F	  qS9   x  K  	  R # 5iN .0hinterr_strs   & r   	<genexpr>&vision_analyze_tool.<locals>.<genexpr>  s       ,
4w ,
   zhInsufficient credits or payment required. Please top up your API provider account and try again. Error: c              3   ,   <"   T F	  qS9   x  K  	  R # 5ir   r   r   s   & r   r   r     s       .
T .
r   zO does not support vision or our request was not accepted by the server. Error: zQThere was a problem with the request and the image could not be analyzed. Error: r   )402insufficientzpayment requiredcreditsbilling)zdoes not supportznot support imageinvalid_requestcontent_policyrV   
multimodalzunrecognized request argumentzimage input))rM   tools.interruptr   jsondumpsexistsunlinkr{   debugr&   r|   infor   r    path
expanduseris_filer7   r   rt   uuiduuid4r   r#   statst_sizerT   r   r$   r   r%   r   r   r	   _debuglog_callsaver-   r~   rO   any)rV   r   r   debug_call_datatemp_image_pathshould_cleanupdetected_mime_typer   cleanup_error
local_pathr   temp_dirr   image_size_kbimage_data_urldata_size_kbcomprehensive_promptr   vision_timeoutr   _cfg_vtcall_kwargsr`   r   r   resultr   	error_msgr   s   &&&                          @r   r   r     s    L 	K8H38N;t,u4T_U

 	51eAO O Nn2::y%-HIH o/2H2H2J2J&&(>? 3Ko>E 	)9S>:%{4'89 "'',,Y78
KK4i@(O"N ++*95G%gi&899KK7823H&;tzz|nD)IIO!)===!N] 
 +//199(4/+];4_E!WXX 	342?a>*T19<H.>*+  +
  4
 #!>&
$ 	;<
 	5=D((;+//"=AA)LC!&s H3$~
 #(K '6+66 09 NNMN+:k::H3H=Hh->P too$o

 &*	"-<)* 	-?zz&?\ o/2H2H2J2J&&(>? 3Ko>  9=SW   i >v  		 7 ;L  9=SW   a  (@-c!fX6	T9t4 a&,,.3  ,
 333  ,
   >>?SB  S  .
 SSS  .
    ' 2236 ''(c+  uY
 $- -?zz&?? o/2H2H2J2J&&(>? 9=SW   	 3Ko>Y(@X o/2H2H2J2J&&(>? 9=SW   	 3Ko>s  ;[;,R= +[;;[;%Q7[;;DR= Q2C"R= 6AQ5 R= R= *R+:R= %R
&;R= "AR= 2[;[;%R>[;Q/Q*%[;*Q//[;2R= 5R R= RR= 
R= R:R50[;5R::[;=Y4	AY/*Y/Y/Y/0Y/AY/*Y4+Y7 /[;?[;%X<;[;<Y)Y$[;$Y))[;/Y44Y7 7[8[8%[[8[2[-([8-[22[88[;c                $    V ^8  d   QhR\         /# r   )r.   )r   s   "r   r   r     s      4 r   c                 P     ^ RI Hp  V ! 4       w  rpVRJ#   \         d     R# i ; i)zACheck if the configured runtime vision path can resolve a client.)resolve_vision_provider_clientNF)agent.auxiliary_clientr   r&   )r   	_providerr   _models       r   check_vision_requirementsr     s4    I$B$D!	6T!! s    %%c                F    V ^8  d   QhR\         \        \        3,          /# r   )r   r-   r   )r   s   "r   r   r     s     % %S#X %r   c                 *    \         P                  4       # )z
Get information about the current debug session.

Returns:
    Dict[str, Any]: Dictionary containing debug session information
)r   get_session_infor   r   r   get_debug_session_infor     s     ""$$r   __main__u   👁️ Vision Tools Moduleu'   ❌ No auxiliary vision model availablezvConfigure a supported multimodal backend (OpenRouter, Nous, Codex, Anthropic, or a custom OpenAI-compatible endpoint).u   ✅ Vision model availableu#   🛠️ Vision tools ready for use!u&   🐛 Debug mode ENABLED - Session ID: z:   Debug logs will be saved to: ./logs/vision_tools_debug_z.jsonu@   🐛 Debug mode disabled (set VISION_TOOLS_DEBUG=true to enable)z
Basic usage:z.  from vision_tools import vision_analyze_toolz  import asyncior   z  async def main():z)      result = await vision_analyze_tool(z4          image_url='https://example.com/image.jpg',z6          user_prompt='What do you see in this image?'z      )z      print(result)z  asyncio.run(main())z
Example prompts:z0  - 'What architectural style is this building?'z2  - 'Describe the emotions and mood in this image'z+  - 'What text can you read in this image?'z)  - 'Identify any safety hazards visible'z(  - 'What products or brands are shown?'z
Debug mode:z  # Enable debug loggingz   export VISION_TOOLS_DEBUG=truez<  # Debug logs capture all vision analysis calls and resultsz6  # Logs saved to: ./logs/vision_tools_debug_UUID.json)registrynamevision_analyzedescriptionz}Analyze images using AI vision. Provides a comprehensive description and answers a specific question about the image content.r   r   object
propertiesrV   stringz5Image URL (http/https) or local file path to analyze.questionzYour specific question or request about the image to resolve. The AI will automatically provide a complete image description AND answer your specific question.requiredc                t    V ^8  d   QhR\         \        \        3,          R\        R\        \        ,          /# )r   argskwr   )r   r-   r   r   )r   s   "r   r   r   S  s-     > >c3h >s >y~ >r   c                     V P                  R R4      pV P                  RR4      pRV 2p\        P                  ! RR4      P                  4       ;'       g    Rp\	        W$V4      # )rV   r   r   z]Fully describe and explain everything about this image, then answer the following question:

AUXILIARY_VISION_MODELN)r%   r    r!   r"   r   )r   r  rV   r   full_promptr   s   &,    r   _handle_vision_analyzer  S  sg    b)Ixx
B'H	""*	-  II.399;CCtEyu==r   r   Tu   👁️)r   toolsetschemahandlercheck_fnis_asyncemoji)   r   z(========================================)/__doc__r   r   loggingr    r   pathlibr   typingr   r   r   r   urllib.parser   ru   r   r   r	   tools.debug_helpersr
   tools.website_policyr   	getLogger__name__r{   r   r*   rw   r7   rT   r   r   r   r   r   r   printapi_availableexitactive
session_idtools.registryr   VISION_ANALYZE_SCHEMAr  registerr   r   r   <module>r     s  :    	   1 1 !  O , 5			8	$	n.B	C
" 56 >,Xv3.4fR% z 

'(	(O ./M78  G  	HQ*+	
/0 }}}6v7H7H6IJKJ6K\K\J]]bcdPQ	
	
:;	
	"I	
 	
56	
@A	
BC	)	
 	
!"	
	
<=	
>?	
78	
56	
45	/	
$%	
,-	
HI	
BC $   SV    A	
 	[*- (> 	  	 "&
r   