Ë
    âß²j4  ã                  óÂ  — 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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 ddlmZmZmZmZmZmZmZmZm Z   ejB                  e"«      Z#dZ$d	Z%d
Z&dZ' e(h d£«      Z)dZ*d d„Z+d!d„Z,d"d„Z-d#d„Z.d$d„Z/d%d„Z0d&d„Z1d&d„Z2d&d„Z3d'd„Z4	 	 	 	 	 	 d(d„Z5ddœ	 	 	 	 	 	 	 	 	 d)d„Z6ddœ	 	 	 	 	 	 	 	 	 	 	 d*d„Z7d$d„Z8 G d„ de«      Z9d+d„Z:y),u  OpenAI image generation â€” ChatGPT/Codex OAuth variant.

Same catalog/tiers as the ``openai`` plugin (``gpt-image-2`` low/medium/high), posted to the
Codex backend's native ``images/generations`` and ``images/edits`` endpoints, the same route the
official Codex client uses (``codex-rs/ext/image-generation``). No ``OPENAI_API_KEY`` is needed.

There is deliberately NO chat/host model here. An earlier version rode a Responses call with a
hosted ``image_generation`` tool on a pinned chat model (``gpt-5.5``): when OpenAI withdrew that id
from an account cohort every image call 404'd while chat kept working (#105398, #107076), and the
host model was free to answer in text instead of calling the tool. The native route has neither
failure mode.

The backend does not enforce ``model``/``quality``/``size`` â€” it accepts unknown model ids and
returns its own quality/size (#107233). We send the catalog values and report what came back
(``reported_quality``/``reported_size``) so a request that was not honoured is diagnosable.
é    )ÚannotationsN)ÚPath)ÚAnyÚDictÚListÚOptionalÚTuple)ÚDEFAULT_ASPECT_RATIOÚresolve_aspect_ratioÚsave_b64_imageÚsuccess_response)	ÚGPT_IMAGE_2_API_MODELÚGPT_IMAGE_2_DEFAULTÚGPT_IMAGE_2_TIERSÚStaticImageGenProviderÚcollect_source_imagesÚerror_factoryÚprompt_required_errorÚresolve_static_modelÚsize_forz%https://chatgpt.com/backend-api/codexiô  é   i  �>   ú	image/gifú	image/pngú
image/jpegú
image/webpuz   No Codex/ChatGPT OAuth credentials available. Run `hermes auth add openai-codex` (or `hermes setup` â†’ Codex) to sign in.c                óp  — | xs d}	 t        j                  |«      }t        |t        «      r|j	                  d«      nd}t        |t        «      r|j	                  d«      nd}t        |t
        «      r'|j                  «       r|j                  «       dt         S |dt         S # t        t        f$ r Y Œw xY w)zZBounded summary preferring parsed ``error.message`` (Codex bodies carry leading metadata).Ú ÚerrorNÚmessage)
ÚjsonÚloadsÚ
isinstanceÚdictÚgetÚstrÚstripÚ_MAX_ERROR_BODY_CHARSÚ	TypeErrorÚ
ValueError)ÚbodyÚtextÚpayloadr   r   s        úL/home/ubuntu/.hermes/hermes-agent/plugins/image_gen/openai-codex/__init__.pyÚ_summarize_error_bodyr.   1   s¡   € àŠ:�2€DðÜ—*‘*˜TÓ"ˆÜ(2°7¼DÔ(A�—‘˜GÔ$ÀtˆÜ*4°U¼DÔ*A�%—)‘)˜IÔ&ÀtˆÜ�gœsÔ#¨¯©¬Ø—=‘=“?Ð#9Ô$9Ð:Ð:ð Ð&Ô&Ð'Ð'øô ”zÐ"ò Ùðús   ˆBB# Â#B5Â4B5c                 ó0   — t        t        t        dd¬«      S )NÚOPENAI_IMAGE_MODELúopenai-codex)Úenv_varÚ
config_key)r   r   ÚDEFAULT_MODEL© ó    r-   Ú_resolve_modelr7   ?   s   € ÜÜœ=Ð2FÐSaôcð cr6   c                 óÚ   — 	 ddl m}   | «       }t        |t        «      r |j	                  «       r|j	                  «       S dS # t
        $ r }t        j                  d|«       Y d}~yd}~ww xY w)zSUsable Codex OAuth token or None (``agent.auxiliary_client`` owns expiry/pool/JWT).r   )Ú_read_codex_access_tokenNz(Could not resolve Codex access token: %s)Úagent.auxiliary_clientr9   r"   r%   r&   Ú	ExceptionÚloggerÚdebug)Ú_readerÚtokenÚexcs      r-   r9   r9   D   sU   € ðÝNá“	ˆÜ *¨5´#Ô 6¸5¿;¹;¼=ˆu�{‰{‹}ÐRÈdÐRøÜò Ü�‰Ð?ÀÔEÜûðús   ‚<A ¿A Á	A*Á
