
    Jjg1                       d 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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 dd
lmZ ddlZ ej        e          Z  edd          Z!de"fdZ# e#            Z$dZ%ddl&Z&de'fdZ(de'fdZ) e)            Z* ee*d          Z+d Z,de-de.fdZ/de-de.fdZ0de-de.fdZ1de2dee-         fdZ3 e4h d          Z5dedede.fdZ6d ed!e-de7ee         ee-         ee-         f         fd"Z8d#e9de.fd$Z:dd&e-d'ed(e'defd)Z;d ede-fd*Z<dd ed+ee-         de-fd,Z=d-Z>d.Z?d/Z@d0ZAd#e9de.fd1ZBd ed2e'de.fd3ZCdeAdfd ed+ee-         d4e'd2ee'         de-f
d5ZDd6e-d7e-de.fd8ZEde.fd9ZFd&e-d:e-d;e-d<e'dee-ef         f
d=ZGejH        d>             ZI	 dd&e-d:e-d?ee-         defd@ZJ	 	 dd&e-dAe-d7e-d?ee-         de-f
dBZKde.fdCZLedDk    r	  eMdE            eMdF            eL            ZNeNs' eMdG            eMdH            ejO        dI           n eMdJ            eMdK           e!jP        r( eMdLe!jQ                     eMdMe!jQ         dN           n eMdO            eMdP            eMdQ            eMdR            eMdS            eMdT            eMdU            eMdV            eMdW            eMdX            eMdY            eMdZ            eMd[            eMd\            eMd]            eMd^            eMd_            eMd`            eMda            eMdb            eMdc            eMdd            eMde           ddflRmSZSmTZT dgdhdidjdkdldjdmdldnd&d:gdodpZUdqee-ef         drede-fdsZV eSjW        dgdteUeVeLdudvw           dxdydzdxdxd{d{d|ZXdZYd-ZZd}edee-         fd~Z[dd}ed+ee-         de-fdZ\dde-d'ed(e'defdZ]	 dde-dAe-d7e-de-fdZ^dddidjddldjddlddd:gdodpZ_dqee-ef         dredee-         fdZ` eSjW        dde_e`eLdudw           dS )aV  
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)ThreadPoolExecutor)Path)Any	AwaitableDictOptional)urlparse)async_call_llmextract_content_or_reasoning)get_hermes_dir)DebugSession)check_website_accessvision_toolsVISION_TOOLS_DEBUG)env_varreturnc                     t          j        dd                                          } | r 	 t          |           S # t          $ r Y nw xY w	 ddlm}m}  |            } ||ddd          }|t          |          S n# t          $ r Y nw xY wdS )	NHERMES_VISION_DOWNLOAD_TIMEOUT r   cfg_getload_config	auxiliaryvisiondownload_timeoutg      >@)	osgetenvstripfloat
ValueErrorhermes_cli.configr   r   	Exception)env_valr   r   cfgvals        8/home/rurouni/.hermes/hermes-agent/tools/vision_tools.py_resolve_download_timeoutr'   8   s    i8"==CCEEG 	>>! 	 	 	D	::::::::kmmgc;2DEE?::    4s!   : 
AA0A= =
B
	B
i   c                      	 t          dt          t          j        d                              S # t          t
          f$ r& t          dt          j                    pd          cY S w xY w)u  Best-effort host CPU count, honoring cgroup/affinity limits when set.

    Prefers ``os.sched_getaffinity`` (the CPUs this process may actually run
    on — respects container/cpuset pinning) and falls back to
    ``os.cpu_count()``. Returns at least 1.
       r   )maxlenr   sched_getaffinityAttributeErrorOSError	cpu_count     r&   _detect_host_cpusr2   p   sk    +1c".q1122333G$ + + +1blnn)*****+s   .1 4A('A(c                  X   t          j        dd                                          } | r)	 t          |           }|dk    r|S n# t          $ r Y nw xY w	 ddlm}m}  |            } ||ddd          }|t          |          }|dk    r|S n# t          $ r Y nw xY wt                      S )	u  Resolve how many vision encode/resize bursts may run concurrently.

    Defaults to the host's usable core count (``_detect_host_cpus``) — no fixed
    ceiling, because the cap tracks the actual exhausted resource (CPU cores),
    not a magic number. The LLM call is NOT covered by this limit, so legitimate
    multi-image fan-out keeps full request concurrency; only the simultaneous
    CPU bursts are bounded so the event loop always keeps a core.

    Resolution order: HERMES_VISION_MAX_CONCURRENCY env →
    config.yaml auxiliary.vision.max_concurrency → host core count. Any value
    that parses to < 1 is ignored in favor of the next source so the cap can
    never be disabled into an unbounded encode storm.
    HERMES_VISION_MAX_CONCURRENCYr   r)   r   r   r   r   max_concurrency)
r   r   r   intr    r!   r   r   r"   r2   )r#   parsedr   r   r$   r%   s         r&   _resolve_vision_cpu_workersr8   }   s     i7<<BBDDG 	\\F{{  	 	 	D		::::::::kmmgc;2CDD?XXF{{   s#   A 
AA8B 
BBzvision-encode)max_workersthread_name_prefixc                    K   ddl }t          j                    }|                    t           |j        | g|R i |           d{V S )u  Run a sync encode/resize callable on the bounded vision CPU executor.

    Offloads CPU-bound image work to :data:`_vision_cpu_executor` so it (a)
    never runs on the caller's event-loop thread and (b) is bounded to the
    host's usable core count process-wide. Excess encodes queue on the
    executor instead of all running at once, leaving cores free for the loop.
    The LLM call must NOT be routed through here — only the encode/resize.
    r   N)	functoolsasyncioget_running_looprun_in_executor_vision_cpu_executorpartial)fnargskwargsr<   loops        r&   _run_encode_on_cpu_executorrF      s{       #%%D%%/i/DTDDDVDD        r1   urlc                     | rt          | t                    sdS |                     d          sdS t          |           }|j        sdS dS )z2HTTP(S) shape check only (scheme, netloc). No DNS.F)zhttp://zhttps://T)
isinstancestr
startswithr	   netloc)rG   r7   s     r&   _image_url_shape_okrM      s[     jc** u>>122 u c]]F= u4r1   c                 F    t          |           sdS ddlm}  ||           S )zHValidate image URL for sync callers and tests (SSRF via sync DNS check).Fr   )is_safe_url)rM   tools.url_safetyrO   )rG   rO   s     r&   _validate_image_urlrQ      s9    s## u,,,,,,;sr1   c                 V   K   t          |           sdS ddlm}  ||            d{V S )zAValidate remote image URL without blocking the event loop on DNS.Fr   )async_is_safe_urlN)rM   rP   rS   )rG   rS   s     r&   _validate_image_url_asyncrT      sP      s## u222222""3'''''''''r1   datac                 4   | dd         }|                     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 dS )u2  Magic-byte MIME sniff on raw bytes (authoritative; no extension trust).

    Returns ``None`` for anything without a recognized image header — including
    SVG, which has no magic bytes. The resolver special-cases SVG (sniffs
    ``<svg``) and passes it through for rasterization at the call sites.
    N@   s   PNG

	image/pngs   
