
    PL
jc:                       d Z ddlmZ ddlZddlZddlZddlZddlmZm	Z	m
Z
mZmZ ddlZddlmZmZ ddlmZmZ  ej        e          ZdZdZd	Zd
ZdZdGdZdHdZdIdZdIdZdJdZ dKdZ!dLdZ"dMdZ#dNd Z$dOd#Z%	 	 	 	 	 	 dPdQd-Z&d.d/d0d1d2d3d4d5d1id6d7d4d5d1id8d7d1d9d3d1d:d3d;d<d%d=d;d>d%d=d?d&gd@dAZ'dB Z( ej)        d.d.e'e(e!dCgdDdEF           dS )Ru  X Search tool backed by xAI's built-in ``x_search`` Responses API tool.

Authentication
--------------
The tool registers when **either** xAI credential path is available:

* ``XAI_API_KEY`` is set in ``~/.hermes/.env`` or the process environment
  (paid xAI API key), OR
* The user is signed in via xAI Grok OAuth — SuperGrok subscription —
  i.e. ``hermes auth add xai-oauth`` has been run and the stored refresh
  token still works.

Credential preference at call time matches
:func:`tools.xai_http.resolve_xai_http_credentials`: SuperGrok OAuth first,
direct OAuth resolver second, ``XAI_API_KEY`` last. That helper also
auto-refreshes the OAuth access token when it's within the refresh skew
window, so a ``True`` from :func:`check_x_search_requirements` means the
bearer is fetchable AND non-empty.

Salvaged from PR #10786 (originally by @Jaaneek); credential resolution
reworked to honor both auth modes per Teknium's design.
    )annotationsN)AnyDictListOptionalTuple)registry
tool_error)hermes_xai_user_agentresolve_xai_http_credentialszhttps://api.x.ai/v1zgrok-4.20-reasoning      
   returnDict[str, Any]c                 t    	 ddl m}   |                                 di           pi S # t          $ r i cY S w xY w)Nr   load_configx_search)hermes_cli.configr   get	Exceptionr   s    7/home/kuhnn/.hermes/hermes-agent/tools/x_search_tool.py_load_x_search_configr   3   s^    111111{}}  R006B6   			s   %( 77strc                     t                      } t          |                     d          pd                                          pt          S )Nmodel )r   r   r   stripDEFAULT_X_SEARCH_MODEL)cfgs    r   _get_x_search_modelr"   <   s>    

!
!C  &B''--//I3IJ    intc                     t                      } |                     dt                    }	 t          dt	          |                    S # t
          $ r
 t          cY S w xY w)Ntimeout_seconds   )r   r    DEFAULT_X_SEARCH_TIMEOUT_SECONDSmaxr$   r   r!   	raw_values     r   _get_x_search_timeout_secondsr,   A   sd    

!
!C)+KLLI02s9~~&&& 0 0 0////0   A AAc                     t                      } |                     dt                    }	 t          dt	          |                    S # t
          $ r
 t          cY S w xY w)Nretriesr   )r   r   DEFAULT_X_SEARCH_RETRIESr)   r$   r   r*   s     r   _get_x_search_retriesr1   J   sc    

!
!C	#;<<I(1c)nn%%% ( ( (''''(r-   Tuple[str, str, str]c                    t                      } t          |                     d          pd                                          }|st	          d          t          |                     d          pt
                                                                        d          }t          |                     d          pd          }|||fS )u  Return ``(api_key, base_url, source)``.

    ``source`` is one of ``"xai-oauth"`` or ``"xai"`` so callers (and tests)
    can tell which credential path won. Raises ``RuntimeError`` if no usable
    credential is available — the registered :func:`check_x_search_requirements`
    gate makes that case unreachable in normal operation, but the runtime
    check exists so a credential that expires between registration and
    invocation produces a clean tool error instead of a 401.
    api_keyr   z~No xAI credentials available. Run `hermes auth add xai-oauth` to sign in with your SuperGrok subscription, or set XAI_API_KEY.base_url/providerxai)r   r   r   r   RuntimeErrorDEFAULT_XAI_BASE_URLrstrip)credsr4   r5   sources       r   _resolve_xai_bearerr>   W   s     )**E%))I&&,"--3355G 
O
 
 	
 599Z((@,@AAGGIIPPQTUUH:&&/%00FHf$$r#   boolc                     	 t                      } t          t          |                     d          pd                                                    S # t
          $ r Y dS w xY w)a)  Return True when xAI credentials are available AND valid.

    ``resolve_xai_http_credentials`` calls
    :func:`hermes_cli.auth.resolve_xai_oauth_runtime_credentials` which
    auto-refreshes the OAuth access token if it's expiring; a successful
    return therefore implies a usable bearer.
    r4   r   F)r   r?   r   r   r   r   )r<   s    r   check_x_search_requirementsrA   m   sg    ,..C		),,23399;;<<<   uus   AA 
A! A!handlesOptional[List[str]]
field_name	List[str]c                   g }| pg D ]O}t          |pd                                                              d          }|r|                    |           Pt	          |          t
          k    rt          | dt
           d          |S )Nr   @z supports at most z handles)r   r   lstripappendlenMAX_HANDLES
ValueError)rB   rD   cleanedhandle
normalizeds        r   _normalize_handlesrP      s    G-R ' '2&&,,..55c::
 	'NN:&&&
7||k!!JOO+OOOPPPNr#   payloadc                4   t          |                     d          pd                                          }|r|S g }|                     dg           pg D ]}|                    d          dk    r|                    dg           pg D ]h}|                    d          }|dv rMt          |                    d          pd                                          }|r|                    |           id	                    |                                          S )
Noutput_textr   outputtypemessagecontent>   textrS   rX   z

)r   r   r   rI   join)rQ   rS   partsitemrW   ctyperX   s          r   _extract_response_textr]      s$   gkk-006B77==??K EHb))/R ' '88Fy((xx	2..4" 	' 	'GKK''E///7;;v..4"55;;== 'LL&&&	' ;;u##%%%r#   List[Dict[str, Any]]c                   g }|                      dg           pg D ]}|                     d          dk    r|                     dg           pg D ]}|                     dg           pg D ]}|                     d          dk    r|                    |                     dd          |                     d	d          |                     d
          |                     d          d           |S )NrT   rU   rV   rW   r   url_citationurlr   titlestart_index	end_index)ra   rb   rc   rd   )r   rI   )rQ   	citationsr[   rW   
annotations        r   _extract_inline_citationsrg      s   &(IHb))/R  88Fy((xx	2..4" 	 	G%kk-<<B 
 

>>&))^;;  )~~eR88!+!<!<'1~~m'D'D%/^^K%@%@	    
	 r#   excrequests.HTTPErrorc                   t          | dd           }|t          |           S 	 |                                }n# t          $ r d }Y nw xY wt	          |t
                    rt          |                    d          pd                                          }t          |                    d          pd                                          }|pt          |          }|r||vr| d| }|pt          |           S t          t          |dd          pd                                          }|r
|d d         S t          |           S )Nresponsecoder   errorz: rX     )getattrr   jsonr   
isinstancedictr   r   )rh   rk   rQ   rl   rm   rV   rX   s          r   _http_error_messagers      sU   sJ--H3xx--//    '4   #7;;v&&,"--3355GKK((.B//5577'3w<< 	+D''****G"#c(("wx,,23399;;D DSDzs88Os   9 AAr   Fqueryallowed_x_handlesexcluded_x_handles	from_dateto_dateenable_image_understandingenable_video_understandingc                	   | r|                                  st          d          S 	 t                      \  }}}	n3# t          $ r&}
t          t	          |
                    cY d }
~
S d }
~
ww xY w	 t          |d          }t          |d          }|r|rt          d          S ddi}|r||d<   |r||d<   |                                 r|                                 |d<   |                                 r|                                 |d<   |rd	|d
<   |rd	|d<   t                      d|                                  dg|gdd}t                      }t                      }d }t          |dz             D ]`}	 t          j        | dd| dt                      d||          }|                                  n# t          j        $ r}t          t          |dd           dd           }||dk     s||k    r t                               d|dz   |dz   t%          |                     t'          j        t+          dd|dz   z                       Y d }~d }~wt          j        t          j        f$ r]}||k    r t                               d|dz   |dz   |           t'          j        t+          dd|dz   z                       Y d }~Zd }~ww xY w|t          d          |                                }t3          |          }t5          |                    d          pg           }t9          |          }t1          j        d	d|	d|d          |                                  |||d!	d"          S # t          j        $ rb}t                               d#|d	$           t1          j        dddt%          |          t?          |          j         d%d"          cY d }~S d }~wt          j        $ re}t                               d&|d	$           t1          j        dddd't                       d(t?          |          j         d%d"          cY d }~S d }~wtB          $ rb}t                               d#|d	$           t1          j        dddt	          |          t?          |          j         d%d"          cY d }~S d }~ww xY w))Nzquery is required for x_searchru   rv   z@allowed_x_handles and excluded_x_handles cannot be used togetherrU   r   rw   rx   Try   rz   user)rolerW   F)r   inputtoolsstore   z
/responseszBearer zapplication/json)AuthorizationzContent-Typez
User-Agent)headersrp   timeoutrk   status_codern   z.x_search upstream failure on attempt %s/%s: %sg      @g      ?z/x_search transient failure on attempt %s/%s: %sz*x_search request did not return a responsere   r8   r   )	successr7   credential_sourcetoolr   rt   answerre   inline_citations)ensure_asciizx_search failed: %s)exc_info)r   r7   r   rm   
error_typezx_search timed out: %szxAI x_search timed out after z seconds)"r   r
   r>   r9   r   rP   r"   r,   r1   rangerequestspostr   raise_for_status	HTTPErrorro   loggerwarningrs   timesleepminReadTimeoutConnectionErrorrp   r]   listr   rg   dumpsrm   rU   __name__r   )rt   ru   rv   rw   rx   ry   rz   r4   r5   r=   rh   allowedexcludedtool_defrQ   r&   max_retriesrk   attempter   datar   re   r   s                            r   x_search_toolr      s     < <:;;;$$7$9$9!66 $ $ $#c((########$A
$%68KLL%&8:NOO 	bx 	b`aaa$*J#7 	4,3H() 	6-5H)*?? 	6$-OO$5$5H[!==?? 	2")--//HY% 	:59H12% 	:59H12 )** #${{}}  Z

 

 899+--04[1_-- "	: "	:G!:#=+++)<7)<)<(:&;&=&= 
 !+	 	 	 ))+++% 
