mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
feat(x_search): auto-enable toolset when xAI OAuth or XAI_API_KEY is configured (#27376)
The x_search toolset is gated on xAI credentials (SuperGrok OAuth or XAI_API_KEY), but it was staying off-by-default even for users who had already configured those credentials — they had to also click through `hermes tools` → X (Twitter) Search to flip it on. The HASS_TOKEN → homeassistant rule already handles the parallel case cleanly; x_search needs the same treatment. Why a separate code path from HASS_TOKEN: `ha_*` tools live inside the `hermes-cli` composite, so the subset-inference loop picks them up and the HASS branch just unmasks default_off. `x_search` is its own one-tool toolset NOT in the composite, so the subset loop never adds it — it has to be injected directly. * Add `_xai_credentials_present()` — side-effect-free check for stored xAI OAuth tokens or XAI_API_KEY (dotenv or env). No network. * In `_get_platform_tools()` else branch (no explicit user config), inject `x_search` and carve a parallel hole in default_off. * Auto-enable does NOT fire when the user has saved an explicit toolset list via `hermes tools` — that list stays authoritative. * `agent.disabled_toolsets: [x_search]` still wins (global override). Tests: 4 new in test_tools_config.py covering OAuth path, API-key path, no-creds path, and explicit-config-respect. All pass alongside existing 70/70 in that file.
This commit is contained in:
parent
519657aa98
commit
ad1aa1a037
2 changed files with 110 additions and 4 deletions
|
|
@ -88,12 +88,40 @@ CONFIGURABLE_TOOLSETS = [
|
|||
# who want it opt in via `hermes tools` → Video Generation, which walks
|
||||
# them through provider + model selection.
|
||||
#
|
||||
# X search is off by default — gated on xAI credentials (SuperGrok OAuth
|
||||
# or XAI_API_KEY). Users opt in via `hermes tools` → X (Twitter) Search,
|
||||
# which walks them through credential setup. The tool's check_fn means
|
||||
# the schema won't appear to the model even if enabled without credentials.
|
||||
# X search is off by default for users without xAI credentials, but
|
||||
# auto-enables when SuperGrok OAuth tokens are stored OR XAI_API_KEY is
|
||||
# set — mirroring the HASS_TOKEN → homeassistant auto-enable below. The
|
||||
# `hermes tools` → X (Twitter) Search setup walks users through credential
|
||||
# setup. The tool's check_fn means the schema still won't appear to the
|
||||
# model if the credential later goes missing or expires.
|
||||
_DEFAULT_OFF_TOOLSETS = {"moa", "homeassistant", "spotify", "discord", "discord_admin", "video", "video_gen", "x_search"}
|
||||
|
||||
|
||||
def _xai_credentials_present() -> bool:
|
||||
"""Cheap, side-effect-free check for usable xAI credentials.
|
||||
|
||||
Used to auto-enable the ``x_search`` toolset when the user has either
|
||||
completed xAI Grok OAuth (SuperGrok subscription) or set
|
||||
``XAI_API_KEY``. Does NOT hit the network — only inspects the local
|
||||
auth store and environment. The tool's runtime ``check_fn`` still
|
||||
gates schema registration if creds later expire or get revoked.
|
||||
"""
|
||||
try:
|
||||
from hermes_cli.auth import _read_xai_oauth_tokens
|
||||
|
||||
_read_xai_oauth_tokens()
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
from tools.xai_http import get_env_value as _xai_get_env_value
|
||||
|
||||
if str(_xai_get_env_value("XAI_API_KEY") or "").strip():
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return bool(str(os.environ.get("XAI_API_KEY") or "").strip())
|
||||
|
||||
# Platform-scoped toolsets: only appear in the `hermes tools` checklist for
|
||||
# these platforms, and only resolve/save for these platforms. A toolset
|
||||
# absent from this map is available on every platform (current behaviour).
|
||||
|
|
@ -1129,6 +1157,23 @@ def _get_platform_tools(
|
|||
if ts_tools and ts_tools.issubset(all_tool_names):
|
||||
enabled_toolsets.add(ts_key)
|
||||
|
||||
# Auto-enable ``x_search`` when xAI credentials are configured.
|
||||
# Unlike ``homeassistant`` (whose ``ha_*`` tools live inside the
|
||||
# platform composite and thus pass the subset check above),
|
||||
# ``x_search`` is its own one-tool toolset that the composite does
|
||||
# NOT include, so the subset loop never picks it up. Inject it
|
||||
# directly here, mirroring the HASS_TOKEN → ``homeassistant`` rule
|
||||
# below: once you have working creds, you don't have to also click
|
||||
# through ``hermes tools`` to flip the toolset on. Only fires when
|
||||
# the user has not yet saved an explicit toolset list — once they
|
||||
# do, the saved list is authoritative.
|
||||
x_search_auto_enabled = (
|
||||
_toolset_allowed_for_platform("x_search", platform)
|
||||
and _xai_credentials_present()
|
||||
)
|
||||
if x_search_auto_enabled:
|
||||
enabled_toolsets.add("x_search")
|
||||
|
||||
default_off = set(_DEFAULT_OFF_TOOLSETS)
|
||||
# Legacy safety: if the platform's own name matches a default-off
|
||||
# toolset (e.g. `homeassistant` platform + `homeassistant` toolset),
|
||||
|
|
@ -1146,6 +1191,11 @@ def _get_platform_tools(
|
|||
# regressed after #14798 made cron honor per-platform tool config.
|
||||
if "homeassistant" in default_off and os.getenv("HASS_TOKEN"):
|
||||
default_off.remove("homeassistant")
|
||||
# Symmetric carve-out for x_search auto-enable (see the inject
|
||||
# block above). Without this, the default_off subtraction would
|
||||
# strip the entry we just added.
|
||||
if x_search_auto_enabled and "x_search" in default_off:
|
||||
default_off.remove("x_search")
|
||||
enabled_toolsets -= default_off
|
||||
|
||||
# Recover non-configurable platform toolsets (e.g. discord, feishu_doc,
|
||||
|
|
|
|||
|
|
@ -125,6 +125,62 @@ def test_get_platform_tools_homeassistant_toolset_off_for_cron_when_hass_token_m
|
|||
assert "homeassistant" not in cron_enabled
|
||||
|
||||
|
||||
def test_get_platform_tools_x_search_auto_enabled_when_xai_oauth_present(monkeypatch):
|
||||
"""x_search toolset auto-enables across platforms when xAI Grok OAuth
|
||||
tokens are present, mirroring the HASS_TOKEN → homeassistant rule.
|
||||
|
||||
The user already authenticated via SuperGrok OAuth; they shouldn't have
|
||||
to also click through `hermes tools` → X (Twitter) Search to flip the
|
||||
toolset on. Tool's check_fn still gates schema registration if creds
|
||||
later go missing.
|
||||
"""
|
||||
monkeypatch.delenv("XAI_API_KEY", raising=False)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.tools_config._xai_credentials_present", lambda: True
|
||||
)
|
||||
|
||||
for plat in ("cli", "cron", "telegram"):
|
||||
enabled = _get_platform_tools({}, plat)
|
||||
assert "x_search" in enabled, f"x_search missing for {plat}"
|
||||
|
||||
|
||||
def test_get_platform_tools_x_search_auto_enabled_when_xai_api_key_present(monkeypatch):
|
||||
"""x_search toolset auto-enables when XAI_API_KEY is set, even without
|
||||
OAuth tokens — the API-key path is a supported credential source."""
|
||||
monkeypatch.setenv("XAI_API_KEY", "fake-xai-key")
|
||||
|
||||
cli_enabled = _get_platform_tools({}, "cli")
|
||||
assert "x_search" in cli_enabled
|
||||
|
||||
|
||||
def test_get_platform_tools_x_search_off_when_no_xai_credentials(monkeypatch):
|
||||
"""Without any xAI credentials, x_search stays off — preserves the
|
||||
"don't ship the schema to users who can't use it" default."""
|
||||
monkeypatch.delenv("XAI_API_KEY", raising=False)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.tools_config._xai_credentials_present", lambda: False
|
||||
)
|
||||
|
||||
cli_enabled = _get_platform_tools({}, "cli")
|
||||
assert "x_search" not in cli_enabled
|
||||
|
||||
|
||||
def test_get_platform_tools_x_search_respects_explicit_config(monkeypatch):
|
||||
"""Once the user has saved an explicit toolset list via `hermes tools`,
|
||||
that list is authoritative — x_search auto-enable does NOT fire even
|
||||
when xAI creds exist. The saved list represents deliberate choices."""
|
||||
monkeypatch.delenv("XAI_API_KEY", raising=False)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.tools_config._xai_credentials_present", lambda: True
|
||||
)
|
||||
|
||||
# User explicitly opted into spotify but not x_search via `hermes tools`.
|
||||
config = {"platform_toolsets": {"cli": ["hermes-cli", "spotify"]}}
|
||||
enabled = _get_platform_tools(config, "cli")
|
||||
assert "x_search" not in enabled
|
||||
assert "spotify" in enabled
|
||||
|
||||
|
||||
def test_get_platform_tools_expands_composite_when_mixed_with_configurable():
|
||||
"""``[hermes-cli, spotify]`` (composite + configurable) must keep the full
|
||||
``hermes-cli`` toolset alongside the explicit Spotify opt-in. The
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue