@rivus/gateway 0.18.2 → 0.19.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 +4 -0
- package/assets/audio-transcriber/THIRD_PARTY_NOTICES.md +86 -0
- package/assets/audio-transcriber/byte-tokens.mjs +33 -0
- package/assets/audio-transcriber/fetch-model.mjs +225 -0
- package/assets/audio-transcriber/manifest.json +64 -0
- package/assets/audio-transcriber/npm-shrinkwrap.json +345 -0
- package/assets/audio-transcriber/package.json +15 -0
- package/assets/audio-transcriber/transcribe.mjs +411 -0
- package/dist/bootstrap/pi-feishu.js +80 -21
- package/dist/chunks/herdr-feishu-runtime.js +1 -1
- package/dist/chunks/{runtime-cache.js → rivus-home-deployment-env.js} +16 -3
- package/dist/chunks/rivus-node-entrypoint.js +609 -8
- package/package.json +14 -10
package/README.md
CHANGED
|
@@ -19,6 +19,10 @@ Gateway is the sole owner of deployment templates, the starter Plugin and runtim
|
|
|
19
19
|
`@rivus/gateway/plugin/starter` and `@rivus/gateway/skills/runtime-management/SKILL.md` directly. Background-session
|
|
20
20
|
MCP uses `@rivus/gateway/mcp`; old Agent bootstrap, Plugin, Skill and MCP aliases are not available.
|
|
21
21
|
|
|
22
|
+
Gateway also ships the installer assets of the Rivus-managed audio transcriber under `assets/audio-transcriber/`.
|
|
23
|
+
`rivus audio install` downloads the engine, its npm packages and the model onto the host at install time; the package
|
|
24
|
+
contains none of them.
|
|
25
|
+
|
|
22
26
|
Pi deployment Bootstrap opens the Runtime JSONL Memory Repository and injects one canonical Effect Memory service.
|
|
23
27
|
The former Gateway Memory service factories are removed; use Runtime's public Repository and service constructors.
|
|
24
28
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
The Rivus-managed audio transcriber ships only its own scripts. `rivus audio install` downloads the components below
|
|
4
|
+
from their upstream sources into the installed release and verifies them against the pins in `package.json`,
|
|
5
|
+
`npm-shrinkwrap.json` and `manifest.json`. Each component keeps its own license.
|
|
6
|
+
|
|
7
|
+
## Models
|
|
8
|
+
|
|
9
|
+
| Component | Source | License |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| OpenAI Whisper turbo (native runtime) or small (WebAssembly runtime) weights and tokenizer, exported to int8 ONNX by sherpa-onnx | `sherpa-onnx-whisper-turbo.tar.bz2` / `sherpa-onnx-whisper-small.tar.bz2`, `k2-fsa/sherpa-onnx` GitHub release `asr-models` | MIT, Copyright (c) 2022 OpenAI (text below) |
|
|
12
|
+
| Silero VAD (`silero_vad.int8.onnx`) | `k2-fsa/sherpa-onnx` GitHub release `asr-models` | MIT, Copyright (c) 2020-present Silero Team (text below) |
|
|
13
|
+
| Install smoke-test recording (`model/smoke.wav`) | `test_wavs/0.wav`, a test file inside the same sherpa-onnx Whisper archive. Its English transcript appears to be a LibriSpeech utterance, but the archive names no source for it. Rivus does not redistribute it: the installer extracts it on the deployment host and uses it only as a local smoke test. | Not stated in the archive |
|
|
14
|
+
|
|
15
|
+
The sherpa-onnx model archives contain no license file of their own. The Whisper weights are the ones published by
|
|
16
|
+
OpenAI in <https://github.com/openai/whisper>, whose README states that the code and the model weights are released
|
|
17
|
+
under the MIT License.
|
|
18
|
+
|
|
19
|
+
## npm packages
|
|
20
|
+
|
|
21
|
+
| Package | License |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| `sherpa-onnx-node`, its platform packages (`sherpa-onnx-<os>-<cpu>`) and `sherpa-onnx` (WebAssembly) 1.13.8 | Apache-2.0. The binaries bundle onnxruntime (MIT) and statically include espeak-ng (GPL-3.0), which only the text-to-speech functions use. |
|
|
24
|
+
| `ogg-opus-decoder` 1.7.5, `opus-decoder`, `@wasm-audio-decoders/*`, `simple-yenc` | MIT; the decoders are compiled from libopus (BSD-3-Clause) |
|
|
25
|
+
| `codec-parser` | LGPL-3.0-or-later |
|
|
26
|
+
| `@eshaz/web-worker` | Apache-2.0 |
|
|
27
|
+
| `opencc-js` 1.4.2 | MIT AND Apache-2.0 (code MIT; dictionary data generated from `opencc-data`, Apache-2.0) |
|
|
28
|
+
| `unbzip2-stream` 1.4.3, `through`, `buffer`, `base64-js` | MIT |
|
|
29
|
+
| `ieee754` | BSD-3-Clause |
|
|
30
|
+
| `tar` 7.5.22, `minipass`, `chownr`, `yallist` | BlueOak-1.0.0 |
|
|
31
|
+
| `minizlib` | MIT |
|
|
32
|
+
| `@isaacs/fs-minipass` | ISC |
|
|
33
|
+
|
|
34
|
+
Each installed package carries its license text under `node_modules/<package>`.
|
|
35
|
+
|
|
36
|
+
## OpenAI Whisper
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
MIT License
|
|
40
|
+
|
|
41
|
+
Copyright (c) 2022 OpenAI
|
|
42
|
+
|
|
43
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
44
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
45
|
+
in the Software without restriction, including without limitation the rights
|
|
46
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
47
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
48
|
+
furnished to do so, subject to the following conditions:
|
|
49
|
+
|
|
50
|
+
The above copyright notice and this permission notice shall be included in all
|
|
51
|
+
copies or substantial portions of the Software.
|
|
52
|
+
|
|
53
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
54
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
55
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
56
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
57
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
58
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
59
|
+
SOFTWARE.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Silero VAD
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
MIT License
|
|
66
|
+
|
|
67
|
+
Copyright (c) 2020-present Silero Team
|
|
68
|
+
|
|
69
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
70
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
71
|
+
in the Software without restriction, including without limitation the rights
|
|
72
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
73
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
74
|
+
furnished to do so, subject to the following conditions:
|
|
75
|
+
|
|
76
|
+
The above copyright notice and this permission notice shall be included in all
|
|
77
|
+
copies or substantial portions of the Software.
|
|
78
|
+
|
|
79
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
80
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
81
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
82
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
83
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
84
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
85
|
+
SOFTWARE.
|
|
86
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Whisper's byte-level BPE splits many CJK characters across tokens, so one token can hold an incomplete UTF-8
|
|
2
|
+
// sequence. sherpa-onnx 1.13.8 drops invalid UTF-8 from every token before joining them, which deletes those characters
|
|
3
|
+
// (fixed upstream after 1.13.8 by k2-fsa/sherpa-onnx#3965). fetch-model.mjs therefore installs tokens.bytes.txt, which
|
|
4
|
+
// spells every non-ASCII token byte as the code point BYTE_BASE + byte, valid UTF-8 on its own; transcribe.mjs maps the
|
|
5
|
+
// recognizer's text back to bytes.
|
|
6
|
+
const BYTE_BASE = 0xe000;
|
|
7
|
+
|
|
8
|
+
/** Rewrites tokens.txt, whose lines are "<base64 of the token's bytes> <id>", into tokens.bytes.txt. */
|
|
9
|
+
export function spellByteTokens(tokensText) {
|
|
10
|
+
const lines = tokensText.split("\n").filter(Boolean);
|
|
11
|
+
return lines
|
|
12
|
+
.map((line) => {
|
|
13
|
+
const [base64, id] = line.split(" ");
|
|
14
|
+
if (!/^\d+$/.test(id ?? "")) {
|
|
15
|
+
throw new Error(`unexpected tokens.txt line: ${line.slice(0, 40)}`);
|
|
16
|
+
}
|
|
17
|
+
const text = [...Buffer.from(base64, "base64")]
|
|
18
|
+
.map((byte) => String.fromCodePoint(byte < 0x80 ? byte : BYTE_BASE + byte))
|
|
19
|
+
.join("");
|
|
20
|
+
// The last entry ("= 50256") decodes to no bytes and stays as it is.
|
|
21
|
+
return text ? `${Buffer.from(text, "utf8").toString("base64")} ${id}\n` : `${line}\n`;
|
|
22
|
+
})
|
|
23
|
+
.join("");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Turns recognizer text back into bytes and decodes them. A chunk cut off inside a character drops that character. */
|
|
27
|
+
export function decodeByteText(text) {
|
|
28
|
+
const bytes = [...text].map((character) => {
|
|
29
|
+
const code = character.codePointAt(0);
|
|
30
|
+
return code >= BYTE_BASE + 0x80 && code <= BYTE_BASE + 0xff ? code - BYTE_BASE : code;
|
|
31
|
+
});
|
|
32
|
+
return Buffer.from(bytes).toString("utf8").replaceAll("\uFFFD", "");
|
|
33
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// Installs the transcriber model into <model-dir>, all or nothing. Usage: node fetch-model.mjs <manifest.json> <model-dir>
|
|
2
|
+
//
|
|
3
|
+
// The engine runtime is chosen here, once per installation: the native sherpa-onnx addon when it loads on this host
|
|
4
|
+
// (on Linux x64 it needs glibc 2.32 and the libstdc++ of GCC 11), otherwise the WebAssembly build, which runs wherever
|
|
5
|
+
// Node runs but on one thread. Each runtime has its own Whisper size in the manifest, and the choice is recorded in
|
|
6
|
+
// <model-dir>/runtime.json for transcribe.mjs.
|
|
7
|
+
//
|
|
8
|
+
// RIVUS_AUDIO_RUNTIME=auto|native|wasm (default auto) forces a runtime. RIVUS_AUDIO_MODEL_BASE_URL replaces the
|
|
9
|
+
// manifest's baseUrl with a mirror of the sherpa-onnx GitHub releases; the sha256 pins still apply.
|
|
10
|
+
//
|
|
11
|
+
// A mirror is untrusted, so no downloaded byte is parsed before its file matches the manifest's size and sha256: the
|
|
12
|
+
// model archive is downloaded completely into the staging directory and only then unpacked, which needs room for the
|
|
13
|
+
// archive and its extracted members at the same time.
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
15
|
+
import { createReadStream, createWriteStream } from "node:fs";
|
|
16
|
+
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
17
|
+
import { createRequire } from "node:module";
|
|
18
|
+
import { join, resolve } from "node:path";
|
|
19
|
+
import { Readable, Transform } from "node:stream";
|
|
20
|
+
import { pipeline } from "node:stream/promises";
|
|
21
|
+
|
|
22
|
+
import { extract } from "tar";
|
|
23
|
+
import bunzip2 from "unbzip2-stream";
|
|
24
|
+
|
|
25
|
+
import { spellByteTokens } from "./byte-tokens.mjs";
|
|
26
|
+
|
|
27
|
+
const RUNTIMES = ["auto", "native", "wasm"];
|
|
28
|
+
// A download that delivers no bytes for this long is aborted and tried once more.
|
|
29
|
+
const STALL_MS = 60_000;
|
|
30
|
+
const ATTEMPTS = 2;
|
|
31
|
+
|
|
32
|
+
class IntegrityError extends Error {}
|
|
33
|
+
|
|
34
|
+
async function chooseRuntime(requested) {
|
|
35
|
+
if (!RUNTIMES.includes(requested)) {
|
|
36
|
+
throw new Error(`RIVUS_AUDIO_RUNTIME must be one of ${RUNTIMES.join(", ")}, not ${JSON.stringify(requested)}`);
|
|
37
|
+
}
|
|
38
|
+
if (requested === "wasm") {
|
|
39
|
+
return { runtime: "wasm" };
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
await import("sherpa-onnx-node");
|
|
43
|
+
return { runtime: "native" };
|
|
44
|
+
} catch (error) {
|
|
45
|
+
const reason = nativeLoadError(error);
|
|
46
|
+
if (requested === "native") {
|
|
47
|
+
throw new Error(`the native sherpa-onnx addon does not load on this host: ${reason}`);
|
|
48
|
+
}
|
|
49
|
+
return { nativeError: reason, runtime: "wasm" };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// sherpa-onnx-node's loader replaces the dlopen error with a list of the paths it tried. Loading the platform binary
|
|
54
|
+
// directly reports the actual cause, such as a missing package or GLIBC version.
|
|
55
|
+
function nativeLoadError(error) {
|
|
56
|
+
const platform = process.platform === "win32" ? "win" : process.platform;
|
|
57
|
+
try {
|
|
58
|
+
createRequire(import.meta.url)(`sherpa-onnx-${platform}-${process.arch}/sherpa-onnx.node`);
|
|
59
|
+
} catch (direct) {
|
|
60
|
+
return firstLine(direct);
|
|
61
|
+
}
|
|
62
|
+
return firstLine(error);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Downloads `url` into `file`, failing as soon as it exceeds `size` and when it does not match `sha256`. */
|
|
66
|
+
async function download(url, { sha256, size }, file, stallMs) {
|
|
67
|
+
const hash = createHash("sha256");
|
|
68
|
+
const controller = new AbortController();
|
|
69
|
+
let received = 0;
|
|
70
|
+
let timer;
|
|
71
|
+
const resetStallTimer = () => {
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
timer = setTimeout(() => controller.abort(), stallMs);
|
|
74
|
+
};
|
|
75
|
+
const tap = new Transform({
|
|
76
|
+
transform(chunk, _encoding, callback) {
|
|
77
|
+
resetStallTimer();
|
|
78
|
+
received += chunk.length;
|
|
79
|
+
hash.update(chunk);
|
|
80
|
+
callback(received > size ? new IntegrityError(`received more than the expected ${size} bytes`) : null, chunk);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
resetStallTimer();
|
|
84
|
+
try {
|
|
85
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
86
|
+
if (!response.ok || !response.body) {
|
|
87
|
+
throw new Error(`HTTP ${response.status}`);
|
|
88
|
+
}
|
|
89
|
+
await pipeline(Readable.fromWeb(response.body), tap, createWriteStream(file));
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error instanceof IntegrityError) {
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
const reason = controller.signal.aborted ? `no data for ${stallMs / 1000} s` : describe(error);
|
|
95
|
+
throw new Error(`download failed after ${received} bytes: ${reason}`);
|
|
96
|
+
} finally {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
}
|
|
99
|
+
const actual = hash.digest("hex");
|
|
100
|
+
if (actual !== sha256) {
|
|
101
|
+
throw new IntegrityError(`sha256 mismatch: expected ${sha256}, got ${actual} (${received} bytes)`);
|
|
102
|
+
}
|
|
103
|
+
return received;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function downloadWithRetry(url, pin, file, stallMs) {
|
|
107
|
+
for (let attempt = 1; ; attempt++) {
|
|
108
|
+
try {
|
|
109
|
+
return await download(url, pin, file, stallMs);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
// A wrong digest is deterministic; retrying would only download the same bytes again. A retry overwrites the
|
|
112
|
+
// file of the failed attempt.
|
|
113
|
+
if (error instanceof IntegrityError || attempt === ATTEMPTS) {
|
|
114
|
+
throw new Error(`${url}: ${error.message}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Unpacks only the listed archive members, each under its installed name directly in `directory`. A listed member
|
|
122
|
+
* must be a regular file of exactly its pinned size, checked against its tar header before any of it is written, so the
|
|
123
|
+
* extracted bytes never exceed the sum of the pinned sizes.
|
|
124
|
+
*/
|
|
125
|
+
async function extractMembers(archive, directory, members) {
|
|
126
|
+
let mismatch;
|
|
127
|
+
const unpack = extract({
|
|
128
|
+
cwd: directory,
|
|
129
|
+
filter: (path, entry) => {
|
|
130
|
+
if (!Object.hasOwn(members, path)) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
if (entry.type !== "File" || entry.size !== members[path].size) {
|
|
134
|
+
// Skipped rather than aborted: an abort fails the pipeline while earlier members are still being written,
|
|
135
|
+
// which would race the removal of the staging directory.
|
|
136
|
+
mismatch ??= new Error(`${path} is a ${entry.size}-byte ${entry.type}, not a ${members[path].size}-byte file`);
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
},
|
|
141
|
+
onReadEntry: (entry) => {
|
|
142
|
+
entry.path = members[entry.path].file;
|
|
143
|
+
},
|
|
144
|
+
preserveOwner: false,
|
|
145
|
+
strict: true
|
|
146
|
+
});
|
|
147
|
+
await pipeline(createReadStream(archive), bunzip2(), unpack);
|
|
148
|
+
if (mismatch) {
|
|
149
|
+
throw mismatch;
|
|
150
|
+
}
|
|
151
|
+
for (const [member, { file, size }] of Object.entries(members)) {
|
|
152
|
+
const info = await stat(join(directory, file)).catch(() => undefined);
|
|
153
|
+
if (info?.size !== size) {
|
|
154
|
+
throw new Error(`the archive has no ${size}-byte member ${member}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function writeByteTokens(directory) {
|
|
160
|
+
const source = join(directory, "tokens.txt");
|
|
161
|
+
await writeFile(join(directory, "tokens.bytes.txt"), spellByteTokens(await readFile(source, "latin1")));
|
|
162
|
+
// Pointing sherpa-onnx at the original table would silently lose characters again.
|
|
163
|
+
await rm(source);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Installs the model that `manifest` pins for this host's runtime into `modelDirectory`, replacing what is there only
|
|
168
|
+
* once everything is in place. Tests call it directly with a short `stallMs`.
|
|
169
|
+
*/
|
|
170
|
+
export async function install(manifest, modelDirectory, { env = process.env, stallMs = STALL_MS } = {}) {
|
|
171
|
+
const { nativeError, runtime } = await chooseRuntime(env.RIVUS_AUDIO_RUNTIME || "auto");
|
|
172
|
+
const model = manifest.models[runtime];
|
|
173
|
+
const baseUrl = (env.RIVUS_AUDIO_MODEL_BASE_URL || manifest.baseUrl).replace(/\/+$/, "");
|
|
174
|
+
const staging = `${modelDirectory}.partial-${process.pid}`;
|
|
175
|
+
await rm(staging, { force: true, recursive: true });
|
|
176
|
+
await mkdir(staging, { recursive: true });
|
|
177
|
+
try {
|
|
178
|
+
const { vad } = manifest;
|
|
179
|
+
let downloaded = await downloadWithRetry(`${baseUrl}/${vad.path}`, vad, join(staging, vad.file), stallMs);
|
|
180
|
+
// The archive's name must not be one of the installed file names.
|
|
181
|
+
const archive = join(staging, "model.tar.bz2.download");
|
|
182
|
+
downloaded += await downloadWithRetry(`${baseUrl}/${model.path}`, model, archive, stallMs);
|
|
183
|
+
try {
|
|
184
|
+
await extractMembers(archive, staging, model.members);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
throw new Error(`${model.path}: ${error.message}`);
|
|
187
|
+
}
|
|
188
|
+
await rm(archive);
|
|
189
|
+
await writeByteTokens(staging);
|
|
190
|
+
const selection = { model: model.name, runtime, ...(nativeError ? { nativeError } : {}) };
|
|
191
|
+
await writeFile(join(staging, "runtime.json"), `${JSON.stringify(selection)}\n`);
|
|
192
|
+
await rm(modelDirectory, { force: true, recursive: true });
|
|
193
|
+
await rename(staging, modelDirectory);
|
|
194
|
+
return { ...selection, downloadedBytes: downloaded };
|
|
195
|
+
} catch (error) {
|
|
196
|
+
// A failed extraction can still be closing files in the staging directory.
|
|
197
|
+
await rm(staging, { force: true, maxRetries: 3, recursive: true });
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function firstLine(error) {
|
|
203
|
+
return String(error instanceof Error ? error.message : error).split("\n")[0];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// fetch() reports network failures as "fetch failed" with the reason in `cause`.
|
|
207
|
+
function describe(error) {
|
|
208
|
+
const cause = error instanceof Error && error.cause instanceof Error ? ` (${error.cause.message})` : "";
|
|
209
|
+
return `${firstLine(error)}${cause}`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (import.meta.main) {
|
|
213
|
+
try {
|
|
214
|
+
const [manifestPath, modelDirectory] = process.argv.slice(2);
|
|
215
|
+
if (!manifestPath || !modelDirectory) {
|
|
216
|
+
throw new Error("usage: node fetch-model.mjs <manifest.json> <model-dir>");
|
|
217
|
+
}
|
|
218
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
219
|
+
const result = await install(manifest, resolve(modelDirectory));
|
|
220
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
process.stderr.write(`fetch-model: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
223
|
+
process.exitCode = 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "whisper-1",
|
|
3
|
+
"smoke": {
|
|
4
|
+
"input": "model/smoke.wav",
|
|
5
|
+
"expect": "yellow lamps"
|
|
6
|
+
},
|
|
7
|
+
"baseUrl": "https://github.com/k2-fsa/sherpa-onnx/releases/download",
|
|
8
|
+
"vad": {
|
|
9
|
+
"path": "asr-models/silero_vad.int8.onnx",
|
|
10
|
+
"size": 212860,
|
|
11
|
+
"sha256": "c36d490aff5ab924ca6c7aeec4d8f6bd3d22db6fa17611b9c5b17eae58ac3a20",
|
|
12
|
+
"file": "silero_vad.int8.onnx"
|
|
13
|
+
},
|
|
14
|
+
"models": {
|
|
15
|
+
"native": {
|
|
16
|
+
"name": "whisper-turbo-int8",
|
|
17
|
+
"path": "asr-models/sherpa-onnx-whisper-turbo.tar.bz2",
|
|
18
|
+
"size": 563790207,
|
|
19
|
+
"sha256": "b11acbbcd660b44a8e0df33724feb5aaa709cf65668f2823d59f656312544f22",
|
|
20
|
+
"members": {
|
|
21
|
+
"sherpa-onnx-whisper-turbo/turbo-encoder.int8.onnx": {
|
|
22
|
+
"file": "encoder.int8.onnx",
|
|
23
|
+
"size": 674716297
|
|
24
|
+
},
|
|
25
|
+
"sherpa-onnx-whisper-turbo/turbo-decoder.int8.onnx": {
|
|
26
|
+
"file": "decoder.int8.onnx",
|
|
27
|
+
"size": 361080764
|
|
28
|
+
},
|
|
29
|
+
"sherpa-onnx-whisper-turbo/turbo-tokens.txt": {
|
|
30
|
+
"file": "tokens.txt",
|
|
31
|
+
"size": 816730
|
|
32
|
+
},
|
|
33
|
+
"sherpa-onnx-whisper-turbo/test_wavs/0.wav": {
|
|
34
|
+
"file": "smoke.wav",
|
|
35
|
+
"size": 212044
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"wasm": {
|
|
40
|
+
"name": "whisper-small-int8",
|
|
41
|
+
"path": "asr-models/sherpa-onnx-whisper-small.tar.bz2",
|
|
42
|
+
"size": 639387718,
|
|
43
|
+
"sha256": "486a46afbb7ba798507190ffe02fea2dd726049af212e774537efac6afb210a6",
|
|
44
|
+
"members": {
|
|
45
|
+
"sherpa-onnx-whisper-small/small-encoder.int8.onnx": {
|
|
46
|
+
"file": "encoder.int8.onnx",
|
|
47
|
+
"size": 112442483
|
|
48
|
+
},
|
|
49
|
+
"sherpa-onnx-whisper-small/small-decoder.int8.onnx": {
|
|
50
|
+
"file": "decoder.int8.onnx",
|
|
51
|
+
"size": 262226114
|
|
52
|
+
},
|
|
53
|
+
"sherpa-onnx-whisper-small/small-tokens.txt": {
|
|
54
|
+
"file": "tokens.txt",
|
|
55
|
+
"size": 816730
|
|
56
|
+
},
|
|
57
|
+
"sherpa-onnx-whisper-small/test_wavs/0.wav": {
|
|
58
|
+
"file": "smoke.wav",
|
|
59
|
+
"size": 212044
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|