A%Á%A*c                 ó,   — 	 dd l } y# t        $ r Y yw xY w)Nr   FT)ÚhttpxÚImportError)rB   s    r-   Ú_httpx_availablerD   P   s#   € ðÛð øô ò Ùðús   ‚ ‡	’c                ó6   — ddl m}  || «      }|t        v r|S d S )Nr   )Ú_sniff_mime_from_bytes)Úagent.image_routingrF   Ú_ACCEPTED_INPUT_MIME)ÚrawrF   Úmimes      r-   Ú_sniff_image_mimerK   X   s"   € Ý:á! #Ó&€DØÔ/Ñ/ˆ4Ð9°TÐ9r6   c                óÀ   — t        | «      t        kD  rt        |«      ‚t        | «      }|€t        |«      ‚d|› dt	        j
                  | «      j                  d«      › �S )zLSize- and MIME-check raw image bytes, then return a canonical ``data:`` URL.údata:z;base64,Úascii)ÚlenÚ_MAX_INPUT_IMAGE_BYTESr)   rK   Úbase64Ú	b64encodeÚdecode)rI   Útoo_bigÚunsupportedrJ   s       r-   Ú_encode_input_imagerV   _   s^   € ä
ˆ3ƒxÔ(Ò(Ü˜Ó!Ð!Ü˜SÓ!€DØ€|Ü˜Ó%Ð%Ø�4�&˜¤×!1Ñ!1°#Ó!6×!=Ñ!=¸gÓ!FÐ GÐHÐHr6   c                óî   — d| vrt        d«      ‚| j                  dd«      \  }}|j                  «       }|j                  d«      rd|vrt        d«      ‚t	        t        j                  |d¬«      d	d
«      S )Nú,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)r)   ÚsplitÚlowerÚ
startswithrV   rQ   Ú	b64decode)ÚvalueÚheaderÚdataÚ	header_lcs       r-   Ú_data_url_to_input_image_urlrc   i   s{   € Ø
�%ÑÜÐFÓGÐGØ—;‘;˜s AÓ&�L€FˆDØ—‘“€IØ×Ñ Ô.°)À9Ñ2LÜÐZÓ[Ð[ÜÜ×Ñ˜¨Ô-Ø)Ø?óAð Ar6   c                óø   — ddl m}m}  || «      st        d| › �«      ‚ |dd¬«      5 }|j	                  | «      }ddd«       j                  «        t        |j                  d| › �d	| › �«      S # 1 sw Y   Œ6xY w)
zWThe edit endpoint takes inline data URLs only (as the official client sends), so fetch.r   )Úcreate_ssrf_safe_clientÚis_safe_urlz(Image URL failed the SSRF safety check: ç      N@T)ÚtimeoutÚfollow_redirectsNzImage URL exceeds 25MB cap: z,Image URL did not return a supported image: )Útools.url_safetyre   rf   r)   r$   Úraise_for_statusrV   Úcontent)r_   re   rf   ÚclientÚresponses        r-   Ú_remote_image_to_data_urlro   v   s‡   € çEá�uÔÜÐCÀEÀ7ÐKÓLÐLÙ	 ¨ÀÔ	Eð %ÈØ—:‘:˜eÓ$ˆ÷%à×ÑÔÜØ×ÑØ
& u gÐ.Ø
6°u°gÐ>ó@ð @÷%ð %ús   ©A0Á0A9c                óx  — ddl m}  || «      }|rt        |«      ‚t        t        j
                  j                  | «      «      j                  «       }|j                  «       st        d| › �«      ‚|j                  «       j                  dk  rt        d| › �«      ‚t        |j                  «       d| › �d| › �«      S )Nr   )Úget_read_block_errorz2Image 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: )Úagent.file_safetyrq   r)   r   ÚosÚpathÚ
