
    `gj                     	   U d Z ddl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m	Z	m
Z
 daeed<   defdZddlmZ ddlmZmZmZ dd	lmZ dd
lmZmZmZmZ  ej6                  e      Zdddddddddddddh dddh ddddddd dddddd!d"d#ddd$d%d&h d'd%d(h d)ddd*d+d,d-dddddd.d#dddd/h d0dd1	d2d3d4d5d6d7d8d9dd#dd$d:d;h d<dd=h d>d?dd@dAdBdCdDdEdFdGddHd#ddIh dJddKh dLdMddNdOdPdQddRddSddHd#ddIh dTddUh dVdMddWdXdYdZdddddd[d%d\d]h d^dd_h d`d#ddad3dbdcddddddddih dedd1	dfdgdhdiddddddjdkd#ddldmh dnddoh dpdqddrdsdtdud6d7d8d9ddvdHih dwdd1	dxdydzd{d6d7d8d9ddvdHih dwdd1	d|Ze	ee	eef   f   ed}<   d~Z dZ!dZ"dZ#d?Z$dZ%dZ&dZ'dZ(dZ)dZ*dZ+ edd      Z,da-da. ej^                         Z0d Z1d Z2dede	eef   fdZ3de4fdZ5e!ddfdeded6ede
e6   de
e	eef      de	eef   fdZ7e!ddfdedede8d6ede
e6   de
e	eef      de	eef   fdZ9dedede
e	eef      fdZ:dede;fdZ<dedz  fdZ=dededz  fdZ>dedededz  fdZ?deddfdZ@ddededz  defdZAe!dddddddfded6ede
e6   de
eB   de
e6   de
e   de
e6   de
e   de
e8   defdZCde;fdZDdefdZEde;fdZFedk(  r@ eGd        eGd        eD       s  eGd        eGd        eGd        eHd#       eGd       	 ddla eGd        e5       \  ZJZK eGdeKj                  deJ       deJ d        eGdeKj                  dd       deKj                  dd               eGdeKj                  d      rdnd         eGdë       ej                         D ]C  \  ZNZOeNeJk(  rdndZP eGdeNdǛdeOj                  dd      dțdeOj                  dd       eP        E e,j                  r eGde,j                          ddlSmTZTmUZU ddddddМd e8e"      de!dҜdddМdddiddלd؜dgdٜdڜZVdۄ ZWd܄ ZX	 	 dded6ede
e   de
e8   fd݄ZYh dޣZZde
e   de
e   fd߄Z[de
e   de;fdZ\	 	 dded6ede
e   de
e8   de
e   f
dZ]d Z^eVd   Z_de	eef   fdZ`de	eef   fdZa eTj                  ddeVe^eFg ddea	       y# eI$ r  eGd        eHd#      w xY w)u  
Image Generation Tools Module

Provides image generation via FAL.ai. Multiple FAL models are supported and
selectable via ``hermes tools`` → Image Generation; the active model is
persisted to ``image_gen.model`` in ``config.yaml``.

Architecture:
- ``FAL_MODELS`` is a catalog of supported models with per-model metadata
  (size-style family, defaults, ``supports`` whitelist, upscaler flag).
- ``_build_fal_payload()`` translates the agent's unified inputs (prompt +
  aspect_ratio) into the model-specific payload and filters to the
  ``supports`` whitelist so models never receive rejected keys.
- Upscaling via FAL's Clarity Upscaler is gated per-model via the ``upscale``
  flag — on for FLUX 2 Pro (backward-compat), off for all faster/newer models
  where upscaling would either hurt latency or add marginal quality.

Pricing shown in UI strings is as-of the initial commit; we accept drift and
update when it's noticed.
    N)AnyDictOptional
fal_clientreturnc                  @    t         t         S ddlm}   |        a t         S )u'  Lazily import fal_client and rebind the module global on first use.

    Idempotent. Returns the (now-loaded) ``fal_client`` module reference.
    Skips the import if the global is already truthy — this preserves the
    test pattern of monkeypatching the module global to install a mock.
    r   import_fal_client)r   tools.fal_commonr
   r	   s    N/root/.hermes/venv/lib/python3.12/site-packages/tools/image_generation_tool.py_load_fal_clientr   ,   s!     2"$J    )DebugSession)_ManagedFalSyncClient_extract_http_status_normalize_fal_queue_url_format)resolve_managed_tool_gateway)fal_key_is_configuredmanaged_nous_tools_enabled%nous_tool_gateway_unavailable_messageprefers_gatewayzFLUX 2 Klein 9Bz<1szFast, crisp textz	$0.006/MPimage_size_presetlandscape_16_9	square_hdportrait_16_9)	landscapesquareportrait   pngF)num_inference_stepsoutput_formatenable_safety_checker>   seedprompt
image_sizer"   r!   r#   zfal-ai/flux-2/klein/9b/edit>   r$   r%   
image_urlsr"   r!   r#   	   )displayspeed	strengthsprice
size_stylesizesdefaultssupportsupscaleedit_endpointedit_supportsmax_reference_imagesz
FLUX 2 Proz~6szStudio photorealismz$0.03/MP2   g      @   5T)r!   guidance_scale
num_imagesr"   r#   safety_tolerance	sync_mode>
   r$   r%   r;   r&   r9   r"   r8   r:   r!   r#   zfal-ai/flux-2-pro/edit>
   r$   r%   r;   r'   r9   r"   r8   r:   r!   r#   zZ-Image Turboz~2szBilingual EN/CN, 6Bz	$0.005/MP   )r!   r9   r"   r#   enable_prompt_expansion>   r$   r%   r&   r9   r"   r!   r#   r=   )	r)   r*   r+   r,   r-   r.   r/   r0   r1   z$Nano Banana Pro (Gemini 3 Pro Image)z~8sz-Gemini 3 Pro, reasoning depth, text renderingz$0.15/image (1K)aspect_ratioz16:9z1:1z9:161K)r9   r"   r:   
resolution>
   r$   r%   r;   r9   r@   r>   r"   r:   enable_web_searchlimit_generationszfal-ai/nano-banana-pro/edit>   r$   r%   r;   r'   r9   r@   r>   r"   r:   rA   rB      zGPT Image 1.5z~15szPrompt adherencez$0.034/imagegpt_literal	1536x1024	1024x1024	1024x1536medium)qualityr9   r"   >   r%   rI   r;   
backgroundr&   r9   r"   zfal-ai/gpt-image-1.5/edit>   r%   rI   r;   r&   r'   r9   r"      zGPT Image 2z~20sz3SOTA text rendering + CJK, world-aware photorealismu   $0.04–0.06/imagelandscape_4_3portrait_4_3>   r%   rI   r;   r&   r9   r"   zopenai/gpt-image-2/edit>   r%   rI   r;   r'   r9   r"   mask_image_urlzIdeogram V3z~5szBest typographyz$0.03-0.09/imageBALANCEDAUTO)rendering_speedexpand_promptstyle>   r$   rS   r%   r&   rR   rQ   zfal-ai/ideogram/v3/edit>   r$   rS   r%   r'   rR   rQ   zRecraft V4 Proz'Design, brand systems, production-readyz$0.25/imager#   >   colorsr%   r&   background_colorr#   z
Qwen Imagez~12szLLM-based, complex textz$0.02/MP   g      @regular)r!   r8   r9   r"   acceleration>	   r$   r%   r;   r&   r9   rX   r"   r8   r!   zfal-ai/qwen-image-2/pro/edit>	   r$   r%   r;   r'   r9   rX   r"   r8   r!      zKrea 2 Mediumz~15-25sz9Illustration, anime, painting, expressive/artistic stylesz#$0.030 (text) / $0.035 (style refs)
creativity>   r$   r%   rZ   r>   image_style_referenceszKrea 2 Largez~25-60sz;Photorealism, raw textured looks (motion blur, grain, film)z#$0.060 (text) / $0.065 (style refs))fal-ai/flux-2/klein/9bzfal-ai/flux-2-prozfal-ai/z-image/turbozfal-ai/nano-banana-prozfal-ai/gpt-image-1.5zfal-ai/gpt-image-2zfal-ai/ideogram/v3z#fal-ai/recraft/v4/pro/text-to-imagezfal-ai/qwen-imagez#fal-ai/krea/v2/medium/text-to-imagez"fal-ai/krea/v2/large/text-to-image
FAL_MODELSr\   r   zfal-ai/clarity-upscalerz"masterpiece, best quality, highresz.(worst quality, low quality, normal quality:2)gffffff?g333333?   image_toolsIMAGE_TOOLS_DEBUG)env_varc                  D    t               rt        d      syt        d      S )zsReturn managed fal-queue gateway config when the user prefers the gateway
    or direct FAL credentials are absent.	image_genNz	fal-queue)r   r   r    r   r   _resolve_managed_fal_gatewayre     s     {'C'44r   c                 4   | j                   j                  d      | j                  f}t        5  t        t
        |k(  rt        cddd       S t                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./N)keyqueue_run_origin)	gateway_originrstripnous_user_token_managed_fal_client_lock_managed_fal_client_managed_fal_client_configr   r   r   )managed_gatewayclient_configs     r   _get_managed_fal_clientrr     s    
 	&&--c2''M 
" #*/I]/Z&# # 	3//,;;

 &3""# # #s   B7BBmodel	argumentsc           	         t                dt        t        j                               i}t	               }|t        j                  | ||      S t        |      }	 |j                  | ||      S # t        $ rL}t        |      }|9d|cxk  rdk  r.n  d}|dv rdt        d	d
      z   }t        d|  d| d|       | d}~ww xY w)zKSubmit a FAL request using direct credentials or the managed queue gateway.zx-idempotency-keyN)rt   headersi  i   >         z

managed FAL image generationT)force_freshz*Nous Subscription gateway rejected model '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` → Image Generation.)r   struuiduuid4re   r   submitrr   	Exceptionr   r   
ValueError)rs   rt   request_headersrp   managed_clientexcstatusgateway_messages           r   _submit_fal_requestr     s    *C

