@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.
Files changed (116) hide show
  1. package/README.md +90 -8
  2. package/dist/cache/adapter.d.ts +7 -0
  3. package/dist/cache/controller.d.ts +12 -0
  4. package/dist/cache/controller.js +24 -0
  5. package/dist/catalog/chat-models/bt/btl4-35b-q4/manifest.json +1 -0
  6. package/dist/catalog/chat-models/gl/glm-5.2-754b-q2/manifest.json +1 -0
  7. package/dist/catalog/chat-models/index.json +1 -1
  8. package/dist/catalog/chat-models/lf/lfm2.5-24b-a2b-q4/manifest.json +122 -0
  9. package/dist/catalog/chat-models/lf/lfm2.5-24b-a2b-q4/versions/1.0.0/manifest.json +15 -0
  10. package/dist/catalog/chat-models/mi/minicpm-v4.7-35b-a3b-q4/manifest.json +185 -0
  11. package/dist/catalog/chat-models/mi/minicpm-v4.7-35b-a3b-q4/versions/1.0.0/manifest.json +94 -0
  12. package/dist/catalog/chat-models/mi/minicpm5-1b-q4/manifest.json +1 -0
  13. package/dist/catalog/chat-models/mi/minicpm5-2b-q4/manifest.json +1 -0
  14. package/dist/catalog/chat-models/qw/qwen3.5-0.8b-q4/manifest.json +1 -0
  15. package/dist/catalog/chat-models/raw-index.json +1 -1
  16. package/dist/catalog/provenance.json +1 -1
  17. package/dist/engines/electron-native-reuse.d.ts +9 -0
  18. package/dist/engines/electron-native-reuse.js +146 -45
  19. package/dist/engines/native-file-manifest.json +404 -404
  20. package/dist/engines/native-manifest.d.ts +11 -3
  21. package/dist/engines/native-manifest.js +48 -18
  22. package/dist/http/openai-compat/apple-model.d.ts +2 -0
  23. package/dist/http/openai-compat/apple-model.js +21 -1
  24. package/dist/http/openai-compat/chat-target.d.ts +12 -0
  25. package/dist/http/openai-compat/chat-target.js +27 -1
  26. package/dist/http/openai-compat/engine-failure.d.ts +25 -0
  27. package/dist/http/openai-compat/engine-failure.js +76 -0
  28. package/dist/http/openai-compat/responses-streaming.js +2 -1
  29. package/dist/http/openai-compat/responses-translate.d.ts +1 -1
  30. package/dist/http/openai-compat/translate.d.ts +1 -1
  31. package/dist/http/routes/engines.js +44 -3
  32. package/dist/http/routes/v1-chat.js +30 -8
  33. package/dist/http/routes/v1-models.d.ts +6 -0
  34. package/dist/http/routes/v1-models.js +137 -30
  35. package/dist/http/routes/v1-responses.js +16 -3
  36. package/dist/in-flight-requests.d.ts +19 -0
  37. package/dist/in-flight-requests.js +34 -0
  38. package/dist/inference-service.js +3 -3
  39. package/dist/knowledge/catalog-host.d.ts +9 -1
  40. package/dist/knowledge/catalog-host.js +2 -0
  41. package/dist/knowledge/manager.d.ts +14 -0
  42. package/dist/knowledge/manager.js +53 -6
  43. package/dist/knowledge/search-worker.d.ts +1 -1
  44. package/dist/knowledge/spatial-query.d.ts +2 -3
  45. package/dist/knowledge/spatial-query.js +12 -6
  46. package/dist/knowledge/worker-host.js +2 -0
  47. package/dist/memory/embed-core.d.ts +16 -0
  48. package/dist/memory/embed-core.js +31 -0
  49. package/dist/memory/embed-worker.d.ts +7 -3
  50. package/dist/memory/embed-worker.js +13 -4
  51. package/dist/memory/embeddings.d.ts +17 -2
  52. package/dist/memory/embeddings.js +36 -7
  53. package/dist/models/ensure.d.ts +2 -0
  54. package/dist/models/ensure.js +9 -0
  55. package/dist/perf/responsiveness.d.ts +14 -1
  56. package/dist/perf/responsiveness.js +77 -7
  57. package/dist/providers/llama-cpp/backend-health.d.ts +30 -0
  58. package/dist/providers/llama-cpp/backend-health.js +64 -0
  59. package/dist/providers/llama-cpp/backend-quarantine.d.ts +40 -0
  60. package/dist/providers/llama-cpp/backend-quarantine.js +100 -0
  61. package/dist/providers/llama-cpp/build-provider.js +140 -118
  62. package/dist/providers/llama-cpp/cache-adapter.d.ts +12 -0
  63. package/dist/providers/llama-cpp/cache-adapter.js +36 -1
  64. package/dist/providers/llama-cpp/devices.d.ts +11 -2
  65. package/dist/providers/llama-cpp/devices.js +10 -4
  66. package/dist/providers/llama-cpp/engine-flags.d.ts +0 -18
  67. package/dist/providers/llama-cpp/engine-flags.js +12 -18
  68. package/dist/providers/llama-cpp/gguf-metadata-async.js +7 -0
  69. package/dist/providers/llama-cpp/gguf-metadata.js +20 -3
  70. package/dist/providers/llama-cpp/gpu-startup-watch.d.ts +41 -0
  71. package/dist/providers/llama-cpp/gpu-startup-watch.js +58 -0
  72. package/dist/providers/llama-cpp/models.d.ts +6 -0
  73. package/dist/providers/llama-cpp/models.js +19 -1
  74. package/dist/providers/llama-cpp/offload-planner.d.ts +3 -18
  75. package/dist/providers/llama-cpp/offload-planner.js +3 -14
  76. package/dist/providers/llama-cpp/provider.d.ts +8 -0
  77. package/dist/providers/llama-cpp/provider.js +4 -1
  78. package/dist/providers/mlx/provider.js +14 -11
  79. package/dist/providers/mlx/python/batch_processors.py +26 -0
  80. package/dist/providers/mlx/python/gezel_mlx_server.py +88 -20
  81. package/dist/providers/mlx/python/qwen4_ple.py +9 -2
  82. package/dist/providers/mlx/python/response_format.py +374 -0
  83. package/dist/providers/mlx/python/spec_decode.py +5 -0
  84. package/dist/providers/mlx/python/think_budget.py +35 -1
  85. package/dist/providers/mlx/python/tool_grammar.py +62 -3
  86. package/dist/providers/mlx/reasoning-stream.d.ts +20 -2
  87. package/dist/providers/mlx/reasoning-stream.js +77 -16
  88. package/dist/providers/mlx/runtime-diagnostics.d.ts +16 -0
  89. package/dist/providers/mlx/runtime-diagnostics.js +29 -2
  90. package/dist/providers/native/capacity-broker.d.ts +51 -10
  91. package/dist/providers/native/capacity-broker.js +80 -9
  92. package/dist/providers/native/capacity-budget.d.ts +35 -3
  93. package/dist/providers/native/capacity-budget.js +49 -5
  94. package/dist/providers/native/device-capacity.d.ts +1 -1
  95. package/dist/providers/native/device-capacity.js +23 -5
  96. package/dist/providers/native/local-engine-runtime.js +15 -13
  97. package/dist/providers/native/measured-budget.js +2 -0
  98. package/dist/providers/native/supervisor.d.ts +25 -0
  99. package/dist/providers/native/supervisor.js +59 -7
  100. package/dist/remotes/identity.d.ts +1 -0
  101. package/dist/remotes/identity.js +6 -0
  102. package/dist/remotes/pinned-fetch.d.ts +2 -0
  103. package/dist/remotes/pinned-fetch.js +30 -1
  104. package/dist/remotes/types.d.ts +14 -0
  105. package/dist/runtime-lock.js +1 -1
  106. package/dist/system/amdgpu-adapters.d.ts +18 -0
  107. package/dist/system/amdgpu-adapters.js +55 -0
  108. package/dist/system/gpu-carve-out.d.ts +84 -0
  109. package/dist/system/gpu-carve-out.js +111 -0
  110. package/dist/system/memory.d.ts +17 -1
  111. package/dist/system/memory.js +105 -14
  112. package/dist/transformers-cache.d.ts +2 -0
  113. package/dist/transformers-cache.js +6 -3
  114. package/dist/utils/service-worker-entry.d.ts +4 -0
  115. package/dist/utils/service-worker-entry.js +4 -0
  116. package/package.json +14 -10
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@bendyline/gilde",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "kinds": [
5
5
  "chat-models",
