Ë
    tPêj$ ã                   óè  — d dl Z d dlZd dlZd dlmZmZ d dlmZ d dlmZ d dl	m
Z
mZmZmZmZmZmZ d dlZd dlmZmZ d dlZd dlmZ d dlmZ d d	lmZmZmZ d d
lmZ e
rd dlm Z! d dl"m#Z# d dl$m%Z%m&Z& d dl'm(Z(m)Z) d dl*m+Z+ d dl,m-Z-m.Z.m/Z/m0Z0m1Z1 d dl2m3Z3m4Z4 d dlm5Z5m6Z6 d dl7m8Z8m9Z9m:Z:m;Z;  G d„ ded¬«      Z< G d„ de«      Z=e>e j~                  e j€                  f   ZAdeBdeCfd„ZD G d„ de(«      ZEy)é    N)ÚAsyncGeneratorÚSequence)Úasynccontextmanager)Údatetime)ÚTYPE_CHECKINGÚAnyÚFinalÚLiteralÚOptionalÚ	TypedDictÚcast)ÚNotRequiredÚReadOnly)Ú
get_secret)Úverbose_proxy_logger)Ú)DEFAULT_PRESIDIO_ANALYZE_CHUNK_SIZE_BYTESÚ"PRESIDIO_ANALYZE_CHUNK_CONCURRENCYÚ$PRESIDIO_ANALYZE_CHUNK_OVERLAP_CHARS)ÚGenericGuardrailAPIInputs)ÚLogging)Ú	DualCache)ÚBlockedPiiEntityErrorÚGuardrailRaisedException)ÚCustomGuardrailÚlog_guardrail_information)ÚUserAPIKeyAuth)ÚGuardrailEventHooksÚLitellmParamsÚ	PiiActionÚPiiEntityTypeÚPresidioPerRequestConfig)ÚPresidioAnalyzeRequestÚPresidioAnalyzeResponseItem)ÚGuardrailStatusÚStreamingChoices)ÚEmbeddingResponseÚImageResponseÚModelResponseÚModelResponseStreamc                   ó$   — e Zd ZU eedz     ed<   y)Ú_PresidioAnonymizeItemNÚentity_type)Ú__name__Ú
__module__Ú__qualname__r   ÚstrÚ__annotations__© ó    úv/home/oem/CyberBrain26-unified/.venv/lib/python3.12/site-packages/litellm/proxy/guardrails/guardrail_hooks/presidio.pyr+   r+   >   s   … Ø˜# ™*Ñ%Ô%r3   r+   F)Útotalc                   ó:   — e Zd ZU ee   ed<   eeee         ed<   y)Ú_PresidioAnonymizeResponseÚtextÚitemsN)	r-   r.   r/   r   r0   r1   r   Úlistr+   r2   r3   r4   r7   r7   B   s"   … Ø
�3‰-ÓØ�K Ð%;Ñ <Ñ=Ñ>Ô>r3   r7   r8   Úreturnc                 ób   — t        t        j                  | «      j                  d«      «      dz
  S )zÜ
    Byte length of ``text`` as it appears serialized inside the JSON request
    body sent to Presidio (``json.dumps`` escapes non-ASCII characters, so a
    3-byte UTF-8 character can occupy 6+ bytes on the wire).
    úutf-8é   )ÚlenÚjsonÚdumpsÚencode)r8   s    r4   Ú_json_escaped_lenrC   J   s(   € ô Œt�z‰z˜$Ó×&Ñ& wÓ/Ó0°1Ñ4Ð4r3   c                   ó,  ‡ — e Zd ZU dZdZee   dz  ed<   edee	   fd„«       Z
	 	 	 	 	 	 	 	 	 	 	 	 	 dXdededz  dedz  dedz  d	edz  d
ededz  dedz  deeez  ef   dz  dedz  deeez  ef   dz  deeez     dz  dedz  fˆ fd„Z	 	 dYdedz  dedz  fd„Zedeej.                  df   fd„«       ZdZd„Zd„ Zdefd„Zdededz  dedefd„Zdededz  dedee   ez  fd„Z dededz  dedee   fd„Z!de"jF                  fd„Z$e%dedz  defd „«       Z&e%ded!ed"ede'e(eef      fd#„«       Z)e%d$e'e(eef      d%e'e'e      dee   fd&„«       Z*ded'ee   ez  dedz  fd(„Z+d)ed*eeef   defd+„Z,ded'e-dedz  d*eeef   def
d,„Z.	 d[ded'ee   ez  d	ed*eeef   dedz  defd-„Z/d'ee   ez  dee   ez  fd.„Z0d'ee   ez  fd/„Z1ded	ededz  dedef
d0„Z2d1e3d2e4d3ed4efd5„Z5d6ed7e-d4ede(ee-f   fd8„Z6d6ed7e-d4ede(ee-f   fd9„Z7d3ed1e3d:e8e9z  e:z  fd;„Z;e%ded<eeef   defd=„«       Z<e%d:e8e9z  e:z  eee=f   z  defd>„«       Z>d:eded?e?d@   defdA„Z@d:e8ded?e?d@   de8fdB„ZAd:e8e9z  e:z  defdC„ZBd:e-dedeeCeDz  df   fdD„ZEe%dEeDd<eeef   deDfdF„«       ZFdEe-d<eeef   ddfdG„ZGd:e-dedeeCeDz  df   fdH„ZHd1e3d:e-dedeeCeDz  df   fdI„ZIe%dJe8dKeeC   ddfdL„«       ZJd3ededz  fdM„ZKdN„ ZLeM	 d[dOdPdedQe?dR   dSeNdT   ddPf
dU„«       ZOdVePddfˆ fdW„ZQˆ xZRS )\Ú_OPTIONAL_PresidioPIIMaskingNÚad_hoc_recognizersr;   c                 óº   — t         j                  t         j                  t         j                  t         j                  t         j
                  t         j                  gS ©N)r   Úpre_callÚduring_callÚ	post_callÚlogging_onlyÚpre_mcp_callÚpost_mcp_call)Úclss    r4   Úget_supported_event_hooksz6_OPTIONAL_PresidioPIIMasking.get_supported_event_hooksW   sD   € ô  ×(Ñ(Ü×+Ñ+Ü×)Ñ)Ü×,Ñ,Ü×,Ñ,Ü×-Ñ-ð
ð 	
r3   Úmock_testingÚmock_redacted_textÚpresidio_analyzer_api_baseÚpresidio_anonymizer_api_baseÚoutput_parse_piiÚapply_to_outputÚpresidio_ad_hoc_recognizersrL   Úpii_entities_configÚpresidio_languageÚpresidio_score_thresholdsÚpresidio_entities_deny_listÚ!presidio_analyze_chunk_size_bytesc                 óœ  •— |du rd| _         t        j                   |d<   |j                  dt        | j	                  «       «      «       t        ‰| �  di |¤Ž d| _        i | _        || _	        |xs d| _
        || _        | j                  s| j                  rv|st| j                  }t        |t        «      r$|dk7  rt        t        t           |dg«      | _        n4t        |t        «      r$d|vr t        t        t           |dgz   «      | _        |	xs i | _        |xs i | _        |xs g | _        |
xs d| _        | j)                  |«      | _        d | _        t/        j0                  «       | _        t5        j6                  «       | _        i | _        i | _        |du ry |}|�0	 t?        |d«      5 }tA        jB                  |«      | _"        d d d «       | jM                  ||¬«       y # 1 sw Y   ŒxY w# tF        $ r tI        d	|› �«      ‚t@        jJ                  $ r}tI        d
|› d|› �«      ‚d }~wtH        $ r}tI        d|› d|› �«      ‚d }~ww xY w)NTÚ
event_hookÚsupported_event_hooksÚpresidioFrK   ÚenÚrzFile not found. file_path=zError decoding JSON file: z, file_path=zAn error occurred: )rS   rT   r2   )'rL   r   Ú
setdefaultr:   rP   ÚsuperÚ__init__Úguardrail_providerÚ
pii_tokensrR   rU   rV   r^   Ú
isinstancer0   r   rX   rZ   r[   rY   Ú_coerce_analyze_chunk_sizer\   Ú_http_sessionÚasyncioÚLockÚ_session_lockÚ	threadingÚ	get_identÚ_main_thread_idÚ_loop_sessionsÚ_loop_chunk_semaphoresÚopenr@   ÚloadrF   ÚFileNotFoundErrorÚ	ExceptionÚJSONDecodeErrorÚvalidate_environment)ÚselfrQ   rR   rS   rT   rU   rV   rW   rL   rX   rY   rZ   r[   r\   ÚkwargsÚcurrent_hookrF   ÚfileÚeÚ	__class__s                      €r4   re   z%_OPTIONAL_PresidioPIIMasking.__init__c   si  ø€ ð" ˜4ÑØ $ˆDÔÜ#6×#CÑ#CˆF�<Ñ Ø×ÑÐ1´4¸×8VÑ8VÓ8XÓ3YÔZÜ‰ÑÑ"˜6Ò"Ø",ˆÔð ð 	Œð #5ˆÔØ 0Ò 9°EˆÔØ.ˆÔð
 ×!Ò! T×%9Ò%9Á<Ø"&§/¡/ˆLÜ˜,¬Ô,°ÀÒ1LÜ"&¤tÔ,?Ñ'@À<ÐQ\ÐB]Ó"^�•Ü˜L¬$Ô/°KÀ|Ñ4SÜ"&¤tÔ,?Ñ'@À,ÐR]ÐQ^ÑB^Ó"_�”ØI\ÒIbÐ`bˆÔ ØKdÒKjÐhjˆÔ&ØFaÒFgÐegˆÔ(Ø!2Ò!:°dˆÔØ6:×6UÑ6UÐVwÓ6xˆÔ.à;?ˆÔô ,3¯<©<«>ˆÔô  )×2Ñ2Ó4ˆÔð WYˆÔð 8:ˆÔ#à˜4ÑØà$?ÐØÐ)ð[ÜÐ,¨cÓ2ð >°dÜ.2¯i©i¸«o�DÔ+÷>ð 	×!Ñ!Ø'AØ)Eð 	"õ 	
