
    `gjT                       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d
Zded<   dZ G d dej0                        Zd&dZddd	 	 	 	 	 	 	 d'dZddd	 	 	 	 	 	 	 d(dZdddddZdddd	 	 	 	 	 	 	 	 	 d)dZddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d*d Zd!ddddd"	 	 	 	 	 	 	 	 	 	 	 	 	 d+d#Z  G d$ d%e      Z!y),u  
Video Generation Provider ABC
=============================

Defines the pluggable-backend interface for video generation. Providers register
instances via ``PluginContext.register_video_gen_provider()``; the active one
(selected via ``video_gen.provider`` in ``config.yaml``) services every
``video_generate`` tool call.

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

Mirrors the ``image_gen`` provider design (``agent/image_gen_provider.py``) so
the two surfaces stay learnable together.

Unified surface
---------------
One tool — ``video_generate`` — covers **text-to-video** and **image-to-video**.
The router is the presence of ``image_url``: if it's set, the provider routes
to its image-to-video endpoint; if it's omitted, the provider routes to
text-to-video. Users pick one **model family** (e.g. Pixverse v6, Veo 3.1,
Kling O3 Standard); the provider handles which underlying FAL/xAI endpoint
to hit.

Video edit and video extend are intentionally NOT exposed in this surface —
the inconsistency across backends is too large for one unified tool. If
those use cases warrant attention later they can ship as separate tools.

Response shape
--------------
All providers return a dict built by :func:`success_response` /
:func:`error_response`. Keys:

    success         bool
    video           str | None      URL or absolute file path
    model           str             provider-specific model identifier
    prompt          str             echoed prompt
    modality        str             "text" | "image" (which mode was used)
    aspect_ratio    str             provider-native (e.g. "16:9") or ""
    duration        int             seconds (0 if not applicable)
    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)16:9z9:16z1:1z4:3z3:4z3:2z2:3zTuple[str, ...]COMMON_ASPECT_RATIOSr
   )480p540p720p1080pCOMMON_RESOLUTIONSr   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                  d	d	d	d	eed	d	d	d
		 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dd       Zy	)VideoGenProvideru   Abstract base class for a video 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 ``video_gen.provider`` config.

        Lowercase, no spaces. Examples: ``xai``, ``fal``, ``google``.
        N selfs    K/root/.hermes/venv/lib/python3.12/site-packages/agent/video_gen_provider.pynamezVideoGenProvider.nameR           c                6    | j                   j                         S )zMHuman-readable label shown in ``hermes tools``. Defaults to ``name.title()``.)r   titler   s    r   display_namezVideoGenProvider.display_nameZ   s     yy  r   c                     y)zReturn True when this provider can service calls.

        Typically checks for a required API key and optional-dependency
        import. Default: True.
        Tr   r   s    r   is_availablezVideoGenProvider.is_available_   s     r   c                    g S )a  Return catalog entries for ``hermes tools`` model picker.

        Each entry represents a **model family** that supports text-to-video
        and/or image-to-video routing internally::

            {
                "id": "veo-3.1",                       # required
                "display": "Veo 3.1",                  # optional; defaults to id
                "speed": "~60s",                       # optional
                "strengths": "...",                    # optional
                "price": "$0.20/s",                    # optional
                "modalities": ["text", "image"],       # optional, advisory
            }

        Default: empty list (provider has no user-selectable models).
        r   r   s    r   list_modelszVideoGenProvider.list_modelsg   s	    " 	r   c                $    | j                   ddg dS )z9Return provider metadata for the ``hermes tools`` picker. )r   badgetagenv_vars)r   r   s    r   get_setup_schemaz!VideoGenProvider.get_setup_schemaz   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VideoGenProvider.default_model   s)    !!#!9==&&r   c           	     N    dgt        t              t        t              ddddddS )a  Return what this provider supports.

        Returned dict (all keys optional)::

            {
                "modalities": ["text", "image"],      # which inputs the backend accepts
                "aspect_ratios": ["16:9", "9:16", ...],
                "resolutions": ["720p", "1080p"],
                "max_duration": 15,                   # seconds
                "min_duration": 1,
                "supports_audio": True,
                "supports_negative_prompt": True,
                "max_reference_images": 7,
            }

        Used by the tool layer for soft validation and by ``hermes tools``
        for the picker. Default: text-only.
        text
      Fr   )
modalitiesaspect_ratiosresolutionsmax_durationmin_durationsupports_audiosupports_negative_promptmax_reference_images)listr   r   r   s    r   capabilitieszVideoGenProvider.capabilities   s4    ( "(!"67 23#(-$%	
 		
r   N	model	image_urlreference_image_urlsdurationaspect_ratio
resolutionnegative_promptaudioseedc       	             y)u  Generate a video from a prompt (text-to-video) or animate an image
        (image-to-video).

        Routing: if ``image_url`` is provided, the provider should route to
        its image-to-video endpoint; otherwise text-to-video. The plugin
        is responsible for picking the right underlying endpoint within
        the user's chosen model family.

        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   promptr<   r=   r>   r?   r@   rA   rB   rC   rD   kwargss               r   generatezVideoGenProvider.generate   r   r   returnstrrJ   bool)rJ   zList[Dict[str, Any]])rJ   Dict[str, Any])rJ   Optional[str]rF   rK   r<   rO   r=   rO   r>   zOptional[List[str]]r?   Optional[int]r@   rK   rA   rK   rB   rO   rC   zOptional[bool]rD   rQ   rG   r   rJ   rN   )__name__
__module____qualname____doc__propertyabcabstractmethodr   r   r   r!   r'   r,   r:   DEFAULT_ASPECT_RATIODEFAULT_RESOLUTIONrH   r   r   r   r   r   K   s       ! !&

< 	
  $#'48"&0,)- $" 	
 ! 2     '    
 r   r   c                 R    ddl m}   |        dz  dz  }|j                  dd       |S )zBReturn ``$HERMES_HOME/cache/videos/``, creating parents as needed.r   )get_hermes_homecachevideosT)parentsexist_ok)hermes_constantsr\   mkdir)r\   paths     r   _videos_cache_dirrd      s,    0w&1DJJtdJ+Kr   videomp4)prefix	extensionc                  t        j                  |       }t        j                  j                         j	                  d      }t        j                         j                  dd }t               | d| d| d| z  }|j                  |       |S )zDecode base64 video data and write under ``$HERMES_HOME/cache/videos/``.

    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hexrd   write_bytes)b64_datarg   rh   rawtsshortrc   s          r   save_b64_videor{      s     

8
$C						 	)	)/	:BJJLRa EF81RD%)!EEDSKr   c                   t         j                   j                         j                  d      }t        j                         j
                  dd }t               | d| d| d| z  }|j                  |        |S )z@Write raw video bytes (e.g. an HTTP download body) to the cache.rj   Nrk   rl   rm   )rp   rq   rr   rs   rt   ru   rd   rv   )rx   rg   rh   ry   rz   rc   s         r   save_bytes_videor}      sr     
					 	)	)/	:BJJLRa EF81RD%)!EEDSKr   webmmovmkv)z	video/mp4z
video/webmzvideo/quicktimezvideo/x-matroskag     f@i  )rg   timeout	max_bytesc          	     8   ddl }|j                  | |d      }|j                          |j                  j                  d      xs dj	                  dd      d   j                         j                         }t        j                  |      }|B| j	                  d	d      d   j                         }d
D ]  }	|j                  d|	       s|	} 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)a  Download a video URL and write it under ``$HERMES_HOME/cache/videos/``.

    The video twin of :func:`agent.image_gen_provider.save_url_image`: several
    backends (DeepInfra, FAL) return an *ephemeral* delivery URL that expires
    before a downstream consumer can fetch it, so we materialise the bytes
    locally at tool-completion time. Streams with a size cap.

    Raises on any network / HTTP / oversize error so callers can fall back to
    returning the bare URL.
    r   NT)r   streamzContent-Typer#   ;r0   ?)rf   r~   r   r   rm   rf   rj   rk   rl   wbi   )
chunk_sizez	Video at z	 exceeds i   zMB cap; refusing to cache.z was empty (0 bytes).)requestsr*   raise_for_statusheaderssplitstriplower_URL_VIDEO_CONTENT_TYPESendswithrp   rq   rr   rs   rt   ru   rd   openiter_contentlencloseunlinkOSError
ValueErrorwrite)urlrg   r   r   r   responsecontent_typerh   url_pathextry   rz   rc   bytes_writtenfhchunks                   r   save_url_videor      s4   " ||C|>H$$((8>BEEc1MaPVVX^^`L(,,\:I99S!$Q'--/0 	C  1SE+		 							 	)	)/	:BJJLRa EF81RD%)!EEDM	4 B**j*A 	ESZ'My(
KKM !uIiK.H-IIcd  HHUO	  	KKM 9SE)>?@@K   &  		sB   <H
G2(HH 2	G>;H=G>>HH
	HHr.   r#   )modalityr@   r?   extrac           	         d| |||||rt        |      nd|d}|r*|j                         D ]  \  }	}
|j                  |	|
        |S )u  Build a uniform success response dict.

    ``video`` may be an HTTP URL or an absolute filesystem path.
    ``modality`` is ``"text"`` (text-to-video) or ``"image"`` (image-to-video) —
    indicates which endpoint was actually hit, useful for diagnostics.
    Tr   )successre   r<   rF   r   r@   r?   provider)intitems
