§
    ÷žyjy  ã                  ó4  — 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d;d„Z eh d£¦  «        Z eh d£¦  «        Zd<d„Zddœd=d „Z d>d!„Z!d?d$„Z"d@d%„Z#dAd&„Z$	 dBddœdCd'„Z%ddœdDd(„Z&dEd+„Z' eh d,£¦  «        Z(dFd.„Z)dBdGd1„Z*dHd2„Z+	 dBdId9„Z,g d:¢Z-dS )Juˆ  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     ú9/home/ragecks/.hermes/hermes-agent/agent/image_routing.pyú	<genexpr>r   ?   s,   è è € ÐAÐA°˜aŸhšh s™mœmÐ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Ð7¡D A q�1˜�<�<’<�<˜a’<�<�<�<Ð7Ð7Ð7Ð7Ð7Ð7r   )Úany)r    Ú
code_spanss   `€r   Ú_in_codez$extract_image_refs.<locals>._in_codeq   s'   øø€ ÝÐ7Ð7Ð7Ð7¨JÐ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  ø€ õ( �d�CÑ Ô ð ¨ð Ø�2ˆvˆð )+€JÝŒ[Ð-¨tµR´YÑ?Ô?ð 0ð 0ˆØ×Ò˜1Ÿ7š7™9œ9 a§e¢e¡g¤gÐ.Ñ/Ô/Ð/Ð/ÝŒ[˜ tÑ,Ô,ð 0ð 0ˆØ×Ò˜1Ÿ7š7™9œ9 a§e¢e¡g¤gÐ.Ñ/Ô/Ð/Ð/ð8ð 8ð 8ð 8ð 8ð 8ð  €KÝ™5œ5€JÝ%×.Ò.¨tÑ4Ô4ð %ð %ˆØˆ8�E—K’K‘M”MÑ"Ô"ð 	ØØ�kŠk˜!‰nŒnˆÝ”7×%Ò% cÑ*Ô*ˆð	Ý”7—>’> (Ñ+Ô+ð Øðøåð 	ð 	ð 	àˆHð	øøøð �zÐ!Ð!ØØ�Š�xÑ Ô Ð Ø×Ò˜8Ñ$Ô$Ð$Ð$à€DÝ™%œ%€IÝ×'Ò'¨Ñ-Ô-ð 
ð 
ˆØˆ8�E—K’K‘M”MÑ"Ô"ð 	ØØ�kŠk˜!‰nŒnˆð �jŠj˜Ñ%Ô%ˆØ�)ÐÐØØ�Š�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ÑÔð Ø�&ˆ=ˆ=Ý˜‘9”9ÐØˆtÝ�#•sÑÔð Ø�IŠI‰KŒK×ÒÑÔˆØ•ÐÐØ�4Ø•ÐÐØ�5Øˆ4r   Ú ©Úrequested_providerÚcfgúOptional[Dict[str, Any]]ÚproviderÚmodelrY   c          
     ó   — t          | t          ¦  «        sdS |                      d¦  «        }t          |t          ¦  «        r|ni }t          |                     d¦  «        ¦  «        }|�|S t	          |                     d¦  «        pd¦  «                             ¦   «         }g }|||fD ]]}	|	sŒ|                     |	¦  «         |	                     d¦  «        r.|	t          d¦  «        d…         }
|
r|                     |
¦  «         Œ^|                      d¦  «        }t          |t          ¦  «        r|ni }t           	                    |¦  «        D ]È}|                     |¦  «        }t          |t          ¦  «        r|ni }|                     d¦  «        }t          |t          ¦  «        r|ni }|                     |¦  «        }t          |t          ¦  «        r|ni }t          |                     d|                     d	¦  «        ¦  «        ¦  «        }|�|c S ŒÉ|                      d
¦  «        }t          |t          ¦  «        �rKt           	                    |¦  «        D �]/}	|	                     ¦   «                              ¦   «         }|D �]}t          |t          ¦  «        sŒt	          |                     d¦  «        pd¦  «                             ¦   «                              ¦   «         }||k    rŒh|                     d¦  «        }t          |t          ¦  «        r|ni }|                     |¦  «        }t          |t          ¦  «        r|ni }t          |                     d|                     d	¦  «        ¦  «        ¦  «        }|�|c c S �Œ�Œ1d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"``, the runtime's originally requested provider,
         and/or the user-declared name under ``model.provider``; all are
         tried. For ``custom:<name>`` syntax, the stripped ``<name>`` is also
         tried as a provider key.)
      2b. ``custom_providers`` (legacy list form) ``.models.<model>``

    Under (2) and (2b), the per-model capability key may be written as
    either ``supports_vision`` or the shorter ``vision`` alias; both work.

    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.
    Nr]   Úsupports_visionr\   rW   úcustom:Ú	providersÚmodelsÚvisionÚcustom_providersÚname)r+   ÚdictÚgetrV   r   rR   r/   Ú
startswithÚlenÚfromkeysÚlistrS   )rZ   r\   r]   rY   Úmodel_cfg_rawÚ	model_cfgÚtopÚconfig_providerÚprovider_candidatesÚ	candidateÚstripped_candidateÚproviders_rawÚproviders_cfgÚpÚ	entry_rawÚentryÚ
models_rawÚ
models_cfgÚper_model_rawÚ	per_modelÚcoercedrd   Úcandidate_nameÚ
entry_names                           r   Ú_supports_vision_overrider   ´   s’  € õ4 �c�4Ñ Ô ð Øˆtð —G’G˜GÑ$Ô$€MÝ1;¸MÍ4Ñ1PÔ1PÐ X  ÐVX€IÝ
! )§-¢-Ð0AÑ"BÔ"BÑ
CÔ
C€CØ
€Øˆ
õ ˜)Ÿ-š-¨
Ñ3Ô3Ð9°rÑ:Ô:×@Ò@ÑBÔB€OØ%'ÐØ(¨(°OÐDð ?ð ?ˆ	Øð 	ØØ×"Ò" 9Ñ-Ô-Ð-Ø×Ò 	Ñ*Ô*ð 	?Ø!*­3¨y©>¬>¨?¨?Ô!;ÐØ!ð ?Ø#×*Ò*Ð+=Ñ>Ô>Ð>øØ—G’G˜KÑ(Ô(€MÝ5?ÀÍtÑ5TÔ5TÐ$\ M MÐZ\€MÝ�]Š]Ð.Ñ/Ô/ð ð ˆØ!×%Ò% aÑ(Ô(ˆ	Ý-7¸	Å4Ñ-HÔ-HÐ P 	 	ÈbˆØ—Y’Y˜xÑ(Ô(ˆ
Ý3=¸jÍ$Ñ3OÔ3OÐ%W Z ZÐUWˆ
Ø"Ÿš uÑ-Ô-ˆÝ5?ÀÍtÑ5TÔ5TÐ$\ M MÐZ\ˆ	Ý)Ø�MŠMÐ+¨Y¯]ª]¸8Ñ-DÔ-DÑEÔEñ
ô 
ˆð ÐØˆNˆNˆNð ð —w’wÐ1Ñ2Ô2ÐÝÐ"¥DÑ)Ô)ñ #õ ŸšÐ':Ñ;Ô;ð 	#ñ 	#ˆIØ&Ÿ_š_Ñ.Ô.×4Ò4Ñ6Ô6ˆNØ-ð #ñ #�	Ý! )­TÑ2Ô2ð ØÝ  §¢¨vÑ!6Ô!6Ð!<¸"Ñ=Ô=×CÒCÑEÔE×KÒKÑMÔM�
Ø Ò/Ð/ØØ&Ÿ]š]¨8Ñ4Ô4�
Ý+5°jÅ$Ñ+GÔ+GÐO˜Z˜ZÈR�
Ø *§¢¨uÑ 5Ô 5�Ý-7¸ÅtÑ-LÔ-LÐT˜M˜MÐRT�	Ý1Ø—M’MÐ"3°Y·]²]À8Ñ5LÔ5LÑMÔMñô �ð Ð&Ø"�N�N�N�N�Nñ 'ñ#ð  ˆ4r   c                ó*  — 	 ddl m} t           |d¦  «        pd¦  «                             ¦   «         }t           |d¦  «        pd¦  «                             ¦   «                              ¦   «         }t          |pd¦  «                             ¦   «                              ¦   «         }|r
|r||k    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          ¦  «        r´d„ |
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_valueÚbase_urlrW   r\   r]   Nr`   ú:rQ   ra   rd   c                ó6   — h | ]}|                      ¦   «         ’ŒS r&   )rS   )r   Úns     r   ú	<setcomp>z._resolve_inference_base_url.<locals>.<setcomp>A  s    € Ð6Ð6Ð6 �1—7’7‘9”9Ð6Ð6Ð6r   re   )Úagent.auxiliary_clientr‚   r   rR   rS   Ú	Exceptionr+   rf   rg   r2   Úfilterr:   rh   Úsplitrk   )rZ   r\   r‚   ÚruntimeÚruntime_providerrY   rl   rm   rƒ   ro   Úcandidate_namesru   rt   re   rw   Úburd   Úloweredrv   r~   s                       r   Ú_resolve_inference_base_urlr‘     s­  € ð
	Ø>Ð>Ð>Ð>Ð>Ð>åÐ)Ð)¨*Ñ5Ô5Ð;¸Ñ<Ô<×BÒBÑDÔDˆÝÐ2Ð2°:Ñ>Ô>ÐDÀ"ÑEÔE×KÒKÑMÔM×SÒSÑUÔUÐÝ   ¨RÑ0Ô0×6Ò6Ñ8Ô8×>Ò>Ñ@Ô@ÐØð 	Ð.ð 	Ð2DÐHXÒ2XÐ2XØˆNøøÝð ð ð Øˆðøøøõ �c�4Ñ Ô ð Øˆrà—G’G˜GÑ$Ô$€MÝ1;¸MÍ4Ñ1PÔ1PÐ X  ÐVX€IÝ�9—=’= Ñ,Ô,Ð2°Ñ3Ô3×9Ò9Ñ;Ô;€HØð Øˆå˜)Ÿ-š-¨