÷>ð >ûä$ò SÜÐ"<Ð=OÐ<PÐ QÓRÐRÜ×'Ñ'ò bÜÐ"<¸Q¸C¸|ÐL^ÐK_Ð `ÓaÐaûÜò [ÜÐ"5°a°S¸ÐEWÐDXÐ YÓZÐZûð[ús<   Æ G/ Æ,G#ÇG/ Ç#G,Ç(G/ Ç/)IÈH)È)IÈ5IÉIc                 ó´  — |xs t        dd «      | _        |xs t        j                   dd «      | _        | j                  €t	        d«      ‚| j                  j                  d«      s| xj                  dz  c_        | j                  j                  d«      s/| j                  j                  d«      sd| j                  z   | _        | j                  €t	        d«      ‚| j                  j                  d«      s| xj                  dz  c_        | j                  j                  d«      s1| j                  j                  d«      sd| j                  z   | _        y y y )NÚPRESIDIO_ANALYZER_API_BASEÚPRESIDIO_ANONYMIZER_API_BASEz5Missing `PRESIDIO_ANALYZER_API_BASE` from environmentú/zhttp://zhttps://z7Missing `PRESIDIO_ANONYMIZER_API_BASE` from environment)r   rS   ÚlitellmrT   rv   ÚendswithÚ
startswith)ry   rS   rT   s      r4   rx   z1_OPTIONAL_PresidioPIIMasking.validate_environment³   sF  € ð
 7Qò 7
ÔT^Ø(¨$óU
ˆÔ'ð 9Uò 9
ÔX_×XjÑXjØ*¨DóY
ˆÔ)ð ×*Ñ*Ð2ÜÐSÓTÐTØ×.Ñ.×7Ñ7¸Ô<Ø×+Ò+¨sÑ2Õ+à×+Ñ+×6Ñ6°yÔAØ×.Ñ.×9Ñ9¸*ÔEð /8¸$×:YÑ:YÑ.YˆDÔ+à×,Ñ,Ð4ÜÐUÓVÐVØ×0Ñ0×9Ñ9¸#Ô>Ø×-Ò-°Ñ4Õ-à×-Ñ-×8Ñ8¸ÔCØ×0Ñ0×;Ñ;¸JÔGð 1:¸D×<]Ñ<]Ñ0]ˆDÕ-ð Hð Dr3   c                ó<  K  — t        j                  «       }t        j                  «       | j                  k(  rp| j
                  4 ƒd{  –—†  | j                  �| j                  j                  rt        j                  «       | _        | j                  ­–— ddd«      ƒd{  –—†  y|| j                  vs| j                  |   j                  r!t        j                  «       | j                  |<   | j                  |   ­–— y7 Œº7 Œb# 1 ƒd{  –—†7  sw Y   yxY w­w)ah  
        Async context manager for yielding an HTTP session.

        Logic:
        1. If running in the main thread (where the object was initialized/destined to live normally),
           use the shared `self._http_session` (protected by a lock).
        2. If running in a background thread (e.g. logging hook), use a cached session for that loop.
        N)rk   Úget_running_looprn   ro   rp   rm   rj   ÚclosedÚaiohttpÚClientSessionrq   )ry   Úcurrent_loops     r4   Ú_get_session_iteratorz2_OPTIONAL_PresidioPIIMasking._get_session_iteratorÕ   sð   è ø€ ô &×6Ñ6Ó8ˆô ×ÑÓ  D×$8Ñ$8Ò8à×)Ñ)÷ )ñ )Ø×%Ñ%Ð-°×1CÑ1C×1JÒ1JÜ)0×)>Ñ)>Ó)@�DÔ&Ø×(Ñ(Ó(÷)÷ )ð )ð  4×#6Ñ#6Ñ6¸$×:MÑ:MÈlÑ:[×:bÒ:bÜ4;×4IÑ4IÓ4K�×#Ñ# LÑ1Ø×%Ñ% lÑ3Ô3ð)øð )ø÷ )÷ )ñ )üsJ   ‚ADÁDÁ	DÁADÂDÂ"DÂ#A!DÄDÄDÄDÄDÄDc              ƒ   óf  K  — | j                   �?| j                   j                  s)| j                   j                  «       ƒ d{  –—†  d| _         | j                  j	                  «       D ]'  }|j                  rŒ|j                  «       ƒ d{  –—†  Œ) | j                  j                  «        y7 Œj7 Œ#­w)zClose all cached HTTP sessions.N)rj   rˆ   Úcloserq   ÚvaluesÚclear)ry   Úsessions     r4   Ú_close_http_sessionz0_OPTIONAL_PresidioPIIMasking._close_http_sessionò   s“   è ø€ à×ÑÐ)°$×2DÑ2D×2KÒ2KØ×$Ñ$×*Ñ*Ó,×,Ð,Ø!%ˆDÔà×*Ñ*×1Ñ1Ó3ò 	&ˆGØ—>“>Ø—m‘m“o×%Ñ%ð	&ð 	×Ñ×!Ñ!Õ#ð -øð
 &ús*   ‚A B1ÁB-Á4B1Á8B1ÂB/Â"B1Â/B1c                  ó   — y)zFCleanup: we try to close, but doing async cleanup in __del__ is risky.Nr2   ©ry   s    r4   Ú__del__z$_OPTIONAL_PresidioPIIMasking.__del__ý   s   � r3   c                 óp   — | j                   syt        d„ | j                   j                  «       D «       «      S )zYReturn True if pii_entities_config has any BLOCK action (fail-closed on analyzer errors).Fc              3   óB   K  — | ]  }|t         j                  k(  –— Œ y ­wrH   )r   ÚBLOCK)Ú.0Úactions     r4   ú	<genexpr>zA_OPTIONAL_PresidioPIIMasking._has_block_action.<locals>.<genexpr>  s   è ø€ Ò]°�6œYŸ_™_Õ,Ñ]ùs   ‚)rX   Úanyr�   r”   s    r4   Ú_has_block_actionz._OPTIONAL_PresidioPIIMasking._has_block_action   s.   € à×'Ò'ØÜÑ]¸4×;SÑ;S×;ZÑ;ZÓ;\Ô]Ó]Ð]r3   r8   Úpresidio_configÚrequest_datac                 ó†  — t        || j                  ¬«      }| j                  �| j                  |d<   | j                  r&t	        | j                  j                  «       «      |d<   |r|j                  r|j                  |d<   t        t        |«      }|j                  | j                  |¬«      «       t        t         |«      S )z¸
        Construct the payload for the Presidio analyze request

        API Ref: https://microsoft.github.io/presidio/api-docs/api-docs.html#tag/Analyzer/paths/~1analyze/post
        )r8   ÚlanguagerF   Úentitiesr¡   )rŸ   )r"   rY   rF   rX   r:   Úkeysr¡   r   ÚdictÚupdateÚ)get_guardrail_dynamic_request_body_params)ry   r8   rž   rŸ   Úanalyze_payloadÚcasted_analyze_payloads         r4   Ú%_get_presidio_analyze_request_payloadzB_OPTIONAL_PresidioPIIMasking._get_presidio_analyze_request_payload  s·   € ô :PØØ×+Ñ+ô:
ˆð ×"Ñ"Ð.Ø48×4KÑ4KˆOÐ0Ñ1à×#Ò#Ü*.¨t×/GÑ/G×/LÑ/LÓ/NÓ*OˆO˜JÑ'ñ ˜×7Ò7Ø*9×*BÑ*BˆO˜JÑ'ä.2´4¸Ó.IÐØ×%Ñ% d×&TÑ&TÐbnÐ&TÓ&oÔpÜÔ*Ð,BÓCÐCr3   c           
   ƒ   óò  ‡ K  — |rNt        |«      dkD  r@‰ j                  €4t        |«      ‰ j                  kD  r‰ j	                  |||¬«      ƒ d{  –—† S 	 |rt        |j                  «       «      dk(  rt        j                  d«       g S ‰ j                  �‰ j                  S ‰ j                  «       4 ƒd{  –—† }‰ j                  › d�}‰ j                  |||¬«      }t        j                  d||«       dt        d	t        t           fˆ fd
„}|j                  ||ddi¬«      4 ƒd{  –—† }|j                  dk\  rU|j!                  «       ƒ d{  –—† }	 |d|j                  › d|	dd › �«      cddd«      ƒd{  –—†  cddd«      ƒd{  –—†  S t#        |d|j$                  j'                  dd«      «      }
d|
vrL|j!                  «       ƒ d{  –—† }	 |d|
› d|	dd › d�«      cddd«      ƒd{  –—†  cddd«      ƒd{  –—†  S |j)                  «       ƒ d{  –—† }t        j                  d|«       ddd«      ƒd{  –—†  t+        t,        «      rbd|v r+ |d|j'                  d«      › �«      cddd«      ƒd{  –—†  S t        j                  d«       	 t        d#i |¤Žgcddd«      ƒd{  –—†  S t+        |t        «      s> |dt1        |«      j2                  › dt        |«      dd › �«      cddd«      ƒd{  –—†  S g }|D ]d  }t+        |t,        «      s7t        j4                  dt1        |«      j2                  t        |«      dd  «       ŒJ	 |j7                  t        d#i |¤Ž«       Œf |cddd«      ƒd{  –—†  S 7 �Œ57 �ŒÑ7 �Œb7 �Œ>7 �Œ7 �Œ7 �ŒÇ7 �Œ¨7 �Œš7 �Œ„7 �Œa# 1 ƒd{  –—†7  sw Y   �ŒrxY w7 �Œ<7 �Œ# t.        $ r'} |d|› �«      cY d}~cddd«      ƒd{  –—†7   S d}~ww xY w7 Œó# t.        $ r"}t        j4                  d!||«       Y d}~�Œd}~ww xY w7 Œ¥# 1 ƒd{  –—†7  sw Y   yxY w# t8        $ r ‚ t.        $ r'}t/        d"t1        |«      j2                  › �«      |‚d}~ww xY w­w)$a½  
        Send text to the Presidio analyzer endpoint and get analysis results

        Texts larger than ``presidio_analyze_chunk_size_bytes`` (UTF-8) are split
        into overlapping chunks, analyzed per chunk, and the per-chunk results
        are remapped onto the original text. Presidio analyzer deployments
        commonly cap the /analyze request body size (e.g. at 1 MB), and analyzer
        latency grows with payload size.
        é   N©r8   rž   rŸ   r   z9Skipping Presidio analysis for empty/whitespace-only textÚanalyzez&Making request to: %s with payload: %sÚreasonr;   c                 óÐ   •— t        ‰j                  «      xs ‰j                  xs ‰j                  }|rt	        ‰j
                  d| › �d¬«      ‚t        j                  d| «       g S )NzbPresidio analyzer returned invalid response; cannot verify PII when PII protection is configured: F)Úguardrail_nameÚmessageÚ should_wrap_with_default_messagez*Presidio analyzer %s, returning empty list)ÚboolrX   rU   rV   r   r°   r   Úwarning)r®   Úshould_fail_closedry   s     €r4   Ú_fail_on_invalid_responsezL_OPTIONAL_PresidioPIIMasking.analyze_text.<locals>._fail_on_invalid_responsec  s|   ø€ ô *.¨d×.FÑ.FÓ)GÒ)xÈ4×K`ÑK`Ò)xÐdh×dxÑdxÐ&Ù)Ü6Ø+/×+>Ñ+>ð 'Ið  JPð  IQð  %RØ=Bôð ô
 )×0Ñ0Ð1]Ð_eÔfØ�Ir3   ÚAcceptúapplication/json©r@   Úheadersé�  zHTTP z from Presidio analyzer: éÈ   Úcontent_typeúContent-TypeÚ z5expected application/json Content-Type but received 'ú
'; body: 'ú'úanalyze_results: %sÚerrorzerror: zGPresidio returned dict (not list), attempting to process as single itemzfailed to parse dict response: zunexpected type z$ (expected list or dict), response: zASkipping invalid Presidio result item (expected dict, got %s): %séd   z4Failed to parse Presidio result item: %s (error: %s)zPresidio PII analysis failed: r2   )r?   rR   rC   r\   Ú_analyze_text_chunkedÚstripr   ÚdebugrŒ   rS   r©   r0   r:   r#   ÚpostÚstatusr8   Úgetattrrº   Úgetr@   rh   r¤   rv   Útyper-   r´   Úappendr   )ry   r8   rž   rŸ   r‘   Úanalyze_urlr§   r¶   ÚresponseÚ
error_bodyr½   Úanalyze_resultsr}   Úfinal_resultsÚitems   `              r4   Úanalyze_textz)_OPTIONAL_PresidioPIIMasking.analyze_text*  s/  øè ø€ ñ( Ü�D“	˜A’Ø×'Ñ'Ð/Ü! $Ó'¨$×*PÑ*PÒPà×3Ñ3ØØ /Ø)ð 4ó ÷ ð ð
r	Xñ œ3˜tŸz™z›|Ó,°Ò1Ü$×*Ñ*Ð+fÔgØ�	à×&Ñ&Ð2Ø×.Ñ.Ð.ð ×1Ñ1Ó3÷ `%ð `%°wà(,×(GÑ(GÐ'HÈÐ%P�àAE×AkÑAkØØ$3Ø!-ð Bló B�ô %×*Ñ*Ø<ØØ#ôðÜðäÔ5Ñ6õð #Ÿ<™<ØØ(Ø%Ð'9Ð:ð (ó ÷ Wð Wð à—‘¨#Ò-Ø+3¯=©=«?×%:˜
Ù8Ø# H§O¡OÐ#4Ð4MÈjÐY]ÐZ]ÐN^ÐM_Ð`ó ÷W÷ Wð W÷;`%÷ `%ñ `%ôT +2Ø Ø&Ø ×(Ñ(×,Ñ,¨^¸RÓ@ó+�Lð
 *°Ñ=Ø+3¯=©=«?×%:˜
Ù8ØSÐT`ÐSaÐakÐlvÐw{Ðx{Ðl|Ðk}Ð}~Ðó ÷)W÷ Wð W÷;`%÷ `%ñ `%ðj 4<·=±=³?×-B�OÜ(×.Ñ.Ð/DÀoÔV÷3W÷ Wô< ˜o¬tÔ4Ø /Ñ1Ù8¸7À?×CVÑCVÐW^ÓC_ÐB`Ð9aÓb÷{`%÷ `%ñ `%ô~ )×.Ñ.Øaôð`Ü ;Ñ N¸oÑ NÐO÷G`%÷ `%ñ `%ôP " /´4Ô8Ù4Ø*¬4°Ó+@×+IÑ+IÐ*JÐJnÔorð  tCó  pDð  EIð  FIð  pJð  oKð  Ló÷S`%÷ `%ñ `%ð\ (*�Ø+ò !�DÜ% d¬DÔ1Ü,×4Ñ4Ø_Ü  ›J×/Ñ/Ü ›I d s˜Oôð
 !ð!Ø%×,Ñ,Ô-HÑ-PÈ4Ñ-PÕQð!ð" %÷A`%÷ `%ñ `%ð!ùð `%ùð:Wùð &;ùðWùð;`%ùð` &;ùð'Wùð;`%ùðj .Cùð1Wù÷ W÷ Wò Wúð;`%ùð `%úôH %ò `Ù8Ð;ZÐ[\ÐZ]Ð9^Ó_Ô_÷K`%÷ `%ò `%ûðH`úðI`%ùôr %ò !Ü,×4Ñ4ØRØ Øôõ
 !ûð!úðs`%ø÷ `%÷ `%ñ `%ûôB (ò 	àÜò 	Xô Ð<¼TÀ!»W×=MÑ=MÐ<NÐOÓPÐVWÐWûð	Xüsr  ƒAQ7ÁNÁQ7Á4P; Â	Q7Â
P; Â!Q7Â"P; Â6NÂ7P; Â:A.P&Ä(N
Ä)P&Ä,#N%ÅNÅN%Å.P&Å:NÅ;P&Å?P; ÆNÆP; ÆQ7Æ>N%ÇNÇN%Ç%P&Ç1NÇ2P&Ç6P; ÈNÈP; ÈQ7ÈN%ÈNÈN%È6P&ÉN"É1P&É3P; É?N;Ê P; ÊQ7ÊP&ÊOÊ&P; Ê2N>Ê3P; Ê7Q7Ê8<P&Ë4P; Ì O4ÌP; ÌQ7ÌAP&ÍO6Í/P&Í2P; Í>P$Í?P; ÎQ7ÎP; Î
P&ÎN%ÎP&ÎP; ÎN%ÎP&ÎP; ÎN%Î"P&Î%N8	Î+N.Î,N8	Î3P&Î;P; Î>P; Ï	O1Ï

O,ÏO1ÏP&ÏP; Ï%O(Ï&P; Ï+Q7Ï,O1Ï1P&Ï4P; Ï6	P!Ï?PÐP&ÐP!Ð!P&Ð$P; Ð&P8Ð,P/Ð-P8Ð4P; Ð7Q7Ð8P; Ð;Q4Ñ"Q/Ñ/Q4Ñ4Q7c              ƒ   ó0  ‡ ‡‡‡
‡K  — ‰ j                  |‰ j                  t        ¬«      }t        j                  d‰ j                  t        |«      «       ‰ j                  «       Šdt        dt        t           t        z  fˆˆˆˆ fd„Š
t        j                  ˆ
fd„|D «       ddiŽƒ d	{  –—† }g }|D ]M  }t        |t        «      r|‚t        d
|«      }‰ j!                  |¬«      }	|j#                  t        d
|	«      «       ŒO ‰ j%                  ||¬«      S 7 Œk­w)a™  
        Analyze an oversized text by splitting it into overlapping chunks.

        Each chunk serializes to at most ``presidio_analyze_chunk_size_bytes``
        bytes inside the JSON request body, so every /analyze call stays below
        the analyzer deployment's request body limit; per-chunk results are remapped onto the original text and
        merged. Raises exactly like a single ``analyze_text`` call if any chunk
        fails.

        Only the analyzer side is chunked: the later anonymize call still
        receives the full original text, so texts above the anonymizer's own
        body limit that contain detections keep failing there.
        )r8   Úchunk_size_bytesÚoverlap_charszKPresidio analyze: text exceeds %s bytes, analyzing in %s overlapping chunksÚ
