
    oq'jeZ                       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mZm	Z	m
Z
mZmZ ddlmZmZmZ  ej        e          Zdddd	dd
ddddddddddddddddddddddddddddddddddd ddd!dd"d#d$d%d&dd'dd(d)dd*dd+d,d-d.dd/ddd0d1ddd2dd3d4dddd'd'dd5Zd6ed7<   d8Zd`d=ZdadBZdbdCZdcdGZdddQZdad:edR<    ej                    ZdedSZda d:edT<   da!d:edU<    ej                    Z"dV Z#dW Z$dfdZZ%dgd[Z& G d\ d]e          Z'dhd_Z(dS )iu	  FAL.ai video generation backend.

User-facing surface: pick a **model family** (e.g. "Pixverse v6",
"Veo 3.1", "Seedance 2.0", "Kling v3 4K", "LTX 2.3", "Happy Horse").
The plugin auto-routes to the family's text-to-video endpoint when
called without ``image_url``, and to its image-to-video endpoint when
``image_url`` is provided. The agent never sees the routing — it just
calls ``video_generate(prompt=..., image_url=...)``.

Model families (each with t2v + i2v endpoints):

  Cheap tier:
    ltx-2.3       fal-ai/ltx-2.3-22b/text-to-video               /  fal-ai/ltx-2.3-22b/image-to-video
    pixverse-v6   fal-ai/pixverse/v6/text-to-video               /  fal-ai/pixverse/v6/image-to-video

  Premium tier:
    veo3.1        fal-ai/veo3.1                                  /  fal-ai/veo3.1/image-to-video
    seedance-2.0  bytedance/seedance-2.0/text-to-video           /  bytedance/seedance-2.0/image-to-video
    kling-v3-4k   fal-ai/kling-video/v3/4k/text-to-video         /  fal-ai/kling-video/v3/4k/image-to-video
    happy-horse   alibaba/happy-horse/text-to-video              /  alibaba/happy-horse/image-to-video

Selection precedence for the active family:
    1. ``model=`` arg from the tool call
    2. ``FAL_VIDEO_MODEL`` env var
    3. ``video_gen.fal.model`` in ``config.yaml``
    4. ``video_gen.model`` in ``config.yaml`` (when it's one of our family IDs)
    5. ``DEFAULT_MODEL``

Authentication via ``FAL_KEY`` or the managed Nous gateway. Output is an
HTTPS URL from FAL's CDN; the gateway downloads and delivers it.
    )annotationsN)AnyDictListOptionalTuple)VideoGenProvidererror_responsesuccess_responsezLTX 2.3 (22B)z~30-60scheapz322B model with native audio generation. Affordable.z fal-ai/ltx-2.3-22b/text-to-videoz!fal-ai/ltx-2.3-22b/image-to-videoT)displayspeedprice	strengthstiertext_endpointimage_endpointaspect_ratiosresolutions	durationsaudionegativezPixverse v6z~30-90sz.Affordable. Negative prompts. 1-15s durations.z fal-ai/pixverse/v6/text-to-videoz!fal-ai/pixverse/v6/image-to-video360p540p720p1080p)      zVeo 3.1z~60-120spremiumzBGoogle DeepMind. Cinematic, native audio, strong prompt adherence.zfal-ai/veo3.1zfal-ai/veo3.1/image-to-video)16:99:16)r   r   4k)         s)r   r   r   r   r   r   r   r   r   r   duration_suffixr   r   zSeedance 2.0z;ByteDance. Cinematic, synchronized audio + lip-sync, 4-15s.z$bytedance/seedance-2.0/text-to-videoz%bytedance/seedance-2.0/image-to-video)z21:9r!   z4:31:1z3:4r"   )480pr   r   )r$   r   FzKling v3 4Kz	~120-300sz14K output, native audio (Chinese/English), 3-15s.z&fal-ai/kling-video/v3/4k/text-to-videoz'fal-ai/kling-video/v3/4k/image-to-videostart_image_urlr!   r"   r)   )   r   )r   r   r   r   r   r   r   image_param_keyr   r   r   r   r   zHappy Horse 1.0uA   Alibaba. New model, sparse public docs — conservative defaults.z!alibaba/happy-horse/text-to-videoz"alibaba/happy-horse/image-to-video)zltx-2.3pixverse-v6zveo3.1zseedance-2.0zkling-v3-4kzhappy-horsezDict[str, Dict[str, Any]]FAL_FAMILIESr/   r   r   returnboolc                    t          | t                    rt          |           dk    rdS t          d | D                       sdS | d         | d         z
  dk    S )zIHeuristic: a 2-tuple of ints with a gap > 1 is treated as ``(min, max)``.   Fc              3  @   K   | ]}t          |t                    V  d S N)
