@deadair/plugin-sdk 0.2.0

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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1062 -0
  3. package/dist/article.parse.d.ts +82 -0
  4. package/dist/article.parse.d.ts.map +1 -0
  5. package/dist/boundary.json.safe.d.ts +191 -0
  6. package/dist/boundary.json.safe.d.ts.map +1 -0
  7. package/dist/capabilities/analysis.d.ts +330 -0
  8. package/dist/capabilities/analysis.d.ts.map +1 -0
  9. package/dist/capabilities/charts.d.ts +134 -0
  10. package/dist/capabilities/charts.d.ts.map +1 -0
  11. package/dist/capabilities/enrichment.d.ts +255 -0
  12. package/dist/capabilities/enrichment.d.ts.map +1 -0
  13. package/dist/capabilities/llm.d.ts +318 -0
  14. package/dist/capabilities/llm.d.ts.map +1 -0
  15. package/dist/capabilities/mixer.d.ts +183 -0
  16. package/dist/capabilities/mixer.d.ts.map +1 -0
  17. package/dist/capabilities/music.provider.d.ts +245 -0
  18. package/dist/capabilities/music.provider.d.ts.map +1 -0
  19. package/dist/capabilities/news.d.ts +171 -0
  20. package/dist/capabilities/news.d.ts.map +1 -0
  21. package/dist/capabilities/scrobble.d.ts +133 -0
  22. package/dist/capabilities/scrobble.d.ts.map +1 -0
  23. package/dist/capabilities/search.d.ts +122 -0
  24. package/dist/capabilities/search.d.ts.map +1 -0
  25. package/dist/capabilities/similarity.d.ts +101 -0
  26. package/dist/capabilities/similarity.d.ts.map +1 -0
  27. package/dist/capabilities/speech.d.ts +211 -0
  28. package/dist/capabilities/speech.d.ts.map +1 -0
  29. package/dist/capabilities/weather.d.ts +192 -0
  30. package/dist/capabilities/weather.d.ts.map +1 -0
  31. package/dist/chunk-7QVYU63E.js +7 -0
  32. package/dist/chunk-7QVYU63E.js.map +1 -0
  33. package/dist/define.plugin.d.ts +56 -0
  34. package/dist/define.plugin.d.ts.map +1 -0
  35. package/dist/feed.parse.d.ts +97 -0
  36. package/dist/feed.parse.d.ts.map +1 -0
  37. package/dist/html.text.d.ts +71 -0
  38. package/dist/html.text.d.ts.map +1 -0
  39. package/dist/index.d.ts +30 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +1153 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/match.text.d.ts +28 -0
  44. package/dist/match.text.d.ts.map +1 -0
  45. package/dist/plugin.api.version.d.ts +9 -0
  46. package/dist/plugin.api.version.d.ts.map +1 -0
  47. package/dist/plugin.base.d.ts +80 -0
  48. package/dist/plugin.base.d.ts.map +1 -0
  49. package/dist/plugin.config.fields.d.ts +524 -0
  50. package/dist/plugin.config.fields.d.ts.map +1 -0
  51. package/dist/plugin.config.read.d.ts +52 -0
  52. package/dist/plugin.config.read.d.ts.map +1 -0
  53. package/dist/plugin.error.d.ts +177 -0
  54. package/dist/plugin.error.d.ts.map +1 -0
  55. package/dist/plugin.host.d.ts +220 -0
  56. package/dist/plugin.host.d.ts.map +1 -0
  57. package/dist/plugin.host.response.d.ts +42 -0
  58. package/dist/plugin.host.response.d.ts.map +1 -0
  59. package/dist/plugin.http.d.ts +80 -0
  60. package/dist/plugin.http.d.ts.map +1 -0
  61. package/dist/plugin.lifecycle.d.ts +68 -0
  62. package/dist/plugin.lifecycle.d.ts.map +1 -0
  63. package/dist/plugin.manifest.d.ts +282 -0
  64. package/dist/plugin.manifest.d.ts.map +1 -0
  65. package/dist/plugin.permissions.d.ts +186 -0
  66. package/dist/plugin.permissions.d.ts.map +1 -0
  67. package/dist/testing/fake.plugin.host.d.ts +85 -0
  68. package/dist/testing/fake.plugin.host.d.ts.map +1 -0
  69. package/dist/testing/index.d.ts +10 -0
  70. package/dist/testing/index.d.ts.map +1 -0
  71. package/dist/testing/index.js +141 -0
  72. package/dist/testing/index.js.map +1 -0
  73. package/package.json +67 -0
