
    YffjZ                       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dLd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'dMd)Z(dNd+Z)dOd-Z*dPd0Z+dQd2Z,dQd3Z-dRd5Z.dSd:Z/dd;dTdAZ0dUdCZ1dVdEZ2dd;dWdGZ3 G dH dIe          Z4dXdKZ5dS )Yu  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_responsei  bodystrreturnc                   | pd}	 t          j        |          }t          |t                    r|                    d          nd}t          |t                    r|                    d          nd}t          |t
                    r5|                                r!|                                dt                   S n# t          t          f$ r Y nw xY w|dt                   S )u  Return a bounded, information-preserving summary of an error body.

    Prefers the parsed ``error.message`` field, because a blind head-truncation
    of the raw body can cut the actual message off entirely — Codex error
    payloads sometimes carry hundreds of bytes of leading metadata, so
    ``body[:500]`` yielded a wall of padding and no diagnosis. Falls back to a
    truncated raw body for non-JSON responses.
     errorNmessage)
jsonloads
isinstancedictgetr   strip_MAX_ERROR_BODY_CHARS	TypeError
ValueError)r   textpayloadr   r   s        M/home/thesage/.hermes/hermes-agent/plugins/image_gen/openai-codex/__init__.py_summarize_error_bodyr$   <   s     :2D*T""(27D(A(AKG$$$t*4UD*A*AK%))I&&&tgs## 	; 	;==??#9$9#9::z"   &&&''s   B5B= =CC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]]_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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;   r   r   r   	Exceptionloggerdebug)r;   cfgsectionexcs       r#   _load_image_gen_configrD      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-   rD   r   r   r   DEFAULT_MODEL)rJ   env_overriderA   sub	candidatevaluetops          r#   _resolve_modelrR      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_clientrV   r   r   r   r>   r?   r@   )_readertokenrC   s      r#   rV   rV      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_routingr]   _ACCEPTED_INPUT_MIME)rZ   r]   mimes      r#   _sniff_image_mimera      s<     ;:::::!!#&&D###4rS   rP   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,)r    splitlower
startswithbase64	b64decodelen_MAX_INPUT_IMAGE_BYTESra   	b64encodedecode)rP   headerdata	header_lcrZ   r`   encodeds          r#   _data_url_to_input_image_urlrv      s    
%FGGG;;sA&&LFDI.. \)92L2LZ[[[

4$
/
/
/C
3xx(((:;;;S!!D|PQQQs##**733G*4*****rS   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: rf   rg   rh   )agent.file_safetyrx   r    r>   r?   r@   r   rJ   path
expanduserresolveis_filestatst_sizero   
read_bytesra   rl   rp   rq   )	rP   rx   blockedrC   rz   sizerZ   r`   ru   s	            r#   _local_image_to_data_urlr      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.r   zBlank image inputzhttp://zhttps://rg   input_image)type	image_url)r   r    rj   rk   rv   r   )rP   rO   loweredr   s       r#   _to_input_image_partr   	  s    "##%%I .,---ooG)$$ 8(:(::(F(F 8					G	$	$ 80;;		,Y77	!	:::rS   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   ).0rP   s     r#   
<listcomp>z+_normalize_input_images.<locals>.<listcomp>#  s!    <<<E ''<<<rS   )r   r   r   appendr   _MAX_REFERENCE_IMAGES)r   r   valuesrefs       r#   _normalize_input_imagesr     s    
 F)S!! )ioo&7&7 )ioo''(((*+?@@FB  c***+F<<V<<<<rS   )input_imagespromptr   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S )zDBuild the Codex Responses request body for an image_generation call.
input_text)r   r!   Fr   user)r   rolecontentimage_generationpngopaquerd   )r   rI   r   r)   output_format
backgroundpartial_imagesT)rI   storeinstructionsinputtoolsstream)extend_CODEX_CHAT_MODEL_CODEX_INSTRUCTIONS	API_MODEL)r   r   r)   r   r   s        r#   _build_responses_payloadr   &  s     /;F%K%K$LG %|$$$"+
 
  '""
 
 $ 7  rS   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)rP   foundr   partialchildnesteds         r#   r   r   P  s
   E% 99V 777YYx((F&#&& 6 ))/00gs## 	 	E\\^^ 	 	E'..F 	 
E4	 	   	 	E'..F LrS   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   )joinr   r   r   r   r   )rZ   eventr"   
data_lines
event_names      r#   flushz_iter_sse_json.<locals>.flushq  s     	J4ii
##))++

 	cXoo4*S//gt$$ 	$ 	$63H3H#GFOrS   zutf-8replace)errorsr   :zevent:rg   )

iter_linesr   r[   rq   r   rk   rn   r   r   lstrip)r   r   liner"   r   r   s       @@r#   _iter_sse_jsonr   g  s^      !%JJ       ##%% < <dE"" 	:;;wy;99D4yy2::eggG"??3 	??8$$ 	<c(mmnn-3355JJ__W%% 	<d3w<<==188::;;;eggG rS   rY   c                  ddl }ddlm}  ||           }|                    dd|  dd           t	          ||||          }|                    d	d
d	d
d
          }	d}