chunk_textr;   c              “   ó²   •K  — ‰4 ƒd {  –—†  ‰j                  | ‰‰¬«      ƒ d {  –—† cd d d «      ƒd {  –—†  S 7 Œ17 Œ7 Œ	# 1 ƒd {  –—†7  sw Y   y xY w­w)Nr¬   )rÔ   )rØ   Úanalyze_semaphorerž   rŸ   ry   s    €€€€r4   Ú_analyze_chunk_boundedzR_OPTIONAL_PresidioPIIMasking._analyze_text_chunked.<locals>._analyze_chunk_boundedâ  sb   øè ø€ ð )÷ ñ Ø!×.Ñ.Ø#Ø$3Ø!-ð /ó ÷ ÷÷ ò øðøðø÷ ÷ ñ üsN   ƒAŠ<‹AŽA¦>§AªA¶A ·A¾AÁ AÁAÁAÁ	AÁAc              3   ó4   •K  — | ]  \  }} ‰|«      –— Œ y ­wrH   r2   )r™   Ú_rØ   rÛ   s      €r4   r›   zE_OPTIONAL_PresidioPIIMasking._analyze_text_chunked.<locals>.<genexpr>í  s   øè ø€ ÒR±]°Q¸
Ñ$ Z×0ÑRùs   ƒÚreturn_exceptionsTNz!list[PresidioAnalyzeResponseItem]©rÑ   )Útext_chunksÚchunk_results)Ú_split_text_for_analysisr\   r   r   rÇ   r?   Ú_get_chunk_semaphorer0   r   r#   r7   rk   Úgatherrh   ÚBaseExceptionr   Úfilter_analyze_results_by_scorerÍ   Ú_merge_chunked_analyze_results)ry   r8   rž   rŸ   rà   Úgatheredrá   ÚresultÚtyped_resultÚfiltered_resultrÛ   rÚ   s   ` ``      @@r4   rÅ   z2_OPTIONAL_PresidioPIIMasking._analyze_text_chunked¼  s.  üè ø€ ð& "×:Ñ:ØØ!×CÑCÜ>ð ;ó 
ˆô
 	×"Ñ"ØYØ×2Ñ2Ü�Óô	
ð $(×#<Ñ#<Ó#>Ðð	Üð	äÔ1Ñ2Ô5OÑO÷	ð 	ô !(§¡ÛRÀkÔRð!
à"ñ!
÷ 
ˆð  "ˆØò 	ˆFÜ˜&¤-Ô0Ø�ô  Ð CÀVÓLˆLð #×BÑBÐS_ÐBÓ`ˆOØ× Ñ ÜÐ8¸/ÓJõð	ð ×2Ñ2¸{ÐZgÐ2ÓhÐhð)
ús   ‡B!DÂ(DÂ)A,Dc                 ó¼   — t        j                  «       }| j                  j                  |«      }|�|S t        j                  t
        «      }|| j                  |<   |S )zNPer-event-loop semaphore shared by all chunked analyze calls on this instance.)rk   r‡   rr   rË   Ú	Semaphorer   )ry   ÚloopÚexistingÚcreateds       r4   rã   z1_OPTIONAL_PresidioPIIMasking._get_chunk_semaphore  sW   € ä×.Ñ.Ó0ˆØ×5Ñ5×9Ñ9¸$Ó?ˆØÐØˆOÜ ×*Ñ*Ô+MÓNˆØ,3ˆ×#Ñ# DÑ)Øˆr3   Úvaluec                 ó4   — | r| dk  rt         S t        | d«      S )a¸  
        Validate a configured chunk size, falling back to the default.

        Non-positive values would either bypass chunking entirely or degenerate
        it into per-character splits (silently disabling detection), so they are
        replaced by the default; values below 4 bytes are floored to 4 and the
        splitter always emits at least one character per chunk, so the chunked
        path can never re-enter itself.
        r   é   )r   Úmax)rñ   s    r4   ri   z7_OPTIONAL_PresidioPIIMasking._coerce_analyze_chunk_size  s   € ñ ˜ š
Ü<Ð<Ü�5˜!‹}Ðr3   rÖ   r×   c                 ó”  — g }t        | «      }d}||k  r³| |||z    }t        |«      |k  r|}nAdt        |«      }	}||	k  r*||	z   dz   dz  }
t        |d|
 «      |k  r|
}n|
dz
  }	||	k  rŒ*|d| }|t        |«      z   }|j                  ||f«       ||k\  r	 |S t        |t        |«      dz  «      }t	        |dz   ||z
  «      }||k  rŒ³|S )a  
        Split ``text`` into chunks whose JSON-serialized form is at most
        ``chunk_size_bytes`` bytes (the analyzer body limit applies to the
        JSON request body, where non-ASCII characters are escaped and larger
        than their raw UTF-8 encoding).

        Consecutive chunks overlap by up to ``overlap_chars`` characters so a
        PII entity up to that length lying across a chunk boundary is still
        seen whole by one of the chunks (longer boundary-straddling entities
        may be seen only truncated); ``_merge_chunked_analyze_results`` resolves
        the duplicate and truncated detections this produces. Returns
        ``(char_offset, chunk_text)`` pairs where ``char_offset`` is the
        chunk's start position in the original text.
        r   r«   r>   N)r?   rC   rÍ   Úminrô   )r8   rÖ   r×   ÚchunksÚtext_lenÚstartÚ	candidateÚchunkÚlowÚhighÚmidÚendÚeffective_overlaps                r4   râ   z5_OPTIONAL_PresidioPIIMasking._split_text_for_analysis  s  € ð( ˆÜ˜d›)ˆØˆØ�hÒð ˜U UÐ-=Ñ%=Ð>ˆIÜ  Ó+Ð/?Ò?Ø!‘ð œs 9›~�T�Ø˜D’jØ ™:¨™>¨aÑ/�CÜ(¨°4°C¨Ó9Ð=MÒMØ!™à" Q™w˜ð ˜D“jð " $ 3˜�Øœ#˜e›*Ñ$ˆCØ�M‰M˜5 %˜.Ô)Ø�hŠØð ˆô !$ M´3°u³:À±?Ó CÐÜ˜ ™	 3Ð):Ñ#:Ó;ˆEð1 �hÓð2 ˆr3   rà   rá   c                 ó   ‡‡— g }t        | |d¬«      D ]V  \  \  }}}|D ]I  }|j                  d«      Š|j                  d«      Š‰�‰|z   |d<   ‰�‰|z   |d<   |j                  |«       ŒK ŒX dt        dt        t
        t        f   fd„}g }i }	t        ||¬«      D ]ž  }|j                  d«      Š|j                  d«      Š‰�‰€|j                  |«       Œ;|	j                  t        |j                  d	«      «      g «      }
t        ˆˆfd
„|
D «       «      rŒ{|
j                  ‰‰f«       |j                  |«       Œ  |j                  d„ ¬«       |S )u  
        Remap per-chunk analyzer offsets onto the original text and merge.

        A detection in an overlap region is reported by both neighbouring
        chunks, and a boundary entity can additionally be reported truncated by
        the chunk that saw only its head or tail. Same-entity-type detections
        with overlapping remapped spans are therefore resolved by keeping the
        longest span (highest score on ties) â€” mirroring the same-type conflict
        removal Presidio's AnalyzerEngine applies within a single call, and
        keeping overlapping spans from corrupting the numbered-token rewriter.
        Detections of DIFFERENT entity types may still overlap, exactly as in a
        single-call response. The merged list is sorted by position.
        T)Ústrictrù   rÿ   rÓ   r;   c                 óŽ   — | j                  d«      xs d}| j                  d«      xs d}||z
   | j                  d«      xs d fS )Nrù   r   rÿ   Úscoreg        ©rË   )rÓ   Ú