expanduserÚresolveÚis_fileÚstatÚst_sizerV   Ú
read_bytes)r_   rq   Úblockedrt   s       r-   Ú_local_image_to_data_urlr|   …   s­   € Ý6á" 5Ó)€GÙÜ˜Ó!Ð!Ü”—‘×"Ñ" 5Ó)Ó*×2Ñ2Ó4€DØ�<‰<Œ>ÜÐMÈeÈWÐUÓVÐVØ‡y�yƒ{×Ñ˜aÒÜÐ6°u°gÐ>Ó?Ð?ÜØ�‰ÓØ
-¨e¨WÐ5Ø
5°e°WÐ=ó?ð ?r6   c                ó  — | xs dj                  «       }|st        d«      ‚|j                  «       }|j                  d«      rt	        |«      }d|iS |j                  d«      rt        |«      }d|iS t        |«      }d|iS )zRConvert a URL/data URL/local path into an ``images[]`` entry for ``images/edits``.r   zBlank image input)zhttp://zhttps://rM   Ú	image_url)r&   r)   r\   r]   ro   rc   r|   )r_   Ú	candidateÚloweredr~   s       r-   Ú_to_input_imager�   –   s“   € à’˜"×#Ñ#Ó%€IÙÜÐ,Ó-Ð-Ø�o‰oÓ€GØ×ÑÐ1Ô2Ü-¨iÓ8ˆ	ð
 ˜Ð#Ð#ð	 
×	Ñ	˜GÔ	$Ü0°Ó;ˆ	ð ˜Ð#Ð#ô -¨YÓ7ˆ	Ø˜Ð#Ð#r6   c                ób   — t        | |t        ¬«      }|D �cg c]  }t        |«      ‘Œ c}S c c}w )N)Úlimit)r   Ú_MAX_REFERENCE_IMAGESr�   )r~   Úreference_image_urlsÚvaluesr_   s       r-   Ú_normalize_input_imagesr‡   ¥   s-   € ô # 9Ð.BÔJ_Ô`€FØ06Ö7 uŒO˜EÕ"Ò7Ð7ùÒ7s   —,)Úinput_imagesc                ó:   — | t         d||ddœ}|r	||d<   d|fS d|fS )uÍ   ``(endpoint_path, json_body)`` â€” ``images/edits`` when sources are present, else
    ``images/generations``. Field set mirrors the official client's ``ImageGenerationRequest`` /
    ``ImageEditRequest``.rY   Úopaque)ÚpromptÚmodelÚnÚqualityÚsizeÚ
backgroundÚimageszimages/editszimages/generations)Ú	API_MODEL)r‹   r�   rŽ   rˆ   r*   s        r-   Ú_build_image_requestr“   ¬   s>   € ð ¤9°1ÀÐRVØñ€Dñ Ø%ˆˆX‰Ø˜tÐ#Ð#Ø Ð%Ð%r6   c               ót  — ddl }ddlm}  || «      }|j                  d| › �dt	        t        j                  «       «      dœ«       t        ||||¬«      \  }}	|j                  dd	dd
d	¬«      }
|j                  |
|¬«      5 }|j                  t        › d|› �|	¬«      }ddd«       j                  dk\  r.t        d|j                  › dt        |j                  «      › �«      ‚|j!                  «       }t#        |t$        «      st        d«      ‚|j&                  j)                  d«      |d<   |S # 1 sw Y   Œ‘xY w)z–POST to the native Codex images endpoint; return the decoded JSON body plus
    ``imagegen_request_id`` (backend correlation id, for support tickets).r   N)Úcodex_cloudflare_headerszBearer zapplication/json)ÚAuthorizationzContent-Typezx-codex-image-turn-id©r‹   r�   rŽ   rˆ   g     Àr@g      >@rg   )ÚconnectÚreadÚwriteÚpool)rh   Úheadersú/)r    i�  zCodex images API returned HTTP z: z+Codex images API returned a non-object bodyzx-codex-imagegen-request-idÚimagegen_request_id)rB   Úagent.codex_headersr•   Úupdater%   ÚuuidÚuuid4r“   ÚTimeoutÚClientÚpostÚ_CODEX_BASE_URLÚstatus_codeÚRuntimeErrorr.   r+   r    r"   r#   rœ   r$   )r?   r‹   r�   rŽ   rˆ   rB   r•   rœ   rt   r*   rh   Úhttprn   r,   s                 r-   Ú_post_image_requestrª   ¼   s<  € ó
 Ý<á& uÓ-€GØ‡N�NØ" 5 'Ð*Ø*Ü!$¤T§Z¡Z£\Ó!2ñô ô
 &¨V¸$ÈÐ^jÔk�J€Dˆ$Ø�m‰m˜E¨4°eÀ4ÈdˆmÓS€GØ	�‰˜g¨wˆÓ	7ð E¸4Ø—9‘9¤Ð0°°$°Ð8¸t�9ÓDˆ÷Eà×Ñ˜sÒ"ÜØ-¨h×.BÑ.BÐ-CÀ2Ü$ X§]¡]Ó3Ð4ð6ó7ð 	7ð �m‰m‹o€GÜ�gœtÔ$ÜÐHÓIÐIØ%-×%5Ñ%5×%9Ñ%9Ð:WÓ%X€GÐ!Ñ"Ø€N÷Eð Eús   ÂD.Ä.D7c                ó†   — ddl }t        | «      dk  s| dd dk7  s| dd dk7  ry|j                  d	| dd «      \  }}|› d
