@wirecat/cli-core 0.446.0 → 0.448.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/CHANGELOG.md +28 -0
- package/README.md +3 -2
- package/dist/embeddings/embed.js +21 -4
- package/dist/embeddings/models.js +1 -1
- package/dist/embeddings/onnx-runtime.d.ts +42 -0
- package/dist/embeddings/onnx-runtime.js +161 -0
- package/dist/services/messages-discovery.d.ts +8 -0
- package/dist/services/messages-discovery.js +7 -2
- package/dist/services/model-downloads.js +15 -0
- package/dist/speech/install.d.ts +1 -1
- package/package.json +2 -2
- package/parity.json +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ Notable changes to `@wirecat/cli-core` (`@wirecat/cli-messaging` up to 0.261.0,
|
|
|
5
5
|
minor version may break callers, and says how under "Changed — may break callers". `pnpm docs:check` checks the
|
|
6
6
|
shape of this file.
|
|
7
7
|
|
|
8
|
+
## Unreleased
|
|
9
|
+
|
|
10
|
+
## 0.448.0 — 26.10.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Text models and rerankers run on native ONNX Runtime on Linux (glibc, x64 and arm64) and Apple-silicon Macs**,
|
|
15
|
+
2–3× faster than WebAssembly: on one real archive `search messages --discover` went from 4.6 s to 3.2 s with the
|
|
16
|
+
same answers. `models download` of a text model or reranker now also fetches Microsoft's ONNX Runtime 1.30.0
|
|
17
|
+
library for the system (10–42 MB, checked by sha256) into `models/runtime/`; an install that already has its
|
|
18
|
+
models gets it on its next `models download`. Windows, Intel Macs, musl, picture models, and any library that
|
|
19
|
+
does not load keep running through WebAssembly. Needs `@wirecat/cli-core-onnx` 1.1.0.
|
|
20
|
+
|
|
21
|
+
## 0.447.0 — 26.10.2026
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **The parity manifest knows `session start --answer-stdin`**, in tg and max: the flag that lets WireCat Desktop
|
|
26
|
+
answer a sign-in's questions on stdin (`signInSteps`, 0.443.0). WhatsApp's QR sign-in asks nothing, so wa does not
|
|
27
|
+
take it yet.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **`--discover`'s reranker reads only the first 300 characters of the message a hit replies to.** The parent came
|
|
32
|
+
first in full, and a long one filled the reranker's 512 tokens, so the hit itself was cut off and the model scored
|
|
33
|
+
the parent instead (18 of 119 replies in one real chat). With the parent cut, the six known questions on that
|
|
34
|
+
chat kept every answer in the first five, and the reranker does about 40% less work.
|
|
35
|
+
|
|
8
36
|
## 0.446.0 — 26.10.2026
|
|
9
37
|
|
|
10
38
|
### Fixed
|
package/README.md
CHANGED
|
@@ -130,8 +130,9 @@ code and its document must agree. A search plan that reads an index once per mes
|
|
|
130
130
|
pattern can hang the search.
|
|
131
131
|
- **Presets for what people actually look for.** `preset:password`, `api-key`, `card`, `phone`, `email`, `url` and
|
|
132
132
|
seven more find candidates, labelled as candidates.
|
|
133
|
-
- **Search by meaning, opt in.** Conversations are split into chunks and embedded locally by
|
|
134
|
-
|
|
133
|
+
- **Search by meaning, opt in.** Conversations are split into chunks and embedded locally by ONNX Runtime — native
|
|
134
|
+
on Linux (glibc) and Apple-silicon Macs once `models download` has fetched it, WebAssembly elsewhere, on Node and
|
|
135
|
+
Bun alike — e5-small by default, or any OpenAI-compatible API with your own key, after
|
|
135
136
|
you see the cost. Results by meaning and by words are merged by reciprocal rank fusion.
|
|
136
137
|
- **The archive and the server together.** `--backend both` also asks the messenger's own search; its hits are saved
|
|
137
138
|
and filtered by the same query, so nothing is counted twice. `--thread` brings the reply thread around a hit.
|
package/dist/embeddings/embed.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { availableParallelism } from "node:os";
|
|
3
3
|
import { basename, join } from "node:path";
|
|
4
4
|
import { filesPresent, sharedModelsDirectory } from "../speech/install.js";
|
|
5
|
+
import { onnxRuntime } from "./onnx-runtime.js";
|
|
5
6
|
export const textModelsDirectory = (env = process.env) => sharedModelsDirectory("text", env);
|
|
6
7
|
export const placedText = (model, directory) => model.files.map((file) => [file, join(directory, model.id, file.name)]);
|
|
7
8
|
export const isTextModelInstalled = (model, directory) => filesPresent(placedText(model, directory));
|
|
@@ -122,13 +123,29 @@ export const meanOf = (data, tokens, dims) => {
|
|
|
122
123
|
};
|
|
123
124
|
/** A model's tokenizer and ONNX session, from its folder. */
|
|
124
125
|
export const loadModel = async (model, directory, threads) => {
|
|
125
|
-
const [{ Tokenizer },
|
|
126
|
+
const [{ Tokenizer }, runtime] = await Promise.all([import("@huggingface/tokenizers"), onnxRuntime()]);
|
|
126
127
|
const path = (name) => join(directory, model.id, name);
|
|
127
128
|
const tokenizer = new Tokenizer(JSON.parse(readFileSync(path("tokenizer.json"), "utf8")), JSON.parse(readFileSync(path("tokenizer_config.json"), "utf8")));
|
|
128
|
-
|
|
129
|
-
const
|
|
129
|
+
const bytes = readFileSync(path(model.onnx));
|
|
130
|
+
const external = model.externalData
|
|
130
131
|
? { externalData: [{ path: basename(model.externalData), data: readFileSync(path(model.externalData)) }] }
|
|
131
|
-
: {}
|
|
132
|
+
: {};
|
|
133
|
+
if (runtime.native) {
|
|
134
|
+
try {
|
|
135
|
+
const session = await runtime.ort.InferenceSession.create(bytes, {
|
|
136
|
+
...external,
|
|
137
|
+
intraOpNumThreads: threads,
|
|
138
|
+
interOpNumThreads: 1,
|
|
139
|
+
});
|
|
140
|
+
return { ort: runtime.ort, tokenizer, session };
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// A model the native runtime will not open still runs through WebAssembly.
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const ort = await import("@wirecat/cli-core-onnx");
|
|
147
|
+
ort.env.wasm.numThreads = threads;
|
|
148
|
+
const session = await ort.InferenceSession.create(bytes, external);
|
|
132
149
|
return { ort, tokenizer, session };
|
|
133
150
|
};
|
|
134
151
|
export const openEmbedder = async (model, directory, { threads = defaultThreads() } = {}) => {
|
|
@@ -94,7 +94,7 @@ export const RERANK_MODELS = [
|
|
|
94
94
|
languages: "about 100 languages, Russian and English among them",
|
|
95
95
|
licence: "Apache-2.0",
|
|
96
96
|
maxTokens: 512,
|
|
97
|
-
// The x86 name is the quantizer's target;
|
|
97
|
+
// The x86 name is the quantizer's target; WebAssembly and native ONNX Runtime both run it on any processor.
|
|
98
98
|
onnx: "onnx/model_quint8_avx2.onnx",
|
|
99
99
|
files: [
|
|
100
100
|
{
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Fetch } from "../speech/install.js";
|
|
2
|
+
import type { ModelFile } from "../speech/models.js";
|
|
3
|
+
export declare const ONNX_RUNTIME_VERSION = "1.30.0";
|
|
4
|
+
/** The library of Microsoft's release, in its archive, and the name the binding looks for beside itself. */
|
|
5
|
+
export interface NativeRuntime {
|
|
6
|
+
readonly target: string;
|
|
7
|
+
readonly archive: ModelFile;
|
|
8
|
+
readonly member: string;
|
|
9
|
+
readonly library: {
|
|
10
|
+
name: string;
|
|
11
|
+
sha256: string;
|
|
12
|
+
bytes: number;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export declare const nativeRuntimeFor: ({ platform, arch, musl, }?: {
|
|
16
|
+
platform?: string;
|
|
17
|
+
arch?: string;
|
|
18
|
+
musl?: boolean;
|
|
19
|
+
}) => NativeRuntime | undefined;
|
|
20
|
+
export declare const runtimeFolder: (runtime: NativeRuntime, env?: NodeJS.ProcessEnv) => string;
|
|
21
|
+
/**
|
|
22
|
+
* The library this system's binding links to, out of Microsoft's release archive, checked against its own sha256
|
|
23
|
+
* and placed beside a copy of the binding, which loads it from its own folder. Only from `models download`: a search
|
|
24
|
+
* never downloads. `undefined` when this system runs models through WebAssembly only.
|
|
25
|
+
*/
|
|
26
|
+
export declare const installNativeRuntime: ({ env, progress, fetch, runtime, binding, }?: {
|
|
27
|
+
env?: NodeJS.ProcessEnv;
|
|
28
|
+
progress?: (line: string) => void;
|
|
29
|
+
fetch?: Fetch;
|
|
30
|
+
runtime?: NativeRuntime;
|
|
31
|
+
binding?: string;
|
|
32
|
+
}) => Promise<string | undefined>;
|
|
33
|
+
export type Ort = typeof import("@wirecat/cli-core-onnx");
|
|
34
|
+
export interface OnnxRuntime {
|
|
35
|
+
readonly ort: Ort;
|
|
36
|
+
readonly native: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The native runtime when `models download` placed it for this system and it loads, else WebAssembly. One per
|
|
40
|
+
* process: a binding, once loaded, stays.
|
|
41
|
+
*/
|
|
42
|
+
export declare const onnxRuntime: (env?: NodeJS.ProcessEnv) => Promise<OnnxRuntime>;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { CliError } from "@wirecat/cli-base";
|
|
5
|
+
import { gunzipSync } from "fflate";
|
|
6
|
+
import { installFiles, megabytes, sharedModelsDirectory } from "../speech/install.js";
|
|
7
|
+
export const ONNX_RUNTIME_VERSION = "1.30.0";
|
|
8
|
+
const release = (name, sha256, bytes) => ({
|
|
9
|
+
name,
|
|
10
|
+
url: `https://github.com/microsoft/onnxruntime/releases/download/v${ONNX_RUNTIME_VERSION}/${name}`,
|
|
11
|
+
sha256,
|
|
12
|
+
bytes,
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* The systems the ONNX workflow runs natively; everywhere else, and on musl, the models run through WebAssembly.
|
|
16
|
+
* Windows too: its binding needs the DirectML build of onnxruntime.dll, which Microsoft's release does not carry.
|
|
17
|
+
*/
|
|
18
|
+
const NATIVE = {
|
|
19
|
+
"linux-x64": {
|
|
20
|
+
archive: release("onnxruntime-linux-x64-1.30.0.tgz", "a5ed5a3cac51fbb2e90da632ae43d19212faaa20e76484e62bcb7c23ddb3b3fd", 11_306_877),
|
|
21
|
+
member: "onnxruntime-linux-x64-1.30.0/lib/libonnxruntime.so.1.30.0",
|
|
22
|
+
library: {
|
|
23
|
+
name: "libonnxruntime.so.1",
|
|
24
|
+
sha256: "245a6f8c38127551057a1cd1ffd59f0a186a227ade4f3492dea2494eb565542e",
|
|
25
|
+
bytes: 28_985_152,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
"linux-arm64": {
|
|
29
|
+
archive: release("onnxruntime-linux-aarch64-1.30.0.tgz", "e16a27a8ed330bbc698df7330b0cf56e722f354e3bcc92118682c74ef3c3e3da", 10_269_495),
|
|
30
|
+
member: "onnxruntime-linux-aarch64-1.30.0/lib/libonnxruntime.so.1.30.0",
|
|
31
|
+
library: {
|
|
32
|
+
name: "libonnxruntime.so.1",
|
|
33
|
+
sha256: "64e903a43a041240fd6bcffe0ac6d4fea47ef87bf24b9d097801bd00a9612a4b",
|
|
34
|
+
bytes: 25_135_496,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
"darwin-arm64": {
|
|
38
|
+
archive: release("onnxruntime-osx-arm64-1.30.0.tgz", "6ebb5062a934537c352937821f9fe9718e7de1a2db1122a93dd363ffd53a7012", 42_373_116),
|
|
39
|
+
member: "onnxruntime-osx-arm64-1.30.0/lib/libonnxruntime.1.30.0.dylib",
|
|
40
|
+
library: {
|
|
41
|
+
name: "libonnxruntime.1.dylib",
|
|
42
|
+
sha256: "bcc9110f9d638a119de2db7afb3ba9a1da8085f0cb3401e1c48ae1caf450b6fa",
|
|
43
|
+
bytes: 43_879_424,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
const onMusl = () => {
|
|
48
|
+
try {
|
|
49
|
+
return readFileSync("/proc/self/maps", "utf8").includes("ld-musl");
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
export const nativeRuntimeFor = ({ platform = process.platform, arch = process.arch, musl = platform === "linux" && onMusl(), } = {}) => {
|
|
56
|
+
const target = `${platform}-${arch}`;
|
|
57
|
+
const runtime = NATIVE[target];
|
|
58
|
+
return runtime && !musl ? { target, ...runtime } : undefined;
|
|
59
|
+
};
|
|
60
|
+
export const runtimeFolder = (runtime, env = process.env) => join(sharedModelsDirectory("runtime", env), `onnxruntime-${ONNX_RUNTIME_VERSION}-${runtime.target}`);
|
|
61
|
+
const BINDING = "onnxruntime_binding.node";
|
|
62
|
+
const sizeOf = (path) => {
|
|
63
|
+
try {
|
|
64
|
+
return statSync(path).size;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const isInstalled = (runtime, folder) => sizeOf(join(folder, runtime.library.name)) === runtime.library.bytes && sizeOf(join(folder, BINDING)) !== undefined;
|
|
71
|
+
/**
|
|
72
|
+
* The library this system's binding links to, out of Microsoft's release archive, checked against its own sha256
|
|
73
|
+
* and placed beside a copy of the binding, which loads it from its own folder. Only from `models download`: a search
|
|
74
|
+
* never downloads. `undefined` when this system runs models through WebAssembly only.
|
|
75
|
+
*/
|
|
76
|
+
export const installNativeRuntime = async ({ env = process.env, progress = () => { }, fetch, runtime = nativeRuntimeFor(), binding, } = {}) => {
|
|
77
|
+
if (!runtime)
|
|
78
|
+
return undefined;
|
|
79
|
+
const source = binding ?? (await import("@wirecat/cli-core-onnx")).nativeBindingFor();
|
|
80
|
+
if (!source)
|
|
81
|
+
return undefined;
|
|
82
|
+
const folder = runtimeFolder(runtime, env);
|
|
83
|
+
if (isInstalled(runtime, folder))
|
|
84
|
+
return folder;
|
|
85
|
+
progress(`ONNX Runtime ${ONNX_RUNTIME_VERSION} for ${runtime.target}: ${megabytes(runtime.archive.bytes)} from Microsoft`);
|
|
86
|
+
const parent = sharedModelsDirectory("runtime", env);
|
|
87
|
+
mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
88
|
+
const staging = mkdtempSync(join(parent, ".onnxruntime-"));
|
|
89
|
+
try {
|
|
90
|
+
const archive = join(staging, runtime.archive.name);
|
|
91
|
+
await installFiles([[runtime.archive, archive]], { ...(fetch ? { fetch } : {}), progress });
|
|
92
|
+
const library = tarMember(gunzipSync(readFileSync(archive)), runtime.member);
|
|
93
|
+
rmSync(archive);
|
|
94
|
+
const sha256 = library && createHash("sha256").update(library).digest("hex");
|
|
95
|
+
if (!library || library.length !== runtime.library.bytes || sha256 !== runtime.library.sha256)
|
|
96
|
+
throw new CliError("invalid_response", `${runtime.archive.name} does not hold the ${runtime.library.name} this version expects — nothing was installed`);
|
|
97
|
+
writeFileSync(join(staging, runtime.library.name), library, { mode: 0o755 });
|
|
98
|
+
copyFileSync(source, join(staging, BINDING));
|
|
99
|
+
rmSync(folder, { recursive: true, force: true });
|
|
100
|
+
renameSync(staging, folder);
|
|
101
|
+
return folder;
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
rmSync(staging, { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
/** One regular file of a ustar archive, by its path: PAX and GNU long names, and a leading `./`, are understood. */
|
|
108
|
+
const tarMember = (tar, member) => {
|
|
109
|
+
const text = (from, length) => new TextDecoder().decode(tar.subarray(from, from + length)).replace(/\0.*$/s, "");
|
|
110
|
+
const plain = (path) => path.replace(/^\.\//, "");
|
|
111
|
+
let longName;
|
|
112
|
+
for (let offset = 0; offset + 512 <= tar.length;) {
|
|
113
|
+
const name = text(offset, 100);
|
|
114
|
+
if (!name)
|
|
115
|
+
return undefined;
|
|
116
|
+
const size = Number.parseInt(text(offset + 124, 12).trim() || "0", 8);
|
|
117
|
+
const type = text(offset + 156, 1);
|
|
118
|
+
const prefix = text(offset + 345, 155);
|
|
119
|
+
const data = tar.subarray(offset + 512, offset + 512 + size);
|
|
120
|
+
offset += 512 + Math.ceil(size / 512) * 512;
|
|
121
|
+
if (type === "x") {
|
|
122
|
+
longName = /(?:^|\n)\d+ path=([^\n]*)\n/.exec(new TextDecoder().decode(data))?.[1] ?? longName;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (type === "L") {
|
|
126
|
+
longName = new TextDecoder().decode(data).replace(/\0.*$/s, "");
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const path = plain(longName ?? (prefix ? `${prefix}/${name}` : name));
|
|
130
|
+
longName = undefined;
|
|
131
|
+
if (path === plain(member) && (type === "0" || type === ""))
|
|
132
|
+
return data;
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
};
|
|
136
|
+
const opened = new Map();
|
|
137
|
+
/**
|
|
138
|
+
* The native runtime when `models download` placed it for this system and it loads, else WebAssembly. One per
|
|
139
|
+
* process: a binding, once loaded, stays.
|
|
140
|
+
*/
|
|
141
|
+
export const onnxRuntime = (env = process.env) => {
|
|
142
|
+
const runtime = nativeRuntimeFor();
|
|
143
|
+
const folder = runtime ? runtimeFolder(runtime, env) : "";
|
|
144
|
+
let found = opened.get(folder);
|
|
145
|
+
if (!found) {
|
|
146
|
+
found = (async () => {
|
|
147
|
+
const wasm = await import("@wirecat/cli-core-onnx");
|
|
148
|
+
if (runtime && isInstalled(runtime, folder)) {
|
|
149
|
+
try {
|
|
150
|
+
return { ort: wasm.openNative(join(folder, BINDING)), native: true };
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// A library that does not load on this system: WebAssembly runs the same models, slower.
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return { ort: wasm, native: false };
|
|
157
|
+
})();
|
|
158
|
+
opened.set(folder, found);
|
|
159
|
+
}
|
|
160
|
+
return found;
|
|
161
|
+
};
|
|
@@ -54,3 +54,11 @@ export declare const searchDiscovery: (store: MessageStore, account: AccountKey,
|
|
|
54
54
|
export declare const nearEligible: (store: MessageStore, account: AccountKey, request: SearchQuery, messenger: Partial<Pick<MessengerTraits, "savedChatId" | "app" | "history" | "provider">>, ranked: MeaningRanked, scope: string) => Promise<(FoundMessage & {
|
|
55
55
|
cosine: number;
|
|
56
56
|
})[]>;
|
|
57
|
+
/** What the reranker reads of a hit, as the benchmark gave it: its reply parent first, each line headed by where, who and when. */
|
|
58
|
+
type Said = {
|
|
59
|
+
senderName: string | null;
|
|
60
|
+
timestamp: string | null;
|
|
61
|
+
text: string;
|
|
62
|
+
};
|
|
63
|
+
export declare const pairInput: (hit: FoundMessage, parent: Said | null | undefined) => string;
|
|
64
|
+
export {};
|
|
@@ -289,9 +289,14 @@ const fusedWithMeaning = (words, near, described) => {
|
|
|
289
289
|
});
|
|
290
290
|
return [...merged.values()].sort((a, b) => b.rrf - a.rrf).map(({ hit, rrf }) => ({ ...hit, score: rrf }));
|
|
291
291
|
};
|
|
292
|
-
|
|
292
|
+
/**
|
|
293
|
+
* Only the start of the parent: whole, a long one filled the model's 512 tokens and cut off the hit it was scoring
|
|
294
|
+
* (18 of 119 replies in one real chat). Cut to 300 characters, every answer on the six known questions stayed.
|
|
295
|
+
*/
|
|
296
|
+
const PARENT_CHARACTERS = 300;
|
|
297
|
+
export const pairInput = (hit, parent) => {
|
|
293
298
|
const line = (m) => `[${hit.chatTitle ?? hit.chatId} · ${m.senderName ?? "unknown"} · ${m.timestamp?.slice(0, 10) ?? "undated"}] ${m.text}`;
|
|
294
|
-
return (parent ? [parent, hit] : [hit]).
|
|
299
|
+
return (parent ? [line(parent).slice(0, PARENT_CHARACTERS), line(hit)] : [line(hit)]).join("\n");
|
|
295
300
|
};
|
|
296
301
|
const accountOfHit = (hit) => {
|
|
297
302
|
const { provider, account } = parseLocator(hit.locator);
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { isTextModelInstalled, placedText, textModelsDirectory } from "../embeddings/embed.js";
|
|
3
3
|
import { rerankModel, textModel } from "../embeddings/models.js";
|
|
4
|
+
import { installNativeRuntime } from "../embeddings/onnx-runtime.js";
|
|
4
5
|
import { imageModel, imageModelsDirectory, isImageModelInstalled, placedImage, } from "../images/picture/models.js";
|
|
5
6
|
import { installFiles, megabytes } from "../speech/install.js";
|
|
6
7
|
const bytesOf = (model) => model.files.reduce((sum, file) => sum + file.bytes, 0);
|
|
8
|
+
/**
|
|
9
|
+
* The native ONNX Runtime beside the text models, where this system has one. It only makes them faster, so a
|
|
10
|
+
* failed download says why and the model is still installed, to run through WebAssembly.
|
|
11
|
+
*/
|
|
12
|
+
const nativeRuntime = async ({ env, progress = () => { } }) => {
|
|
13
|
+
try {
|
|
14
|
+
await installNativeRuntime({ env, progress });
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
progress(`ONNX Runtime not installed (${error instanceof Error ? error.message : String(error)}); models run through WebAssembly`);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
7
20
|
/**
|
|
8
21
|
* The text embedding model into the shared models folder, checked against the sha256 this version expects and
|
|
9
22
|
* loaded once on a word. Nothing is downloaded when it is already there; it is still loaded.
|
|
@@ -18,6 +31,7 @@ export const downloadTextModel = async (id, { env, progress = () => { }, acceptT
|
|
|
18
31
|
progress(`${model.id}: ${megabytes(bytesOf(model))} from Hugging Face`);
|
|
19
32
|
await installFiles(placedText(model, directory), { progress });
|
|
20
33
|
}
|
|
34
|
+
await nativeRuntime({ env, progress });
|
|
21
35
|
progress(`${model.id}: loading it once to check it works`);
|
|
22
36
|
const { openEmbedder } = await import("../embeddings/embed.js");
|
|
23
37
|
const embedder = await openEmbedder(model, directory);
|
|
@@ -39,6 +53,7 @@ export const downloadRerankModel = async (id, { env, progress = () => { } }) =>
|
|
|
39
53
|
progress(`${model.id}: ${megabytes(bytesOf(model))} from Hugging Face`);
|
|
40
54
|
await installFiles(placedText(model, directory), { progress });
|
|
41
55
|
}
|
|
56
|
+
await nativeRuntime({ env, progress });
|
|
42
57
|
progress(`${model.id}: loading it once to check it works`);
|
|
43
58
|
const { openReranker } = await import("../embeddings/rerank.js");
|
|
44
59
|
const reranker = await openReranker(model, directory);
|
package/dist/speech/install.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ModelFile, type SpeechModel } from "./models.js";
|
|
2
2
|
/** Shared by every CLI of the family, whatever it talks to: a model is hundreds of MB and is downloaded once. */
|
|
3
|
-
export declare const sharedModelsDirectory: (kind: "audio" | "text" | "image", env?: NodeJS.ProcessEnv) => string;
|
|
3
|
+
export declare const sharedModelsDirectory: (kind: "audio" | "text" | "image" | "runtime", env?: NodeJS.ProcessEnv) => string;
|
|
4
4
|
export declare const modelsDirectory: (env?: NodeJS.ProcessEnv) => string;
|
|
5
5
|
export declare const modelPath: (directory: string, model: SpeechModel) => (name: string) => string;
|
|
6
6
|
export declare const vadPath: (directory: string) => string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.448.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
"@modelcontextprotocol/server": "^2.4.0",
|
|
177
177
|
"@resvg/resvg-js": "2.6.2",
|
|
178
178
|
"@valibot/to-json-schema": "1.8.0",
|
|
179
|
-
"@wirecat/cli-core-onnx": "1.0
|
|
179
|
+
"@wirecat/cli-core-onnx": "1.1.0",
|
|
180
180
|
"@wirecat/cli-core-sqlite": "1.1.0",
|
|
181
181
|
"@xmldom/xmldom": "0.9.12",
|
|
182
182
|
"chardet": "2.2.0",
|
package/parity.json
CHANGED
|
@@ -68,6 +68,9 @@
|
|
|
68
68
|
"value": "<model>",
|
|
69
69
|
"meaning": "only the stored answers this model gave"
|
|
70
70
|
},
|
|
71
|
+
"--answer-stdin": {
|
|
72
|
+
"meaning": "with --json and no terminal: print each question as a `needs` line on stderr and read its answer, one line, from stdin"
|
|
73
|
+
},
|
|
71
74
|
"--answerer": {
|
|
72
75
|
"value": "<person>",
|
|
73
76
|
"meaning": "stored name, alias, @username, ID or scoped person locator; ambiguous names require a choice; repeat it for more"
|
|
@@ -4193,6 +4196,10 @@
|
|
|
4193
4196
|
"session start": {
|
|
4194
4197
|
"in": "all",
|
|
4195
4198
|
"options": {
|
|
4199
|
+
"--answer-stdin": {
|
|
4200
|
+
"in": ["tg", "max"],
|
|
4201
|
+
"reason": "only tg and max ask questions during sign-in (app keys, codes, a 2FA or cloud password); WhatsApp's QR sign-in asks nothing, and its phone login is not ported to signInSteps yet"
|
|
4202
|
+
},
|
|
4196
4203
|
"--app": {
|
|
4197
4204
|
"in": ["tg"],
|
|
4198
4205
|
"reason": "Telegram asks each client for its own app from my.telegram.org; MAX has no such step; WhatsApp has no application registration"
|