span_startÚspan_ends      r4   Ú	_priorityzN_OPTIONAL_PresidioPIIMasking._merge_chunked_analyze_results.<locals>._priorityj  sK   € Ø $§¡¨Ó 1Ò 6°QˆJØ"Ÿh™h u›oÒ2°ˆHØ Ñ+Ð,°·±¸Ó0AÒ0HÀSÐ.IÐJÐJr3   ©Úkeyr,   c              3   ó<   •K  — | ]  \  }}‰|k  xr |‰k  –— Œ y ­wrH   r2   )r™   Ú
kept_startÚkept_endÚitem_endÚ
item_starts      €€r4   r›   zN_OPTIONAL_PresidioPIIMasking._merge_chunked_analyze_results.<locals>.<genexpr>x  s)   øè ø€ ÒjÑG[ÀzÐS[�: Ñ(ÒB¨Z¸(Ñ-BÓBÑjùs   ƒc                 óV   — | j                  d«      xs d| j                  d«      xs dfS )Nrù   r   rÿ   r  )rb   s    r4   ú<lambda>zM_OPTIONAL_PresidioPIIMasking._merge_chunked_analyze_results.<locals>.<lambda>|  s%   €  1§5¡5¨£>Ò#6°Q¸¿¹¸e»Ò8IÈÐ"J€ r3   )ÚziprË   rÍ   r#   ÚtupleÚintÚfloatÚsortedrc   r0   rœ   Úsort)rà   rá   ÚremappedÚchar_offsetrÝ   ÚresultsrÓ   r  ÚmergedÚkept_spans_by_typeÚ
kept_spansr  r  s              @@r4   rç   z;_OPTIONAL_PresidioPIIMasking._merge_chunked_analyze_resultsM  sk  ù€ ð$ ˆÜ),¨[¸-ÐPTÔ)Uò 	&Ñ%Ñˆ[˜!˜gØò &�Ø!ŸX™X gÓ.�
ØŸ8™8 E›?�ØÐ)Ø$.°Ñ$<�D˜‘MØÐ'Ø"*¨[Ñ"8�D˜‘KØ—‘ Õ%ñ&ð	&ð	KÔ7ð 	K¼EÄ#ÄuÀ*Ñ<Mó 	Kð
 ˆØ$&ÐÜ˜8¨Ô3ò 
	 ˆDØŸ™ 'Ó*ˆJØ—x‘x “ˆHØÐ! XÐ%5Ø—‘˜dÔ#ØØ+×6Ñ6´s¸4¿8¹8ÀMÓ;RÓ7SÐUWÓXˆJÜÔjÐ_iÔjÔjØØ×Ñ˜z¨8Ð4Ô5Ø�M‰M˜$Õð
	 ð 	�‰ÑJˆÔKØˆr3   rÑ   c           
   ƒ   ó  K  — | j                  «       4 ƒd{  –—† }| j                  › d�}t        j                  d|«       ||dœ}|j	                  ||ddi¬«      4 ƒd{  –—† }|j
                  dk\  r6|j                  «       ƒ d{  –—† }t        d	|j
                  › d
|dd › �«      ‚t        |d|j                  j                  dd«      «      }d|vr-|j                  «       ƒ d{  –—† }t        d|› d|dd › d�«      ‚|j                  «       ƒ d{  –—† cddd«      ƒd{  –—†  cddd«      ƒd{  –—†  S 7 �Œ%7 ŒÞ7 Œ¹7 ŒZ7 Œ/7 Œ!7 Œ# 1 ƒd{  –—†7  sw Y   nxY wddd«      ƒd{  –—†7   y# 1 ƒd{  –—†7  sw Y   yxY w­w)z5POST to Presidio anonymize; returns parsed JSON body.NÚ	anonymizezMaking request to: %s)r8   Úanalyzer_resultsr·   r¸   r¹   r»   z"Presidio anonymizer returned HTTP z: r¼   r½   r¾   r¿   z4Presidio anonymizer returned non-JSON Content-Type 'rÀ   rÁ   )rŒ   rT   r   rÇ   rÈ   rÉ   r8   rv   rÊ   rº   rË   r@   )	ry   r8   rÑ   r‘   Úanonymize_urlÚanonymize_payloadrÏ   rÐ   r½   s	            r4   Ú_post_presidio_anonymizez5_OPTIONAL_PresidioPIIMasking._post_presidio_anonymize  s«  è ø€ ð ×-Ñ-Ó/÷ 	-ð 	-°7Ø&*×&GÑ&GÐ%HÈ	Ð#RˆMÜ ×&Ñ&Ð'>ÀÔNàØ$3ñ(Ðð —|‘|ØØ&Ø!Ð#5Ð6ð $ó ÷ -ð -ð Ø—?‘? cÒ)Ø'/§}¡}£×!6�JÜ#Ð&HÈÏÉÐHYÐY[Ð\fÐgkÐhkÐ\lÐ[mÐ$nÓoÐoÜ&-ØØ"Ø×$Ñ$×(Ñ(¨¸Ó<ó'�ð
 &¨\Ñ9Ø'/§}¡}£×!6�JÜ#ØNÈ|ÈnÐ\fÐgqÐrvÐsvÐgwÐfxÐxyÐzóð ð &Ÿ]™]›_×,÷%-÷ -ð -÷	-÷ 	-ò 	-ùð-øð "7øð "7øð -øð%-øð	-ø÷-÷ -ñ -ú÷	-÷ 	-÷ 	-÷ 	-ó 	-üsÖ   ‚F—D<˜F›AE2Á D?Á!E2Á$#EÂEÂA EÃ(EÃ),EÄEÄEÄE2Ä%EÄ&E2Ä*FÄ6E	Ä7FÄ?E2ÅEÅEÅEÅE2Å	FÅE	ÅEÅE	ÅE2Å FÅ+E.Å,FÅ2FÅ8E;Å9FÆ FÚredacted_textÚmasked_entity_countc                 ó”   — |j                  dg «      D ]/  }|j                  dd «      }|€Œ|j                  |d«      dz   ||<   Œ1 |d   S )Nr9   r,   r   r«   r8   r  )ry   r$  r%  rÓ   r,   s        r4   Ú#_finalize_presidio_anonymize_simplez@_OPTIONAL_PresidioPIIMasking._finalize_presidio_anonymize_simple¡  sd   € ð "×%Ñ% g¨rÓ2ò 	_ˆDØŸ(™( =°$Ó7ˆKØÑ&Ø3F×3JÑ3JÈ;ÐXYÓ3ZÐ]^Ñ3^Ð# KÒ0ð	_ð ˜VÑ$Ð$r3   c                 óö  — |}|€t        j                  d«       i }|j                  d«      si |d<   d|d   vri |d   d<   |d   d   }t        |d„ ¬«      }i }t	        |d¬«      D ]  \  }	}
|	||
d   |
d	   f<   Œ t        |«      D ]p  }
|
d   }|
d	   }|
d
   }d|› d�}|||f   }|j                  d«      r|d d › d|› d�}n|› d|› �}||| ||<   |d | |z   ||d  z   }|j                  |d«      dz   ||<   Œr |S )Nu‹   Presidio anonymize_text called without request_data â€” PII tokens cannot be stored per-request. This may indicate a missing caller update.Úmetadatarg   c                 ó   — | d   S )Nrù   r2   )Úxs    r4   r  z[_OPTIONAL_PresidioPIIMasking._finalize_presidio_anonymize_numbered_tokens.<locals>.<lambda>Ë  s
   € ÀaÈÁj€ r3   r	  r«   )rù   rù   rÿ   r,   ú<ú>éÿÿÿÿrÝ   r   )r   r´   rË   r  Ú	enumerateÚreversedr„   )ry   r8   rÑ   rŸ   r%  Únew_textrg   Úsorted_forwardÚseq_mapÚidxÚarrù   rÿ   r,   ÚreplacementÚseqs                   r4   Ú,_finalize_presidio_anonymize_numbered_tokenszI_OPTIONAL_PresidioPIIMasking._finalize_presidio_anonymize_numbered_tokens°  s‹  € ð ˆØÐÜ ×(Ñ(ð=ôð
 ˆLØ×Ñ 
Ô+Ø')ˆL˜Ñ$Ø˜|¨JÑ7Ñ7Ø57ˆL˜Ñ$ \Ñ2Ø(¨Ñ4°\ÑBˆ
ô !' Ñ<PÔ QˆØˆÜ  °qÔ9ò 	4‰GˆC�Ø03ˆG�R˜‘[ " U¡)Ð,Ò-ð	4ô ˜>Ó*ò 	[ˆBØ�w‘KˆEØ�U‘)ˆCØ˜]Ñ+ˆKØ˜k˜]¨!Ð,ˆKØ˜5 #˜,Ñ'ˆCØ×#Ñ# CÔ(Ø!,¨S¨bÐ!1Ð 2°!°C°5¸Ð:‘à!, ¨Q¨s¨eÐ4�Ø&*¨5° oˆJ�{Ñ#Ø  Ð'¨+Ñ5¸ÀÀ¸ÑFˆHØ/B×/FÑ/FÀ{ÐTUÓ/VÐYZÑ/ZÐ Ò,ð	[ð ˆr3   c              ƒ   ó¨  K  — 	 t        |t        «      rt        |«      dk(  r|S | j                  ||«      ƒ d{  –—† }|€t	        d«      ‚t        j                  d|«       |s| j                  ||«      S | j                  ||||«      S 7 ŒO# t        $ r;}t        |«      }d|v sd|v r‚ t	        dt        |«      j                  › �«      |‚d}~ww xY w­w)z`
        Send analysis results to the Presidio anonymizer endpoint to get redacted text
        r   Nz*Invalid anonymizer response: received Nonezredacted_text: %szInvalid anonymizer responsezPresidio anonymizer returnedz#Presidio PII anonymization failed: )rh   r:   r?   r#  rv   r   rÇ   r'  r8  r0   rÌ   r-   )	ry   r8   rÑ   rU   r%  rŸ   r$  r}   Ú	error_strs	            r4   Úanonymize_textz+_OPTIONAL_PresidioPIIMasking.anonymize_textâ  sì   è ø€ ð	]ä˜/¬4Ô0´S¸Ó5IÈQÒ5NØ�à)-×)FÑ)FÀtÈ_Ó)]×#]ˆMØÐ$ÜÐ LÓMÐMä ×&Ñ&Ð':¸MÔJá#Ø×?Ñ?ÀÐObÓcÐcà×DÑDØ�o |Ð5Hóð ð $^ùô ò 	]ô  # 1›vˆIØ,°	Ñ9Ð=[Ð_hÑ=hØÜÐAÄ$ÀqÃ'×BRÑBRÐASÐTÓUÐ[\Ð\ûð	]üsN   ‚C„B £C¤B ¹B	º:B Á4CÁ5B ÂCÂ	B Â	CÂ6C
Ã
CÃCc           
      ó   — | j                   s| j                  s|S t        |t        «      s|S g }| j                  D �cg c]  }t	        |dt        |«      «      ‘Œ }}|D ]¨  }|j                  d«      }t        |�t	        |d|«      n|«      }|r||v rŒ5| j                   rW|j                  d«      }d}	|�| j                   j                  |«      }	|	€| j                   j                  d«      }	|	�|�||	k  rŒ˜|j                  |«       Œª |S c c}w )zŠ
        Drop detections that fall below configured per-entity score thresholds
        or match an entity type in the deny list.
        rñ   r,   Nr  ÚALL)rZ   r[   rh   r:   rÊ   r0   rË   rÍ   )
ry   rÑ   Úfiltered_resultsr+  Údeny_list_stringsrÓ   r,   Ústr_entity_typer  Ú	thresholds
             r4   ræ   z<_OPTIONAL_PresidioPIIMasking.filter_analyze_results_by_score  s(  € ð ×-Ò-°d×6VÒ6VØ"Ð"ä˜/¬4Ô0Ø"Ð"àEGÐØIM×IiÑIiÖ#jÀA¤G¨A¨w¼¸A»Õ$?Ð#jÐÐ#jØ#ò 	*ˆDØŸ(™( =Ó1ˆKä!Ø>IÐ>U”˜ W¨kÔ:Ð[fóˆOñ ˜Ð2CÑCØà×-Ò-ØŸ™ Ó)�Ø �	ØÐ*Ø $× >Ñ >× BÑ BÀ;Ó O�IØÐ$Ø $× >Ñ >× BÑ BÀ5Ó I�IàÐ(Ø�}¨°	Ò(9Ø à×#Ñ# DÕ)ð+	*ð.  Ðùò1 $ks   ½Dc                 ó  — | j                   €yt        |t        «      ry|D ]\  }|j                  d«      }|sŒ|| j                   v sŒ&| j                   |   t        j
                  k(  sŒGt        || j                  ¬«      ‚ y)zE
        Raise an exception if blocked entities are detected
        Nr,   )r,   r°   )rX   rh   r¤   rË   r   r˜   r   r°   )ry   rÑ   ré   r,   s       r4   Ú,raise_exception_if_blocked_entities_detectedzI_OPTIONAL_PresidioPIIMasking.raise_exception_if_blocked_entities_detected.  sƒ   € ð ×#Ñ#Ð+Øä�o¤tÔ,ð à%ò 		ˆFØ Ÿ*™* ]Ó3ˆKâà $×":Ñ":Ò:¸t×?WÑ?WÐXcÑ?dÔhq×hwÑhwÓ?wÜ/Ø$/Ø'+×':Ñ':ôð ñ		r3   c              ƒ   óÈ  K  — t        j                  «       }d}d}i }d}		 | j                  �| j                  }
�n| j                  |||¬«      ƒ d{  –—† }t	        j
                  d|«       | j                  |¬«      }| j                  |¬«       | j                  |||||¬«      ƒ d{  –—† }|i }|dk(  r)t        |t        «      r|D �cg c]  }t        |«      ‘Œ }}n|	}| j                  | j                  ||||j                  «       t        j                  «       j                  «       t        j                  «       |z
  j                  «       |¬«       S |
d	   i }|dk(  r)t        |t        «      r|D �cg c]  }t        |«      ‘Œ }}n|	}| j                  | j                  ||||j                  «       t        j                  «       j                  «       t        j                  «       |z
  j                  «       |¬«       S 7 �Œ°7 �Œ[c c}w c c}w # t         $ r}d
}t#        |«      }	|‚d}~ww xY w# i }|dk(  r/t        |t        «      r!|D �cg c]  }t        |«      ‘Œ nc c}w }}n|	}| j                  | j                  ||||j                  «       t        j                  «       j                  «       t        j                  «       |z
  j                  «       |¬«       w xY w­w)zY
        Calls Presidio Analyze + Anonymize endpoints for PII Analysis + Masking
        NÚsuccessr¿   r¬   rÂ   rß   )r8   rÑ   rU   r%  rŸ   )rf   Úguardrail_json_responserŸ   Úguardrail_statusÚ
start_timeÚend_timeÚdurationr%  r8   Úguardrail_failed_to_respond)r   ÚnowrR   rÔ   r   rÇ   ræ   rC  r;  rh   r:   r¤   Ú:add_standard_logging_guardrail_information_to_request_datarf   Ú	timestampÚtotal_secondsrv   r0   )ry   r8   rU   rž   rŸ   rH  rÑ   rÉ   r%  Úexception_strr$  Úanonymized_textrF  rÓ   r}   s                  r4   Ú	check_piiz&_OPTIONAL_PresidioPIIMasking.check_piiG  sæ  è ø€ ô %ŸL™L›Nˆ
ØaeˆØ"+ˆØ57ÐØˆð6	Ø×&Ñ&Ð2Ø'+×'>Ñ'>’ð )-×(9Ñ(9ØØ$3Ø!-ð ):ó )÷ #�ô %×*Ñ*Ð+@À/ÔRð #'×"FÑ"FÐWfÐ"FÓ"g�ð
 ×AÑAÐRaÐAÔbð 04×/BÑ/BØØ$3Ø%5Ø(;Ø!-ð 0Có 0÷ *�ð 'ð LNÐ#Ø˜Ò"Ü˜o¬tÔ4ØFUÖ.V¸d¬t°D­zÐ.VÐ+Ñ.Và*7Ð'Ø×KÑKØ#'×#:Ñ#:Ø(?Ø)Ø!'Ø%×/Ñ/Ó1Ü!Ÿ™›×1Ñ1Ó3Ü"Ÿ,™,›.¨:Ñ5×DÑDÓFØ$7ð Lõ 	ð ! Ñ(ð LNÐ#Ø˜Ò"Ü˜o¬tÔ4ØFUÖ.V¸d¬t°D­zÐ.VÐ+Ñ.Và*7Ð'Ø×KÑKØ#'×#:Ñ#:Ø(?Ø)Ø!'Ø%×/Ñ/Ó1Ü!Ÿ™›×1Ñ1Ó3Ü"Ÿ,™,›.¨:Ñ5×DÑDÓFØ$7ð Lõ 	ðQ#ùð"*ûò( /WùÒ.Vøô ò 	Ø2ˆFÜ ›FˆMØˆGûð	ûð LNÐ#Ø˜Ò"Ü˜o¬tÔ4ØFUÖ.V¸d¬t°D­zÑ.VùÒ.VÐ+Ñ.Và*7Ð'Ø×KÑKØ#'×#:Ñ#:Ø(?Ø)Ø!'Ø%×/Ñ/Ó1Ü!Ÿ™›×1Ñ1Ó3Ü"Ÿ,™,›.¨:Ñ5×DÑDÓFØ$7ð Lõ 	üs’   ‚K" 1H ÁHÁAH Â)HÂ*H Â/K"Ã
HÃA:K"ÅH ÅK"Å5HÆA:K"ÈH ÈH È
K"È	H.ÈH)È)H.È.H1 È1KÉI ÉB KËK"Úuser_api_key_dictÚcacheÚdataÚ	call_typec           
   ƒ   óÀ  K  — | j                  |t        j                  ¬«      dur|S 	 |j                  dd«      }t	        j
                  d|«       | j                  |«      }|j                  dd«      }|€|S g }g }	t        |«      D ]ï  \  }
}|j                  dd«      }|€Œt        |t        «      rB|j                  | j                  || j                  ||¬«      «       |	j                  |
df«       Œmt        |t        «      sŒ~t        |«      D ]d  \  }}|j                  d	d«      }|€Œ|j                  | j                  || j                  ||¬«      «       |	j                  |
t        |«      f«       Œf Œñ t        j                   |Ž ƒ d{  –—† }t        |«      D ]ˆ  \  }}|	|   }t#        t        |d
   «      }
t#        t        dz  |d   «      }||
   j                  dd«      }|€ŒLt        |t        «      r|€	|||
   d<   Œgt        |t        «      sŒx|€Œ{|||
   d   |   d	<   ŒŠ t	        j
                  d|d   «       ||d<   |S 7 Œº# t$        $ r}|‚d}~ww xY w­w)a�  
        - Check if request turned off pii
            - Check if user allowed to turn off pii (key permissions -> 'allow_pii_controls')

        - Take the request data
        - Call /analyze -> get the results
        - Call /anonymize w/ the analyze results -> get the redacted text

        For multiple messages in /chat/completions, we'll need to call them in parallel.
        )rU  Ú
event_typeTÚcontent_safetyNzcontent_safety: %sÚmessagesÚcontent©r8   rU   rž   rŸ   r8   r   r«   ú.Presidio PII Masking: Redacted pii message: %s)Úshould_run_guardrailr   rI   rË   r   rÇ   Ú'get_presidio_settings_from_request_datar/  rh   r0   rÍ   rR  rU   r:   r  rk   rä   r   rv   )ry   rS  rT  rU  rV  rY  rž   rZ  ÚtasksÚtask_mappingsÚmsg_idxÚmr[  Úcontent_idxÚcÚtext_strÚ	responsesÚtask_idxrb   ÚmappingÚcontent_idx_optionalr}   s                         r4   Úasync_pre_call_hookz0_OPTIONAL_PresidioPIIMasking.async_pre_call_hookŽ  s¤  è ø€ ð, ×%Ñ%ØÜ.×7Ñ7ð &ó ð ñ	ð ˆKð:	Ø$(§H¡HÐ-=¸tÓ$DˆNÜ ×&Ñ&Ð';¸^ÔLØ%)×%QÑ%QÐRVÓ%WˆOØ"Ÿh™h z°4Ó8ˆHØÐØ�ØˆEØ:<ˆMä'¨Ó1ò J‘
�˜ØŸ%™% 	¨4Ó0�Ø�?ØÜ˜g¤sÔ+Ø—L‘LØŸ™Ø!(Ø-1×-BÑ-BØ,;Ø)-ð	 'ó ôð "×(Ñ(¨'°4¨Õ9Ü ¬Õ.Ü*3°GÓ*<ò J™˜ QØ#$§5¡5¨°Ó#6˜Ø#Ð+Ø$ØŸ™Ø ŸN™NØ%-Ø15×1FÑ1FØ0?Ø-1ð	 +ó ôð &×,Ñ,¨g´s¸;Ó7GÐ-HÕIñJðJô: &-§^¡^°UÐ%;×;ˆIô  )¨Ó3ò 
S‘�˜!Ø'¨Ñ1�Üœs G¨A¡JÓ/�Ü'+¬C°$©J¸À¹
Ó'CÐ$Ø" 7Ñ+×/Ñ/°	¸4Ó@�Ø�?ØÜ˜g¤sÔ+Ð0DÐ0LØ34�H˜WÑ% iÒ0Ü ¬Õ.Ð3GÑ3SØQR�H˜WÑ% iÑ0Ð1EÑFÀvÒNð
Sô !×&Ñ&Ð'WÐY]Ð^hÑYiÔjØ'ˆD�ÑØˆKð#  <ùô$ ò 	ØˆGûð	üs\   ‚&I©AI Á7IÁ8BI ÄB	I ÆI	ÆBI ÈI È/I ÉIÉ	I É	IÉIÉIÉIrz   ré   c                 óü   ‡ ‡‡‡— ddl m} ˆˆˆˆ fd„}	 t        j                  «       } |d¬«      5 }|j	                  |«      }|j                  «       cd d d «       S # 1 sw Y   y xY w# t        $ r
  |«       cY S w xY w)Nr   )ÚThreadPoolExecutorc                  ó<  •— t        j                  «       } 	 t        j                  | «       | j                  ‰j	                  ‰‰‰¬«      «      | j                  «        t        j                  d«       S # | j                  «        t        j                  d«       w xY w)z9Run the coroutine in a new event loop within this thread.)rz   ré   rV  N)rk   Únew_event_loopÚset_event_loopÚrun_until_completeÚasync_logging_hookrŽ   )Únew_looprV  rz   ré   ry   s    €€€€r4   Úrun_in_new_loopzB_OPTIONAL_PresidioPIIMasking.logging_hook.<locals>.run_in_new_loopë  s~   ø€ ä%×4Ñ4Ó6ˆHð-Ü×&Ñ& xÔ0Ø×2Ñ2Ø×+Ñ+°6À&ÐT]Ð+Ó^óð —‘Ô Ü×&Ñ& tÕ,øð —‘Ô Ü×&Ñ& tÕ,ús   —7A4 Á4'Br«   )Úmax_workers)Úconcurrent.futuresrm  rk   r‡   Úsubmitré   ÚRuntimeError)	ry   rz   ré   rV  rm  rt  rÝ   ÚexecutorÚfutures	   ````     r4   Úlogging_hookz)_OPTIONAL_PresidioPIIMasking.logging_hookè  so   û€ Ý9÷
	-ð	%ä×(Ñ(Ó*ˆAñ $°Ô2ð '°hØ (§¡°Ó @�Ø—}‘}“÷'÷ 'ò 'ûô ò 	%á"Ó$Ò$ð	%ús.   ”A( ±!AÁ	A( ÁA%Á!A( Á%A( Á(A;Á:A;c           
   ƒ   ó   K  — |dk(  s|dk(  �ru|j                  dd«      }g }g }|€||fS | j                  |«      }t        |«      D ]Û  \  }}	|	j                  dd«      }
|
€Œt        |
t        «      r8|j                  | j                  |
d||¬«      «       |j                  |df«       Œct        |
t        «      sŒtt        |
«      D ]Z  \  }}|j                  dd«      }|€Œ|j                  | j                  |d||¬«      «       |j                  |t        |«      f«       Œ\ ŒÝ t        j                  |Ž ƒ d{  –—† }t        |«      D ]ˆ  \  }}||   }t        t        |d	   «      }t        t        dz  |d
   «      }||   j                  dd«      }
