@moxxy/plugin-stt-local 0.28.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 (61) hide show
  1. package/LICENSE +21 -0
  2. package/dist/audio.d.ts +55 -0
  3. package/dist/audio.d.ts.map +1 -0
  4. package/dist/audio.js +197 -0
  5. package/dist/audio.js.map +1 -0
  6. package/dist/decode.d.ts +25 -0
  7. package/dist/decode.d.ts.map +1 -0
  8. package/dist/decode.js +50 -0
  9. package/dist/decode.js.map +1 -0
  10. package/dist/ffmpeg.d.ts +26 -0
  11. package/dist/ffmpeg.d.ts.map +1 -0
  12. package/dist/ffmpeg.js +207 -0
  13. package/dist/ffmpeg.js.map +1 -0
  14. package/dist/host-client.d.ts +70 -0
  15. package/dist/host-client.d.ts.map +1 -0
  16. package/dist/host-client.js +156 -0
  17. package/dist/host-client.js.map +1 -0
  18. package/dist/host-protocol.d.ts +96 -0
  19. package/dist/host-protocol.d.ts.map +1 -0
  20. package/dist/host-protocol.js +70 -0
  21. package/dist/host-protocol.js.map +1 -0
  22. package/dist/index.d.ts +32 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +61 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/local-stt.d.ts +86 -0
  27. package/dist/local-stt.d.ts.map +1 -0
  28. package/dist/local-stt.js +201 -0
  29. package/dist/local-stt.js.map +1 -0
  30. package/dist/models.d.ts +47 -0
  31. package/dist/models.d.ts.map +1 -0
  32. package/dist/models.js +61 -0
  33. package/dist/models.js.map +1 -0
  34. package/dist/platform.d.ts +37 -0
  35. package/dist/platform.d.ts.map +1 -0
  36. package/dist/platform.js +96 -0
  37. package/dist/platform.js.map +1 -0
  38. package/dist/sidecar.d.ts +18 -0
  39. package/dist/sidecar.d.ts.map +1 -0
  40. package/dist/sidecar.js +86 -0
  41. package/dist/sidecar.js.map +1 -0
  42. package/package.json +67 -0
  43. package/src/audio.test.ts +213 -0
  44. package/src/audio.ts +220 -0
  45. package/src/decode.test.ts +126 -0
  46. package/src/decode.ts +74 -0
  47. package/src/ffmpeg.test.ts +142 -0
  48. package/src/ffmpeg.ts +215 -0
  49. package/src/host-client.test.ts +208 -0
  50. package/src/host-client.ts +200 -0
  51. package/src/host-protocol.test.ts +171 -0
  52. package/src/host-protocol.ts +152 -0
  53. package/src/index.ts +108 -0
  54. package/src/live.test.ts +80 -0
  55. package/src/local-stt.test.ts +224 -0
  56. package/src/local-stt.ts +273 -0
  57. package/src/models.test.ts +58 -0
  58. package/src/models.ts +111 -0
  59. package/src/platform.test.ts +77 -0
  60. package/src/platform.ts +109 -0
  61. package/src/sidecar.ts +97 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Parent-side manager for the sherpa sidecar: lazy-spawn on first transcribe,
