@bendyline/gezel-inference 1.0.0 → 1.0.2
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 +90 -8
- package/dist/cache/adapter.d.ts +7 -0
- package/dist/cache/controller.d.ts +12 -0
- package/dist/cache/controller.js +24 -0
- package/dist/catalog/chat-models/bt/btl4-35b-q4/manifest.json +1 -0
- package/dist/catalog/chat-models/gl/glm-5.2-754b-q2/manifest.json +1 -0
- package/dist/catalog/chat-models/index.json +1 -1
- package/dist/catalog/chat-models/lf/lfm2.5-24b-a2b-q4/manifest.json +122 -0
- package/dist/catalog/chat-models/lf/lfm2.5-24b-a2b-q4/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/chat-models/mi/minicpm-v4.7-35b-a3b-q4/manifest.json +185 -0
- package/dist/catalog/chat-models/mi/minicpm-v4.7-35b-a3b-q4/versions/1.0.0/manifest.json +94 -0
- package/dist/catalog/chat-models/mi/minicpm5-1b-q4/manifest.json +1 -0
- package/dist/catalog/chat-models/mi/minicpm5-2b-q4/manifest.json +1 -0
- package/dist/catalog/chat-models/qw/qwen3.5-0.8b-q4/manifest.json +1 -0
- package/dist/catalog/chat-models/raw-index.json +1 -1
- package/dist/catalog/provenance.json +1 -1
- package/dist/engines/electron-native-reuse.d.ts +9 -0
- package/dist/engines/electron-native-reuse.js +146 -45
- package/dist/engines/native-file-manifest.json +404 -404
- package/dist/engines/native-manifest.d.ts +11 -3
- package/dist/engines/native-manifest.js +48 -18
- package/dist/http/openai-compat/apple-model.d.ts +2 -0
- package/dist/http/openai-compat/apple-model.js +21 -1
- package/dist/http/openai-compat/chat-target.d.ts +12 -0
- package/dist/http/openai-compat/chat-target.js +27 -1
- package/dist/http/openai-compat/engine-failure.d.ts +25 -0
- package/dist/http/openai-compat/engine-failure.js +76 -0
- package/dist/http/openai-compat/responses-streaming.js +2 -1
- package/dist/http/openai-compat/responses-translate.d.ts +1 -1
- package/dist/http/openai-compat/translate.d.ts +1 -1
- package/dist/http/routes/engines.js +44 -3
- package/dist/http/routes/v1-chat.js +30 -8
- package/dist/http/routes/v1-models.d.ts +6 -0
- package/dist/http/routes/v1-models.js +137 -30
- package/dist/http/routes/v1-responses.js +16 -3
- package/dist/in-flight-requests.d.ts +19 -0
- package/dist/in-flight-requests.js +34 -0
- package/dist/inference-service.js +3 -3
- package/dist/knowledge/catalog-host.d.ts +9 -1
- package/dist/knowledge/catalog-host.js +2 -0
- package/dist/knowledge/manager.d.ts +14 -0
- package/dist/knowledge/manager.js +53 -6
- package/dist/knowledge/search-worker.d.ts +1 -1
- package/dist/knowledge/spatial-query.d.ts +2 -3
- package/dist/knowledge/spatial-query.js +12 -6
- package/dist/knowledge/worker-host.js +2 -0
- package/dist/memory/embed-core.d.ts +16 -0
- package/dist/memory/embed-core.js +31 -0
- package/dist/memory/embed-worker.d.ts +7 -3
- package/dist/memory/embed-worker.js +13 -4
- package/dist/memory/embeddings.d.ts +17 -2
- package/dist/memory/embeddings.js +36 -7
- package/dist/models/ensure.d.ts +2 -0
- package/dist/models/ensure.js +9 -0
- package/dist/perf/responsiveness.d.ts +14 -1
- package/dist/perf/responsiveness.js +77 -7
- package/dist/providers/llama-cpp/backend-health.d.ts +30 -0
- package/dist/providers/llama-cpp/backend-health.js +64 -0
- package/dist/providers/llama-cpp/backend-quarantine.d.ts +40 -0
- package/dist/providers/llama-cpp/backend-quarantine.js +100 -0
- package/dist/providers/llama-cpp/build-provider.js +140 -118
- package/dist/providers/llama-cpp/cache-adapter.d.ts +12 -0
- package/dist/providers/llama-cpp/cache-adapter.js +36 -1
- package/dist/providers/llama-cpp/devices.d.ts +11 -2
- package/dist/providers/llama-cpp/devices.js +10 -4
- package/dist/providers/llama-cpp/engine-flags.d.ts +0 -18
- package/dist/providers/llama-cpp/engine-flags.js +12 -18
- package/dist/providers/llama-cpp/gguf-metadata-async.js +7 -0
- package/dist/providers/llama-cpp/gguf-metadata.js +20 -3
- package/dist/providers/llama-cpp/gpu-startup-watch.d.ts +41 -0
- package/dist/providers/llama-cpp/gpu-startup-watch.js +58 -0
- package/dist/providers/llama-cpp/models.d.ts +6 -0
- package/dist/providers/llama-cpp/models.js +19 -1
- package/dist/providers/llama-cpp/offload-planner.d.ts +3 -18
- package/dist/providers/llama-cpp/offload-planner.js +3 -14
- package/dist/providers/llama-cpp/provider.d.ts +8 -0
- package/dist/providers/llama-cpp/provider.js +4 -1
- package/dist/providers/mlx/provider.js +14 -11
- package/dist/providers/mlx/python/batch_processors.py +26 -0
- package/dist/providers/mlx/python/gezel_mlx_server.py +88 -20
- package/dist/providers/mlx/python/qwen4_ple.py +9 -2
- package/dist/providers/mlx/python/response_format.py +374 -0
- package/dist/providers/mlx/python/spec_decode.py +5 -0
- package/dist/providers/mlx/python/think_budget.py +35 -1
- package/dist/providers/mlx/python/tool_grammar.py +62 -3
- package/dist/providers/mlx/reasoning-stream.d.ts +20 -2
- package/dist/providers/mlx/reasoning-stream.js +77 -16
- package/dist/providers/mlx/runtime-diagnostics.d.ts +16 -0
- package/dist/providers/mlx/runtime-diagnostics.js +29 -2
- package/dist/providers/native/capacity-broker.d.ts +51 -10
- package/dist/providers/native/capacity-broker.js +80 -9
- package/dist/providers/native/capacity-budget.d.ts +35 -3
- package/dist/providers/native/capacity-budget.js +49 -5
- package/dist/providers/native/device-capacity.d.ts +1 -1
- package/dist/providers/native/device-capacity.js +23 -5
- package/dist/providers/native/local-engine-runtime.js +15 -13
- package/dist/providers/native/measured-budget.js +2 -0
- package/dist/providers/native/supervisor.d.ts +25 -0
- package/dist/providers/native/supervisor.js +59 -7
- package/dist/remotes/identity.d.ts +1 -0
- package/dist/remotes/identity.js +6 -0
- package/dist/remotes/pinned-fetch.d.ts +2 -0
- package/dist/remotes/pinned-fetch.js +30 -1
- package/dist/remotes/types.d.ts +14 -0
- package/dist/runtime-lock.js +1 -1
- package/dist/system/amdgpu-adapters.d.ts +18 -0
- package/dist/system/amdgpu-adapters.js +55 -0
- package/dist/system/gpu-carve-out.d.ts +84 -0
- package/dist/system/gpu-carve-out.js +111 -0
- package/dist/system/memory.d.ts +17 -1
- package/dist/system/memory.js +105 -14
- package/dist/transformers-cache.d.ts +2 -0
- package/dist/transformers-cache.js +6 -3
- package/dist/utils/service-worker-entry.d.ts +4 -0
- package/dist/utils/service-worker-entry.js +4 -0
- package/package.json +14 -10
|
@@ -35,6 +35,15 @@ export interface ElectronNativeReuseOptions {
|
|
|
35
35
|
* this false so their parent app is also signature/notarization checked.
|
|
36
36
|
*/
|
|
37
37
|
allowStandaloneMacPayload?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Remember each pinned file that hashed to its pin, with its fingerprint
|
|
40
|
+
* (see file-fingerprint.ts in core), so a later call re-hashes only files
|
|
41
|
+
* whose metadata moved: `nativePayloadVerificationPath(home)`. Everything
|
|
42
|
+
* else still runs on every call, including the listing that rejects a
|
|
43
|
+
* loadable file the release does not pin. Keep it somewhere no easier to
|
|
44
|
+
* write than the payload itself: whoever can edit it can vouch for any bytes.
|
|
45
|
+
*/
|
|
46
|
+
cacheFile?: string;
|
|
38
47
|
}
|
|
39
48
|
/**
|
|
40
49
|
* Adopt Electron's extracted native payload, or an explicitly allowed
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
3
|
-
import { lstat, readdir, readlink } from 'node:fs/promises';
|
|
4
|
-
import { basename, join, posix, relative, resolve, sep } from 'node:path';
|
|
5
|
-
import { resolvePlatformKey } from '@bendyline/gezel/native';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { lstat, mkdir, open, readFile, readdir, readlink, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { basename, dirname, join, posix, relative, resolve, sep } from 'node:path';
|
|
5
|
+
import { fileFingerprint, resolvePlatformKey, sameFileFingerprint, } from '@bendyline/gezel/native';
|
|
6
6
|
import pinnedManifestJson from './native-file-manifest.json' with { type: 'json' };
|
|
7
7
|
import { effectiveEngineRelease } from './native-manifest.js';
|
|
8
8
|
import { BENDYLINE_APPLE_TEAM_ID, BENDYLINE_PUBLISHER, verifyCodeSignature } from './signature.js';
|
|
@@ -52,38 +52,89 @@ export async function verifyNativeBinaries(opts) {
|
|
|
52
52
|
reason: `release ${release} has no pinned files for ${baseKey}`,
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
|
+
const cache = opts.cacheFile ? await readHashCache(opts.cacheFile) : null;
|
|
56
|
+
const pinnedCount = platformEntries.reduce((n, [, pin]) => n + Object.keys(pin.files).length, 0);
|
|
55
57
|
const failures = [];
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
58
|
+
try {
|
|
59
|
+
for (const candidate of opts.candidates) {
|
|
60
|
+
const startedAt = Date.now();
|
|
61
|
+
const hashedBefore = cache?.hashed ?? 0;
|
|
62
|
+
try {
|
|
63
|
+
await verifyCandidate({
|
|
64
|
+
root: candidate,
|
|
65
|
+
platform,
|
|
66
|
+
entries: platformEntries,
|
|
67
|
+
verifySignature: opts.verifySignature ?? verifyCodeSignature,
|
|
68
|
+
allowStandaloneMacPayload: opts.allowStandaloneMacPayload === true,
|
|
69
|
+
cache,
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
reused: true,
|
|
73
|
+
nativeBinDir: resolve(candidate),
|
|
74
|
+
// The elapsed time is load-bearing diagnostics, not decoration: this
|
|
75
|
+
// is pre-bind boot work whose cost scales with the payload, and on a
|
|
76
|
+
// CUDA host it is the largest single read the daemon ever does at
|
|
77
|
+
// startup. Without it in the log there is nothing to attribute a slow
|
|
78
|
+
// launch to.
|
|
79
|
+
reason: `verified ${opts.allowStandaloneMacPayload ? 'standalone' : 'Electron'} native release ${release} in ${((Date.now() - startedAt) / 1000).toFixed(1)}s${cache ? ` (${cache.hashed - hashedBefore} of ${pinnedCount} files hashed)` : ''}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
failures.push(`${candidate}: ${error instanceof Error ? error.message : String(error)}`);
|
|
84
|
+
}
|
|
76
85
|
}
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
return {
|
|
87
|
+
reused: false,
|
|
88
|
+
reason: failures.length > 0
|
|
89
|
+
? failures.join('; ')
|
|
90
|
+
: 'no Electron native payload candidate was installed',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
if (cache)
|
|
95
|
+
await saveHashCache(cache);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function readHashCache(path) {
|
|
99
|
+
const known = new Map();
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(await readFile(path, 'utf8'));
|
|
102
|
+
if (isRecord(parsed) && parsed.schema === 1 && isRecord(parsed.files)) {
|
|
103
|
+
for (const [file, record] of Object.entries(parsed.files)) {
|
|
104
|
+
// The fingerprint's shape is checked where it is compared.
|
|
105
|
+
if (isRecord(record) && typeof record.sha256 === 'string') {
|
|
106
|
+
known.set(file, record);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
79
109
|
}
|
|
80
110
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
111
|
+
catch {
|
|
112
|
+
// Missing or unreadable: this call hashes everything and writes it anew.
|
|
113
|
+
}
|
|
114
|
+
return { path, known, examined: new Set(), confirmed: new Map(), hashed: 0 };
|
|
115
|
+
}
|
|
116
|
+
async function saveHashCache(cache) {
|
|
117
|
+
const dropped = [...cache.examined].some((file) => cache.known.has(file) && !cache.confirmed.has(file));
|
|
118
|
+
if (cache.hashed === 0 && !dropped)
|
|
119
|
+
return;
|
|
120
|
+
// Records this call never looked at belong to another candidate, which a
|
|
121
|
+
// dev launch verifies in a separate call against the same file. Dropping
|
|
122
|
+
// them would make the two calls erase each other and hash everything on
|
|
123
|
+
// every launch. They are kept while their files exist.
|
|
124
|
+
const files = {};
|
|
125
|
+
for (const [file, record] of cache.known) {
|
|
126
|
+
if (!cache.examined.has(file) && existsSync(file))
|
|
127
|
+
files[file] = record;
|
|
128
|
+
}
|
|
129
|
+
for (const [file, record] of cache.confirmed)
|
|
130
|
+
files[file] = record;
|
|
131
|
+
try {
|
|
132
|
+
await mkdir(dirname(cache.path), { recursive: true });
|
|
133
|
+
await writeFile(cache.path, `${JSON.stringify({ schema: 1, files }, null, 2)}\n`, 'utf8');
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Only a speedup: the next call hashes what it cannot find here.
|
|
137
|
+
}
|
|
87
138
|
}
|
|
88
139
|
async function verifyCandidate(opts) {
|
|
89
140
|
const root = resolve(opts.root);
|
|
@@ -115,11 +166,13 @@ async function verifyCandidate(opts) {
|
|
|
115
166
|
// Semantics are unchanged: any rejection still fails the candidate, and
|
|
116
167
|
// the first one to fail wins the message.
|
|
117
168
|
// The pin is the whole per-file check; see verifyNativeBinaries.
|
|
169
|
+
const hashBuffers = [];
|
|
118
170
|
await mapWithConcurrency(Object.entries(platformPin.files), 4, async ([relativePath, pin]) => {
|
|
119
171
|
if (!isSafeRelativePath(relativePath)) {
|
|
120
172
|
throw new Error(`unsafe pinned native path: ${relativePath}`);
|
|
121
173
|
}
|
|
122
174
|
const absolute = join(platformDir, ...relativePath.split('/'));
|
|
175
|
+
opts.cache?.examined.add(absolute);
|
|
123
176
|
const info = await lstat(absolute);
|
|
124
177
|
if (!info.isFile() || info.isSymbolicLink()) {
|
|
125
178
|
throw new Error(`pinned file is missing or not regular: ${platformKey}/${relativePath}`);
|
|
@@ -129,10 +182,26 @@ async function verifyCandidate(opts) {
|
|
|
129
182
|
if (info.size !== pin.sizeBytes) {
|
|
130
183
|
throw new Error(`size mismatch: ${platformKey}/${relativePath}`);
|
|
131
184
|
}
|
|
132
|
-
const
|
|
133
|
-
|
|
185
|
+
const want = pin.sha256.toLowerCase();
|
|
186
|
+
// Fingerprinted before hashing, so a file that changes mid-hash is
|
|
187
|
+
// recorded as it was and hashed again next time.
|
|
188
|
+
const fingerprint = opts.cache ? await fileFingerprint(absolute) : null;
|
|
189
|
+
const known = fingerprint ? opts.cache?.known.get(absolute) : undefined;
|
|
190
|
+
if (fingerprint &&
|
|
191
|
+
known?.sha256 === want &&
|
|
192
|
+
sameFileFingerprint(known.fingerprint, fingerprint)) {
|
|
193
|
+
opts.cache?.confirmed.set(absolute, known);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const actualSha = await sha256File(absolute, hashBuffers);
|
|
197
|
+
if (actualSha !== want) {
|
|
134
198
|
throw new Error(`sha256 mismatch: ${platformKey}/${relativePath}`);
|
|
135
199
|
}
|
|
200
|
+
if (opts.cache) {
|
|
201
|
+
opts.cache.hashed += 1;
|
|
202
|
+
if (fingerprint)
|
|
203
|
+
opts.cache.confirmed.set(absolute, { sha256: want, fingerprint });
|
|
204
|
+
}
|
|
136
205
|
});
|
|
137
206
|
for (const [relativePath, target] of Object.entries(platformPin.symlinks)) {
|
|
138
207
|
if (!isSafeRelativePath(relativePath)) {
|
|
@@ -203,18 +272,30 @@ async function listNativeEntries(root, platform) {
|
|
|
203
272
|
}
|
|
204
273
|
/**
|
|
205
274
|
* Run `worker` over `items` with at most `limit` in flight, rejecting with the
|
|
206
|
-
* first failure. Workers already in flight are allowed to settle
|
|
207
|
-
*
|
|
275
|
+
* first failure. Workers already in flight are allowed to settle, and only
|
|
276
|
+
* record files that really did match, so an abandoned hash is inert. No new
|
|
277
|
+
* item starts after a failure: without that, the other workers went on hashing
|
|
278
|
+
* the rest of a rejected payload (a stale 1.6 GB dev checkout) in the
|
|
279
|
+
* background while the next candidate was being verified.
|
|
208
280
|
*/
|
|
209
281
|
async function mapWithConcurrency(items, limit, worker) {
|
|
210
282
|
let next = 0;
|
|
283
|
+
let failed = false;
|
|
211
284
|
const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
212
285
|
for (;;) {
|
|
286
|
+
if (failed)
|
|
287
|
+
return;
|
|
213
288
|
const index = next++;
|
|
214
289
|
const item = items[index];
|
|
215
290
|
if (index >= items.length || item === undefined)
|
|
216
291
|
return;
|
|
217
|
-
|
|
292
|
+
try {
|
|
293
|
+
await worker(item);
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
failed = true;
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
218
299
|
}
|
|
219
300
|
});
|
|
220
301
|
await Promise.all(runners);
|
|
@@ -286,15 +367,35 @@ function enclosingMacApp(path) {
|
|
|
286
367
|
// daemon event loop (a 64 KB stream yields ~100k times and queues behind other
|
|
287
368
|
// work). Engines is a lower layer than models, so this mirrors models'
|
|
288
369
|
// MODEL_HASH_READ_BUFFER_BYTES rather than importing it.
|
|
370
|
+
//
|
|
371
|
+
// The buffer is reused, never one per chunk. A 16 MB `highWaterMark` stream
|
|
372
|
+
// allocates a fresh 16 MB buffer for every read, and in Electron's main process
|
|
373
|
+
// (where the dev supervisor runs this, and the embedded service after it) each
|
|
374
|
+
// allocation cost ~44 ms and left V8 in a ~20 s garbage-collection storm once
|
|
375
|
+
// the 1.7 GB Windows CUDA payload was done: the duckdb and MXC provisioning
|
|
376
|
+
// after it took 4.5 s and 13 s on the splash, against ~50 ms each in isolation.
|
|
377
|
+
// Reusing the buffer hashed the same payload in 1.1 s with no storm.
|
|
289
378
|
const HASH_READ_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
290
|
-
function sha256File(path) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
379
|
+
async function sha256File(path, buffers) {
|
|
380
|
+
const buffer = buffers.pop() ?? Buffer.allocUnsafe(HASH_READ_BUFFER_BYTES);
|
|
381
|
+
try {
|
|
382
|
+
const handle = await open(path, 'r');
|
|
383
|
+
try {
|
|
384
|
+
const hash = createHash('sha256');
|
|
385
|
+
for (;;) {
|
|
386
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
|
|
387
|
+
if (bytesRead === 0)
|
|
388
|
+
return hash.digest('hex');
|
|
389
|
+
hash.update(buffer.subarray(0, bytesRead));
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
finally {
|
|
393
|
+
await handle.close();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
finally {
|
|
397
|
+
buffers.push(buffer);
|
|
398
|
+
}
|
|
298
399
|
}
|
|
299
400
|
/** Legacy adoption API. New embedding hosts should use the side-effect-free verifier. */
|
|
300
401
|
export async function reuseVerifiedElectronNativeBinaries(opts) {
|