Ñ3Ô3Ð9°rÑ:Ô:×@Ò@ÑBÔB€OÝ #¡¤€OÝ�D˜8 _Ð5Ñ6Ô6ð /ð /ˆØ×Ò˜AÑÔÐØ�7Š7‰9Œ9×Ò 	Ñ*Ô*ð 	/Ø×Ò §¢¨¨Q¡¤°Ô 2Ñ3Ô3Ð3Ð3à×Ò ¨!  Ñ.Ô.Ð.Ð.à—G’G˜KÑ(Ô(€MÝ�-¥Ñ&Ô&ð Ø#ð 	ð 	ˆDØ!×%Ò% dÑ+Ô+ˆEÝ˜%¥Ñ&Ô&ð Ý˜Ÿš :Ñ.Ô.Ð4°"Ñ5Ô5×;Ò;Ñ=Ô=�Øð Ø�I�I�Iøà—w’wÐ1Ñ2Ô2ÐÝÐ"¥DÑ)Ô)ð 
Ø6Ð6 oÐ6Ñ6Ô6ˆØ)ð 	ð 	ˆIÝ˜i­Ñ.Ô.ð ØÝ˜YŸ]š]¨6Ñ2Ô2Ð8°bÑ9Ô9×?Ò?ÑAÔAˆJØ Ð0Ð0°Z×5EÒ5EÑ5GÔ5GÈwÐ5VÐ5VØÝ�Y—]’] :Ñ.Ô.Ð4°"Ñ5Ô5×;Ò;Ñ=Ô=ˆBØð Ø�	�	�	ðð ˆ2s   ‚B0B4 Â4
CÃ C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.rW   ÚollamaTFr   )Údetect_local_server_type)rR   rS   Úagent.model_metadatar”   r‰   )r\   rƒ   ru   r”   s       r   Ú_should_probe_ollama_visionr–   O  s�   € à	ˆ�R×ÒÑ Ô ×&Ò&Ñ(Ô(€AØˆH‚}€}ØˆtØð ØˆuðØAÐAÐAÐAÐAÐAà'Ð'¨Ñ1Ô1°XÒ=Ð=øÝð ð ð Øˆuˆuðøøø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š   ^  sG   € å�c�3ÑÔð Øˆ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Ú	auxiliaryrc   r\   rW   r]   rƒ   >   rW   r
   T)r+   rf   rg   r   rR   rS   )rZ   Úauxrc   r\   r]   rƒ   s         r   Ú_explicit_aux_vision_overriderž   h  s   € õ �c�4Ñ Ô ð ØˆuØ