|                    |	|          5 }|                    dt           d|          5 }	 |	                                 nb# |j
        $ rU}|j                                         t          d|j        j         dt          |j        j                             |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   r   r)   r   g     r@g      >@)connectreadwritepool)timeoutheadersPOSTz
/responses)r   z"Codex Responses API returned HTTP z: )httpxrW   r   updater   TimeoutClientr   _CODEX_BASE_URLraise_for_statusHTTPStatusErrorr   r   RuntimeErrorstatus_coder$   r!   r   r   )rY   r   r   r)   r   r   r   r   r"   r   	image_b64httpr   rC   r   r   s                   r#   _collect_image_b64r     s]    LLL@@@@@@''..GNN%*5***    
 '!	  G mmE4e4dmSSG#I	gw	7	7 &4[[O!?!?!?g[NN 	&RZ))++++(   !!###"B9Q B B,S\->??B B   (11 & &*511 & %I&	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	&& & & & & & & & & & & & & & & s[   8!EEB0/E0
D:AD

D(E8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 )NrH   r   selfs    r#   namez OpenAICodexImageGenProvider.name  s    ~rS   c                    dS )NOpenAI (Codex auth)r   r   s    r#   display_namez(OpenAICodexImageGenProvider.display_name  s    $$rS   boolc                T    t                      sdS 	 dd l}n# t          $ r Y dS w xY wdS )NFr   T)rV   r   ImportError)r   r   s     r#   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      r#   r   z;OpenAICodexImageGenProvider.list_models.<locals>.<listcomp>  sQ     	
 	
 	
 $ 	?g!+.! 	
 	
 	
rS   )r-   itemsr   s    r#   list_modelsz'OpenAICodexImageGenProvider.list_models  s-    	
 	
 #*--//	
 	
 	
 		
rS   rT   c                    t           S )N)rL   r   s    r#   default_modelz)OpenAICodexImageGenProvider.default_model  s    rS   r9   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=	
 	
 		
rS   c                    ddgt           dS )Nr!   image)
modalitiesmax_reference_images)r   r   s    r#   capabilitiesz(OpenAICodexImageGenProvider.capabilities  s    
  &w/I^___rS   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 ) Nr   z1Prompt is required and must be a non-empty stringinvalid_argumentrH   )r   
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_dependencyr2   )r   r  r  rI   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!   )r   r)   input_image_count)r  rI   r   r  r  modalityextra)r   r   r   rV   r   r   rR   _SIZESr   r   r>   r   r?   r@   r   r   r   rn   )r   r   r  r   r   r  aspectr   tier_idr   r   rY   r   rC   b64
saved_paths                   r#   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   rT   r   r9   )r   r   r  r   r   rT   r   r   r  r   r   r9   )__name__
__module____qualname____doc__propertyr   r   r   r   r   r  r  r
   r  r   rS   r#   r   r     s        OO   X % % % X%   

 

 

 

   

 

 

 

` ` ` ` 1w

 $(48w
 w
 w
 w
 w
 w
 w
 w
rS   r   Nonec                H    |                      t                                 dS )uD   Plugin entry point — register the Codex-backed image-gen provider.N)register_image_gen_providerr   )ctxs    r#   registerr*  }  s#    ##$?$A$ABBBBBrS   )r   r   r   r   r   )r   rE   r  )rZ   r[   r   rT   )rP   r   r   r   )rP   r   r   r   )r   rT   r   r   r   r   )
r   r   r   r   r)   r   r   r   r   r9   )rP   r   r   rT   )r   r   )rY   r   r   r   r   r   r)   r   r   r   r   rT   )r   r&  )6r$  
__future__r   rl   r   loggingrJ   pathlibr   typingr   r   r   r   r	   agent.image_gen_providerr
   r   r   r   r   r   r   	getLoggerr!  r?   r   r$   r   r-   __annotations__rL   r  r   r   r   r   ro   	frozensetr_   rD   rR   rV   ra   rv   r   r   r   r   r   r   r   r   r*  r   rS   r#   <module>r3     sK    ( # " " " " "    				       3 3 3 3 3 3 3 3 3 3 3 3 3 3                  
	8	$	$"  ( ( ( (4 	 '2	  *+	  (C	 & &    * % 
 
  9A 
  ) 
 !y:::  
 
 
 
1 1 1 14   $   $+ + + +$+ + + +:; ; ; ;= = = =& 48' ' ' ' ' 'T   ., , , ,j 48* * * * * *dm
 m
 m
 m
 m
"2 m
 m
 m
jC C C C C CrS   