@deadair/plugin-sdk 0.2.3 → 0.3.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.
package/README.md CHANGED
@@ -720,7 +720,10 @@ The host never interprets that string, and that is deliberate. It is what lets
720
720
  one station voice be a named preset on one engine and a cloned reference clip on
721
721
  another, so swapping engines does not rewrite every persona. Engine-specific
722
722
  tuning belongs in your config, not in the request: the host should not be
723
- carrying knobs only one implementation understands.
723
+ carrying knobs only one implementation understands. The one thing about a
724
+ reading it does carry is a delivery, and that is a word rather than a knob; see
725
+ [Cues and deliveries](#cues-and-deliveries).
726
+
724
727
 
725
728
  Implement `listVoices()` if you have more than one, so the console can draw a
726
729
  list and preview them. It is optional, and a single-voice plugin is a legitimate
@@ -746,8 +749,53 @@ than `select` — a cell with choices renders as an autocomplete, so a value you
746
749
  list cannot enumerate (a blend expression, a clip added a minute ago) stays
747
750
  typeable.
748
751
 
752
+ ### Cues and deliveries
753
+
754
+ Two things about how a line is performed reach you in the STATION's words, and
755
+ both work the same way. The station names what it wants, you say which of those
756
+ your engine can do right now, and you translate each one into whatever your
757
+ engine actually takes. Nothing engine-specific ever crosses the boundary, so a
758
+ station can change engines without rewriting a single script.
759
+
760
+ - **A cue** is something a presenter does at a place in a sentence: `[laugh]`,
761
+ `[sigh]`, the rest of `SPEECH_CUES`. It rides inside `SpeechRequest.text`,
762
+ because where it happens is part of what it is. Claim the ones you perform with
763
+ `listCues()`.
764
+ - **A delivery** is how the WHOLE line is read: `hushed` or `frantic`, the whole
765
+ of `SPEECH_DELIVERIES`. It rides beside the text as `SpeechRequest.delivery`,
766
+ and absent is the voice's own ordinary reading. Claim the ones you perform with
767
+ `listDeliveries()`.
768
+
769
+ The host strips every cue and drops every delivery you did not claim before it
770
+ calls `speak`, and the model writing a break is only offered what you claimed.
771
+ So implementing neither is always safe: your engine never sees a word it would
772
+ read aloud or ignore. The one way to break it is to claim something you cannot
773
+ perform.
774
+
775
+ **Answer from what the engine IS right now**, not from what you were built
776
+ against. On the engine these were written for, the loaded MODEL decides: one
777
+ build performs cues and ignores expressiveness, the others do the opposite. So
778
+ both methods ask the server and answer empty when it cannot be reached.
779
+
780
+ **Translate relative to the voice, not to a fixed point.** A delivery is a
781
+ direction from wherever the voice already is, so a character who is intense at
782
+ rest stays more intense than their neighbours when hushed. Two sketches of the
783
+ same word on different engines:
784
+
785
+ ```ts
786
+ // An engine with expressiveness dials, tuned per voice in your own config.
787
+ const frantic = { exaggeration: clamp(voice.exaggeration + 0.4), cfg_weight: voice.cfgWeight };
788
+
789
+ // An engine with only a speed.
790
+ const hushed = { speed: (voice.speed ?? 1) * 0.9 };
791
+ ```
792
+
793
+ The numbers belong to you and live in your config beside the voice they tune.
794
+ The host never sees them, which is why the station's side of this is a word.
795
+
749
796
  ## Producing words
750
797
 
798
+
751
799
  A plugin that declares `llm` continues a conversation. It is a **transport, not a
752
800
  writer**: nothing in this capability knows what a break, a show or a running
753
801
  order is, because deciding what to say is the station's business and the shapes
@@ -28,6 +28,15 @@
28
28
  * inside your own config where it belongs, rather than asking the host to carry
29
29
  * knobs only you understand.
30
30
  *
31
+ * ## A delivery is a word the station chose, too
32
+ *
33
+ * The one thing about HOW a line is read that the host does carry is
34
+ * {@link SpeechRequest.delivery}: `hushed` or `frantic`, in the station's words
35
+ * and never as a number. That is the same bargain as a voice and a cue. The host
36
+ * says what it wants in a vocabulary every engine can be asked in, and each plugin
37
+ * translates it into whatever its engine has, whether that is an expressiveness
38
+ * dial, a speed, a style preset or nothing at all. The numbers stay in your config.
39
+ *
31
40
  * Every shape here is JSON-safe.
32
41
  */
33
42
  import type { PluginLifecycle } from '../plugin.lifecycle.js';
@@ -89,6 +98,37 @@ export declare function cuesIn(text: string): SpeechCue[];
89
98
  * and stays exactly as it arrived: this is not a bracket stripper.
90
99
  */
91
100
  export declare function withoutCues(text: string, keep?: Iterable<SpeechCue>): string;
101
+ /**
102
+ * How a whole line is read, as the station names it.
103
+ *
104
+ * Two, and the middle is deliberately not one of them: a request with no delivery is the voice's own
105
+ * ordinary reading, which is what nearly every line should be. A third word for "ordinary" would be a
106
+ * second way to ask for nothing, and would key a second cached preview of identical audio.
107
+ *
108
+ * The vocabulary is the STATION's and translating it is yours, exactly as for {@link SPEECH_CUES}.
109
+ * `hushed` is quieter, slower, closer to the microphone; `frantic` is urgent, faster, barely holding
110
+ * on. Whether your engine gets there with an expressiveness dial, a speed, a style token or a
111
+ * different reference clip is engine business, and the host never learns which.
112
+ *
113
+ * ## Why a word and not a number
114
+ *
115
+ * A number is a promise about one engine's scale. `exaggeration: 0.9` means something to one family of
116
+ * models and nothing to the next, so a station that asked for it would be tied to the engine it was
117
+ * built against, which is the thing the voice indirection exists to prevent. A word survives a change
118
+ * of engine. The numbers it becomes live in each plugin's own config, beside the voice they tune.
119
+ *
120
+ * ## Claim only what you can perform
121
+ *
122
+ * {@link SpeechPluginInstance.listDeliveries} says which of these your engine can do RIGHT NOW, and the
123
+ * host drops any delivery you did not claim before it calls {@link SpeechPluginInstance.speak}. So a
124
+ * plugin that implements nothing here never sees one, and the writer is never offered a delivery the
125
+ * engine would ignore. Claiming one you cannot perform is the only way to break it.
126
+ */
127
+ export declare const SPEECH_DELIVERIES: readonly ["hushed", "frantic"];
128
+ /** One of {@link SPEECH_DELIVERIES}. */
129
+ export type SpeechDelivery = (typeof SPEECH_DELIVERIES)[number];
130
+ /** Whether a value is one of {@link SPEECH_DELIVERIES}, exactly as written. */
131
+ export declare function isSpeechDelivery(value: unknown): value is SpeechDelivery;
92
132
  /** One thing to say. */
93
133
  export interface SpeechRequest {
94
134
  /**
@@ -112,6 +152,15 @@ export interface SpeechRequest {
112
152
  * that instead. Ignore it entirely if your engine emits one format.
113
153
  */
114
154
  format?: string;
155
+ /**
156
+ * How to read the whole line, or absent for the voice's own ordinary reading.
157
+ *
158
+ * Only ever one you claimed through {@link SpeechPluginInstance.listDeliveries}: the host drops the
159
+ * rest before this call. Translate it into your engine's own controls, relative to whatever the
160
+ * voice already sounds like, so a voice that is intense at rest is still more intense than its
161
+ * neighbours when hushed. See {@link SPEECH_DELIVERIES}.
162
+ */
163
+ delivery?: SpeechDelivery;
115
164
  }
116
165
  /** The audio for one {@link SpeechPluginInstance.speak}, and what it is. */
117
166
  export interface SpeechHandle {
@@ -207,5 +256,19 @@ export interface SpeechPluginInstance extends PluginLifecycle {
207
256
  * not fail to write one.
208
257
  */
209
258
  listCues?(): Promise<readonly SpeechCue[]>;
259
+ /**
260
+ * Which of {@link SPEECH_DELIVERIES} this plugin can perform RIGHT NOW.
261
+ *
262
+ * Optional, and absent means none, for the reason {@link listCues} gives: most engines have no
263
+ * such control, and saying nothing costs a plugin nothing because the host drops what you do not
264
+ * claim.
265
+ *
266
+ * The same three rules as for cues, too. Answer from what the engine currently IS, since on some
267
+ * engines the control belongs to the loaded model rather than to the server. Keep it cheap,
268
+ * because it is asked on the path that writes a break. And answer empty rather than throwing when
269
+ * the engine cannot be reached, because a station that cannot ask should write an ordinary
270
+ * reading rather than fail to write one.
271
+ */
272
+ listDeliveries?(): Promise<readonly SpeechDelivery[]>;
210
273
  }
211
274
  //# sourceMappingURL=speech.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"speech.d.ts","sourceRoot":"","sources":["../../src/capabilities/speech.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,WAAW,0FAA2F,CAAC;AAEpH,kCAAkC;AAClC,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,+EAA+E;AAC/E,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CAEhD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,QAAQ,CAAC,SAAS,CAAM,GAAG,MAAM,CAQhF;AAYD,wBAAwB;AACxB,MAAM,WAAW,aAAa;IAC1B;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IACzB;;;;;;;;;OASG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;;OAMG;IACH,KAAK,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;CACrC;AAED,8CAA8C;AAC9C,MAAM,WAAW,WAAW;IACxB,0DAA0D;IAC1D,EAAE,EAAE,MAAM,CAAC;IAEX,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IAEd,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,+BAA+B;AAC/B,MAAM,WAAW,oBAAqB,SAAQ,eAAe;IACzD;;;;;;;;;;OAUG;IACH,KAAK,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAErD;;;;;;OAMG;IACH,UAAU,CAAC,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAEtC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,IAAI,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CAAC;CAC9C"}
1
+ {"version":3,"file":"speech.d.ts","sourceRoot":"","sources":["../../src/capabilities/speech.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,WAAW,0FAA2F,CAAC;AAEpH,kCAAkC;AAClC,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,+EAA+E;AAC/E,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CAEhD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,QAAQ,CAAC,SAAS,CAAM,GAAG,MAAM,CAQhF;AAYD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,iBAAiB,gCAAiC,CAAC;AAEhE,wCAAwC;AACxC,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhE,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAExE;AAED,wBAAwB;AACxB,MAAM,WAAW,aAAa;IAC1B;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IACzB;;;;;;;;;OASG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;;OAMG;IACH,KAAK,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;CACrC;AAED,8CAA8C;AAC9C,MAAM,WAAW,WAAW;IACxB,0DAA0D;IAC1D,EAAE,EAAE,MAAM,CAAC;IAEX,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IAEd,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,+BAA+B;AAC/B,MAAM,WAAW,oBAAqB,SAAQ,eAAe;IACzD;;;;;;;;;;OAUG;IACH,KAAK,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAErD;;;;;;OAMG;IACH,UAAU,CAAC,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAEtC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,IAAI,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CAAC;IAE3C;;;;;;;;;;;;OAYG;IACH,cAAc,CAAC,IAAI,OAAO,CAAC,SAAS,cAAc,EAAE,CAAC,CAAC;CACzD"}
package/dist/index.js CHANGED
@@ -291,6 +291,14 @@ __name(withoutCues, "withoutCues");
291
291
  var cuePattern = /* @__PURE__ */ __name(() => new RegExp(`\\[(${[
292
292
  ...SPEECH_CUES
293
293
  ].sort((left, right) => right.length - left.length).join("|")})\\]`, "gi"), "cuePattern");
294
+ var SPEECH_DELIVERIES = [
295
+ "hushed",
296
+ "frantic"
297
+ ];
298
+ function isSpeechDelivery(value) {
299
+ return typeof value === "string" && SPEECH_DELIVERIES.includes(value);
300
+ }
301
+ __name(isSpeechDelivery, "isSpeechDelivery");
294
302
 
295
303
  // src/plugin.http.ts
296
304
  var MAX_UPSTREAM_MESSAGE = 200;
@@ -1101,6 +1109,7 @@ export {
1101
1109
  PluginError,
1102
1110
  ROW_ID_KEY,
1103
1111
  SPEECH_CUES,
1112
+ SPEECH_DELIVERIES,
1104
1113
  baseForm,
1105
1114
  collectGeneration,
1106
1115
  configBaseUrl,
@@ -1126,6 +1135,7 @@ export {
1126
1135
  isPluginError,
1127
1136
  isResourceScopedCode,
1128
1137
  isRowSecretKey,
1138
+ isSpeechDelivery,
1129
1139
  isZodSchema,
1130
1140
  jsonBody,
1131
1141
  networkPermissionSchema,