image/jpeg)s   GIF87as   GIF89a	image/gifs   BM	image/bmp      s   RIFF   s   WEBP
image/webp)rK   r+   )rU   headers     r&   "_detect_image_mime_type_from_bytesra      s     #2#YF-.. {)) |/00 { {
6{{bVBQBZ722vad|w7N7N|4r1   >   rZ   rX   rY   r_   svg_pathout_pathc                    	 ddl }|                    t          |           t          |                     |                                o|                                j        dk    S # t          $ r Y nw xY w	 ddlm} ddl	m
}  |t          |                     }|V|                    |t          |          d           |                                o|                                j        dk    S n# t          $ r Y nw xY wddl}ddl}dd	t          |          t          |           gd
t          |           dd| gfD ]}|                    |d                   re	 |                    |ddd|j                   |                                r |                                j        dk    r dS r# t          $ r Y ~w xY wdS )u  Best-effort SVG → PNG rasterization. Returns True on success.

    Tries, in order: cairosvg, svglib+reportlab, then system rasterizers
    (rsvg-convert, inkscape).  All are soft dependencies; if none is available
    we return False and the caller rejects the image with an actionable error
    rather than embedding an unsupported media_type that would wedge the
    session.
    r   N)rG   write_to)svg2rlg)renderPMPNG)fmtzrsvg-convertz-oinkscapez--export-type=pngz--export-filename=T   )checkcapture_outputtimeoutstdinF)cairosvgsvg2pngrJ   existsstatst_sizer"   svglib.svglibrf   reportlab.graphicsrg   
drawToFileshutil
subprocesswhichrunDEVNULL)	rb   rc   rp   rf   rg   drawing_shutil_subprocesscmds	            r&   _rasterize_svg_to_pngr      s2   S]]S]]CCC  @X]]__%<q%@@   ))))))//////'#h--((XEBBB??$$D)@1)DD      $$$$	s8}}c(mm<	S]]$7	(h	(	(	*  
 ==Q   		tD"%-      ??$$  )@1)D)D44   		 5s8   A%A( (
A54A59A;C6 6
DD"AF66
GG
image_pathdetected_mimec                    |t           v r| |dfS t          dd          }|                    dd           |dt          j                     dz  }|dk    rt          | |          r|d	dfS d
S 	 ddlm} |                    |           5 }|j	        dvr|
                    d          }|                    |d           ddd           n# 1 swxY w Y   |                                r"|                                j        dk    r|d	dfS n3# t          $ r&}t                               d||           Y d}~nd}~ww xY wddd|dfS )u  Ensure an image is in a vision-provider-supported format.

    Returns a 3-tuple ``(path, mime, error)``:
      - If ``detected_mime`` is already supported: ``(image_path, detected_mime, None)``.
      - If conversion succeeds: ``(new_png_path, "image/png", None)`` — the new
        path is a temp file the CALLER must clean up.
      - If conversion is impossible: ``(None, None, <error message>)``.

    SVG is rasterized to PNG (best-effort, soft deps).  Other raster formats
    Pillow can read (BMP, TIFF, etc.) are re-encoded to PNG.  This runs BEFORE
    the image is base64-embedded into conversation history, so an unsupported
    media_type can never reach the provider and wedge the session.
    Ncache/visiontemp_vision_imagesTparentsexist_ok
converted_.pngimage/svg+xmlrX   )NNu8  This is an SVG, which vision models cannot read directly, and no SVG rasterizer is installed (tried cairosvg, svglib, rsvg-convert, inkscape). Convert the SVG to PNG first — e.g. open it in a browser and screenshot it, or install a rasterizer (`pip install cairosvg`) — then re-run vision_analyze on the PNG.r   Image)RGBRGBALr   rh   )formatz'Failed to normalize %s image to PNG: %szImage format z is not supported by the vision API and could not be converted to PNG (install Pillow for raster conversion). Convert it to PNG or JPEG and try again.) _ANTHROPIC_SUPPORTED_MEDIA_TYPESr   mkdiruuiduuid4r   PILr   openmodeconvertsaverr   rs   rt   r"   loggerwarning)r   r   out_dirrc   	_PILImage_img_excs          r&   _normalize_to_supported_imager   /  s     888=$..^-ABBGMM$M...8djll8888H '' X66 	/[$..
 

,******^^J'' 	.4y 444||F++IIhuI---	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. ?? 	/!81!<!<[$.. , , ,@$d	, 	, 	, 	, 	, 	, 	, 	,, 		A 	A 	A 	A s<   .D 	6C?D CD C8D 
D=D88D=errorc                     t          | t          t          f          rdS t          | t          j                  r&| j        j        }d|cxk    rdk     rn n|dk    rdS dS dS )u  Return True only for transient image-download failures worth retrying.

    Non-retryable (fail-fast):
      - httpx.HTTPStatusError with a 4xx status other than 429 (404/403/410/...):
        the resource is missing or forbidden; retrying can't change that.
      - PermissionError: blocked by website policy / SSRF guard.
      - ValueError: image too large or blocked redirect — deterministic.

    Retryable (transient):
      - httpx 429 (rate limited) and 5xx (server-side) errors.
      - Connection/timeout/transport errors (httpx.TransportError) and any
        other unclassified exception, which may be a flaky network blip.
    Fi  i  i  T)rI   PermissionErrorr    httpxHTTPStatusErrorresponsestatus_code)r   statuss     r&   _is_retryable_download_errorr   i  sx     %/:677 u%.// +&36S==5t4r1      	image_urldestinationmax_retriesc           
        K   ddl }|j                            dd           d }d}t          |          D ]s}	 t	          |           }|rt          |d                   t          j        t          dd|gi          4 d{V 	 }|	                    | d	d
d           d{V }	|	
                                 |	j        	                    d          }