isinstanceint).0ds     C/home/ubuntu/.hermes/hermes-agent/plugins/video_gen/fal/__init__.py	<genexpr>z%_is_duration_range.<locals>.<genexpr>   s,      55az!S!!555555    r   r   )r7   tuplelenall)r   s    r;   _is_duration_rangerA      se    i'' 3y>>Q+>+>u55955555 uQ<)A,&**r=   familyDict[str, Any]durationOptional[int]c                    |                      d          }|sS |d         S t          |          r#|\  }}t          |t          |                    S |v rS t          |fd          S )Nr   r   c                (    t          | z
            S r6   )abs)r:   rD   s    r;   <lambda>z!_clamp_duration.<locals>.<lambda>   s    AL(9(9 r=   )key)getrA   maxmin)rB   rD   r   lohis    `   r;   _clamp_durationrP      s    

;''I |)$$ *B2s2x(()))9y9999::::r=   c                    	 ddl m}   |             }t          |t                    r|                    d          nd }t          |t                    r|ni S # t
          $ r'}t                              d|           i cY d }~S d }~ww xY w)Nr   )load_config	video_genz#Could not load video_gen config: %s)hermes_cli.configrR   r7   dictrK   	Exceptionloggerdebug)rR   cfgsectionexcs       r;   _load_video_gen_sectionr\      s    111111kmm*4S$*?*?I#''+&&&T$Wd33;ww;   :C@@@						s   AA 
B!B=BBexplicitOptional[str]Tuple[str, Dict[str, Any]]c                4   g }|                     |            |                     t          j                            d                     t	                      }t          |                    d          t                    r|                    d          ni }t          |t                    r(|                     |                    d                     |                    d          }t          |t                    r|                     |           |D ]k}t          |t                    rT|                                r@|                                t          v r%|                                }|t          |         fc S lt          t          t                   fS )z>Decide which FAL family to use. Returns ``(family_id, meta)``.FAL_VIDEO_MODELfalmodel)appendosenvironrK   r\   r7   rU   strstripr0   DEFAULT_MODEL)r]   
candidatesrY   fal_cfgtopcfids          r;   _resolve_familyro      sT   &(Jhbjnn%677888
!
#
#C *3775>>4 @ @HcggennnbG'4   0'++g..///
'''

C#s # * *a 	*!'')) 	*		\0I0I''))CS))))),}555r=   promptrg   	image_urlaspect_ratio
resolutionnegative_promptr   Optional[bool]seedc                  i }	|r||	d<   |r|                      d          pd}
||	|
<   |||	d<   |                      d          r|| d         v r||	d<   |                      d          r|| d         v r||	d	<   t          | |          }|4|                      d
          r|                      dd          }| | |	d<   |                      d          r|t          |          |	d<   |                      d          r|r||	d<   |	S )zJBuild a family-specific payload, dropping keys the family doesn't declare.rp   r.   rq   Nrv   r   rr   r   rs   r   r(    rD   r   generate_audior   rt   )rK   rP   r2   )rB   rp   rq   rD   rr   rs   rt   r   rv   payloadrJ   clampedsuffixs                r;   _build_payloadr}      sZ    !G #" ! jj*++:{ zz/"" 36/222&2GN# zz-   /...$.GL! fh//Gvzz+66 -r22!(2&22
zz' 0u0$(KK !zz* 5/ 5%4!"Nr=   _fal_clientc                     t           t           S t          5  t           t           cddd           S ddlm}   |             a t           cddd           S # 1 swxY w Y   dS )ao  Lazy-load the ``fal_client`` SDK and cache it on this module.

    Delegates the actual import to :func:`tools.fal_common.import_fal_client`
    so the ``lazy_deps`` ensure-install handling stays in one place.

    Thread-safe via double-checked locking: concurrent first calls import
    the SDK exactly once instead of each racing thread re-running the import.
    Nr   import_fal_client)r~   _fal_client_locktools.fal_commonr   r   s    r;   _load_fal_clientr   )  s     	  "        	766666''))                 s   AAAA_managed_fal_video_client _managed_fal_video_client_configc                 b    ddl m} m}  |             r |d          sdS ddlm}  |d          S )zsReturn managed fal-queue gateway config when the user prefers the gateway
    or direct FAL credentials are absent.r   )fal_key_is_configuredprefers_gatewayrS   N)resolve_managed_tool_gatewayz	fal-queue)tools.tool_backend_helpersr   r   tools.managed_tool_gatewayr   )r   r   r   s      r;   "_resolve_managed_fal_video_gatewayr   F  sg     RQQQQQQQ {'C'C tGGGGGG''444r=   c                D   ddl m} | j                            d          | j        f}t
          5  t          t          |k    rt          cddd           S t                        |t          | j        | j                  a|at          cddd           S # 1 swxY w Y   dS )zQReuse the managed FAL client so its internal httpx.Client is not leaked per call.r   )_ManagedFalSyncClient/N)rJ   queue_run_origin)
r   r   gateway_originrstripnous_user_token_managed_fal_video_client_lockr   r   r   r~   )managed_gatewayr   client_configs      r;   _get_managed_fal_video_clientr   R  s"    766666 	&--c22'M 
( ) )$05UYf5f5f,) ) ) ) ) ) ) ) 	$9$9/,;%
 %
 %
!
 ,9(() ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )s   B3BBBendpoint	argumentsc                   t                       dt          t          j                              i}t	                      }|t
                              | ||          S t          |          }	 |                    | ||          S # t          $ r@}ddl	m
}  ||          }|'d|cxk    rdk     rn nt          d|  d	| d
          | d}~ww xY w)zSubmit a FAL video request using direct credentials or the managed queue gateway.

    Returns a request handle whose ``.get()`` blocks until the result is ready.
    zx-idempotency-keyN)r   headersr   )_extract_http_statusi  i  z-Nous Subscription gateway rejected endpoint 'z' (HTTP u   ). This model may not yet be enabled on the Nous Portal's FAL proxy. Either:
  • Set FAL_KEY in your environment to use FAL.ai directly, or
  • Pick a different model via `hermes tools` → Video Generation.)r   rg   uuiduuid4r   r~   submitr   rV   r   r   
ValueError)r   r   request_headersr   managed_clientr[   r   statuss           r;   _submit_fal_video_requestr   i  s;   
 *C
,=,=>O8::O!!(i!YYY2?CCN$$# % 
 
 	

    999999%%c**#"5"5"5"5#"5"5"5"5"5Y Y YY Y Y   	s   .B 
C;CCc                 F    ddl m}   |             rdS t                      duS )zNTrue if the FAL.ai video backend is reachable (direct key or managed gateway).r   r   TN)r   r   r   r   s    r;   _check_fal_video_availabler     s;    @@@@@@ t-//t;;r=   c            
          e Zd ZdZed#d            Zed#d            Zd$dZd%d	Zd&dZ	d'dZ
d'dZdddddddddd	d(d"ZdS ))FALVideoGenProviderzFAL.ai multi-family video generation backend.

    Routes between text-to-video and image-to-video endpoints automatically
    based on whether ``image_url`` was provided.
    r1   rg   c                    dS )Nrb    selfs    r;   namezFALVideoGenProvider.name      ur=   c                    dS )NFALr   r   s    r;   display_namez FALVideoGenProvider.display_name  r   r=   r2   c                B    	 t                      S # t          $ r Y dS w xY w)NF)r   rV   r   s    r;   is_availablez FALVideoGenProvider.is_available  s5    	-/// 	 	 	55	s    
List[Dict[str, Any]]c                   g }t                                           D ]\  }}g }|                    d          r|                    d           |                    d          r|                    d           |                    ||d         |d         |d         |d         |                    d	d
          |d           |S )Nr   textr   imager   r   r   r   r   r    )idr   r   r   r   r   
modalities)r0   itemsrK   rd   )r   outrn   metar   s        r;   list_modelszFALVideoGenProvider.list_models  s    $&%++-- 	 	IC$&Jxx(( *!!&)))xx()) +!!'***JJ	?g!+.g33(      
r=   r^   c                    t           S r6   )ri   r   s    r;   default_modelz!FALVideoGenProvider.default_model  s    r=   rC   c                    dddddddgdS )	Nr   paidu^   LTX, Pixverse, Veo 3.1, Seedance 2.0, Kling 4K, Happy Horse — text-to-video & image-to-videoFAL_KEYzFAL.ai API keyzhttps://fal.ai/dashboard/keys)rJ   rp   url)r   badgetagenv_varsr   r   s    r;   get_setup_schemaz$FALVideoGenProvider.get_setup_schema  s4    s %.: 	
 
 	
r=   c           	     $    ddgg dg ddddddd	S )
Nr   r   r,   r   r   r   Tr   )r   r   r   max_durationmin_durationsupports_audiosupports_negative_promptmax_reference_imagesr   r   s    r;   capabilitiesz FALVideoGenProvider.capabilities  s:    !7+444<<<"(,$%	
 	
 		
r=   Nr!   r   )	rc   rq   reference_image_urlsrD   rr   rs   rt   r   rv   rp   rc   rq   r   Optional[List[str]]rD   rE   rr   rs   rt   r   ru   rv   kwargsr   c       	           t                      st          ddd|          S 	 t                       n## t          $ r t          ddd|          cY S w xY w|pd                                }t          |          \  }}|pd                                pd }|r2|                    d          }d	}|st          d
| ddd||          S n1|                    d          }d}|st          d
| ddd||          S |st          ddd||          S t          ||||||||	|
	  	        }	 t          ||          }|                                }nN# t          $ rA}t                              d|||d           t          d| dd|||          cY d }~S d }~ww xY wt          |t                    r|pi                     d          nd }d }t          |t                    r|                    d          }nt          |t                    r|}|st          ddd||          S d|i}t          |t                    r@|                    d          r|d         |d<   |                    d           r|d          |d <   t          ||||d!|v r|ndd"|v r4t!          d                    d# |d"         D                       pd$          nd%d|&          S )'Nu   No FAL backend available. Either set FAL_KEY (run `hermes tools` → Video Generation → FAL to configure) or sign in to Nous (`hermes setup`) for managed gateway access.auth_requiredrb   )error
error_typeproviderrp   z@fal_client Python package not installed (pip install fal-client)missing_dependencyrx   r   r   zFAL family us    has no image-to-video endpoint. Pick a family with image-to-video support via `hermes tools` → Video Generation.modality_unsupported)r   r   r   rc   rp   r   r   zq has no text-to-video endpoint. Pass an image_url to use its image-to-video endpoint, or pick a different family.zprompt is required.missing_prompt)rp   rq   rD   rr   rs   rt   r   rv   z1FAL video gen failed (family=%s, endpoint=%s): %sT)exc_infozFAL video generation failed: 	api_error)r   r   r   rc   rp   rr   videor   z%FAL returned no video URL in responseempty_responser   	file_sizecontent_typerr   rD   c              3  B   K   | ]}|                                 |V  d S r6   )isdigit)r9   rm   s     r;   r<   z/FALVideoGenProvider.generate.<locals>.<genexpr>_  s/       O Oq199;; O O O O O O Or=   0r   )r   rc   rp   modalityrr   rD   r   extra)r   r
   r   ImportErrorrh   ro   rK   r}   r   rV   rW   warningr7   rU   rg   r   r8   join)r   rp   rc   rq   r   rD   rr   rs   rt   r   rv   r   	family_idrB   image_url_normr   modality_usedrz   handleresultr[   r   r   r   s                           r;   generatezFALVideoGenProvider.generate  s-    *++ 
	!V +	 	 	 		 	 	 	!X/	     	 ,B%%''+E22	6 $/r0022:d 	zz"233H#M 	%Di D D D  6")F   	 zz/22H"M 	%Pi P P P  6")F     	!++i    !$%!+

 

 

	.xAAFZZ\\FF 
	 
	 
	NNC8S4     ";c;;&i)	        
	 0:&$/G/GQ2""7+++T!eT"" 	))E""CCs## 	C 	!=+i    ",X 6eT"" 	>yy%% 8%*;%7k"yy(( >(-n(=n%")77)B)B[eip[p[pS O OGJ,? O O OOOVSVWWWvw	
 	
 	
 		
s-   2 AA)$E 
F6FFF)r1   rg   r1   r2   )r1   r   )r1   r^   r1   rC   )rp   rg   rc   r^   rq   r^   r   r   rD   rE   rr   rg   rs   rg   rt   r^   r   ru   rv   rE   r   r   r1   rC   )__name__
__module____qualname____doc__propertyr   r   r   r   r   r   r   r   r   r=   r;   r   r     s            X    X      &   
 
 
 


 

 

 

   $#'48"&" )- $"E
 E
 E
 E
 E
 E
 E
 E
r=   r   Nonec                H    |                      t                                 dS )uF   Plugin entry point — wire ``FALVideoGenProvider`` into the registry.N)register_video_gen_providerr   )ctxs    r;   registerr   j  s#    ##$7$9$9:::::r=   )r   r   r1   r2   )rB   rC   rD   rE   r1   rE   r   )r]   r^   r1   r_   )rB   rC   rp   rg   rq   r^   rD   rE   rr   rg   rs   rg   rt   r^   r   ru   rv   rE   r1   rC   )r1   r   )r   rg   r   rC   r   )r1   r   ))r   
__future__r   loggingre   	threadingr   typingr   r   r   r   r   agent.video_gen_providerr	   r
   r   	getLoggerr   rW   r0   __annotations__ri   rA   rP   r\   ro   r}   r~   Lockr   r   r   r   r   r   r   r   r   r   r   r   r=   r;   <module>r     s    @ # " " " " "  				      3 3 3 3 3 3 3 3 3 3 3 3 3 3          
	8	$	$. #J;=  " !E;=8   Y(8).   "R?A G0 " !HAC -0 $ %X<>  ca+ a+ a a a aF + + + +; ; ; ;(	 	 	 	6 6 6 661 1 1 1p     !9>##    0 "&  % % % %(,   , , , ,!/!1!1 	5 	5 	5) ) ).   B< < < <J
 J
 J
 J
 J
* J
 J
 J
d; ; ; ; ; ;r=   