package/dist/index.js ADDED
@@ -0,0 +1,1153 @@
1
+ import {
2
+ __name
3
+ } from "./chunk-7QVYU63E.js";
4
+
5
+ // src/boundary.json.safe.ts
6
+ var JSON_SAFE_PAYLOAD_TYPES = [
7
+ "PluginConnectionResult",
8
+ "PluginPermissions",
9
+ "NetworkPermissionHost",
10
+ "NetworkPermissionFromConfig",
11
+ "PluginGrantRequest",
12
+ "ConfigField",
13
+ "ConfigFieldOption",
14
+ "ConfigFieldColumn",
15
+ "PluginManifest",
16
+ "ProviderTrack",
17
+ "ProviderPlaylist",
18
+ "ProviderStream",
19
+ "TrackFetchSession",
20
+ "TrackFetchRequest",
21
+ "SearchTracksOptions",
22
+ "ListPlaylistsOptions",
23
+ "GetPlaylistTracksOptions",
24
+ "PlaybackState",
25
+ "TrackRef",
26
+ "ArtistRef",
27
+ "AlbumRef",
28
+ "ExternalId",
29
+ "ExternalLink",
30
+ "SourceDocument",
31
+ "TrackEnrichment",
32
+ "ArtistEnrichment",
33
+ "AlbumEnrichment",
34
+ "SpeechRequest",
35
+ "SpeechVoice",
36
+ "LlmMessage",
37
+ "LlmToolDeclaration",
38
+ "LlmToolCall",
39
+ "LlmUsage",
40
+ "LlmRequest",
41
+ "LlmResult",
42
+ "LlmModelInfo",
43
+ "AnalysisRef",
44
+ "AudioJoin",
45
+ "AudioOverlay",
46
+ "TrackCuePoints",
47
+ "TrackLoudness",
48
+ "TrackTaggedLoudness",
49
+ "TrackAnalysis",
50
+ "ChartDescriptor",
51
+ "ChartQuery",
52
+ "ChartEntry",
53
+ "NewsFeedDescriptor",
54
+ "NewsQuery",
55
+ "NewsItem",
56
+ "SimilarArtist",
57
+ "ArtistTrack",
58
+ "SearchQuery",
59
+ "SearchResult",
60
+ "WeatherQuery",
61
+ "WeatherConditions",
62
+ "WeatherDay",
63
+ "WeatherReading",
64
+ "ScrobblePlay",
65
+ "ScrobbleRejection",
66
+ "ScrobbleResult"
67
+ ];
68
+ var BOUNDARY_METHOD_TYPES = [
69
+ "PluginLogger",
70
+ "PluginStorage",
71
+ "PluginSecrets",
72
+ "PluginConfigAccess",
73
+ "PluginOAuth",
74
+ "PluginEvents",
75
+ "PluginTrackFetcher",
76
+ "PluginHost",
77
+ "PluginLifecycle",
78
+ "MusicProviderCatalog",
79
+ "MusicProviderStream",
80
+ "MusicProviderSteer",
81
+ "MusicProviderOAuth",
82
+ "MusicProvider",
83
+ "EnrichmentProvider",
84
+ "SpeechPluginInstance",
85
+ "LlmPluginInstance",
86
+ "AnalysisProvider",
87
+ "MixerProvider",
88
+ "ChartsProvider",
89
+ "NewsProvider",
90
+ "SimilarityProvider",
91
+ "SearchProvider",
92
+ "WeatherProvider",
93
+ "ScrobbleProvider"
94
+ ];
95
+ var BOUNDARY_LIVE_OBJECT_TYPES = [
96
+ // `signal`: the invocation's own `AbortSignal`, watched by `host.fetch` and
97
+ // passed on by the plugin to anything else that takes one.
98
+ "HostFetchInit",
99
+ // `audio`: the engine's response body, usually forwarded straight through,
100
+ // so the bytes are never held whole on either side of the call.
101
+ "SpeechHandle",
102
+ // `audio`: the same thing one capability over, on the `mixer` side. A joined production is the
103
+ // longest single piece of audio the station ever makes, so holding it whole
104
+ // is the one thing this must not do.
105
+ "JoinedAudio",
106
+ // `text`: the words as the model produces them, and `result`: a promise that
107
+ // settles when it stops. The stream is what lets the host hold its single
108
+ // model slot until the generation really ends rather than until the call
109
+ // returns. `LlmResult` is the payload half, and it is JSON-safe.
110
+ "LlmHandle"
111
+ ];
112
+
113
+ // src/capabilities/analysis.ts
114
+ var ANALYSIS_SCHEMA_VERSION = 1;
115
+
116
+ // src/capabilities/enrichment.ts
117
+ var ENRICHMENT_MATCH_KEY_ISRC = "isrc";
118
+ var ENRICHMENT_MATCH_KEY_ARTIST_TITLE = "artist-title";
119
+ var KNOWN_ENRICHMENT_MATCH_KEYS = [
120
+ ENRICHMENT_MATCH_KEY_ISRC,
121
+ ENRICHMENT_MATCH_KEY_ARTIST_TITLE
122
+ ];
123
+
124
+ // src/plugin.error.ts
125
+ var PLUGIN_ERROR_CODES = [
126
+ "auth",
127
+ "config",
128
+ "not_found",
129
+ "forbidden",
130
+ "rate_limited",
131
+ "timeout",
132
+ "unavailable",
133
+ "unsupported",
134
+ "upstream",
135
+ "internal"
136
+ ];
137
+ var RESOURCE_SCOPED_CODES = /* @__PURE__ */ new Set([
138
+ "not_found",
139
+ "forbidden",
140
+ "unsupported"
141
+ ]);
142
+ function isResourceScopedCode(code) {
143
+ return RESOURCE_SCOPED_CODES.has(code);
144
+ }
145
+ __name(isResourceScopedCode, "isResourceScopedCode");
146
+ var RETRYABLE_BY_CODE = {
147
+ auth: false,
148
+ config: false,
149
+ not_found: false,
150
+ forbidden: false,
151
+ unsupported: false,
152
+ rate_limited: true,
153
+ timeout: true,
154
+ unavailable: true,
155
+ upstream: true,
156
+ internal: true
157
+ };
158
+ var PLUGIN_ERROR_BRAND = /* @__PURE__ */ Symbol.for("deadair.plugin-error/v1");
159
+ var PluginError = class extends Error {
160
+ static {
161
+ __name(this, "PluginError");
162
+ }
163
+ /** @internal Recognition marker for a foreign copy. See {@link PLUGIN_ERROR_BRAND}. */
164
+ [PLUGIN_ERROR_BRAND] = true;
165
+ /** What went wrong, in host vocabulary. Defaults to `internal`; set it with {@link withCode}. */
166
+ code = "internal";
167
+ /** Whether repeating the call could plausibly succeed. See {@link RETRYABLE_BY_CODE}. */
168
+ retryable = RETRYABLE_BY_CODE.internal;
169
+ /** How long to wait before retrying, when the upstream said so (`Retry-After`). */
170
+ retryAfterMs;
171
+ /**
172
+ * The upstream's HTTP status, for logs and for plugin-internal branching.
173
+ * Diagnostic only: the host never forwards it as its own response status.
174
+ */
175
+ upstreamStatus;
176
+ constructor(message, options) {
177
+ super(message, options);
178
+ Object.setPrototypeOf(this, new.target.prototype);
179
+ this.name = "PluginError";
180
+ }
181
+ /** Classifies the failure, and resets `retryable` to the default for that code. */
182
+ withCode(code) {
183
+ this.code = code;
184
+ this.retryable = RETRYABLE_BY_CODE[code];
185
+ return this;
186
+ }
187
+ /**
188
+ * Attaches the upstream's retry advice. Saying "wait this long and try
189
+ * again" is itself a statement that a retry is worth making, so this marks
190
+ * the failure retryable regardless of what the code defaults to.
191
+ */
192
+ withRetry(retryAfterMs2) {
193
+ this.retryable = true;
194
+ this.retryAfterMs = retryAfterMs2;
195
+ return this;
196
+ }
197
+ withUpstreamStatus(upstreamStatus) {
198
+ this.upstreamStatus = upstreamStatus;
199
+ return this;
200
+ }
201
+ };
202
+ var isPluginError = /* @__PURE__ */ __name((error) => {
203
+ return error instanceof PluginError;
204
+ }, "isPluginError");
205
+ function isForeignPluginError(value) {
206
+ if (typeof value !== "object" || value === null) return false;
207
+ return value[PLUGIN_ERROR_BRAND] === true;
208
+ }
209
+ __name(isForeignPluginError, "isForeignPluginError");
210
+ function toPluginError(error, fallback = "internal") {
211
+ if (isPluginError(error)) return error;
212
+ if (isForeignPluginError(error)) {
213
+ const known = typeof error.code === "string" && PLUGIN_ERROR_CODES.includes(error.code);
214
+ const message2 = typeof error.message === "string" ? error.message : String(error);
215
+ const adopted = new PluginError(message2, {
216
+ cause: error
217
+ }).withCode(known ? error.code : fallback);
218
+ if (typeof error.retryAfterMs === "number" && Number.isFinite(error.retryAfterMs)) adopted.withRetry(error.retryAfterMs);
219
+ if (typeof error.upstreamStatus === "number" && Number.isFinite(error.upstreamStatus)) adopted.withUpstreamStatus(error.upstreamStatus);
220
+ return adopted;
221
+ }
222
+ const message = error instanceof Error ? error.message : String(error);
223
+ return new PluginError(message, {
224
+ cause: error
225
+ }).withCode(fallback);
226
+ }
227
+ __name(toPluginError, "toPluginError");
228
+ var errorText = /* @__PURE__ */ __name((error) => error instanceof Error ? error.message : String(error), "errorText");
229
+
230
+ // src/capabilities/llm.ts
231
+ async function collectGeneration(handle, signal) {
232
+ const reader = handle.text.getReader();
233
+ void handle.result.catch(() => void 0);
234
+ try {
235
+ while (true) {
236
+ if (signal?.aborted) break;
237
+ const chunk = await Promise.race([
238
+ reader.read(),
239
+ aborted(signal)
240
+ ]);
241
+ if (chunk === ABORTED) break;
242
+ if (chunk.done) break;
243
+ }
244
+ } finally {
245
+ if (signal?.aborted) {
246
+ void reader.cancel().catch(() => void 0);
247
+ } else {
248
+ reader.releaseLock();
249
+ }
250
+ }
251
+ if (signal?.aborted) {
252
+ throw new PluginError("the generation was stopped before it finished").withCode("unavailable");
253
+ }
254
+ return handle.result;
255
+ }
256
+ __name(collectGeneration, "collectGeneration");
257
+ var ABORTED = /* @__PURE__ */ Symbol("aborted");
258
+ function aborted(signal) {
259
+ if (signal === void 0) return new Promise(() => void 0);
260
+ if (signal.aborted) return Promise.resolve(ABORTED);
261
+ return new Promise((resolve) => signal.addEventListener("abort", () => resolve(ABORTED), {
262
+ once: true
263
+ }));
264
+ }
265
+ __name(aborted, "aborted");
266
+
267
+ // src/capabilities/speech.ts
268
+ var SPEECH_CUES = [
269
+ "laugh",
270
+ "chuckle",
271
+ "sigh",
272
+ "gasp",
273
+ "cough",
274
+ "clear throat",
275
+ "sniff",
276
+ "groan"
277
+ ];
278
+ function cuesIn(text2) {
279
+ return [
280
+ ...text2.matchAll(cuePattern())
281
+ ].map((match) => match[1].toLowerCase());
282
+ }
283
+ __name(cuesIn, "cuesIn");
284
+ function withoutCues(text2, keep = []) {
285
+ const kept = /* @__PURE__ */ new Set([
286
+ ...keep
287
+ ]);
288
+ return text2.replace(cuePattern(), (match, cue) => kept.has(cue.toLowerCase()) ? match : " ").replace(/[^\S\n]{2,}/g, " ").replace(/[^\S\n]+([.,!?;:])/g, "$1").trim();
289
+ }
290
+ __name(withoutCues, "withoutCues");
291
+ var cuePattern = /* @__PURE__ */ __name(() => new RegExp(`\\[(${[
292
+ ...SPEECH_CUES
293
+ ].sort((left, right) => right.length - left.length).join("|")})\\]`, "gi"), "cuePattern");
294
+
295
+ // src/plugin.http.ts
296
+ var MAX_UPSTREAM_MESSAGE = 200;
297
+ function truncateUpstreamMessage(message) {
298
+ return message.length > MAX_UPSTREAM_MESSAGE ? `${message.slice(0, MAX_UPSTREAM_MESSAGE)}\u2026` : message;
299
+ }
300
+ __name(truncateUpstreamMessage, "truncateUpstreamMessage");
301
+ function upstreamField(body, pick) {
302
+ if (!body) return void 0;
303
+ let value;
304
+ try {
305
+ value = pick(JSON.parse(body));
306
+ } catch {
307
+ return void 0;
308
+ }
309
+ return typeof value === "string" && value.length > 0 ? value : void 0;
310
+ }
311
+ __name(upstreamField, "upstreamField");
312
+ function retryAfterMs(header) {
313
+ if (header === null || header === void 0) return void 0;
314
+ const seconds = Number(header.trim());
315
+ if (!Number.isFinite(seconds) || seconds < 0) return void 0;
316
+ return seconds * 1e3;
317
+ }
318
+ __name(retryAfterMs, "retryAfterMs");
319
+ function pluginCodeForStatus(status) {
320
+ if (status === 404) return "not_found";
321
+ if (status === 429) return "rate_limited";
322
+ if (status >= 500) return "unavailable";
323
+ return "upstream";
324
+ }
325
+ __name(pluginCodeForStatus, "pluginCodeForStatus");
326
+ function upstreamDetail(status, statusText, reason) {
327
+ return [
328
+ `HTTP ${status}`,
329
+ statusText,
330
+ reason
331
+ ].filter((part) => part).join(" ");
332
+ }
333
+ __name(upstreamDetail, "upstreamDetail");
334
+ var HOST_FETCH_METHODS = [
335
+ "GET",
336
+ "POST",
337
+ "PUT",
338
+ "PATCH",
339
+ "DELETE",
340
+ "HEAD"
341
+ ];
342
+ function hostFetchMethod(method) {
343
+ const upper = (method ?? "GET").toUpperCase();
344
+ return HOST_FETCH_METHODS.find((candidate) => candidate === upper);
345
+ }
346
+ __name(hostFetchMethod, "hostFetchMethod");
347
+ function headersToRecord(headers) {
348
+ const record2 = {};
349
+ headers.forEach((value, name) => {
350
+ record2[name.toLowerCase()] = value;
351
+ });
352
+ return record2;
353
+ }
354
+ __name(headersToRecord, "headersToRecord");
355
+
356
+ // src/html.text.ts
357
+ var NAMED_ENTITIES = {
358
+ amp: "&",
359
+ lt: "<",
360
+ gt: ">",
361
+ quot: '"',
362
+ apos: "'",
363
+ nbsp: " ",
364
+ ndash: "\u2013",
365
+ mdash: "\u2014",
366
+ hellip: "\u2026",
367
+ lsquo: "\u2018",
368
+ rsquo: "\u2019",
369
+ ldquo: "\u201C",
370
+ rdquo: "\u201D"
371
+ };
372
+ function decodeEntities(value) {
373
+ return value.replace(/&(#x?[0-9a-f]+|\w+);/gi, (whole, body) => {
374
+ if (body.startsWith("#")) {
375
+ const code = body[1]?.toLowerCase() === "x" ? Number.parseInt(body.slice(2), 16) : Number.parseInt(body.slice(1), 10);
376
+ return Number.isFinite(code) && code > 0 && code <= 1114111 ? String.fromCodePoint(code) : whole;
377
+ }
378
+ return NAMED_ENTITIES[body.toLowerCase()] ?? whole;
379
+ });
380
+ }
381
+ __name(decodeEntities, "decodeEntities");
382
+ function plainText(raw, maxChars) {
383
+ const stripped = decodeEntities(raw.replace(/<[^>]*>/g, " ")).replace(/\s+/g, " ").trim();
384
+ if (stripped.length === 0) return void 0;
385
+ return maxChars === void 0 ? stripped : truncateWords(stripped, maxChars);
386
+ }
387
+ __name(plainText, "plainText");
388
+ function truncateWords(value, maxChars) {
389
+ if (value.length <= maxChars) return value;
390
+ const cut = value.slice(0, maxChars);
391
+ const lastSpace = cut.lastIndexOf(" ");
392
+ return `${(lastSpace > maxChars - 40 ? cut.slice(0, lastSpace) : cut).trimEnd()}\u2026`;
393
+ }
394
+ __name(truncateWords, "truncateWords");
395
+ var SENTENCE_END = /[.!?]["'”’)\]]*\s+(?=["'“‘(]?[A-Z0-9])/g;
396
+ var ABBREVIATIONS = /\b(mr|mrs|ms|dr|prof|rev|st|jr|sr|gov|sen|rep|lt|sgt|col|gen|capt|jan|feb|mar|apr|jun|jul|aug|sept|sep|oct|nov|dec|no|vs|approx|est|inc|ltd|co|dept|univ|u\.s|u\.k)$/i;
397
+ var endsSentence = /* @__PURE__ */ __name((value, at) => !ABBREVIATIONS.test(value.slice(Math.max(0, at - 12), at)), "endsSentence");
398
+ function firstSentence(value) {
399
+ const text2 = value.trim();
400
+ SENTENCE_END.lastIndex = 0;
401
+ for (let match = SENTENCE_END.exec(text2); match !== null; match = SENTENCE_END.exec(text2)) {
402
+ const stop = match.index;
403
+ if (endsSentence(text2, stop)) return text2.slice(0, stop + 1).trim();
404
+ }
405
+ return text2;
406
+ }
407
+ __name(firstSentence, "firstSentence");
408
+ function truncateSentences(value, maxChars) {
409
+ if (value.length <= maxChars) return value;
410
+ let lastStop = -1;
411
+ SENTENCE_END.lastIndex = 0;
412
+ for (let match = SENTENCE_END.exec(value); match !== null; match = SENTENCE_END.exec(value)) {
413
+ if (match.index >= maxChars) break;
414
+ if (endsSentence(value, match.index)) lastStop = match.index;
415
+ }
416
+ if (lastStop <= 0) return truncateWords(value, maxChars);
417
+ return value.slice(0, lastStop + 1).trimEnd();
418
+ }
419
+ __name(truncateSentences, "truncateSentences");
420
+ var wordsIn = /* @__PURE__ */ __name((value) => value.split(/\s+/).filter(Boolean).length, "wordsIn");
421
+ var closersIn = /* @__PURE__ */ __name((boundary) => boundary.slice(1).trimEnd(), "closersIn");
422
+ function sentencesWithin(value, maxWords) {
423
+ const text2 = value.trim();
424
+ if (wordsIn(text2) <= maxWords) return text2;
425
+ let fits;
426
+ SENTENCE_END.lastIndex = 0;
427
+ for (let match = SENTENCE_END.exec(text2); match !== null; match = SENTENCE_END.exec(text2)) {
428
+ if (!endsSentence(text2, match.index)) continue;
429
+ const ending = text2.slice(0, match.index + 1 + closersIn(match[0]).length);
430
+ if (wordsIn(ending) > maxWords) break;
431
+ fits = ending;
432
+ }
433
+ return fits;
434
+ }
435
+ __name(sentencesWithin, "sentencesWithin");
436
+
437
+ // src/article.parse.ts
438
+ var ARTICLE_MAX_CHARS = 2e3;
439
+ var MIN_PARAGRAPH_CHARS = 60;
440
+ var FURNITURE = /<(script|style|noscript|template|svg|figure|figcaption|aside|nav|header|footer|form)\b[^>]*>[\s\S]*?<\/\1>/gi;
441
+ var FURNITURE_CLASSES = /<(div|section|span|p|ul|ol)\b[^>]*(?:class|id|aria-label)="[^"]*\b(caption|credit|byline|promo|newsletter|related|recirc|sidebar|share|social|advert|subscribe|paywall|tags?)\b[^"]*"[^>]*>[\s\S]*?<\/\1>/gi;
442
+ var REFERENCE_APPARATUS = [
443
+ // `{{cite web |…}}`, `{{efn|…}}`. One level and non-nesting, which is the
444
+ // safe direction: a nested template leaves its outer braces behind as two
445
+ // characters rather than eating the sentence after it.
446
+ [
447
+ /\{\{[^{}]*\}\}/g,
448
+ ""
449
+ ],
450
+ // `[ 1 ]`, `[12]`.
451
+ [
452
+ /\[\s*\d+\s*\]/g,
453
+ ""
454
+ ],
455
+ // `[ citation needed ]`, `[ edit ]`.
456
+ [
457
+ /\[\s*(?:citation needed|clarification needed|edit|note \d+|update)\s*\]/gi,
458
+ ""
459
+ ],
460
+ // `[[BBC]]` and `[[David Bowie|Bowie]]`: the only one that keeps its match,
461
+ // because what is inside is the words a reader sees rather than apparatus.
462
+ [
463
+ /\[\[(?:[^[\]|]*\|)?([^[\]|]*)\]\]/g,
464
+ "$1"
465
+ ]
466
+ ];
467
+ var SPONSOR_OPENERS = [
468
+ "a message from",
469
+ "a note from our sponsor",
470
+ "advertisement",
471
+ "advertorial",
472
+ "paid content",
473
+ "paid for by",
474
+ "paid partnership",
475
+ "presented by",
476
+ "promoted by",
477
+ "sponsored",
478
+ "sponsored by",
479
+ "sponsored content",
480
+ "sponsored survey",
481
+ "support for this",
482
+ "support comes from",
483
+ "this post is sponsored"
484
+ ];
485
+ function opensWithASponsor(text2) {
486
+ const said = text2.toLowerCase();
487
+ return SPONSOR_OPENERS.some((opener) => {
488
+ if (!said.startsWith(opener)) return false;
489
+ const next = said.charAt(opener.length);
490
+ return next === "" || /[\s:;,.\u2013\u2014-]/.test(next);
491
+ });
492
+ }
493
+ __name(opensWithASponsor, "opensWithASponsor");
494
+ var CONTAINERS = [
495
+ /<article\b[^>]*>([\s\S]*?)<\/article>/i,
496
+ /<main\b[^>]*>([\s\S]*?)<\/main>/i
497
+ ];
498
+ var PARAGRAPH = /<p\b[^>]*>([\s\S]*?)<\/p>/gi;
499
+ function extractArticle(html, maxChars = ARTICLE_MAX_CHARS) {
500
+ const cleaned = html.replace(FURNITURE, " ").replace(FURNITURE_CLASSES, " ");
501
+ const body = CONTAINERS.map((pattern) => pattern.exec(cleaned)?.[1]).find((found) => found !== void 0) ?? cleaned;
502
+ const paragraphs = [];
503
+ for (const match of body.matchAll(PARAGRAPH)) {
504
+ const text2 = withoutApparatus(plainText(match[1] ?? ""));
505
+ if (text2 === void 0 || text2.length < MIN_PARAGRAPH_CHARS) continue;
506
+ if (opensWithASponsor(text2)) continue;
507
+ if (!paragraphs.includes(text2)) paragraphs.push(text2);
508
+ }
509
+ if (paragraphs.length === 0) return void 0;
510
+ return truncateSentences(paragraphs.join(" "), maxChars);
511
+ }
512
+ __name(extractArticle, "extractArticle");
513
+ function withoutApparatus(text2) {
514
+ if (text2 === void 0) return void 0;
515
+ let stripped = text2;
516
+ for (const [pattern, replacement] of REFERENCE_APPARATUS) stripped = stripped.replace(pattern, replacement);
517
+ const tidied = stripped.replace(/\s+/g, " ").replace(/\s+([,.;:!?])/g, "$1").trim();
518
+ return tidied.length === 0 ? void 0 : tidied;
519
+ }
520
+ __name(withoutApparatus, "withoutApparatus");
521
+ async function fetchArticle(host, url, init, options) {
522
+ const response = await host.fetch(url, init);
523
+ if (!response.ok) {
524
+ await response.body?.cancel();
525
+ const error = new PluginError(`article request failed: ${upstreamDetail(response.status, response.statusText)}`).withCode(pluginCodeForStatus(response.status));
526
+ error.upstreamStatus = response.status;
527
+ const advice = retryAfterMs(response.headers.get("retry-after"));
528
+ if (advice !== void 0) error.withRetry(advice);
529
+ throw error;
530
+ }
531
+ const contentType = response.headers.get("content-type") ?? "";
532
+ if (!/\b(text\/html|application\/xhtml\+xml)\b/i.test(contentType)) {
533
+ await response.body?.cancel();
534
+ return void 0;
535
+ }
536
+ return extractArticle(await response.text(), options?.maxChars);
537
+ }
538
+ __name(fetchArticle, "fetchArticle");
539
+
540
+ // src/define.plugin.ts
541
+ function definePlugin(manifest, factory) {
542
+ return {
543
+ manifest,
544
+ factory
545
+ };
546
+ }
547
+ __name(definePlugin, "definePlugin");
548
+
549
+ // src/feed.parse.ts
550
+ import { XMLParser } from "fast-xml-parser";
551
+ var FEED_SUMMARY_MAX_CHARS = 500;
552
+ var parser = new XMLParser({
553
+ ignoreAttributes: false,
554
+ attributeNamePrefix: "@_",
555
+ removeNSPrefix: true,
556
+ trimValues: true,
557
+ // A guid of `12345` is an id, not a number, and a `<title>2024</title>` is a
558
+ // title. Every field here is read as text, so parsing any of it as a number
559
+ // only produces a value whose `.trim` is missing.
560
+ parseTagValue: false,
561
+ parseAttributeValue: false
562
+ });
563
+ function parseFeed(xml) {
564
+ let document;
565
+ try {
566
+ document = parser.parse(xml);
567
+ } catch {
568
+ return {
569
+ items: []
570
+ };
571
+ }
572
+ if (!isRecord(document)) return {
573
+ items: []
574
+ };
575
+ const rss = record(document.rss);
576
+ const rdf = record(document.RDF);
577
+ const atom = record(document.feed);
578
+ const channel = record(rss?.channel) ?? record(rdf?.channel) ?? atom;
579
+ if (channel === void 0) return {
580
+ items: []
581
+ };
582
+ const entries = [
583
+ ...asArray(record(rss?.channel)?.item),
584
+ ...asArray(rdf?.item),
585
+ ...asArray(atom?.entry)
586
+ ];
587
+ const feed = {
588
+ items: entries.flatMap((entry) => isRecord(entry) ? readItem(entry) ?? [] : [])
589
+ };
590
+ const title = speakable(channel.title);
591
+ if (title !== void 0) feed.title = title;
592
+ const homeUrl = readLink(channel.link);
593
+ if (homeUrl !== void 0) feed.homeUrl = homeUrl;
594
+ return feed;
595
+ }
596
+ __name(parseFeed, "parseFeed");
597
+ async function fetchFeed(host, url, init) {
598
+ const response = await host.fetch(url, init);
599
+ if (!response.ok) {
600
+ await response.body?.cancel();
601
+ const error = new PluginError(`feed request failed: ${upstreamDetail(response.status, response.statusText)}`).withCode(pluginCodeForStatus(response.status));
602
+ error.upstreamStatus = response.status;
603
+ const advice = retryAfterMs(response.headers.get("retry-after"));
604
+ if (advice !== void 0) error.withRetry(advice);
605
+ throw error;
606
+ }
607
+ return parseFeed(await response.text());
608
+ }
609
+ __name(fetchFeed, "fetchFeed");
610
+ function readItem(entry) {
611
+ const title = speakable(entry.title);
612
+ if (title === void 0) return void 0;
613
+ const url = readLink(entry.link);
614
+ const publishedAt = readDate(entry.pubDate ?? entry.published ?? entry.date ?? entry.updated ?? entry.issued);
615
+ const summary = plainText2(entry.description ?? entry.summary ?? entry.encoded ?? entry.content);
616
+ const author = readAuthor(entry.author ?? entry.creator);
617
+ const categories = readCategories(entry.category);
618
+ const item = {
619
+ id: readId(entry, title, publishedAt, url),
620
+ title
621
+ };
622
+ if (summary !== void 0) item.summary = summary;
623
+ if (url !== void 0) item.url = url;
624
+ if (publishedAt !== void 0) item.publishedAt = publishedAt;
625
+ if (author !== void 0) item.author = author;
626
+ if (categories.length > 0) item.categories = categories;
627
+ return item;
628
+ }
629
+ __name(readItem, "readItem");
630
+ function readId(entry, title, publishedAt, url) {
631
+ return text(entry.guid) ?? text(entry.id) ?? url ?? `hash:${hash(`${title}\0${publishedAt ?? ""}`)}`;
632
+ }
633
+ __name(readId, "readId");
634
+ function readLink(value) {
635
+ const candidates = asArray(value);
636
+ for (const candidate of candidates) {
637
+ if (typeof candidate === "string") return trimmed(candidate);
638
+ if (!isRecord(candidate)) continue;
639
+ const rel = text(candidate["@_rel"]);
640
+ if (rel !== void 0 && rel !== "alternate") continue;
641
+ const href = text(candidate["@_href"]) ?? text(candidate["#text"]);
642
+ if (href !== void 0) return href;
643
+ }
644
+ return void 0;
645
+ }
646
+ __name(readLink, "readLink");
647
+ function readDate(value) {
648
+ const raw = text(value);
649
+ if (raw === void 0) return void 0;
650
+ const at = new Date(raw);
651
+ return Number.isNaN(at.getTime()) ? void 0 : at.toISOString();
652
+ }
653
+ __name(readDate, "readDate");
654
+ function readAuthor(value) {
655
+ const first = asArray(value)[0];
656
+ if (typeof first === "string") return trimmed(first);
657
+ if (!isRecord(first)) return void 0;
658
+ return text(first.name) ?? text(first["#text"]);
659
+ }
660
+ __name(readAuthor, "readAuthor");
661
+ function readCategories(value) {
662
+ const seen = /* @__PURE__ */ new Set();
663
+ for (const candidate of asArray(value)) {
664
+ const name = typeof candidate === "string" ? trimmed(candidate) : isRecord(candidate) ? text(candidate["@_term"]) ?? text(candidate["#text"]) : void 0;
665
+ if (name !== void 0) seen.add(name);
666
+ }
667
+ return [
668
+ ...seen
669
+ ];
670
+ }
671
+ __name(readCategories, "readCategories");
672
+ function plainText2(value) {
673
+ const raw = text(value);
674
+ return raw === void 0 ? void 0 : plainText(raw, FEED_SUMMARY_MAX_CHARS);
675
+ }
676
+ __name(plainText2, "plainText");
677
+ function hash(value) {
678
+ let accumulated = 2166136261;
679
+ for (let at = 0; at < value.length; at += 1) {
680
+ accumulated ^= value.charCodeAt(at);
681
+ accumulated = Math.imul(accumulated, 16777619);
682
+ }
683
+ return (accumulated >>> 0).toString(16).padStart(8, "0");
684
+ }
685
+ __name(hash, "hash");
686
+ function asArray(value) {
687
+ if (value === void 0 || value === null) return [];
688
+ return Array.isArray(value) ? value : [
689
+ value
690
+ ];
691
+ }
692
+ __name(asArray, "asArray");
693
+ var isRecord = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
694
+ var record = /* @__PURE__ */ __name((value) => isRecord(value) ? value : void 0, "record");
695
+ var trimmed = /* @__PURE__ */ __name((value) => value.trim().length === 0 ? void 0 : value.trim(), "trimmed");
696
+ function text(value) {
697
+ if (typeof value === "string") return trimmed(value);
698
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
699
+ if (isRecord(value)) return text(value["#text"]);
700
+ return void 0;
701
+ }
702
+ __name(text, "text");
703
+ var speakable = /* @__PURE__ */ __name((value) => {
704
+ const raw = text(value);
705
+ return raw === void 0 ? void 0 : plainText(raw);
706
+ }, "speakable");
707
+
708
+ // src/match.text.ts
709
+ var PUNCTUATION = /[^\p{L}\p{N}\s]/gu;
710
+ var PARENTHETICAL = /[([{][^)\]}]*[)\]}]/g;
711
+ function normalize(value) {
712
+ return value.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().replace(PUNCTUATION, " ").replace(/\s+/g, " ").trim();
713
+ }
714
+ __name(normalize, "normalize");
715
+ function baseForm(value) {
716
+ return normalize(value.replace(PARENTHETICAL, " ").split(/\s+[-–—]\s+/)[0] ?? value);
717
+ }
718
+ __name(baseForm, "baseForm");
719
+
720
+ // src/plugin.api.version.ts
721
+ var PLUGIN_API_VERSION = "1.0.0";
722
+
723
+ // src/plugin.base.ts
724
+ var Plugin = class {
725
+ static {
726
+ __name(this, "Plugin");
727
+ }
728
+ currentHost;
729
+ disposers = [];
730
+ /**
731
+ * The host, once `init` has run.
732
+ *
733
+ * A getter rather than a field so the failure is a sentence naming the
734
+ * plugin instead of a `TypeError` from somewhere three calls deeper.
735
+ *
736
+ * @throws {PluginError} `internal` when read before `init` or after
737
+ * `dispose`. Both are host bugs rather than operator ones, which is what
738
+ * that code means.
739
+ */
740
+ get host() {
741
+ if (this.currentHost === void 0) {
742
+ throw new PluginError(`${this.constructor.name} was used before init() or after dispose()`).withCode("internal");
743
+ }
744
+ return this.currentHost;
745
+ }
746
+ /**
747
+ * Registers something to undo when this plugin unloads.
748
+ *
749
+ * Call it next to the setup it undoes, which is the whole point: a
750
+ * `clearInterval` written beside its `setInterval` is one that cannot drift
751
+ * away from it as the file grows.
752
+ */
753
+ register(disposer) {
754
+ this.disposers.push(disposer);
755
+ }
756
+ /** {@link Plugin.register} for the common case. Works for `setTimeout` and `setInterval` alike. */
757
+ registerTimer(timer) {
758
+ this.register(() => clearTimeout(timer));
759
+ }
760
+ /** Your setup. Called once, after `this.host` is available and before any capability method. */
761
+ onLoad() {
762
+ return Promise.resolve();
763
+ }
764
+ /**
765
+ * Anything left to undo that `register` could not express, such as dropping
766
+ * a cache. Runs after every registered disposer.
767
+ */
768
+ onUnload() {
769
+ return Promise.resolve();
770
+ }
771
+ async init(host) {
772
+ this.currentHost = host;
773
+ await this.onLoad();
774
+ }
775
+ /**
776
+ * Undoes everything, in reverse.
777
+ *
778
+ * Every disposer runs even if an earlier one throws, because one broken
779
+ * undo must not strand the rest: the failure is logged and the walk
780
+ * continues. `onUnload` runs afterwards for the same reason it exists, and
781
+ * `this.host` is released last so both still have it.
782
+ *
783
+ * Idempotent. The host calls it on unload, on a config change and at
784
+ * shutdown, and those can overlap.
785
+ */
786
+ async dispose() {
787
+ if (this.currentHost === void 0) return;
788
+ for (const disposer of this.disposers.splice(0).reverse()) {
789
+ try {
790
+ await disposer();
791
+ } catch (error) {
792
+ this.currentHost.logger.warn("a plugin disposer failed", {
793
+ plugin: this.constructor.name,
794
+ error: error instanceof Error ? error.message : String(error)
795
+ });
796
+ }
797
+ }
798
+ try {
799
+ await this.onUnload();
800
+ } finally {
801
+ this.currentHost = void 0;
802
+ }
803
+ }
804
+ };
805
+
806
+ // src/plugin.config.fields.ts
807
+ import { z } from "zod";
808
+ function parseMultiSelect(raw) {
809
+ if (typeof raw !== "string" || raw.trim().length === 0) return [];
810
+ try {
811
+ const parsed = JSON.parse(raw);
812
+ if (!Array.isArray(parsed)) return [];
813
+ return parsed.filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim());
814
+ } catch {
815
+ return [];
816
+ }
817
+ }
818
+ __name(parseMultiSelect, "parseMultiSelect");
819
+ var ROW_ID_KEY = "$id";
820
+ function rowSecretKey(fieldKey, rowId, columnKey) {
821
+ return `${fieldKey}/${rowId}/${columnKey}`;
822
+ }
823
+ __name(rowSecretKey, "rowSecretKey");
824
+ function isRowSecretKey(key) {
825
+ return key.split("/").length === 3;
826
+ }
827
+ __name(isRowSecretKey, "isRowSecretKey");
828
+ function parseRows(raw) {
829
+ if (typeof raw !== "string" || raw.trim().length === 0) return [];
830
+ try {
831
+ const parsed = JSON.parse(raw);
832
+ if (!Array.isArray(parsed)) return [];
833
+ return parsed.flatMap((entry) => {
834
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return [];
835
+ const row = {};
836
+ for (const [key, value] of Object.entries(entry)) {
837
+ if (typeof value === "string" && value.trim().length > 0) row[key] = value.trim();
838
+ }
839
+ return Object.keys(row).length === 0 ? [] : [
840
+ row
841
+ ];
842
+ });
843
+ } catch {
844
+ return [];
845
+ }
846
+ }
847
+ __name(parseRows, "parseRows");
848
+ var configFieldOptionSchema = z.object({
849
+ value: z.string(),
850
+ label: z.string()
851
+ });
852
+ var configFieldTypeSchema = z.enum([
853
+ "string",
854
+ "text",
855
+ "url",
856
+ "secret",
857
+ "number",
858
+ "boolean",
859
+ "select",
860
+ "multiselect",
861
+ "list",
862
+ "note"
863
+ ]);
864
+ var configFieldUnitSchema = z.enum([
865
+ "bytes",
866
+ "fraction"
867
+ ]);
868
+ var configFieldControlSchema = z.enum([
869
+ "slider",
870
+ "tags"
871
+ ]);
872
+ var configFieldOptionSourceSchema = z.enum([
873
+ "station.newsCategories",
874
+ "station.newsFeeds",
875
+ "intl.timeZones",
876
+ "plugins.speech",
877
+ "plugins.llm",
878
+ "plugins.mixer",
879
+ "plugins.analysis",
880
+ "llm.models"
881
+ ]);
882
+ var noSeparator = /* @__PURE__ */ __name((key) => !key.includes("/"), "noSeparator");
883
+ var separatorMessage = 'a key may not contain "/"';
884
+ var configFieldColumnSchema = z.object({
885
+ key: z.string().min(1).refine(noSeparator, separatorMessage),
886
+ label: z.string().min(1),
887
+ type: z.enum([
888
+ "string",
889
+ "url",
890
+ "select",
891
+ "secret"
892
+ ]),
893
+ required: z.boolean().optional(),
894
+ placeholder: z.string().optional(),
895
+ options: z.array(configFieldOptionSchema).optional(),
896
+ optionsFrom: configFieldOptionSourceSchema.optional(),
897
+ // Named here or dropped: `plugin.loader.ts` stores the zod OUTPUT of this schema as the
898
+ // manifest, and a `z.object` strips what it does not name. A column declaring a condition this
899
+ // schema had not been told about would lose it at load, in silence, and present as a console
900
+ // that ignores the declaration.
901
+ //
902
+ // No refinement tying the values to a target. The `.ck` contract mirroring this cannot express
903
+ // one, and a schema that refuses what the contract accepts is the drift the generated-output
904
+ // rule exists to prevent; values without a target are documented as ignored instead.
905
+ dependsOn: z.string().min(1).optional(),
906
+ dependsOnValues: z.array(z.string().min(1)).optional()
907
+ });
908
+ var configFieldSchema = z.object({
909
+ key: z.string().min(1).refine(noSeparator, separatorMessage),
910
+ label: z.string().min(1),
911
+ type: configFieldTypeSchema,
912
+ required: z.boolean().optional(),
913
+ default: z.union([
914
+ z.string(),
915
+ z.number(),
916
+ z.boolean()
917
+ ]).optional(),
918
+ unit: configFieldUnitSchema.optional(),
919
+ control: configFieldControlSchema.optional(),
920
+ step: z.number().optional(),
921
+ min: z.number().optional(),
922
+ max: z.number().optional(),
923
+ placeholder: z.string().optional(),
924
+ help: z.string().optional(),
925
+ options: z.array(configFieldOptionSchema).optional(),
926
+ optionsFrom: configFieldOptionSourceSchema.optional(),
927
+ columns: z.array(configFieldColumnSchema).optional(),
928
+ dependsOn: z.string().optional(),
929
+ rangeWith: z.string().optional()
930
+ });
931
+
932
+ // src/plugin.config.read.ts
933
+ function configString(value) {
934
+ if (typeof value !== "string") return void 0;
935
+ const trimmed2 = value.trim();
936
+ return trimmed2.length > 0 ? trimmed2 : void 0;
937
+ }
938
+ __name(configString, "configString");
939
+ function configBaseUrl(value) {
940
+ return (configString(value) ?? "").replace(/\/+$/, "");
941
+ }
942
+ __name(configBaseUrl, "configBaseUrl");
943
+ async function readRowSecret(host, fieldKey, row, columnKey) {
944
+ const rowId = row[ROW_ID_KEY];
945
+ if (rowId === void 0 || rowId.length === 0) return void 0;
946
+ return await host.secrets.get(rowSecretKey(fieldKey, rowId, columnKey));
947
+ }
948
+ __name(readRowSecret, "readRowSecret");
949
+
950
+ // src/plugin.host.response.ts
951
+ var BODY_SNIPPET_LENGTH = 120;
952
+ var snippet = /* @__PURE__ */ __name((body) => {
953
+ const trimmed2 = body.trim();
954
+ if (trimmed2.length === 0) return "<empty body>";
955
+ return trimmed2.length <= BODY_SNIPPET_LENGTH ? trimmed2 : `${trimmed2.slice(0, BODY_SNIPPET_LENGTH)}\u2026`;
956
+ }, "snippet");
957
+ async function jsonBody(response) {
958
+ const text2 = await response.text();
959
+ try {
960
+ return JSON.parse(text2);
961
+ } catch (error) {
962
+ const reason = error instanceof Error ? error.message : String(error);
963
+ throw new Error(`expected JSON from ${response.url} (HTTP ${response.status}) but got ${snippet(text2)}: ${reason}`, {
964
+ cause: error
965
+ });
966
+ }
967
+ }
968
+ __name(jsonBody, "jsonBody");
969
+ async function tryJsonBody(response) {
970
+ const text2 = await response.text();
971
+ try {
972
+ return JSON.parse(text2);
973
+ } catch {
974
+ return void 0;
975
+ }
976
+ }
977
+ __name(tryJsonBody, "tryJsonBody");
978
+
979
+ // src/plugin.manifest.ts
980
+ import { z as z3 } from "zod";
981
+
982
+ // src/plugin.permissions.ts
983
+ import { z as z2 } from "zod";
984
+ var pacingShape = {
985
+ ratePerSecond: z2.number().positive().finite().optional(),
986
+ bucket: z2.string().min(1).optional()
987
+ };
988
+ var networkPermissionSchema = z2.union([
989
+ z2.string().min(1),
990
+ z2.object({
991
+ host: z2.string().min(1),
992
+ ...pacingShape
993
+ }),
994
+ z2.object({
995
+ fromConfig: z2.string().min(1),
996
+ ...pacingShape
997
+ })
998
+ ]);
999
+ var grantRequestSchema = z2.object({
1000
+ capability: z2.string().min(1),
1001
+ // Non-empty for the reason the field exists: an operator deciding on a
1002
+ // capability id with no sentence beside it is being asked a question they
1003
+ // cannot answer.
1004
+ reason: z2.string().min(1),
1005
+ ...pacingShape
1006
+ });
1007
+ var pluginPermissionsSchema = z2.object({
1008
+ network: z2.array(networkPermissionSchema),
1009
+ storage: z2.boolean(),
1010
+ oauth: z2.boolean(),
1011
+ trackFetcher: z2.boolean().optional(),
1012
+ grants: z2.array(grantRequestSchema).optional()
1013
+ });
1014
+
1015
+ // src/plugin.manifest.ts
1016
+ var PLUGIN_CAPABILITY_CATALOG = "catalog";
1017
+ var PLUGIN_CAPABILITY_STREAM = "stream";
1018
+ var PLUGIN_CAPABILITY_STEER = "steer";
1019
+ var PLUGIN_CAPABILITY_OAUTH = "oauth";
1020
+ var PLUGIN_CAPABILITY_ENRICHMENT = "enrichment";
1021
+ var PLUGIN_CAPABILITY_SPEECH = "speech";
1022
+ var PLUGIN_CAPABILITY_LLM = "llm";
1023
+ var PLUGIN_CAPABILITY_ANALYSIS = "analysis";
1024
+ var PLUGIN_CAPABILITY_MIXER = "mixer";
1025
+ var PLUGIN_CAPABILITY_CHARTS = "charts";
1026
+ var PLUGIN_CAPABILITY_NEWS = "news";
1027
+ var PLUGIN_CAPABILITY_SIMILARITY = "similarity";
1028
+ var PLUGIN_CAPABILITY_SEARCH = "search";
1029
+ var PLUGIN_CAPABILITY_WEATHER = "weather";
1030
+ var PLUGIN_CAPABILITY_SCROBBLE = "scrobble";
1031
+ var KNOWN_PLUGIN_CAPABILITIES = [
1032
+ PLUGIN_CAPABILITY_CATALOG,
1033
+ PLUGIN_CAPABILITY_STREAM,
1034
+ PLUGIN_CAPABILITY_STEER,
1035
+ PLUGIN_CAPABILITY_OAUTH,
1036
+ PLUGIN_CAPABILITY_ENRICHMENT,
1037
+ PLUGIN_CAPABILITY_SPEECH,
1038
+ PLUGIN_CAPABILITY_LLM,
1039
+ PLUGIN_CAPABILITY_ANALYSIS,
1040
+ PLUGIN_CAPABILITY_MIXER,
1041
+ PLUGIN_CAPABILITY_CHARTS,
1042
+ PLUGIN_CAPABILITY_NEWS,
1043
+ PLUGIN_CAPABILITY_SIMILARITY,
1044
+ PLUGIN_CAPABILITY_SEARCH,
1045
+ PLUGIN_CAPABILITY_WEATHER,
1046
+ PLUGIN_CAPABILITY_SCROBBLE
1047
+ ];
1048
+ function isZodSchema(value) {
1049
+ if (value instanceof z3.ZodType) return true;
1050
+ if (typeof value !== "object" || value === null) return false;
1051
+ const candidate = value;
1052
+ return typeof candidate.parse === "function" && typeof candidate.safeParse === "function";
1053
+ }
1054
+ __name(isZodSchema, "isZodSchema");
1055
+ var PLUGIN_ID_PATTERN = /^[a-z0-9][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)+$/;
1056
+ var pluginManifestSchema = z3.object({
1057
+ id: z3.string().regex(PLUGIN_ID_PATTERN, 'plugin id must be reverse-DNS, e.g. "deadair.spotify"'),
1058
+ name: z3.string().min(1),
1059
+ version: z3.string().min(1),
1060
+ capabilities: z3.array(z3.string().min(1)),
1061
+ apiVersion: z3.string().min(1),
1062
+ description: z3.string().optional(),
1063
+ homepage: z3.string().optional(),
1064
+ icon: z3.string().optional(),
1065
+ permissions: pluginPermissionsSchema,
1066
+ configFields: z3.array(configFieldSchema),
1067
+ configSchema: z3.custom(isZodSchema, {
1068
+ message: "configSchema must be a zod schema"
1069
+ })
1070
+ });
1071
+ export {
1072
+ ANALYSIS_SCHEMA_VERSION,
1073
+ ARTICLE_MAX_CHARS,
1074
+ BOUNDARY_LIVE_OBJECT_TYPES,
1075
+ BOUNDARY_METHOD_TYPES,
1076
+ ENRICHMENT_MATCH_KEY_ARTIST_TITLE,
1077
+ ENRICHMENT_MATCH_KEY_ISRC,
1078
+ FEED_SUMMARY_MAX_CHARS,
1079
+ HOST_FETCH_METHODS,
1080
+ JSON_SAFE_PAYLOAD_TYPES,
1081
+ KNOWN_ENRICHMENT_MATCH_KEYS,
1082
+ KNOWN_PLUGIN_CAPABILITIES,
1083
+ PLUGIN_API_VERSION,
1084
+ PLUGIN_CAPABILITY_ANALYSIS,
1085
+ PLUGIN_CAPABILITY_CATALOG,
1086
+ PLUGIN_CAPABILITY_CHARTS,
1087
+ PLUGIN_CAPABILITY_ENRICHMENT,
1088
+ PLUGIN_CAPABILITY_LLM,
1089
+ PLUGIN_CAPABILITY_MIXER,
1090
+ PLUGIN_CAPABILITY_NEWS,
1091
+ PLUGIN_CAPABILITY_OAUTH,
1092
+ PLUGIN_CAPABILITY_SCROBBLE,
1093
+ PLUGIN_CAPABILITY_SEARCH,
1094
+ PLUGIN_CAPABILITY_SIMILARITY,
1095
+ PLUGIN_CAPABILITY_SPEECH,
1096
+ PLUGIN_CAPABILITY_STEER,
1097
+ PLUGIN_CAPABILITY_STREAM,
1098
+ PLUGIN_CAPABILITY_WEATHER,
1099
+ PLUGIN_ERROR_CODES,
1100
+ Plugin,
1101
+ PluginError,
1102
+ ROW_ID_KEY,
1103
+ SPEECH_CUES,
1104
+ baseForm,
1105
+ collectGeneration,
1106
+ configBaseUrl,
1107
+ configFieldColumnSchema,
1108
+ configFieldControlSchema,
1109
+ configFieldOptionSchema,
1110
+ configFieldOptionSourceSchema,
1111
+ configFieldSchema,
1112
+ configFieldTypeSchema,
1113
+ configFieldUnitSchema,
1114
+ configString,
1115
+ cuesIn,
1116
+ decodeEntities,
1117
+ definePlugin,
1118
+ errorText,
1119
+ extractArticle,
1120
+ fetchArticle,
1121
+ fetchFeed,
1122
+ firstSentence,
1123
+ grantRequestSchema,
1124
+ headersToRecord,
1125
+ hostFetchMethod,
1126
+ isPluginError,
1127
+ isResourceScopedCode,
1128
+ isRowSecretKey,
1129
+ isZodSchema,
1130
+ jsonBody,
1131
+ networkPermissionSchema,
1132
+ normalize,
1133
+ parseFeed,
1134
+ parseMultiSelect,
1135
+ parseRows,
1136
+ plainText,
1137
+ pluginCodeForStatus,
1138
+ pluginManifestSchema,
1139
+ pluginPermissionsSchema,
1140
+ readRowSecret,
1141
+ retryAfterMs,
1142
+ rowSecretKey,
1143
+ sentencesWithin,
1144
+ toPluginError,
1145
+ truncateSentences,
1146
+ truncateUpstreamMessage,
1147
+ truncateWords,
1148
+ tryJsonBody,
1149
+ upstreamDetail,
1150
+ upstreamField,
1151
+ withoutCues
1152
+ };
1153
+ //# sourceMappingURL=index.js.map