|
€ŒLt        |
t        «      r|€	|||   d<   Œgt        |
t        «      sŒx|€Œ{|||   d   |   d<   ŒŠ t        j                  d|«       ||d<   t        |t        «      rI|j                  r=t        |j                  d	   t         «      s | j#                  ||d¬«      ƒ d{  –—†  ||fS | j%                  |«      r*| j'                  t        t(        |«      |d¬«      ƒ d{  –—†  ||fS 7 �ŒN7 ŒJ7 Œ­w)zV
        Masks the input and output before logging to langfuse, datadog, etc.
        Ú
completionÚacompletionrZ  Nr[  Fr\  r8   r   r«   r]  Úmask©rÏ   rŸ   Úmode)rË   r_  r/  rh   r0   rÍ   rR  r:   r  rk   rä   r   r   rÇ   r(   Úchoicesr%   Ú_process_response_for_piiÚ_is_anthropic_message_responseÚ#_process_anthropic_response_for_piir¤   )ry   rz   ré   rV  rZ  r`  ra  rž   rb  rc  r[  rd  re  rf  rg  rh  rb   ri  rj  s                      r4   rr  z/_OPTIONAL_PresidioPIIMasking.async_logging_hook  sÚ  è ø€ ð ˜Ò$¨	°]Ó(BØ+1¯:©:°jÀ$Ó+GˆHØˆEØ:<ˆMàÐØ˜v�~Ð%à%)×%QÑ%QÐRXÓ%YˆOä'¨Ó1ò J‘
�˜ØŸ%™% 	¨4Ó0�Ø�?ØÜ˜g¤sÔ+Ø—L‘LØŸ™Ø!(Ø-2Ø,;Ø)/ð	 'ó ôð "×(Ñ(¨'°4¨Õ9Ü ¬Õ.Ü*3°GÓ*<ò J™˜ QØ#$§5¡5¨°Ó#6˜Ø#Ð+Ø$ØŸ™Ø ŸN™NØ%-Ø16Ø0?Ø-3ð	 +ó ôð &×,Ñ,¨g´s¸;Ó7GÐ-HÕIñJðJô: &-§^¡^°UÐ%;×;ˆIô  )¨Ó3ò 
S‘�˜!Ø'¨Ñ1�Üœs G¨A¡JÓ/�Ü'+¬C°$©J¸À¹
Ó'CÐ$Ø" 7Ñ+×/Ñ/°	¸4Ó@�Ø�?ØÜ˜g¤sÔ+Ð0DÐ0LØ34�H˜WÑ% iÒ0Ü ¬Õ.Ð3GÑ3SØQR�H˜WÑ% iÑ0Ð1EÑFÀvÒNð
Sô !×&Ñ&Ð'WÐYaÔbØ!)ˆF�:Ñô ˜6¤=Ô1Ø—N’NÜ" 6§>¡>°!Ñ#4Ô6FÔGà×4Ñ4¸fÐSYÐ`fÐ4Óg×gÐgð �vˆ~Ðð ×4Ñ4°VÔ<Ø×>Ñ>Ü!¤$¨Ó/Ø!'Øð ?ó ÷ ð ð �vˆ~Ðð?  <ùð. høðúsK   ‚B7JÂ:A?JÄ9JÄ:BJÇJÇA;JÈ?J
É >JÉ>JÉ?	JÊ
JÊJrÏ   c              ƒ   óŠ  K  — t        j                  d| j                  t        |«      «       | j                  du rV| j                  |«      r*| j                  t        t        |«      |d¬«      ƒ d{  –—† S | j                  ||¬«      ƒ d{  –—† S | j                  du rt        j                  du r|S t        |t        «      r;t        |j                  d   t        «      s| j                  ||d	¬«      ƒ d{  –—†  |S | j                  |«      r*| j                  t        t        |«      |d	¬«      ƒ d{  –—†  |S 7 ŒÉ7 Œ°7 ŒG7 Œ­w)
ze
        Output parse the response object to replace the masked tokens with user sent values
        z?PII Masking Args: self.output_parse_pii=%s; type of response=%sTr  r€  N)rÏ   rŸ   Fr   Úunmask)r   rÇ   rU   rÌ   rV   r„  r…  r   r¤   Ú_mask_output_responserƒ   rh   r(   r‚  r%   rƒ  )ry   rU  rS  rÏ   s       r4   Úasync_post_call_success_hookz9_OPTIONAL_PresidioPIIMasking.async_post_call_success_hookP  s[  è ø€ ô 	×"Ñ"ØMÈt×OdÑOdÔfjÐksÓftô	
ð ×Ñ 4Ñ'Ø×2Ñ2°8Ô<Ø!×EÑEÜ!¤$¨Ó1ÀÈ6ð Fó ÷ ð ð ×3Ñ3¸XÐTXÐ3ÓY×YÐYà× Ñ  EÑ)¬g×.FÑ.FÈ%Ñ.OØˆOä�h¤Ô.´zØ×Ñ˜QÑÔ!1ô8
ð ×0Ñ0Ø!Ø!Øð 1ó ÷ ð ð ˆð	 ×0Ñ0°Ô:Ø×:Ñ:Üœd HÓ-¸DÀxð ;ó ÷ ð ð ˆð)øð ZøðøðúsI   ‚A/EÁ1D;Á2EÂD=ÂA*EÃ7D?Ã8<EÄ4EÄ5EÄ=EÄ?EÅErg   c           
      ód  — |j                  «       D ]œ  \  }}|| v r| j                  ||«      } Œt        dt        |«      dz  «      }t	        t        dt        | «      t        |«      z
  «      t        | «      «      D ]1  }| |d }|j                  |«      sŒt        |«      |k\  sŒ)| d| |z   }  Œœ Œž | S )aå  
        Replace PII tokens in *text* with their original values.

        Includes a fallback for tokens that were truncated by ``max_tokens``:
        if the *end* of ``text`` matches the *beginning* of a token and the
        overlap is long enough, the truncated suffix is replaced with the
        original value.  The minimum overlap length is
        ``min(20, len(token) // 2)`` to reduce the risk of false positives
        when multiple tokens share a common prefix.
        é   r>   r   N)r9   Úreplacerö   r?   Úrangerô   r…   )r8   rg   ÚtokenÚoriginal_textÚmin_overlapÚiÚsubs          r4   Ú_unmask_pii_textz-_OPTIONAL_PresidioPIIMasking._unmask_pii_textu  sº   € ð %/×$4Ñ$4Ó$6ò 	Ñ ˆE�=Ø˜‰}Ø—|‘| E¨=Ó9‘ô " "¤c¨%£j°A¡oÓ6�Üœs 1¤c¨$£i´#°e³*Ñ&<Ó=¼sÀ4»yÓIò �AØ˜q˜r˜(�CØ×'Ñ'¨Õ,´°S³¸[Ó1HØ# B Q˜x¨-Ñ7˜Ùñ	ð	ð ˆr3   c                 ó�   — t        | t        «      xr5 | j                  d«      dk(  xr t        | j                  d«      t        «      S )z:Check if the response is an Anthropic native message dict.rÌ   r±   r[  )rh   r¤   rË   r:   )rÏ   s    r4   r„  z;_OPTIONAL_PresidioPIIMasking._is_anthropic_message_response�  sA   € ô �x¤Ó&ò :Ø—‘˜VÓ$¨	Ñ1ò:ä˜8Ÿ<™<¨	Ó2´DÓ9ð	
r3   r�  )r  r‡  c              ƒ   ó  K  — |r|j                  d«      xs i ni }|j                  di «      }|s|dk(  rt        j                  d«       | j                  |xs i «      }|j                  d«      }t	        |t
        «      s|S |D ]|  }t	        |t        «      r|j                  d«      dk7  rŒ(|j                  d«      }	|	€Œ<|dk(  r| j                  |	|«      |d<   ŒW|d	k(  sŒ]| j                  |	d
||¬«      ƒ d{  –—† |d<   Œ~ |S 7 Œ­w)z‰
        Process an Anthropic native message dict for PII masking/unmasking.
        Handles content blocks with type == "text".
        r)  rg   r‡  z7No pii_tokens in metadata for Anthropic response unmaskr[  rÌ   r8   Nr  Fr\  )	rË   r   rÇ   r_  rh   r:   r¤   r“  rR  )
