roon-mcp 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +61 -11
- package/dist/PlaybackService.d.ts +16 -0
- package/dist/PlaybackService.js +59 -2
- package/dist/PlaybackService.js.map +1 -1
- package/dist/PlaybackService.test.js +63 -0
- package/dist/PlaybackService.test.js.map +1 -1
- package/dist/RoonClient.d.ts +21 -0
- package/dist/RoonClient.js +35 -0
- package/dist/RoonClient.js.map +1 -1
- package/dist/RoonMcpServer.d.ts +3 -1
- package/dist/RoonMcpServer.js +215 -39
- package/dist/RoonMcpServer.js.map +1 -1
- package/dist/SearchService.d.ts +12 -0
- package/dist/SearchService.js +63 -1
- package/dist/SearchService.js.map +1 -1
- package/dist/SearchService.test.js +79 -4
- package/dist/SearchService.test.js.map +1 -1
- package/dist/TransportService.d.ts +73 -0
- package/dist/TransportService.js +248 -0
- package/dist/TransportService.js.map +1 -0
- package/dist/TransportService.test.d.ts +1 -0
- package/dist/TransportService.test.js +331 -0
- package/dist/TransportService.test.js.map +1 -0
- package/dist/ZoneService.d.ts +1 -0
- package/dist/ZoneService.js +9 -0
- package/dist/ZoneService.js.map +1 -1
- package/dist/ZoneService.test.js +1 -0
- package/dist/ZoneService.test.js.map +1 -1
- package/dist/ZoneSubscription.d.ts +67 -0
- package/dist/ZoneSubscription.js +187 -0
- package/dist/ZoneSubscription.js.map +1 -0
- package/dist/ZoneSubscription.test.d.ts +1 -0
- package/dist/ZoneSubscription.test.js +185 -0
- package/dist/ZoneSubscription.test.js.map +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +39 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/RoonMcpServer.js
CHANGED
|
@@ -21,13 +21,15 @@ export class RoonMcpServer {
|
|
|
21
21
|
search;
|
|
22
22
|
tracks;
|
|
23
23
|
playback;
|
|
24
|
+
transport;
|
|
24
25
|
server;
|
|
25
|
-
constructor(roon, zones, search, tracks, playback) {
|
|
26
|
+
constructor(roon, zones, search, tracks, playback, transport) {
|
|
26
27
|
this.roon = roon;
|
|
27
28
|
this.zones = zones;
|
|
28
29
|
this.search = search;
|
|
29
30
|
this.tracks = tracks;
|
|
30
31
|
this.playback = playback;
|
|
32
|
+
this.transport = transport;
|
|
31
33
|
this.server = new McpServer({
|
|
32
34
|
name: "roon-mcp",
|
|
33
35
|
version: "0.1.0",
|
|
@@ -37,8 +39,12 @@ export class RoonMcpServer {
|
|
|
37
39
|
registerTools() {
|
|
38
40
|
this.server.registerTool("list_zones", {
|
|
39
41
|
title: "List Roon zones",
|
|
40
|
-
description: "
|
|
41
|
-
"
|
|
42
|
+
description: "Use this when the user asks which rooms, speakers, or outputs Roon can " +
|
|
43
|
+
"play to, or before starting playback when the target zone is unclear " +
|
|
44
|
+
"(e.g. \"what zones are on?\", \"play in the kitchen\", \"which speaker is " +
|
|
45
|
+
"in the office?\"). Lists every zone/output the paired Core exposes with " +
|
|
46
|
+
"its id, display name, current playback state, and output ids. Call this " +
|
|
47
|
+
"first if no zone is obvious and ROON_DEFAULT_ZONE is not set.",
|
|
42
48
|
inputSchema: {},
|
|
43
49
|
}, async () => {
|
|
44
50
|
try {
|
|
@@ -52,24 +58,38 @@ export class RoonMcpServer {
|
|
|
52
58
|
});
|
|
53
59
|
this.server.registerTool("search_music", {
|
|
54
60
|
title: "Search Roon music",
|
|
55
|
-
description: "
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"genre
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
61
|
+
description: "Use this when the user names music to find — an artist, album, track, " +
|
|
62
|
+
"playlist, radio station, or genre (e.g. \"find Tycho\", \"look up the " +
|
|
63
|
+
"album In Rainbows\", \"play some Psytrance\", \"anything by Ryuichi " +
|
|
64
|
+
"Sakamoto?\"). Resolves a free-text query into ranked Roon browse " +
|
|
65
|
+
"candidates. Optionally restrict to one item type (artist, album, track, " +
|
|
66
|
+
"genre, playlist, radio); for non-genre types, an empty typed search " +
|
|
67
|
+
"broadens to all categories. type:\"genre\" is special — genres don't " +
|
|
68
|
+
"appear in Roon's flat search, so the server walks the dedicated Genres " +
|
|
69
|
+
"tree and returns the nearest-match genre nodes (with parent path in the " +
|
|
70
|
+
"subtitle) without broadening; e.g. \"Psychedelic Trance\" yields " +
|
|
71
|
+
"\"Psytrance\"/\"Trance\". Set includeStreaming:true (meaningful for " +
|
|
72
|
+
"type:\"genre\" and type:\"artist\") to also pull a track mix from " +
|
|
73
|
+
"streaming services (e.g. TIDAL): for a genre the server takes the " +
|
|
74
|
+
"genre-relevant albums and samples tracks across them; for an artist " +
|
|
75
|
+
"the server runs a track search and filters to entries by that artist. " +
|
|
76
|
+
"Library candidates come first, then ready-to-play streaming tracks " +
|
|
77
|
+
"(each a `track` candidate, source group `Streaming`) appended after. " +
|
|
78
|
+
"An artist with no library content (e.g. subtitle \"0 Albums\") is " +
|
|
79
|
+
"reported via the result's `message` so the agent can opt in. Returns " +
|
|
80
|
+
"opaque, session-scoped item keys for use by the playback tools — pair " +
|
|
81
|
+
"with get_tracks_for to expand, then play_now or enqueue_and_play.",
|
|
67
82
|
inputSchema: {
|
|
68
|
-
query: z
|
|
83
|
+
query: z
|
|
84
|
+
.string()
|
|
85
|
+
.min(1)
|
|
86
|
+
.describe("What to search for — an artist, album, track, playlist, radio, or " +
|
|
87
|
+
"genre name (e.g. 'Tycho', 'In Rainbows', 'Dark Ambient')."),
|
|
69
88
|
type: z
|
|
70
89
|
.enum(MUSIC_ITEM_TYPES)
|
|
71
90
|
.optional()
|
|
72
|
-
.describe("Restrict
|
|
91
|
+
.describe("Restrict the search to one item type. Omit to broaden across all " +
|
|
92
|
+
"non-genre categories. Use 'genre' for music-genre lookups."),
|
|
73
93
|
limit: z
|
|
74
94
|
.number()
|
|
75
95
|
.int()
|
|
@@ -80,9 +100,11 @@ export class RoonMcpServer {
|
|
|
80
100
|
includeStreaming: z
|
|
81
101
|
.boolean()
|
|
82
102
|
.optional()
|
|
83
|
-
.describe("
|
|
84
|
-
"
|
|
85
|
-
"
|
|
103
|
+
.describe("Meaningful for type 'genre' and type 'artist': also pull a track " +
|
|
104
|
+
"mix from streaming services (e.g. TIDAL). For a genre, library " +
|
|
105
|
+
"genre nodes come first, then sampled streaming tracks. For an " +
|
|
106
|
+
"artist, library candidates come first, then streaming tracks by " +
|
|
107
|
+
"that artist. Default false (library only)."),
|
|
86
108
|
},
|
|
87
109
|
}, async (args) => {
|
|
88
110
|
try {
|
|
@@ -95,16 +117,21 @@ export class RoonMcpServer {
|
|
|
95
117
|
});
|
|
96
118
|
this.server.registerTool("get_tracks_for", {
|
|
97
119
|
title: "Expand a Roon item into tracks",
|
|
98
|
-
description: "
|
|
99
|
-
"
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
"
|
|
120
|
+
description: "Use this after search_music when the user wants a concrete list of " +
|
|
121
|
+
"songs — to preview tracks, build a queue, or pick one to start with " +
|
|
122
|
+
"(e.g. \"what tracks are on this album?\", \"give me 5 tracks of " +
|
|
123
|
+
"Dark Ambient\", \"what's on this playlist?\"). Expands an artist, " +
|
|
124
|
+
"album, genre, or playlist candidate into concrete playable tracks. " +
|
|
125
|
+
"Pass an itemKey from a recent search_music result. Returns track " +
|
|
126
|
+
"candidates with session-scoped item keys (use them promptly with " +
|
|
127
|
+
"enqueue_and_play). Non-expandable items return empty tracks with a " +
|
|
128
|
+
"skipped reason rather than an error.",
|
|
103
129
|
inputSchema: {
|
|
104
130
|
itemKey: z
|
|
105
131
|
.string()
|
|
106
132
|
.min(1)
|
|
107
|
-
.describe("
|
|
133
|
+
.describe("Item key to expand — from a recent search_music candidate (artist, " +
|
|
134
|
+
"album, genre, or playlist)."),
|
|
108
135
|
limit: z
|
|
109
136
|
.number()
|
|
110
137
|
.int()
|
|
@@ -124,22 +151,30 @@ export class RoonMcpServer {
|
|
|
124
151
|
});
|
|
125
152
|
this.server.registerTool("play_now", {
|
|
126
153
|
title: "Play an item now in a Roon zone",
|
|
127
|
-
description: "
|
|
128
|
-
"
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"
|
|
154
|
+
description: "Use this when the user wants one specific thing playing right now — " +
|
|
155
|
+
"an album, artist, playlist, genre mix, or single track (e.g. \"play " +
|
|
156
|
+
"Tycho\", \"put on In Rainbows\", \"start some Psytrance in the office\", " +
|
|
157
|
+
"\"play that track\"). Immediately plays a single search candidate in " +
|
|
158
|
+
"the target zone and replaces whatever was queued. Pass an itemKey from " +
|
|
159
|
+
"a recent search_music (or get_tracks_for) result — item keys are " +
|
|
160
|
+
"session-scoped, so use a fresh one. zoneId is optional: omit it to use " +
|
|
161
|
+
"ROON_DEFAULT_ZONE, or fall back to the only zone / an \"Office\" zone / " +
|
|
162
|
+
"the currently-playing zone; if it still can't decide it returns " +
|
|
163
|
+
"ZONE_AMBIGUOUS so the agent can ask the user or call list_zones. " +
|
|
164
|
+
"Optionally shuffle. Returns a PlaybackResult.",
|
|
133
165
|
inputSchema: {
|
|
134
166
|
zoneId: z
|
|
135
167
|
.string()
|
|
136
168
|
.min(1)
|
|
137
169
|
.optional()
|
|
138
|
-
.describe("Target zone id or output id
|
|
170
|
+
.describe("Target zone id or output id from list_zones (e.g. an id, or a name " +
|
|
171
|
+
"substring like 'Office'). Omit to use ROON_DEFAULT_ZONE or fall " +
|
|
172
|
+
"back automatically."),
|
|
139
173
|
itemKey: z
|
|
140
174
|
.string()
|
|
141
175
|
.min(1)
|
|
142
|
-
.describe("
|
|
176
|
+
.describe("What to play — item key from a recent search_music or get_tracks_for " +
|
|
177
|
+
"result (album, artist, playlist, genre, track, etc.)."),
|
|
143
178
|
shuffle: z
|
|
144
179
|
.boolean()
|
|
145
180
|
.optional()
|
|
@@ -156,10 +191,15 @@ export class RoonMcpServer {
|
|
|
156
191
|
});
|
|
157
192
|
this.server.registerTool("enqueue_and_play", {
|
|
158
193
|
title: "Build and start a curated Roon queue",
|
|
159
|
-
description: "
|
|
160
|
-
"
|
|
194
|
+
description: "Use this when the user wants a custom lineup — a mix of artists, a " +
|
|
195
|
+
"shuffled selection across albums, a hand-picked set of tracks, or any " +
|
|
196
|
+
"time \"queue\", \"setlist\", \"mix of\", or \"play these in order\" comes " +
|
|
197
|
+
"up (e.g. \"queue up five Tycho tracks then some Boards of Canada\", " +
|
|
198
|
+
"\"shuffle 10 ambient tracks\", \"build a set: artist A, then B, then C\"). " +
|
|
199
|
+
"Builds an ad-hoc queue from an ordered list of curated item keys and " +
|
|
200
|
+
"starts playback in the target zone. This replaces the zone's current " +
|
|
161
201
|
"queue: the first playable item starts immediately (Play Now), the rest " +
|
|
162
|
-
"are appended in order. Pass itemKeys from recent get_tracks_for/" +
|
|
202
|
+
"are appended in order. Pass itemKeys from recent get_tracks_for / " +
|
|
163
203
|
"search_music results (use them promptly — they are session-scoped). " +
|
|
164
204
|
"zoneId is optional (omit to use the default zone; see play_now). " +
|
|
165
205
|
"Optionally shuffle. Returns a PlaybackResult with queued/skipped counts " +
|
|
@@ -169,11 +209,13 @@ export class RoonMcpServer {
|
|
|
169
209
|
.string()
|
|
170
210
|
.min(1)
|
|
171
211
|
.optional()
|
|
172
|
-
.describe("Target zone id or output id
|
|
212
|
+
.describe("Target zone id or output id from list_zones. Omit to use the default " +
|
|
213
|
+
"zone (see play_now)."),
|
|
173
214
|
itemKeys: z
|
|
174
215
|
.array(z.string().min(1))
|
|
175
216
|
.min(1)
|
|
176
|
-
.describe("Ordered item keys to queue
|
|
217
|
+
.describe("Ordered item keys to queue, from recent get_tracks_for or " +
|
|
218
|
+
"search_music results (tracks, albums, artists, etc.)."),
|
|
177
219
|
shuffle: z
|
|
178
220
|
.boolean()
|
|
179
221
|
.optional()
|
|
@@ -188,6 +230,140 @@ export class RoonMcpServer {
|
|
|
188
230
|
return toToolError(err);
|
|
189
231
|
}
|
|
190
232
|
});
|
|
233
|
+
this.server.registerTool("now_playing", {
|
|
234
|
+
title: "Get the currently-playing track in a Roon zone",
|
|
235
|
+
description: "Use this when the user asks what is currently playing, what song is " +
|
|
236
|
+
"on, who's singing, what just started, or wants a snapshot before " +
|
|
237
|
+
"skipping or pausing (e.g. \"what's playing?\", \"what's playing in " +
|
|
238
|
+
"the kitchen?\", \"who's this?\", \"what's the current track?\", " +
|
|
239
|
+
"\"what song is this?\"). Returns a structured snapshot: zone id and " +
|
|
240
|
+
"name, playback state (playing/paused/loading/stopped), title, " +
|
|
241
|
+
"artist, album, and the current seek position when available. " +
|
|
242
|
+
"`title`/`artist`/`album` are undefined when nothing is playing. " +
|
|
243
|
+
"Call this before pause/skip/volume changes when the user hasn't " +
|
|
244
|
+
"named a zone — it confirms where to act and what the state is. " +
|
|
245
|
+
"zoneId is optional: omit to use ROON_DEFAULT_ZONE, or fall back " +
|
|
246
|
+
"to the only zone / an \"Office\" zone / the currently-playing zone; " +
|
|
247
|
+
"if it still can't decide it returns ZONE_AMBIGUOUS so the agent " +
|
|
248
|
+
"can ask the user or call list_zones.",
|
|
249
|
+
inputSchema: {
|
|
250
|
+
zoneId: z
|
|
251
|
+
.string()
|
|
252
|
+
.min(1)
|
|
253
|
+
.optional()
|
|
254
|
+
.describe("Target zone id or output id from list_zones (e.g. an id, or a " +
|
|
255
|
+
"name substring like 'Office'). Omit to use ROON_DEFAULT_ZONE " +
|
|
256
|
+
"or fall back automatically."),
|
|
257
|
+
},
|
|
258
|
+
}, async (args) => {
|
|
259
|
+
try {
|
|
260
|
+
const info = await this.transport.getNowPlaying(args.zoneId);
|
|
261
|
+
return structured(info);
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
return toToolError(err);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
this.server.registerTool("control_playback", {
|
|
268
|
+
title: "Run a Roon transport verb (pause/resume/next/previous/stop)",
|
|
269
|
+
description: "Use this for the common transport verbs — pause, resume, skip, " +
|
|
270
|
+
"next track, previous track, stop (e.g. \"pause\", \"pause the " +
|
|
271
|
+
"kitchen\", \"skip\", \"next track\", \"next song\", \"play the " +
|
|
272
|
+
"next song\", \"resume\", \"resume in the office\", \"stop\", " +
|
|
273
|
+
"\"stop the music\"). Runs one transport verb against the resolved " +
|
|
274
|
+
"zone. For \"louder\"/\"softer\" without a number, call now_playing " +
|
|
275
|
+
"first to read the current state, then set_volume with a target " +
|
|
276
|
+
"percent — this tool is verbs only. zoneId is optional and resolves " +
|
|
277
|
+
"like now_playing. Returns the resolved zoneId, the action taken, " +
|
|
278
|
+
"and the resulting playback state.",
|
|
279
|
+
inputSchema: {
|
|
280
|
+
zoneId: z
|
|
281
|
+
.string()
|
|
282
|
+
.min(1)
|
|
283
|
+
.optional()
|
|
284
|
+
.describe("Target zone id or output id from list_zones. Omit to use " +
|
|
285
|
+
"ROON_DEFAULT_ZONE or fall back automatically (see now_playing)."),
|
|
286
|
+
action: z
|
|
287
|
+
.enum(["pause", "resume", "next", "previous", "stop"])
|
|
288
|
+
.describe("Transport verb to run: 'pause' to stop playback, 'resume' to " +
|
|
289
|
+
"restart it, 'next' to skip to the next track, 'previous' to " +
|
|
290
|
+
"go back, 'stop' to release the audio device."),
|
|
291
|
+
},
|
|
292
|
+
}, async (args) => {
|
|
293
|
+
try {
|
|
294
|
+
const result = await this.transport.control(args.zoneId, args.action);
|
|
295
|
+
return structured(result);
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
return toToolError(err);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
this.server.registerTool("set_volume", {
|
|
302
|
+
title: "Set a Roon zone's volume to a target percent",
|
|
303
|
+
description: "Use this when the user wants to change how loud a zone is — turn " +
|
|
304
|
+
"it up, turn it down, set the volume, or pick a level for a " +
|
|
305
|
+
"specific room (e.g. \"turn it up to 60\", \"volume to 30 in the " +
|
|
306
|
+
"office\", \"set the kitchen to 50\", \"louder\" — see note). Sets " +
|
|
307
|
+
"the zone's volume to `level` percent (0 = silent, 100 = max). The " +
|
|
308
|
+
"server rescales to each output's native range, so a single value " +
|
|
309
|
+
"works across mixed devices in a grouped zone. Incremental " +
|
|
310
|
+
"outputs (IR blasters with no numeric range) are reported as " +
|
|
311
|
+
"skipped rather than guessed at. zoneId is optional and resolves " +
|
|
312
|
+
"like now_playing. NOTE on relative changes (\"louder\" / \"softer\" " +
|
|
313
|
+
"without a number): this tool is absolute — read the current " +
|
|
314
|
+
"state with now_playing isn't enough on its own (volume isn't " +
|
|
315
|
+
"exposed there), so for relative changes, ask the user for a " +
|
|
316
|
+
"target percent or apply a reasonable default delta.",
|
|
317
|
+
inputSchema: {
|
|
318
|
+
zoneId: z
|
|
319
|
+
.string()
|
|
320
|
+
.min(1)
|
|
321
|
+
.optional()
|
|
322
|
+
.describe("Target zone id or output id from list_zones. Omit to use " +
|
|
323
|
+
"ROON_DEFAULT_ZONE or fall back automatically."),
|
|
324
|
+
level: z
|
|
325
|
+
.number()
|
|
326
|
+
.min(0)
|
|
327
|
+
.max(100)
|
|
328
|
+
.describe("Target volume in percent (0 = silent, 100 = max). Mapped to " +
|
|
329
|
+
"each output's native range."),
|
|
330
|
+
},
|
|
331
|
+
}, async (args) => {
|
|
332
|
+
try {
|
|
333
|
+
const result = await this.transport.setVolume(args.zoneId, args.level);
|
|
334
|
+
return structured(result);
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
return toToolError(err);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
this.server.registerTool("mute", {
|
|
341
|
+
title: "Mute or unmute a Roon zone",
|
|
342
|
+
description: "Use this when the user wants to mute or unmute a zone (e.g. " +
|
|
343
|
+
"\"mute\", \"mute the kitchen\", \"unmute\", \"silence the " +
|
|
344
|
+
"office\", \"stop the noise\"). Mutes (or unmutes) every output " +
|
|
345
|
+
"in the resolved zone. zoneId is optional and resolves like " +
|
|
346
|
+
"now_playing. `muted: true` mutes, `muted: false` unmutes.",
|
|
347
|
+
inputSchema: {
|
|
348
|
+
zoneId: z
|
|
349
|
+
.string()
|
|
350
|
+
.min(1)
|
|
351
|
+
.optional()
|
|
352
|
+
.describe("Target zone id or output id from list_zones. Omit to use " +
|
|
353
|
+
"ROON_DEFAULT_ZONE or fall back automatically."),
|
|
354
|
+
muted: z
|
|
355
|
+
.boolean()
|
|
356
|
+
.describe("`true` to mute, `false` to unmute."),
|
|
357
|
+
},
|
|
358
|
+
}, async (args) => {
|
|
359
|
+
try {
|
|
360
|
+
const result = await this.transport.mute(args.zoneId, args.muted);
|
|
361
|
+
return structured(result);
|
|
362
|
+
}
|
|
363
|
+
catch (err) {
|
|
364
|
+
return toToolError(err);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
191
367
|
}
|
|
192
368
|
async start() {
|
|
193
369
|
this.roon.start();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RoonMcpServer.js","sourceRoot":"","sources":["../src/RoonMcpServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"RoonMcpServer.js","sourceRoot":"","sources":["../src/RoonMcpServer.ts"],"names":[],"mappings":"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"}
|
package/dist/SearchService.d.ts
CHANGED
|
@@ -21,6 +21,18 @@ export declare class SearchService {
|
|
|
21
21
|
* opt-in cost we accept for the streaming path.
|
|
22
22
|
*/
|
|
23
23
|
private collectStreamingGenreTracks;
|
|
24
|
+
/**
|
|
25
|
+
* Artist discovery beyond the library. The library album search that
|
|
26
|
+
* collectStreamingGenreTracks leans on returns nothing for an artist that
|
|
27
|
+
* has been fully removed from the library, so we go one level shallower: a
|
|
28
|
+
* track search, filtered to entries whose subtitle (the track's artist in
|
|
29
|
+
* Roon's flat search) matches the queried artist. On a TIDAL Core this
|
|
30
|
+
* surfaces the artist's streaming catalog as ready-to-play tracks. The
|
|
31
|
+
* result is best-effort: a small fraction may be features or compilations
|
|
32
|
+
* — the agent's per-artist cap / dedupe (see README "Curation is
|
|
33
|
+
* agent-side") handles that.
|
|
34
|
+
*/
|
|
35
|
+
private collectStreamingArtistTracks;
|
|
24
36
|
private performSearch;
|
|
25
37
|
/** Indices (into the top-level group list) to scan for the requested type. */
|
|
26
38
|
private selectGroupIndices;
|
package/dist/SearchService.js
CHANGED
|
@@ -46,7 +46,24 @@ export class SearchService {
|
|
|
46
46
|
}
|
|
47
47
|
// Search builds its own item keys inside performSearch, so a stale-session
|
|
48
48
|
// failure is recoverable with one reset-and-replay (see runExclusiveWithRetry).
|
|
49
|
-
|
|
49
|
+
const library = await this.browse.runExclusiveWithRetry(() => this.performSearch(input, limit));
|
|
50
|
+
// Artist search is library-only: when the artist has no library content
|
|
51
|
+
// (subtitle "0 Albums") or isn't in the library at all, optionally append a
|
|
52
|
+
// streaming track mix so the user request "play/queue <artist>" doesn't
|
|
53
|
+
// dead-end at get_tracks_for. Mirrors the genre streaming path's shape:
|
|
54
|
+
// library results first, streaming tracks appended with sourceGroup
|
|
55
|
+
// "Streaming". See collectStreamingArtistTracks.
|
|
56
|
+
if (input.type === "artist" && input.includeStreaming) {
|
|
57
|
+
const streaming = await this.collectStreamingArtistTracks(input.query, limit);
|
|
58
|
+
if (streaming.length > 0) {
|
|
59
|
+
const candidates = [...library.candidates, ...streaming];
|
|
60
|
+
const message = library.message
|
|
61
|
+
? `${library.message} Also showing ${streaming.length} streaming track(s).`
|
|
62
|
+
: `No library artist matched "${input.query}"; showing streaming tracks.`;
|
|
63
|
+
return { query: input.query, candidates, broadened: library.broadened, message };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return library;
|
|
50
67
|
}
|
|
51
68
|
async searchGenres(query, limit, includeStreaming) {
|
|
52
69
|
const libraryGenres = await this.genres.searchGenres(query, limit);
|
|
@@ -107,6 +124,42 @@ export class SearchService {
|
|
|
107
124
|
}
|
|
108
125
|
return out;
|
|
109
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Artist discovery beyond the library. The library album search that
|
|
129
|
+
* collectStreamingGenreTracks leans on returns nothing for an artist that
|
|
130
|
+
* has been fully removed from the library, so we go one level shallower: a
|
|
131
|
+
* track search, filtered to entries whose subtitle (the track's artist in
|
|
132
|
+
* Roon's flat search) matches the queried artist. On a TIDAL Core this
|
|
133
|
+
* surfaces the artist's streaming catalog as ready-to-play tracks. The
|
|
134
|
+
* result is best-effort: a small fraction may be features or compilations
|
|
135
|
+
* — the agent's per-artist cap / dedupe (see README "Curation is
|
|
136
|
+
* agent-side") handles that.
|
|
137
|
+
*/
|
|
138
|
+
async collectStreamingArtistTracks(query, limit) {
|
|
139
|
+
const trackSearch = await this.browse.runExclusiveWithRetry(() => this.performSearch({ query, type: "track" }, limit));
|
|
140
|
+
const tracks = trackSearch.candidates.filter((c) => c.type === "track");
|
|
141
|
+
if (tracks.length === 0)
|
|
142
|
+
return [];
|
|
143
|
+
const q = normalize(query);
|
|
144
|
+
const matched = tracks.filter((t) => {
|
|
145
|
+
const artist = normalize(t.subtitle ?? "");
|
|
146
|
+
// Substring is enough: Roon track subtitles are the artist (sometimes
|
|
147
|
+
// "Artist / Album"); a fuller match doesn't help here. We fall back to
|
|
148
|
+
// the track's own title as a last resort so a query that exactly
|
|
149
|
+
// matches a single-song single (e.g. a self-titled track) still
|
|
150
|
+
// surfaces something.
|
|
151
|
+
return artist === q || artist.includes(q) || normalize(t.title) === q;
|
|
152
|
+
});
|
|
153
|
+
return matched.slice(0, limit).map((t) => ({
|
|
154
|
+
itemKey: t.itemKey,
|
|
155
|
+
title: t.title,
|
|
156
|
+
subtitle: t.subtitle,
|
|
157
|
+
type: "track",
|
|
158
|
+
score: t.score,
|
|
159
|
+
available: t.available,
|
|
160
|
+
sourceGroup: "Streaming",
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
110
163
|
async performSearch(input, limit) {
|
|
111
164
|
// Roon only registers the search when `input` and `pop_all` ride in the
|
|
112
165
|
// same browse call; submitting `input` after a separate reset yields a
|
|
@@ -148,6 +201,15 @@ export class SearchService {
|
|
|
148
201
|
else if (ranked.length === 0) {
|
|
149
202
|
message = "No results.";
|
|
150
203
|
}
|
|
204
|
+
// When an artist search surfaces a candidate with no library content,
|
|
205
|
+
// surface that up front so the agent doesn't have to discover the dead
|
|
206
|
+
// end at get_tracks_for time. The streaming-artist fallback in
|
|
207
|
+
// searchMusic picks this up.
|
|
208
|
+
if (input.type === "artist" && /^\s*0\s+albums?\s*$/i.test(ranked[0]?.subtitle ?? "")) {
|
|
209
|
+
const name = ranked[0].title;
|
|
210
|
+
const hint = `Artist "${name}" has no library albums; pass includeStreaming:true to sample streaming tracks.`;
|
|
211
|
+
message = message ? `${message} ${hint}` : hint;
|
|
212
|
+
}
|
|
151
213
|
return { query: input.query, candidates: ranked, broadened, message };
|
|
152
214
|
}
|
|
153
215
|
/** Indices (into the top-level group list) to scan for the requested type. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchService.js","sourceRoot":"","sources":["../src/SearchService.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SearchService.js","sourceRoot":"","sources":["../src/SearchService.ts"],"names":[],"mappings":"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"}
|
|
@@ -162,10 +162,85 @@ test("includeStreaming samples a track mix across genre-relevant albums", async
|
|
|
162
162
|
}));
|
|
163
163
|
assert.ok(albumIndices.size >= 2);
|
|
164
164
|
});
|
|
165
|
-
test("includeStreaming
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
165
|
+
test("includeStreaming for type:artist appends streaming tracks by that artist", async () => {
|
|
166
|
+
// Mirrors the live "Helene Fischer" scenario from issue #2: the artist
|
|
167
|
+
// exists in the library with subtitle "0 Albums", and streaming-side tracks
|
|
168
|
+
// for the same artist surface from a track search. The artist search
|
|
169
|
+
// returns the (empty) library node; the streaming path runs a track search
|
|
170
|
+
// filtered to that artist's subtitle.
|
|
171
|
+
const artists = {
|
|
172
|
+
title: "Artists",
|
|
173
|
+
key: "g:artists",
|
|
174
|
+
items: [{ ...item("Helene Fischer", "a:hf"), subtitle: "0 Albums" }],
|
|
175
|
+
};
|
|
176
|
+
const tracks = {
|
|
177
|
+
title: "Tracks",
|
|
178
|
+
key: "g:tracks",
|
|
179
|
+
items: [
|
|
180
|
+
{ title: "Atemlos durch die Nacht", item_key: "tk:1", subtitle: "Helene Fischer" },
|
|
181
|
+
{ title: "Herzbeben", item_key: "tk:2", subtitle: "Helene Fischer" },
|
|
182
|
+
{ title: "Other Artist Song", item_key: "tk:3", subtitle: "Other Artist" },
|
|
183
|
+
],
|
|
184
|
+
};
|
|
185
|
+
const svc = buildService([artists, tracks]);
|
|
186
|
+
const out = await svc.searchMusic({
|
|
187
|
+
query: "Helene Fischer",
|
|
188
|
+
type: "artist",
|
|
189
|
+
includeStreaming: true,
|
|
190
|
+
});
|
|
191
|
+
// Library artist comes first, then streaming tracks by the same artist.
|
|
192
|
+
// Other-artist entries are filtered out by the subtitle match in
|
|
193
|
+
// collectStreamingArtistTracks.
|
|
194
|
+
assert.equal(out.candidates[0]?.title, "Helene Fischer");
|
|
195
|
+
assert.equal(out.candidates[0]?.sourceGroup, "Artists");
|
|
196
|
+
const streaming = out.candidates.filter((c) => c.sourceGroup === "Streaming");
|
|
197
|
+
assert.equal(streaming.length, 2);
|
|
198
|
+
assert.ok(streaming.every((c) => c.type === "track"));
|
|
199
|
+
assert.ok(streaming.every((c) => c.subtitle === "Helene Fischer"));
|
|
200
|
+
assert.ok(streaming.every((c) => c.title !== "Other Artist Song"));
|
|
201
|
+
assert.match(out.message ?? "", /no library albums/i);
|
|
202
|
+
assert.match(out.message ?? "", /includeStreaming/i);
|
|
203
|
+
});
|
|
204
|
+
test("an artist with 0 Albums surfaces a hint to pass includeStreaming", async () => {
|
|
205
|
+
const empty = {
|
|
206
|
+
title: "Artists",
|
|
207
|
+
key: "g:artists",
|
|
208
|
+
items: [item("Helene Fischer", "a:hf")],
|
|
209
|
+
};
|
|
210
|
+
// The fake's `item()` doesn't set a subtitle; override to "0 Albums" so the
|
|
211
|
+
// diagnostic in performSearch fires.
|
|
212
|
+
empty.items[0].subtitle = "0 Albums";
|
|
213
|
+
const svc = buildService([empty]);
|
|
214
|
+
const out = await svc.searchMusic({ query: "Helene Fischer", type: "artist" });
|
|
215
|
+
assert.equal(out.candidates[0]?.title, "Helene Fischer");
|
|
216
|
+
assert.match(out.message ?? "", /no library albums/i);
|
|
217
|
+
assert.match(out.message ?? "", /includeStreaming/i);
|
|
218
|
+
});
|
|
219
|
+
test("streaming-artist results are capped by limit", async () => {
|
|
220
|
+
const artists = {
|
|
221
|
+
title: "Artists",
|
|
222
|
+
key: "g:artists",
|
|
223
|
+
items: [{ ...item("Tycho", "a:tycho"), subtitle: "0 Albums" }],
|
|
224
|
+
};
|
|
225
|
+
const tracks = {
|
|
226
|
+
title: "Tracks",
|
|
227
|
+
key: "g:tracks",
|
|
228
|
+
items: Array.from({ length: 10 }, (_, i) => ({
|
|
229
|
+
title: `Song ${i}`,
|
|
230
|
+
item_key: `tk:${i}`,
|
|
231
|
+
subtitle: "Tycho",
|
|
232
|
+
})),
|
|
233
|
+
};
|
|
234
|
+
const svc = buildService([artists, tracks]);
|
|
235
|
+
const out = await svc.searchMusic({ query: "Tycho", type: "artist", includeStreaming: true, limit: 3 });
|
|
236
|
+
// 1 library artist + up to 3 streaming tracks. The streaming slice is
|
|
237
|
+
// capped independently of the library slice (the limit is a per-section
|
|
238
|
+
// cap on the streaming-track result, mirroring the genre path's per-album
|
|
239
|
+
// budget).
|
|
240
|
+
const streaming = out.candidates.filter((c) => c.sourceGroup === "Streaming");
|
|
241
|
+
assert.ok(streaming.length <= 3, `expected ≤3 streaming candidates, got ${streaming.length}`);
|
|
242
|
+
assert.equal(out.candidates[0]?.sourceGroup, "Artists");
|
|
243
|
+
assert.equal(out.candidates[0]?.title, "Tycho");
|
|
169
244
|
});
|
|
170
245
|
test("limit caps the number of returned candidates", async () => {
|
|
171
246
|
const many = {
|