§
    ÷žyj²6  ã                  ó0  — U 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dZded<   d	Z G d
„ dej        ¦  «        Zd;d„Zd<d„Zd=d„Zdddœd>d„Zdddddd œZdd!d"d#œd?d)„Zd*dd+œd@d4„Zd5d6d6d6ed7œdAd:„ZdS )Bu*  
Image Generation Provider ABC
=============================

Defines the pluggable-backend interface for image generation. Providers register
instances via ``PluginContext.register_image_gen_provider()``; the active one
(selected via ``image_gen.provider`` in ``config.yaml``) services every
``image_generate`` tool call.

Providers live in ``<repo>/plugins/image_gen/<name>/`` (built-in, auto-loaded
as ``kind: backend``) or ``~/.hermes/plugins/image_gen/<name>/`` (user, opt-in
via ``plugins.enabled``).

Unified surface
---------------
One tool â€” ``image_generate`` â€” covers **text-to-image** and
**image-to-image / image editing**. The router is the presence of
``image_url`` (and/or ``reference_image_urls``): if any source image is
provided, the provider routes to its image-to-image / edit endpoint; if
omitted, the provider routes to text-to-image. Users pick one **model**
(e.g. nano-banana-pro, gpt-image-2, grok-imagine-image); the provider
handles which underlying endpoint to hit. This mirrors the ``video_gen``
provider design (``agent/video_gen_provider.py``) so the two surfaces
stay learnable together.

Response shape
--------------
All providers return a dict that :func:`success_response` / :func:`error_response`
produce. The tool wrapper JSON-serializes it. Keys:

    success        bool
    image          str | None       URL or absolute file path
    model          str              provider-specific model identifier
    prompt         str              echoed prompt
    aspect_ratio   str              "landscape" | "square" | "portrait"
    modality       str              "text" | "image" (which mode was used)
    provider       str              provider name (for diagnostics)
    error          str              only when success=False
    error_type     str              only when success=False
é    )ÚannotationsN)ÚPath)ÚAnyÚDictÚListÚOptionalÚTuple)Ú	landscapeÚsquareÚportraitzTuple[str, ...]ÚVALID_ASPECT_RATIOSr
   c                  ó²   — e Zd ZdZeej        dd„¦   «         ¦   «         Zedd„¦   «         Zdd„Z	dd	„Z
dd„Zdd„Zdd„Zej        efdddœdd„¦   «         ZdS )ÚImageGenProvideru¼   Abstract base class for an image generation backend.

    Subclasses must implement :meth:`generate`. Everything else has sane
    defaults â€” override only what your provider needs.
    ÚreturnÚstrc                ó   — dS )z”Stable short identifier used in ``image_gen.provider`` config.

        Lowercase, no spaces. Examples: ``fal``, ``openai``, ``replicate``.
        N© ©Úselfs    ú>/home/ragecks/.hermes/hermes-agent/agent/image_gen_provider.pyÚnamezImageGenProvider.nameG   ó   € € € ó    c                ó4   — | j                              ¦   «         S )zMHuman-readable label shown in ``hermes tools``. Defaults to ``name.title()``.)r   Útitler   s    r   Údisplay_namezImageGenProvider.display_nameO   s   € ð Œy�ŠÑ Ô Ð r   Úboolc                ó   — dS )zÂReturn True when this provider can service calls.

        Typically checks for a required API key. Default: True
        (providers with no external dependencies are always available).
        Tr   r   s    r   Úis_availablezImageGenProvider.is_availableT   s	   € ð ˆtr   úList[Dict[str, Any]]c                ó   — g S )a  Return catalog entries for ``hermes tools`` model picker.

        Each entry::

            {
                "id": "gpt-image-1.5",               # required
                "display": "GPT Image 1.5",          # optional; defaults to id
                "speed": "~10s",                     # optional
                "strengths": "...",                  # optional
                "price": "$...",                     # optional
            }

        Default: empty list (provider has no user-selectable models).
        r   r   s    r   Úlist_modelszImageGenProvider.list_models\   s	   € ð ˆ	r   úDict[str, Any]c                ó   — | j         ddg dœS )a5  Return provider metadata for the ``hermes tools`` picker.

        Used by ``tools_config.py`` to inject this provider as a row in
        the Image Generation provider list. Shape::

            {
                "name": "OpenAI",                     # picker label
                "badge": "paid",                      # optional short tag
                "tag": "One-line description...",     # optional subtitle
                "env_vars": [                         # keys to prompt for
                    {"key": "OPENAI_API_KEY",
                     "prompt": "OpenAI API key",
                     "url": "https://platform.openai.com/api-keys"},
                ],
            }

        Default: minimal entry derived from ``display_name``. Override to
        expose API key prompts and custom badges.
        Ú )r   ÚbadgeÚtagÚenv_vars)r   r   s    r   Úget_setup_schemaz!ImageGenProvider.get_setup_schemam   s"   € ð* Ô%ØØØð	
