
    g5ji5                    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 )zStable short identifier used in ``image_gen.provider`` config.

        Lowercase, no spaces. Examples: ``fal``, ``openai``, ``replicate``.
        N selfs    >/home/rurouni/.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 )zReturn 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).
        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 )zClamp 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     eS!! $##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 )zCoerce 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% edE]++ tC % %dC   	%TZZ\\ 	%JJtzz||$$$;$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&1DJJtdJ+++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 )zDecode 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JLLRaR EF!E!ER!E!E%!E!E)!E!EED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
   & OOO||C|>>H
 $((88>BEEc1MMaPVVXX^^``L(,,\::I99S!$$Q'--//8 	 	C  S++ %(F]]EE	 						 	 	)	)/	:	:BJLLRaR EF!E!ER!E!E%!E!E)!E!EEDM	4 B**i*@@ 	 	E SZZ'My((


KKMMMM   D dddiK.Hddd   HHUOOOO	                	KKMMMM 	 	 	D	OS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   U  sh    & $ G  %KKMM 	% 	%DAq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   v  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" | | | | |sw | | |H          (    	     2    %B B B B B BX &*     H ',       r   