@deadair/plugin-sdk 0.2.4 → 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 +49 -1
- package/dist/capabilities/speech.d.ts +63 -0
- package/dist/capabilities/speech.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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,
|