ry   rÏ   rŸ   r�  r)  rg   rž   r[  ÚblockÚ
text_values
             r4   r…  z@_OPTIONAL_PresidioPIIMasking._process_anthropic_response_for_piiš  s  è ø€ ñ CO˜<×+Ñ+¨JÓ7Ò=¹2ÐTVˆØ$ŸL™L¨°rÓ:ˆ
Ù˜d hÒ.Ü ×&Ñ&Ð'`ÔaØ!%×!MÑ!MÈlÒN`Ð^`Ó!aˆà!Ÿ™ iÓ0ˆÜ˜'¤4Ô(ØˆOàò 	ˆEÜ˜e¤TÔ*¨e¯i©i¸Ó.?À6Ò.IØØŸ™ 6Ó*ˆJØÐ!ØØ�xÒØ $× 5Ñ 5°jÀ*Ó M��f’Ø˜“Ø&*§n¡nØ#Ø%*Ø$3Ø!-ð	 '5ó '÷ !��f’ð	ð  ˆð!ús   ‚CDÃ!DÃ9DÃ:Dc              ƒ   ó  K  — |r|j                  d«      xs i ni }|j                  di «      }|s|dk(  rt        j                  d«       | j                  |xs i «      }|j                  D �]  }t        |dd«      }|€Œt        |dd«      }	t        |	t        «      rE|dk(  r| j                  |	|«      |_	        n¥|dk(  r | j                  |	d	||¬
«      ƒ d{  –—† |_	        n}t        |	t        «      rm|	D ]h  }
t        |
t        «      sŒ|
j                  d«      }|€Œ(|dk(  r| j                  ||«      |
d<   ŒC|dk(  sŒI| j                  |d	||¬
«      ƒ d{  –—† |
d<   Œj t        |dd«      }|r†|D ]�  }t        |dd«      }|sŒt        |d«      sŒ |j                  }t        |t        «      sŒ=|dk(  r| j                  ||«      |_        ŒZ|dk(  sŒ`| j                  |d	||¬
«      ƒ d{  –—† |_        Œƒ t        |dd«      }|s�Œ™t        |d«      s�Œ§|j                  }t        |t        «      s�ŒÅ|dk(  r| j                  ||«      |_        �Œã|dk(  s�Œê| j                  |d	||¬
«      ƒ d{  –—† |_        �Œ |S 7 �Œ¦7 �Œ+7 Œš7 Œ­w)zt
        Helper to recursively process a ModelResponse for PII.
        Handles all choices and tool calls.
        r)  rg   r‡  uE   No pii_tokens found in request_data['metadata'] â€” nothing to unmaskr±   Nr[  r  Fr\  r8   Ú
tool_callsÚfunctionÚ	argumentsÚfunction_call)rË   r   rÇ   r_  r‚  rÊ   rh   r0   r“  r[  rR  r:   r¤   Úhasattrr›  )ry   rÏ   rŸ   r�  r)  rg   rž   Úchoicer±   r[  rÓ   r—  r™  Ú	tool_callrš  Úargsrœ  s                    r4   rƒ  z6_OPTIONAL_PresidioPIIMasking._process_response_for_piiÀ  s»  è ø€ ñ CO˜<×+Ñ+¨JÓ7Ò=¹2ÐTVˆØ$ŸL™L¨°rÓ:ˆ
Ù˜d hÒ.Ü ×&Ñ&Ð'nÔoØ!%×!MÑ!MÈlÒN`Ð^`Ó!aˆà×&Ñ&ó A	ˆFÜ˜f i°Ó6ˆGØˆØô ˜g y°$Ó7ˆGÜ˜'¤3Ô'Ø˜8Ò#Ø&*×&;Ñ&;¸GÀZÓ&P�G•OØ˜V’^Ø,0¯N©NØ$Ø).Ø(7Ø%1ð	 -;ó -÷ '�G•Oô ˜G¤TÔ*Ø#ò �DÜ% d¬DÔ1Ø Ø!%§¡¨&Ó!1�JØ!Ð)Ø Ø˜xÒ'Ø'+×'<Ñ'<¸ZÈÓ'T˜˜VšØ ›Ø-1¯^©^Ø!+Ø-2Ø,;Ø)5ð	 .<ó .÷ (˜˜Všðô" ! ¨,¸Ó=ˆJÙØ!+ò "�IÜ& y°*¸dÓC�HÚ¤G¨H°kÕ$BØ'×1Ñ1˜Ü% d¬CÕ0Ø# xÒ/Ø59×5JÑ5JÈ4ÐQ[Ó5\ Õ 2Ø!%¨£Ø;?¿>¹>Ø)-Ø5:Ø4CØ1=ð	 <Jó <"÷ 6" Õ 2ð"ô  $ G¨_¸dÓCˆMÛ¤¨¸Ö!DØ$×.Ñ.�Ü˜d¤CÖ(Ø˜xÒ'Ø26×2GÑ2GÈÈjÓ2Y˜Ö/Ø œØ8<¿¹Ø!%Ø-2Ø,;Ø)5ð	 9Gó 9÷ 3˜Ö/ðyA	ðD ˆðo'ùð (ùð$6"øð3ús   ‚CJÃI<ÃA$JÄ<JÅI?Å,JÆJÆJÆ,"JÇJÇ'JÇ(JÈJÈJÈ0#JÉJÉ-JÉ.JÉ?JÊJÊJc              ƒ   ó’   K  — t        |t        «      s|S t        |t        «      r|S | j                  ||d¬«      ƒ d{  –—†  |S 7 Œ­w)zL
        Apply Presidio masking on model responses (non-streaming).
        r  r€  N)rh   r(   r)   rƒ  )ry   rÏ   rŸ   s      r4   rˆ  z2_OPTIONAL_PresidioPIIMasking._mask_output_response  s[   è ø€ ô ˜(¤MÔ2ØˆOô �hÔ 3Ô4ØˆOà×,Ñ,ØØ%Øð -ó 
÷ 	
ð 	
ð
 ˆð	
ús   ‚<A¾A¿Ac                óÌ  K  — ddl m} ddlm} ddlm} g }d}	 |2 3 d{  –—† }t        |t        «      r|r|­–— Œ!|j                  |«       Œ3t        |t        «      r|­–— ŒI|r-t        j                  dt        |«      «       |D ]  }	|	­–— Œ	 g }d}|­–— Œ€7 Œ{6 |rt        j                  d	«       y|st        j                  d
«       y |||j                  d«      ¬«      }
t        |
|«      s|D ]  }|­–— Œ	 y| j                  |
|d¬«      ƒ d{  –—†7    ||
«      }|­–— y# t        $ r,}t        j                   d|«       |D ]  }|­–— Œ	 Y d}~yd}~ww xY w­w)zGApply Presidio masking to streaming output (apply_to_output=True path).r   ©Ú#convert_model_response_to_streaming©Ústream_chunk_builder©r(   FNz°Presidio apply_to_output: mixed stream detected (ModelResponseStream + unknown event). Flushing %d buffered chunks without PII masking and switching to transparent passthrough.Tz›Presidio apply_to_output: streaming response contained unknown event objects (e.g. /v1/responses events). Output PII masking was skipped for this response.z¸Presidio apply_to_output: streaming response contained no ModelResponseStream chunks (e.g. raw SSE bytes or an empty upstream stream). Output PII masking was skipped for this response.rZ  ©r÷   rZ  r  r€  z&Error masking streaming PII output: %s)Ú)litellm.llms.base_llm.base_model_iteratorr¤  Úlitellm.mainr¦  Úlitellm.types.utilsr(   rh   r)   rÍ   Úbytesr   r´   r?   rË   rƒ  rv   rÃ   )ry   rÏ   rŸ   r¤  r¦  r(   Ú
all_chunksÚ'passthrough_due_to_unknown_stream_shaperû   Úbuffered_chunkÚassembled_model_responseÚmock_response_streamr}   s                r4   Ú_stream_apply_output_maskingz9_OPTIONAL_PresidioPIIMasking._stream_apply_output_masking*  s™  è ø€ õ	
õ 	6Ý5à02ˆ
Ø27Ð/ð;	Ø'÷  ð  �eÜ˜eÔ%8Ô9Ù>Ø#œà"×)Ñ)¨%Õ0Ü ¤uÔ-Ø“KØá!ô -×4Ñ4ðxä 
›Oôð
 /9ò 1˜NØ"0Ô0ð1à%'˜
Ø>BÐ;Ø”Kð/ ø˜xñ0 7Ü$×,Ñ,ðeôð ÙÜ$×,Ñ,ð ôð á';À:ÐXd×XhÑXhÐisÓXtÔ'uÐ$äÐ6¸ÔFØ'ò  �EØ”Kð àà×0Ñ0Ø1Ø)Øð 1ó ÷ ñ ñ +NÐNfÓ*gÐ Ø&Ô&øäò 	Ü ×&Ñ&Ð'OÐQRÔSØ#ò �Ø”õûð	üs   ‚E$šD, œB B¡B¤A8D, ÂBÂD, Â6E$Â7D, ÃE$Ã1D, Ä E$ÄD, ÄDÄD, Ä+E$Ä,	E!Ä5"EÅE$ÅE!Å!E$rû   c                 ó$  — 	 | j                  d«      }g }|j                  d«      D �]  }|j                  d«      }|j	                  d«      rÝ|dk7  rØ|dd  }	 t        j                  |«      }t        |t        «      r|j                  d«      nd }t        |t        «      rŠ|j                  d«      d	k(  rv|j                  d«      d
k(  rbt        |j                  d«      t        «      rCt        j                  |d   |«      }||d   k7  r"||d   d<   dt        j                  |d¬«      z   }|j#                  |«       �Œ dj%                  |«      j'                  d«      S # t        $ r | cY S w xY w# t
        j                  t        t         f$ r Y Œew xY w)Nr=   ú
úzdata: zdata: [DONE]é   ÚdeltarÌ   Úcontent_block_deltaÚ
text_deltar8   F)Úensure_ascii)ÚdecodeÚUnicodeDecodeErrorÚsplitÚrstripr…   r@   Úloadsrh   r¤   rË   r0   rE   r“  rA   rw   ÚKeyErrorÚ	TypeErrorrÍ   ÚjoinrB   )	rû   rg   r8   Úresult_linesÚlineÚraw_jsonÚeventr·  Úunmaskeds	            r4   Ú_unmask_sse_bytes_chunkz4_OPTIONAL_PresidioPIIMasking._unmask_sse_bytes_chunku  sw  € ð	ØŸ,™, wÓ/ˆDð *,ˆØ—J‘J˜tÓ$ó 	&ˆDØ—;‘;˜tÓ$ˆDØ�‰˜xÔ(¨T°^Ò-CØ  ˜8�ðÜ ŸJ™J xÓ0�EÜ2<¸UÄDÔ2I˜EŸI™I gÔ.Èt�Eä" 5¬$Ô/Ø!ŸI™I fÓ-Ð1FÒFØ!ŸI™I fÓ-°Ò=Ü& u§y¡y°Ó'8¼#Ô>ä#?×#PÑ#PÐQVÐW]ÑQ^Ð`jÓ#k˜Ø# u¨V¡}Ò4Ø5=˜E '™N¨6Ñ2Ø#+¬d¯j©j¸ÈUÔ.SÑ#S˜Dð ×Ñ Ö%ð'	&ð* �y‰y˜Ó&×-Ñ-¨gÓ6Ð6øô3 "ò 	ØŠLð	ûô* ×,Ñ,¬h¼	ÐBò Ùðús$   ‚E ÁCE.ÅE+Å*E+Å.FÆFc                 ó¨  — t        |dd«      }|€yt        |dd«      xs g }|D ]¬  }t        |dd«      xs g }|D ]”  }t        |t        «      r9t        |j                  d«      t        «      sŒ3| j                  |d   |«      |d<   ŒLt        |d«      sŒYt        |j                  t        «      sŒt| j                  |j                  |«      |_        Œ– Œ® y)a‘  
        Unmask PII tokens in-place for a ``response.completed`` Responses API event.

        The chunk carries a ``response`` attribute (ResponsesAPIResponse) whose
        ``output`` list holds message items.  Each item has a ``content`` list of
        blocks; text blocks expose a ``.text`` string attribute.  We walk the tree
        and replace every PII token with its original value.
        rÏ   NÚoutputr[  r8   )rÊ   rh   r¤   rË   r0   r“  r�  r8   )ry   rû   rg   Úresponse_objrÊ  Úoutput_itemr[  Úcontent_blocks           r4   Ú%_unmask_responses_api_completed_chunkzB_OPTIONAL_PresidioPIIMasking._unmask_responses_api_completed_chunk”  sÖ   € ô & e¨Z¸Ó>ˆØÐØä ¨h¸Ó=ÒCÀˆØ!ò 	_ˆKÜ˜k¨9°dÓ;ÒA¸rˆGØ!(ò _�Ü˜m¬TÔ2Ü! -×"3Ñ"3°FÓ";¼SÕAØ04×0EÑ0EÀmÐTZÑF[Ð]gÓ0h˜ fÒ-Ü˜]¨FÕ3¼
À=×CUÑCUÔWZÕ8[Ø)-×)>Ñ)>¸}×?QÑ?QÐS]Ó)^�MÕ&ñ_ñ	_r3   c                ó4  K  — ddl m} ddlm} ddlm} |r|j                  d«      xs i ni }|j                  di «      }g }d}		 |2 3 d{  –—† }
t        |
t        «      r|	r|
­–— Œ!|j                  |
«       Œ3t        |
t        «      r|r| j                  |
|«      ­–— n|
­–— Œa|r|	s|D ]  }|­–— Œ	 g }t        |
d	d«      }|d
k(  r|r| j                  |
|«       d}	|
­–— Œ¡7 Œœ6 |	ry|sy |||j                  d«      ¬«      }t        ||«      s|D ]  }
|
­–— Œ	 y| j                  ||«       | j                  ||d¬«      ƒ d{  –—†7    ||«      }|­–— y# t         $ r,}t#        j$                  d|«       |D ]  }
|
­–— Œ	 Y d}~yd}~ww xY w­w)zEApply PII unmasking to streaming output (output_parse_pii=True path).r   r£  r¥  r§  r)  rg   FNrÌ   zresponse.completedTrZ  r¨  r‡  r€  z%Error in PII streaming processing: %s)r©  r¤  rª  r¦  r«  r(   rË   rh   r)   rÍ   r¬  rÈ  rÊ   rÎ  Ú_preserve_usage_from_last_chunkrƒ  rv   r   rÃ   )ry   rÏ   rŸ   r¤  r¦  r(   r)  rg   Úremaining_chunksÚsaw_non_chat_chunkrû   r¯  Ú
chunk_typer°  r±  r}   s                   r4   Ú_stream_pii_unmaskingz2_OPTIONAL_PresidioPIIMasking._stream_pii_unmasking«  sÔ  è ø€ õ	
õ 	6Ý5áBN˜<×+Ñ+¨JÓ7Ò=¹2ÐTVˆØ,4¯L©L¸ÀrÓ,Jˆ
à68ÐØ"Ðð8	Ø'÷  ð  �eÜ˜eÔ%8Ô9Ù)Ø#œà(×/Ñ/°Õ6Ü ¤uÔ-Ù!Ø"×:Ñ:¸5À*ÓMÔMà#›Øñ
 (Ñ0BØ.>ò 1˜NØ"0Ô0ð1à+-Ð(Ü!(¨°¸Ó!=�JØ!Ð%9Ò9¹jØ×BÑBÀ5È*ÔUØ)-Ð&Ø”Kð1 ø˜xñ4 "Øá#Øá.BØ'°,×2BÑ2BÀ:Ó2Nô/Ð$ô Ð6¸ÔFØ-ò  �EØ”Kð àà×0Ñ0Ð1IÐK[Ô\à×0Ñ0Ø1Ø)Øð 1ó ÷ ñ ñ +NÐNfÓ*gÐ Ø&Ô&øäò 	Ü ×&Ñ&Ð'NÐPQÔRØ)ò �Ø”õûð	üs…   ‚AFÁE  ÁC*ÁC(ÁC*ÁBE  Ã(C*Ã*E  Ã-FÃ.E  Ã0FÃ11E  Ä"FÄ#)E  ÅEÅE  ÅFÅ 	FÅ)"FÆFÆFÆFc                ó„  K  — | j                   r!| j                  ||«      2 3 d{  –—† }|­–— Œ|r|j                  d«      xs i ni }|j                  di «      }|s|rt        j                  d«       | j
                  r|s|2 3 d{  –—† }|­–— Œ| j                  ||«      2 3 d{  –—† }|­–— Œ7 ŒŽ6 y7 Œ/6 y7 Œ6 y­w)a&  
        Process streaming response chunks to unmask PII tokens when needed.

        Note: the return type includes `bytes` because Anthropic native SSE
        streaming sends raw bytes chunks that pass through untransformed.
        The base class declares ModelResponseStream only.
        Nr)  rg   zCNo pii_tokens in request_data['metadata'] for streaming unmask path)rV   r²  rË   r   rÇ   rU   rÔ  )ry   rS  rÏ   rŸ   rû   r)  rg   s          r4   Ú'async_post_call_streaming_iterator_hookzD_OPTIONAL_PresidioPIIMasking.async_post_call_streaming_iterator_hookö  sÖ   è ø€ ð ×ÒØ#×@Ñ@ÀÈ<ÓX÷ ð �eØ”ñ CO˜<×+Ñ+¨JÓ7Ò=¹2ÐTVˆØ$ŸL™L¨°rÓ:ˆ
