Commit Graph

8 Commits

Author SHA1 Message Date
kshitijk4poor
509d0f12d4 fix(web): give the managed-search refusal an accurate reason
The refusal named a cause that cannot happen. `_vendor_gateway` never contacts the
network, so "unreachable" was wrong for the no-identity, failed-lookup and
not-entitled cases alike — and a guest was told it had no Nous identity when it
holds a perfectly good one.

Root of the guest case: `_error_info` dropped `account_tier`, so a guest whose
account lookup failed — precisely the state where the tier can no longer be read
from the JWT, because the token is inside its refresh window — came back looking
like a registered identity. Carry the stored tier through the error snapshot, and
both this refusal and every other tier-aware surface see it.

Pick the reason in priority order (guest / no usable identity / unreachable) in
one helper beside `fast_search_entitled`, so the predicate and its explanation
cannot drift, and phrase it as a clause for `selection_error`'s "but …" template —
the canonical `FREE_TIER_NEEDS_ACCOUNT` strings are standalone sentences and read
wrong embedded mid-sentence.

Also record why the `error` clause in `fast_search_entitled` is load-bearing
rather than defensive, so it is not removed as belt-and-braces.

The test helper now mirrors what anon_auth actually persists (the tier alongside
the token), which is what makes the failed-lookup case reproducible at all.
2026-10-01 23:55:54 +05:30
kshitijk4poor
9ab49e73b4 fix(web): honest guest refusal, and one route walk per search dispatch
Two review findings on the free fast-search route.

A guest with a stored `web.backend: nous` holds a real Nous identity and a
usable token; the route excludes it by tier, not by absence. The refusal said
"no Nous identity or unreachable", which is wrong for exactly the cohort this
route was gated against. Reuse the canonical FREE_TIER_NEEDS_ACCOUNT text for
the anonymous tier and keep the unreachable wording for everyone else.

search() then called _managed_web_search() a third time just to choose that
message, after _managed_gateway() had already resolved the route — a full
autodetect ladder plus a Portal account read and token peek. Let _managed_gateway
accept the answer its caller already computed so a dispatch walks the ladder
once.

Docs: the autodetect row implies the keyless switch governs a registered
identity's web_search. It does not.
2026-10-01 23:55:54 +05:30
Robin Fernandes
fd24751a9f fix(web): serve free managed fast search to every Nous identity
The Tool Gateway serves Perplexity POST /search with search_type: "fast"
to any Nous identity without funding checks, but Hermes gated the route
locally behind managed_nous_tools_enabled() (paid access or a live tool
pool), and autodetect only selected managed Perplexity after the gated
Firecrawl probe passed. Anonymous and credit-exhausted users fell through
to the keyless ring without trying it.

Resolve the Perplexity search route on identity alone
(resolve_free_search_gateway: usable Nous token, same guest-disabled and
refresh rules), and let autodetect pick it when nothing explicit is
configured. Every other vendor keeps the entitlement gate, and the billed
managed-Firecrawl fallback now requires it too, so a free-search-only
caller whose call fails goes to the keyless rescue ring as before.
Explicit selections and direct keys keep precedence.
2026-10-01 23:55:54 +05:30
ethernet
7c3d5e93e8 Merge remote-tracking branch 'origin/main' into ethie/pm-clean 2026-09-24 16:17:47 -04:00
Robin Fernandes
749220ef00 feat(web): serve managed search through Perplexity
Use Perplexity for Nous-managed search, retaining Firecrawl for extract
and as a per-call search fallback. Explicit search overrides and direct
keys keep their own billing paths; fallback results are never cached.
When the managed route is selected but the Tool Gateway is unavailable
(unentitled account or no Nous token), search reports that selection
error instead of asking for a direct key the user never chose.

The managed search vendor is unannounced, so user-facing copy names the
capability rather than the vendor: status, portal and docs say "managed
web search", and the fallback annotation reads `managed_primary`. Direct-key
configuration docs are unchanged.

Routing, auth, payload, cache and entitlement regressions are covered
through real config loading and local HTTP.
2026-09-24 16:17:16 -04:00
ethernet
c13ea774e6 refactor: make install-stamp.json the single runtime version identity
Runtime identity resolved through hermes_cli.__version__ (a static 0.0.0
on source installs, rewritten by release stamping) leaked v0.0.0 into
About, /api/health, User-Agents, and plugin compat, and source updates
showed "couldn't reach update server" because identity and channel
authority disagreed with the checkout.

Now: get_version_info() resolves install stamp -> live git -> unknown,
never pyproject metadata, never a package constant. Source checkouts
derive identity from their reachable release tag; the completion tail of
every successful install/update/historical takeover atomically rewrites
install-stamp.json with that identity; a stale source stamp whose commit
no longer matches HEAD defers to live git. ACP/TUI use derived_version
for display and base_version for protocol fields; all ~44 runtime
__version__ consumers migrated; hermes_cli.__version__ and generated
_version.py are gone; release stamping only touches the native manifests
external builders consume (nix/tauri/cargo) and passes release identity
straight into write_install_stamp.py; pyproject.toml stays inert 0.0.0.
Desktop no longer synthesizes a competing install-stamp.json: the
checkout owns its stamp, and desktop-bootstrap classification keys on
the bootstrap-complete marker. verify-bootstrap-version-stamp.py now
cross-checks the checkout's stamp (baseVersion + commit == HEAD).

Validation: 31-file focused suite green (version identity, stamping,
adoption, providers, gateway, acp/tui runtime identity, api server via
extras env, release graph); desktop tsc + 25 vitest green; real-repo
probe: base=unknown derived=git.0635606.dirty source=git on this
checkout; clean-env imports resolve entirely from this tree; windows
footgun + compat-pointer scans clean.
2026-09-23 11:41:01 -04:00
Andrew Madson
1ad89ac018 feat(web): send Hermes identity headers on Perplexity requests
Add HTTP-Referer, X-Title, User-Agent (HermesAgent/<version>) and
X-Pplx-Integration: hermes-agent to the Perplexity web provider's
/search and /sdk/content/snippets requests. Same static headers Hermes
already sends Kimi and OpenCode; nothing per-user, no extra requests.
2026-09-14 13:04:39 -07:00
Teknium
f1ccf436a2 feat(web): Perplexity Search API as a web_search + web_extract backend
Adds plugins/web/perplexity — a keyed-only WebSearchProvider over httpx:

- search: POST https://api.perplexity.ai/search (documented Search API),
  search_context_size=low so `snippet` stays description-sized;
  results[].snippet -> description, max_results capped at the API's 20.
- extract: POST /sdk/content/snippets — the query-relevant page-excerpt
  route behind `pplx content snippets` (the CLI's `content fetch` is
  deprecated upstream). web_extract has no query, so the URLs' path words
  serve as the relevance query; per-URL `error` entries survive a 200.
- Wired into the same touchpoints as the other keyed vendors: legacy
  backend set + credential ladder + availability probe (web_tools),
  registry preference walk, OPTIONAL_ENV_VARS, `hermes config`/status/
  dump key lists, nous_subscription direct-credential detection, setup
  summary, test conftests, docs.

Not a keyless-ring member (Perplexity has no anonymous tier). Related
closed PRs #9192 / #23981 / #45225 predate the plugin ABC.
2026-09-04 07:17:00 -07:00