|
r@t          |
          t          k    r(t          dt          |
           dt           d          t          |	j                  }t	          |          }|rt          |d                   |	j        }t%          |          t          k    r(t          dt%          |           dt           d          |                    |           ddd          d{V  n# 1 d{V swxY w Y   |c S # t(          $ r}|}t+          |          r	||dz
  k    r7t,                              d|dz   t          |          dd         d            d|dz   z  }t,                              d|dz   |t          |          dd                    t,                              d|            |j        |           d{V  Y d}~md}~ww xY w||t5          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   NTr   c                 ~   K   ddl m}m}  ||           }|r# ||           d{V st          d|           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   rS   redirect_target_from_responseN.Blocked redirect to private/internal address: rP   rS   r   r    r   rS   r   redirect_urls       r&   _ssrf_redirect_guardz-_download_image.<locals>._ssrf_redirect_guard  s       	VUUUUUUU44X>> 	&7&7&E&E E E E E E E 	OOO  	 	 	 	r1   messager   rn   follow_redirectsevent_hooksoMozilla/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content-lengthzImage too large ( bytes, max )r)   z-Image download failed after %s attempt(s): %sd   exc_info   z)Image download failed (attempt %s/%s): %s2   zRetrying in %ss...zB_download_image exited retry loop without attempting (max_retries=)r=   parentr   ranger   r   r   AsyncClient_VISION_DOWNLOAD_TIMEOUTgetraise_for_statusr   r6   _VISION_MAX_DOWNLOAD_BYTESr    rJ   rG   contentr+   write_bytesr"   r   r   r   r   sleepRuntimeError)r   r   r   r=   r   
last_errorattemptblockedclientr   cl	final_urlbodye	wait_times                  r&   _download_imager     s      NNN TD999   J%% ?+ ?+>	+*955G :%gi&8999
 (0!%'*>)?@    .  .  .  .  .  .  .  . !' 'X"5  ", " "       ))+++ %))*:;; #b''$>>>$^CGG^^A[^^^    --	.y99 >)')*<===  't99999$`CII``C]```   ''---A .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .D  	+ 	+ 	+J 022 gq6P6PCaKFF4C4L!	     gk*INNFRSU`befgbhbhiljlilbmnnnNN/;;;'-	**************'	+. 
[[[[[  s>   AGD'G .G 
G
	
GG
	G
J)B?J$$J)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)
    rY   rX   rZ   r[   r_   r   )z.jpgz.jpegr   z.gifz.bmpz.webpz.svg)suffixlowerr   )r   	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,...")
    asciidata:;base64,)
read_bytesbase64	b64encodedecoder   )r   r   rU   encodedmimedata_urls         r&   _image_to_base64_data_urlr     sf       ""D t$$++G44G 8,Z88D /t..W..HOr1   i  @i  @ i  i  P c                 |    t          |                                           t          fddD                       S )z;Detect if an API error is related to image or payload size.c              3       K   | ]}|v V  	d S Nr0   .0hinterr_strs     r&   	<genexpr>z'_is_image_size_error.<locals>.<genexpr>@  s7        4tw      r1   )		too largepayload413content_too_largerequest_too_larger   invalid_requestexceeds
size limit)rJ   r   any)r   r   s    @r&   _is_image_size_errorr  =  sT    %jj  G     ,      r1   max_dimensionc                     	 ddl m} |                    |           5 }t          |j                  |k    cddd           S # 1 swxY w Y   dS # t
          $ r Y dS w xY w)u  True if the image's longest side exceeds ``max_dimension`` px.

    Anthropic enforces an 8000px per-side cap independently of the 5 MB byte
    cap, so a tall small-byte screenshot can pass every byte check yet trip a
    non-retryable 400.  Returns False (don't force a resize) when Pillow is
    unavailable or the file can't be read as an image — the byte-based checks
    still apply, and we never want a missing soft dependency to break the
    embed path.
    r   r   NF)r   r   r   r*   sizer"   )r   r  r   r   s       r&   _image_exceeds_dimensionr  G  s    ******^^J'' 	24ty>>M1	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2   uus3   A AA AA 	A
A 
AAmax_base64_bytesc           	      	   |                                  j        }|dz  dz  dz   }||k    }d}_	 ddlm} |                    |           5 }	t          |	j                  k    rd}ddd           n# 1 swxY w Y   n# t          $ r Y nw xY w|s)|s't          | |	          }
t          |
          |k    r|
S nd}
	 ddlm} ddl
}np# t          $ rc 	 dd
lm}  |dd           ddlm} ddl
}nA# t          $ r4 t                              d           |
t          | |	          }
|
cY cY S w xY wY nw xY wt                              d|dz  |dz  |dz             |pt!          |           }|dk    rdnd}|dk    rdnd}	 |                    |           }nG# t          $ r:}t                              d|           |
t          | |	          }
|
cY d}~S d}~ww xY w|dk    r|j        dv r|                    d          }|dk    rdnd}|j        |j        f}d}dt*          dt*          dt,          ffd}t/          d          D ]}|dk    rd}t          t+          |j        |z            d          }t          t+          |j        |z            d          }|dk    r;|j        dk    r0d|j        z  }t          t+          |j        |z            d          }n@|dk    r:|j        dk    r/d|j        z  }t          t+          |j        |z            d          }||f|k    r n|                    ||f|j                  }||f}t                              d |||           |D ]}|                                }d!|i}|||d"<    |j        |fi | t9          j        |                                                              d#          }d$| d%| }t          |          |k    rT ||j        |j                  r>t                              d&t          |          dz  ||j        |j                   |c c S ؐ|1t                               d'|dz  t          |          dz             |S |
pt          | |	          S )(aU  Convert an image to a base64 data URL, auto-resizing if too large.

    Tries Pillow first to progressively downscale oversized images.  If Pillow
    is not installed or resizing still exceeds the limit, falls back to the raw
    bytes and lets the caller handle the size check.

    Args:
        max_dimension: If set, images whose longest side exceeds this pixel
            count are forcibly downscaled even if they're under the byte
            budget.  Anthropic enforces an 8000 px per-side cap independently
            of the 5 MB byte cap.

    Returns the base64 data URL string.
    r]   r   r   FNr   r   Tr   )ensureztool.vision)promptu;   Pillow not installed — cannot auto-resize oversized imagezcImage file is %.1f MB (estimated base64 %.1f MB, limit %.1f MB, max_dimension=%s), auto-resizing...   rX   rh   JPEGrY   z)Pillow cannot open image for resizing: %s>   Pr   r   )U   F   r   r   whr   c                 4    dS t          | |          k    S )z3True if both pixel dimensions are within the limit.NT)r*   )r  r  r  s     r&   _dims_okz*_resize_image_for_vision.<locals>._dims_ok  s!     41ayyM))r1      g      ?rW   zResized to %dx%d (attempt %d)r   qualityr   r   r   z4Auto-resized image fits: %.1f MB (quality=%s, %dx%d)z=Auto-resize could not fit image under %.1f MB (best: %.1f MB))!rs   rt   r   r   r   r*   r  r"   r   r+   ioImportErrortools.lazy_depsr  r   infor   r   r   widthheightr6   boolr   resizeLANCZOSBytesIOr   r   r   getvaluer   r   ) r   r   r  r  	file_sizeestimated_b64needs_resize_for_bytesneeds_resize_for_dims	_PILQuick
