mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
feat(spotify): consolidate tools (9→7), add spotify skill, surface in hermes setup (#15154)
Three quality improvements on top of #15121 / #15130 / #15135: 1. Tool consolidation (9 → 7) - spotify_saved_tracks + spotify_saved_albums → spotify_library with kind='tracks'|'albums'. Handler code was ~90 percent identical across the two old tools; the merge is a behavioral no-op. - spotify_activity dropped. Its 'now_playing' action was a duplicate of spotify_playback.get_currently_playing (both return identical 204/empty payloads). Its 'recently_played' action moves onto spotify_playback as a new action — history belongs adjacent to live state. - Net: each API call ships 2 fewer tool schemas when the Spotify toolset is enabled, and the action surface is more discoverable (everything playback-related is on one tool). 2. Spotify skill (skills/media/spotify/SKILL.md) Teaches the agent canonical usage patterns so common requests don't balloon into 4+ tool calls: - 'play X' = one search, then play by URI (not search + scan + describe + play) - 'what's playing' = single get_currently_playing (no preflight get_state chain) - Don't retry on '403 Premium required' or '403 No active device' — both require user action - URI/URL/bare-ID format normalization - Full failure-mode reference for 204/401/403/429 3. Surfaced in 'hermes setup' tool status Adds 'Spotify (PKCE OAuth)' to the tool status list when auth.json has a Spotify access/refresh token. Matches the homeassistant pattern but reads from auth.json (OAuth-based) rather than env vars. Docs updated to reflect the new 7-tool surface, and mention the companion skill in the 'Using it' section. Tests: 54 passing (client 22, auth 15, tools_config 35 — 18 = 54 after renaming/replacing the spotify_activity tests with library + recently_played coverage). Docusaurus build clean.
This commit is contained in:
parent
9d1b277e1d
commit
e5d41f05d4
6 changed files with 254 additions and 127 deletions
|
|
@ -148,7 +148,7 @@ def test_get_currently_playing_returns_explanatory_empty_payload(monkeypatch: py
|
|||
}
|
||||
|
||||
|
||||
def test_spotify_activity_now_playing_returns_explanatory_empty_result(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
def test_spotify_playback_get_currently_playing_returns_explanatory_empty_result(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
spotify_tool,
|
||||
"_spotify_client",
|
||||
|
|
@ -159,11 +159,11 @@ def test_spotify_activity_now_playing_returns_explanatory_empty_result(monkeypat
|
|||
}),
|
||||
)
|
||||
|
||||
payload = json.loads(spotify_tool._handle_spotify_activity({"action": "now_playing"}))
|
||||
payload = json.loads(spotify_tool._handle_spotify_playback({"action": "get_currently_playing"}))
|
||||
|
||||
assert payload == {
|
||||
"success": True,
|
||||
"action": "now_playing",
|
||||
"action": "get_currently_playing",
|
||||
"is_playing": False,
|
||||
"status_code": 204,
|
||||
"message": "Spotify is not currently playing anything. Start playback in Spotify and try again.",
|
||||
|
|
@ -242,3 +242,58 @@ def test_library_remove_uses_generic_library_endpoint(
|
|||
{"uris": ",".join(expected_uris)},
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
|
||||
def test_spotify_library_tracks_list_routes_to_saved_tracks(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
seen: list[str] = []
|
||||
|
||||
class _LibStub:
|
||||
def get_saved_tracks(self, **kw):
|
||||
seen.append("tracks")
|
||||
return {"items": [], "total": 0}
|
||||
|
||||
def get_saved_albums(self, **kw):
|
||||
seen.append("albums")
|
||||
return {"items": [], "total": 0}
|
||||
|
||||
monkeypatch.setattr(spotify_tool, "_spotify_client", lambda: _LibStub())
|
||||
json.loads(spotify_tool._handle_spotify_library({"kind": "tracks", "action": "list"}))
|
||||
assert seen == ["tracks"]
|
||||
|
||||
|
||||
def test_spotify_library_albums_list_routes_to_saved_albums(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
seen: list[str] = []
|
||||
|
||||
class _LibStub:
|
||||
def get_saved_tracks(self, **kw):
|
||||
seen.append("tracks")
|
||||
return {"items": [], "total": 0}
|
||||
|
||||
def get_saved_albums(self, **kw):
|
||||
seen.append("albums")
|
||||
return {"items": [], "total": 0}
|
||||
|
||||
monkeypatch.setattr(spotify_tool, "_spotify_client", lambda: _LibStub())
|
||||
json.loads(spotify_tool._handle_spotify_library({"kind": "albums", "action": "list"}))
|
||||
assert seen == ["albums"]
|
||||
|
||||
|
||||
def test_spotify_library_rejects_missing_kind() -> None:
|
||||
payload = json.loads(spotify_tool._handle_spotify_library({"action": "list"}))
|
||||
assert "kind" in (payload.get("error") or "").lower()
|
||||
|
||||
|
||||
def test_spotify_playback_recently_played_action(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""recently_played is now an action on spotify_playback (folded from spotify_activity)."""
|
||||
seen: list[dict] = []
|
||||
|
||||
class _RecentStub:
|
||||
def get_recently_played(self, **kw):
|
||||
seen.append(kw)
|
||||
return {"items": [{"track": {"name": "x"}}]}
|
||||
|
||||
monkeypatch.setattr(spotify_tool, "_spotify_client", lambda: _RecentStub())
|
||||
payload = json.loads(spotify_tool._handle_spotify_playback({"action": "recently_played", "limit": 5}))
|
||||
assert seen and seen[0]["limit"] == 5
|
||||
assert isinstance(payload, dict)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue