
    CjJ^                     (   S r SSKrSSKrSSKrSSKrSSKrSSKrSSKJ	r	  SSK
JrJrJrJr  SSKrSSKJr  SSKJr  SSKJrJr  SSKJr  SS	KJrJr  SS
KJr  SSKJrJ r J!r!J"r"  \(       a  SSK#J$r%  Sr&Sr'Sr(Sr)Sr* " S S\+5      r, " S S\\5      r-g)z:Rubrik LiteLLM Plugin for tool blocking and batch logging.    N)Counter)TYPE_CHECKINGAnyLiteralOptional)verbose_logger)CustomBatchLogger)CustomGuardrailModifyResponseException)safe_deep_copy)get_async_httpx_clienthttpxSpecialProvider)GuardrailEventHooks)ChatCompletionMessageToolCallFunctionGenericGuardrailAPIInputsStandardLoggingPayload)Loggingz/v1/messagesz/v1/after_completion/openai/v1z/v1/litellm/batchi'  g      N@c                       \ rS rSrSrSrg)#_MalformedToolBlockingResponseError,   a   Raised when the tool blocking service returns a structurally invalid
response (e.g. empty ``choices``).

Distinct from transient network/HTTP errors so callers can surface a
louder, misconfiguration-style log instead of treating it as a routine
fail-open.
 N)__name__
__module____qualname____firstlineno____doc____static_attributes__r       m/home/rurouni/.local/share/pipx/venvs/strix-agent/lib/python3.13/site-packages/litellm/integrations/rubrik.pyr   r   ,   s    r   r   c                   r  ^  \ rS rSr  S+S\S-  S\S-  4U 4S jjjrS\\R                  \	      4S jr
S,S jrS	 r S-S
\S\S\S   S\S   S\4
S jjrS
\S\	S\S\S   S\4
S jr\S\	S\\   4S j5       r\S\\   S\S-  S\\\	4   4S j5       r\S\\\	4   S\\\	4   4S j5       r\S\	S\	4S j5       r\S\\\	4   S\\\	4   S\4S j5       rS\S\S\S-  4S jrS\S\4S jrS,S jrS  rS! rS" r S# r!S$ r"S%\\\	4   S\\\	4   S\\\	4   4S& jr#\S'\\\	4   S(\\   S\\   4S) j5       r$S*r%U =r&$ ).RubrikLogger6   Napi_keyapi_basec           	      6  > [         R                  " 5       U l        UR                  SS5        UR	                  S5      =(       d    [
        R                  US'   UR	                  S5      c  SUS'   [        TU ]   " S#SU R                  0UD6  [        R                  " S5        SU l        [        R                  " S	5      nUbj   [        UR                  5       5      n[!        S
[#        SU5      5      U l        XPR                  :w  a&  [        R$                  " SU SU R                   35        U=(       d    [        R                  " S5      U l        U R(                  (       d  [        R$                  " S5        [        R                  " S5      nU(       a   [+        U5      U l        [.        U l        SU l        S
U l        U=(       d    [        R                  " S5      nUc  ['        S5      eUR7                  S5      R9                  S5      nU [:         3U l        U [>         3U l         [C        [D        RF                  S9U l$        [C        [D        RF                  S[J        RL                  " SSS90S9U l'        SS 0U l(        U R(                  (       a  S!U R(                   3U RP                  S"'   U RS                  5       U l*        g ! [&         a    [        R$                  " SU< S35         GNf = f! [&         a    [        R$                  " SU< S35         GNrf = f)$Nguardrail_namerubrik
event_hook
default_onT
flush_lockzinitializing rubrik loggerg      ?RUBRIK_SAMPLING_RATEg        zRUBRIK_SAMPLING_RATE=z clamped to zInvalid RUBRIK_SAMPLING_RATE: z, using 1.0RUBRIK_API_KEYz@Rubrik: No API key configured. Requests will be unauthenticated.RUBRIK_BATCH_SIZEzInvalid RUBRIK_BATCH_SIZE: z, using defaultr   RUBRIK_WEBHOOK_URLzKRubrik webhook URL not configured. Set RUBRIK_WEBHOOK_URL or pass api_base./z/v1)llm_providertimeoutg      @g       @)connect)r1   paramszContent-Typezapplication/jsonzBearer Authorizationr   )+asyncioLockr+   
setdefaultgetr   	post_callsuper__init__r   debugsampling_rateosgetenvfloatstripmaxminwarning
ValueErrorkeyint
batch_size_MAX_QUEUE_SIZEmax_queue_size_dropped_since_warning_last_drop_warning_timerstripremovesuffix_WEBHOOK_PATH_TOOL_BLOCKINGtool_blocking_endpoint_WEBHOOK_PATH_LOGGING_BATCHlogging_endpointr   r   LoggingCallbackasync_httpx_clienthttpxTimeouttool_blocking_client_headers_start_periodic_flush_task_flush_task)	selfr$   r%   kwargsrbrk_sampling_rateparsed_rate_batch_size_webhook_url	__class__s	           r    r<   RubrikLogger.__init__7   s    ",,.*H5  &zz,7X;N;X;X|::l#+#'F<  	
	
	

 	9: YY'=>)#$6$<$<$>?%(c#{.C%D""4"44"**/}L--.0 9bii(89xx""R ii 34"%k"2 .&'#'*$B299-A#B; 
 $**3/<<UC)57R6S&T##/.1L0M N"8-==#
 %;-==u}}S#>?%