,=>O24O  )_UU,_=N$$# % 
 	

  
 &c*#"5#"5$ 	# !O(;6$(   <UG D !X ##%  	1s   A. .	C7AB>>Cc                  :   d} 	 ddl m}  |       }t        |t              r|j	                  d      nd}t        |t              r1|j	                  d      }t        |t
              r|j                         } | s$t        j                  dd      j                         } | st        t        t           fS | t        vr.t        j                  d	| t               t        t        t           fS | t        |    fS # t        $ r }t        j                  d|       Y d}~d}~ww xY w)
zResolve the active FAL model from config.yaml (primary) or default.

    Returns (model_id, metadata_dict). Falls back to DEFAULT_MODEL if the
    configured model is unknown (logged as a warning).
    rw   r   load_configrc   Nrs   z.Could not load image_gen.model from config: %sFAL_IMAGE_MODELz4Unknown FAL model '%s' in config; falling back to %s)hermes_cli.configr   
isinstancedictgetr}   stripr   loggerdebugosgetenvDEFAULT_MODELr]   warning)model_idr   cfgimg_cfgrawr   s         r   _resolve_fal_modelr     s     H	L1m*4S$*?#''+&Tgt$++g&C#s#99;
 99.399;j777z!Bm	
 j777Z)))#  LEsKKLs   A1C1 1	D:DDr   r%   r$   	overridesc                     t         |    }|d   }|d   }|xs t        j                         j                         }||vrt        }t	        |j                  di             }	|xs dj                         |	d<   |dv r	||   |	d<   n|dk(  r	||   |	d<   nt        d	|      |t        |t              r||	d