_quick_imgr   r   _io_ensure_depr   
pil_formatout_mimeimgexcquality_steps	prev_dims	candidater  r   scalenew_wnew_heffective_scaleqbufsave_kwargsr   s       `                            r&   _resize_image_for_visionr9  Y  s   & !!)I]q(3.M*-== " 	......
++ 1zz''-77,0)1 1 1 1 1 1 1 1 1 1 1 1 1 1 1  	 	 	D	 " *? ,Z9MMMx==,,,O -    
	======
 Ke4444!!!!!! 	 	 	KKUVVV4Z9UUUOOOOO		 ( KKu[)=K+H K0-A A A 8,Z88D+--6J(E11{{|Hjj$$   ?EEE0yQQQH	 VM 9 9kk%   %/&$8$8LLgMCJ'II*C *C *D * * * * * * 88 "! "!Q;; ECI-..33ECJ.//44E {{sy1}}"$sy.C
_ <==rBB"a"$sz/C	O ;<<bAAu~****eU^U];;CIKK7wOOO 	! 	!A++--C#Z0K})*I&CHS((K(((&s||~~66==gFFG;;;';;I9~~!111hhsy#*6U6U1R	NNk:AIsz3 3 3 !     	!  V';7Y;9W	Y 	Y 	Y Q0yQQQQs   A? A3'A? 3A77A? :A7;A? ?
BB=
C 
D5C10D519D/*D5.D//D54D5F 
G%/GGGprovidermodelc                    t          | t                    sdS |                                                                 }|sdS h d}||v rdS |dv rdS |dv rdS |dv rMt          |t                    sdS |                                                                }d|v sd|v sd	|v rdS dS 	 d
dlm}  ||          }|	|j        rdS n# t          $ r Y nw xY wdS )u  Whether the given provider+model combination accepts image content
    inside a tool-result message.

    Providers covered today (per spec docs verified Apr-2026):

      * Anthropic Messages API (``anthropic`` provider, plus aggregators that
        proxy Claude — ``openrouter``, ``nous``, ``vertex``, ``bedrock``):
        ``tool_result`` blocks accept ``image`` content blocks.
      * OpenAI Chat Completions: tool messages accept array content with
        ``image_url`` parts.
      * OpenAI Responses (``openai-codex``): ``function_call_output.output``
        accepts an array of ``input_text``/``input_image`` items.
      * Gemini 3 (and proxied via aggregators): supports multimodal tool
        results. Older Gemini does NOT.

    For unknown / legacy providers we conservatively return False — the
    caller falls back to the legacy aux-LLM text path.  The check is relaxed
    when the provider's ``ProviderProfile`` declares ``supports_vision=True``.
    F>   google-vertexanthropic-vertexnousvertexbedrock
openrouterT>   anthropic-directclaude	anthropic>   openai-chatazure-openaiopenai-codexopenai>   google-geminigoogle-vertex-geminigeminigooglezgemini-3zgemini-pro-3zgemini-flash-3r   )get_provider_profile)rI   rJ   r   r   	providersrN  supports_visionr"   )r:  r;  p_AGGREGATORSmrN  profiles          r&   _supports_media_in_tool_resultsrU    sT   ( h$$ u  A u  L 	Lt 	777t 	EEEt 	III%%% 	5KKMM!!??n115E5J5J4u
222222&&q))7#:4    5s   *C 
CCc                  6   	 ddl m} m} ddlm}m} ddlm}  |             } |            } |            } ||||          dk    rdS t          ||          p ||||          du S # t          $ r&}t                              d|           Y d	}~dS d	}~ww xY w)
aH  Whether vision tools should attach the image to the main model directly
    instead of routing through the auxiliary vision LLM.

    True when image routing resolves to ``native`` AND either the provider is
    known to accept images inside tool results, or the user explicitly declared
    the model vision-capable via the ``model.supports_vision`` config override.
    The override is the escape hatch for custom/local providers that aren't in
    the static allowlist. Best-effort: any resolution failure returns False so
    the caller falls back to the legacy aux-LLM path.
    r   )_read_main_provider_read_main_model)decide_image_input_mode_lookup_supports_vision)r   nativeFTz(Native vision fast-path check failed: %sN)agent.auxiliary_clientrW  rX  agent.image_routingrY  rZ  r!   r   rU  r"   r   debug)	rW  rX  rY  rZ  r   r:  r;  r$   r.  s	            r&   #_should_use_native_vision_fast_pathr_  @  s   PPPPPPPPXXXXXXXX111111&&((  ""kmm""8UC88HDD5+He<< E&&x<<D	
    ?EEEuuuuus   AA( 	A( (
B2BBquestionimage_data_urlimage_size_bytesc                     d}t          |t                    r.|                                r|d|                                 z  }d|dz  dd}dd|d	d
d|idg|| dd         |dddS )a  Build the multimodal tool-result envelope returned by the fast path.

    Shape:
      {
        "_multimodal": True,
        "content": [
          {"type": "text", "text": "<short note + the user's question>"},
          {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
        ],
        "text_summary": "<plain-text fallback>",
        "meta": {"image_url": ..., "size_bytes": N},
      }

    The text part exists for two reasons: (1) it gives the model an
    instruction to act on now that the pixels are in context, and
    (2) providers that don't support multimodal tool results can fall back
    to ``text_summary``.
    ul   Image loaded into your context — you can see it natively now. Use your built-in vision to answer the user.z

Question: z,Image attached natively for the main model (   .1fz# KB). Answer using built-in vision.Ttexttyperf  r   rG   rh  r   N   )r   
size_bytesnative_vision)_multimodalr   text_summarymeta)rI   rJ   r   )r   r`  ra  rb  	text_partsummarys         r&    _build_native_vision_tool_resultrr  ^  s    8	7  (C   9X^^%5%5 98hnn&6&6888		(t#/	( 	( 	(  Y// ~/FGG
  "4C4*!
 
  r1   c                    K   dW V  dS )a  Deprecated no-op shim kept for backward compatibility.

    The fan-out cap was narrowed to the CPU-bound encode/resize burst only
    (see :data:`_vision_cpu_executor` / :func:`_run_encode_on_cpu_executor`).
    Holding a slot across the whole analysis serialized legitimate multi-image
    workflows behind the slow LLM call, which is exactly what we don't want.
    This context manager no longer gates anything; encode/resize is bounded
    where it actually runs. Retained only so any external caller importing it
    keeps working.
    Nr0   r0   r1   r&   _vision_concurrency_slotrt    s       
EEEEEEr1   task_idc                 	  K   t          | t                    r|                                 st          dd          S d}d}	 ddlm}  |            rQt          dd          |r=|<	 |                                r|                                 S S # t          $ r Y S w xY wS S ddl	m
}m}m} 	  ||  ||	                     d{V }	np# |$ rh}
t          t          |
          d          cY d}
