
    `gjeW                       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 ddl	m
Z
mZmZmZmZ ddlmZmZmZmZmZmZmZ  ej.                  e      Z G d de      Zd	Zd
Zd8dZdZddddddddddddddddZ de!d<   dZ"ddd d!Z#d"Z$d#Z%d$Z&d%Z'd&Z( e)h d'      Z*d9d(Z+d:d)Z,d;d*Z-d<d+Z.d=d,Z/d=d-Z0d>d.Z1	 	 	 	 	 	 d?d/Z2dd0	 	 	 	 	 	 	 	 	 d@d1Z3dAd2Z4dBd3Z5dd0	 	 	 	 	 	 	 	 	 	 	 dCd4Z6 G d5 d6e      Z7dDd7Z8y)Eu  OpenAI image generation backend — ChatGPT/Codex OAuth variant.

Identical model catalog and tier semantics to the ``openai`` image-gen plugin
(``gpt-image-2`` at low/medium/high quality), but routes the request through
the Codex Responses API ``image_generation`` tool instead of the
``images.generate`` REST endpoint. This lets users who are already
authenticated with Codex/ChatGPT generate images without configuring a
separate ``OPENAI_API_KEY``.

Selection precedence for the tier (first hit wins):

1. ``OPENAI_IMAGE_MODEL`` env var (escape hatch for scripts / tests)
2. ``image_gen.openai-codex.model`` in ``config.yaml``
3. ``image_gen.model`` in ``config.yaml`` (when it's one of our tier IDs)
4. :data:`DEFAULT_MODEL` — ``gpt-image-2-medium``

Output is saved as PNG under ``$HERMES_HOME/cache/images/``. Source images for
image-to-image/editing are sent as Responses ``input_image`` content parts.
    )annotationsN)Path)AnyDictListOptionalTuple)DEFAULT_ASPECT_RATIOImageGenProvidererror_responsenormalize_reference_imagesresolve_aspect_ratiosave_b64_imagesuccess_responsec                      e Zd ZdZy)$CodexImageGenerationUnsupportedErrorz:The active Codex account cannot use the hosted image tool.N)__name__
__module____qualname____doc__     Z/root/.hermes/venv/lib/python3.12/site-packages/plugins/image_gen/openai-codex/__init__.pyr   r   +   s    Dr   r   zxImage generation is not enabled for the current Codex account. Switch the image provider to OpenAI API key, FAL, or xAI.z>Tool choice 'image_generation' not found in 'tools' parameter.c                F   | dk7  ry	 t        j                  |      }t        |t              r|j	                  d      nd}t        |t              r|j	                  d      nd}t        |t              xr |j                         t        k(  S # t
        t        f$ r |}Y <w xY w)zCMatch only Codex's account-capability rejection for the image tool.i  FerrorNmessage)
jsonloads
isinstancedictget	TypeError
ValueErrorstrstrip#_IMAGE_GENERATION_UNSUPPORTED_ERROR)status_codebodypayloadr   r   s        r   &_is_image_generation_unsupported_errorr*   8   s    c**T"(27D(AG$t*4UD*A%))I&t gs#^;^(^^ z" s   AB B B zgpt-image-2zGPT Image 2 (Low)z~15szFast iteration, lowest costlow)displayspeed	strengthsqualityzGPT Image 2 (Medium)z~40su   Balanced — defaultmediumzGPT Image 2 (High)z~2minz,Highest fidelity, strongest prompt adherencehigh)zgpt-image-2-lowgpt-image-2-mediumzgpt-image-2-highzDict[str, Dict[str, Any]]_MODELSr2   	1536x1024	1024x1024	1024x1536)	landscapesquareportraitzgpt-5.5z%https://chatgpt.com/backend-api/codexzYou are an assistant that must fulfill image generation and image editing requests by using the image_generation tool when provided.   i  >   	image/gif	image/png
image/jpeg
image/webpc                     	 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)z@Read ``image_gen`` from config.yaml (returns {} on any failure).r   )load_config	image_genNz#Could not load image_gen config: %s)hermes_cli.configr@   r   r    r!   	Exceptionloggerdebug)r@   cfgsectionexcs       r   _load_image_gen_configrI      sb    1m*4S$*?#''+&T$Wd3w;; :C@	s$   AA A 	A1A,&A1,A1c                    ddl } | j                  j                  d      }|r|t        v r|t        |   fS t	               }t        |j                  d      t              r|j                  d      ni }d}t        |t              r+|j                  d      }t        |t              r
|t        v r|}|+|j                  d      }t        |t              r
|t        v r|}||t        |   fS t        t        t           fS )z9Decide which tier to use and return ``(model_id, meta)``.r   NOPENAI_IMAGE_MODELopenai-codexmodel)	osenvironr!   r3   rI   r   r    r$   DEFAULT_MODEL)rN   env_overriderF   sub	candidatevaluetops          r   _resolve_modelrV      s    ::>>"67L/W\222
 
"C%/0G%N#''.
!TVC#I#t eS!ew&6Igggc3C7NI'),,,'-000r   c                     	 ddl m}   |        }t        |t              r |j	                         r|j	                         S y# t
        $ r }t        j                  d|       Y d}~yd}~ww xY w)zReturn a usable Codex OAuth token, or None.

    Delegates to the canonical reader in ``agent.auxiliary_client`` so token
    expiry, credential pool selection, and JWT decoding stay in one place.
    r   )_read_codex_access_tokenNz(Could not resolve Codex access token: %s)agent.auxiliary_clientrX   r   r$   r%   rC   rD   rE   )_readertokenrH   s      r   rX   rX      sT    	N	eS!ekkm;;=  ?Es   <A   	A)	A$$A)c                4    ddl m}  ||       }|t        v r|S y)u  Return a safe raster image MIME from magic bytes (not filename labels).

    Delegates magic-byte detection to the shared sniffer in
    ``agent.image_routing`` (single source of truth), then gates the result
    to :data:`_ACCEPTED_INPUT_MIME` — the raster formats gpt-image-2's
    ``input_image`` actually accepts. SVG/TIFF/ICO (which the shared sniffer
    also recognizes) are rejected here so they fail locally with a clear
    error instead of an opaque server-side HTTP 400.
    r   )_sniff_mime_from_bytesN)agent.image_routingr]   _ACCEPTED_INPUT_MIME)rawr]   mimes      r   _sniff_image_mimerb      s"     ;!#&D##r   c                   d| vrt        d      | j                  dd      \  }}|j                         }|j                  d      rd|vrt        d      t	        j
                  |d      }t        |      t        kD  rt        d	      t        |      }|t        d
      t	        j                  |      j                  d      }d| d| S )zEValidate and canonicalize a data:image URL for Responses input_image.,z+Image data URL is missing a comma separator   zdata:image/z;base64z?Only base64 data:image URLs are supported as Codex image inputsT)validatezImage data URL exceeds 25MB capz5Image data URL does not contain supported image bytesasciidata:;base64,)r#   splitlower
startswithbase64	b64decodelen_MAX_INPUT_IMAGE_BYTESrb   	b64encodedecode)rT   headerdata	header_lcr`   ra   encodeds          r   _data_url_to_input_image_urlrw      s    
%FGG;;sA&LFDI.)92LZ[[


4$
/C
3x((:;;S!D|PQQs#**73G4&	**r   c                   	 ddl m}  ||       }|rt        |      	 t        t        j                  j                  |             j                         }|j                         st        d|        |j                         j                  }|dk  rt        d|        |t        kD  rt        d|        |j                         }t!        |      }|t        d|        t#        j$                  |      j'                  d	      }d
| d| S # t        $ r  t        $ r!}t        j                  d|       Y d}~d}~ww xY w)z>Read a local image path and return a validated data:image URL.r   )get_read_block_errorz,Codex image input read guard unavailable: %sNz2Image input path does not exist or is not a file: zImage input path is empty: z#Image input path exceeds 25MB cap: z+Image input path is not a supported image: rg   rh   ri   )agent.file_safetyry   r#   rC   rD   rE   r   rN   path
expanduserresolveis_filestatst_sizerp   
read_bytesrb   rm   rq   rr   )	rT   ry   blockedrH   r{   sizer`   ra   rv   s	            r   _local_image_to_data_urlr      s<   	J:&u-W%%  ""5)*224D<<>MeWUVV99;Dqy6ug>??$$>ugFGG
//
CS!D|FugNOOs#**73G4&	**%   JCSIIJs   D E D;;E c                   | xs dj                         }|st        d      |j                         }|j                  d      s|j                  d      r|}n(|j                  d      rt	        |      }nt        |      }d|dS )zDConvert a URL/data URL/local path into a Responses input_image part. zBlank image inputzhttp://zhttps://rh   input_image)type	image_url)r%   r#   rk   rl   rw   r   )rT   rS   loweredr   s       r   _to_input_image_partr      s|    "##%I,--ooG)$(:(::(F				G	$0;	,Y7	!	::r   c                   g }t        | t              r/| j                         r|j                  | j                                t	        |      xs g D ]  }|j                  |        |dt
         }|D cg c]  }t        |       c}S c c}w )zFCollect primary + reference images as ordered Responses content parts.N)r   r$   r%   appendr   _MAX_REFERENCE_IMAGESr   )r   reference_image_urlsvaluesrefrT   s        r   _normalize_input_imagesr     s}    
 F)S!ioo&7ioo'(*+?@FB c**+F5;<E '<<<s   4B	)input_imagesc                    d| dg}|r|j                  |       t        dt        dd|dgdt        ||dd	d
dgddddigdddS )zDBuild the Codex Responses request body for an image_generation call.
input_text)r   textFr   user)r   rolecontentimage_generationpngopaquere   )r   rM   r   r/   output_format
backgroundpartial_imagesallowed_toolsrequiredr   )r   modetoolsT)rM   storeinstructionsinputr   tool_choicestream)extend_CODEX_CHAT_MODEL_CODEX_INSTRUCTIONS	API_MODEL)promptr   r/   r   r   s        r   _build_responses_payloadr     s     /;F%K$LG|$"+
  '""
  $123

 / r   c                   d}t        | t              r| j                  d      dk(  r%| j                  d      }t        |t              r|r|}| j                  d      }t        |t              r|r|}| j	                         D ]  }t        |      }|s|} |S t        | t              r| D ]  }t        |      }|s|} |S )zBReturn the newest image b64 embedded in a Responses event payload.Nr   image_generation_callresultpartial_image_b64)r   r    r!   r$   r   _extract_image_b64list)rT   foundr   partialchildnesteds         r   r   r   ?  s    E%99V 77YYx(F&#&6))/0gs#E\\^ 	E'.F	 L 
E4	  	E'.F	 Lr   c              #    K   dg fd}| j                         D ]  }t        |t              r|j                  dd      }t	        |      }|dk(  r |       }|| D|j                  d      rV|j                  d      r|t        d      d j                         |j                  d	      sj                  |t        d	      d j                                  |       }|| yyw)
a
  Yield JSON payloads from an SSE response without OpenAI SDK parsing.

    The ChatGPT/Codex backend can emit image-generation events newer than the
    pinned Python SDK understands. Parsing raw SSE keeps this provider tolerant
    of those event-shape changes.
    Nc                     sd y dj                        j                         } }d g | r| dk(  ry t        j                  |       }t	        |t
              r|r	d|vr||d<   |S )N
z[DONE]r   )joinr%   r   r   r   r    )r`   eventr)   
data_lines
event_names      r   flushz_iter_sse_json.<locals>.flush`  sn    Jii
#))+

cXo**S/gt$63H#GFOr   zutf-8replace)errorsr   :zevent:rh   )

iter_linesr   bytesrr   r$   rl   ro   r%   r   lstrip)responser   liner)   r   r   s       @@r   _iter_sse_jsonr   V  s      !%JJ  ##% <dE";;wy;9D4y2:gG"??3??8$c(mn-335J__W%d3w<=188:;<  gG s   B0C15<C1c          	        ddl }ddlm}  ||       }|j                  dd|  dd       t	        ||||      }|j                  d	d
d	d
d
      }	d}
|j                  |	|      5 }|j                  dt         d|      5 }	 |j                          t%        |      D ]  }t'        |      }|s|}
 	 ddd       ddd       |
S # |j                  $ r}|j                  j                          |j                  j                  }t        |j                  j                  |      rt!        |      ||dd }t#        d|j                  j                   d|       |d}~ww xY w# 1 sw Y   xY w# 1 sw Y   |
S xY w)zHStream a Codex Responses image_generation call and return the b64 image.r   N)_codex_cloudflare_headersztext/event-streamzBearer zapplication/json)AcceptAuthorizationzContent-Typer   r   r/   r   g     r@g      >@)connectreadwritepool)timeoutheadersPOSTz
/responses)r   i  z"Codex Responses API returned HTTP z: )httpxrY   r   updater   TimeoutClientr   _CODEX_BASE_URLraise_for_statusHTTPStatusErrorr   r   r   r*   r'   r   RuntimeErrorr   r   )r[   r   r   r/   r   r   r   r   r)   r   	image_b64httpr   rH   	full_bodyr(   r   r   s                     r   _collect_image_b64r     s    @'.GNN%"5'** 
 '!	G mmE4e4dmSG#I	gw	7 &4[[O#4J!?g[N 	&RZ))+ (1 &*51 %I&	&&* # (( !!#LL--	9LL,, ?yIsR #"89Q9Q8RRTUYTZ[	& 	&&* sO   %E0E$CE$/E$4E0E!BEE!!E$$E-	)E00E: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efd	d	d
	 	 	 	 	 	 	 	 	 	 	 ddZy	)OpenAICodexImageGenProviderzEgpt-image-2 routed through ChatGPT/Codex OAuth instead of an API key.c                     y)NrL   r   selfs    r   namez OpenAICodexImageGenProvider.name  s    r   c                     y)NOpenAI (Codex auth)r   r   s    r   display_namez(OpenAICodexImageGenProvider.display_name  s    $r   c                B    t               sy	 dd l}y# t        $ r Y yw xY w)NFr   T)rX   r   ImportError)r   r   s     r   is_availablez(OpenAICodexImageGenProvider.is_available  s,    ')	   		s    	c           
     z    t         j                         D cg c]  \  }}||d   |d   |d   dd c}}S c c}}w )Nr,   r-   r.   varies)idr,   r-   r.   price)r3   items)r   model_idmetas      r   list_modelsz'OpenAICodexImageGenProvider.list_models  sN     #*--/	
 $ 	?g!+.!	
 		
 	
s   7c                    t         S )N)rP   r   s    r   default_modelz)OpenAICodexImageGenProvider.default_model  s    r   c                    dddg ddS )Nr   freeu[   gpt-image-2 via ChatGPT/Codex OAuth — no API key required; supports text and image inputsui   Sign in with `hermes auth codex` (or `hermes setup` → Codex) if you haven't already. No API key needed.)r   badgetagenv_varspost_setup_hintr   r   s    r   get_setup_schemaz,OpenAICodexImageGenProvider.get_setup_schema  s    )p=	
 		
r   c                    ddgt         dS )Nr   image)
modalitiesmax_reference_images)r   r   s    r   capabilitiesz(OpenAICodexImageGenProvider.capabilities  s    
  &w/I^__r   N)r   r   c                  |xs dj                         }t        |      }|st        ddd|      S t               st        ddd|      S 	 dd l}t               \  }}	t        j                  |t        d         }
t               }|st        ddd|||      S 	 t        ||      }	 t        |||
|	d   |xs d       }|st        ddd|||      S 	 t!        |d|       }t#        t%        |      |||d|rdnd|
|	d   t'        |      d !      S # t
        $ r t        d	d
d|      cY S w xY w# t        $ r}t        d| dd|||      cY d }~S d }~ww xY w# t        $ r/ t        j                  dd       t        t        dd|||      cY S t        $ r5}t        j                  dd       t        d| dd|||      cY d }~S d }~ww xY w# t        $ r}t        d| dd|||      cY d }~S d }~ww xY w)"Nr   z1Prompt is required and must be a non-empty stringinvalid_argumentrL   )r   
error_typeprovideraspect_ratiouo   No Codex/ChatGPT OAuth credentials available. Run `hermes auth codex` (or `hermes setup` → Codex) to sign in.auth_requiredr   z6httpx Python package not installed (pip install httpx)missing_dependencyr8   )r   r  r  rM   r   r  z-Invalid image input for Codex image editing: invalid_image_inputr/   r   z.Codex account does not expose image generationT)exc_infocapability_unsupportedzCodex image generation failedz/OpenAI image generation via Codex auth failed: 	api_errorz8Codex response contained no image_generation_call resultempty_responseopenai_codex_)prefixzCould not save image to cache: io_errorr   r   )r   r/   input_image_count)r   rM   r   r  r  modalityextra)r%   r   r   rX   r   r   rV   _SIZESr!   r   rC   r   r   rD   rE   %_IMAGE_GENERATION_UNAVAILABLE_MESSAGEr   r   r$   ro   )r   r   r  r   r   kwargsaspectr   tier_idr   r   r[   r   rH   b64
saved_paths                   r   generatez$OpenAICodexImageGenProvider.generate  s    ,B%%'%l3!I-'#	  ()!T +'# 	 '(zz&&"23(*!T +'#
 

	29>RSL	$Y)1TC> !P+'# 
	'mG94MNJ  j/# ,W&DORUVbRcd
 	
w  	!N/'#	 	6  	!EcUK0'# 	$ 4 	LL@   ";3'#   		LL84LH!GuM&'# 		.  	!7u=%'# 	sr   D D, $E G D)(D),	E5EEE5G
G
*G?G
G
	G4G/)G4/G4)returnr$   )r  bool)r  zList[Dict[str, Any]]r  Optional[str]r  Dict[str, Any])r   r$   r  r$   r   r!  r   Optional[List[str]]r  r   r  r#  )r   r   r   r   propertyr   r   r   r   r   r   r  r
   r  r   r   r   r   r     s    O  % %



` 1D

 $(48D
D
 D

 !D
 2D
 D
 
D
r   r   c                6    | j                  t                      y)uD   Plugin entry point — register the Codex-backed image-gen provider.N)register_image_gen_providerr   )ctxs    r   registerr)    s    ##$?$ABr   )r'   intr(   r$   r  r  r"  )r  zTuple[str, Dict[str, Any]]r   )r`   r   r  r!  )rT   r$   r  r$   )rT   r$   r  zDict[str, str])r   r!  r   r$  r  zList[Dict[str, str]])
r   r$   r   r$   r/   r$   r   Optional[List[Dict[str, str]]]r  r#  )rT   r   r  r!  )r   r   )r[   r$   r   r$   r   r$   r/   r$   r   r+  r  r!  )r  None)9r   
__future__r   rm   r   loggingrN   pathlibr   typingr   r   r   r   r	   agent.image_gen_providerr
   r   r   r   r   r   r   	getLoggerr   rD   r   r   r  r&   r*   r   r3   __annotations__rP   r  r   r   r   r   rp   	frozensetr_   rI   rV   rX   rb   rw   r   r   r   r   r   r   r   r   r)  r   r   r   <module>r5     s  ( #    	  3 3   
		8	$E< E
@ &
 E $

_" 	 '2	 *+	 (C	&	" * % 
  9A 
  ) 
 !: 
14$$+$+:;==-= =& 48## # 	#
 1# #L.,j 4800 0 	0
 0 10 0pz
"2 z
DCr   