@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
@@ -0,0 +1,82 @@
1
+ import type { HostFetchInit, PluginHost } from './plugin.host.js';
2
+ /**
3
+ * The story behind a headline, as the paragraphs somebody else published.
4
+ *
5
+ * The sibling of `feed.parse.ts`, here for the same reason and with the same
6
+ * split. A feed is a summary of a publication and frequently not a summary of
7
+ * anything else: measured against the station's own configured feed, every
8
+ * item's `description` was one sentence restating its title and its
9
+ * `content:encoded` was that same sentence wrapped in a `<p>`. So a station
10
+ * that wants to say what HAPPENED has to read the page the entry points at,
11
+ * and every plugin that reads a feed will want this the moment it wants more
12
+ * than titles.
13
+ *
14
+ * ## It extracts and it does not summarise
15
+ *
16
+ * Everything that comes out of here is the publisher's own prose, in the
17
+ * publisher's own order, with the furniture removed. Nothing is rewritten,
18
+ * nothing is joined, and no sentence is composed. That is the same boundary
19
+ * `plugins/wikipedia` keeps by handing the host an article verbatim: only the
20
+ * host can check a claim against the text it came from, and prose that has been
21
+ * through a plugin's own paraphrase is prose nothing can check.
22
+ *
23
+ * The one thing removed that is not furniture is a paragraph announcing itself as an advertisement
24
+ * — see {@link SPONSOR_OPENERS}. It is still a cut rather than a rewrite: what is left is the
25
+ * publisher's own prose, and what went is prose the publisher was paid for.
26
+ *
27
+ * ## Wrong in one direction on purpose
28
+ *
29
+ * A page that cannot be read answers `undefined` rather than a guess. Every
30
+ * caller's fallback is the entry's own words, which is a real answer, whereas
31
+ * navigation copy and cookie banners scraped off a template are noise a voice
32
+ * would read out as news. So the extraction is deliberately conservative: a
33
+ * block that is not clearly a paragraph of prose is dropped, and a page that
34
+ * yields nothing is a page with nothing on it as far as anything here is
35
+ * concerned.
36
+ */
37
+ /**
38
+ * How much of an article is kept.
39
+ *
40
+ * Sized to what a bulletin can use rather than to the article: the destination
41
+ * is a model's context beside a persona sheet and a set of content rules, and a
42
+ * writer that only ever reads out one sentence per story does not need three
43
+ * thousand words to find it. Cut on a sentence boundary
44
+ * ({@link truncateSentences}), because half a sentence is something a model
45
+ * finishes out of its own head.
46
+ */
47
+ export declare const ARTICLE_MAX_CHARS = 2000;
48
+ /**
49
+ * An article page as plain text, or `undefined` when it does not carry one.
50
+ *
51
+ * Pure, and that is what makes it testable against saved pages with no host in
52
+ * the way — `parseFeed`'s split, for `parseFeed`'s reason.
53
+ *
54
+ * @param html - The page, as served.
55
+ * @param maxChars - Where to cut. See {@link ARTICLE_MAX_CHARS}.
56
+ */
57
+ export declare function extractArticle(html: string, maxChars?: number): string | undefined;
58
+ /**
59
+ * An article off the network, as plain text.
60
+ *
61
+ * Everything about the request except the status check is `host.fetch`'s, and
62
+ * the status ladder is `plugin.http.ts`'s unmodified: `fetchFeed`'s shape, for
63
+ * `fetchFeed`'s reasons.
64
+ *
65
+ * The one thing this adds is the content-type check, which is not fussiness. An
66
+ * entry legitimately links to a PDF, an audio file or a video page, and a
67
+ * megabyte of binary put through a tag stripper produces a long string of
68
+ * plausible-looking rubbish rather than an error — which a bulletin would then
69
+ * read out.
70
+ *
71
+ * `options.maxChars` is separate from `init` because the two are addressed to
72
+ * different parties: everything in `init` is the host's business and this is the
73
+ * parser's. The default is {@link ARTICLE_MAX_CHARS}, which is sized for a
74
+ * bulletin — a writer that reads one sentence per story out of a model's context
75
+ * has no use for three thousand words. A plugin contributing an enrichment
76
+ * `SourceDocument` wants far more of the page, because what reads that is a
77
+ * claim extractor rather than a presenter.
78
+ */
79
+ export declare function fetchArticle(host: PluginHost, url: string, init?: HostFetchInit, options?: {
80
+ maxChars?: number;
81
+ }): Promise<string | undefined>;
82
+ //# sourceMappingURL=article.parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"article.parse.d.ts","sourceRoot":"","sources":["../src/article.parse.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,OAAQ,CAAC;AAwIvC;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAA0B,GAAG,MAAM,GAAG,SAAS,CAgCrG;AA2BD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,YAAY,CAC9B,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,aAAa,EACpB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAChC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA0B7B"}
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Compile-time enforcement of the JSON-safe rule, over the payloads it applies
3
+ * to.
4
+ *
5
+ * ## Which payloads, and why
6
+ *
7
+ * This rule used to cover everything a plugin touched, on the strength of a deferred move behind a
8
+ * subprocess: JSON was going to be the wire format, so nothing could carry a `Date`, a `Uint8Array`
9
+ * or a live object. That move is closed (`packages/plugin-sdk/CLAUDE.md` § "Trust and egress"), and
10
+ * with it the reason to apply the rule to `host.fetch`'s arguments and return value, which nothing
11
+ * serializes.
12
+ *
13
+ * What is registered here is what has an INDEPENDENT reason to survive
14
+ * `JSON.parse(JSON.stringify(x))`: it is stored in Postgres, or sent to the
15
+ * console over HTTP, or both. A `Date` in `TrackEnrichment` is a bug whether or
16
+ * not plugins ever move anywhere, which is why that half of the rule outlived
17
+ * the argument that introduced it.
18
+ *
19
+ * ## Why compile time
20
+ *
21
+ * The runtime conformance suite in `tests/` round-trips hand-written fixtures,
22
+ * which catches a value that violates the rule despite a type that permits it
23
+ * (a class instance where the type says `object`). What it cannot catch is a
24
+ * newly added field: nothing populates it, so nothing round-trips it, and CI
25
+ * stays green while a `Date` crosses the boundary.
26
+ *
27
+ * TypeScript types are erased, so no runtime walk can ever be exhaustive over
28
+ * a type. That check has to happen at compile time, which is what this file
29
+ * is. {@link AssertAllBoundaryPayloadsAreJsonSafe} fails `tsc` the moment any
30
+ * field of any registered payload stops being JSON-safe, without anyone
31
+ * writing a fixture for it.
32
+ *
33
+ * This lives in `src/` rather than `tests/` on purpose: per the repo
34
+ * convention the build tsconfig includes only `./src/**\/*`, so an assertion
35
+ * in `tests/` would never run during `tsc --noEmit`. Everything here is types
36
+ * plus three name arrays, so the runtime cost is the arrays alone.
37
+ */
38
+ import type { AnalysisRef, TrackAnalysis, TrackCuePoints, TrackLoudness, TrackTaggedLoudness } from './capabilities/analysis.js';
39
+ import type { AudioJoin, AudioOverlay } from './capabilities/mixer.js';
40
+ import type { ChartDescriptor, ChartEntry, ChartQuery } from './capabilities/charts.js';
41
+ import type { NewsFeedDescriptor, NewsItem, NewsQuery } from './capabilities/news.js';
42
+ import type { ScrobblePlay, ScrobbleRejection, ScrobbleResult } from './capabilities/scrobble.js';
43
+ import type { SearchQuery, SearchResult } from './capabilities/search.js';
44
+ import type { ArtistTrack, SimilarArtist } from './capabilities/similarity.js';
45
+ import type { WeatherConditions, WeatherDay, WeatherQuery, WeatherReading } from './capabilities/weather.js';
46
+ import type { AlbumEnrichment, AlbumRef, ArtistEnrichment, ArtistRef, ExternalId, ExternalLink, SourceDocument, TrackEnrichment, TrackRef } from './capabilities/enrichment.js';
47
+ import type { GetPlaylistTracksOptions, ListPlaylistsOptions, PlaybackState, ProviderPlaylist, ProviderStream, ProviderTrack, SearchTracksOptions } from './capabilities/music.provider.js';
48
+ import type { LlmMessage, LlmModelInfo, LlmRequest, LlmResult, LlmToolCall, LlmToolDeclaration, LlmUsage } from './capabilities/llm.js';
49
+ import type { SpeechRequest, SpeechVoice } from './capabilities/speech.js';
50
+ import type { ConfigField, ConfigFieldColumn, ConfigFieldOption } from './plugin.config.fields.js';
51
+ import type { TrackFetchRequest, TrackFetchSession } from './plugin.host.js';
52
+ import type { PluginConnectionResult } from './plugin.lifecycle.js';
53
+ import type { PluginManifest } from './plugin.manifest.js';
54
+ import type { NetworkPermissionFromConfig, NetworkPermissionHost, PluginGrantRequest, PluginPermissions } from './plugin.permissions.js';
55
+ /**
56
+ * `T` with every part that cannot survive `JSON.parse(JSON.stringify(x))`
57
+ * replaced by `never`, so `T extends JsonSafe<T>` holds only for a genuinely
58
+ * JSON-safe `T`.
59
+ *
60
+ * Deliberately narrower than structured-clone-safe. `Date`, `Map`, `Set` and
61
+ * typed arrays all survive `structuredClone` and are still rejected here,
62
+ * because what these payloads actually have to survive is Postgres and HTTP,
63
+ * and both of those are JSON. A `Date` that round-trips through
64
+ * `structuredClone` still comes back out of a `jsonb` column as a string.
65
+ *
66
+ * `any` and `unknown` pass through unchecked: there is nothing to inspect at
67
+ * compile time, which is exactly the case the runtime fixture round-trip
68
+ * covers.
69
+ */
70
+ export type JsonSafe<T> = 0 extends 1 & T ? T : unknown extends T ? T : T extends string | number | boolean | null | undefined ? T : T extends (...args: never[]) => unknown ? never : T extends Date | RegExp | Map<unknown, unknown> | Set<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<unknown> ? never : T extends ArrayBuffer | SharedArrayBuffer | ArrayBufferView ? never : T extends bigint | symbol ? never : T extends readonly (infer TElement)[] ? readonly JsonSafe<TElement>[] : T extends object ? {
71
+ [K in keyof T]: JsonSafe<T[K]>;
72
+ } : never;
73
+ /**
74
+ * `true` when `T` survives the boundary, `false` when any part of it does not.
75
+ *
76
+ * The tuple wrappers stop the conditional from distributing over a union, so a
77
+ * field typed `string | Date` fails as a whole rather than partially matching.
78
+ *
79
+ * Written as a conditional rather than the more obvious
80
+ * `T extends JsonSafe<T>` constraint, because TypeScript rejects that form as
81
+ * a circular constraint (TS2313).
82
+ */
83
+ type IsJsonSafe<T> = [T] extends [JsonSafe<T>] ? true : false;
84
+ /**
85
+ * Compiles only when every value in `T` is `true`. When one is not, the error
86
+ * lands on that property, so the diagnostic names the offending boundary type.
87
+ */
88
+ type AssertAllTrue<T extends Record<string, true>> = T;
89
+ /**
90
+ * Every payload that is stored or sent, asserted in one place.
91
+ *
92
+ * ADDING A BOUNDARY TYPE? Add it here and to {@link JSON_SAFE_PAYLOAD_TYPES}.
93
+ * `tests/boundary.registry.test.ts` fails if an exported interface in a
94
+ * boundary source file is in none of the three registries, so this cannot be
95
+ * skipped by accident.
96
+ *
97
+ * `PluginManifest` is asserted without `configSchema`, which is a zod schema:
98
+ * a class instance, deliberately never serialized, and stripped by the host
99
+ * before a manifest is sent anywhere.
100
+ */
101
+ export type AssertAllBoundaryPayloadsAreJsonSafe = AssertAllTrue<{
102
+ PluginConnectionResult: IsJsonSafe<PluginConnectionResult>;
103
+ PluginPermissions: IsJsonSafe<PluginPermissions>;
104
+ NetworkPermissionHost: IsJsonSafe<NetworkPermissionHost>;
105
+ NetworkPermissionFromConfig: IsJsonSafe<NetworkPermissionFromConfig>;
106
+ PluginGrantRequest: IsJsonSafe<PluginGrantRequest>;
107
+ ConfigField: IsJsonSafe<ConfigField>;
108
+ ConfigFieldOption: IsJsonSafe<ConfigFieldOption>;
109
+ ConfigFieldColumn: IsJsonSafe<ConfigFieldColumn>;
110
+ PluginManifestWithoutConfigSchema: IsJsonSafe<Omit<PluginManifest, 'configSchema'>>;
111
+ ProviderTrack: IsJsonSafe<ProviderTrack>;
112
+ ProviderPlaylist: IsJsonSafe<ProviderPlaylist>;
113
+ ProviderStream: IsJsonSafe<ProviderStream>;
114
+ TrackFetchSession: IsJsonSafe<TrackFetchSession>;
115
+ TrackFetchRequest: IsJsonSafe<TrackFetchRequest>;
116
+ SearchTracksOptions: IsJsonSafe<SearchTracksOptions>;
117
+ ListPlaylistsOptions: IsJsonSafe<ListPlaylistsOptions>;
118
+ GetPlaylistTracksOptions: IsJsonSafe<GetPlaylistTracksOptions>;
119
+ PlaybackState: IsJsonSafe<PlaybackState>;
120
+ TrackRef: IsJsonSafe<TrackRef>;
121
+ ArtistRef: IsJsonSafe<ArtistRef>;
122
+ AlbumRef: IsJsonSafe<AlbumRef>;
123
+ ExternalId: IsJsonSafe<ExternalId>;
124
+ ExternalLink: IsJsonSafe<ExternalLink>;
125
+ SourceDocument: IsJsonSafe<SourceDocument>;
126
+ TrackEnrichment: IsJsonSafe<TrackEnrichment>;
127
+ ArtistEnrichment: IsJsonSafe<ArtistEnrichment>;
128
+ AlbumEnrichment: IsJsonSafe<AlbumEnrichment>;
129
+ SpeechRequest: IsJsonSafe<SpeechRequest>;
130
+ SpeechVoice: IsJsonSafe<SpeechVoice>;
131
+ LlmMessage: IsJsonSafe<LlmMessage>;
132
+ LlmToolDeclaration: IsJsonSafe<LlmToolDeclaration>;
133
+ LlmToolCall: IsJsonSafe<LlmToolCall>;
134
+ LlmUsage: IsJsonSafe<LlmUsage>;
135
+ LlmRequest: IsJsonSafe<LlmRequest>;
136
+ LlmResult: IsJsonSafe<LlmResult>;
137
+ LlmModelInfo: IsJsonSafe<LlmModelInfo>;
138
+ AnalysisRef: IsJsonSafe<AnalysisRef>;
139
+ AudioJoin: IsJsonSafe<AudioJoin>;
140
+ AudioOverlay: IsJsonSafe<AudioOverlay>;
141
+ TrackCuePoints: IsJsonSafe<TrackCuePoints>;
142
+ TrackLoudness: IsJsonSafe<TrackLoudness>;
143
+ TrackTaggedLoudness: IsJsonSafe<TrackTaggedLoudness>;
144
+ TrackAnalysis: IsJsonSafe<TrackAnalysis>;
145
+ ChartDescriptor: IsJsonSafe<ChartDescriptor>;
146
+ ChartQuery: IsJsonSafe<ChartQuery>;
147
+ ChartEntry: IsJsonSafe<ChartEntry>;
148
+ NewsFeedDescriptor: IsJsonSafe<NewsFeedDescriptor>;
149
+ NewsQuery: IsJsonSafe<NewsQuery>;
150
+ NewsItem: IsJsonSafe<NewsItem>;
151
+ SimilarArtist: IsJsonSafe<SimilarArtist>;
152
+ ArtistTrack: IsJsonSafe<ArtistTrack>;
153
+ SearchQuery: IsJsonSafe<SearchQuery>;
154
+ SearchResult: IsJsonSafe<SearchResult>;
155
+ WeatherQuery: IsJsonSafe<WeatherQuery>;
156
+ WeatherConditions: IsJsonSafe<WeatherConditions>;
157
+ WeatherDay: IsJsonSafe<WeatherDay>;
158
+ WeatherReading: IsJsonSafe<WeatherReading>;
159
+ ScrobblePlay: IsJsonSafe<ScrobblePlay>;
160
+ ScrobbleRejection: IsJsonSafe<ScrobbleRejection>;
161
+ ScrobbleResult: IsJsonSafe<ScrobbleResult>;
162
+ }>;
163
+ /**
164
+ * Names of the interfaces asserted above. Kept as a runtime array so the
165
+ * registry-coverage test can compare it against what is actually exported
166
+ * from the boundary source files.
167
+ */
168
+ export declare const JSON_SAFE_PAYLOAD_TYPES: readonly ["PluginConnectionResult", "PluginPermissions", "NetworkPermissionHost", "NetworkPermissionFromConfig", "PluginGrantRequest", "ConfigField", "ConfigFieldOption", "ConfigFieldColumn", "PluginManifest", "ProviderTrack", "ProviderPlaylist", "ProviderStream", "TrackFetchSession", "TrackFetchRequest", "SearchTracksOptions", "ListPlaylistsOptions", "GetPlaylistTracksOptions", "PlaybackState", "TrackRef", "ArtistRef", "AlbumRef", "ExternalId", "ExternalLink", "SourceDocument", "TrackEnrichment", "ArtistEnrichment", "AlbumEnrichment", "SpeechRequest", "SpeechVoice", "LlmMessage", "LlmToolDeclaration", "LlmToolCall", "LlmUsage", "LlmRequest", "LlmResult", "LlmModelInfo", "AnalysisRef", "AudioJoin", "AudioOverlay", "TrackCuePoints", "TrackLoudness", "TrackTaggedLoudness", "TrackAnalysis", "ChartDescriptor", "ChartQuery", "ChartEntry", "NewsFeedDescriptor", "NewsQuery", "NewsItem", "SimilarArtist", "ArtistTrack", "SearchQuery", "SearchResult", "WeatherQuery", "WeatherConditions", "WeatherDay", "WeatherReading", "ScrobblePlay", "ScrobbleRejection", "ScrobbleResult"];
169
+ /**
170
+ * Boundary interfaces that are deliberately NOT payloads: they describe
171
+ * methods, so they carry functions by definition and can never be JSON-safe.
172
+ *
173
+ * The arguments and return values of those methods are payloads, and those
174
+ * are what {@link JSON_SAFE_PAYLOAD_TYPES} covers. Listing the method-bearing
175
+ * interfaces explicitly is what makes "is this a payload or a contract?" a
176
+ * conscious decision for anyone adding one, rather than a silent omission.
177
+ */
178
+ export declare const BOUNDARY_METHOD_TYPES: readonly ["PluginLogger", "PluginStorage", "PluginSecrets", "PluginConfigAccess", "PluginOAuth", "PluginEvents", "PluginTrackFetcher", "PluginHost", "PluginLifecycle", "MusicProviderCatalog", "MusicProviderStream", "MusicProviderSteer", "MusicProviderOAuth", "MusicProvider", "EnrichmentProvider", "SpeechPluginInstance", "LlmPluginInstance", "AnalysisProvider", "MixerProvider", "ChartsProvider", "NewsProvider", "SimilarityProvider", "SearchProvider", "WeatherProvider", "ScrobbleProvider"];
179
+ /**
180
+ * Boundary interfaces that deliberately carry a LIVE object, and so are neither
181
+ * payloads nor method contracts.
182
+ *
183
+ * The host and the plugin share a realm, permanently (see `packages/plugin-sdk/CLAUDE.md` § "Trust
184
+ * and egress"), so handing over a real `AbortSignal` or a real stream is the correct design rather
185
+ * than a shortcut around the rule. They are listed rather than simply left out, because the
186
+ * registry-coverage test treats an unclassified boundary interface as an omission, and "this one
187
+ * holds a live object on purpose" is a decision somebody should have to make in writing.
188
+ */
189
+ export declare const BOUNDARY_LIVE_OBJECT_TYPES: readonly ["HostFetchInit", "SpeechHandle", "JoinedAudio", "LlmHandle"];
190
+ export {};
191
+ //# sourceMappingURL=boundary.json.safe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boundary.json.safe.d.ts","sourceRoot":"","sources":["../src/boundary.json.safe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,330 @@
1
+ /**
2
+ * The `analysis` capability. An analysis plugin takes one track's audio and
3
+ * answers with measurements of it: where the record actually starts, where it is
4
+ * underway, where the ending begins, where it stops.
5
+ *
6
+ * ## Why this is not enrichment
7
+ *
8
+ * Enrichment asks an upstream what it KNOWS about a recording, fans the question
9
+ * out to everything that answers, and merges the results in priority order.
10
+ * Nothing here works that way. There is no upstream that knows where a record's
11
+ * outro begins — it is computed from the samples — so there is exactly one
12
+ * answer, one source, and nothing to merge. `TrackRef` also carries no way to
13
+ * reach the audio, deliberately, because no enrichment source wants it.
14
+ *
15
+ * ## The plugin is an adapter, not an analyzer
16
+ *
17
+ * Measuring any of this needs decoded PCM, and decoding is the one thing this
18
+ * tree does not do in Node. So the expected implementation is a thin adapter over
19
+ * a separate program: take {@link AnalysisRef.audioUrl}, hand it to whatever
20
+ * actually decodes, return what comes back. That is the same relationship the
21
+ * bundled speech plugin has with its engine.
22
+ *
23
+ * The reason is decoding and only decoding. An earlier version of this note also claimed the
24
+ * boundary bought a licence position, which `analysis/README.md` § "The rule, stated once" retired:
25
+ * nothing copyleft or non-commercial enters the analysis path in the first place, so there is no
26
+ * position for the boundary to buy.
27
+ *
28
+ * Nothing here requires that shape. A plugin that can measure audio some other
29
+ * way is a valid implementation; this note exists so the first one is not
30
+ * mistaken for the contract.
31
+ *
32
+ * ## Why joining audio is not here
33
+ *
34
+ * It was, for one commit, as an optional `joinAudio`. The argument was the
35
+ * paragraph above read backwards — joining needs decoded PCM too, so it wants the
36
+ * same adapter over the same program, and the bundled plugin does serve both off
37
+ * one sidecar with one address. That much is still true and is why there is still
38
+ * one plugin.
39
+ *
40
+ * What it got wrong is that a capability is the unit of SELECTION, not of
41
+ * implementation: the host picks one plugin per capability, so a joiner carried
42
+ * here is whichever plugin the operator chose to MEASURE with. `capabilities/mixer.ts`
43
+ * has the rest of it.
44
+ *
45
+ * ## What the host guarantees, and what it cannot
46
+ *
47
+ * The host resolves {@link AnalysisRef.audioUrl} because a plugin cannot: the
48
+ * copy that can actually be served is a binding the catalog owns, and asking one
49
+ * plugin to go through another is not a thing the host permits. In exchange the
50
+ * host cannot see the bytes, so it cannot tell a complete download from a
51
+ * truncated one — which is why {@link TrackAnalysis.complete} is reported rather
52
+ * than inferred. See the note on that field: it is the difference between a cache
53
+ * that can be trusted and one that cannot.
54
+ *
55
+ * Every shape here is JSON-safe. Offsets are integer milliseconds.
56
+ */
57
+ import type { PluginLifecycle } from '../plugin.lifecycle.js';
58
+ /**
59
+ * The current shape of {@link TrackAnalysis.data}.
60
+ *
61
+ * Bumped whenever a detector's output changes shape, which is what lets a stored
62
+ * row be recognised as STALE rather than read as missing or, worse, as current.
63
+ * Reanalysis then falls out of an ordinary "needs work" query instead of needing
64
+ * a migration.
65
+ *
66
+ * **An OPTIONAL field being added is not that**, and does not bump this. Every
67
+ * consumer of `data` already has a defined answer for a field that is absent —
68
+ * it has to, since an analyzer may not compute one — so a row written before the
69
+ * field existed is still a correct row of this version rather than a stale one.
70
+ * Bumping for it would mark the whole catalog for re-measurement to gain
71
+ * something the station degrades over anyway. `tagGainDb` and friends arrived
72
+ * exactly this way.
73
+ *
74
+ * The host compares this against what it stored, so a plugin must report the
75
+ * version it actually produced rather than this constant, in case the two have
76
+ * drifted apart across an upgrade.
77
+ */
78
+ export declare const ANALYSIS_SCHEMA_VERSION = 1;
79
+ /**
80
+ * How the host asks about one track.
81
+ *
82
+ * Deliberately not a {@link TrackRef}: nothing here is matched by name, so
83
+ * artist and title would be decoration. What a measurement needs is bytes and a
84
+ * way to tell a truncation from a short record.
85
+ */
86
+ export interface AnalysisRef {
87
+ /**
88
+ * The canonical catalog id for this track.
89
+ *
90
+ * Passed for the plugin's own logging and for its own caching, if it keeps
91
+ * any. It is not a key into anything the plugin can read, and the host does
92
+ * not expect it back.
93
+ */
94
+ trackId: string;
95
+ /**
96
+ * A complete, fetchable URL for the audio, resolved by the host.
97
+ *
98
+ * Carries its own authentication, exactly as the playout URLs do: whatever
99
+ * fetches this sends no headers on the host's behalf. It may be short-lived,
100
+ * so fetch it during the call rather than storing it.
101
+ *
102
+ * **It has to be reachable from wherever the decoding happens**, which is not
103
+ * necessarily where this plugin runs. An address that resolves inside the
104
+ * API process and not inside a sidecar container is the first thing to check
105
+ * when every analysis fails at once.
106
+ */
107
+ audioUrl: string;
108
+ /**
109
+ * How long the catalog believes the track is.
110
+ *
111
+ * A cross-check rather than an input to any measurement: audio that decodes
112
+ * to appreciably less than this was truncated, and measuring it would report
113
+ * a confident cold ending for a record that fades. Absent when the catalog
114
+ * never learned a duration, which is ordinary and is not a reason to refuse.
115
+ */
116
+ durationMs?: number;
117
+ }
118
+ /**
119
+ * The four points, all absolute offsets into the file.
120
+ *
121
+ * Including `cueOut`. Storing it relative to `cueIn` is the obvious-looking
122
+ * choice and it is wrong: everything downstream seeks in file time, so a relative
123
+ * figure has to be re-based at every read, and eventually one read does not.
124
+ *
125
+ * Two lengths fall out — `intro = introEnd - cueIn` and
126
+ * `outro = cueOut - outroStart` — and they are what a transition is actually
127
+ * sized from. Neither is stored, because a stored derivation is a second thing
128
+ * that can disagree with the first.
129
+ */
130
+ export interface TrackCuePoints {
131
+ /** Where audio actually starts, past the leading silence. */
132
+ cueIn: number;
133
+ /**
134
+ * Where the record is fully underway: the beat established, or the vocal in.
135
+ *
136
+ * The talk-up limit, and one of the two points that is real work. A detector
137
+ * weighted to low frequencies places this early on a record that opens with
138
+ * a pad, which reads as "the intro is over" while it plainly is not.
139
+ */
140
+ introEnd: number;
141
+ /**
142
+ * Where the ending begins, so the earliest a blend may start.
143
+ *
144
+ * The other real one, and the one with a specific failure to design against:
145
+ * a low-frequency-weighted detector places it too early on a quiet ending,
146
+ * which is exactly the case an ending-aware transition exists to serve.
147
+ */
148
+ outroStart: number;
149
+ /** Where audio actually stops, before the trailing silence. */
150
+ cueOut: number;
151
+ }
152
+ /**
153
+ * How loud the record is, and how close it already runs to its ceiling.
154
+ *
155
+ * Every field is OPTIONAL, and absent is a real answer rather than a gap: a
156
+ * silent or near-silent track has no loudness, and the alternative to omitting
157
+ * it is a floor value like -80 that a caller would then "correct" by fifty
158
+ * decibels. Absent means no opinion, which is what every consumer of these
159
+ * measurements already has to handle.
160
+ *
161
+ * They are also optional in the weaker sense that an analyzer may not compute
162
+ * them at all. A plugin that only finds cue points is a valid analyzer; a
163
+ * station reading these has to degrade to its live normalizer, which is what it
164
+ * does today anyway.
165
+ */
166
+ export interface TrackLoudness {
167
+ /**
168
+ * Gated programme loudness in LUFS, to ITU-R BS.1770.
169
+ *
170
+ * Gated, which is the whole difference between this and an average level: a
171
+ * record with a long quiet outro is as loud as its body, not as loud as its
172
+ * mean. The station's per-track gain is the distance from this to whatever
173
+ * target it holds.
174
+ */
175
+ integratedLufs?: number;
176
+ /**
177
+ * The highest inter-sample peak in dBTP, which is what caps a boost.
178
+ *
179
+ * Distinct from {@link samplePeakDb} and the distinction is the point: the
180
+ * reconstructed waveform between two samples can exceed both of them,
181
+ * routinely by around a decibel. A gain computed against sample peak alone
182
+ * is how a quiet master gets lifted into clipping, so this is the number a
183
+ * boost has to respect.
184
+ *
185
+ * Legitimately positive. A value above 0 dBTP means the master already
186
+ * overshoots on playback, which is worth knowing before adding anything.
187
+ */
188
+ truePeakDb?: number;
189
+ /**
190
+ * The highest actual sample, in dBFS.
191
+ *
192
+ * Carried alongside the true peak rather than instead of it, because the gap
193
+ * between them is diagnostic: a wide one means the master is already fighting
194
+ * its own ceiling.
195
+ */
196
+ samplePeakDb?: number;
197
+ }
198
+ /**
199
+ * What the FILE says about its own loudness, as opposed to what was measured.
200
+ *
201
+ * A different kind of claim from {@link TrackLoudness}, which is why it is a
202
+ * different interface: those fields are this analyzer's opinion, and these are
203
+ * whoever mastered or scanned the record telling the station what they decided.
204
+ * A consumer that prefers one over the other has to be able to tell them apart,
205
+ * which it cannot do if they arrive in the same field.
206
+ *
207
+ * All optional, and most files carry none of them.
208
+ */
209
+ export interface TrackTaggedLoudness {
210
+ /**
211
+ * The gain the file's own tags ask for, in dB.
212
+ *
213
+ * **Meaningless without {@link tagReferenceLufs}**, and that is the whole
214
+ * reason both are reported. A gain is a correction relative to some level,
215
+ * and the two conventions in the wild are five decibels apart, so a station
216
+ * that stored only this would be storing the answer to a question it can no
217
+ * longer ask.
218
+ */
219
+ tagGainDb?: number;
220
+ /**
221
+ * The loudness {@link tagGainDb} is relative to, in LUFS.
222
+ *
223
+ * -23 for an R128 tag, where the specification fixes it. -18 for a
224
+ * ReplayGain tag, where it is an ASSUMPTION: ReplayGain 2.0 targets -18 and
225
+ * every current scanner writes it, but the older convention used the same
226
+ * tag names with no version field, so a file carrying it is
227
+ * indistinguishable from the outside.
228
+ *
229
+ * Subtracting this pair gives the loudness the tagger believed the record
230
+ * has, which is the figure a station's own target applies to.
231
+ */
232
+ tagReferenceLufs?: number;
233
+ /**
234
+ * The peak the file's tags declare, in dBFS.
235
+ *
236
+ * A SAMPLE peak, always, because that is what ReplayGain defines. It is not
237
+ * a substitute for {@link TrackLoudness.truePeakDb} and nothing should cap a
238
+ * boost with it; it is stored because the gap between the two says how hard
239
+ * the master is already running.
240
+ */
241
+ tagPeakDb?: number;
242
+ }
243
+ /**
244
+ * What one analysis produced.
245
+ *
246
+ * `data` is deliberately the only place measurements live, and the host stores
247
+ * it whole without reading the individual fields. That is what lets a later
248
+ * schema version add a beat grid or a vocal curve without touching the plugin
249
+ * contract, the host, or the database.
250
+ */
251
+ export interface TrackAnalysis {
252
+ /**
253
+ * The shape of {@link data}, as this plugin actually produced it.
254
+ *
255
+ * Report what was measured rather than {@link ANALYSIS_SCHEMA_VERSION}: an
256
+ * adapter over a separate analyzer is reporting that analyzer's version, and
257
+ * the two drift the moment one of them is upgraded and the other is not. A
258
+ * version the host does not know is a configuration problem it can name,
259
+ * where a wrong one is a row nothing can read and nothing can explain.
260
+ */
261
+ schemaVersion: number;
262
+ /**
263
+ * Whether the whole file was measured.
264
+ *
265
+ * **Load-bearing, and the host cannot check it.** A byte-capped, idle-timed
266
+ * out or otherwise truncated download produces perfectly confident
267
+ * measurements of a file that was never the track, and the specific lie it
268
+ * tells is that a record which fades ended cold. Since the host never sees
269
+ * the bytes, this is the only signal that separates a measurement worth
270
+ * keeping from one worth discarding, and a plugin that always answers `true`
271
+ * has quietly disabled the check.
272
+ *
273
+ * A genuinely short track is `true`. A download that stopped early is
274
+ * `false`, and the two are told apart with {@link AnalysisRef.durationMs}
275
+ * where there is one.
276
+ */
277
+ complete: boolean;
278
+ /**
279
+ * The measurements, in the shape {@link schemaVersion} names.
280
+ *
281
+ * Typed as the v1 fields plus room to grow rather than as a closed
282
+ * interface, because the host passes it through unread. A v2 payload with a
283
+ * tempo and a downbeat grid is the same call, the same plugin method, and a
284
+ * different number above.
285
+ *
286
+ * The cue points are required and the loudness is not, which reflects what
287
+ * each costs to produce: the points come from the decode that has already
288
+ * happened, where loudness needs a filter chain an analyzer may reasonably
289
+ * not implement.
290
+ */
291
+ data: TrackCuePoints & TrackLoudness & TrackTaggedLoudness & Record<string, unknown>;
292
+ /**
293
+ * How long the audio turned out to be once decoded.
294
+ *
295
+ * The honest figure, as opposed to the catalog's claim in
296
+ * {@link AnalysisRef.durationMs}. Worth reporting even when the two agree:
297
+ * where they do not, this is the one that the offsets above are on the same
298
+ * timeline as.
299
+ */
300
+ durationMs?: number;
301
+ /**
302
+ * What did the measuring, as a name and version.
303
+ *
304
+ * Stored as provenance, so a row can be attributed after the fact when a
305
+ * detector turns out to have been wrong about a class of records. Free-text
306
+ * and never parsed.
307
+ */
308
+ analyzer?: string;
309
+ }
310
+ /** A plugin that can measure a track's audio. */
311
+ export interface AnalysisProvider extends PluginLifecycle {
312
+ /**
313
+ * Measure one track.
314
+ *
315
+ * One track per call, with no batch sibling: the unit of work is one file's
316
+ * bytes, and there is no upstream round trip to amortise across several. How
317
+ * many run at once is the host's decision, taken against hardware it can see
318
+ * and this plugin cannot.
319
+ *
320
+ * Expect to be given minutes rather than seconds — decoding a full record is
321
+ * not a request, it is a job — but honour `host.signal` all the same, because
322
+ * a station shutting down should not wait on a measurement nobody will read.
323
+ *
324
+ * @throws {PluginError} `config` when the plugin is not set up enough to try
325
+ * (no analyzer address), `upstream` when the audio could not be fetched or
326
+ * could not be decoded, `timeout` when the analyzer did not answer.
327
+ */
328
+ analyzeTrack(ref: AnalysisRef): Promise<TrackAnalysis>;
329
+ }
330
+ //# sourceMappingURL=analysis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analysis.d.ts","sourceRoot":"","sources":["../../src/capabilities/analysis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IACxB;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;;;;;;;;OAWG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,cAAc;IAC3B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IAEd;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,aAAa;IAC1B;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB;IAChC;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC1B;;;;;;;;OAQG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;;;;;;;;;;;;;OAcG;IACH,QAAQ,EAAE,OAAO,CAAC;IAElB;;;;;;;;;;;;OAYG;IACH,IAAI,EAAE,cAAc,GAAG,aAAa,GAAG,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAErF;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,iDAAiD;AACjD,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACrD;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CAC1D"}