~
|r=|<	 |                                r|                                 S S # t          $ r Y S w xY wS S d}
~
ww xY w|	j        }t          |	j                  }t!          d
d          }|                    dd           |dt%          j                     dz  }t)          j        |j        |	j                   d{V  d}t)          j        t.          ||           d{V \  }}}|s|St          |pdd          |r=|<	 |                                r|                                 S S # t          $ r Y S w xY wS S ||k    rY|r:|                                r&	 |                                 n# t          $ r Y nw xY w|}d}|                                j        }t5          t6          ||           d{V }t          |          t8          k    }t5          t:          |t<                     d{V }|s|rt5          t>          ||t8          t<                     d{V }t          |          t@          k    rrt          dt          |          dz  ddt@          dz  ddd          |r=|<	 |                                r|                                 S S # t          $ r Y S w xY wS S tC          | |||          |r=|<	 |                                r|                                 S S # t          $ r Y S w xY wS S # t          $ ry}
tD          #                    d|
           t          d|
 d          cY d}
~
|r=|<	 |                                r|                                 S S # t          $ r Y S w xY wS S d}
~
ww xY w# |r=|<	 |                                r|                                 w w # t          $ r Y w w xY ww w xY w)a.  Fast path for vision-capable main models.

    Loads the image (data: / http(s) / file:// / local path / sandbox-container
    path) via the unified resolver, base64-encodes it, and returns a multimodal
    tool-result envelope. The agent loop unwraps it; provider adapters serialize
    it into the right tool-result-with-image shape for each backend.

    Returns:
        A ``_multimodal`` envelope dict on success.
        A JSON error string on failure (matches the existing tool-result
        contract so the agent loop displays errors normally).
    zimage_url is requiredFsuccessNr   is_interruptedInterruptedImageResolutionErrorResolveContextresolve_image_sourceru  r   r   Tr   temp_image_.imgImage normalization failed.r
  )r   r  r  2Image too large for vision API: base64 payload is r  re   MB (limit .0fw MB) even after resizing. Install Pillow (`pip install Pillow`) for better auto-resize, or compress the image manually.)r   r`  ra  rb  z"Native vision fast path failed: %szNative vision failed: )$rI   rJ   r   
tool_errortools.interruptrz  rr   unlinkr"   tools.image_sourcer}  r~  r  r   r+   rU   r   r   r   r   r=   	to_threadr   r   rs   rt   rF   r   _EMBED_TARGET_BYTESr  _EMBED_MAX_DIMENSIONr9  _MAX_BASE64_BYTESrr  r   r   )r   r`  ru  temp_image_pathshould_cleanuprz  r}  r~  r  resolvedr.  detected_mime_typerb  temp_dirnormalized_path	_norm_errra  _over_bytes
_over_dimss                      r&   _vision_analyze_nativer    sb     " i%% BY__->-> B15AAAA&*ONj222222> 	<mU;;;D  	o9"))++ -#**,,,,-   		9y	
 	
 	
 	
 	
 	
 	
 	
 	
 	
	711)^^T[=\=\=\]]]]]]]]HH# 	7 	7 	7c#hh66666666f  	o9"))++ -#**,,,,-   		9i	7 &]x}--!.2FGGtd333"%E4:<<%E%E%EE ;X]KKKKKKKKK @G?P)?<N@
 @
 :
 :
 :
 :
 :
 :
6+Y  	/::E  @  	o9"))++ -#**,,,,-   		9{ o-- /"8"8":": #**,,,,    D-O!N.3355=:%'9 
  
  
 
 
 
 
 
 
 .)),??6$o7K
 
 
 
 
 
 
 

  	* 	#>(+=!42	$ $ $      N >""%666!7>**k:D7 7/;?J7 7 7 "  ,  	o9"))++ -#**,,,,-   		9 0)-	
 
 
  	o9"))++ -#**,,,,-   		9  I I I;SAAA8388%HHHHHHHH  	o9"))++ -#**,,,,-   		9I
  	o9"))++ -#**,,,,-   		9s(   O9 '(B
BB#
O9 .C 
O9 D8D3-D8.O9 7(D!!
D.-D.3D88CO9 >(H((
H54H5:O9 I, +O9 ,
I96O9 8I99C,O9 *(N
N! N!&O9 =(O''
O43O49
Q<.Q71Q<2Q? ;(Q%%
Q21Q27Q<<Q? ?S(R/-S/
R<9S;R<<Suser_promptc                 .  (K   t          |t                    s|t          |          nd}| t          |          dk    r|dd         dz   n||dddd|dd}d}d	}d}	 dd
lm}  |            rt          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                    ddlm}
m}m} 	  ||  ||                     d{V }n)# |
$ r!}t#          t          |                    d}~ww xY w|j        }t'          dd          }|                    d	d	           |dt+          j                     dz  }t/          j        |j        |j                   d{V  d	}t          |j                  }|dz  }t                              d|           t/          j        t6          ||           d{V \  }}}|s|t#          |pd          ||k    r@|r:|                                r&	 |                                 n# t          $ r Y nw xY w|}d	}t                              d           t9          t:          ||           d{V }t          |          dz  }t                              d |           t          |          t<          k    ret9          t>          ||           d{V }t          |          t<          k    r0t#          d!t          |          d"z  d#d$t<          d"z  d%d&          ||d'<   |}d(d)|d*d+d,|id-gd.g}t                              d/           d0}d1}	 dd2l m!}m"}  |            } ||d3d4i 5          }|#                    d6          }|tI          |          }|#                    d7          }|tI          |          }n# t          $ r Y nw xY wd4||d8|d9} |r|| d:<   	 tK          dTi |  d{V }!n# t          $ r}"tM          |"          rt          |          tN          k    rt                              d;t          |          d"z  tN          d"z             t9          t>          ||           d{V }||d         d<         d=         d+         d,<   tK          dTi |  d{V }!n Y d}"~"nd}"~"ww xY wtQ          |!          }#|#s;t                              d>           tK          dTi |  d{V }!tQ          |!          }#t          |#          }$t                              d?|$           d	|#pd@dA}%d	|dB<   |$|dC<   tR          *                    dD|           tR          +                                 tY          j-        |%dEdF          |rz|ry|                                rf	 |                                 t          	                    d           S # t          $ r'}	t                              d|	d	           Y d}	~	S d}	~	ww xY wS S S # t          $ r}&dGt          |&           }'t          .                    dH|'d	           t          |&          /                                (ta          (fdIdJD                       rdK|& }#n6ta          (fdLdMD                       r| dN|& }#ndO(v sd+(v rdP|& }#ndQ|& }#d|'|#dR}%|'|dS<   tR          *                    dD|           tR          +                                 tY          j-        |%dEdF          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)Ua  
    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 under $HERMES_HOME/cache/vision/ and cleaned up
        - For local file paths, the file is used directly and NOT deleted
        - Supports common image formats (JPEG, PNG, GIF, WebP, etc.)
    Nr   rj  ...)r   r  r;  Fr   )
