
    Ki{V              	       X   d 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 ddlm	Z	m
Z
mZmZ ddl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 ed	d
          ZdefdZ e            ZdedefdZ dedee         fdZ!dTdedede"defdZ#dedefdZ$dUdedee         defdZ%	 dUdedededefdZ&defdZ'deee	f         fdZ(edk    r	  e)d             e)d!            e'            Z*e*s" e)d"            e)d#            e+d$           n e)d%            e)d&           ej,        r( e)d'ej-                     e)d(ej-         d)           n e)d*            e)d+            e)d,            e)d-            e)d.            e)d/            e)d0            e)d1            e)d2            e)d3            e)d4            e)d5            e)d6            e)d7            e)d8            e)d9            e)d:            e)d;            e)d<            e)d=            e)d>            e)d?            e)d@           ddAl.m/Z/ dBdCdDdEdFdGdEdHdGdIddJgdKdLZ0dMeee	f         dNe	de
e         fdOZ1 e/j2        dBdPe0e1e'dQdRS           dS )VaV  
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_varreturnc                  x   t          j        dd                                          } | r 	 t          |           S # t          $ r Y nw xY w	 ddlm}  |            }|                    di                               di                               d          }|t          |          S n# t          $ r Y nw xY wdS )	NHERMES_VISION_DOWNLOAD_TIMEOUT r   load_config	auxiliaryvisiondownload_timeoutg      >@)	osgetenvstripfloat
ValueErrorhermes_cli.configr   get	Exception)env_valr   cfgvals       //home/ubuntu/hermes-agent/tools/vision_tools.py_resolve_download_timeoutr%   3   s    i8"==CCEEG 	>>! 	 	 	D	111111kmmggk2&&**8R88<<=OPP?::    4s"   : 
AAAB* *
B76B7urlc                     | rt          | t                    sdS |                     d          s|                     d          sdS t          |           }|j        sdS ddlm}  ||           sdS dS )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://r   is_safe_urlT)
isinstancestr
startswithr   netloctools.url_safetyr)   )r&   parsedr)   s      r$   _validate_image_urlr0   G   s      jc** u NN9%% 
)C)C u c]]F= u -,,,,,;s u4    
image_pathc                 L   |                      d          5 }|                    d          }ddd           n# 1 swxY w Y   |                    d          rdS |                    d          rdS |                    d          rd	S |                    d
          rdS t          |          dk    r|dd         dk    r|dd         dk    rdS | j                                        dk    r7|                     dd          dd                                         }d|v rdS dS )z>Return a MIME type when the file looks like a supported image.rb@   Ns   PNG

	image/pngs   
image/jpeg)s   GIF87as   GIF89a	image/gifs   BM	image/bmp      s   RIFF   s   WEBP
image/webp.svgzutf-8ignore)encodingerrorsi   z<svgimage/svg+xml)openreadr,   lensuffixlower	read_text)r2   fheaderheads       r$   _detect_image_mime_typerL   f   sy   			 !               -.. {)) |/00 { {
6{{bVBQBZ722vad|w7N7N|  F**##WX#FFuuMSSUUT>>"?4s   8<<   	image_urldestinationmax_retriesc           
      0  K   ddl }|j                            dd           d }d}t          |          D ]}	 t	          |           }|rt          |d                   t          j        t          dd|gi          4 d{V 	 }|	                    | d	d
d           d{V }	|	
                                 t          |	j                  }
t	          |
          }|rt          |d                   |                    |	j                   ddd          d{V  n# 1 d{V swxY w Y   |c S # t          $ r}|}||dz
  k     rtd|dz   z  }t                               d|dz   |t          |          dd                    t                               d|           |                    |           d{V  n3t                               d|t          |          dd         d           Y d}~d}~ww xY w|t)          d| d          |)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
    r   NT)parentsexist_okc                    K   | j         rC| j        r>t          | j        j                  }ddlm}  ||          st          d|           dS dS dS )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.
        r   r(   z.Blocked redirect to private/internal address: N)is_redirectnext_requestr+   r&   r.   r)   r   )responseredirect_urlr)   s      r$   _ssrf_redirect_guardz-_download_image.<locals>._ssrf_redirect_guard   s        	H$9 	x4899L444444;|,,  S\SS  		 	 	 	 r1   messagerW   )timeoutfollow_redirectsevent_hookszoMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36zimage/*,*/*;q=0.8)z