ð 
ð 	
r   úOptional[str]c                óh   — |                       ¦   «         }|r|d                              d¦  «        S dS )z7Return the default model id, or None if not applicable.r   ÚidN)r"   Úget)r   Úmodelss     r   Údefault_modelzImageGenProvider.default_modelˆ   s6   € à×!Ò!Ñ#Ô#ˆØð 	'Ø˜!”9—=’= Ñ&Ô&Ð&Øˆtr   c                ó   — dgddœS )uã  Return what this provider supports.

        Returned dict (all keys optional)::

            {
                "modalities": ["text", "image"],   # which inputs the backend accepts
                "max_reference_images": 9,          # cap for reference_image_urls
            }

        ``modalities`` declares whether the active backend/model supports
        text-to-image (``"text"``), image-to-image / editing (``"image"``),
        or both. The tool layer surfaces this in the dynamic schema so the
        model knows when ``image_url`` is honored. Used by ``hermes tools``
        for the picker too. Default: text-only (backward compatible â€” a
        provider that doesn't override this advertises text-to-image only).
        Útextr   )Ú
modalitiesÚmax_reference_imagesr   r   s    r   ÚcapabilitieszImageGenProvider.capabilities�   s   € ð$ "˜(Ø$%ð
ð 
ð 	
r   N)Ú	image_urlÚreference_image_urlsÚpromptÚaspect_ratior5   r6   úOptional[List[str]]Úkwargsr   c               ó   — dS )u:  Generate an image from a text prompt, or edit/transform a source image.

        Routing: if ``image_url`` (or any ``reference_image_urls``) is
        provided, the provider should route to its image-to-image / edit
        endpoint; otherwise text-to-image. ``image_url`` is the primary
        source image to edit; ``reference_image_urls`` are additional
        style/composition references (provider clamps to its declared
        ``max_reference_images``).

        Implementations should return the dict from :func:`success_response`
        or :func:`error_response`. ``kwargs`` may contain forward-compat
        parameters future versions of the schema will expose â€”
        implementations MUST ignore unknown keys (no TypeError).

        Known optional kwarg: ``upscale`` (bool) â€” when true, the caller
        requests a post-generation high-resolution pass through the
        backend's upscaler/enhancer. Providers without an upscaler simply
        ignore it; providers that honor it should report ``upscaled: True``
        in the response ``extra``.
        Nr   )r   r7   r8   r5   r6   r:   s         r   ÚgeneratezImageGenProvider.generate¥   r   r   )r   r   )r   r   )r   r    )r   r#   )r   r*   )r7   r   r8   r   r5   r*   r6   r9   r:   r   r   r#   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__ÚpropertyÚabcÚabstractmethodr   r   r   r"   r)   r/   r4   ÚDEFAULT_ASPECT_RATIOr<   r   r   r   r   r   @   s  € € € € € ðð ð ØÔðð ð ñ Ôñ „Xðð ð!ð !ð !ñ „Xð!ðð ð ð ðð ð ð ð"
