
    .cj4                       U d Z ddlmZ ddlmZ ddlZddlZddlZddl	Z	ddl
Z
ddlZddlmZmZmZ ddlmZ  ej        e          ZdZdZdZ G d	 d
e          Zd dZdZded<   daded<   d!dZedd"dZd dZ  G d de          Z!dS )#u  DuckDuckGo search — plugin form (via the ``ddgs`` package).

Subclasses the plugin-facing :class:`agent.web_search_provider.WebSearchProvider`.
The legacy in-tree module ``tools.web_providers.ddgs`` was removed in the
same commit that moved this code under ``plugins/``; this file is now the
canonical implementation.

The ``ddgs`` package is an optional dependency. ``is_available()`` reflects
whether the package is importable; the plugin still registers either way so
``hermes tools`` can prompt the user to install it.

Isolation note (#68096): ``ddgs``/``primp`` can block inside native code while
holding the Python GIL. A ``ThreadPoolExecutor`` + ``future.result(timeout=…)``
cap (see #52118) cannot fire in that state — the waiter never reacquires the
GIL — so the whole Hermes process freezes through Ctrl+C/SIGTERM. Each search
therefore runs in a disposable child process the parent can terminate/kill.
    )annotationsN)AnyDictOptional)WebSearchProvider   g?g      ?c                      e Zd ZdZdS )_SearchInterruptedzHRaised when tools.interrupt.is_interrupted() trips during a search wait.N)__name__
__module____qualname____doc__     ;/home/ice/.hermes/hermes-agent/plugins/web/ddgs/provider.pyr
   r
   0   s        RRRRr   r
   querystr
safe_limitintreturnlist[dict[str, Any]]c                   ddl m} g } |d          5 }t          |                    | |                    D ]\  }}||k    r nt	          |                    d          p|                    d          pd          }|                    t	          |                    d	d                    |t	          |                    d
d                    |dz   d           ddd           n# 1 swxY w Y   |S )uO  Run the blocking ddgs query and return normalized hits.

    Module-level (not a closure) so the child worker can import it and so
    tests can patch it for in-process unit tests. ``DDGS(timeout=…)`` bounds
    each individual HTTP request; the overall wall-clock cap is enforced by
    the parent via process timeout (#68096).
    r   )DDGS
   timeout)max_resultshrefurl titlebody   )r!   r   descriptionpositionN)ddgsr   	enumeratetextr   getappend)r   r   r   resultsclientihitr   s           r   _run_ddgs_searchr/   4   sL    $&G	b			 VEz J JKK 	 	FAsJcggfoo==2>>CNN "!5!566#&swwvr':':#;#; !A	                   Ns   CC,,C03C0zOptional[str]
_test_hookOptional[subprocess.Popen]_last_worker_procc                 6   	 ddl } t          | dd          }|rYt          j                            t          j                            t          j                            |                              S n# t          $ r Y nw xY wt          j                            t          j                            t          j                            t          j                            t          j                            t                                                            S )u   Return the ``sys.path`` entry that makes ``import plugins`` work.

    Prefer the live ``plugins`` package location over counting ``dirname``s from
    this file — that stays correct for source checkouts and site-packages.
    r   N__file__)pluginsgetattrospathdirnameabspath	Exceptionr4   )plugins_pkgpkg_files     r   _plugins_path_entryr>   W   s    %%%%;
D99 	O7??27??27??83L3L#M#MNNN	O   7??
GOOBGOOBGOOH,E,EFFGG	
 	
  s   A/A3 3
B ?B )graceprocr?   floatNonec                    dS d	 fd}	                                                                      ||                                             A                                   ||          s$t                              d j                   dS dS dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)
u  Terminate a worker, escalate to kill, and wait so no orphan remains.

    Does not close the parent's pipe ends — the caller must finish any
    ``communicate()``/reader first. Closing stdout while another thread is
    blocked in ``read()`` deadlocks on some platforms.
    NsecondsrA   r   boolc                   t          j                    | z   }t          j                    |k     rA                                dS t          j        d           t          j                    |k     A                                d uS )NTg?)time	monotonicpollsleep)rD   deadliner@   s     r   _wait_until_deadz-_terminate_and_reap.<locals>._wait_until_deadz   su    >##g-n))yy{{&tJt n)) yy{{$&&r   z*DDGS worker pid=%s did not exit after killzDDGS worker reap error: %s)rD   rA   r   rE   )rI   	terminatekillloggerwarningpidr;   debug)r@   r?   rL   excs   `   r   _terminate_and_reaprT   l   s    |' ' ' ' ' '	899;;NNU###99;;IIKKK##E** WKTXVVVVV W W 8 8 8137777777778s   BB 
