
    (GjP                       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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%dId%Z&dJd'Z'dKd)Z(dLd,Z)dMd/Z*dMd0Z+dNd2Z,dOd7Z-dd8dPd>Z.dQd@Z/dRdBZ0dd8dSdDZ1 G dE dFe          Z2dTdHZ3dS )Uu  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_responsez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]]_MODELSr   	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/webpreturnDict[str, Any]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)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(   
isinstancedictget	Exceptionloggerdebug)r(   cfgsectionexcs       M/home/rurouni/.hermes/hermes-agent/plugins/image_gen/openai-codex/__init__.py_load_image_gen_configr5   h   s    111111kmm*4S$*?*?I#''+&&&T$Wd33;ww;   :C@@@						s   AA 
B!B=BBTuple[str, Dict[str, Any]]c                 f   ddl } | j                            d          }|r|t          v r|t          |         fS t	                      }t          |                    d          t                    r|                    d          ni }d}t          |t                    r5|                    d          }t          |t                    r|t          v r|}|5|                    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-   r   r5   r+   r,   strDEFAULT_MODEL)r;   env_overrider1   sub	candidatevaluetops          r4   _resolve_modelrD   u   s#   III:>>"677L 3//W\222
 
"
"C%/0G0G%N%N
V#''.
!
!
!TVC#I#t   eS!! 	ew&6&6Igggc3 	C7NNI'),,,'-000    Optional[str]c                    	 ddl m}   |             }t          |t                    r(|                                r|                                S dS # t
          $ r&}t                              d|           Y d}~dS 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_clientrH   r+   r=   stripr.   r/   r0   )_readertokenr3   s      r4   rH   rH      s    	NNNNNN		eS!! 	!ekkmm 	!;;== t   ?EEEttttts   AA 
