
    `gj[                    J   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dJd:ZdKd;ZdLd<ZdMd=Z	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dNd>Zdad9ed?<    ej:                         ZdOd@Zda d9edA<   da!d9edB<    ej:                         Z"dC Z#dD Z$dPdEZ%dQdFZ& G dG dHe      Z'dRdIZ(y)Su	  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   c                    t        | t              rt        |       dk7  ryt        d | D              sy| d   | d   z
  dkD  S )zIHeuristic: a 2-tuple of ints with a gap > 1 is treated as ``(min, max)``.   Fc              3  <   K   | ]  }t        |t                y wN)
isinstanceint).0ds     Q/root/.hermes/venv/lib/python3.12/site-packages/plugins/video_gen/fal/__init__.py	<genexpr>z%_is_duration_range.<locals>.<genexpr>   s     5az!S!5s   r   r   )r5   tuplelenall)r   s    r9   _is_duration_ranger>      sC    i'3y>Q+>5955Q<)A,&**    c                    | j                  d      }|sS t        |      rd S |d   S t        |      r|\  }}t        |t        |            S |v rS t        |fd      S )Nr   r   c                     t        | z
        S r4   )abs)r8   durations    r9   <lambda>z!_clamp_duration.<locals>.<lambda>   s    AL(9 r?   )key)getr>   maxmin)familyrC   r   lohis    `   r9   _clamp_durationrL      sw    

;'I
 *)4tF)A,F)$B2s2x())9y9::r?   c                     	 ddl m}   |        }t        |t              r|j	                  d      nd }t        |t              r|S i S # t
        $ r"}t        j                  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.configrN   r5   dictrF   	Exceptionloggerdebug)rN   cfgsectionexcs       r9   _load_video_gen_sectionrX      sb    1m*4S$*?#''+&T$Wd3w;; :C@	s$   AA A 	A1A,&A1,A1c                   g }|j                  |        |j                  t        j                  j                  d             t	               }t        |j                  d      t              r|j                  d      ni }t        |t              r |j                  |j                  d             |j                  d      }t        |t              r|j                  |       |D ]W  }t        |t              s|j                         s%|j                         t        v s<|j                         }|t        |   fc S  t        t        t           fS )z>Decide which FAL family to use. Returns ``(family_id, meta)``.FAL_VIDEO_MODELfalmodel)appendosenvironrF   rX   r5   rQ   strstripr0   DEFAULT_MODEL)explicit