C	#CC	c           	     "	   ddl m} | |d}t          r
t          |d<   ddlm}  |t          t          j                            }t          rd|d<   |                    dd	          }t                      }|r:||
                    t          j                  vr|r|t          j        z   |z   n||d<   t          j                            t          j                            t          j                            t                               d
          }i }	t"          j        dk    rt&          j        |	d<   nd|	d<   t'          j        t"          j        |gft&          j        t&          j        t&          j        |dd|	}
|
at5          j        d          }|                    |
j        t=          j        |                    }d}d}d	}	 tA          j!                    tD          z   }	  |            rd}nf|tA          j!                    z
  }|dk    rd}nG	 |#                    tI          tJ          |                    \  }}|pd	}n# t4          j&        $ r Y pw xY wtO          |
           |(                                s6	 |#                    tR                    \  }}|s|pd	}n# tT          $ r Y nw xY w|+                    dd           nu# tO          |
           |(                                s6	 |#                    tR                    \  }}|s|pd	}n# tT          $ r Y nw xY w|+                    dd           w xY w|rtY          d          |rtM          dtD           d          |-                                }|s%t]          d|
/                                 d          	 t=          j0        |          }n2# t<          j1        $ r }t]          d|dd                   |d}~ww xY wte          |t
                    st]          d|          |                    d          r=|                    d          pg }te          |tf                    st]          d           |S t]          ti          |                    d!          pd"                    )#u\  Run ``_run_ddgs_search`` in a disposable process with a hard deadline.

    The parent never joins the child while it may be inside native code holding
    *its* GIL — it only polls a communicator thread and, on timeout/interrupt,
    terminates the child OS process. Raises ``TimeoutError``,
    ``_SearchInterrupted``, or ``RuntimeError``.
    r   )is_interrupted)r   r   	test_hook)_sanitize_subprocess_env1HERMES_DDGS_ALLOW_TEST_HOOKS
PYTHONPATHr    z_search_worker.pywin32creationflagsTstart_new_session)stdinstdoutstderrenvr(   r#   )max_workersFr   )waitcancel_futuresDuckDuckGo search interrupted"DuckDuckGo search timed out after sz*DDGS worker exited without a result (code=)z#DDGS worker returned invalid JSON: N   z*DDGS worker returned an invalid envelope: okr+   z%DDGS worker returned non-list resultserrorzDDGS worker failed)5tools.interruptrV   r0   tools.environments.localrX   dictr7   environr)   r>   splitpathsepr8   joinr9   r:   r4   sysplatform
subprocessCREATE_NEW_PROCESS_GROUPPopen
executablePIPEDEVNULLr2   cfThreadPoolExecutorsubmitcommunicatejsondumpsrG   rH   _SEARCH_TIMEOUT_SECSresultmin_POLL_INTERVAL_SECSTimeoutErrorrT   done_TERMINATE_GRACE_SECSr;   shutdownr
   stripRuntimeErrorrI   loadsJSONDecodeError
isinstancelistr   )r   r   rV   requestrX   rb   child_pythonpath
path_entryworker_pathextra_kwargsr@   poolfut	timed_outinterruptedrawrK   	remainingout_errenveloperS   r+   s                          r   _run_ddgs_search_boundedr      s    /..... ).ZHHG *)AAAAAA
"
"4
#3#3
4
4C 2.1*+
 ww|R00$&&J 