BA<<Brawbytesc                >    ddl m}  ||           }|t          v r|S dS )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_routingrP   _ACCEPTED_INPUT_MIME)rM   rP   mimes      r4   _sniff_image_mimerT      s<     ;:::::!!#&&D###4rE   rB   r=   c                   d| vrt          d          |                     dd          \  }}|                                }|                    d          rd|vrt          d          t	          j        |d          }t          |          t          k    rt          d	          t          |          }|t          d          t	          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 capNz5Image data URL does not contain supported image bytesasciidata:;base64,)
ValueErrorsplitlower
startswithbase64	b64decodelen_MAX_INPUT_IMAGE_BYTESrT   	b64encodedecode)rB   headerdata	header_lcrM   rS   encodeds          r4   _data_url_to_input_image_urlrj      s    
%FGGG;;sA&&LFDI.. \)92L2LZ[[[

4$
/
/
/C
3xx(((:;;;S!!D|PQQQs##**733G*4*****rE   c                   	 ddl m}  ||           }|rt          |          n<# t          $ r  t          $ r%}t                              d|           Y d}~nd}~ww xY wt          t          j        	                    |                     
                                }|                                st          d|            |                                j        }|dk    rt          d|            |t          k    rt          d|            |                                }t!          |          }|t          d|            t#          j        |                              d	          }d
| d| S )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: rY   rZ   r[   )agent.file_safetyrl   r\   r.   r/   r0   r   r;   path
expanduserresolveis_filestatst_sizerc   
read_bytesrT   r`   rd   re   )	rB   rl   blockedr3   rn   sizerM   rS   ri   s	            r4   _local_image_to_data_urlrw      s   	J::::::&&u-- 	&W%%%	&    J J JCSIIIIIIIIJ ""5))**2244D<<>> WUeUUVVV99;;Dqyy>u>>???$$$FuFFGGG
//

CS!!D|NuNNOOOs##**733G*4*****s   "% AAADict[str, str]c                D   | pd                                 }|st          d          |                                }|                    d          s|                    d          r|}n4|                    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://rZ   input_image)type	image_url)rJ   r\   r^   r_   rj   rw   )rB   rA   loweredr}   s       r4   _to_input_image_partr      s    "##%%I .,---ooG)$$ 8(:(::(F(F 8					G	$	$ 80;;		,Y77	!	:::rE   r}   reference_image_urlsOptional[List[str]]List[Dict[str, str]]c                .   g }t          | t                    r;|                                 r'|                    |                                            t	          |          pg D ]}|                    |           |dt
                   }d |D             S )zFCollect primary + reference images as ordered Responses content parts.Nc                ,    g | ]}t          |          S  )r   ).0rB   s     r4   
<listcomp>z+_normalize_input_images.<locals>.<listcomp>   s!    <<<E ''<<<rE   )r+   r=   rJ   appendr   _MAX_REFERENCE_IMAGES)r}   r   valuesrefs       r4   _normalize_input_imagesr      s    
 F)S!! )ioo&7&7 )ioo''(((*+?@@FB  c***+F<<V<<<<rE   )input_imagespromptrv   r   r   Optional[List[Dict[str, str]]]c                    d| dg}|r|                     |           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|   textFmessageuser)r|   rolecontentimage_generationpngopaquerW   )r|   r:   rv   r   output_format
backgroundpartial_imagesallowed_toolsrequiredr|   )r|   modetoolsT)r:   storeinstructionsinputr   tool_choicestream)extend_CODEX_CHAT_MODEL_CODEX_INSTRUCTIONS	API_MODEL)r   rv   r   r   r   s        r4   _build_responses_payloadr      s     /;F%K%K$LG %|$$$"+
 
  '""
 
  $123
 

 /  rE   r   c                   d}t          | t                    r|                     d          dk    r.|                     d          }t          |t                    r|r|}|                     d          }t          |t                    r|r|}|                                 D ]}t          |          }|r|}n-t          | t                    r| D ]}t          |          }|r|}|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)rB   foundr   partialchildnesteds         r4   r   r   %  s
   E% 99V 777YYx((F&#&& 6 ))/00gs## 	 	E\\^^ 	 	E'..F 	 
E4	 	   	 	E'..F LrE   responsec              #  d  K   dg fd}|                                  D ]}t          |t                    r|                    dd          }t	          |          }|dk    r |            }||V  T|                    d          rj|                    d          r*|t          d          d                                         |                    d	          r<                    |t          d	          d         	                                            |            }||V  dS dS )
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 d S d                                                               } }d g | r| dk    rd S t          j        |           }t	          |t
                    r|r	d|vr||d<   |S )N
z[DONE]r|   )joinrJ   jsonloadsr+   r,   )rM   eventpayload
data_lines
event_names      r4   flushz_iter_sse_json.<locals>.flushF  s     	J4ii
##))++

 	cXoo4*S//gt$$ 	$ 	$63H3H#GFOrE   zutf-8replace)errorsrz   :zevent:rZ   )

iter_linesr+   rN   re   r=   r_   rb   rJ   r   lstrip)r   r   liner   r   r   s       @@r4   _iter_sse_jsonr   <  s^      !%JJ       ##%% < <dE"" 	:;;wy;99D4yy2::eggG"??3 	??8$$ 	<c(mmnn-3355JJ__W%% 	<d3w<<==188::;;;eggG rE   rL   c          	        ddl }ddlm}  ||           }|                    dd|  dd           t	          ||||          }|                    d	d
d	d
d
          }	d}
|                    |	|          5 }|                    dt           d|          5 }	 |	                                 n_# |j
        $ rR}|j                                         |j        j        dd         }t          d|j        j         d|           |d}~ww xY wt!          |          D ]}t#          |          }|r|}
	 ddd           n# 1 swxY w Y   ddd           n# 1 swxY w Y   |
S )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   rv   r   r   g     r@g      >@)connectreadwritepool)timeoutheadersPOSTz
/responses)r   i  z"Codex Responses API returned HTTP z: )httpxrI   r   updater   TimeoutClientr   _CODEX_BASE_URLraise_for_statusHTTPStatusErrorr   r   r   RuntimeErrorstatus_coder   r   )rL   r   rv   r   r   r   r   r   r   r   	image_b64httpr   r3   bodyr   r   s                    r4   _collect_image_b64r   k  sR    LLL@@@@@@''..GNN%*5***    
 '!	  G mmE4e4dmSSG#I	gw	7	7 &4[[O!?!?!?g[NN 	&RZ))++++(   !!###|(#."[9Q[[UY[[  (11 & &*511 & %I&	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	&& & & & & & & & & & & & & & & s[   8!EEB0/E0
D:ADD(E5EE	EE		EE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dS )OpenAICodexImageGenProviderzEgpt-image-2 routed through ChatGPT/Codex OAuth instead of an API key.r%   r=   c                    dS )Nr9   r   selfs    r4   namez OpenAICodexImageGenProvider.name  s    ~rE   c                    dS )NOpenAI (Codex auth)r   r   s    r4   display_namez(OpenAICodexImageGenProvider.display_name  s    $$rE   boolc                T    t                      sdS 	 dd l}n# t          $ r Y dS w xY wdS )NFr   T)rH   r   ImportError)r   r   s     r4   is_availablez(OpenAICodexImageGenProvider.is_available  sL    ')) 	5	LLLL 	 	 	55	ts    
%%List[Dict[str, Any]]c                H    d t                                           D             S )Nc                H    g | ]\  }}||d          |d         |d         dd S )r   r   r   varies)idr   r   r   pricer   )r   model_idmetas      r4   r   z;OpenAICodexImageGenProvider.list_models.<locals>.<listcomp>  sQ     	
 	
 	
 $ 	?g!+.! 	
 	
 	
rE   )r   itemsr   s    r4   list_modelsz'OpenAICodexImageGenProvider.list_models  s-    	
 	
 #*--//	
 	
 	
 		
rE   rF   c                    t           S )N)r>   r   s    r4   default_modelz)OpenAICodexImageGenProvider.default_model  s    rE   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    r4   get_setup_schemaz,OpenAICodexImageGenProvider.get_setup_schema  s"    )p=	
 	
 		
rE   c                    ddgt           dS )Nr   image)
modalitiesmax_reference_images)r   r   s    r4   capabilitiesz(OpenAICodexImageGenProvider.capabilities  s    
  &w/I^___rE   N)r}   r   r   aspect_ratior}   r   r   kwargsr   c               J   |pd                                 }t          |          }|st          ddd|          S t                      st          ddd|          S 	 dd l}n## t
          $ r t          d	d
d|          cY S w xY wt                      \  }}	t                              |t          d                   }
t                      }|st          ddd|||          S 	 t          ||          }n/# t          $ r"}t          d| dd|||          cY d }~S d }~ww xY w	 t          |||
|	d         |pd           }nK# t          $ r>}t                              dd           t          d| dd|||          cY d }~S d }~ww xY w|st          ddd|||          S 	 t          |d|           }n/# t          $ r"}t          d| dd|||          cY d }~S d }~ww xY wt          t!          |          |||d|rdnd|
|	d         t#          |          d          S ) Nrz   z1Prompt is required and must be a non-empty stringinvalid_argumentr9   )error
error_typeproviderr  uo   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_dependencyr   )r
  r  r  r:   r   r  z-Invalid image input for Codex image editing: invalid_image_inputr   r   zCodex image generation failedT)exc_infoz/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   )rv   r   input_image_count)r  r:   r   r  r  modalityextra)rJ   r   r   rH   r   r   rD   _SIZESr-   r   r.   r   r/   r0   r   r   r=   rb   )r   r   r  r}   r   r  aspectr   tier_idr   rv   rL   r   r3   b64
saved_paths                   r4   generatez$OpenAICodexImageGenProvider.generate  s    ,B%%''%l33 	!I-'#	    ()) 		!T +'#   	LLLL 	 	 	!N/'#	     	 '((zz&&"233(** 	!T +'#
 
 
 

	29>RSSLL 	 	 	!KcKK0'#        		$Y)1T  CC  		 		 		LL84LHHH!MMM&'#        		  	!P+'#   
	'4MG4M4MNNNJJ 	 	 	!===%'#        	  j//# ,8WW&DORUVbRcRcdd
 
 
 	
sl   A" "BB"C3 3
D=DDD#E   
F
3F=FF#F8 8
G$GG$G$)r%   r=   )r%   r   )r%   r   r%   rF   r%   r&   )r   r=   r  r=   r}   rF   r   r   r  r   r%   r&   )__name__
__module____qualname____doc__propertyr   r   r   r   r   r   r  r
   r  r   rE   r4   r   r     s        OO   X % % % X%   

 

 

 

   

 

 

 

` ` ` ` 1w

 $(48w
 w
 w
 w
 w
 w
 w
 w
rE   r   Nonec                H    |                      t                                 dS )uD   Plugin entry point — register the Codex-backed image-gen provider.N)register_image_gen_providerr   )ctxs    r4   registerr*  R  s#    ##$?$A$ABBBBBrE   r   )r%   r6   r  )rM   rN   r%   rF   )rB   r=   r%   r=   )rB   r=   r%   rx   )r}   rF   r   r   r%   r   )
r   r=   rv   r=   r   r=   r   r   r%   r&   )rB   r   r%   rF   )r   r   )rL   r=   r   r=   rv   r=   r   r=   r   r   r%   rF   )r%   r&  )4r$  
__future__r   r`   r   loggingr;   pathlibr   typingr   r   r   r   r	   agent.image_gen_providerr
   r   r   r   r   r   r   	getLoggerr!  r/   r   r   __annotations__r>   r  r   r   r   r   rc   	frozensetrR   r5   rD   rH   rT   rj   rw   r   r   r   r   r   r   r   r*  r   rE   r4   <module>r3     s/    ( # " " " " "    				       3 3 3 3 3 3 3 3 3 3 3 3 3 3                  
	8	$	$ 	 '2	  *+	  (C	 & &    * % 
 
  9A 
  ) 
 !y:::  
 
 
 
1 1 1 14   $   $+ + + +$+ + + +:; ; ; ;= = = =& 48# # # # # #L   ., , , ,j 48* * * * * *dm
 m
 m
 m
 m
"2 m
 m
 m
jC C C C C CrE   