setdefault)re   r<   rF   r   r@   r?   r   r   payloadkvs              r   success_responser   ?  s^    $ $%-CM1	G KKM 	%DAqq!$	%Nr   provider_error)
error_typer   r<   rF   r@   c           	         dd| |||||dS )z$Build a uniform error response dict.FN)r   re   errorr   r<   rF   r@   r   r   r   r   r   r<   rF   r@   s         r   error_responser   `  s&      $	 	r   c            
          e Zd ZU dZdZded<   dZded<   dZded	<   d
Zded<   ddZ	ddZ
ddZddZddddeedddd		 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZy) OpenAICompatibleVideoGenProviderug  Generic text/image-to-video over the OpenAI ``client.videos`` API.

    DeepInfra, OpenAI/Sora, and OpenRouter all expose the same
    ``POST /videos`` async-job shape (``create`` → poll → ``download_content``),
    so the SDK call lives here once. A concrete backend only needs to declare
    its identity and credentials::

        class FooVideoGenProvider(OpenAICompatibleVideoGenProvider):
            name = "foo"
            _env_key = "FOO_API_KEY"
            _default_base_url = "https://api.foo.com/v1/openai"
            def list_models(self):
                return [...]   # entries with an "id" key; default_model() uses [0]

    ``image_url`` routes to image-to-video; its absence routes to text-to-video.
    Provider-specific fields (``image_url``/``negative_prompt``/``seed``) ride
    in ``extra_body`` so they pass through the SDK unchanged.
    OPENAI_API_KEYrK   _env_keyzhttps://api.openai.com/v1_default_base_urlg      @float_poll_interval_sg      @_poll_deadline_sc                r    dd l }|j                  j                  | j                  d      j	                         S )Nr   r#   )osenvironr*   r   r   )r   r   s     r   _api_keyz)OpenAICompatibleVideoGenProvider._api_key  s'    zz~~dmmR06688r   c                4    t        | j                               S )N)rM   r   r   s    r   r   z-OpenAICompatibleVideoGenProvider.is_available  s    DMMO$$r   c                   ddl } |j                  j                  di |}h d}|j                         | j                  z   }t        |dd      |vr|j                         |k\  r>t        dt        |dd       dt        | j                         d	t        |dd      d
      |j                  | j                         |j                  j                  |j                        }t        |dd      |vr|S )aC  Create the video job and poll to completion with a hard deadline.

        Replaces ``client.videos.create_and_poll`` (unbounded 1/s loop) with a
        coarse interval and a wall-clock cap. Returns the terminal video object
        (any status); raises :class:`TimeoutError` if the deadline passes
        first.
        r   N>   r   failedcanceled	cancelled	completed	succeededstatusz
video job r)   r   z( did not reach a terminal status within zs (last status=)r   )timer^   create	monotonicr   getattrTimeoutErrorr   sleepr   retriever)   )r   clientcall_kwargsr   re   terminaldeadlines          r   _create_and_pollz1OpenAICompatibleVideoGenProvider._create_and_poll  s     	$$$3{3Y>>#d&;&;;eXt,H<~~8+" c!: ; <%%()>)>%?$@ A$$+E8T$B#EQH 
 JJt,,-MM**5884E eXt,H< r   c                    dd l }|j                  j                  | j                  j	                          dd      j                         }|xs | j                  S )Nr   	_BASE_URLr#   )r   r   r*   r   upperr   r   )r   r   overrides      r   	_base_urlz*OpenAICompatibleVideoGenProvider._base_url  sF    ::>>TYY__%6$7y"A2FLLN14111r   Nr;   c       	   	        |r|j                         st        dd| j                        S | j                         s%t        | j                   dd| j                        S 	 dd l}|xs | j                         }|s&t        d	| j                   d
d| j                        S ||||
dj                         D ci c]
  \  }}||| }}}||d}|rt        |      |d<   |r||d<   |r||d<   |j                  | j                         | j                               }	 	 | j                  ||      }t!        |dd       }|dvrXt!        |dd       }t        |rt        |      nd|d| j                  |||      t!        |dd       }t#        |      r |        S S d }t!        |dd       xs g D ]6  }t%        |t&              r|j)                  d      nt!        |dd       }|s4|} n 	 |r!t        t+        || j                               }nS|j,                  j/                  |j0                        j3                         }t        t5        || j                               }t7        ||||rd$nd%||xs d| j                  &      t!        |dd       }t#        |      r |        S S # t        $ r t        dd| j                        cY S w xY wc c}}w # t        $ rv}t        j                  d| j                  d       t        | j                   d| d| j                  |||      cY d }~t!        |dd       }t#        |      r |        S S d }~ww xY w# t        $ r}|r$t        j                  d!| j                  |       |}nOt        | j                   d"| d#| j                  |||      cY d }~t!        |dd       }t#        |      r |        S S Y d }~wd }~ww xY w# t!        |dd       }t#        |      r |        w w xY w)'Nzprompt is requiredinvalid_request)r   r   r   z is not setmissing_credentialsr   z8openai Python package not installed (pip install openai)missing_dependencyzno z, video model available (live catalog empty?)no_model)rB   r@   r=   rD   )r<   rF   secondssize
extra_body)api_keybase_urlz%s video generation failedT)exc_infoz video generation failed: 	api_errorr   r   r   )r   r   r   zvideo job ended with status=
job_faileddatar   )rg   z3%s: saving video locally failed (%s); returning URLz7 video job succeeded but no output could be retrieved: empty_responseimager.   )re   r<   rF   r   r@   r?   r   )r   r   r   r   r   openaiImportErrorr,   r   rK   OpenAIr   r   	Exceptionloggerdebugr   callable
isinstancedictr*   r   r^   download_contentr)   readr}   r   )r   rF   r<   r=   r>   r?   r@   rA   rB   rC   rD   rG   r   model_idr   r   r   r   r   re   excr   r   	job_errorr   item	candidate	video_refrx   s                                r   rH   z)OpenAICompatibleVideoGenProvider.generate  s+    V\\^!*7HSWS\S\  }}!{30 
	 0D..0!DII;&RS%  $3 ,&	
 eg	
1 } qD	

 	
 19F&K%(]K	"",K(2K%t}}AQRN	--fkB UHd3F77 $E7D9	%,5#i.=YZ`Yc;d+!YY"!!-l FGT2E S Cvt4: /9$/EDHHUO7SWY^`dKe	#C	 #N3tyy$I JI !--88BGGIC #$4S$K LI  $$-6)!Q FGT2E c  	!P/ 	"	