j(8(>(>rz(J(JJJ:JZJ#&666PZ 	L ',,rwrwx/H/HIIK^__K $&L
|w(2(K_%% -1()	%
o !
 
 
 
D   Q///D
++d&
7(;(;
<
<CIK
C7>##&::	~ " 4>#3#33IA~~ 	JJs3F	/R/RJSS	TiR?    	D!!!xxzz 	JJ/DJEE	T $)C   56666 	D!!!xxzz 	JJ/DJEE	T $)C   56666 B !@AAA 
H1EHHH
 
 	
 ))++C 
GGGG
 
 	
:c??   ?#dsd)??
 
	
 h%% VTTTUUU||D ,,y))/R'4(( 	HFGGG
s8<<00H4HII
J
JJs   AK 0H> =K >IK IK 7$J 
J)(J)$L6)$LL6
LL6LL6 N5 5O$OO$c                  l    e Zd ZdZedd            Zedd            ZddZddZdd	Z	dddZ
ddZdS )DDGSWebSearchProvidera  DuckDuckGo HTML-scrape search provider.

    No API key needed. Rate limits are enforced server-side by DuckDuckGo;
    the provider surfaces ``DuckDuckGoSearchException`` and other ddgs errors
    as ``{"success": False, "error": ...}`` rather than raising.
    r   r   c                    dS )Nr&   r   selfs    r   namezDDGSWebSearchProvider.name  s    vr   c                    dS )NDuckDuckGo (ddgs)r   r   s    r   display_namez"DDGSWebSearchProvider.display_name  s    ""r   rE   c                2    	 ddl }dS # t          $ r Y dS w xY w)u   Return True when the ``ddgs`` package is importable.

        Probes the import once; cheap because Python caches the import. Must
        NOT perform network I/O — runs at tool-registration time and on every
        ``hermes tools`` paint.
        r   NTF)r&   ImportError)r   r&   s     r   is_availablez"DDGSWebSearchProvider.is_available  s7    	KKK4 	 	 	55	s    
c                    dS )NTr   r   s    r   supports_searchz%DDGSWebSearchProvider.supports_search'  s    tr   c                    dS )NFr   r   s    r   supports_extractz&DDGSWebSearchProvider.supports_extract*  s    ur      r   limitr   Dict[str, Any]c                6   	 ddl }n# t          $ r dddcY S w xY wt          dt          |                    }	 t	          ||          }n# t
          $ r2 t                              dt          |           ddt           d	dcY S t          $ r# t          
                    d
|           dddcY S t          $ r-}t                              d|           dd| dcY d}~S d}~ww xY wt          
                    d|t          |          |           dd|idS )a*  Execute a DuckDuckGo search and return normalized results.

        The synchronous ``ddgs`` call runs in a disposable child process with
        a hard wall-clock timeout (``_SEARCH_TIMEOUT_SECS``) so a hung native
        ``primp`` call cannot freeze the Hermes process (#36776, #68096).
        r   NFu8   ddgs package is not installed — run `pip install ddgs`)successrl   r#   z-DDGS search timed out after %ds for query: %rrg   uh   s — DuckDuckGo may be rate-limiting or slow. Try again later or switch to a different search provider.z%DDGS search interrupted for query: %rrf   zDDGS search error: %szDuckDuckGo search failed: z'DDGS search '%s': %d results (limit %d)Tweb)r   data)r&   r   maxr   r   r   rO   rP   r   r
   infor;   len)r   r   r   r&   r   web_resultsrS   s          r   searchzDDGSWebSearchProvider.search-  s   	KKKK 	 	 	 S    	 CJJ''
	S25*EEKK 	 	 	NN?$   !@9M @ @ @     " 	 	 	KK?GGG 8      	S 	S 	SNN2C888$/QC/Q/QRRRRRRRR	S 	5uc+>N>NPU	
 	
 	
  %)=>>>s5    A 9C')C'1	C':"C"C'"C'c                    dddg ddS )Nr   u   free · no key · search onlyuR   Search via the ddgs Python package — no API key (pair with any extract provider)r&   )r   badgetagenv_vars
post_setupr   r   s    r   get_setup_schemaz&DDGSWebSearchProvider.get_setup_schema_  s"    '4g !
 
 	
r   Nr   r   )r   rE   )r   )r   r   r   r   r   r   )r   r   )r   r   r   r   propertyr   r   r   r   r   r   r   r   r   r   r   r   	  s             X # # # X#         0? 0? 0? 0? 0?d	
 	
 	
 	
 	
 	
r   r   )r   r   r   r   r   r   r   )r@   r1   r?   rA   r   rB   )"r   
__future__r   concurrent.futuresfuturesr|   r   loggingr7   rv   rt   rG   typingr   r   r   agent.web_search_providerr   	getLoggerr   rO   r   r   r   r;   r
   r/   r0   __annotations__r2   r>   rT   r   r   r   r   r   <module>r      s    $ # " " " " "         				     



  & & & & & & & & & & 7 7 7 7 7 7		8	$	$      S S S S S S S S   : !
         15  4 4 4 4   0 )8 8 8 8 8 8DxK xK xK xKv_
 _
 _
 _
 _
- _
 _
 _
 _
 _
r   