!
 *89K(L88/6txxj-ADMM/* ++- 	q  &&45G4J+V  &&1+Ps%   A)K K/ %K,+K,/%LLreturnc                      [         R                  " 5       nUR                  U R                  5       5      $ ! [         a    [        R                  " S5         gf = f)zIStart the periodic flush task only when an event loop is already running.zXRubrik logger init: no running event loop, periodic flush will start on first log event.N)r6   get_running_loopRuntimeErrorr   r=   create_taskperiodic_flush)r\   loops     r    rZ   'RubrikLogger._start_periodic_flush_task   s[    	++-D  3 3 566  	  @ 	s   6  AAc                     U R                   b  U R                   R                  5       (       a  U R                  5       U l         g g N)r[   donerZ   r\   s    r    _ensure_periodic_flush_task(RubrikLogger._ensure_periodic_flush_task   s:     #t'7'7'<'<'>'>#>>@D (?r   c                   #    U R                   bU  U R                   R                  5       (       d6  U R                   R                  5          U R                   I Sh  vN   SU l         U R                  R                  5       I Sh  vN   U R                  R                  5       I Sh  vN   g NP! [        R                  [
        4 a     Njf = f NI N)7f)z5Close the dedicated HTTP clients used by this logger.N)	r[   rn   cancelr6   CancelledError	ExceptionrX   closerU   ro   s    r    acloseRubrikLogger.aclose   s      '0@0@0E0E0G0G##%&&&&  $D''--///%%++--- '**I6  	0-sZ   AC
B, B*B, $CC!C$C%C*B, ,C	CC		CCinputsrequest_data
input_typerequestresponselogging_objLiteLLMLoggingObjc                 T  #    US:w  a  U$ UR                  S5      nU(       d  U$  U R                  XX$5      I Sh  vN $  N! [         a    e [         a"  n[        R
                  " SUSS9  Us SnA$ SnAf[         a%  n[        R                  " SU S3SS9  Us SnA$ SnAff = f7f)	z=Validate tool calls against the blocking service (fail-open).r~   
tool_callsNzTool blocking service returned a malformed response: %s. Tool calls are NOT being checked -- verify the webhook configuration. Returning original response unchanged.Texc_infozTool blocking hook failed: z(. Returning original response unchanged.)r9   _check_tool_callsr   r   r   criticalru   error)r\   ry   rz   r{   r   r   es          r    apply_guardrailRubrikLogger.apply_guardrail   s      #MZZ-
M	//L    ' 	2 	 ##H  M 	  -aS 19 9
 M	s[   #B(A A A B( A B%A3-B%.B(3B% B B%B( B%%B(r   c                   #    U R                  U5      nU(       a  [        US0 5      O0 nUR                  S5      nU(       a  [        USS5      OSnU(       a  U(       d  [        R                  " S5        U R                  XX5      n	U R                  U5      n
U R                  X5      I Sh  vN nU R                  X5      nUb'  U R                  X65      n[        UUUU R                  S9eU$  NA7f)z>Send tool calls to blocking service, raise if any are blocked.model_call_detailsr~   idNz^Rubrik: logging_obj present but model_call_details is empty -- request context will be missing)messagemodelrz   r'   )_normalize_tool_callsgetattrr9   r   rE   _build_tool_call_payload_extract_request_data_post_to_tool_blocking_service_extract_blocked_tools_resolve_modelr   r'   )r\   ry   r   rz   r   message_tool_callscall_detailsr~   
request_idresponse_datareq_dataservice_responseblocked_explanationr   s                 r    r   RubrikLogger._check_tool_calls   s     "77
C ?JGK!5r:r 	  ##J/6>WXtT2D
|""5
 556HU--l;!%!D!D"
 
 #99
 *''CE)+)#22	  !
s   B)C/+C-,AC/c                    / nU  GHE  n[        U[        5      (       a  UR                  U5        M,  [        U[        5      (       av  UR	                  S0 5      nUR                  [        UR	                  SS5      UR	                  SS5      [        UR	                  SS5      UR	                  SS5      S9S95        M  [        US5      (       a_  [        US5      (       aN  UR                  [        UR                  =(       d    S[        USS	5      =(       d    SUR                  S95        GM'  [        S
[        U5      R                   35      e   U$ )zHConvert tool_calls from inputs to ChatCompletionMessageToolCall objects.functionr    typename	arguments)r   r   )r   r   r   Nz#Cannot normalize tool_call of type )
isinstancer   appenddictr9   r   hasattrr   r   r   	TypeErrorr   r   )r   resulttcfuncs       r    r   "RubrikLogger._normalize_tool_calls  s    B";<<b!B%%vvj"-166$+VVFJ7!)!%&"!5&*hh{B&?"	 T""wr:'>'>155;B$R6D*!#  9$r(:K:K9LM 1 6 r   r   c                     U=(       d    S[         R                  " 5        3S[        [        R                  " 5       5      SSSSU  Vs/ s H  o"R	                  SS9PM     snS	.S
S./S.$ s  snf )zHBuild a full OpenAI ChatCompletion-format dict for the blocking service.z	chatcmpl-zchat.completionr   r   	assistantNT)exclude_none)rolecontentr   r   )indexr   finish_reason)r   objectcreatedr   choices)uuiduuid4rH   time
model_dump)r   r   r   s      r    r   %RubrikLogger._build_tool_call_payload.  s{     :)DJJL> :'499;'  +#'GQ'GQMMtM<z'  &2

 	
's   A*r   c                     U (       d  0 $ U R                  S0 5      =(       d    0 nU R                  S5      U R                  S5      [        R                  UR                  S5      5      S.$ )z6Extract original request data from model_call_details.litellm_paramsmessagesr   proxy_server_request)r   r   r   )r9   r"   _sanitize_proxy_server_request)r   r   s     r    r   "RubrikLogger._extract_request_dataH  sg     I%))*:B?E2$((4!%%g.$0$O$O""#9:%
 	
r   r   c                 t    [        U [        5      (       d  U $ S Vs0 s H  nX;   d  M
  XU   _M     sn$ s  snf )a  Allowlist only routing fields (``url``, ``method``) when forwarding
``proxy_server_request`` to the external Rubrik webhook, dropping
inbound ``headers`` (Authorization, Cookie, x-api-key, ...) and the raw
request ``body`` so proxy credentials are not exfiltrated.)urlmethod)r   r   )r   rG   s     r    r   +RubrikLogger._sanitize_proxy_server_requestV  sO     .55'' )
(* +Cc**(
 	
 
s   	5	5c                     U R                  S5      nU(       a&  [        US5      (       a  UR                  =(       d    S$ UR                  SS5      $ )z3Get the model name for the ModifyResponseException.r~   r   unknown)r9   r   r   )rz   r   r~   s      r    r   RubrikLogger._resolve_modeld  sE    
  ##J/'22>>.Y.33r   r]   
event_typec                 L  #    [         R                   " 5       U R                  :  a(  [        R                  " SU SU R                   S35        g[	        US   5      nUR                  S0 5      =(       d    0 nUR                  S0 5      =(       d    0 n[        R                  R                  UR                  SS	5      5      R                  nUR                  [        5      (       a  UR                  S
5      nU(       a  XsS'   SU;   ak  US   n U(       a\  SUS.n	[        US   [        5      (       a  US   R                  SU	5        U$ [        US   [        [         45      (       a
  U	US   /US'   U$ U$ ! ["         a#  n
[        R$                  " SU
 3SS9   Sn
A
U$ Sn
A
ff = f7f)z-Shared logic for success and failure logging.zSkipping Rubrik z logging (sampling_rate=)Nstandard_logging_objectr   r   r   r   litellm_call_idr   system)r   r   r   r   z)Rubrik: failed to prepend system prompt: Tr   )randomr>   r   r=   r   r9   urllibparseurlparsepathendswith_ENDPOINT_ANTHROPIC_MESSAGESr   listinsertr   strru   rE   )r\   r]   r   standard_logging_payloadr   proxy_requesturl_path_litellm_call_idsystem_prompt_msg_listsystem_scaffoldr   s              r    _prepare_log_payload!RubrikLogger._prepare_log_payloadp  s     ==?T///  ":, /""&"4"4!5Q8  <J,-<
   $4b9?R&**+A2FL"<<(():):5")EFKK9::%zz*;<1A.v%+H%5") (#9'O ""::"FMM0<CCAW (' $$<Z$H4QT+VV+4Z@@0< ('''  &&?sC!
 ('s<   D
F$9E4 F$(E4 0F$4
F!>FF$F!!F$c                   #     U R                  5         U R                  X5      I S h  vN nUc  g U R                  R                  U5        U R	                  5         [        U R                  5      U R                  :  a  U R                  5       I S h  vN   g g  Np N! [         a&  n[        R                  " SU SU S3SS9   S nAg S nAff = f7f)NzRubrik z logging hook failed: z". Skipping logging for this event.Tr   )rp   r   	log_queuer   _enforce_max_queue_sizelenrI   flush_queueru   r   r   )r\   r]   r   payloadr   s        r    _enqueue_log_eventRubrikLogger._enqueue_log_event  s     	,,. 55fIIGNN!!'*((*4>>"doo5&&((( 6 J ) 	  *%;A3 ?3 3	s[   C$B BB CA!B BB CB B 
C'CCCCc                 h   [        U R                  5      U R                  -
  nUS::  a  g U R                  S U2	 U =R                  U-  sl        [        R                  " 5       nX R
                  -
  [        :  a:  [        R                  " SU R                  U R                  5        SU l        X l        g g )Nr   zRubrik: log queue exceeded max_queue_size=%s; dropped %s oldest events since the last warning. The Rubrik webhook may be unhealthy or undersized for current traffic.)	r   r   rK   rL   r   rM   _DROP_WARNING_INTERVAL_SECONDSr   rE   )r\   overflownows      r    r   $RubrikLogger._enforce_max_queue_size  s    t~~&)<)<<q=NN9H9%##x/#iik---1OO""B ##++ +,D'+.( Pr   c                 D   #    U R                  US5      I S h  vN   g  N7f)Nsuccessr   r\   r]   response_obj
start_timeend_times        r    async_log_success_event$RubrikLogger.async_log_success_event       %%fi888     c                 D   #    U R                  US5      I S h  vN   g  N7f)Nfailurer   r   s        r    async_log_failure_event$RubrikLogger.async_log_failure_event  r   r   c                   #     U R                   R                  U R                  UU R                  S9I S h  vN nUR	                  5         g  N! [
        R                   aJ  n[        R                  " SUR                  R                   SUR                  R                   35        e S nAf[         a    [        R                  " S5        e f = f7f)N)r   jsonheaderszRubrik HTTP Error: z - zRubrik Layer Error)rU   postrS   rY   raise_for_statusrV   HTTPStatusErrorr   	exceptionr~   status_codetextru   )r\   datar~   r   s       r    _log_batch_to_rubrik!RubrikLogger._log_batch_to_rubrik  s     	!4499)) :  H
 %%' $$ 	$$%ajj&<&<%=S@QR  	$$%9:	s9   C2A AA CA C"AB''%CCc                    #    U R                   (       d  g[        U R                   5      n[        R                  " S[	        U5      5        U R                  US9I Sh  vN   g N7f)a  Handles sending batches of responses to Rubrik.

Note: the canonical flush path is :meth:`flush_queue`, which takes a
single snapshot used for both sending and queue draining. This method
is kept for direct callers / tests; it intentionally does NOT remove
events from the queue.
N#Rubrik: Flushing batch of %s eventsr  )r   r   r   r=   r   r  )r\   log_queue_snapshots     r    async_send_batchRubrikLogger.async_send_batch  s[      ~~!$..1137I3J	
 ''# ( 
 	
 	
s   AA$A"A$c                 <  #    U R                   c  gU R                    ISh  vN   U R                  (       d   SSS5      ISh  vN   g[        U R                  5      n[        R                  " S[        U5      5         U R                  US9I Sh  vN   U R                  S[        U5      2	 [        R                  " 5       U l	        SSS5      ISh  vN   g N N NK! [         a     SSS5      ISh  vN    gf = f N,! , ISh  vN  (       d  f       g= f7f)a  Snapshot, send, and drain in one consistent step.

Overrides the base implementation so the same snapshot drives both
the HTTP send and the queue truncation. This avoids the subtle
coupling where the base class captures `len(self.log_queue)`
separately from the snapshot taken inside `async_send_batch`,
which could otherwise drift in a future refactor and cause
duplicate deliveries to Rubrik.
Nr  r  )
r+   r   r   r   r=   r   r  ru   r   last_flush_time)r\   snapshots     r    r   RubrikLogger.flush_queue  s      ??"???>> #?? DNN+H  !FHV//X/>>>
 X/#'99;D  #?? ?   #?? #???s   DCDDDCD	5D?CCC2DDD DDC
C=)D*D5C86D<C==D DDD	DDr   c                   #    UUS.n[         R                  " SU R                   35        U R                  R	                  U R                  UU R
                  S9I Sh  vN nUR                  5         UR                  5       nU$  N&7f)ab  Post a payload to the tool blocking service and return the response.

Args:
    response_data: The OpenAI-formatted response payload to send.
    request_data: Original LLM request data to include alongside
        the response for additional context. Empty dict if unavailable.

Raises:
    Exception: If the service is unavailable or returns an error.
r|   z*Sending request to tool blocking service: )r   r   N)r   r=   rQ   rX   r  rY   r  r   )r\   r   rz   envelopehttp_responser   s         r    r   +RubrikLogger._post_to_tool_blocking_service  s       $%
 	8**+-	
 #77<<''MM = 
 

 	&&(!.!3!3!5
s   ABB'Br   all_tool_callsc                   ^	 U R                  S/ 5      nU(       d  [        S5      eUS   R                  S0 5      nUR                  S5      =(       d    / nUR                  SS5      n[        S U 5       5      m	[        S	 U 5       5      n[        U5      [        U5      :  =(       a#    [	        U	4S
 jUR                  5        5       5      nU(       a  gU=(       d    SnSU 3$ )a  Return the blocking explanation if any tool calls were blocked.

Compares the service response (which contains only allowed tools) against
the full set of tool calls. Returns ``None`` if all tools are allowed, or
the explanation string (prefixed with newlines) otherwise.

Expects service_response in OpenAI chat completion format:
    {"choices": [{"message": {"tool_calls": [...], "content": "..."}}]}
r   z-Tool blocking service returned empty responser   r   r   r   r   c              3      #    U  H9  n[        U[        5      (       d  M  UR                  S 5      (       d  M2  US    v   M;     g7f)r   N)r   r   r9   .0r   s     r    	<genexpr>6RubrikLogger._extract_blocked_tools.<locals>.<genexpr>M  s7      -
)"d# (*t BtH)s   AAAc              3   ^   #    U  H#  oR                   (       d  M  UR                   v   M%     g 7frm   )r   r  s     r    r  r  R  s     -VnPUPUebeens   --c              3   R   >#    U  H  u  pTR                  US 5      U:  v   M     g7f)r   N)r9   )r  tc_idcountallowed_id_countss      r    r  r  T  s-      N
 : !!%+u4 :s   $'NzTool call blocked by policy.z

)r9   r   r   r   allitems)
r   r  r   r   returned_tool_callsblocking_explanationrequired_id_countsall_allowedexplanationr#  s
            @r    r   #RubrikLogger._extract_blocked_tools5  s     #&&y"55?  !*..B/%kk,7=2&{{9b9%, -
)-
 &

 '.-Vn-V&V-.#n2EE 
# N
 2 8 8 :N
 K

 *L.Lk]##r   )rL   r[   rY   rM   rU   rI   r+   rG   r  rS   rK   r>   rX   rQ   )NN)rd   Nrm   )'r   r   r   r   r   r<   r   r6   Taskr   rZ   rp   rw   r   r   r   r   r   staticmethodr   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r   r   r   r   __classcell__)rb   s   @r    r"   r"   6   s    ##]
t]
 *]
 ]
~
7HW\\#5F,G 
7A.* 6:))) ) 12	)
 12) 
#)V()( ( 	(
 12( 
#(T # $7T2U  @ 
67
$J
 
c3h
 
2 
DcN 
tCH~ 
 
 
S 
S 
 
 438n448cN4	4 42(2((+2(	$	&2(ht  &/$99
(
&/:CH~ 38n 
c3h	@ '$sCx.'$:;'$ 
#'$ '$r   r"   ).r   r6   r?   r   r   urllib.parser   r   collectionsr   typingr   r   r   r   rV   litellm._loggingr   (litellm.integrations.custom_batch_loggerr	   %litellm.integrations.custom_guardrailr
   r   'litellm.litellm_core_utils.core_helpersr   &litellm.llms.custom_httpx.http_handlerr   r   litellm.types.guardrailsr   litellm.types.utilsr   r   r   r   *litellm.litellm_core_utils.litellm_loggingr   r   r   rP   rR   rJ   r   ru   r   r"   r   r   r    <module>r:     s    @  	      8 8  + F C 9    . > 1 !% ) g$?$5 g$r   