<   |r |j                         D ]  \  }
}|	||	|
<    |d   }|	j                         D 
ci c]  \  }
}|
|v s|
dk(  r|
| c}}
S c c}}
w )a)  Build a FAL request payload for `model_id` from unified inputs.

    Translates aspect_ratio into the model's native size spec (preset enum,
    aspect-ratio enum, or GPT literal string), merges model defaults, applies
    caller overrides, then filters to the model's ``supports`` whitelist.
    r-   r.   r/   rw   r%      rD   r   r&   r>   zUnknown size_style: r$   r0   )
r]   DEFAULT_ASPECT_RATIOlowerr   r   r   r   r   intitems)r   r%   r>   r$   r   metar-   r.   aspectpayloadkvr0   s                r   _build_fal_payloadr   5  sC    hDl#JME2299;AACFU%"488J#;<G2,,.GH99 %f	~	%"'-/
~>??JtS1OO% 	DAq}
	 JH
 !A=AM 	
1  s   0D
r'   c                 d   t         |    }|j                  d      xs
 t               }|d   }|d   }	|xs t        j	                         j                         }
|
|	vrt        }
t        |j                  di             }|xs dj                         |d<   t        |      |d<   |dv rd	|v r	|	|
   |d	<   n|d
k(  rd
|v r|	|
   |d
<   |t        |t              r||d<   |r |j                         D ]  \  }}|	|||<    ddh}|j                         D ci c]  \  }}||v s||v r|| c}}S c c}}w )a  Build a FAL *edit* request payload (image-to-image) from unified inputs.

    Every FAL edit endpoint takes ``image_urls`` (a list of source/reference
    image URLs) plus the prompt. Size handling differs from text-to-image:
    most edit endpoints auto-infer output dimensions from the input image, so
    we only send ``image_size`` / ``aspect_ratio`` when the edit endpoint's
    ``edit_supports`` whitelist accepts it. Keys outside ``edit_supports`` are
    stripped before submission.
    r3   r-   r.   r/   rw   r%   r'   r   r&   r>   r$   )r]   r   setr   r   r   r   listr   r   r   )r   r%   r'   r>   r$   r   r   r3   r-   r.   r   r   r   r   	_requireds                  r   _build_fal_edit_payloadr   f  sc   " hDHH_-6Ml#JME2299;AACFU%"488J#;<G2,,.GH ,GL
 99lm>[ %f	~	%.M*I"'-JtS1OO% 	DAq}
	 <(I Ai 	
1  s   D,	image_urloriginal_promptc           
      <   	 t         j                  d       | t         d| t        t        t
        t        t        t        t        d	}t        t        |      }|j                         }|rod|v rk|d   }t         j                  d|j                  dd      |j                  d	d             |d
   |j                  dd      |j                  d	d      dt        dS t         j                  d       y# t        $ r"}t         j                  d|d       Y d}~yd}~ww xY w)zUpscale an image using FAL.ai's Clarity Upscaler.

    Returns upscaled image dict, or None on failure (caller falls back to
    the original image).
    z(Upscaling image with Clarity Upscaler...z, )	r   r%   upscale_factornegative_promptrZ   resemblancer8   r!   r#   rt   imagez$Image upscaled successfully to %sx%swidthunknownheighturlr   T)r   r   r   upscaledr   z"Upscaler returned invalid responseNzError upscaling image: %sexc_info)r   infoUPSCALER_DEFAULT_PROMPTUPSCALER_FACTORUPSCALER_NEGATIVE_PROMPTUPSCALER_CREATIVITYUPSCALER_RESEMBLANCEUPSCALER_GUIDANCE_SCALEUPSCALER_NUM_INFERENCE_STEPSUPSCALER_SAFETY_CHECKERr   UPSCALER_MODELr   errorr   )r   r   upscaler_argumentshandlerresultupscaled_imagees          r   _upscale_imager     s   %>? #01O3DE-7-/5#?%<

 &n@RSg'#G_NKK6""7I6""8Y7 &e,'++GQ7(,,Xq9 "1  	9: 0!dCs   CC0 C0 0	D9DDvaluec                     | rt        | t              sy| j                         }|j                  d      ryt        j
                  j                  |       ryt        |       dk\  xr | d   dk(  xr | d   dv S )	NF)zhttp://zhttps://zdata:TrY   r6   :rC   >   rg   \)r   r}   r   
