Ë
    âß²j²%  ã                  óæ   — d Z ddlmZ ddlZddlmZmZmZ ddlm	Z	 ddl
Z
ddlmZ ddlmZ  ej                   e«      ZdZd	Zd
dde› �ddœZdZdZdZdd„Zdd„Zdd„Zdd„Zdd„Z G d„ de«      Zy)u`  Perplexity web search + page snippets â€” plugin form.

Subclasses :class:`agent.web_search_provider.WebSearchProvider`. Two
capabilities advertised:

- ``supports_search()``  -> True (Perplexity Search API ``POST /search``)
- ``supports_extract()`` -> True (``POST /sdk/content/snippets`` â€” the
  query-relevant page-excerpt route behind ``pplx content snippets``)

Both are sync â€” the underlying call is ``httpx.post(...)``.

Config keys this provider responds to::

    web:
      search_backend: "perplexity"   # explicit per-capability
      extract_backend: "perplexity"  # explicit per-capability
      backend: "perplexity"          # shared fallback for both

Env vars::

    PERPLEXITY_API_KEY=...       # https://www.perplexity.ai/account/api (required)
    PERPLEXITY_BASE_URL=...      # optional override of https://api.perplexity.ai

Keyed only â€” Perplexity has no anonymous tier, so this provider is not a
member of the zero-config keyless ring and never resolves without a key.

Extract caveat: Perplexity's only supported page-content route returns the
passages of a page relevant to a *query* (elisions marked ``â€¦``), not the
whole page. ``web_extract`` has no query, so the URL's own path words are
used as the relevance query, which approximates "what is this page about".
Use Firecrawl / Exa / Parallel when a verbatim full-page dump is required.
é    )ÚannotationsN)ÚAnyÚDictÚList)Úurlparse)ÚWebSearchProvider)Ú__version__zhttps://api.perplexity.aiz%https://www.perplexity.ai/account/apiz%https://hermes-agent.nousresearch.comzHermes AgentzHermesAgent/zhermes-agent)zHTTP-RefererzX-Titlez
User-AgentzX-Pplx-Integrationé   i @  i   c                 ó   — dt         › �S )Nz,PERPLEXITY_API_KEY is not set. Get a key at ©Ú_KEY_URL© ó    úD/home/ubuntu/.hermes/hermes-agent/plugins/web/perplexity/provider.pyÚ_missing_key_errorr   D   s   € Ø9¼(¸ÐDÐDr   c                óÜ  — ddl m}  |d«      }|st        t        «       «      ‚ |d«      xs t        j                  d«      }|› d| j                  d«      › �}t        j                  d| |«       t        j                  ||dd|› �d	d
œt        ¥¬«      }|j                  dk\  r:|j                  xs dj                  «       }t        |xs d|j                  › �«      ‚|j                  «       S )a  POST to the Perplexity API and return the parsed JSON response.

    Raises ``ValueError`` when the key is missing or on any non-2xx status,
    carrying the response body so Perplexity's own error text (invalid key,
    BAD_REQUEST, rate limit) reaches the model verbatim.
    r   ©Úget_provider_envÚPERPLEXITY_API_KEYÚPERPLEXITY_BASE_URLú/zPerplexity %s request to %sé<   zBearer zapplication/json)ÚAuthorizationzContent-Type)ÚjsonÚtimeoutÚheadersi�  Ú zHTTP )Úagent.web_search_providerr   Ú
ValueErrorr   Ú_DEFAULT_BASE_URLÚrstripÚlstripÚloggerÚinfoÚhttpxÚpostÚ_HEADERSÚstatus_codeÚtextÚstripr   )ÚendpointÚpayloadr   Úapi_keyÚbase_urlÚurlÚresponseÚbodys           r   Ú_perplexity_requestr2   H   sð   € õ ;áÐ3Ó4€GÙÜÔ+Ó-Ó.Ð.Ù Ð!6Ó7ÒLÔ;L×TÑTÐUXÓY€HØˆJ�a˜Ÿ™¨Ó,Ð-Ð
.€CÜ
‡K�KÐ-¨x¸Ô=ä�z‰zØØØà& w iÐ0Ø.ñ
ô ð
ô		€Hð ×Ñ˜sÒ"Ø—‘Ò# ×*Ñ*Ó,ˆÜ˜Ò? 5¨×)=Ñ)=Ð(>Ð!?Ó@Ð@Ø�=‰=‹?Ðr   c           	     ó
  — g }t        | j                  d«      xs g «      D ]Z  \  }}|j                  |j                  dd«      xs d|j                  dd«      xs d|j                  dd«      xs d|dz   dœ«       Œ\ dd	|id
œS )zJMap Search API ``{results: [{title,url,snippet,...}]}`` to the tool shape.ÚresultsÚtitler   r/   Úsnippeté   )r5   r/   ÚdescriptionÚpositionTÚweb)ÚsuccessÚdata)Ú	enumerateÚgetÚappend)r0   Úweb_resultsÚiÚresults       r   Ú_normalize_search_resultsrC   h   s’   € à€KÜ˜xŸ|™|¨IÓ6Ò<¸"Ó=ò 
‰	ˆˆ6Ø×ÑàŸ™ G¨RÓ0Ò6°BØ—z‘z %¨Ó,Ò2°Ø%Ÿz™z¨)°RÓ8Ò>¸BØ ™Eñ	õ	
ð
ð  e¨[Ð%9Ñ:Ð:r   c                ó|  — | j                  d«      xs g D �ci c]&  }t        |t        «      sŒ|j                  dd«      |“Œ( }}g }|D ]k  }|j                  |i «      }|j                  d«      xs d}|d||d|idœ}|j                  d«      }	|	s|s|	rt        |	«      nd|d<   |j	                  |«       Œm |S c c}w )	u+  Map ``{results: [{url,text?,tokens_count?,error?}]}`` to extract documents.

    One document per requested URL, in request order. A URL the backend
    omitted or flagged with ``error`` becomes a document carrying ``error``
    rather than raising â€” a 200 does not mean every page succeeded.
    r4   r/   r   r)   Ú	sourceURL)r/   r5   ÚcontentÚraw_contentÚmetadataÚerrorzno content returned)r>   Ú
isinstanceÚdictÚstrr?   )
r0   ÚurlsÚrÚby_urlÚ	documentsr/   rB   r)   ÚdocrI   s
             r   Ú_normalize_snippetsrR   w   sÕ   € ð -5¯L©L¸Ó,CÒ,IÀrÖb aÌjÐYZÔ\`ÕNaˆa�e‰e�E˜2Ó Ñ!Ðb€FÐbØ&(€IØò ˆØ—‘˜C Ó$ˆØ�z‰z˜&Ó!Ò' RˆàØØØØ$ cÐ*ñ
ˆð —
‘
˜7Ó#ˆÙ™Ù).œ3˜uœ:Ð4IˆC�‰LØ×Ñ˜Õðð Ðùò! cs
   ˜B9®B9c                ó  — g }| D ]Ö  }t        |«      }|j                  j                  dd«      j                  dd«      j                  dd«      j                  «       D ]E  }|j	                  «       |vsŒ|j                  «       rŒ'|j                  |j	                  «       «       ŒG |j                  j                  d«      rŒ¼|j                  |j                  «       ŒØ dj                  |«      dd xs dj                  | «      dd S )zUDerive a relevance query from URL path words (``/bloom-filter`` -> ``bloom filter``).Ú-Ú Ú_r   Niô  )
r   ÚpathÚreplaceÚsplitÚlowerÚisdigitr?   r*   ÚnetlocÚjoin)rM   Úwordsr/   ÚparsedÚtokens        r   Ú_query_for_urlsra   ‘   sÚ   € à€EØò (ˆÜ˜#“ˆØ—[‘[×(Ñ(¨¨cÓ2×:Ñ:¸3ÀÓD×LÑLÈSÐRUÓV×\Ñ\Ó^ò 	,ˆEØ�{‰{‹} EÒ)°%·-±-µ/Ø—‘˜UŸ[™[›]Õ+ð	,ð �{‰{× Ñ  Õ%Ø�L‰L˜Ÿ™Õ'ð(ð �8‰8�E‹?˜4˜CÐ Ò8 C§H¡H¨T£N°4°CÐ$8Ð8r   c                  óf   — 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d	„Zy
)ÚPerplexityWebSearchProviderzHPerplexity Search API (search) + content snippets (extract), keyed only.c                 ó   — y)NÚ
perplexityr   ©Úselfs    r   Únamez PerplexityWebSearchProvider.name¡   ó   € àr   c                 ó   — y)NÚ
Perplexityr   rf   s    r   Údisplay_namez(PerplexityWebSearchProvider.display_name¥   ri   r   c                ó0   — ddl m} t         |d«      «      S )zDReturn True when ``PERPLEXITY_API_KEY`` is set to a non-empty value.r   r   r   )r   r   Úbool)rg   r   s     r   Úis_availablez(PerplexityWebSearchProvider.is_available©   s   € å>äÑ$Ð%9Ó:Ó;Ð;r   c                 ó   — y©NTr   rf   s    r   Úsupports_searchz+PerplexityWebSearchProvider.supports_search¯   ó   € Ør   c                 ó   — yrq   r   rf   s    r   Úsupports_extractz,PerplexityWebSearchProvider.supports_extract²   rs   r   c                ób  — 	 ddl m}  |«       rdddœS t        j                  d||«       t	        d|t        dt        |t        «      «      d	d
œ«      }t        |«      S # t        $ r}dt        |«      dœcY d}~S d}~wt        $ r(}t        j                  d|«       dd|› �dœcY d}~S d}~ww xY w)a
  Execute a Perplexity Search API query.

        ``search_context_size: low`` keeps ``snippet`` at description length;
        the default (``high``) returns multi-KB page excerpts per hit, which
        belongs in ``web_extract`` rather than a results list.
        r   ©Úis_interruptedFÚInterrupted)r;   rI   z"Perplexity search: '%s' (limit=%d)Úsearchr7   Úlow)ÚqueryÚmax_resultsÚsearch_context_sizeNzPerplexity search error: %szPerplexity search failed: )Útools.interruptrx   r#   r$   r2   ÚmaxÚminÚ_MAX_SEARCH_RESULTSrC   r   rL   Ú	ExceptionÚwarning)rg   r|   Úlimitrx   ÚrawÚexcs         r   rz   z"PerplexityWebSearchProvider.searchµ   s°   € ð	SÝ6áÔØ#(°=ÑAÐAä�K‰KÐ<¸eÀUÔKÜ%Øà"Ü#& q¬#¨eÔ5HÓ*IÓ#JØ+0ñóˆCô -¨SÓ1Ð1øÜò 	9Ø$¬s°3«xÑ8Õ8ûÜò 	SÜ�N‰NÐ8¸#Ô>Ø$Ð1KÈCÈ5Ð/QÑRÕRûð	Sús5   ‚A ”A	A Á	B.Á'A:Á4B.Á:B.ÂB)Â#B.Â)B.c           
     ó  — 	 ddl m}  |«       r|D �cg c]  }|dddœ‘Œ
 c}S t        j                  dt	        |«      «       t        dt        |«      t        |«      t        t        dœ«      }t        |t        |«      «      S c c}w # t        $ r.}|D �cg c]  }|ddt        |«      d	œ‘Œ nc c}w c}cY d
}~S d
}~wt        $ r>}t        j                  d|«       |D �cg c]  }|ddd|› �d	œ‘Œ nc c}w c}cY d
}~S d
}~ww xY w)uÍ   Return query-relevant snippets for one or more URLs.

        Sync â€” the underlying call is httpx.post(...). Per-URL failures
        become items with ``error``; a missing key errors every URL.
        r   rw   ry   r   )r/   rI   r5   zPerplexity snippets: %d URL(s)zsdk/content/snippets)r|   rM   Ú
max_tokensÚmax_tokens_per_page)r/   r5   rF   rI   NzPerplexity extract error: %szPerplexity extract failed: )r   rx   r#   r$   Úlenr2   ra   ÚlistÚ_MAX_TOKENSÚ_MAX_TOKENS_PER_PAGErR   r   rL   rƒ   r„   )rg   rM   Úkwargsrx   Úur†   r‡   s          r   Úextractz#PerplexityWebSearchProvider.extractÒ   sý   € ð	Ý6áÔØQUÖVÈA ¨MÀBÓGÒVÐVä�K‰KÐ8¼#¸d»)ÔDÜ%Ø&ä,¨TÓ2Ü  ›JÜ"-Ü+?ñ	óˆCô ' s¬D°«JÓ7Ð7ùò Wøô ò 	]ØW[Ö\ÐRS˜A¨°rÄCÈÃHÓMÑ\ùÔ\Õ\ûÜò 	Ü�N‰NÐ9¸3Ô?ð öàð  B°2ÐB]Ð^aÐ]bÐ@cÓdñùô õ ûð	ús]   ‚B “B B £AB ÂB Â	DÂB9ÂB,Â+B9Â3DÂ9DÃC>ÃC1Ã0C>Ã8DÃ>Dc                ó$   — dddddt         dœgddœS )Nrk   Úpaidui   Perplexity Search API â€” ranked, date-stamped web results plus query-relevant page snippets for extract.r   zPerplexity API key)ÚkeyÚpromptr/   )rh   ÚbadgeÚtagÚenv_varsÚweb_tierr   rf   s    r   Úget_setup_schemaz,PerplexityWebSearchProvider.get_setup_schemaò   s1   € à Øð<ð
 0Ø2Ü#ñðð ñ
ð 	
r   N©ÚreturnrL   )rœ   rn   )é   )r|   rL   r…   Úintrœ   úDict[str, Any])rM   ú	List[str]r�   r   rœ   úList[Dict[str, Any]])rœ   rŸ   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__Úpropertyrh   rl   ro   rr   ru   rz   r‘   rš   r   r   r   rc   rc   ž   sK   „ ÙRàòó ðð òó ðó<óóôSó:ô@
r   rc   r›   )r+   rL   r,   rŸ   rœ   rŸ   )r0   rŸ   rœ   rŸ   )r0   rŸ   rM   r    rœ   r¡   )rM   r    rœ   rL   )r¥   Ú
__future__r   ÚloggingÚtypingr   r   r   Úurllib.parser   r%   r   r   Ú
hermes_clir	   Ú_HERMES_VERSIONÚ	getLoggerr¢   r#   r    r   r'   r‚   r�   rŽ   r   r2   rC   rR   ra   rc   r   r   r   ú<module>r®      sœ   ðñõB #ã ß "Ñ "Ý !ã å 7Ý 5à	ˆ×	Ñ	˜8Ó	$€à/Ð Ø2€ð <ØØ  Ð 1Ð2Ø(ñ	€ð Ð à€ØÐ óEóó@;óó4
9ôd
Ð"3õ d
r   