6
6
  "knowledge-catalogs"
@@ -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 { createReadStream } from 'node:fs';
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
- for (const candidate of opts.candidates) {
57
- const startedAt = Date.now();
58
- try {
59
- await verifyCandidate({
60
- root: candidate,
61
- platform,
62
- entries: platformEntries,
63
- verifySignature: opts.verifySignature ?? verifyCodeSignature,
64
- allowStandaloneMacPayload: opts.allowStandaloneMacPayload === true,
65
- });
66
- return {
67
- reused: true,
68
- nativeBinDir: resolve(candidate),
69
- // The elapsed time is load-bearing diagnostics, not decoration: this
70
- // is pre-bind boot work whose cost scales with the payload, and on a
71
- // CUDA host it is the largest single read the daemon ever does at
72
- // startup. Without it in the log there is nothing to attribute a slow
73
- // launch to.
74
- reason: `verified ${opts.allowStandaloneMacPayload ? 'standalone' : 'Electron'} native release ${release} in ${((Date.now() - startedAt) / 1000).toFixed(1)}s`,
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
- catch (error) {
78
- failures.push(`${candidate}: ${error instanceof Error ? error.message : String(error)}`);
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
- return {
82
- reused: false,
83
- reason: failures.length > 0
84
- ? failures.join('; ')
85
- : 'no Electron native payload candidate was installed',
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 actualSha = await sha256File(absolute);
133
- if (actualSha !== pin.sha256.toLowerCase()) {
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; nothing here
207
- * mutates state, so an abandoned hash is inert.
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
- await worker(item);
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
- return new Promise((resolveHash, reject) => {
292
- const hash = createHash('sha256');
293
- const stream = createReadStream(path, { highWaterMark: HASH_READ_BUFFER_BYTES });
294
- stream.on('data', (chunk) => hash.update(chunk));
295
- stream.on('error', reject);
296
- stream.on('end', () => resolveHash(hash.digest('hex')));
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) {