startswithr   pathisabslen)r   r   s     r   _looks_like_absolute_file_pathr     sj    
5#.KKME89	ww}}Uu:?JuQx3J58{3JJr   task_idc                     	 ddl m}  || xs d      S # t        $ r }t        j	                  d|       Y d }~y d }~ww xY w)Nr   )get_active_envdefaultz1Could not inspect active terminal environment: %s)tools.terminal_toolr   r   r   r   )r   r   r   s      r   _active_terminal_envr     s<    6g233 H#Ns    	=8=envc                    | t        | dd       }t        |      r%	  |       }|rt        |      j                  d      S 	 t        | dd       }|rt        |      j                  d       dS | j                  j                  }|dv ryt        j                  d      xs d	j                         j                         }|d
v ry|dk(  ryy # t        $ r }t
        j                  d|       Y d }~d }~ww xY w)Nagent_visible_cache_baserg   z.active env agent_visible_cache_base failed: %s_remote_homez/.hermes>   ModalEnvironmentDockerEnvironmentSingularityEnvironmentz/root/.hermesTERMINAL_ENVlocal>   modaldockersingularitysshz	~/.hermes)getattrcallabler}   rk   r   r   r   	__class____name__r   r   r   r   )r   explicitr   r   remote_homeenv_namebackends          r   _agent_cache_base_for_envr     s    

 3 :DAHT 
u:,,S11 
 c>48+&--c238<<==))ZZ" yy(3G::<BBDG44%)  TMsSSTs   "C 	C1C,,C1	host_pathc                     t        |       sy t        |      }|sy 	 ddlm}  || |      S # t        $ r }t
        j                  d|       Y d }~y d }~ww xY w)Nr   )map_cache_path_to_container)container_basez4Could not translate image cache path for backend: %s)r   r   tools.credential_filesr   r   r   r   )r   r   
cache_baser   r   s        r   _agent_visible_cache_pathr     sZ    ))4*3/JRF*9ZPP RKSQQRs   , 	AAAc                     t        | dd       }|y 	 |j                  d       y # t        $ r }t        j	                  d|       Y d }~y d }~ww xY w)N_sync_managerTforcez1Could not force-sync generated image artifact: %s)r   syncr   r   r   )r   sync_managerr   s      r   _force_artifact_syncr    sT    36LQ% QJCPPQs   % 	AA		Ar   c                    	 t        | t              rt        j                  |       n| }t        |t
              r|j                  d      s| S |j                  d      }t        |t              rt        |      s| S t        |      }t        ||      }|r||k(  r| S |t        |       |j                  d|       |j                  d|       t        j                  |d      S # t        $ r | cY S w xY w)a  Annotate successful local image results with backend-visible paths.

    ``image`` remains the host/gateway-deliverable path.  When the active
    terminal backend has a different filesystem, ``agent_visible_image`` gives
    the path the agent can use with terminal/file tools.
    successr   
host_imageagent_visible_imageF)ensure_ascii)r   r}   jsonloadsr   r   r   r   r   r   r  
setdefaultdumps)r   r   r   r   r   
agent_paths         r   "_postprocess_image_generate_resultr  &  s    %/S%9$**S/s gt$GKK	,B
KK EeS!)G)N

w
'C*5#6Ju,

S!|U+,j9::gE22)  
s   'C" "C0/C0r!   r8   r9   r"   reference_image_urlsc	                    t               \  }	}
g }t        |t              r/|j                         r|j	                  |j                                t        |t
        t        f      rH|D ]C  }t        |t              s|j                         s%|j	                  |j                                E |
j                  d      }t        |      xr t        |      }|rdnd}|	| |||||||t        |      d	ddddd}t        j                  j                         }	 | r,t        | t              rt        | j                               dk(  rt        d	      t               st               st        t                     |r$|s"t        d
|
j                  d|	       d|	 d      |xs t         j#                         j                         }|t$        vr!t&        j)                  d|t                t         }i }|||d<   |||d<   |||d<   |||d<   |rst+        |
j                  d      xs d      }|dkD  r|d| n|}t-        |	| ||||      }|}t&        j/                  d|
j                  d|	      |t        |      | dd        n=t1        |	| |||      }|	}t&        j/                  d|
j                  d|	      |	| dd        t3        ||      }|j                         }t        j                  j                         |z
  j5                         }|rd|vrt        d      |j                  dg       }|st        d      t        |
j                  dd            xr | }g }|D ]  }t        |t6              rd|v s|d   |j                  dd      |j                  d d      d!}|rFt9        |d   | j                               } | r|j	                  |        tt&        j)                  d"       d|d#<   |j	                  |        |st        d$      t;        d% |D              }!t&        j/                  d&t        |      ||!||       d'|r|d   d   nd|d(}"d'|d)<   t        |      |d*<   ||d+<   t<        j?                  d,|       t<        jA                          tC        jD                  |"d-d.      S # tF        $ r}#t        j                  j                         |z
  j5                         }d/t        |#       }$t&        jI                  d0|$d'1       ddt        |#      tK        |#      jL                  d2}"|$|d3<   ||d+<   t<        j?                  d,|       t<        jA                          tC        jD                  |"d-d.      cY d}#~#S d}#~#ww xY w)4a-  Generate an image from a text prompt, or edit a source image, via FAL.

    Routing: when ``image_url`` (or ``reference_image_urls``) is provided AND
    the configured model declares an ``edit_endpoint``, the call routes to that
    image-to-image / edit endpoint; otherwise it's plain text-to-image.

    The agent-facing schema exposes ``prompt``, ``aspect_ratio``, ``image_url``
    and ``reference_image_urls``; the remaining kwargs are overrides for direct
    Python callers and are filtered per-model via the ``supports`` /
    ``edit_supports`` whitelist (unsupported overrides are silently dropped so
    legacy callers don't break when switching models).

    Returns a JSON string with ``{"success": bool, "image": url | None,
    "modality": "text" | "image", "error": str, "error_type": str}``.
    r2   r   text)	r%   r>   r!   r8   r9   r"   r$   modalitysource_imagesNFr   )rs   
