
    `gji5                       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'dZdddded 	 	 	 	 	 	 	 	 	 	 	 	 	 d(d!Zy))u*  
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y	)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.
    c                     y)zStable short identifier used in ``image_gen.provider`` config.

        Lowercase, no spaces. Examples: ``fal``, ``openai``, ``replicate``.
        N selfs    K/root/.hermes/venv/lib/python3.12/site-packages/agent/image_gen_provider.pynamezImageGenProvider.nameG           c                6    | j                   j                         S )zMHuman-readable label shown in ``hermes tools``. Defaults to ``name.title()``.)r   titler   s    r   display_namezImageGenProvider.display_nameO   s     yy  r   c                     y)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     r   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   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   c                P    | j                         }|r|d   j                  d      S y)z7Return the default model id, or None if not applicable.r   idN)r   get)r   modelss     r   default_modelzImageGenProvider.default_model   s)    !!#!9==&&r   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_urlsc                    y)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   promptaspect_ratior/   r0   kwargss         r   generatezImageGenProvider.generate   r   r   )returnstr)r6   bool)r6   zList[Dict[str, Any]])r6   Dict[str, Any])r6   Optional[str])r2   r7   r3   r7   r/   r:   r0   Optional[List[str]]r4   r   r6   r9   )__name__
__module____qualname____doc__propertyabcabstractmethodr   r   r   r   r$   r)   r.   DEFAULT_ASPECT_RATIOr5   r   r   r   r   r   @   s        ! !"
6
, 	 1
 $(48 
 ! 2  
 r   r   c                    t        | t              st        S | j                         j	                         }|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.
    )
isinstancer7   rC   striplowerr   )valuevs     r   resolve_aspect_ratiorJ      s<     eS!##Ar   c                    | yt        | t              r| g} t        | t        t        f      syg }| D ]C  }t        |t              s|j	                         s%|j                  |j	                                E |xs 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)rE   r7   listtuplerF   append)rH   outitems      r   normalize_reference_imagesrQ      sr     }%edE]+C %dC TZZ\JJtzz|$% ;$r   c                 R    ddl m}   |        dz  dz  }|j                  dd       |S )zBReturn ``$HERMES_HOME/cache/images/``, creating parents as needed.r   )get_hermes_homecacheimagesT)parentsexist_ok)hermes_constantsrS   mkdir)rS   paths     r   _images_cache_dirr[      s,    0w&1DJJtdJ+Kr   imagepng)prefix	extensionc                  t        j                  |       }t        j                  j                         j	                  d      }t        j                         j                  dd }t               | d| d| d| z  }|j                  |       |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)b64_datar^   r_   rawtsshortrZ   s          r   save_b64_imagerr      s     

8
$C						 	)	)/	:BJJLRa EF81RD%)!EEDSKr   jpgwebpgif)z	image/pngz
image/jpegz	image/jpgz
image/webpz	image/gifg      N@i  )r^   timeout	max_bytesc          	     F   ddl }|j                  | |d      }|j                          |j                  j                  d      xs dj	                  dd      d   j                         j                         }t        j                  |      }|I| j	                  d	d      d   j                         }d
D ]!  }	|j                  d|	       s|	dk(  rdn|	} n |d}t        j                  j                         j                  d      }
t        j                         j                  dd }t               | d|
 d| d| z  }d}|j!                  d      5 }|j#                  d      D ]_  }|s|t%        |      z  }||kD  r6|j'                          	 |j)                          t-        d|  d|dz   d      |j/                  |       a 	 ddd       |dk(  r 	 |j)                          t-        d|  d      |S # t*        $ r Y cw xY w# 1 sw Y   ?xY w# t*        $ r Y 7w xY w)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)rv   streamzContent-Typer    ;   ?)r]   rs   jpegrt   ru   rd   r}   rs   r]   ra   rb   rc   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splitrF   rG   _URL_IMAGE_CONTENT_TYPESendswithrg   rh   ri   rj   rk   rl   r[   openiter_contentlencloseunlinkOSError
ValueErrorwrite)urlr^   rv   rw   r   responsecontent_typer_   url_pathextrp   rq   rZ   bytes_writtenfhchunks                   r   save_url_imager     s>   & ||C|>H
 $$((8>BEEc1MaPVVX^^`L(,,\:I99S!$Q'--/8 	C  1SE+%(F]E		 							 	)	)/	:BJJLRa EF81RD%)!EEDM	4 B**i*@ 	ESZ'My(
KKM !uIiK.H-IIcd  HHUO	  	KKM 9SE)NOPPK   &  		sB   <HG9!(HH 9	HHHHH	H H r+   )modalityextrac                r    d| |||||d}|r*|j                         D ]  \  }}	|j                  ||	        |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\   modelr2   r3   r   provider)items
setdefault)
r\   r   r2   r3   r   r   r   payloadkrI   s
             r   success_responser   U  sS    & $G KKM 	%DAqq!$	%Nr   provider_errorr    )
error_typer   r   r2   r3   c           	         dd| |||||dS )z$Build a uniform error response dict.FN)r   r\   errorr   r   r2   r3   r   r   )r   r   r   r   r2   r3   s         r   error_responser   v  s&      $	 	r   )rH   r:   r6   r7   )rH   r   r6   r;   )r6   r   )rn   r7   r^   r7   r_   r7   r6   r   )
r   r7   r^   r7   rv   floatrw   intr6   r   )r\   r7   r   r7   r2   r7   r3   r7   r   r7   r   r7   r   zOptional[Dict[str, Any]]r6   r9   )r   r7   r   r7   r   r7   r   r7   r2   r7   r3   r7   r6   r9   ) r?   
__future__r   rA   re   rg   loggingrj   pathlibr   typingr   r   r   r   r	   	getLoggerr<   loggerr   __annotations__rC   ABCr   rJ   rQ   r[   rr   r   r   r   r   r   r   r   <module>r      s  'R # 
      3 3			8	$ (K _ J" |sww |H ( 	  	
 
2   %B	B B 	B
 B 
BX &*  	
    $ H ',  	
    r   