: 
: 
:%gaT&B&BMSWXX&+*;*;w+?U?UDaK!O'**	   
3sC7Q;$78899999999((*BC 	: 	: 	:k))EaK!O	   
3sC7Q;$78899999999	: KLLL}}'--+..4"55	4T::z!%+" ) &$4
 
 
 
 
 	
  
 
 
*A===z !",Q//"1gg.  	
 	
 	
 		
 		
 		
 		
 		
 		
  
 
 
-q4@@@z !"b9V9X9Xbbb"1gg.  	
 	
 	
 		
 		
 		
 		
 		
 		
  
 
 
*A===z !"Q"1gg.  	
 	
 	
 		
 		
 		
 		
 		
 		

s   : 
A*A%A*%A*.2M2 !CM2 5AF:7M2 :K	BIM2 K3AKM2 KB!M2 2R?AOR?R?0AQ
R?R?AR:4R?:R?r   zSearch X (Twitter) posts, profiles, and threads using xAI's built-in X Search tool. Use this for current discussion, reactions, or claims on X rather than general web pages. Available when xAI credentials are configured (SuperGrok OAuth or XAI_API_KEY).objectstringzWhat to look up on X.)rU   descriptionarrayrU   z;Optional list of X handles to include exclusively (max 10).)rU   itemsr   z/Optional list of X handles to exclude (max 10).z)Optional start date in YYYY-MM-DD format.z'Optional end date in YYYY-MM-DD format.booleanz?Whether xAI should analyze images attached to matching X posts.)rU   r   defaultz?Whether xAI should analyze videos attached to matching X posts.rt   ru   rv   rw   rx   ry   rz   )rU   
propertiesrequired)namer   
parametersc                v   t          |                     dd          |                     d          |                     d          |                     dd          |                     dd          t          |                     dd                    t          |                     d	d                    
          S )Nrt   r   ru   rv   rw   rx   ry   Frz   r   )r   r   r?   )argskws     r   _handle_x_searchr     s    hhw##((#67788$899((;++B''#'1Mu(U(U#V#V#'1Mu(U(U#V#V   r#   XAI_API_KEYu   🐦i )r   toolsetschemahandlercheck_fnrequires_envemojimax_result_size_chars)r   r   )r   r   )r   r$   )r   r2   )r   r?   )rB   rC   rD   r   r   rE   )rQ   r   r   r   )rQ   r   r   r^   )rh   ri   r   r   )NNr   r   FF)rt   r   ru   rC   rv   rC   rw   r   rx   r   ry   r?   rz   r?   r   r   )*__doc__
__future__r   rp   loggingosr   typingr   r   r   r   r   r   tools.registryr	   r
   tools.xai_httpr   r   	getLoggerr   r   r:   r    r(   r0   rK   r   r"   r,   r1   r>   rA   rP   r]   rg   rs   r   X_SEARCH_SCHEMAr   register r#   r   <module>r      s   . # " " " " "   				  3 3 3 3 3 3 3 3 3 3 3 3 3 3  / / / / / / / / N N N N N N N N		8	$	$, . #&      K K K K
0 0 0 0( ( ( (% % % %,   &   & & & &$   (   < .2.2',',R
 R
 R
 R
 R
l 	;  !6 
   (+\" "   (+P# # !J 
 !H 
 "` + + "` + +9!
 !
D II% %. .b	 	 	  	(
!	 	 	 	 	 	r#   