
    Yjx                    "   d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	 ddl
mZmZmZmZmZ  ej        e          Z eh d          ZdZd                    d	 eD                       Z ej        d
ez   dz   ej                  Z ej        dez   dz   ej                  Zd8dZ eh d          Z eh d          Zd9dZd:dZ d;dZ!d<d!Z"d=d"Z#d>d#Z$	 d?d@d$Z%dAd%Z&dBd(Z' eh d)          Z(dCd+Z)d?dDd.Z*dEd/Z+	 d?dFd6Z,g d7Z-dS )Gu  Routing helpers for inbound user-attached images.

Two modes:

  native  — attach images as OpenAI-style ``image_url`` content parts on the
            user turn. Provider adapters (Anthropic, Gemini, Bedrock, Codex,
            OpenAI chat.completions) already translate these into their
            vendor-specific multimodal formats.

  text    — run ``vision_analyze`` on each image up-front and prepend the
            description to the user's text. The model never sees the pixels;
            it only sees a lossy text summary. This is the pre-existing
            behaviour and still the right choice for non-vision models.

The decision is made once per message turn by :func:`decide_image_input_mode`.
It reads ``agent.image_input_mode`` from config.yaml (``auto`` | ``native``
| ``text``, default ``auto``) and the active model's capability metadata.

In ``auto`` mode:
  - If the active model reports ``supports_vision=True`` (via config
    override or models.dev metadata), we attach natively — vision-capable
    main models should always see the original pixels, even when an
    auxiliary vision backend is configured. That auxiliary backend then
    acts as a *fallback* for sessions whose main model can't take images.
  - Otherwise, if the user has explicitly configured ``auxiliary.vision``
    (provider/model/base_url not ``auto``/empty), we route through the
    text pipeline so the auxiliary vision backend can describe the image
    for the text-only main model.
  - Otherwise (non-vision model, no explicit override), we fall back to
    text via the default vision_analyze flow.

This keeps ``vision_analyze`` surfaced as a tool in every session — skills
and agent flows that chain it (browser screenshots, deeper inspection of
URL-referenced images, style-gating loops) keep working. The routing only
affects *how user-attached images on the current turn* are presented to the
main model.
    )annotationsN)Path)AnyDictListOptionalTuple>   autotextnative)	.png.jpg.jpeg.gif.webp.bmpz.tiffz.tifz.heic|c              #  @   K   | ]}|                     d           V  dS ).N)lstrip).0es     HC:\Users\server\AppData\Local\hermes\hermes-agent\agent\image_routing.py	<genexpr>r   ?   s,      AAahhsmmAAAAAA    z/(?<![/:\w.])(?:~/|/)(?:[\w.\-]+/)*[\w.\-]+\.(?:z)\bzhttps?://[^\s<>\"']+?\.(?:z)(?:\?[^\s<>\"']*)?r   strreturnTuple[List[str], List[str]]c                   t          | t                    r| sg g fS g t          j        d| t          j                  D ]=}                    |                                |                                f           >t          j        d|           D ]=}                    |                                |                                f           >d
fd}g }t                      }t                              |           D ]} ||                                          r |
                    d          }t          j                            |          }	 t          j                            |          sun# t          $ r Y w xY w||v r|                    |           |                    |           g }t                      }	t"                              |           D ]y} ||                                          r |
                    d          }
|
                    d	          }
|
|	v rO|	                    |
           |                    |
           z||fS )u  Scan free-form text for image references the model should see.

    Returns ``(local_paths, urls)``:

      * ``local_paths`` — absolute (``/``) or home-relative (``~/``) paths
        whose suffix is an image extension AND whose expanded form exists
        on disk as a file. Order-preserving, deduplicated.
      * ``urls`` — ``http(s)://…`` URLs whose path ends in an image
        extension (a ``?query`` is allowed after the extension).
        Order-preserving, deduplicated.

    Matches inside fenced code blocks (``` ``` ```) and inline backticks
    (`` `…` ``) are skipped so that snippets pasted into a task body for
    reference aren't mistaken for live attachments. This mirrors the
    behaviour of ``gateway.platforms.base.BaseAdapter.extract_local_files``.

    Local paths are validated against the filesystem; URLs are not
    (the provider fetches them at request time).
    z```[^\n]*\n.*?```z	`[^`\n]+`posintr   boolc                <     t           fdD                       S )Nc              3  >   K   | ]\  }}|cxk    o|k     nc V  d S N )r   sr   r    s      r   r   z7extract_image_refs.<locals>._in_code.<locals>.<genexpr>r   s;      77DAq1<<<<a<<<<777777r   )any)r    