User-AgentAccept)headers      z)Image download failed (attempt %s/%s): %s2   zRetrying in %ss...z+Image download failed after %s attempts: %sd   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)rN   rO   rP   rg   rY   
last_errorattemptblockedclientrW   	final_urle	wait_times                r$   _download_imager~   |   sD      NNN TD999    J%% - -,	*955G :%gi&8999
 (0!%'*>)?@   : : : : : : : : !' 'X"5  ", " "       ))+++--	.y99 >)')*<=== ''(8999): : : : : : : : : : : : : : : : : : : : : : : : : : :,  	 	 	Jq(('A+.	JGVWKYdfijkflflmpnpmpfqrrr3Y???mmI..........AFF4C4L!	    	 _Q\___
 
 	
 s>   AD7B	D"D7"
D,	,D7/D,	0D77
G>B2G99G>c                 t    | j                                         }dddddddd}|                    |d          S )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)
    r7   r6   r8   r9   r=   rB   ).jpgz.jpegz.pngz.gifz.bmpz.webpr>   )rF   rG   r   )r2   	extension
mime_typess      r$   _determine_mime_typer      sO     !''))I J >>)\222r1   	mime_typec                     |                                  }t          j        |                              d          }|pt	          |           }d| d| }|S )a0  
    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   )r2   r   dataencodedmimedata_urls         r$   _image_to_base64_data_urlr      sf       ""D t$$++G44G 8,Z88D /t..W..HOr1   user_promptmodelc                   K   | t          |          dk    r|dd         dz   n||dddd|dd}d}d}d}	 dd	lm}  |            rt          j        dd
d          |rz|ry|                                rf	 |                                 t                              d           S # t          $ r'}t          
                    d|d           Y d}~S d}~ww xY wS S S t                              d| dd                    t                              d|dd                    t          t          j                            |                     }	|	                                r t                              d|            |	}d}nt#          |           rt%          |           }
|
rt'          |
d                   t                              d           t          d          }|dt)          j                     dz  }t-          | |           d{V  d}nt/          d          |                                j        }|dz  }t                              d|           t5          |          }|st/          d          t                              d           t7          ||          }t          |          dz  }t                              d|           ||d <   |}d!d"|d#d$d%|id&gd'g}t                              d(           d)}	 dd*lm}  |            }|                    d+i                               d,i                               d-          }|t?          |          }n# t          $ r Y nw xY wd,|d.d/|d0}|r||d1<   tA          dFi | d{V }tC          |          }|s;t          
                    d2           tA          dFi | d{V }tC          |          }t          |          }t                              d3|           d|pd4d5}d|d6<   ||d7<   tD          #                    d8|           tD          $                                 t          j        |d9d:          |rz|ry|                                rf	 |                                 t                              d           S # t          $ r'}t          
                    d|d           Y d}~S d}~ww xY wS S S # t          $ rw}d;tK          |           }t          &                    d<|d           tK          |          '                                tQ          fd=d>D                       rd?| }n(tQ          fd@dAD                       r| dB| }ndC| }d||dD}||dE<   tD          #                    d8|           tD          $                                 t          j        |d9d:          cY d}~|rz|ry|                                rf	 |                                 t                              d           S # t          $ r'}t          
                    d|d           Y d}~S d}~ww xY wS S S d}~ww xY w# |rz|ry|                                rf	 |                                 t                              d           w # t          $ r'}t          
                    d|d           Y d}~w d}~ww xY ww w w xY w)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.)
       Nz...)rN   r   r   Fr   )
parametersru   successanalysis_length
model_usedimage_size_bytesT)is_interruptedInterrupted)r   ru   zCleaned up temporary image filez#Could not delete temporary file: %srd   zAnalyzing image: %s<   zUser prompt: %src   zUsing local image file: %srZ   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)r   usertext)typer   rN   r&   )r   rN   )rolerq   z%Processing image with vision model...g      ^@r   r   r   r[   g?i  )taskmessagestemperature
max_tokensr[   r   z0Vision LLM returned empty content, retrying oncez(Image analysis completed (%s characters)zIThere was a problem with the request and the image could not be analyzed.)r   analysisr   r   vision_analyze_toolra   )indentensure_asciizError analyzing image: z%sc              3       K   | ]}|v V  	d S N .0hinterr_strs     r$   	<genexpr>z&vision_analyze_tool.<locals>.<genexpr>  s7        4tw      r1   )402insufficientzpayment requiredcreditsbillingzhInsufficient credits or payment required. Please top up your API provider account and try again. Error: c              3       K   | ]}|v V  	d S r   r   r   s     r$   r   z&vision_analyze_tool.<locals>.<genexpr>  s7        T      r1   )zdoes not supportznot support imageinvalid_requestcontent_policyrN   
multimodalzunrecognized request argumentzimage inputzO 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   ru   r   ru   r   ))rE   tools.interruptr   jsondumpsexistsunlinkrr   debugr    rs   infor   r   path
expanduseris_filer0   r   rk   uuiduuid4r~   r   statst_sizerL   r   r   r   r   r   r	   r
   _debuglog_callsaver+   ru   rG   any)rN   r   r   debug_call_datatemp_image_pathshould_cleanupdetected_mime_typer   cleanup_error