|› �S )z;``"{w}x{h}"`` for a PNG payload, or None if not a PNG IHDR.r   Né   é   s   ‰PNG

é   r   s   IHDRz>IIÚx)ÚstructrO   Úunpack)rI   r°   ÚwidthÚheights       r-   Ú_png_pixel_sizer´   Ù   sY   € ãä
ˆ3ƒx�"‚}˜˜B˜Q˜Ð#7Ò7¸3¸rÀ"¸:ÈÒ;PØØ—M‘M %¨¨R°¨Ó4�M€Eˆ6ØˆW�A�f�XÐÐr6   c                  óf   — e Zd ZdZdZdZeZeZ	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.r1   úOpenAI (Codex auth)Úvariesc                ó@   — t        t        «       «      xr
 t        «       S )N)Úboolr9   rD   ©Úselfs    r-   Úis_availablez(OpenAICodexImageGenProvider.is_availableì   s   € ÜÔ,Ó.Ó/ÒFÔ4DÓ4FÐFr6   c                ó   — dddg dddœS )Nr·   Úfreeu[   gpt-image-2 via ChatGPT/Codex OAuth â€” no API key required; supports text and image inputsÚopenai_codexut   Sign in with `hermes auth add openai-codex` (or `hermes setup` â†’ Codex) if you haven't already. No API key needed.)ÚnameÚbadgeÚtagÚenv_varsÚ
post_setupÚpost_setup_hintr5   r»   s    r-   Úget_setup_schemaz,OpenAICodexImageGenProvider.get_setup_schemaï   s"   € à)ØØpØð )ð=ñ
ð 	
r6   c                ó   — ddgt         dœS )Nr+   Úimage)Ú
modalitiesÚmax_reference_images)r„   r»   s    r-   Úcapabilitiesz(OpenAICodexImageGenProvider.capabilitiesý   s   € Ø% wÐ/ÔI^Ñ_Ð_r6   N)r~   r…   c               ó\  — |xs dj                  «       }t        |«      }|st        d|«      S t        «       }|s t	        d|«      t
        d«      S t        «       s t	        d|«      dd«      S t        «       \  }}	t        |«      }