candidatesrU   fal_cfgtopcfids          r9   _resolve_familyri      s    &(Jhbjjnn%678
!
#C *3775>4 @cggenbG'4 '++g./
'''
C#s# *a!'')	\0I'')CS)))*
 ,}555r?   c                  i }	|r||	d<   |r| j                  d      xs d}
||	|
<   |||	d<   | j                  d      r|| d   v r||	d<   | j                  d      r|| d   v r||	d<   t        | |      }|,| j                  d	      r| j                  d
d      }| | |	d<   | j                  d      r|t        |      |	d<   | j                  d      r|r||	d<   |	S )zJBuild a family-specific payload, dropping keys the family doesn't declare.promptr.   	image_urlseedr   aspect_ratior   
resolutionr   r(    rC   r   generate_audior   negative_prompt)rF   rL   bool)rI   rk   rl   rC   rn   ro   rr   r   rm   payloadrE   clampedsuffixs                r9   _build_payloadrw      s    !G" jj*+:{ zz/"6/22&2GN# zz- ..$.GL! fh/Gvzz+6 -r2!(	&2
zz'u0$(K !zz*/%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 Y   yxY w)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)rx   _fal_client_locktools.fal_commonr{   rz   s    r9   _load_fal_clientr~   -  sK     	 "  	7')  s   AAA_managed_fal_video_client _managed_fal_video_client_configc                 N    ddl m} m}  |        r	 |d      sy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_gatewayrO   N)resolve_managed_tool_gatewayz	fal-queue)tools.tool_backend_helpersr   r   tools.managed_tool_gatewayr   )r   r   r   s      r9   "_resolve_managed_fal_video_gatewayr   J  s&     R{'CG'44r?   c                :   ddl m} | j                  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 Y   yxY w)zQReuse the managed FAL client so its internal httpx.Client is not leaked per call.r   )_ManagedFalSyncClient/N)rE   queue_run_origin)
r}   r   gateway_originrstripnous_user_token_managed_fal_video_client_lockr   r   r~   rx   )managed_gatewayr   client_configs      r9   _get_managed_fal_video_clientr   V  s     7 	&&--c2''M 
( )$05UYf5f,) ) 	$9//,;;%
!
 ,9(() ) )s   B4BBc                b   t                dt        t        j                               i}t	               }|t
        j                  | ||      S t        |      }	 |j                  | ||      S # t        $ r7}ddl	m
}  ||      }|!d|cxk  rdk  r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)	arguments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~   r`   uuiduuid4r   rx   submitr   rR   r}   r   
ValueError)endpointr   request_headersr   managed_clientrW   r   statuss           r9   _submit_fal_video_requestr   m  s    
 *C

,=>O8:O!!(i!YY2?CN$$# % 
 	

  9%c*#"5#"5 	 ?z J !XY  	s   A. .	B.72B))B.c                 6    ddl m}   |        ry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    r9   _check_fal_video_availabler     s    @-/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y	)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.
    c                     y)Nr[    selfs    r9   namezFALVideoGenProvider.name      r?   c                     y)NFALr   r   s    r9   display_namez FALVideoGenProvider.display_name  r   r?   c                6    	 t               S # t        $ r Y yw xY w)NF)r   rR   r   s    r9   is_availablez FALVideoGenProvider.is_available  s"    	-// 		s   	 	c                8   g }t         j                         D ]  \  }}g }|j                  d      r|j                  d       |j                  d      r|j                  d       |j                  ||d   |d   |d   |d   |j                  d	d
      |d        |S )Nr   textr   imager   r   r   r   r   r    )idr   r   r   r   r   
modalities)r0   itemsrF   r]   )r   outrh   metar   s        r9   list_modelszFALVideoGenProvider.list_models  s    $&%++- 	IC$&Jxx(!!&)xx()!!'*JJ	?g!+.g3( 	 
r?   c                    t         S r4   )rb   r   s    r9   default_modelz!FALVideoGenProvider.default_model  s    r?   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)rE   rk   url)r   badgetagenv_varsr   r   s    r9   get_setup_schemaz$FALVideoGenProvider.get_setup_schema  s*    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    r9   capabilitiesz FALVideoGenProvider.capabilities  s)    !7+4<"(,$%	
 		
r?   Nr!   r   )	r\   rl   reference_image_urlsrC   rn   ro   rr   r   rm   c       	           t               st        ddd|      S 	 t                |xs dj	                         }t        |      \  }}|xs dj	                         xs d }|r)|j                  d      }d	}|s=t        d
| ddd||      S |j                  d      }d}|st        d
| ddd||      S |st        ddd||      S t        ||||||||	|
	      }	 t        ||      }|j                         }t        |t              r|xs i j                  d      nd }d }t        |t              r|j                  d      }nt        |t              r|}|st        ddd||      S d|i}t        |t              r2|j                  d      r|d   |d<   |j                  d       r|d    |d <   t        ||||d!|v r|ndd"|v r(t!        dj#                  d# |d"   D              xs d$      nd%d|&      S # t        $ r t        ddd|      cY S w xY w# t        $ r8}t        j                  d|||d       t        d| dd|||      cY d }~S d }~ww xY w)'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_requiredr[   )error
error_typeproviderrk   z@fal_client Python package not installed (pip install fal-client)missing_dependencyrp   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   r\   rk   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)rk   rl   rC   rn   ro   rr   r   rm   z1FAL video gen failed (family=%s, endpoint=%s): %sT)exc_infozFAL video generation failed: 	api_error)r   r   r   r\   rk   rn   videor   z%FAL returned no video URL in responseempty_responser   	file_sizecontent_typern   rC   c              3  B   K   | ]  }|j                         s|  y wr4   )isdigit)r7   rg   s     r9   r:   z/FALVideoGenProvider.generate.<locals>.<genexpr>c  s      Oq199; Os   0r   )r   r\   rk   modalityrn   rC   r   extra)r   r
   r~   ImportErrorra   ri   rF   rw   r   rR   rS   warningr5   rQ   r`   r   r6   join)r   rk   r\   rl   r   rC   rn   ro   rr   r   rm   kwargs	family_idrI   image_url_normr   modality_usedrt   handleresultrW   r   r   r   s                           r9   generatezFALVideoGenProvider.generate  s    *+!V +	 		 ,B%%'+E2	6 $/r002:dzz"23H#M%%i[ 1C D  6")F  zz/2H"M%%i[ 1O P  6")F  !++i  !$%!+

	.xAFZZ\F 0:&$/G2""7+T!eT"))E"Cs#C!=+i  ",X 6eT"yy%%*;%7k"yy((-n(=n%")77)B[eip[pS OGJ,? OOVSVWvw	
 		
  	!X/	 	|  
	NNC8S4   "5cU;&i)	 
	s/   
G0 H 0H
H	I-I
I
I)returnr`   r   rs   )r   zList[Dict[str, Any]])r   Optional[str]r   Dict[str, Any])rk   r`   r\   r   rl   r   r   zOptional[List[str]]rC   Optional[int]rn   r`   ro   r`   rr   r   r   Optional[bool]rm   r   r   r   r   r   )__name__
__module____qualname____doc__propertyr   r   r   r   r   r   r   r   r   r?   r9   r   r     s        &


   $#'48"&" )- $"E
E
 	E

 !E
 2E
  E
 E
 E
 'E
 E
 E
 E
 
E
r?   r   c                6    | j                  t                      y)uF   Plugin entry point — wire ``FALVideoGenProvider`` into the registry.N)register_video_gen_providerr   )ctxs    r9   registerr   n  s    ##$7$9:r?   )r   r   r   rs   )rI   r   rC   r   r   r   r   )rc   r   r   zTuple[str, Dict[str, Any]])rI   r   rk   r`   rl   r   rC   r   rn   r`   ro   r`   rr   r   r   r   rm   r   r   r   )r   r   )r   r`   r   r   r   )r   None))r   
__future__r   loggingr^   	threadingr   typingr   r   r   r   r   agent.video_gen_providerr	   r
   r   	getLoggerr   rS   r0   __annotations__rb   r>   rL   rX   ri   rw   rx   Lockr|   r~   r   r   r   r   r   r   r   r   r   r   r?   r9   <module>r     sX  @ #  	   3 3  
		8	$. #J;= " !E;=8  Y(8).  "R?A G0" !HAC -0$ %X<> ca+' aF +;0	6611 1 	1
 1 1 1 #1 1 1 1p S !9>># 0 "& 3 %(,  # ,!/!1 	5).B<J
* J
d;r?   