
    Oj                    $   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 ddlmZmZmZ ddlmZ  ej*                  e      ZdZ ej2                  d	      Z G d
 de      Zddddded	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZy)u  call_gpt_image_bytes — gpt-image-2 저수준 primitive wrapper (Task B0).

gpt-image-2 direct call site(background_render / background_chain_render /
floor_plan_render / location_floor_plan / space_set_bg_provider)들이 공유하는
"openai images.generate/edit 호출 + b64 decode + capture enqueue"만 캡슐화한다.
retry/sanitizer/ref validation/out_path write/empty·too-small 검증/budget reserve/
result dict 는 각 호출부에 그대로 남긴다 — byte-identical 보존 + budget label drift /
double-count 방지(Codex 합의).

분기(현 사이트 형태 그대로 재현):
  - mode='generate' → ``images.generate(prompt=..., **call_kwargs)`` (ref 없음)
  - mode='edit'     → ``images.edit(image=핸들, prompt=..., **call_kwargs)``
      · ref_paths 1개  → image=단일 핸들 (리스트 아님)
      · ref_paths 2개+ → image=[핸들...] (순서 보존)

``call_kwargs`` 는 각 사이트가 현재 넘기는 정확한 dict 그대로 pass-through 한다
(space_set 은 quality/n 미포함 → 고정 인자로 강제 주입하지 않음).

capture = wrapper 단일점 + capture-only min-size 가드: ``generation_context`` scope 가
열렸고(아니면 sink no-op) ``len(png) >= min_capture_bytes`` 일 때만
``capture_generated_image`` enqueue. 작으면 capture 만 skip 하고 bytes 는 그대로 반환 —
사이트의 too-small/empty 검증·error type 을 wrapper 가 바꾸지 않는다.

빈 응답(resp.data 없음 / b64 None) → ``b""`` 반환(사이트가 자기 error 로 raise).
    )annotationsN)Path)AnyDictListOptional)ambient_call_metarecord_provider_callresolve_step_name)capture_generated_imagei   z4gpt-image-2\.5-(sunburst|flare)(-\d{4}-\d{2}-\d{2})?c                      e Zd ZdZy)GptImageModelNotAlloweduV   2.5 계열이 아닌 gpt-image 모델로 부르려 했다 — 호출 전에 막는다.N)__name__
__module____qualname____doc__     Y/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/llm/gpt_image_primitive.pyr   r   @   s    `r   r   F)	ref_pathscapture_rolecapture_metadatacapture_input_image_idsedit_image_as_listmin_capture_bytesc       	           t        |xs g       |dvrt        d|      |dk(  rst        d      t        |j                  d      xs d      }
t        j                  |
      st        d|
d      |xs i j                         D ci c]
  \  }}||| c}}j                  t                      t              t        |j                  d      xs d
      t        j                         dfdd d!fd}	 |dk(  r | j                  j                  d"di|}nt        j                          5 }t#              dk(  r/|s-|j%                  t'        d         j)                  d            }n6D cg c]+  }|j%                  t'        |      j)                  d            - }} | j                  j*                  d"|d|}d	d	d	       %|j,                  r|j,                  d   j.                  nd	}|s$t1        d        t#              d
       yddlm}  |t7        j8                  |      dxs d       }t1        d        t#              d
      }rt#        |      |	k\  rt=        |||       |S c c}}w c c}w # 1 sw Y   xY w# t:        $ r} ||        d	}~ww xY w)#u  gpt-image 호출 + b64 decode + (scope 열림 시) capture. decode 된 bytes 반환.

    ``edit_image_as_list`` — ref 가 1개일 때 ``image=핸들``(단일, 기본) vs
    ``image=[핸들]``(리스트)를 고른다. background/chain/floor_plan_render 는 1개면
    단일 핸들(default False), location_floor_plan 은 항상 리스트(True) — 각 사이트의
    현 호출 형태를 byte-identical 로 보존하기 위함.
    )editgeneratez#call_gpt_image_bytes: unknown mode r   z@call_gpt_image_bytes(mode='edit') requires at least one ref_pathmodel u;   gpt-image 는 2.5 계열만 허용한다 — 받은 모델 u>   . 설정이나 호출부에 옛 이름이 남았는지 본다.Nz	gpt-imagec                 J    t        t        j                          z
  dz        S )Ni  )inttime	monotonic)t0s   r   _elapsed_msz)call_gpt_image_bytes.<locals>._elapsed_msn   s    DNN$r)T122r   c                d    t        	|        t              t        |       d d 
       y )Ni  
stepr   promptstatusduration_msmeta	ref_count	operationref_image_idserror)r
   lenstr)
excr+   r&   r   r   r-   r   r*   refsr)   s
     r   _failz#call_gpt_image_bytes.<locals>._failq   s3    $eF6&1m$$'I(? #C#		0r   r   r*      r   rb)imager*   empty_responsezno b64_json in responser(   r   )ensure_png_bytesz
gpt-image/?)contextsuccessz[image generated])
r)   r   r*   r+   r,   r-   r.   r/   r0   output_text)roleinput_image_idsgeneration_call_idr*   pipeline_metadata)returnr"   )r1   )r4   	Exceptionr+   r3   rD   Noner   )list
ValueErrorr3   getGPT_IMAGE_ALLOWED_MODEL	fullmatchr   itemsupdater	   r   r#   r$   imagesr   
contextlib	ExitStackr2   enter_contextr   openr   datab64_jsonr
   app.modules.llm.image_formatr;   base64	b64decoderE   r   )openai_clientmoder*   r   call_kwargsr   r   r   r   r   
_model_reqkvr6   respstackr9   pb64r;   pngr4   call_idr&   r-   r   r5   r)   r%   s     `  ` `               @@@@@@r   call_gpt_image_bytesrd   D   s   ( 	R D''>thGHHv~d[\\[__W-34J",,Z8%I* XK LM 	M /?.D"-K-K-M .-MTQ } qD-M .DKK!#$\40D(7K8E		B30 0:0=''00NN+ND%%'5t9>*<!&!4!4T$q']5G5G5M!NENRSdU00ad1CDdES0}++00 ??2=? ( )-(8TYYdiil##T d%%5*5-d(+D	\,C$=? 
 	BS!Z8K7L+MO #viMD	.E'	)G C$553&.	
 JI.8 T	 ('2  c
sP   J'8J> AJ20J-?!J2 AJ> 7*J> -J22J;7J> >	K	KK)rX   r   rY   r3   r*   r3   r   zOptional[List[Any]]rZ   zDict[str, Any]r   zOptional[str]r   zOptional[Dict[str, Any]]r   zOptional[List[str]]r   boolr   r"   rD   bytes)r   
__future__r   rV   rO   loggingrer#   pathlibr   typingr   r   r   r   app.modules.llm.image_tracerr	   r
   r   app.services.image_capture.sinkr   	getLoggerr   logger_MIN_CAPTURE_BYTEScompilerJ   rH   r   rd   r   r   r   <module>rr      s  4 #    	   , , 
 D			8	$  %"**;= aj a &*"&1537$/gg g 	g
 #g  g  g /g 1g g g gr   