local_pathry   temp_dirr   image_size_kbimage_data_urldata_size_kbcomprehensive_promptr   vision_timeoutr   _cfg_vtcall_kwargsrW   r   r   resultr|   	error_msgr   s                                @r$   r   r     sf	     N #8;K8H8H38N8N;tt,u44T_
 

  O O Nn222222> 	J:%-HHIIH  	o 	/2H2H2J2J 	&&(((>????   9=SW         		 	 	E 	)9SbS>:::%{4C4'8999 "',,Y7788
 	KK4i@@@(O"NN ++ 	*955G :%gi&8999KK7888233H&)Itz||)I)I)IIO!)_=========!NN]  
 +//119(4/+];;;4_EE! 	YWXXX 	34442?N`aaa>**T19<HHH.>*+  +
  !' 4 
 !,!>&  
$ 	;<<<
 	555555;==D((;++//"==AA)LLC!&s 	 	 	D	  %
 
  	)#(K '66+66666666 099  	>NNMNNN+::k::::::::H3H==Hh-->PPP  o$o
 

 &*	"-<)* 	-???z&???\  	o 	/2H2H2J2J 	&&(((>????   9=SW         		 	 	Y  (@ (@ (@6c!ff66	T9t444 a&&,,..     ,
      	B>?B B H      .
      	  6 6236 6 H+'(+ +   
 
 $- -???z&????????  	o 	/2H2H2J2J 	&&(((>????   9=SW         		 	 	Y(@X  	o 	/2H2H2J2J 	&&(((>????   9=SW         		 	 	s   &S( 7.B&&
C0CCH*S( AM' &S( '
M41S( 3M44C5S( .R11
S";SS"(Y*3C.Y%!Y*"Y- ?.X..
Y8YY%Y**Y- -[-.Z65[-6
[' ["[-"[''[-c                  V    	 ddl m}   |             \  }}}|duS # t          $ r Y dS w xY w)zACheck if the configured runtime vision path can resolve a client.r   )resolve_vision_provider_clientNF)agent.auxiliary_clientr   r    )r   	_providerrz   _models       r$   check_vision_requirementsr     s[    IIIIII$B$B$D$D!	66T!!   uus    
((c                  4    t                                           S )z
    Get information about the current debug session.
    
    Returns:
        Dict[str, Any]: Dictionary containing debug session information
    )r   get_session_infor   r1   r$   get_debug_session_infor     s     ""$$$r1   __main__u   👁️ Vision Tools Modulez(========================================u'   ❌ No auxiliary vision model availablezvConfigure a supported multimodal backend (OpenRouter, Nous, Codex, Anthropic, or a custom OpenAI-compatible endpoint).r`   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vision_analyzez}Analyze images using AI vision. Provides a comprehensive description and answers a specific question about the image content.objectstringz5Image URL (http/https) or local file path to analyze.)r   descriptionzYour specific question or request about the image to resolve. The AI will automatically provide a complete image description AND answer your specific question.)rN   questionr   )r   
propertiesrequired)namer   r   argskwc                     |                      dd          }|                      dd          }d| }t          j        dd                                          pd }t	          |||          S )NrN   r   r   z]Fully describe and explain everything about this image, then answer the following question:

AUXILIARY_VISION_MODEL)r   r   r   r   r   )r   r   rN   r   full_promptr   s         r$   _handle_vision_analyzer   S  sv    b))Ixx
B''H	-"*	- 	-  I.3399;;CtEy+u===r1   r   Tu   👁️)r   toolsetschemahandlercheck_fnis_asyncemoji)rM   r   )3__doc__r   r   loggingr   r   pathlibr   typingr   r   r   r   urllib.parser   rl   r   r	   r
   tools.debug_helpersr   tools.website_policyr   	getLogger__name__rr   r   r   r%   rn   r+   boolr0   rL   intr~   r   r   r   r   r   printapi_availableexitactive
session_idtools.registryr   VISION_ANALYZE_SCHEMAr   registerr   r1   r$   <module>r     s   :    				        1 1 1 1 1 1 1 1 1 1 1 1 ! ! ! ! ! !  O O O O O O O O , , , , , , 5 5 5 5 5 5		8	$	$	n.B	C	C	C
5    " 5466 S T    > #    ,X XS Xt X# XVZ X X X Xv3T 3c 3 3 3 3. $ 8C= TW    : f fff f 		f f f fR4    %S#X % % % % z 
E
'(((	E(OOO .-//M ,7888  G  	H  	H  	HQ*+++	E
/000 } RJv7HJJKKKc6K\cccddddPQQQ	E
	E
:;;;	E
	E"III	E
   	E
5666	E
@AAA	E
BCCC	E)	E
   	E
!"""	E
	E
<===	E
>???	E
7888	E
5666	E
4555	E/	E
$%%%	E
,---	E
HIII	E
BCCC $ # # # # #  S !V 
 !  A 	
 	
 !*-   (>c3h >s >y~ > > > >  	 "&
     r1   