parametersr   rx  analysis_length
model_usedrb  Try  r{  rw  zCleaned up temporary image file#Could not delete temporary file: %sr   zAnalyzing image: %s<   User prompt: %sr   r|  r  r   r   r   r  r  rd  zImage ready (%.1f KB)r  zConverting image to base64...r
  z#Image converted to base64 (%.1f KB)r  r  re  r  r  r  rb  userrf  rg  r   rG   ri  roler   z%Processing image with vision model...g      ^@皙?r   r   r   defaultrn   temperaturei  taskmessagesr  
max_tokensrn   r;  zYAPI rejected image (%.1f MB, likely too large); auto-resizing to ~%.0f MB and retrying...r   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.rx  analysisrx  r  vision_analyze_toolr   indentensure_asciizError analyzing image: %sc              3       K   | ]}|v V  	d S r   r0   r   s     r&   r   z&vision_analyze_tool.<locals>.<genexpr>  7        4tw      r1   402insufficientzpayment requiredcreditsbillinghInsufficient credits or payment required. Please top up your API provider account and try again. Error: c              3       K   | ]}|v V  	d S r   r0   r   s     r&   r   z&vision_analyze_tool.<locals>.<genexpr>!  s7        T      r1   )does not supportznot support imagecontent_policy
multimodalunrecognized request argumentzimage inputzO does not support vision or our request was not accepted by the server. Error: r   zThe vision API rejected the image. This can happen when the image is in an unsupported format, corrupted, or still too large after auto-resize. Try a smaller JPEG/PNG and retry. Error: zQThere was a problem with the request and the image could not be analyzed. Error: rx  r   r  r   r0   )1rI   rJ   r+   r  rz  r  rr   r  r   r^  r"   r   r  r  r}  r~  r  r    r   r   r   r   r   r=   r  r   rU   r   rF   r   r  r9  r!   r   r   r   r   r
   r  _RESIZE_TARGET_BYTESr   _debuglog_callr   jsondumpsr   r   r  ))r   r  r;  ru  debug_call_datar  r  r  rz  cleanup_errorr}  r~  r  r  r.  r  rb  image_size_kbr  r  ra  data_size_kbcomprehensive_promptr  vision_timeoutvision_temperaturer   r   _cfg_vision_cfg_vt_vtempcall_kwargsr   _api_errr  r  resultr   	error_msgr   s)                                           @r&   r  r  '  s     L k3'' J*5*Ac+&&&r #8;K8H8H38N8N;tt,u44T_
 

  O O Nk222222> 	<mU;;;B  	o 	/2H2H2J2J 	&&(((>????   9=SW         		 	 	 	)9SbS>:::%{4C4'8999	
 	
 	
 	
 	
 	
 	
 	
 	
 	
	'11)^^T[=\=\=\]]]]]]]]HH# 	' 	' 	'SXX&&&	' &]!.2FGGtd333"%E4:<<%E%E%EE ;X]KKKKKKKKK x}--(4/+];;; @G?P)?<N@
 @
 :
 :
 :
 :
 :
 :
6+Y  	I/YG*GHHHo-- /"8"8":": #**,,,,    D-O!N 	3444:%BT V  V  V V V V V V V>**T19<HHH ~!222#>(+=$? $? $? ? ? ? ? ? ?N >""%666 7>**k:D7 7/;?J7 7 7   /?*+  +
  !' 4 
 !,!>&  
$ 	;<<<
  	>>>>>>>>;==D!'$XrJJJK//),,C!&s __]33F!%*6]]" 	 	 	D	  -%
 
  	)#(K 	+::k::::::::HH 	 	 	$X.. N++.BBB@'';7(K8	   (C,#/A(C (C (C "C "C "C "C "C "C AOI&q)+6u=!/!>!>+!>!>>>>>>> 	$ 099  	>NNMNNN+::k::::::::H3H==Hh-->PPP  o$o
 

 &*	"-<)* 	-???z&???j  	o 	/2H2H2J2J 	&&(((>????   9=SW         		 	 	g  /@ /@ /@6c!ff66	T9t444 a&&,,..     ,
      	B>?B B H      .
      	  6 6236 6 H '))[G-C-C   H+'(+ +   
 
 $- -???z&????????  	o 	/2H2H2J2J 	&&(((>????   9=SW         		 	 	g/@f  	o 	/2H2H2J2J 	&&(((>????   9=SW         		 	 	sE    Z .C
C9C44C9?AZ E- ,Z -F2FFC<Z J% $Z %
J2/Z 1J22DZ A-P> =Z >
QZ 
QZ Q1 0Z 1
T.;B)T)$Z )T..CZ .Y


Y;Y66Y;`C<``	` &._
````` b..ab
b'b	b	bbc                      	 ddl m}  n# t          $ r Y dS w xY w	  |             \  }}}|dS  | d          \  }}}|duS # t          $ r Y dS w xY w)u  Check if the configured runtime vision path can resolve a client.

    Mirrors the fallback chain that ``call_llm(task="vision")`` actually uses
    at runtime: first the explicit ``auxiliary.vision.provider`` (if any),
    and if that fails, the auto chain (main provider → openrouter → nous).
    Without the auto-fallback step the tool would disappear from the model's
    tool list whenever the explicit provider name was unresolvable, even
    when the auto chain would have served the request (issue #31179).
    r   )resolve_vision_provider_clientFNTauto)r:  )r\  r  r  r"   )r  	_providerr   _models       r&   check_vision_requirementsr  P  s    IIIIIII   uu	$B$B$D$D!	664 %C$BF$S$S$S!	66T!!   uus"   	 
A A 
AA__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)registryr  vision_analyzeu  Load an image into the conversation so you can see it. Accepts a URL, local file path, or data URL. When your active model has native vision, the image is attached to your context directly and you read the pixels yourself on the next turn — call this any time the user references an image (filepath in their message, URL in tool output, screenshot from the browser, etc.). For non-vision models, falls back to an auxiliary vision model that returns a text description.objectstringz>Image URL (http/https), local file path, or data: URL to load.)rh  descriptionz{Your specific question or request about the image. Optional context the model uses on the next turn after seeing the image.)r   r`  )rh  
propertiesrequired)namer  r  rC   kwc                 L  K   |                      dd          }|                      dd          }|                     d          }t                      r2t                              d           t	          |||           d {V S d| }d }	 dd	lm}m}  |            }	 ||	d
dd          }
|
r#t          |
          	                                pd }n# t          $ r Y nw xY w|s)t          j        dd          	                                pd }t          ||||           d {V S )Nr   r   r`  ru  z vision_analyze: native fast pathr  z]Fully describe and explain everything about this image, then answer the following question:

r   r   r   r   r;  AUXILIARY_VISION_MODEL)r   r_  r   r  r  r!   r   r   rJ   r   r"   r   r   r  )rC   r  r   r`  ru  full_promptr;  r   r   r  _vmodels              r&   _handle_vision_analyzer    s}     b))Ixx
B''HffYG +,, R6777+IxQQQQQQQQQQ	-"*	- 	- 
 E::::::::{}}'$Xw?? 	1LL&&((0DE    H	2B77==??G4$YUGTTTTTTTTTTs   AC 
CCr   Tu   👁️)r  toolsetschemahandlercheck_fnis_asyncemoji	video/mp4z
video/webmz	video/movz
video/mpeg).mp4z.webmz.movz.aviz.mkvz.mpegz.mpg
video_pathc                 h    | j                                         }t                              |          S )zIReturn a video MIME type based on file extension, or None if unsupported.)r   r   _VIDEO_MIME_TYPESr   )r  exts     r&   _detect_video_mime_typer    s*    


!
!
#
#C  %%%r1   c                     |                                  }t          j        |                              d          }|p1t                              | j                                        d          }d| d| S )z2Convert a video file to a base64-encoded data URL.r   r  r   r   )r   r   r   r   r  r   r   r   )r  r   rU   r   r   s        r&   _video_to_base64_data_urlr    sq      ""Dt$$++G44GU)--j.?.E.E.G.GUUD*4*****r1   	video_urlc           
      "  K   ddl }|j                            dd           d }d}t          |          D ]A}	 t	          |           }|rt          |d                   t          j        ddd|gi	          4 d{V 	 }|                    | d
dd           d{V }	|		                                 |	j
                            d          }
|
r@t          |
          t          k    r(t          dt          |
           dt           d          t          |	j                  }t	          |          }|rt          |d                   |	j        }t#          |          t          k    r(t          dt#          |           dt           d          |                    |           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                     |j        |           d{V  n3t(                              d|t          |          dd         d           Y d}~;d}~ww xY w|t1          d| d          |)z7Download video from URL with SSRF protection and retry.r   NTr   c                 ~   K   ddl m}m}  ||           }|r# ||           d {V st          d|           d S d S )Nr   r   r   r   r   s       r&   r   z-_download_video.<locals>._ssrf_redirect_guard  s      UUUUUUUU44X>> 	&7&7&E&E E E E E E E 	OOO  	 	 	 	r1   r   g      N@r   r   r   zvideo/*,*/*;q=0.8r   r   r   zVideo too large (r   r   r)   r   z)Video download failed (attempt %s/%s): %sr   z+Video download failed after %s attempts: %sr   r   zB_download_video exited retry loop without attempting (max_retries=)r=   r   r   r   r   r   r   r   r   r   r   r6   _MAX_VIDEO_BASE64_BYTESr    rJ   rG   r   r+   r   r"   r   r   r   r   r   )r  r   r   r=   r   r   r   r   r   r   r   r   r   r   r   s                  r&   _download_videor    s     NNNTD999   J%% 1 10	*955G :%gi&8999(!%'*>)?@   . . . . . . . . !' 'X"5  ", " "       ))+++%))*:;; #b''$;;;$[CGG[[AX[[[    --	.y99 >)')*<==='t99666$]CII]]CZ]]]   ''---=. . . . . . . . . . . . . . . . . . . . . . . . . . .@  
	 
	 
	Jq(('A+.	JGVWKYdfijkflflmpnpmpfqrrr#gmI..........AQ    
	 _Q\___
 
 	
 s>   AGD'F;)G;
G	GG		G
I7BI22I7c                   "K   t          |t                    s|t          |          nd}| t          |          dk    r|dd         dz   n||dddd|dd}d}d	}	 dd
lm}  |            rt          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                    | }|                    d          r|t          d          d         }t          t          j                            |                    }	|	                                r>ddlm}
  |
t          |	                     t                              d|            |	}d}nt+          |            d{V rit-          |           }|rt/          |d                   t1          dd          }|dt3          j                     dz  }t7          | |           d{V  d	}nt9          d          |                                j        }|dz  }t                              d|           t?          |          }|sQt9          d|j          d d!!                    tE          tF          $                                                               |tJ          k    rt                              d"|           tM          ||#          }t          |          dz  }t          |          tN          k    r t9          d$|d%d&tN          dz  d'd(          ||d)<   d*d+|d,d-d.|id/gd0g}d1}d2}	 dd3l(m)}m*}  |            } ||d4d5i 6          }|+                    d7          }|tY          t[          |          d1          }|+                    d8          }|t[          |          }n# t          $ r Y nw xY wd5||d9|d:}|r||d;<   t]          dSi | d{V }t_          |          }|s;t                              d<           t]          dSi | d{V }t_          |          }|rt          |          nd}t                              d=|           d	|pd>d?}d	|d@<   ||dA<   t`          1                    dB|           t`          2                                 tg          j4        |dCd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          $ r} dEt          |            }!t          5                    dF|!d	           t          |           6                                "to          "fdGdHD                       rdI|  }nGto          "fdJdKD                       rdL|  }n&to          "fdMdND                       rdO|  }ndP|  }d|!|dQ}|!|dR<   t`          1                    dB|           t`          2                                 tg          j4        |dCdD          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)TzEAnalyze a video via multimodal LLM. Returns JSON {success, analysis}.Nr   rj  r  )r  r  r;  Fr   )r  r   rx  r  r  video_size_bytesTry  r{  rw  zCleaned up temporary video filer  r   zAnalyzing video: %sr  r  r   zfile://)raise_if_read_blockedzUsing local video file: %sr   zcache/videotemp_video_filestemp_video_r  zKInvalid video source. Provide an HTTP/HTTPS URL or a valid local file path.r  zVideo ready (%.1f MB)zUnsupported video format: 'z'. Supported: z, u,   Video is %.1f MB — may be slow or rejectedr
  z+Video too large for API: base64 payload is re  r  r  z+ MB). Compress or trim the video and retry.r  r  rf  rg  r  rG   )rh  r  r  g     f@r  r   r   r   r  rn   r  i  r  r;  z#Empty video response, retrying oncez(Video analysis completed (%s characters)zIThere was a problem with the request and the video could not be analyzed.r  rx  r  video_analyze_toolr   r  zError analyzing video: r  c              3       K   | ]}|v V  	d S r   r0   r   s     r&   r   z%video_analyze_tool.<locals>.<genexpr>  r  r1   r  r  c              3       K   | ]}|v V  	d S r   r0   r   s     r&   r   z%video_analyze_tool.<locals>.<genexpr>  s7        T      r1   )r  znot support videor  r  r  zvideo inputr  zThe model does not support video analysis or the request was rejected. Ensure you're using a video-capable model (e.g. google/gemini-2.5-flash). Error: c              3       K   | ]}|v V  	d S r   r0   r   s     r&   r   z%video_analyze_tool.<locals>.<genexpr>
  s7        T      r1   )r   r   r   r   r   r   r  z_The video is too large for the API. Try compressing or trimming the video (max ~50 MB). Error: zQThere was a problem with the request and the video could not be analyzed. Error: r  r   r0   )8rI   rJ   r+   r  rz  r  rr   r  r   r^  r"   r   r  rK   r   r   path