Ù™lÜ ×&Ñ&Ð'lÔmØ×%Ò%©*Ø'÷ ð �eØ”ð  ×5Ñ5°hÀÓM÷ 	ð 	�%ØŒKðøÐXàðø˜xàð	øÑMùsn   ‚C ¡B6¥B4¦B6©AC ÂB:ÂB8Â	B:ÂC Â%B>Â)B<Â*B>Â-C Â4B6Â6C Â8B:Â:C Â<B>Â>C r°  r÷   c                 óf   — t        | dd«      s$|r!t        |d   dd«      }|rt        | d|«       yyyy)zLCopy usage metadata from the last chunk when stream_chunk_builder misses it.ÚusageNr.  )rÊ   Úsetattr)r°  r÷   Úlast_chunk_usages      r4   rÐ  z<_OPTIONAL_PresidioPIIMasking._preserve_usage_from_last_chunk  sF   € ô Ð/°¸$Ô?ÁFÜ&-¨f°R©j¸'À4Ó&HÐÙÜÐ0°'Ð;KÕLð  ð EKÐ?r3   c                 óv   — d|v r5|j                  dd «      }|€y |j                  d«      }|rt        di |¤Ž}|S y )Nr)  Úguardrail_configr2   )rË   r!   )ry   rU  Ú	_metadataÚ_guardrail_configÚ_presidio_configs        r4   r_  zD_OPTIONAL_PresidioPIIMasking.get_presidio_settings_from_request_data  sN   € Ø˜ÑØ#Ÿx™x¨
°DÓ9ˆIØÐ ØØ'0§}¡}Ð5GÓ'HÐÙ Ü*BÑ*WÐEVÑ*WÐ Ø'Ð'àr3   c                 ó†   — 	 t        j                  |«       t        j                  rt	        |«       y y # t
        $ r Y y w xY wrH   )r   rÇ   rƒ   Úset_verboseÚprintrv   )ry   Úprint_statements     r4   Úprint_verbosez*_OPTIONAL_PresidioPIIMasking.print_verbose+  s=   € ð	Ü ×&Ñ& Ô7Ü×"Ò"Ü�oÕ&ð #øäò 	Ùð	ús   ‚04 ´	A ¿A Úinputsr   Ú
input_type)ÚrequestrÏ   Úlogging_objÚLiteLLMLoggingObjc              ƒ   ó€  K  — |j                  dg «      }|r|j                  d«      xs i ni }|j                  di «      }g }|dk(  r+|r)|D ]#  }	|j                  | j                  |	|«      «       Œ% nC|D ]>  }	| j                  |	| j                  d|xs i ¬«      ƒ d{  –—† }
|j                  |
«       Œ@ ||d<   |S 7 Œ­w)a  
        UI will call this function to check:
            1. If the connection to the guardrail is working
            2. When Testing the guardrail with some text, this function will be called with the input text and returns a text after applying the guardrail
        Útextsr)  rg   rÏ   Nr\  )rË   rÍ   r“  rR  rU   )ry   rå  rŸ   ræ  rè  rë  r)  rg   Ú	new_textsr8   Úmodified_texts              r4   Úapply_guardrailz,_OPTIONAL_PresidioPIIMasking.apply_guardrail3  sæ   è ø€ ð —z‘z '¨2Ó.ˆñ CO˜<×+Ñ+¨JÓ7Ò=¹2ÐTVˆØ$ŸL™L¨°rÓ:ˆ
àˆ	Ø˜Ò#©
Øò J�Ø× Ñ  ×!6Ñ!6°t¸ZÓ!HÕIñJð ò 0�Ø&*§n¡nØØ%)×%:Ñ%:Ø$(Ø!-Ò!3°ð	 '5ó '÷ !�ð × Ñ  Õ/ð0ð $ˆˆw‰Øˆð!ús   ‚BB>ÂB<ÂB>Úlitellm_paramsc                 ó,  •— t         ‰| �  |«       |j                  r|j                  | _        |j                  r|j                  | _        |j                  r|j                  | _        |j
                  �!| j                  |j
                  «      | _        yy)z@
        Update the guardrails litellm params in memory
        N)rd   Úupdate_in_memory_litellm_paramsrX   rZ   r[   r\   ri   )ry   rï  r~   s     €r4   rñ  z<_OPTIONAL_PresidioPIIMasking.update_in_memory_litellm_paramsW  s‡   ø€ ô 	‰Ñ/°Ô?Ø×-Ò-Ø'5×'IÑ'IˆDÔ$Ø×3Ò3Ø-;×-UÑ-UˆDÔ*Ø×5Ò5Ø/=×/YÑ/YˆDÔ,Ø×;Ñ;ÐGð 6:×5TÑ5TØ×@Ñ@ó6ˆDÕ2ð Hr3   )FNNNFFNNNNNNN)NN)r;   NrH   )Sr-   r.   r/   Úuser_api_key_cacherF   r:   r0   r1   Úclassmethodr   rP   r³   r7   r¤   r    r   r  r  re   rx   r   r   r‰   rŠ   rŒ   r’   r•   r�   r!   r"   r©   r#   rÔ   rÅ   rk   rí   rã   Ústaticmethodri   r   r  râ   rç   r#  r'  r   r8  r;  ræ   rC  rR  r   r   rk  r{  rr  r(   r&   r'   r‰  r“  Úobjectr„  r
   r…  rƒ  rˆ  r)   r¬  r²  rÈ  rÎ  rÔ  rÖ  rÐ  r_  rä  r   r   rî  r   rñ  Ú__classcell__)r~   s   @r4   rE   rE   S   sT  ø… ØÐØ+/Ð˜˜S™	 DÑ(Ó/àð
¨$Ð/BÑ*Cò 
ó ð
ð #Ø@DØ15Ø37Ø(-Ø %Ø26Ø$(ØKOØ(,ØMQØHLØ8<ñN
àðN
ð 7¸Ñ=ðN
ð %(¨$¡Jð	N
ð
 '*¨D¡jðN
ð  ™+ðN
ð ðN
ð &)¨4¡ZðN
ð ˜T‘kðN
ð " -°#Ñ"5°yÐ"@ÑAÀDÑHðN
ð  ™:ðN
ð $(¨¸Ñ(;¸UÐ(BÑ#CÀdÑ#JðN
ð &*¨-¸#Ñ*=Ñ%>ÀÑ%EðN
ð ,/°©:õN
ðd 26Ø37ñ ^à$'¨$¡Jð ^ð '*¨D¡jó ^ðD ð4à	˜×-Ñ-¨tÐ3Ñ	4ò4ó ð4ó8	$òUð^ 4ó ^ð"Dàð"Dð 2°DÑ8ð"Dð ð	"Dð
 
 ó"DðHPXàðPXð 2°DÑ8ðPXð ð	PXð
 
Ð)Ñ	*Ð-GÑ	GóPXðdDiàðDið 2°DÑ8ðDið ð	Dið
 
Ð)Ñ	*óDiðL g×&7Ñ&7ó ð ð¨#°©*ð ¸ò ó ðð ð/Øð/àð/ð ð/ð 
�%˜˜S˜‘/Ñ	"ò	/ó ð/ðb ð/Ø˜e C¨ H™oÑ.ð/à Ð)DÑ EÑFð/ð 
Ð)Ñ	*ò/ó ð/ðb -àð -ð Ð9Ñ:Ð=WÑWð -ð 
$ dÑ	*ó	 -ðD%à1ð%ð " # s (™^ð%ð 
ó	%ð0àð0ð ð0ð ˜T‘kð	0ð
 " # s (™^ð0ð 
ó0ðp %)ñ"]àð"]ð Ð9Ñ:Ð=WÑWð"]ð ð	"]ð
 " # s (™^ð"]ð ˜T‘kð"]ð 
ó"]ðH& Ø#Ð$?Ñ@ÐC]Ñ]ð& à	Ð)Ñ	*Ð-GÑ	Gó& ðPØ#Ð$?Ñ@ÐC]Ñ]óð2EàðEð ðEð 2°DÑ8ð	Eð
 ðEð 
óEðNXà)ðXð ðXð ð	Xð
 óXðt% 4ð %°ð %Àð %ÈÈtÐUXÈyÑIYó %ð8J¨tð J¸Sð JÈSð JÐUZÐ[_ÐadÐ[dÑUeó JðX#àð#ð *ð#ð  Ð"3Ñ3°mÑCó	#ðJ ð˜sð °°S¸#°X±ð À3ò ó ðð2 ð
ØÐ"3Ñ3°mÑCÀdÈ3ÐPVÈ;ÑFWÑWð
à	ò
ó ð
ð$àð$ð ð$ð Ð&Ñ'ð	$ð
 
ó$ðLRàðRð ðRð Ð&Ñ'ð	Rð
 
óRðhàÐ"3Ñ3°mÑCðð óð,IàðIð ðIð 
Ð+¨eÑ3°TÐ9Ñ	:ó	IðV ð7 uð 7¸$¸sÀC¸x¹.ð 7ÈUò 7ó ð7ð<_¸3ð _ÈDÐQTÐVYÐQYÉNð _Ð_có _ð.IàðIð ðIð 
Ð+¨eÑ3°TÐ9Ñ	:ó	IðVà)ðð ðð ð	ð
 
Ð+¨eÑ3°TÐ9Ñ	:óð< ðMØ"/ðMàÐ(Ñ)ðMð 
òMó ðMð
¸Dð 
ÐE]Ð`dÑEdó 
òð ð 6:ñ!à+ð!ð ð!ð Ð1Ñ2ð	!ð
 Ð1Ñ2ð!ð 
%ò!ó ð!ðF¸mð ÐPT÷ ñ r3   rE   )Frk   r@   rn   Úcollections.abcr   r   Ú
contextlibr   r   Útypingr   r   r	   r
   r   r   r   r‰   Útyping_extensionsr   r   rƒ   r   Úlitellm._loggingr   Úlitellm.constantsr   r   r   r«  r   Ú*litellm.litellm_core_utils.litellm_loggingr   ré  Úlitellm.caching.cachingr   Úlitellm.exceptionsr   r   Ú%litellm.integrations.custom_guardrailr   r   Úlitellm.proxy._typesr   Úlitellm.types.guardrailsr   r   r   r    r!   Ú7litellm.types.proxy.guardrails.guardrail_hooks.presidior"   r#   r$   r%   Úlitellm.utilsr&   r'   r(   r)   r+   r7   r¤   ÚAbstractEventLooprí   Ú_LoopSemaphoresr0   r  rC   rE   r2   r3   r4   ú<module>r     sÍ   ðó Û Û ß 4Ý *Ý ß P× PÑ Pã ß 3ã Ý Ý 1÷ñ õ
 :áÝWå -ß N÷õ 0÷õ ÷÷ B÷ó ô&˜Y¨eõ &ô? ô ?ð
 �w×0Ñ0°'×2CÑ2CÐCÑD€ð5˜Cð 5 Có 5ôT ?õ Tr3   