3
+ * a correlated request/reply protocol over the fork IPC channel, restart-once
4
+ * on a crash, and a clean shutdown. The `fork` itself is injectable (a
5
+ * {@link ForkLike}) so tests drive the whole protocol against a fake child with
6
+ * no real process.
7
+ */
8
+ import type { HostRequest, TranscribeResult } from './host-protocol.js';
9
+ /** The minimal child-process surface the client drives — satisfied by a real
10
+ * `ChildProcess` and by a test fake. */
11
+ export interface ChildHandle {
12
+ send(message: unknown): boolean;
13
+ on(event: 'message', listener: (message: unknown) => void): this;
14
+ on(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
15
+ on(event: 'error', listener: (err: Error) => void): this;
16
+ kill(signal?: NodeJS.Signals): boolean;
17
+ }
18
+ /** Fork the sidecar with the given env overrides. Injectable for tests. */
19
+ export type ForkLike = (modulePath: string, env: NodeJS.ProcessEnv) => ChildHandle;
20
+ /** Default fork: advanced serialization (Float32Array round-trip), an IPC
21
+ * channel, and inherited std streams so sherpa's own diagnostics reach the
22
+ * runner log. `env` already carries the platform loader-path var. */
23
+ export declare const defaultFork: ForkLike;
24
+ export interface HostClientOptions {
25
+ /** Absolute path to the built sidecar (`dist/sidecar.js`). */
26
+ readonly hostPath: string;
27
+ /** Env the child is forked with (platform loader path merged over process.env). */
28
+ readonly env: NodeJS.ProcessEnv;
29
+ /** Injected fork (tests). Defaults to {@link defaultFork}. */
30
+ readonly forkImpl?: ForkLike;
31
+ /** Per-request deadline. Default 300s (a long clip on a big model on CPU). */
32
+ readonly requestTimeoutMs?: number;
33
+ /** Optional diagnostic sink. */
34
+ readonly log?: (msg: string) => void;
35
+ }
36
+ /** The slice of a host the transcriber depends on — lets tests swap a fake. */
37
+ export interface HostClientLike {
38
+ transcribe(req: Omit<HostRequest, 'id' | 'type'>): Promise<TranscribeResult>;
39
+ shutdown(): void;
40
+ }
41
+ /** Raised when the sidecar dies with a request in flight; drives the one retry. */
42
+ export declare class HostCrashError extends Error {
43
+ constructor(message: string);
44
+ }
45
+ export declare class HostClient implements HostClientLike {
46
+ private readonly hostPath;
47
+ private readonly env;
48
+ private readonly forkImpl;
49
+ private readonly timeoutMs;
50
+ private readonly log;
51
+ private child;
52
+ private readonly pending;
53
+ private nextId;
54
+ private disposed;
55
+ constructor(opts: HostClientOptions);
56
+ /** Transcribe, restarting the sidecar ONCE if it crashes mid-request. */
57
+ transcribe(req: Omit<HostRequest, 'id' | 'type'>): Promise<TranscribeResult>;
58
+ private send;
59
+ private ensureChild;
60
+ private onMessage;
61
+ private onExit;
62
+ private onError;
63
+ /** Resolve/reject one pending request, clearing its timer and map entry. */
64
+ private settle;
65
+ private rejectAll;
66
+ private killChild;
67
+ /** Kill the sidecar and fail any in-flight requests. Idempotent. */
68
+ shutdown(): void;
69
+ }
70
+ //# sourceMappingURL=host-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-client.d.ts","sourceRoot":"","sources":["../src/host-client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAa,WAAW,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEnF;yCACyC;AACzC,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC;IAChC,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC;IACjE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAAC;IAChG,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAAC;IACzD,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC;CACxC;AAED,2EAA2E;AAC3E,MAAM,MAAM,QAAQ,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,WAAW,CAAC;AAEnF;;sEAEsE;AACtE,eAAO,MAAM,WAAW,EAAE,QAMI,CAAC;AAE/B,MAAM,WAAW,iBAAiB;IAChC,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mFAAmF;IACnF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,gCAAgC;IAChC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC7E,QAAQ,IAAI,IAAI,CAAC;CAClB;AAED,mFAAmF;AACnF,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI5B;AAUD,qBAAa,UAAW,YAAW,cAAc;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAwB;IAE5C,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;IACtD,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,QAAQ,CAAS;gBAEb,IAAI,EAAE,iBAAiB;IAQnC,yEAAyE;IACnE,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAYlF,OAAO,CAAC,IAAI;IAuBZ,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,MAAM;IAOd,OAAO,CAAC,OAAO;IAKf,4EAA4E;IAC5E,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,SAAS;IAYjB,oEAAoE;IACpE,QAAQ,IAAI,IAAI;CAMjB"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Parent-side manager for the sherpa sidecar: lazy-spawn on first transcribe,
3
+ * a correlated request/reply protocol over the fork IPC channel, restart-once
4
+ * on a crash, and a clean shutdown. The `fork` itself is injectable (a
5
+ * {@link ForkLike}) so tests drive the whole protocol against a fake child with
6
+ * no real process.
7
+ */
8
+ import { fork } from 'node:child_process';
9
+ /** Default fork: advanced serialization (Float32Array round-trip), an IPC
10
+ * channel, and inherited std streams so sherpa's own diagnostics reach the
11
+ * runner log. `env` already carries the platform loader-path var. */
12
+ export const defaultFork = (modulePath, env) => fork(modulePath, [], {
13
+ serialization: 'advanced',
14
+ env,
15
+ execArgv: [],
16
+ stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
17
+ });
18
+ /** Raised when the sidecar dies with a request in flight; drives the one retry. */
19
+ export class HostCrashError extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = 'HostCrashError';
23
+ }
24
+ }
25
+ const DEFAULT_TIMEOUT_MS = 300_000;
26
+ export class HostClient {
27
+ hostPath;
28
+ env;
29
+ forkImpl;
30
+ timeoutMs;
31
+ log;
32
+ child = null;
33
+ pending = new Map();
34
+ nextId = 1;
35
+ disposed = false;
36
+ constructor(opts) {
37
+ this.hostPath = opts.hostPath;
38
+ this.env = opts.env;
39
+ this.forkImpl = opts.forkImpl ?? defaultFork;
40
+ this.timeoutMs = opts.requestTimeoutMs ?? DEFAULT_TIMEOUT_MS;
41
+ this.log = opts.log ?? (() => { });
42
+ }
43
+ /** Transcribe, restarting the sidecar ONCE if it crashes mid-request. */
44
+ async transcribe(req) {
45
+ try {
46
+ return await this.send(req);
47
+ }
48
+ catch (err) {
49
+ if (err instanceof HostCrashError && !this.disposed) {
50
+ this.log(`stt-local: sherpa sidecar crashed (${err.message}) — restarting once`);
51
+ return await this.send(req); // a second crash propagates
52
+ }
53
+ throw err;
54
+ }
55
+ }
56
+ send(req) {
57
+ if (this.disposed)
58
+ return Promise.reject(new Error('stt-local host is shut down'));
59
+ const child = this.ensureChild();
60
+ const id = this.nextId++;
61
+ const message = { ...req, id, type: 'transcribe' };
62
+ return new Promise((resolve, reject) => {
63
+ const timer = setTimeout(() => {
64
+ this.pending.delete(id);
65
+ // A hung decode is unrecoverable for this child — reset it so the next
66
+ // call starts fresh.
67
+ this.killChild();
68
+ reject(new Error(`stt-local transcription timed out after ${this.timeoutMs} ms`));
69
+ }, this.timeoutMs);
70
+ timer.unref?.();
71
+ this.pending.set(id, { resolve, reject, timer });
72
+ try {
73
+ child.send(message);
74
+ }
75
+ catch (err) {
76
+ this.settle(id, () => reject(err instanceof Error ? err : new Error(String(err))));
77
+ }
78
+ });
79
+ }
80
+ ensureChild() {
81
+ if (this.child)
82
+ return this.child;
83
+ const child = this.forkImpl(this.hostPath, this.env);
84
+ this.child = child;
85
+ child.on('message', (msg) => this.onMessage(msg));
86
+ child.on('exit', (code, signal) => this.onExit(code, signal));
87
+ child.on('error', (err) => this.onError(err));
88
+ return child;
89
+ }
90
+ onMessage(msg) {
91
+ const reply = msg;
92
+ if (!reply || typeof reply !== 'object' || typeof reply.id !== 'number')
93
+ return;
94
+ const p = this.pending.get(reply.id);
95
+ if (!p)
96
+ return;
97
+ this.settle(reply.id, () => {
98
+ if (reply.ok) {
99
+ p.resolve(reply.language !== undefined ? { text: reply.text, language: reply.language } : { text: reply.text });
100
+ }
101
+ else {
102
+ p.reject(new Error(`stt-local sidecar ${reply.error.kind} error: ${reply.error.message}`));
103
+ }
104
+ });
105
+ }
106
+ onExit(code, signal) {
107
+ this.child = null;
108
+ if (this.pending.size === 0)
109
+ return;
110
+ const why = `sidecar exited (code=${code ?? 'null'}, signal=${signal ?? 'null'})`;
111
+ this.rejectAll(new HostCrashError(why));
112
+ }
113
+ onError(err) {
114
+ this.child = null;
115
+ this.rejectAll(new HostCrashError(`sidecar spawn/runtime error: ${err.message}`));
116
+ }
117
+ /** Resolve/reject one pending request, clearing its timer and map entry. */
118
+ settle(id, run) {
119
+ const p = this.pending.get(id);
120
+ if (!p)
121
+ return;
122
+ if (p.timer)
123
+ clearTimeout(p.timer);
124
+ this.pending.delete(id);
125
+ run();
126
+ }
127
+ rejectAll(err) {
128
+ for (const [id, p] of this.pending) {
129
+ if (p.timer)
130
+ clearTimeout(p.timer);
131
+ this.pending.delete(id);
132
+ p.reject(err);
133
+ }
134
+ }
135
+ killChild() {
136
+ const child = this.child;
137
+ this.child = null;
138
+ if (child) {
139
+ try {
140
+ child.kill('SIGKILL');
141
+ }
142
+ catch {
143
+ /* already gone */
144
+ }
145
+ }
146
+ }
147
+ /** Kill the sidecar and fail any in-flight requests. Idempotent. */
148
+ shutdown() {
149
+ if (this.disposed)
150
+ return;
151
+ this.disposed = true;
152
+ this.rejectAll(new Error('stt-local host shut down'));
153
+ this.killChild();
154
+ }
155
+ }
156
+ //# sourceMappingURL=host-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-client.js","sourceRoot":"","sources":["../src/host-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The tiny message protocol spoken between the parent (host-client) and the
3
+ * forked sherpa sidecar, plus the pure per-message handler the sidecar runs.
4
+ * Kept free of `node:child_process` so BOTH sides can import it (the parent for
5
+ * the types, the child for the handler) without dragging fork plumbing into
6
+ * either bundle.
7
+ *
8
+ * Wire shape (over the fork IPC channel, `serialization: 'advanced'` so the
9
+ * `Float32Array` of samples round-trips intact):
10
+ * parent → child: TranscribeRequest (carries the decoded 16 kHz mono PCM)
11
+ * child → parent: HostReply (the transcript text)
12
+ */
13
+ /** One transcription request. Carries the full model config so the sidecar is
14
+ * stateless-per-message except for a recognizer cache keyed by `modelKey`. The
15
+ * audio is already decoded to Float32 mono @ 16 kHz by the parent — the sidecar
16
+ * only owns the native recognition. */
17
+ export interface TranscribeRequest {
18
+ readonly id: number;
19
+ readonly type: 'transcribe';
20
+ /** Cache key for the loaded recognizer (the absolute encoder path). */
21
+ readonly modelKey: string;
22
+ /** Absolute path to the Whisper encoder `.onnx`. */
23
+ readonly encoder: string;
24
+ /** Absolute path to the Whisper decoder `.onnx`. */
25
+ readonly decoder: string;
26
+ /** Absolute path to `<id>-tokens.txt`. */
27
+ readonly tokens: string;
28
+ /** Inference thread count. */
29
+ readonly numThreads: number;
30
+ /** Compute provider (`cpu`). */
31
+ readonly provider: string;
32
+ /** Best-effort Whisper language tag; empty string ⇒ auto-detect. */
33
+ readonly language: string;
34
+ /** Whisper task: `transcribe` (same-language) or `translate` (→ English). */
35
+ readonly task: string;
36
+ /** Mono PCM in [-1, 1] at `sampleRate`; structured-cloned intact over IPC. */
37
+ readonly samples: Float32Array;
38
+ /** Sample rate of `samples` (16000 — the parent resamples before sending). */
39
+ readonly sampleRate: number;
40
+ }
41
+ export type HostRequest = TranscribeRequest;
42
+ export type HostErrorKind =
43
+ /** Recognizer failed to load (bad/absent files, unsupported arch, dlopen). */
44
+ 'init'
45
+ /** Decoding itself threw. */
46
+ | 'runtime';
47
+ export interface TranscribeResult {
48
+ readonly text: string;
49
+ /** Whisper's detected language, when the native result reports one. */
50
+ readonly language?: string;
51
+ }
52
+ export type HostReply = ({
53
+ readonly id: number;
54
+ readonly ok: true;
55
+ } & TranscribeResult) | {
56
+ readonly id: number;
57
+ readonly ok: false;
58
+ readonly error: {
59
+ readonly message: string;
60
+ readonly kind: HostErrorKind;
61
+ };
62
+ };
63
+ /** The subset of the sherpa-onnx-node surface the sidecar uses. Declared here
64
+ * (rather than leaning on the addon's ambient types) so the handler is
65
+ * unit-testable with a fake module and typechecks without the native addon. */
66
+ export interface SherpaOfflineStream {
67
+ acceptWaveform(args: {
68
+ sampleRate: number;
69
+ samples: Float32Array;
70
+ }): void;
71
+ }
72
+ export interface SherpaOfflineRecognizerResult {
73
+ readonly text: string;
74
+ /** Whisper detected-language field on some builds; read defensively. */
75
+ readonly lang?: string;
76
+ }
77
+ export interface SherpaOfflineRecognizer {
78
+ createStream(): SherpaOfflineStream;
79
+ decode(stream: SherpaOfflineStream): void;
80
+ getResult(stream: SherpaOfflineStream): SherpaOfflineRecognizerResult;
81
+ }
82
+ export interface SherpaModule {
83
+ OfflineRecognizer: new (config: unknown) => SherpaOfflineRecognizer;
84
+ }
85
+ /** Lazily load the native sherpa module (so a dlopen failure surfaces as a
86
+ * classified reply, not a boot crash). */
87
+ export type LoadSherpa = () => SherpaModule;
88
+ export type HostMessageHandler = (req: HostRequest) => Promise<HostReply>;
89
+ /**
90
+ * Build the sidecar's message handler. Loads the sherpa module on first use and
91
+ * caches one `OfflineRecognizer` per `modelKey` so repeated calls on the same
92
+ * model reuse the loaded weights. Every failure becomes a typed `ok:false`
93
+ * reply — nothing throws out of `handle`.
94
+ */
95
+ export declare function createMessageHandler(loadSherpa: LoadSherpa): HostMessageHandler;
96
+ //# sourceMappingURL=host-protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-protocol.d.ts","sourceRoot":"","sources":["../src/host-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;wCAGwC;AACxC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oDAAoD;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8BAA8B;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,gCAAgC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,WAAW,GAAG,iBAAiB,CAAC;AAE5C,MAAM,MAAM,aAAa;AACvB,8EAA8E;AAC5E,MAAM;AACR,6BAA6B;GAC3B,SAAS,CAAC;AAEd,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,SAAS,GACjB,CAAC;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG,gBAAgB,CAAC,GAC/D;IACE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;KAAE,CAAC;CAC5E,CAAC;AAEN;;gFAEgF;AAChF,MAAM,WAAW,mBAAmB;IAClC,cAAc,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,YAAY,CAAA;KAAE,GAAG,IAAI,CAAC;CAC3E;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,YAAY,IAAI,mBAAmB,CAAC;IACpC,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC1C,SAAS,CAAC,MAAM,EAAE,mBAAmB,GAAG,6BAA6B,CAAC;CACvE;AAED,MAAM,WAAW,YAAY;IAC3B,iBAAiB,EAAE,KAAK,MAAM,EAAE,OAAO,KAAK,uBAAuB,CAAC;CACrE;AAED;2CAC2C;AAC3C,MAAM,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC;AAE5C,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;AAE1E;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,kBAAkB,CAgD/E"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The tiny message protocol spoken between the parent (host-client) and the
3
+ * forked sherpa sidecar, plus the pure per-message handler the sidecar runs.
4
+ * Kept free of `node:child_process` so BOTH sides can import it (the parent for
5
+ * the types, the child for the handler) without dragging fork plumbing into
6
+ * either bundle.
7
+ *
8
+ * Wire shape (over the fork IPC channel, `serialization: 'advanced'` so the
9
+ * `Float32Array` of samples round-trips intact):
10
+ * parent → child: TranscribeRequest (carries the decoded 16 kHz mono PCM)
11
+ * child → parent: HostReply (the transcript text)
12
+ */
13
+ /**
14
+ * Build the sidecar's message handler. Loads the sherpa module on first use and
15
+ * caches one `OfflineRecognizer` per `modelKey` so repeated calls on the same
16
+ * model reuse the loaded weights. Every failure becomes a typed `ok:false`
17
+ * reply — nothing throws out of `handle`.
18
+ */
19
+ export function createMessageHandler(loadSherpa) {
20
+ const cache = new Map();
21
+ let mod = null;
22
+ return async function handle(req) {
23
+ let recognizer = cache.get(req.modelKey);
24
+ if (!recognizer) {
25
+ try {
26
+ mod ??= loadSherpa();
27
+ recognizer = new mod.OfflineRecognizer({
28
+ featConfig: { sampleRate: 16_000, featureDim: 80 },
29
+ modelConfig: {
30
+ whisper: {
31
+ encoder: req.encoder,
32
+ decoder: req.decoder,
33
+ // `language`/`task` are present in the OfflineWhisperModelConfig
34
+ // types but exercised by no upstream example — pass them through
35
+ // best-effort (empty language ⇒ Whisper auto-detects anyway).
36
+ language: req.language,
37
+ task: req.task,
38
+ },
39
+ tokens: req.tokens,
40
+ numThreads: req.numThreads,
41
+ provider: req.provider,
42
+ debug: false,
43
+ },
44
+ });
45
+ cache.set(req.modelKey, recognizer);
46
+ }
47
+ catch (err) {
48
+ return { id: req.id, ok: false, error: { message: errMsg(err), kind: 'init' } };
49
+ }
50
+ }
51
+ try {
52
+ const stream = recognizer.createStream();
53
+ stream.acceptWaveform({ sampleRate: req.sampleRate, samples: req.samples });
54
+ recognizer.decode(stream);
55
+ const result = recognizer.getResult(stream);
56
+ const text = typeof result?.text === 'string' ? result.text : '';
57
+ const reply = typeof result?.lang === 'string' && result.lang
58
+ ? { id: req.id, ok: true, text, language: result.lang }
59
+ : { id: req.id, ok: true, text };
60
+ return reply;
61
+ }
62
+ catch (err) {
63
+ return { id: req.id, ok: false, error: { message: errMsg(err), kind: 'runtime' } };
64
+ }
65
+ };
66
+ }
67
+ function errMsg(err) {
68
+ return err instanceof Error ? err.message : String(err);
69
+ }
70
+ //# sourceMappingURL=host-protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-protocol.js","sourceRoot":"","sources":["../src/host-protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { type Plugin } from '@moxxy/sdk';
2
+ import { type LocalWhisperOptions } from './local-stt.js';
3
+ export { LocalWhisperTranscriber, createLocalWhisperTranscriber, shutdownLocalStt, LOCAL_WHISPER_TRANSCRIBER_NAME, type LocalWhisperOptions, type WhisperModelPaths, } from './local-stt.js';
4
+ export { MODEL_CATALOG, DEFAULT_MODEL_ID, modelIds, findModel, requireModel, type WhisperModelEntry, type WhisperModelId, } from './models.js';
5
+ export { pcm16ToFloat32, downmixToMono, resampleLinear, parseWav, isRiffWave, TARGET_SAMPLE_RATE, type ParsedWav, } from './audio.js';
6
+ export { decodeToMono16k, type DecodeOptions } from './decode.js';
7
+ export { decodeViaFfmpeg, missingFfmpegError, __resetFfmpegProbeForTest, } from './ffmpeg.js';
8
+ export { HostClient, type HostClientLike, type ForkLike, type ChildHandle } from './host-client.js';
9
+ export { sherpaPlatformPackage, resolveSherpaLibDir, sherpaEnv, libraryPathVar, } from './platform.js';
10
+ export interface BuildLocalSttPluginOptions {
11
+ /** Build-time defaults / test seams for the transcriber (injected
12
+ * `ensureModelImpl`, `hostFactory`, `fetchImpl`, `modelsDir`, `log`, …). */
13
+ readonly defaults?: LocalWhisperOptions;
14
+ }
15
+ /**
16
+ * Build the @moxxy/plugin-stt-local plugin. Registers exactly one transcriber,
17
+ * `local-whisper`, backed by sherpa-onnx Whisper models running in a forked
18
+ * sidecar. Side-effect free at load: no model is downloaded and no process is
19
+ * spawned until the first `transcribe`. Per-activation config
20
+ * (`session.transcribers.setActive('local-whisper', { model, language,
21
+ * numThreads })`) overrides the build-time defaults.
22
+ *
23
+ * Like the OpenAI STT sibling, the plugin does NOT set itself active —
24
+ * registering it is side-effect free (no auto-adopt in TranscriberRegistry).
25
+ * The host/user activates it explicitly, so a local-only setup never
26
+ * surprises anyone with a network call (there isn't one here) and a mixed
27
+ * setup keeps whatever STT backend was chosen.
28
+ */
29
+ export declare function buildLocalSttPlugin(opts?: BuildLocalSttPluginOptions): Plugin;
30
+ declare const _default: Plugin;
31
+ export default _default;
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAE1E,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,uBAAuB,EACvB,6BAA6B,EAC7B,gBAAgB,EAChB,8BAA8B,EAC9B,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,GACvB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,cAAc,EACd,aAAa,EACb,cAAc,EACd,QAAQ,EACR,UAAU,EACV,kBAAkB,EAClB,KAAK,SAAS,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,KAAK,cAAc,EAAE,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpG,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,SAAS,EACT,cAAc,GACf,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,0BAA0B;IACzC;iFAC6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CACzC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,0BAA+B,GAAG,MAAM,CAgBjF;;AAsBD,wBAAqC"}
package/dist/index.js ADDED
@@ -0,0 +1,61 @@
1
+ import { definePlugin, defineTranscriber } from '@moxxy/sdk';
2
+ import { createLocalWhisperTranscriber, LOCAL_WHISPER_TRANSCRIBER_NAME, shutdownLocalStt, } from './local-stt.js';
3
+ export { LocalWhisperTranscriber, createLocalWhisperTranscriber, shutdownLocalStt, LOCAL_WHISPER_TRANSCRIBER_NAME, } from './local-stt.js';
4
+ export { MODEL_CATALOG, DEFAULT_MODEL_ID, modelIds, findModel, requireModel, } from './models.js';
5
+ export { pcm16ToFloat32, downmixToMono, resampleLinear, parseWav, isRiffWave, TARGET_SAMPLE_RATE, } from './audio.js';
6
+ export { decodeToMono16k } from './decode.js';
7
+ export { decodeViaFfmpeg, missingFfmpegError, __resetFfmpegProbeForTest, } from './ffmpeg.js';
8
+ export { HostClient } from './host-client.js';
9
+ export { sherpaPlatformPackage, resolveSherpaLibDir, sherpaEnv, libraryPathVar, } from './platform.js';
10
+ /**
11
+ * Build the @moxxy/plugin-stt-local plugin. Registers exactly one transcriber,
12
+ * `local-whisper`, backed by sherpa-onnx Whisper models running in a forked
13
+ * sidecar. Side-effect free at load: no model is downloaded and no process is
14
+ * spawned until the first `transcribe`. Per-activation config
15
+ * (`session.transcribers.setActive('local-whisper', { model, language,
16
+ * numThreads })`) overrides the build-time defaults.
17
+ *
18
+ * Like the OpenAI STT sibling, the plugin does NOT set itself active —
19
+ * registering it is side-effect free (no auto-adopt in TranscriberRegistry).
20
+ * The host/user activates it explicitly, so a local-only setup never
21
+ * surprises anyone with a network call (there isn't one here) and a mixed
22
+ * setup keeps whatever STT backend was chosen.
23
+ */
24
+ export function buildLocalSttPlugin(opts = {}) {
25
+ const defaults = opts.defaults ?? {};
26
+ return definePlugin({
27
+ name: '@moxxy/plugin-stt-local',
28
+ version: '0.0.0',
29
+ transcribers: [
30
+ defineTranscriber({
31
+ name: LOCAL_WHISPER_TRANSCRIBER_NAME,
32
+ displayName: 'Local Whisper (offline, multilingual)',
33
+ createClient: (config) => createLocalWhisperTranscriber({ ...defaults, ...configToOptions(config) }),
34
+ }),
35
+ ],
36
+ // Kill every spawned sidecar when the session/runner shuts down.
37
+ hooks: { onShutdown: () => shutdownLocalStt() },
38
+ });
39
+ }
40
+ /** Narrow the untrusted per-activation config into typed options — only the
41
+ * known string `model` / `language` / `provider` and a positive-integer
42
+ * `numThreads` are honored so a malformed config can't break `createClient` or
43
+ * route to a bad model (unknown ids are caught by `requireModel`). */
44
+ function configToOptions(config) {
45
+ const out = {};
46
+ if (typeof config.model === 'string' && config.model)
47
+ out.model = config.model;
48
+ if (typeof config.language === 'string')
49
+ out.language = config.language;
50
+ if (typeof config.provider === 'string' && config.provider)
51
+ out.provider = config.provider;
52
+ if (typeof config.numThreads === 'number' &&
53
+ Number.isInteger(config.numThreads) &&
54
+ config.numThreads > 0) {
55
+ out.numThreads = config.numThreads;
56
+ }
57
+ return out;
58
+ }
59
+ // Discovery entry: `createPluginLoader` requires a default Plugin export.
60
+ export default buildLocalSttPlugin();
61
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAe,MAAM,YAAY,CAAC;AAE1E,OAAO,EACL,6BAA6B,EAC7B,8BAA8B,EAC9B,gBAAgB,GAEjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,uBAAuB,EACvB,6BAA6B,EAC7B,gBAAgB,EAChB,8BAA8B,GAG/B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,SAAS,EACT,YAAY,GAGb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,cAAc,EACd,aAAa,EACb,cAAc,EACd,QAAQ,EACR,UAAU,EACV,kBAAkB,GAEnB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAsB,MAAM,aAAa,CAAC;AAClE,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAwD,MAAM,kBAAkB,CAAC;AACpG,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,SAAS,EACT,cAAc,GACf,MAAM,eAAe,CAAC;AAQvB;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAmC,EAAE;IACvE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;IACrC,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,OAAO;QAChB,YAAY,EAAE;YACZ,iBAAiB,CAAC;gBAChB,IAAI,EAAE,8BAA8B;gBACpC,WAAW,EAAE,uCAAuC;gBACpD,YAAY,EAAE,CAAC,MAA+B,EAAE,EAAE,CAChD,6BAA6B,CAAC,EAAE,GAAG,QAAQ,EAAE,GAAG,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;aAC7E,CAAC;SACH;QACD,iEAAiE;QACjE,KAAK,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,gBAAgB,EAAE,EAAE;KAChD,CAAC,CAAC;AACL,CAAC;AAED;;;uEAGuE;AACvE,SAAS,eAAe,CAAC,MAA+B;IACtD,MAAM,GAAG,GAA4E,EAAE,CAAC;IACxF,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK;QAAE,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC/E,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ;QAAE,GAAG,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IACxE,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ;QAAE,GAAG,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC3F,IACE,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ;QACrC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC;QACnC,MAAM,CAAC,UAAU,GAAG,CAAC,EACrB,CAAC;QACD,GAAG,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;IACrC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,0EAA0E;AAC1E,eAAe,mBAAmB,EAAE,CAAC"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The `local-whisper` Transcriber: fully local, on-device speech-to-text.
3
+ *
4
+ * On the first transcription for a model it downloads + verifies that model's
5
+ * Whisper export (once, via @moxxy/model-fetch), then decodes the inbound audio
6
+ * to Float32 mono @ 16 kHz IN-PROCESS (raw PCM / WAV) or via ffmpeg (compressed
7
+ * containers) and hands the samples to the sherpa sidecar ({@link HostClient}).
8
+ * No key, no network at transcription time.
9
+ *
10
+ * Every collaborator is injectable (`ensureModelImpl`, `hostFactory`, `log`,
11
+ * `modelsDir`, `fetchImpl`, `spawnImpl`) so the whole flow is unit-testable
12
+ * without the native addon, a real download, or ffmpeg.
13
+ */
14
+ import type { spawn } from 'node:child_process';
15
+ import { type TranscribeOptions, type Transcriber, type TranscriptionResult } from '@moxxy/sdk';
16
+ import { ensureModel, type FetchLike } from '@moxxy/model-fetch';
17
+ import { type HostClientLike } from './host-client.js';
18
+ /** The single registered transcriber name. */
19
+ export declare const LOCAL_WHISPER_TRANSCRIBER_NAME = "local-whisper";
20
+ /** Resolved on-disk paths sherpa needs for one model. */
21
+ export interface WhisperModelPaths {
22
+ readonly encoder: string;
23
+ readonly decoder: string;
24
+ readonly tokens: string;
25
+ }
26
+ export interface LocalWhisperOptions {
27
+ /** Model id: `tiny` | `base` | `small`. Default `base` (`small` for Polish). */
28
+ readonly model?: string;
29
+ /** Default Whisper language tag (BCP-47-ish, e.g. `en`, `pl`). Per-call
30
+ * `TranscribeOptions.language` overrides it; omit to let Whisper auto-detect. */
31
+ readonly language?: string;
32
+ /** Inference threads. Default 2. */
33
+ readonly numThreads?: number;
34
+ /** Root for downloaded models. Default `~/.moxxy/models/stt` (MOXXY_HOME-aware). */
35
+ readonly modelsDir?: string;
36
+ /** sherpa compute provider. Default `cpu`. */
37
+ readonly provider?: string;
38
+ /** Absolute path to the built sidecar entry. Default: `sidecar.js` beside
39
+ * this module (i.e. `dist/sidecar.js` in a published build). */
40
+ readonly sidecarPath?: string;
41
+ /** Injected model-ensure (tests). Defaults to @moxxy/model-fetch `ensureModel`. */
42
+ readonly ensureModelImpl?: typeof ensureModel;
43
+ /** Injected `fetch`, threaded into `ensureModel` (tests). */
44
+ readonly fetchImpl?: FetchLike;
45
+ /** Injected sidecar host factory (tests). Defaults to a real {@link HostClient}. */
46
+ readonly hostFactory?: () => HostClientLike;
47
+ /** Injected `spawn` for the ffmpeg decode path (tests). */
48
+ readonly spawnImpl?: typeof spawn;
49
+ /** Diagnostic log sink (download progress etc.). Defaults to stderr. */
50
+ readonly log?: (msg: string) => void;
51
+ }
52
+ /** Shut down every live local transcriber (kills their sidecars). */
53
+ export declare function shutdownLocalStt(): void;
54
+ export declare class LocalWhisperTranscriber implements Transcriber {
55
+ readonly name = "local-whisper";
56
+ private readonly model;
57
+ private readonly defaultLanguage;
58
+ private readonly numThreads;
59
+ private readonly modelsDir;
60
+ private readonly provider;
61
+ private readonly sidecarPath;
62
+ private readonly ensureModelImpl;
63
+ private readonly fetchImpl;
64
+ private readonly hostFactory;
65
+ private readonly spawnImpl;
66
+ private readonly log;
67
+ private host;
68
+ /** In-flight model download promise — de-dupes concurrent first-use. */
69
+ private ensuring;
70
+ constructor(opts?: LocalWhisperOptions);
71
+ transcribe(audio: Uint8Array | ArrayBuffer, opts?: TranscribeOptions): Promise<TranscriptionResult>;
72
+ /** Kill this transcriber's sidecar (if any) and drop registration. */
73
+ shutdown(): void;
74
+ private getHost;
75
+ /** Resolve on-disk paths for the model, downloading + extracting it once. */
76
+ private ensureModelFiles;
77
+ /** A progress callback that logs a single start line then ~every-10% ticks,
78
+ * and an extraction line — but stays silent when the model is already
79
+ * present (ensureModel emits only `done`). */
80
+ private makeProgressLogger;
81
+ /** Build a real sidecar-backed host, resolving the platform loader path.
82
+ * Fails clearly when no sherpa binary exists for this platform/arch. */
83
+ private defaultHost;
84
+ }
85
+ export declare function createLocalWhisperTranscriber(opts?: LocalWhisperOptions): LocalWhisperTranscriber;
86
+ //# sourceMappingURL=local-stt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-stt.d.ts","sourceRoot":"","sources":["../src/local-stt.ts"],"names":[],"mappings":"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"}