@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.
- package/LICENSE +21 -0
- package/dist/audio.d.ts +55 -0
- package/dist/audio.d.ts.map +1 -0
- package/dist/audio.js +197 -0
- package/dist/audio.js.map +1 -0
- package/dist/decode.d.ts +25 -0
- package/dist/decode.d.ts.map +1 -0
- package/dist/decode.js +50 -0
- package/dist/decode.js.map +1 -0
- package/dist/ffmpeg.d.ts +26 -0
- package/dist/ffmpeg.d.ts.map +1 -0
- package/dist/ffmpeg.js +207 -0
- package/dist/ffmpeg.js.map +1 -0
- package/dist/host-client.d.ts +70 -0
- package/dist/host-client.d.ts.map +1 -0
- package/dist/host-client.js +156 -0
- package/dist/host-client.js.map +1 -0
- package/dist/host-protocol.d.ts +96 -0
- package/dist/host-protocol.d.ts.map +1 -0
- package/dist/host-protocol.js +70 -0
- package/dist/host-protocol.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/local-stt.d.ts +86 -0
- package/dist/local-stt.d.ts.map +1 -0
- package/dist/local-stt.js +201 -0
- package/dist/local-stt.js.map +1 -0
- package/dist/models.d.ts +47 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +61 -0
- package/dist/models.js.map +1 -0
- package/dist/platform.d.ts +37 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +96 -0
- package/dist/platform.js.map +1 -0
- package/dist/sidecar.d.ts +18 -0
- package/dist/sidecar.d.ts.map +1 -0
- package/dist/sidecar.js +86 -0
- package/dist/sidecar.js.map +1 -0
- package/package.json +67 -0
- package/src/audio.test.ts +213 -0
- package/src/audio.ts +220 -0
- package/src/decode.test.ts +126 -0
- package/src/decode.ts +74 -0
- package/src/ffmpeg.test.ts +142 -0
- package/src/ffmpeg.ts +215 -0
- package/src/host-client.test.ts +208 -0
- package/src/host-client.ts +200 -0
- package/src/host-protocol.test.ts +171 -0
- package/src/host-protocol.ts +152 -0
- package/src/index.ts +108 -0
- package/src/live.test.ts +80 -0
- package/src/local-stt.test.ts +224 -0
- package/src/local-stt.ts +273 -0
- package/src/models.test.ts +58 -0
- package/src/models.ts +111 -0
- package/src/platform.test.ts +77 -0
- package/src/platform.ts +109 -0
- package/src/sidecar.ts +97 -0
|
@@ -0,0 +1,201 @@
|
|
|
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 path from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { MoxxyError, } from '@moxxy/sdk';
|
|
17
|
+
import { moxxyPath } from '@moxxy/sdk/server';
|
|
18
|
+
import { ensureModel, } from '@moxxy/model-fetch';
|
|
19
|
+
import { TARGET_SAMPLE_RATE } from './audio.js';
|
|
20
|
+
import { decodeToMono16k } from './decode.js';
|
|
21
|
+
import { HostClient } from './host-client.js';
|
|
22
|
+
import { DEFAULT_MODEL_ID, requireModel } from './models.js';
|
|
23
|
+
import { resolveSherpaLibDir, sherpaEnv, sherpaPlatformPackage } from './platform.js';
|
|
24
|
+
/** The single registered transcriber name. */
|
|
25
|
+
export const LOCAL_WHISPER_TRANSCRIBER_NAME = 'local-whisper';
|
|
26
|
+
const DEFAULT_NUM_THREADS = 2;
|
|
27
|
+
/** Process-wide set of live transcribers so the plugin's `onShutdown` hook can
|
|
28
|
+
* kill every sidecar it spawned. */
|
|
29
|
+
const ACTIVE_STT = new Set();
|
|
30
|
+
/** Shut down every live local transcriber (kills their sidecars). */
|
|
31
|
+
export function shutdownLocalStt() {
|
|
32
|
+
for (const t of ACTIVE_STT)
|
|
33
|
+
t.shutdown();
|
|
34
|
+
ACTIVE_STT.clear();
|
|
35
|
+
}
|
|
36
|
+
export class LocalWhisperTranscriber {
|
|
37
|
+
name = LOCAL_WHISPER_TRANSCRIBER_NAME;
|
|
38
|
+
model;
|
|
39
|
+
defaultLanguage;
|
|
40
|
+
numThreads;
|
|
41
|
+
modelsDir;
|
|
42
|
+
provider;
|
|
43
|
+
sidecarPath;
|
|
44
|
+
ensureModelImpl;
|
|
45
|
+
fetchImpl;
|
|
46
|
+
hostFactory;
|
|
47
|
+
spawnImpl;
|
|
48
|
+
log;
|
|
49
|
+
host = null;
|
|
50
|
+
/** In-flight model download promise — de-dupes concurrent first-use. */
|
|
51
|
+
ensuring = null;
|
|
52
|
+
constructor(opts = {}) {
|
|
53
|
+
// Validate the configured model up front so a bad config fails clearly at
|
|
54
|
+
// construction rather than on the first (possibly channel-triggered) call.
|
|
55
|
+
this.model = requireModel(opts.model ?? DEFAULT_MODEL_ID, 'model');
|
|
56
|
+
this.defaultLanguage = typeof opts.language === 'string' ? opts.language.trim() : '';
|
|
57
|
+
this.numThreads =
|
|
58
|
+
opts.numThreads && Number.isInteger(opts.numThreads) && opts.numThreads > 0
|
|
59
|
+
? opts.numThreads
|
|
60
|
+
: DEFAULT_NUM_THREADS;
|
|
61
|
+
this.modelsDir = opts.modelsDir ?? moxxyPath('models', 'stt');
|
|
62
|
+
this.provider = opts.provider ?? 'cpu';
|
|
63
|
+
this.sidecarPath = opts.sidecarPath;
|
|
64
|
+
this.ensureModelImpl = opts.ensureModelImpl ?? ensureModel;
|
|
65
|
+
this.fetchImpl = opts.fetchImpl;
|
|
66
|
+
this.hostFactory = opts.hostFactory ?? (() => this.defaultHost());
|
|
67
|
+
this.spawnImpl = opts.spawnImpl;
|
|
68
|
+
this.log = opts.log ?? ((msg) => process.stderr.write(`${msg}\n`));
|
|
69
|
+
ACTIVE_STT.add(this);
|
|
70
|
+
}
|
|
71
|
+
async transcribe(audio, opts = {}) {
|
|
72
|
+
opts.signal?.throwIfAborted();
|
|
73
|
+
// Decode to the canonical Float32 mono @ 16 kHz sherpa wants. This is where
|
|
74
|
+
// raw PCM / WAV are handled in-process and compressed audio goes to ffmpeg.
|
|
75
|
+
const samples = await decodeToMono16k(audio, opts.mimeType, {
|
|
76
|
+
...(this.spawnImpl ? { spawnImpl: this.spawnImpl } : {}),
|
|
77
|
+
});
|
|
78
|
+
opts.signal?.throwIfAborted();
|
|
79
|
+
if (samples.length === 0) {
|
|
80
|
+
return { text: '' };
|
|
81
|
+
}
|
|
82
|
+
const paths = await this.ensureModelFiles(opts.signal);
|
|
83
|
+
opts.signal?.throwIfAborted();
|
|
84
|
+
const language = ((opts.language ?? this.defaultLanguage) || '').trim();
|
|
85
|
+
const host = this.getHost();
|
|
86
|
+
const result = await host.transcribe({
|
|
87
|
+
modelKey: paths.encoder,
|
|
88
|
+
encoder: paths.encoder,
|
|
89
|
+
decoder: paths.decoder,
|
|
90
|
+
tokens: paths.tokens,
|
|
91
|
+
numThreads: this.numThreads,
|
|
92
|
+
provider: this.provider,
|
|
93
|
+
language,
|
|
94
|
+
task: 'transcribe',
|
|
95
|
+
samples,
|
|
96
|
+
sampleRate: TARGET_SAMPLE_RATE,
|
|
97
|
+
});
|
|
98
|
+
const durationSec = samples.length / TARGET_SAMPLE_RATE;
|
|
99
|
+
// Prefer Whisper's detected language; else the caller/default hint (when
|
|
100
|
+
// set) so downstream still gets a BCP-47 tag.
|
|
101
|
+
const reported = result.language ?? (language || undefined);
|
|
102
|
+
const out = { text: result.text.trim(), durationSec };
|
|
103
|
+
if (reported)
|
|
104
|
+
out.language = reported;
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
/** Kill this transcriber's sidecar (if any) and drop registration. */
|
|
108
|
+
shutdown() {
|
|
109
|
+
this.host?.shutdown();
|
|
110
|
+
this.host = null;
|
|
111
|
+
ACTIVE_STT.delete(this);
|
|
112
|
+
}
|
|
113
|
+
getHost() {
|
|
114
|
+
this.host ??= this.hostFactory();
|
|
115
|
+
return this.host;
|
|
116
|
+
}
|
|
117
|
+
/** Resolve on-disk paths for the model, downloading + extracting it once. */
|
|
118
|
+
async ensureModelFiles(signal) {
|
|
119
|
+
const dir = path.join(this.modelsDir, this.model.id);
|
|
120
|
+
let inflight = this.ensuring;
|
|
121
|
+
if (!inflight) {
|
|
122
|
+
inflight = this.ensureModelImpl({
|
|
123
|
+
url: this.model.url,
|
|
124
|
+
sha256: this.model.sha256,
|
|
125
|
+
dir,
|
|
126
|
+
...(this.fetchImpl ? { fetchImpl: this.fetchImpl } : {}),
|
|
127
|
+
...(signal ? { signal } : {}),
|
|
128
|
+
onProgress: this.makeProgressLogger(),
|
|
129
|
+
});
|
|
130
|
+
this.ensuring = inflight;
|
|
131
|
+
// On failure, forget the promise so a later call retries the download.
|
|
132
|
+
inflight.catch(() => {
|
|
133
|
+
if (this.ensuring === inflight)
|
|
134
|
+
this.ensuring = null;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
await inflight;
|
|
138
|
+
const root = path.join(dir, this.model.archiveRootDir);
|
|
139
|
+
return {
|
|
140
|
+
encoder: path.join(root, this.model.encoderFile),
|
|
141
|
+
decoder: path.join(root, this.model.decoderFile),
|
|
142
|
+
tokens: path.join(root, this.model.tokensFile),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** A progress callback that logs a single start line then ~every-10% ticks,
|
|
146
|
+
* and an extraction line — but stays silent when the model is already
|
|
147
|
+
* present (ensureModel emits only `done`). */
|
|
148
|
+
makeProgressLogger() {
|
|
149
|
+
let started = false;
|
|
150
|
+
let extracting = false;
|
|
151
|
+
let lastPct = -1;
|
|
152
|
+
return (p) => {
|
|
153
|
+
if (p.phase === 'downloading') {
|
|
154
|
+
if (!started) {
|
|
155
|
+
started = true;
|
|
156
|
+
this.log(`stt-local: first use — downloading Whisper ${this.model.id} (~${this.model.approxMb} MB) to ${this.modelsDir}, one-time`);
|
|
157
|
+
}
|
|
158
|
+
if (p.totalBytes > 0) {
|
|
159
|
+
const pct = Math.floor((p.receivedBytes / p.totalBytes) * 100);
|
|
160
|
+
if (pct >= lastPct + 10) {
|
|
161
|
+
lastPct = pct - (pct % 10);
|
|
162
|
+
this.log(`stt-local: downloading ${this.model.id} … ${lastPct}%`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else if (p.phase === 'extracting' && !extracting) {
|
|
167
|
+
extracting = true;
|
|
168
|
+
this.log(`stt-local: extracting ${this.model.id} …`);
|
|
169
|
+
}
|
|
170
|
+
else if (p.phase === 'done' && started) {
|
|
171
|
+
this.log(`stt-local: ${this.model.id} ready.`);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/** Build a real sidecar-backed host, resolving the platform loader path.
|
|
176
|
+
* Fails clearly when no sherpa binary exists for this platform/arch. */
|
|
177
|
+
defaultHost() {
|
|
178
|
+
const libDir = resolveSherpaLibDir();
|
|
179
|
+
if (!libDir) {
|
|
180
|
+
const pkg = sherpaPlatformPackage();
|
|
181
|
+
throw new MoxxyError({
|
|
182
|
+
code: 'PLUGIN_LOAD_FAILED',
|
|
183
|
+
message: pkg
|
|
184
|
+
? `Local Whisper could not load the sherpa-onnx binary (${pkg}). Reinstall @moxxy/plugin-stt-local so its platform dependency is fetched.`
|
|
185
|
+
: `Local Whisper has no sherpa-onnx binary for ${process.platform}-${process.arch}.`,
|
|
186
|
+
hint: 'Use the OpenAI Whisper backend instead, or run on a supported platform (macOS/Linux/Windows x64, macOS/Linux arm64).',
|
|
187
|
+
context: { platform: process.platform, arch: process.arch },
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const hostPath = this.sidecarPath ?? path.join(path.dirname(fileURLToPath(import.meta.url)), 'sidecar.js');
|
|
191
|
+
return new HostClient({
|
|
192
|
+
hostPath,
|
|
193
|
+
env: { ...process.env, ...sherpaEnv(libDir) },
|
|
194
|
+
log: this.log,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
export function createLocalWhisperTranscriber(opts = {}) {
|
|
199
|
+
return new LocalWhisperTranscriber(opts);
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=local-stt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-stt.js","sourceRoot":"","sources":["../src/local-stt.ts"],"names":[],"mappings":"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"}
|
package/dist/models.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pinned Whisper model catalog.
|
|
3
|
+
*
|
|
4
|
+
* Every model is a multilingual sherpa-onnx Whisper export published as a
|
|
5
|
+
* `.tar.bz2` under the project's `asr-models` GitHub release. Each `sha256` is
|
|
6
|
+
* copied VERBATIM from that release's `checksum.txt` (re-verified 2026-07-05) —
|
|
7
|
+
* so a first-use download is content-addressed and tamper-evident, unlike an
|
|
8
|
+
* unverified blob fetch. The archive extracts to a single top directory
|
|
9
|
+
* (`archiveRootDir`) holding `<id>-encoder.onnx`, `<id>-decoder.onnx` (+ int8
|
|
10
|
+
* variants we don't use) and `<id>-tokens.txt`.
|
|
11
|
+
*
|
|
12
|
+
* All three are the MULTILINGUAL models (not the `.en` English-only exports),
|
|
13
|
+
* so Polish (and any other Whisper language) works — `small` is the accuracy
|
|
14
|
+
* sweet spot for Polish, `base` the balanced default, `tiny` the fastest.
|
|
15
|
+
*/
|
|
16
|
+
export type WhisperModelId = 'tiny' | 'base' | 'small';
|
|
17
|
+
export interface WhisperModelEntry {
|
|
18
|
+
/** Stable model id (also the `model` config value). */
|
|
19
|
+
readonly id: WhisperModelId;
|
|
20
|
+
/** Human label for UI surfaces / the first-use notice. */
|
|
21
|
+
readonly label: string;
|
|
22
|
+
/** Pinned archive URL (sherpa-onnx GitHub `asr-models` release). */
|
|
23
|
+
readonly url: string;
|
|
24
|
+
/** Pinned hex sha256 of the archive (from the release checksum.txt). */
|
|
25
|
+
readonly sha256: string;
|
|
26
|
+
/** Top directory the archive extracts to (`sherpa-onnx-whisper-<id>`). */
|
|
27
|
+
readonly archiveRootDir: string;
|
|
28
|
+
/** Encoder ONNX filename inside `archiveRootDir` (`<id>-encoder.onnx`). */
|
|
29
|
+
readonly encoderFile: string;
|
|
30
|
+
/** Decoder ONNX filename inside `archiveRootDir` (`<id>-decoder.onnx`). */
|
|
31
|
+
readonly decoderFile: string;
|
|
32
|
+
/** Tokens filename inside `archiveRootDir` (`<id>-tokens.txt`). */
|
|
33
|
+
readonly tokensFile: string;
|
|
34
|
+
/** Approximate download size in MB (for the one-time first-use notice). */
|
|
35
|
+
readonly approxMb: number;
|
|
36
|
+
}
|
|
37
|
+
export declare const MODEL_CATALOG: readonly WhisperModelEntry[];
|
|
38
|
+
/** Default model when the caller doesn't pick one. `base` balances size vs.
|
|
39
|
+
* accuracy; `small` is recommended for Polish (see the catalog description). */
|
|
40
|
+
export declare const DEFAULT_MODEL_ID: WhisperModelId;
|
|
41
|
+
/** All valid model ids, for error messages and validation. */
|
|
42
|
+
export declare function modelIds(): string[];
|
|
43
|
+
export declare function findModel(id: string): WhisperModelEntry | undefined;
|
|
44
|
+
/** Resolve a model id to its catalog entry, or throw a clear, actionable error
|
|
45
|
+
* listing the valid ids. `field` names where the bad id came from. */
|
|
46
|
+
export declare function requireModel(id: string, field: string): WhisperModelEntry;
|
|
47
|
+
//# sourceMappingURL=models.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEvD,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAC;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAwBD,eAAO,MAAM,aAAa,EAAE,SAAS,iBAAiB,EAmBrD,CAAC;AAEF;iFACiF;AACjF,eAAO,MAAM,gBAAgB,EAAE,cAAuB,CAAC;AAEvD,8DAA8D;AAC9D,wBAAgB,QAAQ,IAAI,MAAM,EAAE,CAEnC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAEnE;AAED;uEACuE;AACvE,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAWzE"}
|
package/dist/models.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pinned Whisper model catalog.
|
|
3
|
+
*
|
|
4
|
+
* Every model is a multilingual sherpa-onnx Whisper export published as a
|
|
5
|
+
* `.tar.bz2` under the project's `asr-models` GitHub release. Each `sha256` is
|
|
6
|
+
* copied VERBATIM from that release's `checksum.txt` (re-verified 2026-07-05) —
|
|
7
|
+
* so a first-use download is content-addressed and tamper-evident, unlike an
|
|
8
|
+
* unverified blob fetch. The archive extracts to a single top directory
|
|
9
|
+
* (`archiveRootDir`) holding `<id>-encoder.onnx`, `<id>-decoder.onnx` (+ int8
|
|
10
|
+
* variants we don't use) and `<id>-tokens.txt`.
|
|
11
|
+
*
|
|
12
|
+
* All three are the MULTILINGUAL models (not the `.en` English-only exports),
|
|
13
|
+
* so Polish (and any other Whisper language) works — `small` is the accuracy
|
|
14
|
+
* sweet spot for Polish, `base` the balanced default, `tiny` the fastest.
|
|
15
|
+
*/
|
|
16
|
+
import { MoxxyError } from '@moxxy/sdk';
|
|
17
|
+
const RELEASE_BASE = 'https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models';
|
|
18
|
+
function whisperModel(id, label, sha256, approxMb) {
|
|
19
|
+
const archiveRootDir = `sherpa-onnx-whisper-${id}`;
|
|
20
|
+
return {
|
|
21
|
+
id,
|
|
22
|
+
label,
|
|
23
|
+
url: `${RELEASE_BASE}/${archiveRootDir}.tar.bz2`,
|
|
24
|
+
sha256,
|
|
25
|
+
archiveRootDir,
|
|
26
|
+
encoderFile: `${id}-encoder.onnx`,
|
|
27
|
+
decoderFile: `${id}-decoder.onnx`,
|
|
28
|
+
tokensFile: `${id}-tokens.txt`,
|
|
29
|
+
approxMb,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export const MODEL_CATALOG = [
|
|
33
|
+
whisperModel('tiny', 'Whisper tiny (multilingual — fastest, lowest accuracy)', 'c46116994e539aa165266d96b325252728429c12535eb9d8b6a2b10f129e66b1', 111),
|
|
34
|
+
whisperModel('base', 'Whisper base (multilingual — balanced; default)', '911b2083efd7c0dca2ac3b358b75222660dc09fb716d64fbfc417ba6c99ff3de', 198),
|
|
35
|
+
whisperModel('small', 'Whisper small (multilingual — best accuracy; recommended for Polish)', '486a46afbb7ba798507190ffe02fea2dd726049af212e774537efac6afb210a6', 610),
|
|
36
|
+
];
|
|
37
|
+
/** Default model when the caller doesn't pick one. `base` balances size vs.
|
|
38
|
+
* accuracy; `small` is recommended for Polish (see the catalog description). */
|
|
39
|
+
export const DEFAULT_MODEL_ID = 'base';
|
|
40
|
+
/** All valid model ids, for error messages and validation. */
|
|
41
|
+
export function modelIds() {
|
|
42
|
+
return MODEL_CATALOG.map((m) => m.id);
|
|
43
|
+
}
|
|
44
|
+
export function findModel(id) {
|
|
45
|
+
return MODEL_CATALOG.find((m) => m.id === id);
|
|
46
|
+
}
|
|
47
|
+
/** Resolve a model id to its catalog entry, or throw a clear, actionable error
|
|
48
|
+
* listing the valid ids. `field` names where the bad id came from. */
|
|
49
|
+
export function requireModel(id, field) {
|
|
50
|
+
const entry = findModel(id);
|
|
51
|
+
if (!entry) {
|
|
52
|
+
throw new MoxxyError({
|
|
53
|
+
code: 'CONFIG_INVALID',
|
|
54
|
+
message: `Unknown local Whisper model ${JSON.stringify(id)} (${field}).`,
|
|
55
|
+
hint: `Valid models: ${modelIds().join(', ')}.`,
|
|
56
|
+
context: { field, model: id },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return entry;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAyBxC,MAAM,YAAY,GAAG,oEAAoE,CAAC;AAE1F,SAAS,YAAY,CACnB,EAAkB,EAClB,KAAa,EACb,MAAc,EACd,QAAgB;IAEhB,MAAM,cAAc,GAAG,uBAAuB,EAAE,EAAE,CAAC;IACnD,OAAO;QACL,EAAE;QACF,KAAK;QACL,GAAG,EAAE,GAAG,YAAY,IAAI,cAAc,UAAU;QAChD,MAAM;QACN,cAAc;QACd,WAAW,EAAE,GAAG,EAAE,eAAe;QACjC,WAAW,EAAE,GAAG,EAAE,eAAe;QACjC,UAAU,EAAE,GAAG,EAAE,aAAa;QAC9B,QAAQ;KACT,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAiC;IACzD,YAAY,CACV,MAAM,EACN,wDAAwD,EACxD,kEAAkE,EAClE,GAAG,CACJ;IACD,YAAY,CACV,MAAM,EACN,iDAAiD,EACjD,kEAAkE,EAClE,GAAG,CACJ;IACD,YAAY,CACV,OAAO,EACP,sEAAsE,EACtE,kEAAkE,EAClE,GAAG,CACJ;CACF,CAAC;AAEF;iFACiF;AACjF,MAAM,CAAC,MAAM,gBAAgB,GAAmB,MAAM,CAAC;AAEvD,8DAA8D;AAC9D,MAAM,UAAU,QAAQ;IACtB,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,EAAU;IAClC,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAChD,CAAC;AAED;uEACuE;AACvE,MAAM,UAAU,YAAY,CAAC,EAAU,EAAE,KAAa;IACpD,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;IAC5B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,UAAU,CAAC;YACnB,IAAI,EAAE,gBAAgB;YACtB,OAAO,EAAE,+BAA+B,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,KAAK,IAAI;YACxE,IAAI,EAAE,iBAAiB,QAAQ,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YAC/C,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;SAC9B,CAAC,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the sherpa-onnx platform binary package and the environment the
|
|
3
|
+
* sidecar must be spawned with.
|
|
4
|
+
*
|
|
5
|
+
* CRITICAL native gotcha (scout-verified): sherpa-onnx-node's addon dlopen's
|
|
6
|
+
* sibling shared libs (`libonnxruntime.dylib`, `libsherpa-onnx-c-api.dylib`, …)
|
|
7
|
+
* that live inside the per-platform package dir, and dyld/ld.so read
|
|
8
|
+
* `DYLD_LIBRARY_PATH` / `LD_LIBRARY_PATH` ONCE at process start. So the addon
|
|
9
|
+
* only resolves its libs when that env var already points at the platform
|
|
10
|
+
* package dir when the process launches — which is exactly why the host runs in
|
|
11
|
+
* a freshly-forked child: the PARENT resolves the dir here and sets the var in
|
|
12
|
+
* the fork's env. On Windows the DLLs sit next to the `.node`, so no env var is
|
|
13
|
+
* needed.
|
|
14
|
+
*
|
|
15
|
+
* Shared verbatim with @moxxy/plugin-tts-local (deliberately duplicated rather
|
|
16
|
+
* than cross-imported so neither voice plugin depends on the other).
|
|
17
|
+
*/
|
|
18
|
+
/** The platform binary package name for a host, or null if unsupported. */
|
|
19
|
+
export declare function sherpaPlatformPackage(platform?: NodeJS.Platform, arch?: string): string | null;
|
|
20
|
+
/** A `require.resolve`-shaped function (injectable for tests). */
|
|
21
|
+
export type ResolveLike = (request: string) => string;
|
|
22
|
+
/**
|
|
23
|
+
* Absolute directory of the resolved platform package (which holds the `.node`
|
|
24
|
+
* addon and its sibling shared libraries), or null when the package can't be
|
|
25
|
+
* resolved (unsupported platform, or the optionalDependency didn't install).
|
|
26
|
+
* `resolve` is injectable for tests; the default is anchored at sherpa-onnx-node.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveSherpaLibDir(platform?: NodeJS.Platform, arch?: string, resolve?: ResolveLike): string | null;
|
|
29
|
+
/** The dynamic-loader env var name for a platform, or null on Windows. */
|
|
30
|
+
export declare function libraryPathVar(platform?: NodeJS.Platform): string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Build the env overrides the sidecar must launch with: the platform loader var
|
|
33
|
+
* with `libDir` PREPENDED to any existing value. Returns `{}` on Windows (DLLs
|
|
34
|
+
* resolve next to the addon). `existing` defaults to `process.env`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function sherpaEnv(libDir: string, platform?: NodeJS.Platform, existing?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
37
|
+
//# sourceMappingURL=platform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAeH,2EAA2E;AAC3E,wBAAgB,qBAAqB,CACnC,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,IAAI,GAAE,MAAqB,GAC1B,MAAM,GAAG,IAAI,CAEf;AAED,kEAAkE;AAClE,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;AAyBtD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,IAAI,GAAE,MAAqB,EAC3B,OAAO,CAAC,EAAE,WAAW,GACpB,MAAM,GAAG,IAAI,CAUf;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,MAAM,GAAG,IAAI,CAG1F;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,QAAQ,GAAE,MAAM,CAAC,UAAwB,GACxC,MAAM,CAAC,UAAU,CAMnB"}
|
package/dist/platform.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the sherpa-onnx platform binary package and the environment the
|
|
3
|
+
* sidecar must be spawned with.
|
|
4
|
+
*
|
|
5
|
+
* CRITICAL native gotcha (scout-verified): sherpa-onnx-node's addon dlopen's
|
|
6
|
+
* sibling shared libs (`libonnxruntime.dylib`, `libsherpa-onnx-c-api.dylib`, …)
|
|
7
|
+
* that live inside the per-platform package dir, and dyld/ld.so read
|
|
8
|
+
* `DYLD_LIBRARY_PATH` / `LD_LIBRARY_PATH` ONCE at process start. So the addon
|
|
9
|
+
* only resolves its libs when that env var already points at the platform
|
|
10
|
+
* package dir when the process launches — which is exactly why the host runs in
|
|
11
|
+
* a freshly-forked child: the PARENT resolves the dir here and sets the var in
|
|
12
|
+
* the fork's env. On Windows the DLLs sit next to the `.node`, so no env var is
|
|
13
|
+
* needed.
|
|
14
|
+
*
|
|
15
|
+
* Shared verbatim with @moxxy/plugin-tts-local (deliberately duplicated rather
|
|
16
|
+
* than cross-imported so neither voice plugin depends on the other).
|
|
17
|
+
*/
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
/** `${platform}-${arch}` → npm package that ships the native addon + libs.
|
|
21
|
+
* Note the Windows package is `sherpa-onnx-win-x64` (renamed from win32-x64). */
|
|
22
|
+
const PLATFORM_PACKAGES = {
|
|
23
|
+
'darwin-arm64': 'sherpa-onnx-darwin-arm64',
|
|
24
|
+
'darwin-x64': 'sherpa-onnx-darwin-x64',
|
|
25
|
+
'linux-x64': 'sherpa-onnx-linux-x64',
|
|
26
|
+
'linux-arm64': 'sherpa-onnx-linux-arm64',
|
|
27
|
+
'win32-x64': 'sherpa-onnx-win-x64',
|
|
28
|
+
};
|
|
29
|
+
/** The platform binary package name for a host, or null if unsupported. */
|
|
30
|
+
export function sherpaPlatformPackage(platform = process.platform, arch = process.arch) {
|
|
31
|
+
return PLATFORM_PACKAGES[`${platform}-${arch}`] ?? null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A `require.resolve` anchored at sherpa-onnx-node itself.
|
|
35
|
+
*
|
|
36
|
+
* Load-bearing: sherpa's per-platform binary packages are its OWN
|
|
37
|
+
* `optionalDependencies`, so under pnpm's strict layout they are visible from
|
|
38
|
+
* sherpa-onnx-node's `node_modules` but NOT hoisted into this plugin's — a
|
|
39
|
+
* resolve anchored here would `MODULE_NOT_FOUND`. So we first resolve
|
|
40
|
+
* sherpa-onnx-node, then resolve the platform package from ITS context. Cached
|
|
41
|
+
* because it walks two package boundaries.
|
|
42
|
+
*/
|
|
43
|
+
let cachedSherpaResolve;
|
|
44
|
+
function sherpaAnchoredResolve() {
|
|
45
|
+
if (cachedSherpaResolve !== undefined)
|
|
46
|
+
return cachedSherpaResolve;
|
|
47
|
+
try {
|
|
48
|
+
const pluginRequire = createRequire(import.meta.url);
|
|
49
|
+
const sherpaPkg = pluginRequire.resolve('sherpa-onnx-node/package.json');
|
|
50
|
+
cachedSherpaResolve = createRequire(sherpaPkg).resolve;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
cachedSherpaResolve = null;
|
|
54
|
+
}
|
|
55
|
+
return cachedSherpaResolve;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Absolute directory of the resolved platform package (which holds the `.node`
|
|
59
|
+
* addon and its sibling shared libraries), or null when the package can't be
|
|
60
|
+
* resolved (unsupported platform, or the optionalDependency didn't install).
|
|
61
|
+
* `resolve` is injectable for tests; the default is anchored at sherpa-onnx-node.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveSherpaLibDir(platform = process.platform, arch = process.arch, resolve) {
|
|
64
|
+
const pkg = sherpaPlatformPackage(platform, arch);
|
|
65
|
+
if (!pkg)
|
|
66
|
+
return null;
|
|
67
|
+
const r = resolve ?? sherpaAnchoredResolve();
|
|
68
|
+
if (!r)
|
|
69
|
+
return null;
|
|
70
|
+
try {
|
|
71
|
+
return path.dirname(r(`${pkg}/package.json`));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** The dynamic-loader env var name for a platform, or null on Windows. */
|
|
78
|
+
export function libraryPathVar(platform = process.platform) {
|
|
79
|
+
if (platform === 'win32')
|
|
80
|
+
return null;
|
|
81
|
+
return platform === 'darwin' ? 'DYLD_LIBRARY_PATH' : 'LD_LIBRARY_PATH';
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Build the env overrides the sidecar must launch with: the platform loader var
|
|
85
|
+
* with `libDir` PREPENDED to any existing value. Returns `{}` on Windows (DLLs
|
|
86
|
+
* resolve next to the addon). `existing` defaults to `process.env`.
|
|
87
|
+
*/
|
|
88
|
+
export function sherpaEnv(libDir, platform = process.platform, existing = process.env) {
|
|
89
|
+
const varName = libraryPathVar(platform);
|
|
90
|
+
if (!varName)
|
|
91
|
+
return {};
|
|
92
|
+
const prev = existing[varName];
|
|
93
|
+
const value = prev ? `${libDir}${path.delimiter}${prev}` : libDir;
|
|
94
|
+
return { [varName]: value };
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=platform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;kFACkF;AAClF,MAAM,iBAAiB,GAAqC;IAC1D,cAAc,EAAE,0BAA0B;IAC1C,YAAY,EAAE,wBAAwB;IACtC,WAAW,EAAE,uBAAuB;IACpC,aAAa,EAAE,yBAAyB;IACxC,WAAW,EAAE,qBAAqB;CACnC,CAAC;AAEF,2EAA2E;AAC3E,MAAM,UAAU,qBAAqB,CACnC,WAA4B,OAAO,CAAC,QAAQ,EAC5C,OAAe,OAAO,CAAC,IAAI;IAE3B,OAAO,iBAAiB,CAAC,GAAG,QAAQ,IAAI,IAAI,EAAE,CAAC,IAAI,IAAI,CAAC;AAC1D,CAAC;AAKD;;;;;;;;;GASG;AACH,IAAI,mBAAmD,CAAC;AACxD,SAAS,qBAAqB;IAC5B,IAAI,mBAAmB,KAAK,SAAS;QAAE,OAAO,mBAAmB,CAAC;IAClE,IAAI,CAAC;QACH,MAAM,aAAa,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrD,MAAM,SAAS,GAAG,aAAa,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC;QACzE,mBAAmB,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,mBAAmB,GAAG,IAAI,CAAC;IAC7B,CAAC;IACD,OAAO,mBAAmB,CAAC;AAC7B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,WAA4B,OAAO,CAAC,QAAQ,EAC5C,OAAe,OAAO,CAAC,IAAI,EAC3B,OAAqB;IAErB,MAAM,GAAG,GAAG,qBAAqB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClD,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,CAAC,GAAG,OAAO,IAAI,qBAAqB,EAAE,CAAC;IAC7C,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG,eAAe,CAAC,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,cAAc,CAAC,WAA4B,OAAO,CAAC,QAAQ;IACzE,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,iBAAiB,CAAC;AACzE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CACvB,MAAc,EACd,WAA4B,OAAO,CAAC,QAAQ,EAC5C,WAA8B,OAAO,CAAC,GAAG;IAEzC,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACxB,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IAClE,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* sherpa-onnx STT sidecar — a forked child that owns the native sherpa addon.
|
|
4
|
+
*
|
|
5
|
+
* It exists as a SEPARATE process for one hard reason (see ./platform.ts): the
|
|
6
|
+
* addon's shared libraries only resolve when `DYLD_LIBRARY_PATH` /
|
|
7
|
+
* `LD_LIBRARY_PATH` points at the platform package dir AT PROCESS START, so the
|
|
8
|
+
* parent forks this with that env pre-set. Forking (not `spawn`) gives us a
|
|
9
|
+
* structured IPC channel; the parent uses `serialization: 'advanced'` so the
|
|
10
|
+
* `Float32Array` of samples round-trips without manual byte packing.
|
|
11
|
+
*
|
|
12
|
+
* Protocol: {@link ./host-protocol.ts}. This module is the thin runtime glue —
|
|
13
|
+
* lazily `require`ing the native module (a dlopen failure becomes a classified
|
|
14
|
+
* `init` reply rather than a boot crash), plus lifecycle self-termination so an
|
|
15
|
+
* orphaned sidecar never lingers.
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=sidecar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidecar.d.ts","sourceRoot":"","sources":["../src/sidecar.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG"}
|
package/dist/sidecar.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* sherpa-onnx STT sidecar — a forked child that owns the native sherpa addon.
|
|
4
|
+
*
|
|
5
|
+
* It exists as a SEPARATE process for one hard reason (see ./platform.ts): the
|
|
6
|
+
* addon's shared libraries only resolve when `DYLD_LIBRARY_PATH` /
|
|
7
|
+
* `LD_LIBRARY_PATH` points at the platform package dir AT PROCESS START, so the
|
|
8
|
+
* parent forks this with that env pre-set. Forking (not `spawn`) gives us a
|
|
9
|
+
* structured IPC channel; the parent uses `serialization: 'advanced'` so the
|
|
10
|
+
* `Float32Array` of samples round-trips without manual byte packing.
|
|
11
|
+
*
|
|
12
|
+
* Protocol: {@link ./host-protocol.ts}. This module is the thin runtime glue —
|
|
13
|
+
* lazily `require`ing the native module (a dlopen failure becomes a classified
|
|
14
|
+
* `init` reply rather than a boot crash), plus lifecycle self-termination so an
|
|
15
|
+
* orphaned sidecar never lingers.
|
|
16
|
+
*/
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { createMessageHandler, } from './host-protocol.js';
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
/** Lazy native load — deferred so a missing/broken addon is a caught, reported
|
|
22
|
+
* `init` error on the first transcribe, not an unhandled boot exception. */
|
|
23
|
+
function loadSherpa() {
|
|
24
|
+
return require('sherpa-onnx-node');
|
|
25
|
+
}
|
|
26
|
+
function send(reply) {
|
|
27
|
+
// `process.send` is defined only when spawned with an IPC channel (our fork).
|
|
28
|
+
process.send?.(reply);
|
|
29
|
+
}
|
|
30
|
+
function isRequest(msg) {
|
|
31
|
+
return (typeof msg === 'object' &&
|
|
32
|
+
msg !== null &&
|
|
33
|
+
msg.type === 'transcribe' &&
|
|
34
|
+
typeof msg.id === 'number');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Self-terminate if the parent runner disappears. `fork` delivers a
|
|
38
|
+
* `disconnect` event when the IPC channel closes (parent exit / explicit
|
|
39
|
+
* disconnect); belt-and-braces, poll the parent PID too — a hard SIGKILL of the
|
|
40
|
+
* parent may not always close the channel promptly.
|
|
41
|
+
*/
|
|
42
|
+
function armLifecycle() {
|
|
43
|
+
process.on('disconnect', () => process.exit(0));
|
|
44
|
+
const parentPid = process.ppid;
|
|
45
|
+
if (!parentPid || parentPid <= 1)
|
|
46
|
+
return;
|
|
47
|
+
const timer = setInterval(() => {
|
|
48
|
+
try {
|
|
49
|
+
process.kill(parentPid, 0); // existence probe, no signal delivered
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
if (err.code === 'ESRCH')
|
|
53
|
+
process.exit(0);
|
|
54
|
+
// EPERM ⇒ the process exists but we can't signal it — still alive.
|
|
55
|
+
}
|
|
56
|
+
}, 3000);
|
|
57
|
+
timer.unref?.();
|
|
58
|
+
}
|
|
59
|
+
function main() {
|
|
60
|
+
const handle = createMessageHandler(loadSherpa);
|
|
61
|
+
// Serialise requests: sherpa's OfflineRecognizer is not re-entrant, and
|
|
62
|
+
// processing one at a time keeps memory bounded under a burst of voice notes.
|
|
63
|
+
let queue = Promise.resolve();
|
|
64
|
+
process.on('message', (msg) => {
|
|
65
|
+
if (!isRequest(msg))
|
|
66
|
+
return;
|
|
67
|
+
queue = queue
|
|
68
|
+
.then(async () => {
|
|
69
|
+
const reply = await handle(msg);
|
|
70
|
+
send(reply);
|
|
71
|
+
})
|
|
72
|
+
.catch((err) => {
|
|
73
|
+
send({
|
|
74
|
+
id: msg.id,
|
|
75
|
+
ok: false,
|
|
76
|
+
error: { message: err instanceof Error ? err.message : String(err), kind: 'runtime' },
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
armLifecycle();
|
|
81
|
+
}
|
|
82
|
+
// Run only when executed as the forked child, never when imported by a test.
|
|
83
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
84
|
+
main();
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=sidecar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidecar.js","sourceRoot":"","sources":["../src/sidecar.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EACL,oBAAoB,GAIrB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C;6EAC6E;AAC7E,SAAS,UAAU;IACjB,OAAO,OAAO,CAAC,kBAAkB,CAAiB,CAAC;AACrD,CAAC;AAED,SAAS,IAAI,CAAC,KAAgB;IAC5B,8EAA8E;IAC9E,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,SAAS,CAAC,GAAY;IAC7B,OAAO,CACL,OAAO,GAAG,KAAK,QAAQ;QACvB,GAAG,KAAK,IAAI;QACX,GAA0B,CAAC,IAAI,KAAK,YAAY;QACjD,OAAQ,GAAwB,CAAC,EAAE,KAAK,QAAQ,CACjD,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,YAAY;IACnB,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAC/B,IAAI,CAAC,SAAS,IAAI,SAAS,IAAI,CAAC;QAAE,OAAO;IACzC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,uCAAuC;QACrE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAK,GAA6B,CAAC,IAAI,KAAK,OAAO;gBAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACrE,mEAAmE;QACrE,CAAC;IACH,CAAC,EAAE,IAAI,CAAC,CAAC;IACT,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;AAClB,CAAC;AAED,SAAS,IAAI;IACX,MAAM,MAAM,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAChD,wEAAwE;IACxE,8EAA8E;IAC9E,IAAI,KAAK,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IAC7C,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAY,EAAE,EAAE;QACrC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;YAAE,OAAO;QAC5B,KAAK,GAAG,KAAK;aACV,IAAI,CAAC,KAAK,IAAI,EAAE;YACf,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACb,IAAI,CAAC;gBACH,EAAE,EAAE,GAAG,CAAC,EAAE;gBACV,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,EAAE,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE;aACtF,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IACH,YAAY,EAAE,CAAC;AACjB,CAAC;AAED,6EAA6E;AAC7E,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,IAAI,EAAE,CAAC;AACT,CAAC"}
|