�'Š'�+Ñ
Ô
Ð
$ "€CÝ�c�4Ñ Ô ð ØˆuØ�WŠW�XÑÔÐ$ "€FÝ�f�dÑ#Ô#ð Øˆuå�6—:’:˜jÑ)Ô)Ð/¨RÑ0Ô0×6Ò6Ñ8Ô8×>Ò>Ñ@Ô@€HÝ�—
’
˜7Ñ#Ô#Ð) rÑ*Ô*×0Ò0Ñ2Ô2€EÝ�6—:’:˜jÑ)Ô)Ð/¨RÑ0Ô0×6Ò6Ñ8Ô8€Hð �<ÐÐ¨Ð°hÐØˆuØˆ4r   c               ób  — |�s	 ddl m} t           |d¦  «        pd¦  «                             ¦   «                              ¦   «         }t           |d¦  «        pd¦  «                             ¦   «         }t          | pd¦  «                             ¦   «                              ¦   «         }t          |pd¦  «                             ¦   «         }||k    r2||k    r,t           |d¦  «        pd¦  «                             ¦   «         }n# t
          $ r Y nw xY w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.
    r   r�   r\   rW   r]   rY   rX   N)Úget_model_capabilitiesu2   image_routing: caps lookup failed for %s:%s â€” %sr“   zhttp://localhost:11434/v1)Úquery_ollama_supports_visionu:   image_routing: ollama vision probe failed for %s:%s â€” %s)rˆ   r‚   r   rR   rS   r‰   r   Úagent.models_devr    ÚloggerÚdebugr"   r_   r‘   r–   r•   r¡   )r\   r]   rZ   rY   r‚   r�   Úruntime_modelÚlookup_providerÚlookup_modelÚoverrideÚcapsr    Úexcrƒ   r¡   Úollama_visions                   r   Ú_lookup_supports_visionr¬   ƒ  sù  € ð$ ñ ð	ØBÐBÐBÐBÐBÐBå"Ø#Ð# JÑ/Ô/Ð5°2ñ ô  çŠe‰gŒg—e’e‘g”gð õ  Ð 3Ð 3°GÑ <Ô <Ð BÀÑCÔC×IÒIÑKÔKˆMÝ! ( .¨bÑ1Ô1×7Ò7Ñ9Ô9×?Ò?ÑAÔAˆOÝ˜u˜{¨Ñ+Ô+×1Ò1Ñ3Ô3ˆLØ ?Ò2Ð2°}ÈÒ7TÐ7TÝ%(Ø'Ð'Ð(<Ñ=Ô=ÐCÀñ&ô &ç’%‘'”'ð #øøõ ð 	ð 	ð 	ØˆDð	øøøõ )ØØØØ-ð	ñ ô €Hð ÐØˆØð ˜5ð ØˆtØ€DðaØ;Ð;Ð;Ð;Ð;Ð;Ø%Ð% h°Ñ6Ô6ˆˆøÝð að að aÝ�ŠÐIÈ8ÐUZÐ\_Ñ`Ô`Ð`Ð`Ð`Ð`Ð`Ð`øøøøðaøøøàÐÝ�DÔ(Ñ)Ô)Ð)å*¨3°Ñ9Ô9€HØð /˜˜ R×.Ò.Ñ0Ô0×6Ò6Ñ8Ô8¸HÒDÐDØ.ˆÝ" 8¨XÑ6Ô6ð ð	ØIÐIÐIÐIÐIÐIà8Ð8¸ÀÑIÔIˆMØÐ(Ø$Ð$ð )øåð 	ð 	ð 	Ý�LŠLØLØØØñ	ô ð ð ð ð ð ð øøøøð	øøøð ˆ4sB   …D D Ä