ð 
ð 
ð 
ð6ð ð ð ð
ð 
ð 
ð 
ð, 	Ôð 1ðð
 $(Ø48ðð ð ð ð ñ Ôðð ð r   r   Úvaluer*   r   r   c                óª   — t          | t          ¦  «        st          S |                      ¦   «                              ¦   «         }|t
          v r|S t          S )z¸Clamp an aspect_ratio value to the valid set, defaulting to landscape.

    Invalid values are coerced rather than rejected so the tool surface is
    forgiving of agent mistakes.
    )Ú
isinstancer   rD   ÚstripÚlowerr   )rE   Úvs     r   Úresolve_aspect_ratiorK   Ê   sL   € õ �e�SÑ!Ô!ð $Ý#Ð#Ø�Š‰Œ×ÒÑÔ€AØÕÐÐØˆÝÐr   r   r9   c                ó,  — | €dS t          | t          ¦  «        r| g} t          | t          t          f¦  «        sdS g }| D ]R}t          |t          ¦  «        r;|                     ¦   «         r'|                     |                     ¦   «         ¦  «         ŒS|pdS )zÿCoerce a reference-image argument into a clean list of URL/path strings.

    Accepts a single string or a list; strips blanks and whitespace. Returns
    ``None`` when nothing usable remains so providers can treat "no refs" as a
    single sentinel.
    N)rG   r   ÚlistÚtuplerH   Úappend)rE   ÚoutÚitems      r   Únormalize_reference_imagesrR   Ø   sœ   € ð €}ØˆtÝ�%�ÑÔð Ø�ˆÝ�e�d¥E˜]Ñ+Ô+ð ØˆtØ€CØð %ð %ˆÝ�d�CÑ Ô ð 	% T§Z¢Z¡\¤\ð 	%Ø�JŠJ�t—z’z‘|”|Ñ$Ô$Ð$øØˆ;�$Ðr   r   c                 ó`   — ddl m}   | ¦   «         dz  dz  }|                     dd¬¦  «         |S )zBReturn ``$HERMES_HOME/cache/images/``, creating parents as needed.r   )Úget_hermes_homeÚcacheÚimagesT)ÚparentsÚexist_ok)Úhermes_constantsrT   Úmkdir)rT   Úpaths     r   Ú_images_cache_dirr\   ì   sF   € à0Ð0Ð0Ð0Ð0Ð0àˆ?ÑÔ˜wÑ&¨Ñ1€DØ‡J‚J�t d€JÑ+Ô+Ð+Ø€Kr   ÚimageÚpng)ÚprefixÚ	extensionÚb64_datar_   r`   c               ó2  — t          j        | ¦  «        }t          j                             ¦   «                              d¦  «        }t          j        ¦   «         j        dd…         }t          ¦   «         |› d|› d|› d|› �z  }| 	                    |¦  «         |S )zÔDecode base64 image data and write it under ``$HERMES_HOME/cache/images/``.

    Returns the absolute :class:`Path` to the saved file.

    Filename format: ``<prefix>_<YYYYMMDD_HHMMSS>_<short-uuid>.<ext>``.
    ú%Y%m%d_%H%M%SNé   Ú_ú.)