parametersr   r  images_generatedgeneration_timez1Prompt is required and must be a non-empty stringzModel 'r)   z' (u   ) is not capable of image-to-image / editing. Provide a text-only prompt (omit image_url), or switch to an edit-capable model via `hermes tools` → Image Generation.z-Invalid aspect_ratio '%s', defaulting to '%s'r!   r8   r9   r"   r4   r6   )r$   r   u=   Editing image with %s (%s) — %d source image(s), prompt: %sP   u,   Generating image with %s (%s) — prompt: %sr   imagesu7   Invalid response from FAL.ai API — no images returnedzNo images were generatedr1   r   r   r   )r   r   r   z1Using original image as fallback (upscale failed)r   z%No valid image URLs returned from APIc              3   D   K   | ]  }|j                  d       sd  yw)r   r6   N)r   ).0imgs     r   	<genexpr>z&image_generate_tool.<locals>.<genexpr>  s     R3cggj>QQRs     z8Generated %s image(s) in %.1fs (%s upscaled) via %s [%s]T)r  r   r  r  r  r  image_generate_toolrC   )indentr  zError generating image: z%sr   r  r   r   
error_typer   )'r   r   r}   r   appendr   tupler   boolr   datetimenowr   r   re   _build_no_backend_setup_messager   r   VALID_ASPECT_RATIOSr   r   r   r   r   r   r   total_secondsr   r   sum_debuglog_callsaver  r
  r   r   typer   )%r%   r>   r!   r8   r9   r"   r$   r   r  r   r   r  refr2   use_editr  debug_call_data
start_time	aspect_lcr   max_refsclamped_sourcesrt   endpointr   r   r  r  should_upscaleformatted_imagesr  original_imager   upscaled_countresponse_datar   	error_msgs%                                        r   r  r  F  s}   4 ()NHd M)S!ioo&7Y__./&u6' 	2C#s#		$$SYY[1	2 HH_-MM":tM':H"wH (#6,$*  /

 !O& ""&&(JIGZ4FLLN8Kq8PPQQ%'+G+I<>??
 $((9h78H: F; <  "9%9@@BHHJ	//NN?2 -I$&	*/BI+,%*8I&'!&0Il#$)6Io&488$:;@qAH:BQ,mIX6MO/&/9YI %HKKOH-x_9Ms +&)$)I  HKK>H-x
 &h)D#,,002Z?NNP/VWWHb)788 dhhy%89J(l 	4CsD)esl5z!,''(A.N !/E
FLLN!K!$++N;RS).N:&##N3#	4&  DEER*:RRF !?NH	
 3C%a(/ 
 &*	".12B.C*+-<)*-?zz-FF G#,,002Z?NNP.s1vh7	T9t4 Vq'**	
 $- -<)*-?zz-FF#Gs    NR) )	V 2CU;5V ;V c                  @    t        t               xs
 t                     S )zDTrue if the FAL.ai API key (direct or managed gateway) is available.)r!  r   re   rd   r   r   check_fal_api_keyr;    s    %'I+G+IJJr   c                     ddg} | j                  d       t               r| j                  d       n2| j                  d       t        d      }|r| j                  d|        | j                  d       | j                  d       | j                  d	       t               r| j                  d
       | j                  d       dj                  |       S )aR  Build an actionable error string when no FAL backend is reachable.

    Used by the in-tree FAL path. Mentions:
      - FAL_KEY signup link
      - managed-gateway status (if Nous tools are enabled)
      - plugin alternative pointer (so users on a stale ``image_gen.provider``
        know the registry exists and how to inspect it)
    z4Image generation is unavailable in this environment.rw   zMissing requirements:zA  - FAL_KEY is not set and the managed FAL gateway is unreachablez+  - FAL_KEY environment variable is not setr{   z  - z&To enable image generation, do one of:z_  1. Get a free API key at https://fal.ai and set FAL_KEY=<your-key> (then restart the session)zX  2. Sign in to a Nous account that has the managed FAL gateway enabled (`hermes setup`)u     3. Configure a different image_gen provider via `hermes tools` → Image Generation (run `hermes plugins list` to see installed backends)
)r  r   r   join)linesr   s     r   r$  r$    s     DRHE	LL()!#O	
 	BC?*
 LL4012	LL	LL9:	LL	8 "#/	
 
LL	
 99Ur   c                     	 t               rt                y	 t               } | r| dk(  ry	 ddlm} ddlm}  |         ||       }t        |xr |j                               S # t        $ r Y Vw xY w# t        $ r Y yw xY w)zDTrue if FAL or the explicitly configured image backend is available.TfalFr   get_provider_ensure_plugins_discovered)r;  r   ImportError_read_configured_image_provideragent.image_gen_registryrC  hermes_cli.pluginsrE  r!  is_availabler   )
configuredrC  rE  providers       r   #check_image_generation_requirementsrM  >  s    	
   12Ju,9A"$