DÄDÄ6E	 Å	
E:ÅE5Å5E:Ç$G; Ç;
H,ÈH'È'H,c               ól  — d}t          |t          ¦  «        rN|                     d¦  «        pi }t          |t          ¦  «        r"t          |                     d¦  «        ¦  «        }|dk    rdS |dk    rdS |rt	          | |||¬¦  «        }nt	          | ||¦  «        }|du rdS t          |¦  «        rdS dS )a~  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.
      requested_provider: provider identity before runtime canonicalization.
    r
   ÚagentÚimage_input_moder   r   rX   T)r+   rf   rg   rš   r¬   rž   )r\   r]   rZ   rY   Úmode_cfgÚ	agent_cfgÚsupportss          r   Údecide_image_input_moder³   Í  sé   € ð €HÝ�#•tÑÔð GØ—G’G˜GÑ$Ô$Ð*¨ˆ	Ý�i¥Ñ&Ô&ð 	GÝ# I§M¢MÐ2DÑ$EÔ$EÑFÔFˆHà�8ÒÐØˆxØ�6ÒÐØˆvð ð 
AÝ*ØØØØ1ð	
ñ 
ô 
ˆˆõ +¨8°U¸CÑ@Ô@ˆØ�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)rh   ri   r   rS   )rA   ÚbrandÚheads      r   Ú_sniff_mime_from_bytesrÐ     s—  € ð ð Øˆtà
‡~‚~Ð*Ñ+Ô+ð Øˆ{à
‡~‚~�oÑ&Ô&ð Øˆ|à
ˆ2ˆAˆ2„wÐ(Ð(Ð(Øˆ{å
ˆ3�x„x�2‚~€~˜#˜b˜q˜bœ' WÒ,Ð,°°Q°r°T´¸gÒ1EÐ1EØˆ|à
‡~‚~�eÑÔð Øˆ{å
ˆ3�x„x�2‚~€~˜#˜a ˜cœ( gÒ-Ð-Ø�A�b�D”	ˆØÐ&Ð&Ð&Ø�<Øð 
ð 
ð 
ð  �<à
ˆ2ˆAˆ2„wÐ*Ð*Ð*Øˆ|à
ˆ2ˆAˆ2„wÐ%Ò%Ð%Øˆ~àˆt�ˆtŒ9×ÒÑÔ×#Ò#Ñ%Ô%€DØ‡‚�xÑ Ô ð # D§O¢O°GÑ$<Ô$<ð #Ø�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   )ÚImagez·image_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ë   O  s  € ðØÐÐÐÐÐÐøÝð ð ð Ý�ŠðLñ	
ô 	
ð 	
ð
 ˆtˆtðøøøðØÐÐÐà×(Ò(Ñ*Ô*Ð*Ð*øÝð ð ð ØˆðøøøðØÐÐÐÐøÝð ð ð ØˆðøøøðØÐÐÐÐÐà�ZŠZ˜˜ ™œÑ%Ô%ð 	"¨ð ŒwÐ=Ð=Ð=Ø—Z’Z Ñ'Ô'�Ø�'‘)”)ˆCØ�GŠG�C °ˆGÑ6Ô6Ð6Ø—<’<‘>”>ð	"ð 	"ð 	"ð 	"ñ 	"ô 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"øøøð 	"ð 	"ð 	"ð 	"ð 	"ð 	"øõ ð ð ð Ý�ŠØJÈCñ	
ô 	
ð 	
ð ˆtˆtˆtˆtˆtøøøøð	øøø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 )	z»Return 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   rh   ÚsuffixrS   rg   )r6   rA   ÚsniffedÚmimeÚ_rï   s         r   Ú_guess_mimeró   ƒ  s¤   € ð €Ý(¨Ñ-Ô-ˆØð 	ØˆNÝÔ"¥3 t¡9¤9Ñ-Ô-�G€Dˆ!Øð �—’ Ñ)Ô)ð Øˆð Œ[×ÒÑ Ô €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   z‰image_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ß   re   Úbase64Ú	b64encodeÚdecode)r6   rõ   rª   rA   rñ   Ú
transcodedÚb64s          r   Ú_file_to_data_urlr  �  s¤  € ð,	Ø;Ð;Ð;Ð;Ð;Ð;àÐ�c $™iœiÑ(Ô(Ð(Ð(øÝð ð ð Ý�ŠÐOÐQUÐWZÑ[Ô[Ð[ØˆtˆtˆtˆtˆtøøøøÝð ð ð àˆðøøøðØ�oŠoÑÔˆˆøÝð ð ð Ý�ŠÐ@À$ÈÑLÔLÐLØˆtˆtˆtˆtˆtøøøøðøøøõ �t Ð%Ñ%Ô%€DØÕ/Ð/Ð/Ý& sÑ+Ô+ˆ
ØÐÝ�NŠNð,ð �dñ	ô ð ð �4Ý�ŠØWØŒI�tñ	
ô 	
ð 	
ð ˆØˆÝ
Ô
˜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	  rW   zWhat do you see in this image?c              3  ó"   K  — | ]
}d |› d�V — ŒdS )z[Image attached at: Ú]Nr&   )r   ru   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ÐJ°qÐ2¨aÐ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_pathru   Údata_urlrE   r   Ú	base_textÚ
hint_linesÚcombined_textÚpartss                   r   Úbuild_native_content_partsr  Ø  s\  € ðL €GØ(*€KØ "€NØ!€Màð -ð -ˆÝ�‰NŒNˆØ�xŠx‰zŒzð 	 §¢¡¤ð 	Ø�NŠN�3˜x™=œ=Ñ)Ô)Ð)ØÝ$ QÑ'Ô'ˆØð 	Ø�NŠN�3˜x™=œ=Ñ)Ô)Ð)ØØ×ÒØØ Ð*ð
ð 
ñ 	ô 	ð 	ð 	×Ò�c (™mœmÑ,Ô,Ð,Ð,àÐ˜Rð "ð "ˆØˆy�b×ÒÑ!Ô!ˆØð 	ØØ×ÒØØ ˜ð
ð 
ñ 	ô 	ð 	ð 	×Ò˜SÑ!Ô!Ð!Ð!àˆO˜×"Ò"Ñ$Ô$€Dð ð ˜ð ØÐ<Ð<ˆ	Ø "ˆ
Ø×ÒÐNÐN¸~ÐNÑNÔNÑNÔNÐNØ×ÒÐJÐJ¸MÐJÑJÔJÑJÔJÐJØ$Ð*Ð*Ð*¨T¯YªY°zÑ-BÔ-BÑBˆØ06ÀÐ'NÐ'NÐ&OˆØ�Š�[Ñ!Ô!Ð!Ø�gˆ~Ðð €EØð 5Ø�Š˜f¨dÐ3Ð3Ñ4Ô4Ð4Ø�'ˆ>Ðr   )r³   r  rF   )r   r   r   r   )rA   r   r   rO   )
rZ   r[   r\   r   r]   r   rY   r   r   rO   )rZ   r[   r\   r   r   r   )r\   r   rƒ   r   r   r"   )rA   r   r   r   )rZ   r[   r   r"   r%   )
r\   r   r]   r   rZ   r[   rY   r   r   rO   )
r\   r   r]   r   rZ   r[   rY   r   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€ð€ð —X’XÐAÐA°[ÐAÑAÔAÑAÔAÐ ð
 "�r”zØ6Ð9KÑKÈfÑTØ„Mñô Ð ð �”
Ø!Ð$6Ñ6Ð9OÑOØ„Mñô €ðBð Bð Bð BðZ ˆyÐ3Ð3Ð3Ñ4Ô4€Ø�	Ð5Ð5Ð5Ñ6Ô6€ðð ð ð ð, !ð]ð ]ð ]ð ]ð ]ð ]ð@8ð 8ð 8ð 8ðvð ð ð ðð ð ð ðð ð ð ð< %)ðGð
 !ðGð Gð Gð Gð Gð Gð^ !ð-ð -ð -ð -ð -ð -ð~0ð 0ð 0ð 0ð|  )˜yð *ð *ð *ñ  ô  Ð ð
1ð 1ð 1ð 1ðh ð  ð  ð  ð  ð48'ð 8'ð 8'ð 8'ð| '+ðVð Vð Vð Vð Vðrð ð €€€r   