code_spanss   `r   _in_codez$extract_image_refs.<locals>._in_codeq   s'    7777J777777r   r   z	.,;:!?)]>)r    r!   r   r"   )
isinstancer   refinditerDOTALLappendstartendset_LOCAL_IMAGE_PATH_REgroupospath
expanduserisfileOSErroradd_IMAGE_URL_RErstrip)r   mr*   local_paths
seen_pathsmatchrawexpandedurls	seen_urlsurlr)   s              @r   extract_image_refsrF   R   ss   ( dC    2v )+J[-tRY?? 0 017799aeegg.////[t,, 0 017799aeegg.////8 8 8 8 8 8  K55J%..t44 % %8EKKMM"" 	kk!nn7%%c**	7>>(++  	 	 	H	 z!!x   8$$$$D%%I''-- 
 
8EKKMM"" 	kk!nn jj%%)cCs   E66
FF>   1onyestrue>   0noofffalserA   r   Optional[bool]c                *   t          | t                    r| S t          | t                    r| dv rt          |           S dS t          | t                    r<|                                                                 }|t          v rdS |t          v rdS dS )z@Return True/False for recognised boolean values, None otherwise.)r      NTF)r+   r"   r!   r   striplower_TRUE_TOKENS_FALSE_TOKENS)rA   r'   s     r   _coerce_capability_boolrV      s    #t 
#s &==99t#s IIKK454r   cfgOptional[Dict[str, Any]]providermodelc                   t          | t                    sdS |                     d          }t          |t                    r|ni }t          |                    d                    }||S t	          |                    d          pd                                          }d}|                    d          r|t          d          d         }|                     d          }t          |t                    r|ni }	t                              t          d|||f                    D ]}
|	                    |
          }t          |t                    r|ni }|                    d          }t          |t                    r|ni }|                    |          }t          |t                    r|ni }t          |                    d                    }||c S |                     d	          }t          |t                    rjt                      }t          d||f          D ]o}
|                    |
           |
                    d          r+|                    |
t          d          d                    W|                    d|
            p|D ]}t          |t                    st	          |                    d
          pd                                          }||vrS|                    d          }t          |t                    r|ni }|                    |          }t          |t                    r|ni }t          |                    d                    }||c S dS )u  Resolve user-declared vision capability from config.yaml.

    Resolution order, first hit wins:
      1. ``model.supports_vision`` (top-level shortcut for the active model)
      2. ``providers.<provider>.models.<model>.supports_vision``
         (named custom providers — ``provider`` may be the runtime-resolved
         value ``"custom"`` and/or the user-declared name under
         ``model.provider``; both are tried. For ``custom:<name>`` syntax,
         the stripped ``<name>`` is also tried as a provider key.)

    Returns None when no override is set, so the caller falls through to
    models.dev. Returns False explicitly only when the user wrote a
    recognised boolean false token.
    NrZ   supports_visionrY    custom:	providersmodelscustom_providersname)r+   dictgetrV   r   rR   
startswithlenfromkeysfilterlistr2   r:   )rW   rY   rZ   model_cfg_raw	model_cfgtopconfig_providerstripped_suffixproviders_rawproviders_cfgp	entry_rawentry
models_raw
models_cfgper_model_raw	per_modelcoercedra   candidate_names
entry_names                        r   _supports_vision_overrider{      sh   & c4   t GGG$$M1;M41P1P XVXI
!)--0A"B"B
C
CC

 )--
339r::@@BBOO!!),, ;)#i..//:GGK((M5?t5T5T$\MMZ\M]]6$?O(TUUVV 	 	!%%a((	-7	4-H-H P		bYYx((
3=j$3O3O%WZZUW
"u--5?t5T5T$\MMZ\	))--8I*J*JKKNNN  ww122"D))   #uux9:: 	3 	3A"""||I&& 3##Ac)nnoo$67777##MaMM2222) 	 	Ii.. Y]]6228b99??AAJ00"x00J'1*d'C'CKJ&NN511M)3M4)H)HPbI-imm<M.N.NOOG" # 4r   c                "   	 ddl m} t          |pd                                          }|r|S n# t          $ r Y nw xY wt          | t                    sdS |                     d          }t          |t                    r|ni }t          |                    d          pd                                          }|r|S t          |                    d          pd                                          }t                      }t          d||f          D ]}	|
                    |	           |	                                                    d          r0|
                    |	                    d	d
          d
                    n|
                    d|	            |                     d          }
t          |
t                    rk|D ]h}|
                    |          }t          |t                    r<t          |                    d          pd                                          }|r|c S i|                     d          }t          |t                    rd |D             }|D ]}t          |t                    st          |                    d          pd                                          }||vr|                                |vrit          |                    d          pd                                          }|r|c S dS )z7Best-effort base URL for the active inference provider.r   )_RUNTIME_MAIN_BASE_URLr]   rZ   base_urlrY   Nr^   :rQ   r_   ra   c                6    h | ]}|                                 S r&   )rS   )r   ns     r   	<setcomp>z._resolve_inference_base_url.<locals>.<setcomp>3  s     66617799666r   rb   )agent.auxiliary_clientr}   r   rR   	Exceptionr+   rc   rd   r2   rh   r:   rS   re   splitri   )rW   rY   r}   runtimerj   rk   r~   rm   ry   rq   rp   rb   rs   bura   loweredrr   rz   s                     r   _resolve_inference_base_urlr     s1   
AAAAAA,23399;; 	N	    c4   rGGG$$M1;M41P1P XVXI9==,,23399;;H )--
339r::@@BBO #OD8_566 / /A7799	** 	/Q 23333!....GGK((M-&& # 	 	D!%%d++E%&& :..4"55;;== IIIww122"D)) 
66o666) 	 	Ii.. Y]]6228b99??AAJ00Z5E5E5G5Gw5V5VY]]:..4"55;;==B 			 2s   ,0 
==r~   r"   c                    | pd                                                                 }|dk    rdS |sdS 	 ddlm}  ||          dk    S # t          $ r Y dS w xY w)zBTrue when the active provider likely fronts a local Ollama server.r]   ollamaTFr   )detect_local_server_type)rR   rS   agent.model_metadatar   r   )rY   r~   rq   r   s       r   _should_probe_ollama_visionr   A  s    	R  &&((AH}}t uAAAAAA''11X==   uus   A 
AAc                    t          | t                    sdS |                                                                 }|t          v r|S dS )z5Normalize a config value into one of the valid modes.r
   )r+   r   rR   rS   _VALID_MODES)rA   vals     r   _coerce_moder   P  sG    c3 v
))++



C
l
6r   c                h   t          | t                    sdS |                     d          pi }t          |t                    sdS |                    d          pi }t          |t                    sdS t          |                    d          pd                                                                          }t          |                    d          pd                                          }t          |                    d          pd                                          }|dv r|s|sdS d	S )
uc  True when the user configured a specific auxiliary vision backend.

    An explicit override means the user has a dedicated vision backend
    available; it's used as a *fallback* when the main model can't take
    images natively. In ``auto`` mode, native vision on a vision-capable
    main model still wins over this fallback — see issue #29135.
    F	auxiliaryvisionrY   r]   rZ   r~   >   r]   r
   T)r+   rc   rd   r   rR   rS   )rW   auxr   rY   rZ   r~   s         r   _explicit_aux_vision_overrider   Z  s     c4   u
''+


$"Cc4   uWWX$"Ffd## u6::j))/R006688>>@@H

7##)r**0022E6::j))/R006688H <hu4r   c                4   t          || |          }||S | r|sdS d}	 ddlm}  || |          }n4# t          $ r'}t                              d| ||           Y d}~nd}~ww xY w|t          |j                  S t          ||           }|s.| pd	                                
                                dk    rd}t          | |          rL	 ddlm}  |||          }	|	|	S n4# t          $ r'}t                              d	| ||           Y d}~nd}~ww xY wdS )
a  Return True/False if we can resolve caps, None if unknown.

    Consults the user's ``supports_vision`` override in config.yaml first
    (so custom/local models declared as vision-capable don't fall through to
    text routing in ``auto`` mode), then falls back to models.dev.
    Nr   )get_model_capabilitiesu2   image_routing: caps lookup failed for %s:%s — %sr]   r   zhttp://localhost:11434/v1)query_ollama_supports_visionu:   image_routing: ollama vision probe failed for %s:%s — %s)r{   agent.models_devr   r   loggerdebugr"   r\   r   rR   rS   r   r   r   )
rY   rZ   rW   overridecapsr   excr~   r   ollama_visions
             r   _lookup_supports_visionr   u  s    )h>>H 5 tDa;;;;;;%%h66 a a aI8UZ\_````````aD()))*399H /R..006688HDD."8X66 	IIIIII88IIM($$ ) 	 	 	LLL	       	 4s,   2 
A#AA#C$ $
D.DDc                @   d}t          |t                    rN|                    d          pi }t          |t                    r"t          |                    d                    }|dk    rdS |dk    rdS t	          | ||          }|du rdS t          |          rdS dS )a1  Return ``"native"`` or ``"text"`` for the given turn.

    Args:
      provider: active inference provider ID (e.g. ``"anthropic"``, ``"openrouter"``).
      model:    active model slug as it would be sent to the provider.
      cfg:      loaded config.yaml dict, or None. When None, behaves as auto.
    r
   agentimage_input_moder   r   T)r+   rc   rd   r   r   r   )rY   rZ   rW   mode_cfg	agent_cfgsupportss         r   decide_image_input_moder     s     H#t GGGG$$*	i&& 	G#IMM2D$E$EFFH8x6v 'x<<H4x$S)) v6r   bytesOptional[str]c                |   | sdS |                      d          rdS |                      d          rdS | dd         dv rdS t          |           d	k    r| dd
         dk    r| dd	         dk    rdS |                      d          rdS t          |           d	k    r$| d
d         dk    r| dd	         }|dv rdS |dv rdS | dd
         dv rdS | dd
         dk    rdS | dd                                                                         }|                     d          s|                     d          rd|v rdS dS )a  Detect image MIME from magic bytes. Returns None if unrecognised.

    Filename-based detection (``mimetypes.guess_type``) is unreliable when
    upstream platforms lie about content-type. Discord, for example, can
    serve a PNG with ``content_type=image/webp`` for proxied/animated
    stickers, custom emoji previews, or images uploaded via certain bots.
    Anthropic strictly validates that declared media_type matches the
    actual bytes and returns HTTP 400 on mismatch, so we sniff to be safe.
    Ns   PNG

	image/pngs   
image/jpeg   >      GIF87a   GIF89a	image/gif      s   RIFF   s   WEBP
image/webps   BM	image/bmps   ftyp>      avif   avisz
image/avif>      heic   heim   heis   heix   hevc   hevx   mif1   msf1z
image/heic>      II*    MM *z
image/tiffs      zimage/x-iconi   s   <?xmls   <svgzimage/svg+xml)re   rf   r   rS   )rA   brandheads      r   _sniff_mime_from_bytesr     s     t
~~*++ {
~~o&& |
2A2w((({
3xx2~~#bqb'W,,QrTg1E1E|
~~e {
3xx2~~#ac(g--AbD	&&&< 
 
 
  <
2A2w***|
2A2w%%%~tt9##%%Dx   #DOOG$<$< #d??"?4r   >   r   r   r   r   Optional[bytes]c                t   	 ddl m} n+# t          $ r t                              d           Y dS w xY w	 ddl}|                                 n# t          $ r Y nw xY w	 ddl}n# t          $ r Y nw xY w	 ddl	m
} |                     ||                     5 }|j        dvr|                    d          } |            }|                    |dd	
           |                                cddd           S # 1 swxY w Y   dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)a  Decode arbitrary image bytes with Pillow and re-encode as PNG.

    Returns None if Pillow isn't installed or can't decode the input
    (rare formats, corrupted bytes, missing optional decoder plugin for
    HEIC/AVIF, or vector formats like SVG). Caller falls back to skipping
    the image so the rest of the turn still works.

    HEIC/HEIF and AVIF need optional Pillow plugins; we try to register
    them on demand and swallow ImportError so a missing plugin just
    looks like 'Pillow can't decode this' rather than crashing.
    r   )Imagezimage_routing: Pillow not installed; cannot transcode non-standard image format to PNG. Install with `pip install Pillow` (and `pillow-heif` / `pillow-avif-plugin` for those formats).N)BytesIO>   LPLARGBRGBAr   PNGF)formatoptimizez<image_routing: Pillow could not transcode image to PNG -- %s)PILr   ImportErrorr   infopillow_heifregister_heif_openerr   pillow_avifior   openmodeconvertsavegetvalue)rA   r   r   r   r   imbufr   s           r   _transcode_to_pngr     s      L	
 	
 	

 tt((****      ZZ%% 	" w===ZZ'''))CGGCG666<<>>	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	"    JC	
 	
 	
 ttttt	sl   	 $11A 
AAA$ $
A10A15$D AC:-D :C>>D C>D 
D7D22D7r6   r   c                   |t          |          }|r|S t          j        t          |                     \  }}|r|                    d          r|S | j                                        }ddddddd                    |d          S )	zReturn image MIME type for *path*.

    If *raw* bytes are provided, magic-byte sniffing wins (authoritative).
    Otherwise we fall back to ``mimetypes`` then suffix-based defaults.
    Nzimage/r   r   r   r   r   )r   r   r   r   r   r   )r   	mimetypes
guess_typer   re   suffixrS   rd   )r6   rA   sniffedmime_r   s         r   _guess_mimer   K  s     (-- 	N"3t99--GD! ))  [  F  
c&, r   c                   	 ddl m}  |t          |                      n?# t          $ r'}t                              d| |           Y d}~dS d}~wt          $ r Y nw xY w	 |                                 }n4# t          $ r'}t                              d| |           Y d}~dS d}~ww xY wt          | |          }|t          vrTt          |          }|t                              d| |           dS t                              d| j        |           |}d	}t          j        |                              d
          }d| d| S )uW  Encode a local image as a base64 data URL at its native size.

    Size limits are NOT enforced here — the agent retry loop
    (``run_agent._try_shrink_image_parts_in_messages``) shrinks on the
    provider's first rejection. Keeping this simple means providers that
    accept large images (OpenAI 49 MB+, Gemini 100 MB) don't pay a silent
    quality tax just because one other provider is stricter.

    Format compatibility IS handled here: if the sniffed MIME isn't one
    of ``_UNIVERSALLY_SUPPORTED_MIMES`` (i.e. it's something like AVIF,
    HEIC, BMP, TIFF, or ICO that some providers reject outright), we
    transcode to PNG with Pillow before declaring media_type. This fixes
    the user-visible "Could not process image" HTTP 400 from Anthropic on
    Discord-attached AVIF/HEIC/BMP files.

    Returns None if the file can't be read OR if the format isn't
    universally supported AND Pillow can't transcode it (Pillow missing,
    HEIC/AVIF plugin missing, vector format like SVG, corrupt bytes). The
    caller reports those paths in ``skipped`` and the rest of the turn
    proceeds.
    r   )raise_if_read_blockedz6image_routing: blocked local image attachment %s -- %sNu'   image_routing: failed to read %s — %s)rA   zimage_routing: %s is %s which is not accepted by all major vision providers and could not be transcoded to PNG; skipping this attachment.zIimage_routing: transcoded %s (%s) -> image/png for provider compatibilityr   asciizdata:z;base64,)agent.file_safetyr   r   
ValueErrorr   warningr   
read_bytesr   _UNIVERSALLY_SUPPORTED_MIMESr   r   rb   base64	b64encodedecode)r6   r   r   rA   r   
transcodedb64s          r   _file_to_data_urlr   e  s   ,	;;;;;;c$ii((((   OQUWZ[[[ttttt   oo   @$LLLttttt t%%%D///&s++
NN, d	   4WIt	
 	
 	
 

3


&
&w
/
/C&4&&&&&s2   ! 
AAAA!A6 6
B' B""B'	user_textimage_paths	List[str]
image_urlsOptional[List[str]]&Tuple[List[Dict[str, Any]], List[str]]c                   g }g }g }g }|D ]}t          |          }|                                r|                                s#|                    t	          |                     \t          |          }	|	s#|                    t	          |                     |                    dd|	id           |                    t	          |                     |pg D ]J}
|
pd                                }
|
s|                    dd|
id           |                    |
           K| pd                                }|s|r~|pd}g }|                    d |D                        |                    d |D                        | dd	                    |          z   }d
|dg}|                    |           ||fS g }|r|                    d
|d           ||fS )u  Build an OpenAI-style ``content`` list for a user turn.

    Shape:
      [{"type": "text", "text": "...\n\n[Image attached at: /local/path]"},
       {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}},
       {"type": "image_url", "image_url": {"url": "https://example.com/a.png"}},
       ...]

    Local paths are read from disk and embedded as base64 ``data:`` URLs.
    Remote URLs (``http(s)://``) are passed through verbatim — the provider
    fetches them server-side. The model still sees the pixels either way.

    For each successfully attached image, a hint is appended to the text
    part:

      * local path → ``[Image attached at: <path>]``
      * URL        → ``[Image attached: <url>]``

    The hint gives the model a string handle so MCP/skill tools that take
    an image path or URL argument can be invoked on the same image without
    an extra round-trip. This parallels the text-mode hint produced by
    ``Runner._enrich_message_with_vision`` (``vision_analyze using image_url:
    <path>``) so behaviour is consistent across both image input modes.

    Images are attached at their native size. If a provider rejects the
    request because an image is too large (e.g. Anthropic's 5 MB per-image
    ceiling), the agent's retry loop transparently shrinks and retries
    once — see ``run_agent._try_shrink_image_parts_in_messages``.

    Returns (content_parts, skipped). Skipped entries are local paths
    that couldn't be read from disk; URLs are never skipped (they're
    not validated here).
    	image_urlrE   )typer   r]   zWhat do you see in this image?c              3  "   K   | ]
}d | dV  dS )z[Image attached at: ]Nr&   )r   rq   s     r   r   z-build_native_content_parts.<locals>.<genexpr>  s.      NN!5555NNNNNNr   c              3  "   K   | ]
}d | dV  dS )z[Image attached: r  Nr&   )r   us     r   r   z-build_native_content_parts.<locals>.<genexpr>  s.      JJq2a222JJJJJJr   z


r   )r   r   )	r   existsis_filer/   r   r   rR   extendjoin)r   r   r   skippedimage_partsattached_pathsattached_urlsraw_pathrq   data_urlrE   r   	base_text
hint_linescombined_textpartss                   r   build_native_content_partsr    s\   L G(*K "N!M - -NNxxzz 	 	NN3x==)))$Q'' 	NN3x==)))*
 
 	 	 	 	c(mm,,,,R " "yb!! 	
 
 	 	 	 	S!!!!O""$$D   <<	 "
NN~NNNNNNJJMJJJJJJ$***TYYz-B-BB06'N'N&O[!!!g~ E 5fd33444'>r   )r   r  rF   )r   r   r   r   )rA   r   r   rO   )rW   rX   rY   r   rZ   r   r   rO   )rW   rX   rY   r   r   r   )rY   r   r~   r   r   r"   )rA   r   r   r   )rW   rX   r   r"   r%   )rY   r   rZ   r   rW   rX   r   rO   )rY   r   rZ   r   rW   rX   r   r   )rA   r   r   r   )rA   r   r   r   )r6   r   rA   r   r   r   )r6   r   r   r   )r   r   r   r   r   r   r   r   ).__doc__
__future__r   r   loggingr   r5   r,   pathlibr   typingr   r   r   r   r	   	getLogger__name__r   	frozensetr   _IMAGE_EXTSr	  _IMAGE_EXT_PATTERNcompile
IGNORECASEr3   r;   rF   rT   rU   rV   r{   r   r   r   r   r   r   r   r   r   r   r   r  __all__r&   r   r   <module>r"     s  $ $L # " " " " "       				 				       3 3 3 3 3 3 3 3 3 3 3 3 3 3		8	$	$ y33344 XXAA[AAAAA 
 "rz69KKfTM   
!$669OOM B B B BZ y33344	55566   "Q Q Q Qh6 6 6 6r         < %)* * * * *Z       d0 0 0 0|  )y * * *     
1 1 1 1h         48' 8' 8' 8'| '+V V V V Vr  r   