expanduseris_fileagent.file_safetyr  rT   r   r   r   r   r   r  r    rs   rt   r  r   joinsortedr  keys_VIDEO_SIZE_WARN_BYTESr  r  r!   r   r   r   r*   r   r
   r   r  r  r   r  r  r   r   r  )#r  r  r;  r  temp_video_pathr  rz  r  resolved_url
local_pathr  r   r  r  video_size_mbr   video_data_urldata_size_mbr  r  r  r   r   r  r  r  r  r  r   r  r  r  r   r  r   s#                                     @r&   r  r  ^  s
      k3'' J*5*Ac+&&&r #8;K8H8H38N8N;tt,u44T_
 

  O ONv222222> 	<mU;;;X  	o 	/2H2H2J2J 	&&(((>????   9=SW         		 	 	U 	)9SbS>:::%{4C4'8999 !""9-- 	9'I8L"',,\::;;
 	??????!!#j//222KK4i@@@(O"NN,Y77777777 	*955G :%gi&8999%m5GHHH&)Itz||)I)I)IIO!)_=========!NN]   +//119(K8+];;;/@@ 	Lo.D L L"ii/@/E/E/G/G(H(HIIL L  
 444NNI=YYY2?m\\\>**k:~!8889lT 9 91[AM9 9 9   /?*+  !' + 
 !,!>&  
$  	>>>>>>>>;==D!'$XrJJJK//),,C!$U3ZZ!7!7 __]33F!%*6]]" 	 	 	D	  -%
 
  	)#(K '66+66666666/99 	>NN@AAA+::k::::::::H3H==H+3:#h--->PPP  o$o
 

 &*	"-<)*,o>>>z&???h  	o 	/2H2H2J2J 	&&(((>????   9=SW         		 	 	e  /@ /@ /@6c!ff66	T9t444a&&,,..     ,
      	B>?B B H      .
      	>:;> > H
      .
      	
6236 6 H+'(+ +   
 
 $- ,o>>>z&????????  	o 	/2H2H2J2J 	&&(((>????   9=SW         		 	 	e/@d  	o 	/2H2H2J2J 	&&(((>????   9=SW         		 	 	s    V3 .C
C7C22C7=J4V3 2A;P. -V3 .
P;8V3 :P;;C9V3 .U<<
V-V((V-3]>D]]] ).\
]	"]]	]] _1.^ _ 
_*____video_analyzeu?  Analyze a video from a URL or local file path using a multimodal AI model. Sends the video to a video-capable model (e.g. Gemini) for understanding. Use this for video files — for images, use vision_analyze instead. Supports mp4, webm, mov, avi, mkv, mpeg formats. Note: large videos (>20 MB) may be slow; max ~50 MB.z5Video URL (http/https) or local file path to analyze.zpYour specific question about the video. The AI will describe what happens in the video and answer your question.)r  r`  c                    |                      dd          }|                      dd          }d| }d }	 ddlm}m}  |            } ||ddd	          p ||dd
d	          }	|	r#t	          |	                                          pd }n# t          $ r Y nw xY w|sPt          j        dd                                          p(t          j        dd                                          pd }t          |||          S )Nr  r   r`  zFully describe and explain everything happening in this video, including visual content, motion, audio cues, text overlays, and scene transitions. Then answer the following question:

r   r   r   videor;  r   AUXILIARY_VIDEO_MODELr  )
r   r!   r   r   rJ   r   r"   r   r   r  )
rC   r  r  r`  r  r;  r   r   r  r  s
             r&   _handle_video_analyzer  I  sA   b))Ixx
B''H	J?G	J 	J  E::::::::{}}'$Wg>>o''$P[]egnBoBo 	1LL&&((0DE    z	1266<<>>y")LdfhBiBiBoBoBqBqyuyie<<<s   AB	 	
BBr  u   🎬)r   r   )NN)a__doc__r   
contextlibr=   r  concurrent.futuresr   loggingr   r   pathlibr   typingr   r   r   r   urllib.parser	   r   r\  r
   r   hermes_constantsr   tools.debug_helpersr   tools.website_policyr   sys	getLogger__name__r   r  r   r'   r   r   	threadingr6   r2   r8   _VISION_CPU_WORKERSr@   rF   rJ   r  rM   rQ   rT   bytesra   	frozensetr   r   tupler   r"   r   r   r   r   r  r  r  r  r  r  r9  rU  r_  rr  asynccontextmanagerrt  r  r  r  printapi_availableexitactive
session_idtools.registryr  r  VISION_ANALYZE_SCHEMAr  registerr  r  r  r  r  r  r  VIDEO_ANALYZE_SCHEMAr  r0   r1   r&   <module>r8     sX   :        1 1 1 1 1 1  				        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  . @    
+3 
+ 
+ 
+ 
+ S        F 2133  *)#&      S T    S T    ( ( ( ( ( (U x}    6 $-9:::$ $  
,D ,D ,T , , , ,^77%(7
8D>8C=(3-787 7 7 7t	 d    0k kS kt k# kVZ k k k k\3T 3c 3 3 3 3. $ 8C= TW    8 %  &    ' 	 d     c d    $ KO6J=APR PR PR(3- PR03PR-5c]PRFIPR PR PR PR~Ec E# E$ E E E EPT    <444 4 	4
 
#s(^4 4 4 4n 
 
  
" "@ @@@ c]@ 		@ @ @ @L !	f fff f c]	f
 	f f f fR	4    6 z 
E
'(((	E(OOO .-//M ,7888  G  	H  	H  	H*+++	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 0 / / / / / / / 	&  !_ 
 !  ] 	
 	
 !*-   :%UtCH~ %US %US %U %U %U %UP  	 "&
   "    + ) & &# & & & &+ +$ +8C= +TW + + + +F FS Ft F# FVZ F F F FX N NNN N 		N N N Nd 	?  !V 
 !  R 	
 	
 !*-   4=S#X =c =in = = = =0  	!&
     r1   