t	        d|||¬«      }	 t        ||«      }	 t        |||
|	d	   |xs d ¬
«      }|j                  d«      }t        |t         «      r)|r't        |d   t"        «      r|d   j                  d«      nd }t        |t$        «      r|s	 |dd«      S 	 t'        t)        j*                  |«      «      }t-        |d|› �¬«      }t/        t%        |«      |||d|rdnd|
|	d	   t1        |«      |
||j                  d	«      |j                  d«      |j                  d«      dœ¬«      S # t        $ r} |d|› �d«      cY d }~S d }~ww xY w# t        $ r-}t        j                  dd¬«        |d|› �d«      cY d }~S d }~ww xY w# t        $ r} |d|› �d«      cY d }~S d }~ww xY w)Nr   r1   Úauth_requiredz6httpx Python package not installed (pip install httpx)Úmissing_dependency)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_errorra   r   Úb64_jsonz1Codex images API response contained no image dataÚempty_responseÚopenai_codex_)ÚprefixzCould not save image to cache: Úio_errorrÉ   r+   r�   rž   )r�   rŽ   Úinput_image_countÚrequested_sizeÚ
pixel_sizeÚreported_qualityÚreported_sizerž   )rÉ   rŒ   r‹   Úaspect_ratioÚproviderÚmodalityÚextra)r&   r   r   r9   r   Ú_NO_AUTHrD   r7   r   r‡   r;   rª   r<   r=   r$   r"   Úlistr#   r%   r´   rQ   r^   r   r   rO   )r¼   r‹   rÝ   r~   r…   ÚkwargsÚaspectr?   Útier_idÚmetar�   Úfailrˆ   r@   r,   ra   Úb64rÚ   Ú
saved_paths                      r-   Úgeneratez$OpenAICodexImageGenProvider.generate   sR  € ð
 ’,˜B×%Ñ%Ó'ˆÜ% lÓ3ˆÙÜ(¨¸Ó@Ð@Ü(Ó*ˆÙØ8”= °Ó8¼À?ÓSÐSÜÔ!Ø8”= °Ó8ØHÐJ^ó`ð `ô 'Ó(‰ˆ�Ü˜ÓˆÜ˜^¨V¸7È6ÔRˆð	fÜ2°9Ð>RÓSˆLð	^Ü)Ø˜f¨4¸¸i¹ÐWcÒWkÐgkômˆGð �{‰{˜6Ó"ˆÜ)3°D¼$Ô)?ÁDÌZÐX\Ð]^ÑX_ÔaeÔMfˆd�1‰g�k‰k˜*Ô%ÐlpˆÜ˜#œsÔ#©3ÙÐKÐM]Ó^Ð^ð	MÜ(¬×)9Ñ)9¸#Ó)>Ó?ˆJÜ'¨°mÀGÀ9Ð4MÔNˆJô  Ü�j“/¨¸ÈfØ#¹©gÈ6à¨¨i©ÌsÐS_ÓO`Ø"&°jØ$+§K¡K°	Ó$:ÈWÏ[É[ÐY_ÓM`Ø'.§{¡{Ð3HÓ'Iñ	ôð 	øô) ò 	fÙÐGÈÀuÐMÐOdÓeÕeûð	fûô ò 	^Ü�L‰LÐ8À4ˆLÔHÙÐIÈ#ÈÐOÐQ\Ó]Õ]ûð	^ûô ò 	MÙÐ9¸#¸Ð?ÀÓLÕLûð	MúsZ   ÂF1 Â#G Ä".H Æ1	GÆ:GÇGÇGÇ	H	Ç"HÇ>H	ÈH	È	H+ÈH&È H+È&H+©Úreturnrº   )rì   úDict[str, Any])r‹   r%   rÝ   r%   r~   úOptional[str]r…   úOptional[List[str]]rã   r   rì   rí   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__Úprovider_idÚlabelr   Úmodelsr4   Údefault_model_idÚpricer½   rÇ   rÌ   r
   rê   r5   r6   r-   r¶   r¶   ã   sz   „ ÙOà €KØ!€EØ€FØ$ÐØ€EóGó
ó`ð 0Dð1à#'ÐUYñ1Øð1Ø),ð1à ð1à?Rð1ð ð1ð 
ô	1r6   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ü   4  s   € à×#Ñ#Ô$?Ó$AÕBr6   )r*   r%   rì   r%   )rì   úTuple[str, Dict[str, Any]])rì   rî   rë   )rI   Úbytesrì   rî   )rI   rþ   rT   r%   rU   r%   rì   r%   )r_   r%   rì   r%   )r_   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ý   )r?   r%   r‹   r%   r�   r%   rŽ   r%   rˆ   rÿ   rì   rí   )rì   ÚNone);ró   Ú
__future__r   rQ   r    Úloggingrs   r¡   Úpathlibr   Útypingr   r   r   r   r	   Úagent.image_gen_providerr
   r   r   r   Úplugins.image_gen._commonr   r’   r   r4   r   r   r   r   r   r   r   Ú	getLoggerrð   r<   r¦   r'   r„   rP   Ú	frozensetrH   rá   r.   r7   r9   rD   rK   rV   rc   ro   r|   r�   r‡   r“   rª   r´   r¶   rü   r5   r6   r-   ú<module>r	     s\  ðñõ" #ã Û Û Û 	Û Ý ß 3Õ 3ç qÓ q÷$÷ $õ $ð
 
ˆ×	Ñ	˜8Ó	$€à9€ØÐ àÐ Ø)Ð á Ò!WÓXÐ ðOð 	ó
(ócó
	óó:óIó
Aó@ó?ó"$ð8Øð8Ø4Gð8àó8ð ]añ&Øð&Øð&Ø(+ð&Ø;Yð&àó&ð" imñØðØðØ&)ðØ47ðØGeðàóó:ôNÐ"8ô NôbCr6   