,  	9499tT%!YYK'A#G*!YY"!!- N FGT2E U	`  LL!VX\XaXacfg #I)!%+bcfbgh#3!%&%%1 & FGT2E - 	2 FGT2E s   !J! 9KK 1AO AO !O 'A6M #O !!KK	MAM"M#O MO 	OAO(O)O O OO "O>rI   rL   )r   r   r   rN   rJ   r   rP   )rR   rS   rT   rU   r   __annotations__r   r   r   r   r   r   r   rY   rZ   rH   r   r   r   r   r   {  s    & %Hc$8s8 "e!#e#9
%02  $#'48"&0,)- $"MM 	M
 !M 2M  M M M 'M M M M 
Mr   r   )rJ   r   )rw   rK   rg   rK   rh   rK   rJ   r   )rx   bytesrg   rK   rh   rK   rJ   r   )
r   rK   rg   rK   r   r   r   r   rJ   r   )re   rK   r<   rK   rF   rK   r   rK   r@   rK   r?   r   r   rK   r   zOptional[Dict[str, Any]]rJ   rN   )r   rK   r   rK   r   rK   r<   rK   rF   rK   r@   rK   rJ   rN   )"rU   
__future__r   rW   rn   rp   loggingrs   pathlibr   typingr   r   r   r   r	   	getLoggerrR   r   r   r   rY   r   rZ   ABCr   rd   r{   r}   r   r   r   r   r   r   r   r   <module>r     s	  ,\ # 
      3 3			8	$ )\ o [ &G O G ysww yB 	  	
 
. 		  	
 
 	  &=	= = 	=
 = 
=J &*  	
     $ H '  	
    6S'7 Sr   