+H8!6!6!899     s"   A# 7A2 #	A/.A/2	A>=A>__main__u:   🎨 Image Generation Tools — FAL.ai multi-model supportz<============================================================u(   ❌ FAL_KEY environment variable not setz-   Set it via: export FAL_KEY='your-key-here'z   Get a key: https://fal.ai/u   ✅ FAL.ai API key foundu    ✅ fal_client library availableu;   ❌ fal_client library not found — pip install fal-clientu   🤖 Active model: r)   z ()z
   Speed: r*   ?u     ·  Price: r,   z   Upscaler: r1   onoffz
Available models:u    ← activerw   z  z<32z<6u%   
🐛 Debug mode enabled — session )registry
tool_errorimage_generateuB  Generate high-quality images from text prompts (text-to-image), or edit / transform an existing image (image-to-image) when the active model supports it. Pass `image_url` to edit that image; add `reference_image_urls` for style/composition references; omit both for text-to-image. The underlying backend (FAL, OpenAI, xAI, etc.) and model are user-configured and not selectable by the agent. Returns the result in the `image` field — either a URL or an absolute file path. To show it to the user, reference that path/URL in your response using the file-delivery convention for the current platform (your platform guidance describes how files are delivered here). When the active terminal backend has a different filesystem, successful local-file results may also include `agent_visible_image` for follow-up terminal/file operations.objectstringzThe text prompt describing the desired image (text-to-image) or the edit to apply (image-to-image). Be detailed and descriptive.)r+  descriptionzlThe aspect ratio of the generated image. 'landscape' is 16:9 wide, 'portrait' is 16:9 tall, 'square' is 1:1.)r+  enumrX  r   ud  Optional source image to edit/transform (image-to-image). When provided, the active backend routes to its image editing endpoint; when omitted, it generates from text alone. Pass a public URL or an absolute local file path from the conversation. Only honored by models that support editing — the description above indicates whether the active model does.arrayr+  zOptional list of additional reference image URLs / paths (style, character, or composition references) to guide an image-to-image edit. Supported only by some models and capped per-model; the description above indicates the max.)r+  r   rX  r%   r>   r   r  )r+  
propertiesrequired)namerX  r  c                  `   	 ddl m}   |        }t        |t              r|j	                  d      nd}t        |t              rA|j	                  d      }t        |t
              r |j                         r|j                         S y# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)zBReturn the value of ``image_gen.model`` from config.yaml, or None.r   r   rc   Nrs   z"Could not read image_gen.model: %s
r   r   r   r   r   r}   r   r   r   r   r   r   sectionr   r   s        r   _read_configured_image_modelrc    s    	@1m*4S$*?#''+&Tgt$KK(E%%%++-{{}$   @93??@   B B 	B-B((B-c                  `   	 ddl m}   |        }t        |t              r|j	                  d      nd}t        |t              rA|j	                  d      }t        |t
              r |j                         r|j                         S y# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)u  Return ``image_gen.provider`` from config.yaml, or None.

    We only consult the plugin registry when this is explicitly set — an
    unset value keeps users on the in-tree FAL fallback even when other
    providers happen to be registered (e.g. a user has OPENAI_API_KEY set
    for other features but never asked for OpenAI image gen). ``"fal"``
    explicitly routes through ``plugins/image_gen/fal/`` (which delegates
    back into this module's pipeline via call-time indirection — see
    issue #26241).
    r   r   rc   NrL  z%Could not read image_gen.provider: %sr`  ra  s        r   rG  rG    s    	C1m*4S$*?#''+&Tgt$KK
+E%%%++-{{}$   C<cBBCrd  c                    t               }|r|dk(  ryt               }	 ddlm} ddlm}  |         ||      }|	  |d        ||      }|t        j                  d
dd| ddd      S | |d}
	 |r||
d<   t        |t              r#|j                         r|j                         |
d<   d}|ddlm}  ||      }|r||
d<    |j                   d i |
}t        |t(              st        j                  d
dddd      S t        j                  |      S # t        $ r }	t        j                  d|	       Y d}	~	yd}	~	ww xY w# t        $ r!}	t        j                  d	|	       Y d}	~	d}	~	ww xY w# t"        $ r}	d|
v sd|
v rPt        j%                  dt'        |dd      |	       t        j                  d
ddt'        |dd       ddd      cY d}	~	S t        j%                  dt'        |dd      |	       t        j                  d
ddt'        |dd       d|	 dd      cY d}	~	S d}	~	wt        $ rW}	t        j%                  dt'        |dd      |	       t        j                  d
ddt'        |dd       d|	 dd      cY d}	~	S d}	~	ww xY w)!u  Route the call to a plugin-registered provider when one is selected.

    Returns a JSON string on dispatch, or ``None`` to fall through to the
    in-tree FAL fallback in ``image_generate_tool``.

    Dispatch fires when ``image_gen.provider`` is explicitly set — including
    ``"fal"`` itself, which now resolves to the
    ``plugins/image_gen/fal/`` plugin (the plugin re-enters this module's
    pipeline via ``_it`` indirection so behavior is identical to the
    direct call, just routed through the registry).

    ``image_url`` / ``reference_image_urls`` enable image-to-image / editing:
    they are forwarded to the provider's ``generate()`` so the backend can
    route to its edit endpoint.
    rA  Nr   rB  rD  z%image_gen plugin dispatch skipped: %sTr   z*image_gen plugin force-refresh skipped: %sFzimage_gen.provider='zk' is set but no plugin registered that name. Run `hermes plugins list` to see available image gen backends.provider_not_registeredr  )r%   r>   rs   r   normalize_reference_imagesr  zQimage_gen provider '%s' rejected image-to-image kwargs (signature too narrow): %sr^  rP  z
Provider 'u   ' does not support image-to-image / editing (its generate() signature is out of date with the image_generate schema). Omit image_url for text-to-image, or pick a backend that supports editing via `hermes tools` → Image Generation.modality_unsupportedz,Image gen provider '%s' raised TypeError: %sz	' error: provider_exceptionz"Image gen provider '%s' raised: %sz#Provider returned a non-dict resultprovider_contractrd   )rG  rc  rH  rC  rI  rE  r   r   r   r  r
  r   r}   r   agent.image_gen_providerri  generate	TypeErrorr   r   r   )r%   r>   r   r  rK  configured_modelrC  rE  rL  r   kwargs	norm_refsri  r   s                 r   _dispatch_to_plugin_providerrs    s   * 12Ju, 45
 	:A"$
+
 	L 'T2#J/H zz&zl 30 1 4	
 	 		 )/MF8.F7Oi%)//*;"+//"3F;	+K23GHI-6F)*""",V,Z fd#zz:-	
  	 ::fw  <cB  	LLLEsKK	L:   
 & $:f$DNN-&#.
 ::  63!? @ AP Q 5   	:Hfc*C	
 zz!'(FC"@!A3%P.	
  	  
0Hfc*C	
 zz!'(FC"@!A3%P.	
  	
sn   D E 3A%E0 	E  D;;E 	E-E((E-0	J9AH#JAH#J#J/AJ;JJ>   krea-2-largekrea-2-mediumkrea-2-medium-turboc                 Z    t        | t              sy| j                         }|t        v r|S y)zIReturn the native Krea plugin model id when ``model_id`` is ``krea-2-*``.N)r   r}   r   _KREA_NATIVE_MODELS)r   	candidates     r   _normalize_krea_modelrz  |  s-    h$ I''r   c                     t        |       duS )zATrue when ``model_id`` is a native Krea plugin id (``krea-2-*``).N)rz  )r   s    r   is_krea_modelr|    s     *$66r   c                 2   t               dk(  ryt        t                     }|y	 ddlm}  |       y	 	 ddlm	} ddl
m}  |         |d      }	|	y| ||d	}
	 t        |t              r#|j                         r|j                         |
d
<   d}|ddlm}  ||      }|r||
d<    |	j"                  di |
}t        |t*              st'        j(                  ddddd      S t'        j(                  |      S # t
        $ r }t        j                  d|       Y d}~yd}~ww xY w# t
        $ r }t        j                  d|       Y d}~yd}~ww xY w# t
        $ r=}t        j%                  d|       t'        j(                  ddd| dd      cY d}~S d}~ww xY w)aO  Route a native ``krea-2-*`` model to the managed Krea gateway, in managed mode.

    Returns a JSON result string when handled by the Krea managed gateway, or
    ``None`` to fall through to the normal plugin/FAL pipeline. Fires only when
    all hold:
      - the configured image model is a native ``krea-2-*`` id, AND
      - the user isn't already routed to the Krea plugin via
        ``image_gen.provider`` (that path dispatches normally), AND
      - the managed Krea gateway is resolvable (portal/managed mode).

    Direct/BYO users (no managed gateway) fall through untouched.
    kreaNr   )_resolve_managed_krea_gatewayz%Managed Krea routing probe failed: %srB  rD  z.Managed Krea routing: provider unavailable: %s)r%   r>   rs   r   rh  r  zManaged Krea routing failed: %sFzManaged Krea generation error: rk  r  z(Krea provider returned a non-dict resultrl  rd   )rG  rz  rc  plugins.image_gen.krear  r   r   r   rH  rC  rI  rE  r   r}   r   rm  ri  rn  r   r  r
  r   )r%   r>   r   r  
normalizedr  r   rC  rE  rL  rq  rr  ri  r   s                 r   _maybe_route_managed_krear    s   & '(F2&'C'EFJH(*2 39A"$'  $F
i%)//*;"+//"3F;	+K23GHI-6F)*""",V, fd#zz?-	
  	 ::f_  <cB  EsK,  8#>zz6se<.	
  	sM   C8 D$ AE 8	D!DD!$	E-EE	F2FFFc                    | j                  dd      }|st        d      S | j                  dt              }| j                  d      }| j                  d      }|j                  d      }t        ||||      }|t	        ||	      S t        ||||      }|t	        ||	      S t        ||||
      }	t	        |	|	      S )Nr%   rw   z'prompt is required for image generationr>   r   r  r   )r   r  )r   r[  )r   rT  r   rs  r  r  r  )
argskwr%   r>   r   r  r   
dispatchedkrea_routedr   s
             r   _handle_image_generater    s    XXh#FCDD88N,@AL%I88$:;ffYG
 .1J
 1*gNN ,1K
 1+wOO
!1	C .c7CCr   rX  c                     dgdd} t               }|r|dk7  r	 ddlm} ddlm}  |         ||      }|i }	 |j                         xs i }|j                  | d<   t               xs |j                         xs d| d	<   |j                  d
      rt        |d
         | d
<   |j                  d      rt        |d         | d<   | S 	 	 t               \  }}d| d<   |j                  d|      | d	<   |j                  d      r*ddg| d
<   t        |j                  d      xs d      | d<   | S dg| d
<   d| d<   	 | S # t        $ r i }Y w xY w# t        $ r Y w xY w# t        $ r Y | S w xY w)a  Best-effort: return the active backend/model's image capabilities.

    Resolution order mirrors the runtime dispatch:
    1. If ``image_gen.provider`` is set, ask that plugin provider.
    2. Otherwise inspect the in-tree FAL model catalog for the active model.

    Returns a dict like ``{"modalities": [...], "max_reference_images": N,
    "model": "...", "provider": "..."}``. Never raises.
    r  r   )
modalitiesr4   rA  rB  rD  rL  rw   rs   r  r4   zFAL.air)   r2   r   r6   )rG  rH  rC  rI  rE  capabilitiesr   display_namerc  default_modelr   r   r   r   )r   configured_providerrC  rE  rL  capsr   r   s           r   _active_image_capabilitiesr    s    ,2(AND9;2e;	=E&(#$78H##0028bD $,#8#8Z  < > b8CYCYC[Ca_aW88L))-d<.@)AD&882336t<R7S3TD/0 $"+-$#ZH5W88O$"('!2D+.txx8N/O/TST+UD'( K #)D+,D'( K5 ! D  		  KsN   E D7 A7E A E )E 7EE EE 	EE	E$#E$c                  $   t         g} 	 t               }|j                  d      }|j                  d      }t	        |j                  d      xs dg      }d}|r|d| z  }|r|d| z  }| j                  |       d	|v r>d|v r:|j                  d
      xs d}|r|dkD  rd| dnd}| j                  d| d       n+d	|v rd|vr| j                  d       n| j                  d       ddj                  |       iS # t        $ r dt         icY S w xY w)zIBuild a description reflecting whether the active model supports editing.rX  rL  rs   r  r  z
Active backendz: u    · model: r   r4   r   r6   z; up to z, reference image(s) via reference_image_urlsrw   z\- supports both text-to-image (omit image_url) and image-to-image / editing (pass image_url)u    — routes automaticallyuD   - this model is image-to-image / edit only — image_url is REQUIREDu   - this model is text-to-image only — it is NOT capable of image-to-image / editing; do not pass image_url or reference_image_urls (they will be rejected). Provide a text-only prompt.r=  )_GENERIC_IMAGE_DESCRIPTIONr  r   r   r   r  r>  )partsr   rL  rs   r  liner1  ref_notes           r   _build_dynamic_image_schemar  K  sV   '(E;)+ xx
#HHHWETXXl+7x8JD"XJ+eW%%	LL*:!588238q HqL xj LM 	
 	88@z B##	

 
J	6#;R	
 	 	
 499U+,,M  ;9::;s   
C; ;DDrc   u   🎨)	r^  toolsetschemar   check_fnrequires_envis_asyncemojidynamic_schema_overrides)N)NN)c__doc__r  loggingr   r"  	threadingr~   typingr   r   r   r   __annotations__r   tools.debug_helpersr   r   r   r   r   tools.managed_tool_gatewayr   tools.tool_backend_helpersr   r   r   r   	getLoggerr   r   r]   r}   r   r   r%  r   r   r   r   r   r   r   r   r   r(  rn   ro   Lockrm   re   rr   r   r   r   r   r   r   r   r   r!  r   r   r   r   r  r  floatr  r;  r$  rM  print
SystemExitrF  r   r   r   r   midmmarkeractive
session_idtools.registryrS  rT  IMAGE_GENERATE_SCHEMArc  rG  rs  rx  rz  r|  r  r  r  r  r  registerrd   r   r   <module>r     s
  *   	    & & 
C #  - 
 D  
		8	$4 %'))!'
 $%"%*


  7
 !"9>  *))!'
 $&!"%* #


 1

 !"C"H #*))!'
 $%"%*',


 /4 :D#$
 " # 


  7

 !"C"H #'#$!#
  "

 4
 !#;@ !J% *(!&
  "

  3
 !#O(T !&#))!'
  *!


 2
 !"7< $>))!'
 $U

 ',,  .))!'
 $&!"%

  8
 !"=H #P6$
 (

 %,* "R6$
 (

 %+_	C)
Dd38n$% CL
 )" 9  + > K    !  
m-@	A ! )9>>+ 5#0(s (tCH~ (\ *E  *L -*.... . 3-	.
 S#X'. 
#s(^.j -*.666 6 	6
 3-6 S#X'6 
#s(^6x+c +C +HT#s(^<T +bK# K$ K#*  3  3:  F 3 3: "Qc Qd Q3C 3#* 3PS 3D -)-&* $#'#+/GGGGGG "#GG UO	GG
 GG C=GG 3-GG }GG #4.GG 	GGTK4 K
& &RT B z	
FG	(O89=>-.m	
$%01
 ()NHd	H =>b
!
LM	Jtxx-.mDHHWc<R;S
TU	M$((9"5$5A
BC	
 ""$ WQ"%/r3s)2aeeGS1"5Rgs8K7LVHUVW }}6v7H7H6IJK 0 	.  !' !01  N/	 !-   (+Q		%7%
L JQ)-@ F6  $+/	 } #4.	V O HSM hsm 7HSM 7d 7  $+/	OOO }O #4.	O
 c]Od'Dl 3=A 2DcN 2j,-T#s(^ ,-^   	 "0
8
[  KLms   ?R: :S