Úbase64Ú	b64decodeÚdatetimeÚnowÚstrftimeÚuuidÚuuid4Úhexr\   Úwrite_bytes)ra   r_   r`   ÚrawÚtsÚshortr[   s          r   Úsave_b64_imagers   õ   s”   € õ Ô
˜8Ñ
$Ô
$€CÝ	Ô	×	Ò	Ñ	 Ô	 ×	)Ò	)¨/Ñ	:Ô	:€BÝŒJ‰LŒLÔ˜R˜a˜RÔ €EÝÑÔ FÐ!EÐ!E¨RÐ!EÐ!E°%Ð!EÐ!E¸)Ð!EÐ!EÑE€DØ×Ò�SÑÔÐØ€Kr   ÚjpgÚwebpÚgif)z	image/pngz
image/jpegz	image/jpgz
image/webpz	image/gifg      N@i  �)r_   ÚtimeoutÚ	max_bytesÚurlrw   Úfloatrx   Úintc          	     óø  — ddl }|                     | |d¬¦  «        }|                     ¦   «          |j                             d¦  «        pd                     dd¦  «        d                              ¦   «                              ¦   «         }t                               |¦  «        }|€W|                      d	d¦  «        d                              ¦   «         }d
D ]&}	|                     d|	› �¦  «        r|	dk    rdn|	} nŒ'|€d}t          j	         
                    ¦   «                              d¦  «        }
t          j        ¦   «         j        dd…         }t          ¦   «         |› d|
› d|› d|› �z  }d}|                     d¦  «        5 }|                     d¬¦  «        D ]…}|sŒ|t%          |¦  «        z  }||k    rS|                     ¦   «          	 |                     ¦   «          n# t*          $ r Y nw xY wt-          d| › d|dz  › d�¦  «        ‚|                     |¦  «         Œ†	 ddd¦  «         n# 1 swxY w Y   |dk    r9	 |                     ¦   «          n# t*          $ r Y nw xY wt-          d| › d�¦  «        ‚|S )u£  Download an image URL and write it under ``$HERMES_HOME/cache/images/``.

    Used by providers (xAI, fallback OpenAI) whose API returns an *ephemeral*
    URL instead of inline base64 â€” those URLs frequently expire before a
    downstream consumer (Telegram ``send_photo``, browser fetch) can resolve
    them, so we materialise the bytes locally at tool-completion time.
    Mirrors :func:`save_b64_image`'s shape so providers can swap in one line.

    Returns the absolute :class:`Path` to the saved file.  Raises on any
    network / HTTP / oversize / non-image-content-type error so callers can
    fall back to returning the bare URL with a clear error message.
    r   NT)rw   ÚstreamzContent-Typer%   ú;é   ú?)r^   rt   Újpegru   rv   rf   r�   rt   r^   rc   rd   re   Úwbi   )Ú
chunk_sizez	Image at z	 exceeds i   zMB cap; refusing to cache.z% returned 0 bytes; refusing to cache.)Úrequestsr-   Úraise_for_statusÚheadersÚsplitrH   rI   Ú_URL_IMAGE_CONTENT_TYPESÚendswithri   rj   rk   rl   rm   rn   r\   ÚopenÚiter_contentÚlenÚcloseÚunlinkÚOSErrorÚ
ValueErrorÚwrite)ry   r_   rw   rx   r„   ÚresponseÚcontent_typer`   Úurl_pathÚextrq   rr   r[   Úbytes_writtenÚfhÚchunks                   r   Úsave_url_imager™     s
  € ð& €O€O€Oà�|Š|˜C¨¸ˆ|Ñ>Ô>€HØ×ÒÑÔÐð
 Ô$×(Ò(¨Ñ8Ô8Ð>¸B×EÒEÀcÈ1ÑMÔMÈaÔP×VÒVÑXÔX×^Ò^Ñ`Ô`€LÝ(×,Ò,¨\Ñ:Ô:€IØÐØ—9’9˜S !Ñ$Ô$ QÔ'×-Ò-Ñ/Ô/ˆØ8ð 	ð 	ˆCØ× Ò   S  Ñ+Ô+ð Ø%(¨F¢] ]˜E˜E¸�	Ø�ðð ÐØˆ	å	Ô	×	Ò	Ñ	 Ô	 ×	)Ò	)¨/Ñ	:Ô	:€BÝŒJ‰LŒLÔ˜R˜a˜RÔ €EÝÑÔ FÐ!EÐ!E¨RÐ!EÐ!E°%Ð!EÐ!E¸)Ð!EÐ!EÑE€Dà€MØ	�Š�4‰Œð ˜BØ×*Ò*°iÐ*Ñ@Ô@ð 	ð 	ˆEØð ØØ�S ™ZœZÑ'ˆMØ˜yÒ(Ð(Ø—’‘
”
�
ðØ—K’K‘M”M�M�MøÝð ð ð Ø�Dðøøøå Ød ÐdÐd¨i¸KÑ.HÐdÐdÐdñô ð ð �HŠH�U‰OŒOˆOˆOð	ðð ð ñ ô ð ð ð ð ð ð øøøð ð ð ð ð  ˜ÒÐð	Ø�KŠK‰MŒMˆMˆMøÝð 	ð 	ð 	ØˆDð	øøøåÐO SÐOÐOÐOÑPÔPÐPà€KsI   ÆAH0ÇG$Ç#H0Ç$
G1Ç.H0Ç0G1Ç12H0È0H4È7H4ÉI É
I$É#I$r1   )ÚmodalityÚextraÚmodelr7   r8   Úproviderrš   r›   úOptional[Dict[str, Any]]r#   c                ó~   — d| |||||dœ}|r0|                      ¦   «         D ]\  }}	|                     ||	¦  «         Œ|S )u”  Build a uniform success response dict.

    ``image`` may be an HTTP URL or an absolute filesystem path (for b64
    providers like OpenAI). ``modality`` is ``"text"`` (text-to-image) or
    ``"image"`` (image-to-image / editing) â€” indicates which endpoint was
    actually hit, useful for diagnostics. Callers that need to pass through
    additional backend-specific fields can supply ``extra``.
    T)Úsuccessr]   rœ   r7   r8   rš   r�   )ÚitemsÚ
setdefault)
r]   rœ   r7   r8   r�   rš   r›   ÚpayloadÚkrJ   s
             r   Úsuccess_responser¥   [  sh   € ð& ØØØØ$ØØðð €Gð ð %Ø—K’K‘M”Mð 	%ð 	%‰DˆAˆqØ×Ò˜q !Ñ$Ô$Ð$Ð$Ø€Nr   Úprovider_errorr%   )Ú
error_typer�   rœ   r7   r8   Úerrorr§   c           	     ó   — dd| |||||dœS )z$Build a uniform error response dict.FN)r    r]   r¨   r§   rœ   r7   r8   r�   r   )r¨   r§   r�   rœ   r7   r8   s         r   Úerror_responserª   |  s+   € ð ØØØ ØØØ$Øð	ð 	ð 	r   )rE   r*   r   r   )rE   r   r   r9   )r   r   )ra   r   r_   r   r`   r   r   r   )
ry   r   r_   r   rw   rz   rx   r{   r   r   )r]   r   rœ   r   r7   r   r8   r   r�   r   rš   r   r›   rž   r   r#   )r¨   r   r§   r   r�   r   rœ   r   r7   r   r8   r   r   r#   ) r@   Ú
__future__r   rB   rg   ri   Úloggingrl   Úpathlibr   Útypingr   r   r   r   r	   Ú	getLoggerr=   Úloggerr   Ú__annotations__rD   ÚABCr   rK   rR   r\   rs   rˆ   r™   r¥   rª   r   r   r   ú<module>r³      s  ðð'ð 'ð 'ðR #Ð "Ð "Ð "Ð "Ð "à 
€
€
€
Ø €€€Ø €€€Ø €€€Ø €€€Ø Ð Ð Ð Ð Ð Ø 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3à	ˆÔ	˜8Ñ	$Ô	$€ð (KÐ Ð JÐ JÐ JÑ JØ"Ð ðBð Bð Bð Bð B�s”wñ Bô Bð BðT ð  ð  ð  ðð ð ð ð(ð ð ð ð Øð	ð ð ð ð ð ð2 ØØØØðð Ð ð ØØ%ðBð Bð Bð Bð Bð BðX Ø&*ðð ð ð ð ð ðH 'ØØØØ,ðð ð ð ð ð ð ð r   